Sunday, January 25, 2009

Why am I getting a SyntaxError when trying to start Apache after adding the PHP module?

This one was a killer to figure out and I can't believe I couldn't find the answer with Google. I just installed Apache 2.11 and PHP 5.2.8. Both worked independent of each other, but when I tried to add the PHP module to Apache, it wouldn't start. Specifically, I added:


LoadModule php5_module "c:/path/to/php/php5apache2.dll"
AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps


to the httpd.conf file. This was what every set of setup instructions said to do. When I originally tried to fire up Apache as service, it couldn't start. To get some information about the error I tried to start it from the command line. This is probably obvious to most, but you can of course run the web server from the command line. Something like:


>>> c:\Program Files\Apache\bin\httpd.exe


ought to do the trick. Obviously, if you install it elsewhere, replace the relevant parts of the path.

Anyway, I ran it from the command line and got the terrific error message:


Syntax error in line 128 of C:/Program Files/Apache/conf/httpd.conf: Cannot load c:/path/to/php/php5apache2.dll in to server: The specified module could not be found.


It's a little misleading that it says "SyntaxError" because I then spent about 15 minutes trying to figure out if I had a typo somewhere. When I finally started Googling around, I quickly realized the important part of the error message was the last sentence.

To make a long story short, I finally was tipped off by the fact someone mentioned using Apache 2.0.x instead of Apache 2.2 to solve the problem. Ah ha! If you look in the PHP installation directory, there is a suspicious DLL named php5apache2_2.dll. I switched my httpd.conf to use that one instead, and bingo. We're in business.

Like my last post, hopefully this helps someone else stuck in this situation. I had a feeling there might be something about different versions of Apache when I was downloading it and my options were 2.11 and 2.0.63. I didn't notice any discussion of what the big differences were on the Apache site, but apparently there are some. Feel free to leave a comment about the main differences between the versions if you know.

After I install Apache, why am I forbidden from hitting any page in my webroot?

Now there are a lot of possible answers to this question, but I searched Google for quite some time looking for answers. Everyone mentioned checking the permissions on the folder, which is good advice, you should start there. But I don't know much about permission and everything seemed to look okay and still nothing. Since I was migrating from IIS to Apache, I already had my webroot setup to work with IIS and that worked fine, so I was a bit at a loss for what was wrong. Well, the problem I had at least ended up being in the httpd.conf file. Of course I had changed my DocumentRoot to:

DocumentRoot "C:/www"

but it turns out there is another place in the httpd.conf where you need to update the webroot about 30 lines down. There was a nice comment above the code that said it needed to be changed, so this is yet another reason to at least skim through the configuration file. Anyway, I edited that line and voila, everything works. Here in the line I edited along with the comment above it:


#
# Note that from this point forward you must specifically allow
# particular features to be enabled - so if something's not working as
# you might expect, make sure that you have specifically enabled it
# below.
#

#
# This should be changed to whatever you set DocumentRoot to.
#
<Directory "C:/www">


Here's to hoping this saves someone some time and also a reminder to read through the configuration or ini files once. Yes, it's tedious and often boring, but you always learn something and it can keep you from wasting an hour like I did.

Also, if anyone is wondering, I'm running Apache 2.11. It's probably a good idea to search through the httpd.conf file for references to "htdocs" since that's where the default webroot is located in case future versions include references to your document root elsewhere.

Tuesday, January 6, 2009

War rooms!

APT wants their new recruits to pick up things fast. Very fast. A small company needs to keep progressing and there isn't too much time to get everyone up to speed.

So how does someone who has never even typed any html or “public static void main” cope with this situation when they join?

APT's solution = the war room. The “war room” is what we’ve termed a room filled with about 5 engineers at close quarters. While this may sound unappealing at first, it is actually awesome to have your team mates right by you. Nothing beats learning by example, and even learning by osmosis by overhearing conversations among other engineers.

Having ready and willing help literally right by your side rapidly accelerates the learning of new engineers as they never have to spin their wheels waiting for assistance when they get stuck. You can get someone to look at your code with a yell, a nudge or the highly effective throw of a stuffed gorilla (because most people listen to music at work and might not hear you yell).

It is a fun and exciting environment to learn in and if you like cooperation and team work, you'd much rather have this that a lot of space and your own private office. In fact, our Senior VP of Engineering leaves his own office empty so that he can join the rest of his team members in one of our war rooms.

Tuesday, December 16, 2008

Multi-part emails on the iPhone and Thunderbird

