Sistema para Gerenciamento de Academia de Ginástica

Esta é uma aplicação web que nasceu através de uma parceria entre mim (o único desenvolvedor) e o proprietário de uma academia de ginástica ( a qual eu frequento ). O sistema está em uso e o proprietário/usuário está muito satisfeito.

A aplicação valoriza a simplicidade e é super fácil de usar.

A figura a seguir mostra a home page de onde o usuário pode acessar as funcionalidades da aplicação. Clique nela para ver uma versão ampliada...

Página inicial da aplicação Fitness Center

A aplicação é composta por 5 módulos e possui Controle de Acesso baseado em cargos (Administrador, Professor, Usuário, etc).

Módulos
- Alunos
- Anamneses
- Medidas
- Pagamentos
- Relatórios ( baseados em gráficos e listas )

O sistema está disponível em Português do Brasil (pt-BR) e Inglês (en-US). Ele tem tudo pronto para o caso em que exista a necessidades de localizações adicionais. É completamente viável ter a aplicação em execução em qualquer outra cultura/idioma com pouco trabalho.

Estilos/cores/formatação e logotipo podem ser personalizados usando (CSS).

Tecnologias
A aplicação usa o estado da arte das tecnologias da pilha Microsoft, bem como bibliotecas de código aberto:

C#, .NET Framework 4, ASP.NET MVC 3, SQL Server Compact 4.1, CSS, jQuery 1.5.2, jQuery UI 1.8.12, jQuery Globalization 1.0, jQuery qTip2, modernizr 1.7, Microsoft MVC Ajax, Microsoft Chart Controls for. NET 4, WebGrid, Chirpy

Telas
Você pode ver screenshots da aplicação aqui.

Descrição
As seções subsequentes descrevem cada módulo e as ações que o usuário tem permissão para executar.

A lógica de negócios também é descrita ao longo do caminho se eu ver que ela se encaixa na descrição do módulo.

Não é minha intenção descrever cada regra de negócio da aplicação neste post. Este post é uma visão geral.

Alunos
Ações: Criar, Editar, Detalhes, Excluir
Este módulo trata de dados básicos do aluno como foto, nome e sobrenome, data de nascimento, endereço, sexo, telefone, e-mail, etc. 
Tudo no sistema está relacionado ao aluno. Isto significa que se um aluno é excluído, os dados de cada módulo que fazem referência aquele aluno também serão excluídos.
Alunos que estão em débito são destacados em vermelho na lista/grid de alunos.
Se o nome de um aluno é clicado na lista de alunos, o sistema leva o usuário à lista de pagamentos do respectivo aluno.

Anamneses
Ações: Criar, Editar, Detalhes, Excluir
Este módulo manipula as informações básicas sobre o histórico de saúde do aluno, tais como se o aluno é hipertenso, diabético, fumante, etc. Diferentes tipos de doenças também podem ser selecionados. Estas doenças são agrupadas em categorias, tais como doenças dos ossos e articulações, respiratórias e doenças da tireoide. O estilo da atividade física na qual um aluno se encaixa quando ele se matricula na academia também pode ser selecionado.

Medidas
Ações: Criar, Editar, Detalhes, Excluir
Este módulo trata das medidas das circunferências do corpo do aluno. Estas são as medidas que o sistema controla:
- Altura (m)
- Peso (kg)
- Gordura (%) 
- Pescoço (m)
- Tórax (m)
- Glúteos (m)
- Ombro (m)
- Abdome (m)
- Cintura (m)
- Braço Esquerdo (m)
- Braço Direito (m)
- Coxa Esquerda (m)
- Coxa direita (m)
- Panturrilha Esquerda (m)
- Panturrilha Direita (m)

O usuário é notificado através da lista de medidas se é hora de tomar novas medidas de um aluno. O intervalo de tempo entre as avaliações (medidas) pode ser configurado no sistema. Atualmente está definido como um intervalo de três meses.
A % de Gordura é calculada pelo sistema através de uma fórmula padrão (ver link). Se o usuário passar o mouse sobre o cabeçalho da coluna (%) Gordura na listagem de medidas ele verá uma tabela com valores padrão para % de gordura que inclui Essencial, Atleta, Fitness, Média e Obeso.

Pagamentos
Ações: Criar, Editar, Detalhes, Excluir
Este módulo é o coração do sistema, uma vez que lida com o que podemos chamar de a Raison d'être (razão de ser) de um sistema como este.
O sistema permite a criação de pagamentos para um determinado aluno.
O usuário define um valor de desconto (se necessário) e o campo total é calculado automaticamente. Depois disso uma data de pagamento deve ser selecionada. Há também um campo de observação no qual o usuário pode anotar alguma coisa importante relacionada ao pagamento.
O usuário sinaliza que o pagamento foi realizado marcando o campo pago.
Sempre que um pagamento é marcado como pago, o sistema gera um novo pagamento automaticamente (cópia do pagamento feito) para o mesmo aluno com a data de vencimento 1 mês à frente.

Relatórios
Este módulo gera 4 tipos de relatórios. São eles:

- Total de Pagamentos (gráfico)
O usuário seleciona uma data de início e uma data de fim para gerar um gráfico de colunas que agrupa os pagamentos por mês.

