Recently for a project at work, I was tasked with doing research into load testing software. Now, there are tons of options for load testing; in my search I found some nice-looking hosted solutions, such as browsermob.com. But of course, if you can get away with doing something for free, that's usually the option that makes the most business sense (that is, if the time you invest in learning the free tool is less than the money you would save by using the easier solution). The free solution in this case is JMeter, a Java-based program maintained by the Apache Foundation. Since I gained a basic understanding of the software through my recent research, I thought I'd share a bit here on how to create a basic load testing script. JMeter has many more options that what I'll detail here, but hopefully this tutorial will at least get you over the initial learning curve.
Obviously, first of all you should download JMeter. Unzip the downloaded package, and place the whole folder structure somewhere easy to remember. If you want, you can set the /bin directory on your path so you can start the program from the command line. I just double-click the ApacheJMeter.jar package to start the program.
After JMeter starts up, you'll be faced with a totally blank slate. What we want to do is create a group of users, and have each of those users perform a certain sequence of actions (such as access a URL, post a form, or whatever). There are two ways to do this with JMeter: enter the sequence by hand, or configure a proxy server to intercept your browser requests and save them as an action sequence. If the list of actions you want to perform is complex, it might be a good idea to set up a proxy. I'll show you how to manually set up your test; running the proxy server will be reserved for a future post.
First, let's rename the test plan to be something more descriptive. Click on the "Test Plan" text in the project sidebar, and rename it to something descriptive, such as "My Jawesome Test Plan." Now, the first step in creating your test scenario is to make a group of users. Right click on your test plan, then select Add > Threads (Users) > Thread Group. "Thread Group" is kind of a confusing term, so you can rename the group to "Users" if you want. When you click on your user group in the sidebar, you'll see there are various options you can change about it, the most significant being the number of users, how fast they start performing their tasks, and whether they repeat their tasks. Change the number of users to 10, and the ramp-up period to 10 as well. That means that it'll take 10 seconds for all the users to be activated. Since there are 10 total users, that means that one will start up each second. Change the loop count to 2, so that each user runs through their tasks twice.
Next, we'll add some configuration options to the test, so that you don't have to enter certain info each time you want to add an action for the user group to perform (such as the base site URL). Right-click your user group and select Add > Config Element > HTTP Cache Manager. This simulates a browser cache for each of your users, so if they download a static resource on the first test run, they'll have a cached version for the second, making the test more realistic. Select the user group again, right-click, and choose Add > Config Element > HTTP Request Defaults. You can put in any default options you want here, such as the site name, port number, and path. So for example, if you were testing your site foo.com, you could put in "http://foo.com" as the server name. Then all subsequent requests from your users would use that information. If a user requested plain ol' "bar.html," the defaults would be prepended to that request, resulting in a request to "foo.com/bar.html." When creating a test by hand, the Request Defaults configuration becomes very useful. For our example, let's query the JMeter site, so in the HTTP Request Defaults options, enter "http://jakarta.apache.org" as the server name.
OK, we've got the test users all set up. Next, let's have them try to make some requests. Right click on your user group in the sidebar, and select Add > Sampler > HTTP Request. This represents a request for a single page or resource on your site. Let's have it request the JMeter homepage. If you remember, we already set up the default site URL and path, so the only option we have to change for the sampler is the path value, which we'll set to "/jmeter/index.html" (for the homepage). Also, change the name of the HTTP Request to "JMeter Homepage" so that it's easier to see what it's requesting.
At this point, you've got enough set up to actually run the test and see real results, but let's go ahead and add another HTTP Request to the test. Right-click your user group, add another HTTP Request, and set the path to "/jmeter/usermanual/index.html." Rename this request to "JMeter User Manual." Now the test is completely set up, but there's no way to view the test results yet. To remedy this, we'll add two "listeners," which track results and display them in a human-readable way. Right-click your user group, and select Add > Listener > Graph Results. Also Add > Listener > Summary Report. These are two simple listeners that show your results in a graph and text-based summary.
Now, go ahead and run the test by choosing Run > Start (or Command-R). Wait for a bit for the test to finish, then click on your results listeners. You should see the (hopefully successful) results of your test. Now for some embellishments. In a more realistic scenario, users would load a page, read it for a few seconds, then click a link to go to a different page. In the test script right now, your users make requests as fast as they can. To insert a random delay between requests, right click each HTTP Request in the sidebar, and choose Add > Timer > Uniform Random Timer. This timer allows you to set a base constant delay (such as 2 seconds), and then add another random delay on top of that. I like to have a 2 second constant delay and a 3 second random delay, so go ahead and edit both timers to have values of 3000 (random) and 2000 (constant). Also, ideally we want to make sure that the results of each request were successful. There are a number of ways to do this in JMeter, but the simplest is to make an assertion that each request returned a HTTP status code of 200. Right-click each of the HTTP Requests and choose Add > Assertions > Response Assertion. Modify each assertion to check the Response Headers, choose "Equals" for the Pattern Matching Rules, and add "200" as a Pattern to Test. The Response Assertion is very powerful... you can modify it to check for patterns in the HTML of the requested page, etc., but this basic assertion is enough for our needs right now.
And that's it! You now have a fully functional basic test plan. You can run it and see a cool graph of response times as well as get the summary report for the whole test. Hopefully that will get you started with the wonderful world of load testing.
One of the building blocks of a cocos2d app is the "scene" object. In any game, you'll have multiple scenes that help break apart your code into manageable chunks. Each scene contains discrete bits of game logic; for example, your project could consist of a title scene, game options scene, and gameplay scene. Each scene, in turn, contains one or more "layers." A layer is where you will actually do most of your programming to make stuff happen. For example, your "GameplayScene" might have two layers: an "ActionLayer" and a "HUDLayer." Both layers can be (obviously) stacked on top of each other, so that the HUD always appears over the gameplay. The default cocos2d template project creates a file called HelloWorldScene.m that automatically creates a scene and then adds a layer to it. You can basically copy this method if you find that you only need one layer for each scene (to be honest, for most games, you'll probably only need one layer per scene).
To navigate through your game's UI, you'll make calls to the cocos2d Director singleton to replace the currently active scene; something like [[CCDirector sharedDirector] replaceScene:[MyAwesomeScene node]]; Most likely this call would be in a method that is run in response to a button being tapped, or after a level is completed so the player can return to a level select screen. You can also switch your scenes using some nifty transition effects that are built in to cocos2d. These are called like so: [[CCDirector sharedDirector] replaceScene:[CCFlipXTransition transitionWithDuration:0.75 scene:[MyAwesomeScene node]]]; You can see a list of transitions by looking at the "CCTransition.h" file in the "cocos2d Sources/cocos2d" folder in your Xcode project.
As an example, I'll extend the default cocos2d template with an additional scene that I created, and then tie everything together with buttons that transition from one scene to the other. First off, create a new Xcode project and have it use the cocos2d template. I called my project "SceneExample." Build and run this project and you'll see the normal Hello World! text appear. So far, so good. Now let's add a new scene to the project. In the Groups & Files gutter, right-click on the Classes group, and select the Add > New File... option. In the window that pops up, choose to have your new class extend the CCNode class (under User Templates), and name the new file "MyAwesomeScene.m" (or whatever you want, really). Click Finish to add the new class files to your project.
Next, select the MyAwesomeScene.h header file, and make a few changes:
#import "cocos2d.h"
@interface MyAwesomeScene : CCScene {}
@end
// Extending CCColorLayer so that we can specify a
// default background color other than black
@interface MyAwesomeLayer : CCColorLayer {}
@end
The template we used automatically made MyAwesomeScene extend CCNode, but we actually want it to extend CCScene. In addition, I'm defining the new layer that I want to add to my scene here. This is basically just for convenience... if you want, you could put this code in MyAwesomeLayer.h/MyAwesomeLayer.m instead. Normally, I would have my layer class extend CCLayer, then add a .png as a background for whatever I wanted displayed. However, for this example I was lazy and just wanted to have a solid colored background on the new scene, so it'd be easy to see the neat-o transition effects. So I'm having the layer extend CCColorLayer. It's the exact same as a CCLayer, but when you initialize it, you can specify a RGBA value for the background. Awesome, right? Next, open up the MyAwesomeScene.m file. Here we'll define the implementation for the scene and layer that we put in the header:
#import "MyAwesomeScene.h"
#import "HelloWorldScene.h"
@implementation MyAwesomeScene
- (id)init
{
if ((self = [super init]))
{
// All this scene does upon initialization is init & add the layer class
MyAwesomeLayer *layer = [MyAwesomeLayer node];
[self addChild:layer];
}
return self;
}
- (void)dealloc
{
// Nothing else to deallocate
[super dealloc];
}
@end
This is the code for the scene. It's about as bare bones as it gets. In the "init" method, which gets called when the class is created, we simply declare and create a new layer, then add it to the scene. Layers are set to auto-release their memory when no longer in use, so we don't have to do anything extra in the "dealloc" method. The "node" method that is called to create the layer is a static method that each cocos2d class inherits from CCNode, the base cocos class. It creates the object, runs your "init" method and handles memory management for you. Now on to the more interesting stuff.
@implementation MyAwesomeLayer
- (id)init
{
// Since we extended CCColorLayer instead of regular ol' CCLayer,
// we'll init the object with initWithColor. ccc4 takes rgba arguments - in
// this case it's a bright green background
if ((self = [super initWithColor:ccc4(0, 255, 0, 255)]))
{
// Get window size
CGSize size = [[CCDirector sharedDirector] winSize];
// Add a button which takes us back to HelloWorldScene
// Create a label with the text we want on the button
CCLabelTTF *label = [CCLabelTTF labelWithString:@"Tap Here" fontName:@"Helvetica" fontSize:32.0];
// Create a button out of the label, and tell it to run the "switchScene" method
CCMenuItem *button = [CCMenuItemLabel itemWithLabel:label target:self selector:@selector(switchScene)];
// Add the button to a menu - "nil" terminates the list of items to add
CCMenu *menu = [CCMenu menuWithItems:button, nil];
// Place the menu in center of screen
[menu setPosition:ccp(size.width / 2, size.height / 2)];
// Finally add the menu to the layer
[self addChild:menu];
}
return self;
}
- (void)switchScene
{
// Create a scene transition that uses the "RotoZoom" effect
CCTransitionRotoZoom *transition = [CCTransitionRotoZoom transitionWithDuration:1.0 scene:[HelloWorld scene]];
// Tell the director to run the transition
[[CCDirector sharedDirector] replaceScene:transition];
}
- (void)dealloc
{
// Nothing else to deallocate
[super dealloc];
}
@end
This is the implementation for the layer, which is in the same MyAwesomeClass.m source file. You can see that we have all our code to actually display stuff on screen here. Don't worry too much about the code that creates a menu and button; one good thing about Objective-C is that method calls are so verbose that you can pretty easily figure out what is going on. The only thing I'll mention now is that you can't directly display a button in cocos2d; you have to add the button to a menu, then display the menu. The real point of interest here is the switchScene method, which creates a transition object and then tells the CCDirector singleton to run it. You can see that when I create my transition object, I specify a time duration for the transition to run over (one second) and the scene that I want to transition to (the default scene already in the project). There are a number of transition types... I'll leave it as a reader exercise to try out all the different effects.
OK, so the new class is created, which displays a button that transitions back to the first scene in the app. However, we'll have to modify that first scene a bit so we can transition to the new scene. In HelloWorldScene.m, make the following additions/changes:
// on "init" you need to initialize your instance
-(id) init
{
// always call "super" init
// Apple recommends to re-assign "self" with the "super" return value
if( (self=[super init] )) {
// create and initialize a Label
CCLabelTTF *label = [CCLabelTTF labelWithString:@"Hello World" fontName:@"Marker Felt" fontSize:64];
// ask director the the window size
CGSize size = [[CCDirector sharedDirector] winSize];
// position the label on the center of the screen
//label.position = ccp( size.width /2 , size.height/2 );
// add the label as a child to this Layer
//[self addChild: label];
CCMenuItemLabel *button = [CCMenuItemLabel itemWithLabel:label target:self selector:@selector(switchScene)];
CCMenu *menu = [CCMenu menuWithItems:button, nil];
[menu setPosition:ccp(size.width / 2, size.height / 2)];
[self addChild:menu];
}
return self;
}
- (void)switchScene
{
// Set up a "FlipY" transition that moves to MyAwesomeScene
CCTransitionFlipY *transition = [CCTransitionFlipY transitionWithDuration:1.0 scene:[MyAwesomeScene node]];
// Tell the director to replace the currently running scene
[[CCDirector sharedDirector] replaceScene:transition];
}
What we're doing here is slightly modifying the existing code. Instead of adding the label directly to the layer, we're shoving the label inside a button, shoving the button inside a menu, then adding the menu to the layer. The button is set up to call the switchScene method, which transitions to the new scene we created.
And with that, we're done! Build and run the project, then click on the "Hello World" text that pops up. You'll see a transition to the new scene. Click the "Tap Here" text to go back to the original scene. It's a basic example, but you can see how the code here could be expanded in a larger project. Download a .zip with the project files, iff'n you'd like. If there are any points that are unclear, don't hesitate to let me know in the comments.
Learning to program for the iPhone or iPad probably seems daunting. You've heard rumors about Objective-C's steep learning curve. Where can you even start? Well, fear not. In this post I'll point you in the right direction to get you started with the tools that I currently use, Xcode and the cocos2d for iPhone framework. Yes, there are tons of other ways to write & publish apps, but this is what has worked out for me so far. When we finish, you'll have a template program running on a simulator on your Macintosh. From there, you can decide if the process was interesting enough to continue with.
The first step is to download Apple's iOS development tools. To do that, you'll need to register as an Apple developer. It's free, and gives you access to a bunch of technical documentation as well. Once you register and have your Apple ID, check out the iOS Dev Center. You'll be coming back to this site pretty often, so a bookmark might be in order. After you log in, you'll be able to scroll to the bottom of this page and download the most recent version of Xcode and the iOS SDK. Be warned, this is a hefty download (about 3.5 GB) so you might want to start it whilst at work or overnight. Once you get the disk image, you can install everything as you would any other program. All the developer tools will be installed in a /Developer folder in your hard drive's root.
Next, you'll download the cocos2d for iPhone framework. Go ahead and get the stable version. After that's finished downloading, unzip it into your home directory. Click the magnifying glass (Spotlight) in the upper right of your screen, and do a search for "Terminal." This'll give you a command line window that is opened to your home directory. Navigate into the cocos2d folder and install the cocos2d Xcode templates by typing the following commands:
cd cocos2d-iphone-0.99.4
sudo ./install-templates.sh
(Note that your version of cocos2d may be different.) Now you can use Spotlight to find Xcode and start it up. When Xcode starts, you'll see a splash screen. Choose the "Create a new Xcode project" option, and choose cocos2d from under the "User Templates" header. Go ahead and create your project using just the regular cocos2d Application template. Once you've played around with the default template, you can go back and experiment with one of the two physics engines (Box2D and Chipmunk) included in cocos2d. Once you click "Create," the template will create a default program as a starting point for you. To try it out, click the dropdown menu on the far left, and choose "Simulator." You won't be able to test your app on a device until you've actually paid Apple for the ability to publish on the App Store. Kind of lame, but there it is. Then click the "Build and Run" button. Xcode will churn for a while as the app compiles, then a simulator window will pop up and you'll see a Hello World message, along with the FPS count.
And that's it! You've finished one of the arguably harder parts of learning any new type of programming — getting your environment set up. After this, you can look at tutorials and through the cocos2d forums to learn more about Objective-C and how cocos2d works.
So, as an ongoing effort to try more iOS games and learn from other developers,
I’ve decided to resurrect the dreaded “game review.” This’ll hopefully force me
to think a bit more critically about some of the games I’m playing, and it might
help out the random reader who is looking for something interesting on the App Store.
This week I’m playing a game called BQLSI STAR LASER
(yes, all caps unfortunately). The only reason I’d heard of it was because I was
browsing around the website of Procyon Studio,
a company founded by Yasunori Mitsuda.
Mitsuda is most well-known as the composer of a number of Squaresoft games, such
as Chrono Trigger and Xenogears.
Well, apparently as a side project, he produced a small iOS game, as well as
created the music/SFX for it. Since I’m a fan of his music, I decided to try out the game.
The game itself is actually pretty interesting. It’s a great example of how a
touchscreen device can create and use any sort of interface the program creators
can think up. In this case, the UI paradigm is that of an old-school LCD handheld
game (similar to this).
It’s actually very well done, with clever little touches that add a bit of verisimilitude.
When you first load the app, you’re presented with two empty battery sockets,
which you have to touch to insert batteries. Then the game actually begins. BQLSI is a shooting game, and true to its’ inspiration, it limits itself to two types of enemy objects that appear on the screen. There are the aliens you have to shoot, and asteroids (or whatever) that you have to avoid. For being so limited in terms of “objects,” the game is actually pretty varied and creative. While you just blast enemies at first, soon you have to dodge asteroids, and navigate through collapsing rock corridors. It’s definitely challenging, and a fun throwback to days of gaming before the Game Boy.
I only really have two complaints regarding this game. The first is that there’s no music, just short jingles when you finish a level. I was really hoping for some catchy tunes from Mitsuda. Annoyingly, in place of the music there’s a low-pitched hum that plays continually… I’m not sure what that’s supposed to emulate. None of my LCD games made noises like that. The second is that sometimes your fingers drift off the controls when you’re concentrating on watching the gameplay. There’s really nothing that can be done about this; with no physical buttons, you just have to train yourself to know where the controls are in a game like BQLSI.
In conclusion, I’d have to say that BQLSI STAR LASER is a solid game. It has some
minor annoyances, but I’m glad to support interesting games from Japanese
developers. Let’s hope that the sequel gets some sweet, sweet chiptunes.
Weird that this little issue doesn't show more results in Google. Apple's file naming convention for supporting 2x rez images for Retina Display support is to add "@2x" at the end of a file name, before the extension. All well and good, except that if you're using Subversion for version control, it borks when you try to add files with a '@' in the name. Solution? Add an additional '@' at the end of your line. So instead of