At APT, most of us read our email in Outlook. But many of us also have iPhones, and a few of the more adventurous folks (myself included) use Thunderbird out of the office. For the longest time, our software's emails would look just fine in Outlook but would be completely blank in other mail clients (notably iPhone and Thunderbird).

So it turns out that when you send a multi-part email, and one part is plain text and the other part is HTML, most clients will display whatever part occurs LAST.

When you use the <cfmail> tag with <cfmailpart>, and the only part you declare is HTML, ColdFusion "helpfully" adds another plain text mailpart for you. At the end. With no content. Thanks a lot, ColdFusion. Very helpful.

So, the "solution" is to explicitly declare an empty <cfmailpart type="text"> tag BEFORE the <cfmailpart type="html"> tag.

Thursday, October 23, 2008

Automatic Bug Filing

I like to automate things. This is welcome trait at APT, as rapidly developing software with an engineering team about 20 in size does not leave much time for manual testing. Out of necessity, we have built a fairly sophisticated automating testing framework, which has been critical in monitoring the integrity of our code. We have software that interacts with our product as if somebody was controlling it themselves. Along the way it checks for errors, or even worse, changed output numbers. The testing code that tells the software what to do is dynamically generated from an object-oriented state-based model abstracted in a database. This allows us to quickly create thousands of test cases that interact with our product in a variety of different ways. These test cases are prioritized and assigned to one of about a dozen automated testing machines which constantly execute them every minute of every day of every week and report on the results.

So there we have it: a distributed prioritized automated testing framework. What more could we want? Well, I found myself spending a lot of time examining the failed tests. If I determined that the problem encountered was not a known issue, I would file a bug a report with the relevant information. Otherwise, I would have to take note that we know about this issue and ignore that test until it is fixed. My coworkers were experiencing the same thing. As we scaled our framework to run more and more tests, we had no analogous expansion of our abilities to monitor and react to the result of these tests. This is where our affinity for automating things comes in. Why not automate responses to our automated tests? And this is exactly what we did.

Now when one of our automated tests hit an error, a check is done to see if it is a new error or not. If it is not a new error, we associate the test with it. This association helps us avoid wasting any more time on subsequent failures as well as logging which tests to use to determine if the problem has been fixed. If it is a new error, we automatically file a detailed bug report with an appropriate priority determined by characteristics of the test case and the error hit.

This automation was not without complexities; in fact we are still working out some kinks. First of all, it hinges on the ability to accurately determine if errors are new or not. Once that is done, you want to be able to filter out errors that are not relevant. Automatic bug filing is a fine line. File too few bugs and you still must spend time going over reports checking for things that may have been missed. File too many bugs and you have to go through them all and weed out the legitimate ones. However once the logic is tweaked the previously manual task of responding to the results of automated tests is now automated itself. The benefits include less time spent looking over reports, as well as zero lag time between the time a problem occurs and the time a bug report is filed, quickly bringing the issue to the attention of product engineers. And of course there is the good feeling you get when you’ve automated something that used to be done manually!

Monday, October 6, 2008

Starting Selenium Server in Java


For some of our automated tests we are switching to use the open-source project Selenium-RC. You can read more about it at its web site: http://selenium-rc.openqa.org/, but essentially it runs a java server which can control an internet browser, and then your testing code sends commands to this server. One key part of this setup is that you need the server running while your testing code is executing. For automated testing machines it would be no big deal to make the Selenium server a service; however developers probably don’t want it running all the time—in fact they do not want to think about it!



Thus our solution was to have our testing code launch the server. I’ve seen a number of posts on various forums asking how to start the selenium server form Java, but none of them had concrete answers. Thus I will reproduce our implementation for you to use and modify as you please:




Process p = null;

try {

    String[] cmd = {"java","-jar","C:\\<path to selenium>\\server\\selenium-server.jar" };

    p = Runtime.getRuntime().exec(cmd);

} catch (IOException e) {

    System.out.println("IOException caught: "+e.getMessage());

    e.printStackTrace();

}


System.out.println("Waiting for server...");

int sec = 0;

int timeout = 20;

boolean serverReady = false;

try {

    BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream()));

    while (sec < timeout && !serverReady) {

        while (input.ready()) {

            String line = input.readLine();

            System.out.println("From selenium: "+line);

            if (line.contains("Started HttpContext[/,/]")) {

                serverReady = true;

            }

    }

        Thread.sleep(1000);

         ++sec;

    }

    input.close();

} catch (Exception e) {

    System.out.println("Exception caught: "+e.getMessage());

}



if (!serverReady) {

    throw new RuntimeException("Selenium server not ready");

}



System.out.println("Done waiting");