- Pagamentos em Atraso/Vencidos (grid/lista) 
O sistema gera automaticamente um relatório em formato de lista extraindo os dados dos pagamentos que têm uma data de vencimento inferior a uma data especificada. Por exemplo: pode-se conceder uma margem (em dias) para que o sistema considere que o aluno está em débito - 1 semana, 2 semanas, etc após a data do vencimento do pagamento. Isso pode ser configurado no sistema.

- % Alunos Ativos/Inativos (gráfico)
O sistema gera automaticamente um gráfico em formato de pizza que retrata muito bem essa informação.

- Medidas ao Longo do Tempo (gráfico)
O usuário seleciona um aluno e uma data de início e fim para gerar um belo gráfico de linhas que mostra o aumento/diminuição das medidas ao longo do tempo.

Por que comprar?
O principal benefício é que você terá um ponto central para gerenciar sua Academia de Ginástica. A partir dele você pode extrair informações valiosas dos dados. Isto te permite estar sempre um passo à frente para planejar seu próximo passo. Não haverá mais anotações em cadernos e com isso você deixará de perder dados importantes!

Instalação
A aplicação é um arquivo .zip (arquivo compactado) e possui todo o que é necessário para ser executada em um servidor web que tenha o .NET Framework 4 instalado.

Se precisar de ajuda adicional no que diz respeito à hospedagem web/instalação e configuração, eu também posso fazer isso por você. É só me avisar.

Há também a possibilidade de executar a aplicação em um servidor web local usando Microsoft WebMatrix por exemplo. É assim que instalei o sistema no computador do usuário. Fazendo dessa forma você não terá que pagar um provedor de hospedagem on-line. A única desvantagem neste caso é que você não será capaz de acessar a aplicação a partir de qualquer lugar.

Como comprar?
Basta clicar no botão abaixo para comprar esta app barata ( somente R$ 1.200,00 ) através do PayPal:


Depois de clicar no botão acima e concluir o processo de pagamento, escreva para mim e me informe sobre sua compra. Poderei, então, dar prosseguimento no processo de negociação entregando para você a aplicação através de e-mail. A aplicação está contida em um arquivo .zip pequeno ~4MB.

Software é um campo muito interessante porque você trabalha muito para construir uma aplicação, digamos meses, e o produto é apenas um pacote de alguns megabytes. Vai entender…

Direitos autorais
Este produto de software é protegido por direitos autorais.

Você pode ler a EULA aqui (em construção).

Suporte
Ficarei mais do que feliz em fornecer qualquer informação adicional que você precise para que a aplicação seja instalada e funcione em seu servidor.

Se você encontrar algum bug na aplicação eu prontamente irei corrigi-lo e disponibilizarei a aplicação atualizada para você.

Recursos adicionais
Se você precisar de qualquer outro recurso, seja ele um novo módulo ou relatório que não faz parte desta aplicação, apenas me informe e o considerarei para uma versão futura.

Software to automate website screenshot capture

I needed a software that allowed me to capture screenshots of a web application I developed. The software should do it automatically (batch capture). This way it’d save me a lot of time.

How do I used to do that?
I visited each web page I wanted to take a screenshot. It took me about 1 hour to finish the work.

I posted a question at Super User as always: Software to automate website screenshot capture and got an answer suggesting that I use a combination of a URL fetcher + Selenium capability to take screenshots.

Well, I tried Selenium (.NET bindings selenium-dotnet-2.0rc3.zip ) to test its screenshot capture feature but it doesn’t seem to fit the job because it doesn’t allow you to configure screenshot properties as size (height x width). Moreover it doesn’t work well with Ajax (requires you to write a lot of code to check for the existence of Ajax calls, etc). This kills a screenshot that needs everything in place (I mean every DOM object should be part of the screenshot). I tried the 3 drivers available: Internet Explorer, Firefox and Chrome. Screenshots taken with Internet Explorer driver were close to what I expected.

This is a sample code I used based on the code taken from here:

using System;
using System.Drawing.Imaging;
using System.Text;
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.IE;
using OpenQA.Selenium.Support.UI;

namespace SeleniumTest
{
    [TestFixture]
    public class SeleniumExample
    {
        private FirefoxDriver firefoxDriver;
    
        #region Setup

        [SetUp]
        public void Setup()
        {
            firefoxDriver = new FirefoxDriver();
        }

        #endregion

        #region Tests

        [Test]
        public void DisplayReport()
        {
            // Navigate
            firefoxDriver.Navigate().GoToUrl("http://localhost/FitnessCenter/Report/TotalPaymentsByPeriod");

            IWebElement startDate = firefoxDriver.FindElement(By.Name("StartDate"));
            
            startDate.Clear();
            startDate.SendKeys("January 2011");

            IWebElement generate = firefoxDriver.FindElement(By.Id("Generate"));
            generate.Click();

            var wait = new WebDriverWait(firefoxDriver, TimeSpan.FromSeconds(5));
            wait.Until(driver => driver.FindElement(By.Id("Map")));

            SaveScreenShot(firefoxDriver.Title);
        }

        /// <summary>
        /// Saves a screenshot of the current error page
        /// </summary>
        public void SaveScreenShot(string fileName)
        {
            // Get the screenshot
            Screenshot screenshot = firefoxDriver.GetScreenshot();

            // Build up our filename
            StringBuilder filename = new StringBuilder(fileName);
            filename.Append("-");
            filename.Append(DateTime.Now.ToString("yyyy-MM-dd HH_mm_ss"));
            filename.Append(".png");

            // Save the image
            screenshot.SaveAsFile(filename.ToString(), ImageFormat.Png);
        }

        #endregion

        #region TearDown

        [TearDown]
        public void FixtureTearDown()
        {
            if (firefoxDriver != null) firefoxDriver.Close();
        }

        #endregion
    }
}

Indeed, Selenium is powerful for what it does, that is, helping you automate browser interactions while you test your code. It even allows you to take a screenshot let’s say when something goes wrong (a test fail for example). That’s great and that’s what it does best. It’s a choice for every operating system since it’s an API that can be programmed against.

Paparazzi! beautiful application iconWhat I needed was something more specialized to take screenshots. A software that allows me to configure screenshot properties. The good news is that I managed to find such piece of software and it’s called Paparazzi! - a very suggestive name by the way. One drawback is that it’s only available for Mac OS. As one would expect, it uses Safari browser engine behind the curtains to capture the screenshots. Paparazzi! has minor bugs but it gets the job done. It doesn’t have documentation. I had a hard time trying to make it work. It has batch capture capability but no docs explaining how to do it. So I hope this post will shed some light…

The following lines describe what I did to achieve my objective with Paparazzi!:

1 - Created a list of URLs I’d like to take screenshots of. Like this (one URL per line):

http://192.168.1.106/FitnessCenter/Account/ChangeCulture?lang=en&returnUrl=%2FFitnessCenter%2F
http://192.168.1.106/FitnessCenter/Student
http://192.168.1.106/FitnessCenter/Student/Create
http://192.168.1.106/FitnessCenter/Student/Edit/79
http://192.168.1.106/FitnessCenter/Student/Details/79
http://192.168.1.106/FitnessCenter/Student/Delete/79
http://192.168.1.106/FitnessCenter/Anamnesis
http://192.168.1.106/FitnessCenter/Anamnesis/Create
http://192.168.1.106/FitnessCenter/Anamnesis/Edit/79
http://192.168.1.106/FitnessCenter/Anamnesis/Details/79

.
.
.

2 - Open Paparazzi! and click the Window menu => Batch capture (really difficult to find this option Disappointed smile ):

Paparazzi! difficult to find Batch Capture menu optionPicture 1 - Paparazzi! difficult to find Batch Capture menu option

3 - Drag and drop a text file .txt (the file that contains the URLs) to the Batch Capture window:

Paparazzi! Batch Capture window surfacePicture 2 - Paparazzi! Batch Capture window surface

Here is where I think I found a limitation and it’s by design. This should definitely not happen IMHO. If you try to add a file clicking on ( + button), Paparazzi won’t let you select a text file. The only way I got it working was selecting the .txt file and then drag and dropping the file to the Batch Capture window.

4 - Configure screenshot properties by clicking the list button (see mouse cursor above it):

Paparazzi! screenshot process basic configurationsPicture 3 - Paparazzi! screenshot process basic configurations

You can define the screenshot size. There are pre-defined values for standard screen resolutions. It allows you to define new presets.

You can also delay the capture to wait the page finish loading, etc.

There are a set of configurations available related to the batch capture functionality. To access these configurations, go to Paparazzi! menu and select Preferences:

Paparazzi! Preferences… menu optionPicture 4 - Paparazzi! Preferences… menu option

The first configuration worth mentioning the Default Filename Format available in the General section:

Paparazzi! General preferences sectionPicture 5 - Paparazzi! General preferences section

Above I’m defining this format:

%t = page title
%Y = year
%m = month
%d = day
%H = hour
%M = minute
%S = second

The example in the picture is pretty clear… Smile

Another set of configurations is available in the Batch capture section:

Paparazzi! Batch Capture preferences sectionPicture 6 - Paparazzi! Batch Capture preferences section

Here you can choose where to save the screenshots as well as the type of the images.

After configuring the batch capture session, it’s the gran finale time...

5 - Click the Play button, go take a coffee and relax while the computer does the job for you Fingers crossed.

Paparazzi! Batch Capture in actionPicture 7 - Paparazzi! Batch Capture in action

Hope you have found this post interesting and that it’s useful to help in documenting a little bit of this small but really powerful application.

Pointing up Now I get all the screenshots in less than 1 minute!

References
Paparazzi!
http://derailer.org/paparazzi/

Fitness Center management software ( web app )

Fitness Center Management System/Web Application
This is a web application that was born in a partnership between me (the only developer) and the owner of a Fitness Center that I happen to be a frequenter/student. It’s currently being used there and he (the owner/user) is really satisfied with it.

The application values simplicity and is super easy to use.

The following screenshot shows the home page from where the user can access the application functionalities. Click it to see a larger version…

Fitness Center Management System Home page

The app comprises 5 modules and has access control based on Roles (Administrator, User, etc).