Some notes on the above code:

  • I left in some handy print statements; however these are of course completely optional.

  • For non-automated testing machines, be sure to have your outer most try-catch block of your testing code kill the server or it may be left running even when the testing code finishes.

  • For code on automated testing machines, you may want to check to see if the server is running and start it only if it is not. This way you don’t waste time waiting for the server to be ready if another test already brought it up.


  • Friday, August 29, 2008

    SQL INSERTs

    The other day, we were confronted with an interesting SQL problem. A user wanted to be able to paste in a set of record IDs to be saved and made available for use throughout the rest of the product. For large sets of data (in the millions of rows), it makes sense for users to upload these files to us via FTP or even through the web server, then have the files loaded into the database. In these cases, users most likely already have the data readily available in some file format, so it’s easy for them to just upload it to us. But in the small to medium size cases (anywhere from hundreds to tens of thousands of records), the user could be pulling the data from an Excel spreadsheet or similar document so that it’s easier for him or her to simply paste the data into a text field of a form. For simplicity’s sake, we can assume that the data consists of a set of ID numbers that are carriage return delimited.

    So then, how do we persist these IDs to the database? Essentially we are given a ginormous string of delimited IDs that we want to dump into a table with a single column that is ID number, like so:


    CREATE TABLE records
    (
    recordID INT NOT NULL,
    CONSTRAINT PK_records PRIMARY KEY CLUSTERED (recordID)
    )


    In tackling this problem, we considered four different approaches. Our goal was to find the fastest approach (in terms of user wait time), since in many cases the user could be forced to wait several minutes for the upload to complete. As with many enterprise web applications, our DB resides on a different machine than our web server. So each approach’s performance is really driven by two factors:

  • Query Time – the total amount of time the DB spends processing the queries that insert IDs into the table we designate
  • NetworkTransfer Time – the total amount of time spent transferring requests from our web server to the DB

    The first approach we tried, which we’ll refer to as the Naïve Insert Loop, was to loop through the delimited IDs, inserting a single row into the database for each ID. Each query to insert a row was fired off as a separate DB request:


    INSERT INTO records (recordID)
    SELECT 12

    INSERT INTO records (recordID)
    SELECT 15

    INSERT INTO records (recordID)
    SELECT 17
    ...


    This approach is problematic for several reasons, all stemming from the fact that it creates an individual query per record ID and fires it off from the web app to the DB one at a time. Since each query and request has a certain overhead to it, this solution pays huge penalties for the large numbers of queries and requests used. We used this naïve approach as a baseline for which to improve upon.

    Recognizing that we needed to reduce the number of queries and requests fired, we then considered Improved Insert Loop, which was very similar to the Insert Loop except that we combined the INSERTs together via UNION ALLs before firing them off to the DB:


    INSERT INTO records (recordID)
    SELECT 12
    UNION ALL
    SELECT 15
    UNION ALL
    SELECT 17
    ...

    INSERT INTO records (recordID)
    SELECT 27
    UNION ALL
    SELECT 28
    UNION ALL
    SELECT 54
    ...


    We can combine these INSERTs together into batches of a thousand* SELECT statements UNION ALLed together, so we essentially have reduced the number of queries and network requests by a factor of thousand. The queries and requests are themselves approximately a thousand times larger than before. But we have improved net performance because by combining a thousand queries together, we don’t have to pay the overhead attached to all of the individual thousand queries and requests we would have run otherwise. For instance, by reducing the number of queries, we are reducing the number of DB transactions, and therefore we reduce the number of disk writes that happen on the DB since we are reducing the number of transaction log flushes.

    In both approaches above, we are forced to wrap the IDs in queries that insert them into the table on the DB. If we could somehow transmit the raw IDs to the DB and have them parsed and inserted completely on the DB side, we could greatly reduce the size of the data sent over the network and thus greatly reduce the Network Transfer Time.

    With that in mind, we came up with the Stored Procedure Loop approach. Essentially we would pass the entire string of delimited IDs as a TEXT field to a stored procedure, which would do the work of parsing the field and INSERTing the individual records into a target table. Below is the stored procedure definition. It starts by logging the entirety of the paste request into a dataStagingTable and parses the data logged in the table.


    CREATE TABLE dataStagingTable (
    logID int not null identity(1,1),
    data Text,
    pastingTime datetime
    )

    exec processPastedData 'wilfred', '2
    3
    4
    5', '
    ',','

    select * from wilfred

    CREATE PROCEDURE dbo.processPastedData
    @targetTable VARCHAR(32),
    @data TEXT,
    @rowDelimiter CHAR,
    @colDelimiter CHAR

    AS
    BEGIN
    DECLARE @logID INT
    DECLARE @dlen BIGINT
    DECLARE @offset INT
    DECLARE @linePtr INT
    DECLARE @buf varchar(4000)
    DECLARE @cols varchar(255)

    INSERT INTO dataStagingTable (data, pastingTime)
    SELECT @data, getDate()

    SELECT @logID = @@Identity
    SELECT @offset = 1

    SELECT @dlen = datalength(data)
    FROM dataStagingTable
    WHERE logID = @logID

    SELECT @cols = 'recordID'

    SET NOCOUNT ON

    WHILE (@offset > 0)
    BEGIN
    SELECT @linePtr = CHARINDEX(@rowDelimiter, SUBSTRING(data, @offset, 4000))
    FROM dataStagingTable
    WHERE logID = @logID

    if (@linePtr > 0)
    SELECT @buf = REPLACE (SUBSTRING(data, @offset , @linePtr-1), @colDelimiter, ''',''')
    FROM dataStagingTable
    WHERE logID = @logID
    else
    SELECT @buf = REPLACE (SUBSTRING(data, @offset , 8000), @colDelimiter, ''',''')
    FROM dataStagingTable
    WHERE logID = @logID

    SELECT @buf = REPLACE (@buf, char(13), '')

    EXEC ('INSERT INTO ' + @targetTable + ' (' + @cols + ') SELECT ' + @buf)

    SET @offset = @offset + @linePtr

    if (@linePtr = 0)
    BREAK
    END
    END


    This approach yielded a huge performance improvement, as we had essentially minimized the Network Transfer Time by minimizing the amount of data being transmitted. We could have further improved the Query Time by reducing the number of transactions via either explicit transaction blocks or by combining INSERTs into batches as we did with the Improved Loop Insert. But even by making these improvements, we still would have had approximately the same number of queries being run as we did for the Improved Loop Insert (although now they would be run within the stored procedure on the DB side).

    In order to further improve the Query Time, we finally arrived at a fourth approach, the BCP Insert. BCP is a utility included with SQL Server that loads data from a file into a DB table. In this case, we dump the delimited IDs into a text file, then invoke BCP on the new text file. To run the BCP utility, we had to make sure there was a way for the web server to transmit these files to the DB machine. After that, we could run the BCP utility as a console command:


    bcp ClientDB.dbo.records in dataDump.txt -S DBMachine -U userID -P password -f formatFile.fmt

    ------------

    ClientDB.dbowner.records = [DATABASE].[SCHEMA].[TABLE TO UPLOAD DATA TO]
    dataDump.txt = data file that contains pasted data
    -S DBMachine = name of server to connect to
    -U userID = tells bcp to use a specific userID to log into DB machine
    -P password = tells bcp the password to use with userID to log into DB machine
    -f formatFile.fmt = format file that tells bcp how to parse the data file and how to insert into records table


    Creating the format file for configuring BCP to parse the data correctly was also straightforward:


    8.0
    1
    1 SQLCHAR 0 50 "\r\n" 1 recordID SQL_Latin1_General_CP1_CI_AS

    ------------

    8.0 = SQL Version
    1 = number of columns

    Third row going left to right:
    1 = File field order
    SQLCHAR = Host file data type
    0 = Prefix length
    50 = Host file data length
    "\r\n" = line terminator
    1 = Server column order
    recordID = name of column we are uploading to
    SQL_Latin1_General_CP1_CI_AS = column collation type



    Like with the Stored Procedure Loop, the data sent is essentially just the raw data, but in this case, SQL loads all the data without having to run a whole slew of queries. Furthermore, SQL’s loading of this data is not logged, which results in much less overhead than the previous approaches. This final approach does require additional time to move data to and from the file system, but in sum it was still faster than the other approaches. It is important to note, however, that as the number of records being pasted decreases, the difference in performance between these approaches also decreases. In fact, if we are inserting fewer than a thousand records, it is actually faster to revert from BCP Insert to one of the more naïve solutions, since the overhead of the file dump begins to dominate the actual Query and Network Transfer Time.

    In conclusion, the BCP Insert solution was the fastest of the four. This is not unexpected, since these are the types of operations that BCP was designed to perform quickly. Another interesting build on these solutions would have been to incorporate a means of compression of the raw data before transmitting from web server to DB server. That said, we found this to be an interesting chance to experiment with a few different creative solutions.

    * The thousand here is selected for simplicity; we can continue to increase this number to maximize the benefit of these batch combinations. The cap on the batch size is dependent on the RDBMS you are using.
  •