Modules
- Students
- Anamneses
- Measurements
- Payments
- Reports (chart and grid based reports)

It’s available in Portuguese from Brazil (pt-BR) and English (en-US). It has everything in place in case of additional localization needs. It’s completely viable to have the app running in any other culture/language with little work.

Application styles/colors and logo can be customized using cascading style sheet (CSS).

Technologies
The app uses state of the art technologies from Microsoft stack as well as open source libraries:
C#, .NET Framework 4, ASP.NET MVC 3, SQL Server Compact 4.1, CSS, jQuery 1.5.2, jQuery UI 1.8.12, jQuery Globalization 1.0, jQuery qTip2, modernizr 1.7, Microsoft MVC Ajax, Microsoft Chart Controls for .NET 4, WebGrid, Chirpy

Screenshots
You can see screenshots of the application here.

Description
The subsequent sections describe each module and the actions the user is allowed to perform. Business logic is also described along the way if I see it fits. It’s not my intention to describe every business rule of the application in this post. It’s just an overview.

Students
Actions: Create, Edit, Details, Delete
This module handles basic student data as photo, first and last names, birth date, address, gender, phone, email, etc. 
Everything in the system is related to Student. This means that if a Student is deleted, every other module's data is deleted as well.
Students that are in debt are highlighted in red in the Students list/grid.
If the name of a Student is clicked in the list, the system takes the user to the Payments list of that respective Student.

Anamneses
Actions: Create, Edit, Details, Delete
This module handles basic information about the Student health history such as if the student is hypertense, diabetic, smoker, etc. Different types of diseases can also be selected. These diseases are grouped in categories such as Bones and Joints, Respiratory and Thyroid diseases. The style of physical activity a student currently fits in when he/she joins the Fitness Center can also be selected.

Measurements
Actions: Create, Edit, Details, Delete
This module handles measurements of a Student's body circumferences. These are the measurements the system keeps track:
- Height (m)
- Weight (kg)
- Fat (%)
- Neck (m)
- Thorax (m)
- Buttocks (m)
- Shoulder (m)
- Abdomen (m)
- Waist (m)
- Left Arm (m)
- Right Arm (m)
- Left Thigh (m)
- Right Thigh (m)
- Left Calf (m)
- Right Calf (m)

The user is notified through the measurements list if it's time to take new measurements for a Student. The time interval between measurements can be configured in the system. Currently this is set as a 3 month interval.
Fat (%) is calculated by the system. If the user hovers his mouse over the Fat (%) table header he/she can see a table with standard Fat (%) values that include Essential, Athletes, Fitness, Average and Obese.

Payments
Actions: Create, Edit, Details, Delete
This module is the heart of the system since it handles what we can call the Raison d'être of a system like this.
The system allows the creation of payments for a given Student. The user defines a value, discount (if necessary) and the Total field is automatically calculated. After that a Due date must be selected. There’s also an observation field in which the user can annotate anything important related to that payment. The user signals that a payment was made by checking the Paid field.
Whenever a payment is marked paid, the system automatically generates a new payment (copy of the payment being made) for that same Student with a due date 1 month ahead.

Reports
This module generates 4 types of reports. They are:

- Total Payments ( chart )
The user selects a Start and End date to generate a column chart that groups the payments by month.

- Overdue Payments ( grid/listing ) 
The system automatically generates a grid/listing report extracting data for payments that have a due date less than a specified date. This can be configured in the system.

- % of Active/Inactive Students ( chart )
The system automatically generates a pie chart that depicts very well this information.

- Measurements Over Time ( chart )
The user selects a Student and a Start and End date to generate a beautiful line chart that shows measurements increase/decrease over time.

Why to buy?
I think the main benefit is that you’ll have a centralized point to manage your Fitness Center. From this you can extract valued information from data and be always one step ahead to plan your next endeavor. No more notebook entries and missing important data!

Installation
The app is self-contained in a .zip file that has everything it needs to run on a webserver that has the .NET Framework 4 installed.

If you need additional assistance regarding installation/web server hosting and configuration, I can also do that for you. Just let me know.

There's also the possibility of running the app in a local webserver by using Microsoft WebMatrix for example. This is how I installed it in the user's computer. Doing this way you won't need to pay an online hosting provider. The only downside here is that you won't be able to access the app on the go.

How to buy?
Just click the following button to buy this cheap app ( only $ 600.00 ) through PayPal:

After clicking the button above and completing the payment process, write to me and let me know about the purchase. I can then go further in the negotiation process and handle you the app through e-mail since it’s a small .zip package ~4MB.

Software is a really interesting field because you put a lot of work to construct it, let’s say months and the product is just a package of only a few megabytes. Go figure.

Support
I'll be more than happy in providing any further information you need to get the app up and running on your server.

If you find any bug in the app I'll promptly correct it and send the patched app to you.

Additional features
If you need any other feature, be it a new module or report that is not part of this app, just let me know and I'll consider it for a future release.

Replacing Web.config settings with Transforms

First off: this is the 100th post I write in this blog. A great amount of shared info… Party smile

Now let’s say you want to point to a different connection string when you deploy your ASP.NET Web Project to your hosting provider. Until recently you’d have to modify your Web.config file manually. This is an easy procedure but you might end screwing up the file in some way.

Visual Studio 2010 comes with a great new feature called Web.config Transformation that allows you to perform transformations in whatever section of your Web.config file. This transformation process happens automatically when you build the deployment package for your application. Transformations occur as part of the MSBuild process. With this it’s easy to change the connection string (or better yet, anything you want) depending on the configuration currently selected when you build the deployment package. No more Web.config manual editing. Thanks God!

This post shows a simple replace transformation in which I replace one of my app settings named Connection.

This is the actual code of my Web.config file:

<appSettings>
<!-- Database connection pointer -->
<add key="Connection" value="MyProject.Web.Settings.local" />
</appSettings>
<connectionStrings>
<
add name="MyProject.Web.Settings.local" connectionString="Server=localhost\sqlexpress;Database=MyDatabase;Integrated Security=True;" providerName="System.Data.SqlClient" />
<
add name="MyProject.Web.Settings.hostingProvider" connectionString="Server=123.45.678.9;Database=MyDatabase;UID=user;PWD=pass123;MultipleActiveResultSets=true;Asynchronous Processing=True;" providerName="System.Data.SqlClient" />
</connectionStrings>

Step 1 - Create a new Configuration named Test:

Visual Studio Configuration Manager (menu Build => Configuration Manager)Figure 1 - Visual Studio Configuration Manager (menu Build => Configuration Manager)

Creating a New ConfigurationFigure 2 - Creating a New Configuration

New Solution Configuration named TestFigure 3 - New Solution Configuration named Test

Step 2 - Right-click your project’s Web.config file and select Add Config Transforms:

Adding Config Transforms for the Web.config fileFigure 4 - Adding Config Transforms for the Web.config file

Now you’ll see that VS creates one Web.config file for each configuration we have defined. Now we have 3 child .config files/transforms. In this case Web.Debug.config and Web.Release.config are related to the standard configurations that are automatically created with new web projects. Web.Test.config is related to the Test configuration we created in Step 1.

Web.config transforms backing filesFigure 5 - Web.config Transforms backing files

Step 3 - Double click Web.Test.config and add this code:

<?xml version="1.0"?>

<!-- For more information on using web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 -->

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <appSettings>

        <!-- Database connection pointer -->
        <add key="Connection" value="MyProject.Web.Settings.hostingProvider" xdt:Transform="Replace" xdt:Locator="Match(key)" />

    </appSettings>

</configuration>

Above I’m transforming the Web.config file so that when I build my deployment package for the Test configuration I have the Connection appsetting replaced. My Connection appsetting will then point to the hostingProvider connection string. Take a special look in the transform I’m using (Replace) and the Locator to match the key Connection. For a more detailed explanation about the available transforms, read the post Web Deployment: Web.Config Transformation by Vishal Joshi.

Step 4 - Make sure you have selected the Test configuration in Visual Studio. Now Create the deployment package by right-clicking your ASP.NET Web Project and select Build Deployment Package:

ASP.NET Web Project’s Build Deployment Package context menu optionFigure 6 - ASP.NET Web Project’s Build Deployment Package context menu option

When you do this Visual Studio 2010 will generate a package with all the necessary files your app needs to run. My package is created in this path:

C:\MyProject\trunk\MyProject\MyProject.Web\obj\Test\Package

Inside this folder you’ll find a .zip file named MyProject.Web.zip (the package) that you can use to install the app in IIS or you can handle it to the one responsible for actually installing the app on the server.

Note: Visual Studio 2010 comes with other great feature that allows you to Publish this package directly to your hosting provider using Web Deploy, FTP, etc. I’ll cover this in another post…

You’ll also find a folder named PackageTmp. This folder has everything that goes into that .zip package. Open the Web.config file that lies within this folder and see if the transformation was applied correctly. It should’ve been applied.

In my case in my main Web.config file I’m pointing to a local connection string:

<add key="Connection" value="MyProject.Web.Settings.local" />

This makes sense since I’m working in my home machine. The transformation I'm applying should transform/replace the Connection appsetting when I build the deployment package for the Test configuration. Now the appsetting must point to the hosting provider connection string like this:

<add key="Connection" value="MyProject.Web.Settings.hostingProvider" />

Hope this simple sample helps shed some light in this really powerful and interesting feature that comes with Visual Studio 2010.

Note: if you wanna get direct access to the original and transformed Web.config files, check these folders:

C:\MyProject\trunk\MyProject\MyProject.Web\obj\Test\TransformWebConfig\original

C:\MyProject\trunk\MyProject\MyProject.Web\obj\Test\TransformWebConfig\transformed

Test transforms online
http://webconfigtransformationtester.apphb.com/

Productivity and happiness going from 4 to 8 GB RAM

This post tries to illustrate the productivity improvements you get when you go from 4 GB to 8 GB RAM.

I bought a Mac mini last year and when I started using it to do software development, data started to be processed very very slow… I ordered mini with “just” 4 GB RAM. My assumption that I’d develop only Mac software wasn’t true because now I know that I can’t live without Windows development. This way I have to use a virtual machine/VM (Windows 7) to install Visual Studio, database servers and everything else. That requires a lot of memory as you may know already.

Situation before and after memory upgrade

Mac OS Win VM Comment Feeling
Before 3 GB 1 GB This is crazy. I know. Necessity rules. Steaming mad
After 4 GB 4GB This is what a software developer needs today. Open-mouthed smile

There’s nothing better than images to illustrate my situation before and after the memory upgrade. Pictures can tell thousands words… Seeing is believing!

Before memory upgrade

Mac OS Activity Monitor  ( before memory upgrade ) Screenshot 1 - Mac OS Activity Monitor  ( before memory upgrade )

Parallels Desktop with Windows 7 virtual machine - Process Explorer ( before memory upgrade ) Screenshot 2 - Parallels Desktop with Windows 7 virtual machine
Process Explorer ( before memory upgrade )

Parallels Desktop with Windows 7 virtual machine - System Information ( before memory upgrade ) Screenshot 3 - Parallels Desktop with Windows 7 virtual machine
System Information ( before memory upgrade )

After memory upgrade

Mac OS Activity Monitor ( after memory upgrade ) Screenshot 4 - Mac OS Activity Monitor ( after memory upgrade )

Parallels Desktop with Windows 7 virtual machine - Process Explorer ( after memory upgrade ) Screenshot 5 - Parallels Desktop with Windows 7 virtual machine
Process Explorer ( after memory upgrade )

Parallels Desktop with Windows 7 virtual machine - System Information ( after memory upgrade ) Screenshot 6 - Parallels Desktop with Windows 7 virtual machine
System Information ( after memory upgrade )

Analysis and Points to take into account
1 - In both Activity Monitors (Screenshot 1 and Screenshot 4) you can see in the pie chart that the sum of memory (3.75 GB and 7.75 GB) is always 250 MB bellow the total installed memory. This is because Mac mini shares its RAM with the video card.

2 - I do not turn off the computer, that is, I put the computer to Sleep (Mac OS term for what we call Hibernate in Windows). When the computer comes back from sleep its memory isn’t completely empty. These screenshots were taken while the computer was in the process of sleeping/waking for a few days and hence the values of Wired, Active, Inactive memory tend to stay high even after waking the computer.

3 - The number of open apps is for sure different but I can guarantee that I have much more apps open after the upgrade, for example: two Visual Studios (desenv.exe) in Screenshot 5 while I had only 1 VS opened in Screenshot 2. To make it even more clear we just have to compare the Totals section of Win 7 System Information. Before I had 53 processes and after 75 (Screenshot 3 and Screenshot 6). An increase of 41.50%.

4 - Despite the values for Page outs and Swap used, after the upgrade I change screens very very fast. So these values do not scary me anymore.

5 - After upgrading Parallels Desktop 6 to its latest release (Build 6.0.12090 / Revision 660720; May 26, 2011) I noted a better memory management regarding the virtual machine. Now Page outs and Swap used are in MBs as shown in Screenshot 7:

Mac OS Activity Monitor after Parallels Desktop upgradeScreenshot 7 - Mac OS Activity Monitor after Parallels Desktop upgrade

Conclusion
Comparing the data shown above, it’s clear that RAM upgrade plays a big role when it comes to developer productivity and happiness.

Now I can say that I have a responsive computer and that means a lot in my day to day work.

Developer: get 8 GB RAM if you can afford it. It’s the bare minimum in 2011. You’ll be happy. No stress… If you can afford and your hardware supports 16 GB RAM, go for it and you’ll have no RAM headaches for a long long period.

Manager or whoever pays the bill: give a descent computer to your developers with at least 8 GB RAM! No excuses… RAM is so cheap nowadays that I don’t understand how can a lot of developers out there work with less than the necessary amount of RAM.

Note 1: Windows processes and memory information come from Process Explorer by Sysinternals. It’s way better and more powerful than Windows default Task Manager.

Note 2: To better understand memory information in Mac OS, refer to this article by Apple: Mac OS X: Reading system memory usage in Activity Monitor

Generating code with Preprocessed T4 Text Templates

If you wanna be a T4 ninja, read all this: T4: Text Template Transformation Toolkit

Last Friday I was writing some code to populate a database with country/state information. I wrote the code to include Brazil and its 27 states but when I realized that I’d have to do the same thing again with USA and its states I thought that it’d be boring. US has 50 states and creating an object for each state would be a pain.

I have to populate the Name and Abbreviation properties of each State object. Each state is created this way in code:

new State()
    {
        Name = "Alabama",
        Abbreviation = "AL",
        Country = country2
    },

This sounded like a good a chance to try T4 Templates (Wikipedia article). T4 stands for Text Template Transformation Toolkit. That’s a beautiful name IMHO. I’ve read some articles about it since its inception but haven’t had a chance to try it. T4 can generate any text you want for whatever programming language.

T4 is used within Microsoft in ASP.NET MVC for the creation of the views and controllers, ADO.NET Entity Framework for entity generation, and ASP.NET Dynamic Data. Really powerful tool!

T4 came to my mind because I answered a question at StackOverflow 4 days ago: Is T4 template run every time it is requested? This proves that StackOverflow is a great tool to keep things fresh in your mind. The asker is creating e-mails with T4. I can imagine the creation of e-mail messages in a way that you pass a list of e-mail addresses an then for each e-mail address, T4 generates a .msg message. Just thinking..

So to start with T4 I created a simple Console Application as always and followed the steps described in this excellent MSDN step by step: Run-Time Text Generation by using Preprocessed T4 Text Templates.

To generate code to create each State object (50 in total) I created the following files within a Visual Studio Solution titled T4CodeGeneration:

T4 Code Generation Project in Solution Explorer viewFigure 1 - T4 Code Generation Project in Solution Explorer view

States’ information is stored within the USAStates.txt file that I got on the internet. This file have StateName, StateAbbreviation in each line. It’s a really basic CSV file…

Alabama,AL
Alaska,AK
Arizona,AZ
Arkansas,AR
California,CA
.
.
.

To read this simple .txt file I wrote this code (Program.cs):

class Program
{
    static void Main(string[] args)
    {
        States code = new States(ReadData());
        String result = code.TransformText();
        File.WriteAllText("outputCode.cs", result);
    }

    public static List<Tuple<string, string>> ReadData()
    {
        List<Tuple<string, string>> states = new List<Tuple<string, string>>();

        using (var myCsvFile =
                new TextFieldParser(@"C:\Users\Leniel\Documents\Visual Studio 2010\Projects\T4CodeGeneration\USAStates.txt"))
        {
            myCsvFile.TextFieldType = FieldType.Delimited;
            myCsvFile.SetDelimiters(",");

            while (!myCsvFile.EndOfData)
            {
                string[] fieldArray;

                try
                {
// Reading line data fieldArray = myCsvFile.ReadFields(); } catch (MalformedLineException) { // Not a valid delimited line - log, terminate, or ignore continue; } // Process values in fieldArray states.Add(new Tuple<string, string>(fieldArray[0], fieldArray[1])); } } return states; } }

I’m using the built in TextFieldParser class in the ReadData() method that’s part of the Microsoft.VisualBasic namespace. This class is used to read the CSV file. Take a look at the project’s references above. This class has everything one needs to read a simple text file.

I then read and add each line of the file to the states List<Tuple<string, string>>.

I use this states list within the T4 template to generate my custom code.

This is the content of the file (StatesCode.cs):

partial class States
{
    private readonly List<Tuple<string, string>> states;

    public States(List<Tuple<string, string>> states)
    {
        this.states = states;
    }
}

Interesting thing to note here is that it’s a partial class named States (this is the same name of the T4 template). During compilation this class code will be merged with the T4 accompanying States.cs file code (generated automatically by Visual Studio code generator). That’s why it’s a partial class… This class is used exclusively to pass data to the T4 template.

Now comes the really interesting part that lies within the States.tt file:

<#@ template language="C#" #>
var usaStates = new List<State>
{
<# foreach (Tuple<string, string> tuple in states) 
// states is declared in StatesCode.cs
{ #>
    new State()
    {
        Name = "<#= tuple.Item1 #>",
        Abbreviation = "<#= tuple.Item2 #>",
        Country = country2
    },
<# } // end of foreach #>
};

usaStates.ForEach(s => context.States.Add(s));

As you can see, the T4 template code has its own syntax and structures. C# code goes within <#  #> code blocks. To actually get a variable value you must place it within
<#=  #> code blocks. I’m using a foreach that traverses the states list. See that I’m accessing the Tuple<string, string> items (Item1 and Item2) within these code blocks.

Everything that lies outside <# #> is just static text and will be output as such in the generated result (code output). This is the case with the first two and last lines of T4 template code above.

Really important thing: check that you have set the T4 template CustomTool property appropriately to TextTemplatingFilePreprocessor as this is a critical part to make things work as expected:

T4 Template and Custom Tool Property configurationFigure 2 - T4 Template and Custom Tool Property configuration

Now to end result: when I run the project, the Main method (part of Program.cs above) is called:

static void Main(string[] args)
{
    States code = new States(ReadData());
    String result = code.TransformText();
    File.WriteAllText("outputCode.cs", result);
}

I read the data and pass it to the T4 template. I then call T4 TransformText() method that does the magic! Last but not least, the resulting code is written to a file called outputCode.cs.

I get a beautiful code that is placed in the Project’s Debug folder: C:\Users\Leniel\Documents\Visual Studio 2010\Projects\T4CodeGeneration\bin\Debug.

To accomplish my objective I just Copy/Paste this file code in my SampleData.cs class that I use in other project to seed my database.

As this is a pretty extensive code listing (that I’d have to type – oh my God!), I’ve chosen to let it for the final part of the post for your viewing pleasure. Open-mouthed smile See that the code is well formatted. To get code formatted you have to fight with your T4 template file.

Now that you have a basic overview of T4 I hope you have fun with it as I had. This is power in your hands. Now it’s up to you to give wings to your imagination!

Visual Studio 2010 Console Application
http://sites.google.com/site/leniel/blog/T4CodeGeneration.zip

Code output:

var usaStates = new List<State>
{
    new State()
    {
        Name = "Alabama",
        Abbreviation = "AL",
        Country = country2
    },
    new State()
    {
        Name = "Alaska",
        Abbreviation = "AK",
        Country = country2
    },
    new State()
    {
        Name = "Arizona",
        Abbreviation = "AZ",
        Country = country2
    },
    new State()
    {
        Name = "Arkansas",
        Abbreviation = "AR",
        Country = country2
    },
    new State()
    {
        Name = "California",
        Abbreviation = "CA",
        Country = country2
    },
    new State()
    {
        Name = "Colorado",
        Abbreviation = "CO",
        Country = country2
    },
    new State()
    {
        Name = "Connecticut",
        Abbreviation = "CT",
        Country = country2
    },
    new State()
    {
        Name = "Delaware",
        Abbreviation = "DE",
        Country = country2
    },
    new State()
    {
        Name = "Florida",
        Abbreviation = "FL",
        Country = country2
    },
    new State()
    {
        Name = "Georgia",
        Abbreviation = "GA",
        Country = country2
    },
    new State()
    {
        Name = "Hawaii",
        Abbreviation = "HI",
        Country = country2
    },
    new State()
    {
        Name = "Idaho",
        Abbreviation = "ID",
        Country = country2
    },
    new State()
    {
        Name = "Illinois",
        Abbreviation = "IL",
        Country = country2
    },
    new State()
    {
        Name = "Indiana",
        Abbreviation = "IN",
        Country = country2
    },
    new State()
    {
        Name = "Iowa",
        Abbreviation = "IA",
        Country = country2
    },
    new State()
    {
        Name = "Kansas",
        Abbreviation = "KS",
        Country = country2
    },
    new State()
    {
        Name = "Kentucky",
        Abbreviation = "KY",
        Country = country2
    },
    new State()
    {
        Name = "Louisiana",
        Abbreviation = "LA",
        Country = country2
    },
    new State()
    {
        Name = "Maine",
        Abbreviation = "ME",
        Country = country2
    },
    new State()
    {
        Name = "Maryland",
        Abbreviation = "MD",
        Country = country2
    },
    new State()
    {
        Name = "Massachusetts",
        Abbreviation = "MA",
        Country = country2
    },
    new State()
    {
        Name = "Michigan",
        Abbreviation = "MI",
        Country = country2
    },
    new State()
    {
        Name = "Minnesota",
        Abbreviation = "MN",
        Country = country2
    },
    new State()
    {
        Name = "Mississippi",
        Abbreviation = "MS",
        Country = country2
    },
    new State()
    {
        Name = "Missouri",
        Abbreviation = "MO",
        Country = country2
    },
    new State()
    {
        Name = "Montana",
        Abbreviation = "MT",
        Country = country2
    },
    new State()
    {
        Name = "Nebraska",
        Abbreviation = "NE",
        Country = country2
    },
    new State()
    {
        Name = "Nevada",
        Abbreviation = "NV",
        Country = country2
    },
    new State()
    {
        Name = "New Hampshire",
        Abbreviation = "NH",
        Country = country2
    },
    new State()
    {
        Name = "New Jersey",
        Abbreviation = "NJ",
        Country = country2
    },
    new State()
    {
        Name = "New Mexico",
        Abbreviation = "NM",
        Country = country2
    },
    new State()
    {
        Name = "New York",
        Abbreviation = "NY",
        Country = country2
    },
    new State()
    {
        Name = "North Carolina",
        Abbreviation = "NC",
        Country = country2
    },
    new State()
    {
        Name = "North Dakota",
        Abbreviation = "ND",
        Country = country2
    },
    new State()
    {
        Name = "Ohio",
        Abbreviation = "OH",
        Country = country2
    },
    new State()
    {
        Name = "Oklahoma",
        Abbreviation = "OK",
        Country = country2
    },
    new State()
    {
        Name = "Oregon",
        Abbreviation = "OR",
        Country = country2
    },
    new State()
    {
        Name = "Pennsylvania",
        Abbreviation = "PA",
        Country = country2
    },
    new State()
    {
        Name = "Rhode Island",
        Abbreviation = "RI",
        Country = country2
    },
    new State()
    {
        Name = "South Carolina",
        Abbreviation = "SC",
        Country = country2
    },
    new State()
    {
        Name = "South Dakota",
        Abbreviation = "SD",
        Country = country2
    },
    new State()
    {
        Name = "Tennessee",
        Abbreviation = "TN",
        Country = country2
    },
    new State()
    {
        Name = "Texas",
        Abbreviation = "TX",
        Country = country2
    },
    new State()
    {
        Name = "Utah",
        Abbreviation = "UT",
        Country = country2
    },
    new State()
    {
        Name = "Vermont",
        Abbreviation = "VT",
        Country = country2
    },
    new State()
    {
        Name = "Virginia",
        Abbreviation = "VA",
        Country = country2
    },
    new State()
    {
        Name = "Washington",
        Abbreviation = "WA",
        Country = country2
    },
    new State()
    {
        Name = "West Virginia",
        Abbreviation = "WV",
        Country = country2
    },
    new State()
    {
        Name = "Wisconsin",
        Abbreviation = "WI",
        Country = country2
    },
    new State()
    {
        Name = "Wyoming",
        Abbreviation = "WY",
        Country = country2
    },
};

usaStates.ForEach(s => context.States.Add(s));