synaptic hudson style

August 11th, 2010

Recently Sakai has started incorporating the necessary things for a platform to be module, and this will make things easier for allowing Sakai to be available as a number of distributions. It’s something I’ve been excited about in Sakai since my days in the basement, and now it’s finally starting to happen.

Recent Activity

Over the past year we’ve been moving all the modules in Sakai to be what we call Indie releases, which basically means that they are modular units that can be sucked into a build or Sakai Distribution rather than Sakai just being a huge directory of source code you have to build at one time. This is pretty exciting because in the next half year or so I think it’s going to allow us to develop some functionality that mirrors 2 of my favorite open source projects. What follows is a sort of organized version of a lot of exciting conversations I had at the Denver Sakai conference with folks last June.

Hudson

Hudson is a continuous build system that performs builds and tests for software projects… continuously. One of the neat things about it, is that it’s written in Java *and* the user interface is actually nice. I choose a lot of software packages over others just because they are open source, but in addition to being open source, Hudson is actually an awesome project that works, is well organized, and has a strong community. One of the strongest recommendations I’ve ever heard for it was when I was at PyCon, and during a testing presentation folks decided it was probably just as well to just use Hudson rather than roll something natively in Python (which says a lot since it’s a Java thing).

One of the interesting things about Hudson, which is not terribly related to continuous building, is their plugin system. I actually don’t know anything about the internals, but have only experienced it from the installation user interface.


Installed Hudson Plugins
A list of installed Hudson Plugins


Available Hudson Plugins
A list of Hudson Plugins you can install

You click the plugins you want, click go, and Hudson restarts itself if necessary. For the most part it just works, and it’s slick and simple, and all done in the browser admin. This is one of the cool things our Indie release process is going to allow. Ease of install, via either the command line, or via an actual page inside the Sakai administration interface.

Synaptic

Another piece of software I really like, and part of the reason I’m so much more productive using Ubuntu Linux vs something like Windows or MacOS, is the Synaptic Package Manager. You search for packages, and then install them. Having our Indie releases will allow us to do this sort of thing now too, because the Indie release process places a heavy emphasis on specific versioning for different modules, and the ability to do binary releases, which are needed for these install interfaces at a practical level.


Synaptic Package Manager for Ubuntu showing Open Office Software
Viewing some packages inside Sakai.

One of the cool features of Synaptic/Apt/Debian packages is the channel system. In this paradigm, you have different ‘channels’ to which different software packages below too. A common source of confusion these days in Sakai, especially for those that have been around for several releases, is the difference between core, contrib, and third party modules. I think part of the confusion for that is that the demarcation lines between the categories really have changed. In the monolithic source directory days you could just tell pretty quick by the physical location of the source parts. But now with the independent release process, Sakai is finally transforming into a development platform, so now releases must be packaged in distributions similar to Ubuntu or Redhat Linux, by pulling in a number of modules of varying version.

Anyways, the cool thing is that, to clarify how various modules are supported (core vs contrib vs XYZed), we can just have software channels now, thanks to the power of maven repositories and our independent release process. This means that the Sakai Foundation can have a channel with officially QA’d modules, such as announcements, rwiki, kernel stuff. There can be a contrib like universe channel, that contains things that have been purported to be QA’d by someone, but not the official foundation. ( Melete, Yaft, etc. ) There can be a 3rd party channel for proprietary modules (we can even have a warning saying you are installing proprietary software! ( I can’t think of anything off the top of my head that is actually closed source, maybe TurningTech integration? ) ). Etcetera.

So while the default Ubuntu channel dialog looks like:


Ubuntu Synaptic Repositories
These are the default software subscription channels in Ubuntu Linux

Our might look like:


Hypothetical Sakai Repositories based off of Ubuntu repositories
A hypothetical set of software channels for Sakai.

Marvin

With a bit of work and some additional functionality added to the Sakai Maven Plugins, I think this can be pretty cool. There is a good Maven Handbook here that displays some of the more powerful things we’ll need to do this.

For Sakai2, whose install consists of a heavily bastardized tomcat, I think one good option would be to use an embedded database such as sqlite to manage the installed modules for Sakai. Work would then consist of adding more functionality to the Sakai Maven Plugin, using a productive language that Maven supports (groovy/scala/jruby) as well as the uninstall option that never got added back into the Maven 2 version of our plugins. (If you remember back in the day, our Maven 1 had an uninstall. I ported it to Maven2 but didn’t carve out the time to follow through with getting the patch back in.)

The install and uninstall plugins will actually fire up the tomcat/sakai/installedmodules.sqlite and track what has been installed. I think theoretically you might be able to calculate it based off of scanning the directories, but it’s difficult since what we throw in components may not be easy to figure out the version of etc.

The nice thing about this, is then that we can also give errors and warnings if you are about to install a second version of a particular API/Component/Webapp. This is particularly troublesome with API’s, because you can mvn install 2 different versions without noticing, and then are often left scratching your head when you start up Sakai and begin getting ClassCastExceptions and things because there are multiple versions of API’s floating around (and java doesn’t do assembly versioning or anything natively).

So in addition to pleasant install and deployment GUI’s this can also have a lot of value just for development. I’ve looked a little bit at how to run Maven programmatically (so like maybe installing things from inside of a Sakai Admin UI could call a maven command) but it wasn’t completely straightforward. I’m sure we can figure it out though, or in the worst case fetch the jars and copy them with a bit of code.

As usual some more work will need to be done to support a cluster. You’ll have to fire an event and have each node update itself, etc etc.

This will require less infrastructure work for Sling/Sakai3 because the OSGi runtime already has support for some of this installation/uninstallation. However, I think we’ll still want the special UI and branding and experience to be similar, as the current UI is heavily targetted towards developers. Not related, but I’m still interested in prototyping running Sakai2/3 in the same tomcat by using the webapp build of Sling.

28 hours later

June 16th, 2010

The Sakai 2010 conference is going pretty well so far, lots of good stuff.

When I’ve had a little spare time the last few months I’ve been trying to look through the Etherpad code base so that we could make it a part of Sakai. After a bit of poking around it seemed like a better first step would be to Basic LTI-isize it, primarily because Etherpad does lots of Comet streaming stuff, requires JDK6, and I can imagine may require it’s own tuning. Also BLTI makes it easy to use all over the place. I can see some scenerios where it could end up in the same JVM as Sakai in the future. But this is a good first step.

I went to the BLTI workshop on Monday morning and after some hacking on the Denver Light Rail and random places Monday and Tuesday night got some basic stuff working. It still needs a lot of plumbing production work, but you can see fairly closely what it would actually look like.

There is a demo screencast here on blip.tv


Etherpad in Sakai 2

Etherpad in Sakai 2, click for bigger version. You can have multiple pads on the side bar. Eventually we need to write a Resource Type Shim for BLTI stuff.


Etherpad in Sakai 3

Etherpad in Sakai 3, click for bigger version.

When it comes to trying to enable a cloud service to be embedded in a container such as Sakai using BLTI you really only need to know one thing. It POST’s you a bunch of keys and then you serve the page. That’s about it.


BLTI Workflow

General workflow of Basic LTI backend interaction.

The above is a bit of a simplification, because there are particular rules on how you handle those POST keys, but I think it’s important to emphasize that that’s all it is. 1. POST some keys to a webapp. 2. Webapp uses the OAuth secret, provisions that user in it’s system, and serves stuff.

Sooo, obviously it’s not any sort of deep integration and could leave you wanting more, but if you have some neat webapp that would be cool in your class with just a rectangle on the page and list of users, then it’s pretty great.

One of the biggest questions going forward is which Etherpad Fork/Community to try and spearhead as a permanent sustainable community. There is the original Etherpad Open Source release, here, but it might be better to join one of the offshoots like Titanpad on Github since all the guys from the original project have to work on Wave now.

Hacking Etherpad

The build instructions
for setting up an etherpad server are pretty straight forward.

If you look at the src tree below, you can see that in the etherpad tree at one point it’s separated into ‘etherpad’ and ‘infrastructure’. For the most part (so far) we don’t need to touch infrastructure. That area contains the appjet engine framework that etherpad is built on top of. It is pretty cool, and written in Scala.


BLTI Workflow

Layout of Etherpad Source Tree.

The actual etherpad application code is in ‘etherpad/src’. Inside ‘etherpad/src/etherpad’ is all the server side code. The server side is all written in Rhino (Javascript on the JVM), so you can go in and work on code while the server is running, and it will reload the files. The static directory contains most of the client side js, images, css, etc. The templates contain the HTML templates. They are all *.ejs files, which are like JSP’s, but using javascript instead of Java.

Digging in for the hack

I’m still getting familiar with how appjet works, but it all starts in src/main.js. This file contains a bunch of initialization code and also some dispatchers. Dispatching URL’s in appjet looks fairly similar to other routes style and some python web frameworks.

var commonDispatcher = new Dispatcher();
  commonDispatcher.addLocations([
    [‘/favicon.ico’, forward(static_control)],
    [‘/robots.txt’, forward(static_control)],
    [‘/crossdomain.xml’, forward(static_control)],
    [PrefixMatcher(‘/static/’), forward(static_control)],
    [PrefixMatcher(‘/ep/genimg/’), genimg.renderPath],
    [PrefixMatcher(‘/blti’), pad_control.basic_lti],
    [PrefixMatcher(‘/ep/pad/’), forward(pad_control)],
    [PrefixMatcher(‘/ep/script/’), forward(scriptcontrol)],
    [/^\/([^\/]+)$/, pad_control.render_pad],
    [DirMatcher(‘/ep/unit-tests/’), forward(testcontrol)],
    [DirMatcher(‘/ep/pne-manual/’), forward(pne_manual_control)],
    [DirMatcher(‘/ep/pro-help/’), forward(pro_help_control)]
  ]);

For now I’m handling the BLTI POST with the /blti route, so that’s what you need to put into the configuration on the embedded widgets.

At the moment the rest of the parts I’ve had to hack are:

  • static/css/pad2_ejs.css
  • static/js/pad2.js
  • templates/html.ejs
  • templates/pad/pad_body2.ejs
  • etherpad/control/pad/pad_control.js

A few years ago, Josh Ryan and I hacked together a prototype of Google Docs in Sakai, and one of the crappy things was not having many options about what was being embedded. The great thing here is that we can strip out all the HTML Branding and other things we don’t need, which I’ve started to do in the static css/js and pad_body2.ejs.

Most of the work so far has been in pad_control.js, and a lot of poking around in sessions.js. One of the things we need to do is get the code for Etherpad Pro accounts back up and running so we can provision real accounts (in all the open source builds, the default configuration is just to use Guest Accounts). And then we can properly handle the OAuth tokens.

function basic_lti() {
  var reslinkid = request.params.resource_link_id;
  reslinkid = reslinkid.replace(/\//g,‘_’);

  var dispname = request.params.lis_person_name_full;
  var userid = request.params.user_id;

  sessions.setTrackingCookie();
  sessions.preRequestCookieCheck();
  getSession().instantCreate = reslinkid;
  getSession().guestDisplayName = dispname;

  var pad = padutils.getCurrentPad();
  padusers.userId = userid;
  response.redirect(‘/’+reslinkid+"?displayName="+dispname);
}

Our current basic lti handler in pad_control.js is pretty simple. We suck out the resource_link_id and user info so we can create a unique pad for the placement and make sure their user name shows up in the chat window. Again, we need to work on the session handling code a bit as we get familier with the etherpad code base (which is not actually all that large (probably because it’s written in javascript ) ), and set up the pro accounts. But it’s pretty close.

Another interesting use case is that we can create a version of the blti that just hides the actual pad editor and just use the chat window, since it’s actually nicer than the regular chat functionality in Sakai.

ONE MORE EXCITING THING!!

This gives us a great excuse to actually learn how to set up a server side open office installation, since that is what etherpad uses for all it’s office importing/exporting.

Awesome!

Will be checking in the patches on google code, github, bitbucket or somewhere soon.

fine grain load test verification and pitivi actually works

May 18th, 2010

Recently I was tasked with doing some load testing on Assignments 2 as we slowly increase the amount of usage our pilot is getting. And actually, Fall Term Instructors will have to choose between Assignments tools when they set up their sites, so it should see a fair amount of load next fall as well.

It seems like our load tests usually involve something basic like hitting 5 or 6 pages in Sakai and ramping it up until something breaks, and maybe there is some sort of interaction as different user roles. In the past with assignments we’ve had lots of issues with duplicated entries and other oddities that occurred during concurrent submissions, so I wanted to do something with that a bit more, and with truly original data for each test.

Each run of this test consists of:

  1. Submitting an assignment as a student.
  2. Grading that assignment as an instructor and leaving some feedback.
  3. Going back and viewing that feedback as a student.

The Grinder

The last 9 months or so I’ve (well we’ve), been using The Grinder to do load testing when there was time. I originally picked it over JMeter because it seems slicker, more extensible, and the tests are generally written in Python. It is a Java based application though, so it’s really Jython, and you can call and use Java libraries pretty easily. One of the nice things is that all testing is done by instrumenting objects or single methods, meaning you can test/measure just about anything. It also has a proxy recorder like most of these sorts of tool suites do, but I haven’t used it too much since many of the things you need to test in Sakai have so much parameterization and weird fields and urls that it’s sometimes easier to hand craft the interaction for performing fine grained load.

However! Last month I actually gave a demo of The Grinder and some of our tests at the Indianapolis Python Meetup, and someone there told me that the newest release of Selenium Server can now use a headless Firefox so it might actually be possible for load testing. We had experimented with running recorded Selenium scripts in The Grinder last fall, but because they spawned an actual Firefox GUI it wouldn’t have been useful for much more than functional testing. ( Selenium allows you to export scripts in a large number of programming languages. 2 of those are Python and Java, so consequently you can use either of those exports as test cases to measure in The Grinder ).

All that aside, for this particular exercise I’m mostly interested in using programmatic Python code to carefully test the steps listed above.

The Grinder Installation and Architecture

Because the default Grinder package isn’t as turnkey as I’d like, I’ve been putting together a package in contrib that all you have to do is check it out, and you should be able to run it assuming you have Java6 and a JAVA_HOME property set.

( Until a recent version The Grinder only required Java5. However, after a recent upgrade to the newest version of Jython 2.5 (yay! This helps us run lots of extra libraries), they also changed some of the underlying mechanisms for measuring tests. You’ll see in more depth below, but basically, previously they created proxy objects that would measure how long test executions would take. In the latest major Grinder upgrade they also added new support for measuring test execution by actually instrumenting the code. One positive impact this may have in the future is the ability to use other JVM languages to write tests. Personally I really prefer Python, but it’s always a bonus to include more of the JVM Language community. )

So, to check out Sakai Grinder and fire it up you can basically go:

# Fetch Sakai Grinder from Contrib
svn co https://source.sakaiproject.org/contrib/sakaigrinder/trunk/ sakaigrinder

# Create a grinder.properties based off the template.
cp projects/grinder.properties.template projects/grinder.properties

# Start up the Console which is basically a server to collect
# statistics from each agent that is running a test.
./bin/sakaigrinder.sh startconsole &

# Start a client. At this point you can click the Start button on the
# console that will have this agent run tests. After that you’ve run the
# Hello Sakai Test that connects to a fresh localhost:8080 sakai as
# the default admin.
./bin/sakaigrinder.sh startagent


Grinder Console

Grinder Console after running tests with a connected agent loaded with the basic sakaihttp.py script.
The Grinder uses a pretty standard client/server sort of architecture in the form of the Console and Agents. Although the console that is started above is actually a GUI, there is a headless version so you can set up an automated test farm.

Each Agent starts up with a grinder.properties that specifies a number of things such as:

  1. The test script to run. The above script used the simple example sakaihttp.py in the projects folder.
  2. Things such as number of processes/threads/trials the agent should run, as well as ramp up time for threads and things.
  3. An optional port and IP address for the console to connect to. This is so you can set up a farm of agents and control them all from a console. We’ve actually done that in the past for some load tests where we had agents running in Bloomington and Indianapolis and all connected to the same console to control them.
  4. A lot of other things I haven’t looked at, as well as any user specified properties you want to create (we use these for like sakai server url prefix, etc, etc)

So it ends up looking like:

Grinder Server/Client Architecture

You’ve noticed that we’re checking out a trunk version of Sakai Grinder. After a few things get cleaned up I do want to roll a proper tag of it and zip it up for some sort of release.

Hello World

A basic Hello World, from the screen shot above is:

import sakaigrinder
from net.grinder.script import Test

test1 = Test(1, "Loading the workspace")

class TestRunner:
    def __call__(self):
        self.conn = sakaigrinder.SakaiGrinder()
        self.timeworkspace = test1.wrap(self.loadworkspace)
        for i in range(0,20):
            self.timeworkspace()

    def loadworkspace(self):
        worksite = self.conn.request.GET("http://localhost:8080/portal")
        print "Wrote http body to: ",  sakaigrinder.writeToFile(worksite.text)

This has one test. You can see the Test object is what will show up in the console has a timed method. You then use that Test object to “wrap” a method or object that you want to time. This example actually uses the old proxy version of timing. The more modern method (that was added a few months ago) is called record(), which I’ll eventually switch to. The method or object that is returned from the wrap/record is what you actually call when you want to test, and each usage of it is timed and statistics are collected for it. Obviously this is pretty great, because you can measure anything ranging from HTTP calls, to JDBC calls, to well any object or method thats running on your JVM. And it works nice because Python has enough language facilities to make method wrapping and argument packing convenient.

Each script must have one of these TestRunner classes to use as the scripts test start point. You can also set it up to use a more naturally callable method (rather than a class with __call__), but I’m usually happy to stick with the default example.

Sakai Logins

In order to make scripting Sakai a bit easier, there is a sakaigrinder module, that builds on top of Grinders HTTP connection library. The Grinder HTTP connections, while maybe not as pretty as Pythons httplib2 library, is actually really great because it does lots of homework for keeping track of your sessions, cookies, and other things. So I actually prefer using it now, though I have though about implementing some httplib2 signatures on top of it.

You create a connection as follows:

import sakaigrinder

self.conn = sakaigrinder.SakaiGrinder(urlbase="http://localhost:8080",username="admin",password="admin")

And then you get a HTTPConnection back. Under the covers it just uses portal/xlogin at the moment, but I’ve started looking at creating other options for it, such as using the CAS login mechanisms at IU.

Script Structure and Threads/Processes

Taking a look at the Assignments 2 test, asnn2.py (which is still pretty sloppy but works), we’ll see the interaction between threads.

Each Grinder Agent’s properties file dictates the number of processes and threads to start up. Each process starts it’s own invocation of the test script. Then each thread in that process creates it’s own instance of the TestRunner class. In this way you can share data between test threads by putting shared state outside the TestRunner class in the script file. I’m currently doing something like this to round robin between test users, so I can load in a huge list of test students and have each thread use a different student:

# SNIP
curUser = 0
countLock = threading.Lock()

def getNextUser():
    global curUser
    countLock.acquire()
    curUser = curUser + 1
    userEid, userId = asnn2data.test_students[curUser%len(asnn2data.test_students)].split(‘,’)
    togo = User(userEid,userId)
    countLock.release()
    return togo

# SNIP

# Timed and Instrumented functions
test_submission = Test(1, "Student Submission POST")
time_submission = test_submission.wrap(submit_asnn)

test_grading = Test(2, "Instructor Grading a Submission")
time_grading = test_grading.wrap(grade_and_leave_feedback)

test_viewfeedback = Test(3, "Student Viewing Feedback")
time_viewfeedback = test_viewfeedback.wrap(review_submission)

class TestRunner:
    def __call__(self):
        self.user = getNextUser()
        self.conn = sakaigrinder.SakaiGrinder(urlbase=urlprefix,username=self.user.eid,password=oncpasswd)
        # Submit Assignment
        student_text = "Input text from %s , %s" % (self.user.eid, uuid.uuid1())
        time_submission(self.conn, placementInfo[0][2], placementInfo[0][1], student_text)

        # Grade Assignment
        inst_feedback = "Feedback text %s" % (uuid.uuid1())
        self.inst = inst_user
        self.instconn = sakaigrinder.SakaiGrinder(urlbase=urlprefix,username=self.inst.eid,password=oncpasswd)
        time_grading(self.instconn, placementInfo[0][2], placementInfo[0][1], self.user.sid, inst_feedback)

        # Review Submission as student
        self.conn = sakaigrinder.SakaiGrinder(urlbase=urlprefix,username=self.user.eid,password=oncpasswd)
        time_viewfeedback(self.conn, placementInfo[0][2], placementInfo[0][1], [student_text,inst_feedback])

In the example we can see that I have 3 tests I’m measuring. Submitting an assignment, Grading it, and Reviewing it as a student again. Outside of the TestRunner function I have the little getNextUser function that is shared by all threads and gives each test thread a separate user.

This is a bit primitive still, and I’m looking at better and more reusable ways to create huge distributed tests that include students/instructors and Sites to go along with each. It’s worked fine for testing so far though. I’ll probably move it into the sakaigrinder library.

As far as I know, there isn’t really a best practice way to share information between processes so it might require a database or something else, but that may have changed since I looked at the docs and skimmed the mailing list.

Manual HTTP Requests

Because of how I wanted the tests to work I ended up handcrafting the requests and post keys, and because they aren’t new age JSON payloads or something it’s a little ugly, but works.

def submit_asnn(conn, asnnId, placementId, asnnText="This is some input text"):
    """Submits an assignment, the conn being passed in should be for a student
    in the course."""

    m = [
    (‘el-binding’,‘j#{StudentSubmissionBean.ASOTPKey}new 1′),
    (‘el-binding’,‘j#{StudentSubmissionBean.assignmentId}’+str(asnnId)),
    (‘previewAsnnAsStudent’,‘false’),
    (‘previewsubmission’,‘false’),
    (‘page-replace::portletBody:1:assignment-edit-submission::text:1:input’,asnnText),
    (‘page-replace::portletBody:1:assignment-edit-submission::text:1:input-fossil’,
        ‘jstring#{StudentSubmissionVersionFlowBean.new 1.submittedText}’),
    (‘page-replace::portletBody:1:assignment-edit-submission::attachment_list:1:attachments-input-fossil’,
        ‘istringarray#{StudentSubmissionVersionFlowBean.new 1.submittedAttachmentRefs}[]‘),
    (‘command link parameters&Submitting%20control=page-replace%3A%3AportletBody%3A1%3Aassignment-edit-submission%3A%3Asubmit_button&   amp;Fast%20track%20action=StudentSubmissionBean.processActionSubmit’,‘Submit’),
    ]
    resp = conn.request.POST(urlprefix+"/portal/tool/"+placementId+"/student-submit/1", convertlist_to_nvpairlist(m))

def review_submission(conn, asnnId, placementId, verifytxt=[""]):
    """This simulates a student checking the feedback from their assignment."""
    reviewurl = "%s/portal/tool/%s/student-submit/%s" % (urlprefix,placementId,asnnId)
    resp = conn.request.GET(reviewurl)
    for txt in verifytxt:
    if resp.text.find(txt) < 0:
        grinder.statistics.forCurrentTest.setSuccess(0)

def grade_and_leave_feedback(conn, asnnId, placementId, studentId, inst_feedback=""):
    """Simulates an instructor leaving some feedback and grading.
    StudentId should be the long guid internal Sakai ID.
    """

    import re
    subfeedurl = "%s/direct/assignment2submission.json?asnnid=%s&placementId=%s&_start=0&_limit=200&_order=studentName" %    (urlprefix,asnnId,placementId)
    conn.request.GET(subfeedurl)
    url = "%s/portal/tool/%s/grade/%s/%s?viewSubPageIndex=0" % (urlprefix,placementId,asnnId,studentId)
    resp = conn.request.GET(url)

    match =  re.search("jstring#{AssignmentSubmissionVersion\.([0-9]+)\.annotate", resp.text)
    if match == None:
        grinder.statistics.forCurrentTest.setSuccess(0)
        return
    subm_vers = match.group(1)

    searchstr = (‘"jstring#{AssignmentSubmissionVersion\.%s\.annotatedText}(.+)"’ % (subm_vers) )
    match = re.search(searchstr,resp.text)
    orig_text = match.group(1)

    m = [
    (‘el-binding’,‘j#{AssignmentSubmissionBean.assignmentId}%s’ % (asnnId)),
    (‘el-binding’,‘j#{AssignmentSubmissionBean.userId}%s’ % (studentId)),
    (‘viewSubPageIndex’,‘0′),
    (‘page-replace::override_settings-fossil’,‘iboolean#{AssignmentSubmissionBean.overrideResubmissionSettings}false’),
    #(’page-replace::resubmission_additional-selection-fossil’,'istring#{AssignmentSubmission.1.numSubmissionsAllowed}1′),
    (‘page-replace::feedback_section::feedback_text:1:input’,"%s\n%s" % (orig_text,inst_feedback)),
    (‘page-replace::feedback_section::feedback_text:1:input-fossil’,
    ‘jstring#{AssignmentSubmissionVersion.%s.annotatedText}%s’ % (subm_vers, orig_text)),
    (‘page-replace::feedback_section::feedback_notes:1:input’,‘This is the feedback text’),
    (‘page-replace::feedback_section::feedback_notes:1:input-fossil’,‘jstring#{AssignmentSubmissionVersion.%s.feedbackNotes}’ % (subm_vers)),
    (‘page-replace::feedback_section::attachment_list:1:attachments-input-fossil’,
    ‘istringarray#{AssignmentSubmissionVersion.%s.feedbackAttachmentRefs}[]‘ % (subm_vers)),
    (‘page-replace::grade_input-fossil’,‘istring#{AssignmentSubmissionBean.grade}’),
    (‘page-replace::grade_comment_input-fossil’,‘istring#{AssignmentSubmissionBean.gradeComment}’),
    (‘page-replace::grade_input’,‘8′),
    (‘page-replace::grade_comment_input’,‘These are my fantastic gradebook comments.’),
    # This one saves the feedback but does not release it.
    #(’command link parameters&Submitting%20control=page-replace%3A%3Asubmit&    amp;Fast%20track%20action=AssignmentSubmissionBean.processActionGradeSubmit’,'Save’)
    (‘command link parameters&Submitting%20control=page-replace%3A%3Arelease_feedback&Fast%20track%20action=AssignmentSubmissionBean.processActionSaveAndReleaseFeedbackForSubmission’,‘Save and Release Feedback’)
    ]

    resp = conn.request.POST(urlprefix+"/portal/tool/"+placementId+"/student-submit/1", convertlist_to_nvpairlist(m))

These do the actual work and are the methods that we wrap in order to measure elapsed time and stuff. For Assignments 2 implementation reasons I won’t go in to, we actually have to go through and do some tricky editing of POST keys, and screen scraping HTTP Responses in order to have the entire life cycle work with unique data for every single test.

Failing Tests

You can cause the currently executing test in any Grinder script to fail using:

from net.grinder.script.Grinder import grinder

grinder.statistics.forCurrentTest.setSuccess(0)

You can see above in the TestRunner class that we generate UUID’s to put into the students submission text, and to insert into the Instructors feedback. This is so we can verify under load that the assignments are actually getting recorded correctly, graded correctly, and we are aren’t destroying any data and whatnot.

So in the grade feedback test, we check to see that the student submission is completely intact and containing the UUID. And then when the student checks their submission at the end, we ensure it contains the intact feedback from the Grader with the second UUID that was generated. If these don’t check out that particular test is set to fail and this shows up in the aggregated data in the Console.

Honestly, we should go even further and write these out to a data file, and then re-validate the results against the SQL tables afterwards. But you have to start somewhere…

Getting Properties

The only other really interesting thing here is adding your own properties and fetching them.

urlprefix = grinder.getProperties().getProperty(’sakai.serverUrl’)
oncpasswd =  grinder.getProperties().getProperty(‘grinder.passwd’)

The urlprefix I store in the grinder.properties. We could probably actually dynamically merge this with a sakai.properties so we can use those properties, and for testing JDBC queries and things.

The grinder.passwd actually comes from launching the agents that we have in sakaigrinder.sh using the startagentpasswd command.

case $1 in
startconsole)
echo Starting Sakai Grinder Console
java -cp $CLASSPATH net.grinder.Console
;;
startagent)
echo Starting Sakai Grinder Agent
java -cp $CLASSPATH  net.grinder.Grinder $GRINDERPROPERTIES
;;
startproxy)
echo Starting Sakai Grinder Proxy
java -cp $CLASSPATH net.grinder.TCPProxy -console -http
;;
startagentpasswd)
echo Staring Sakai Grinder Agent with a Login password
read -s -p "password: " PASSWD
printf "%b" "\n"
java "-Dgrinder.passwd=$PASSWD" -cp $CLASSPATH net.grinder.Grinder $GRINDERPROPERTIES
;;
*)
echo ‘Usage: startconsole, startagent, startagentpasswd, or startproxy’
;;
esac

I added this after accidentally checking in the password for our test users and having to reset a hundred of them. :p So you start with the startagentpasswd option and it prompts you for a password, then you can use that password property in your scripts. Obviously this assumes you’re using the same password for all your test users. This bit of functionality deserves some more sophistication in the future as well.

Epilogue
I’m pretty happy with this so far, and hope to keep cleaning it up as well as creating sophisticated tests that cover more than just overloading the server with requests until it breaks. I am pretty excited to give another look at headless selenium servers. Because selenium has a great recorder, and the headless server actually contains a full browser/javascript environment it could be pretty cool to set up in a cloud. There has been work by folks to create Grinder setups to run on Amazon EC2 clouds and things, so that is cool. Also, because it is Java based, I could see actually embedding it as a library in a Sakai module so you could load test executions to various loaded components using Sash and other things.

I’ve done several runs of this with a few hundred concurrent users, students and instructors, with no errors, and am still looking at ways to easily scale this up by auto generating assignments and classes on the fly.

Holy crap! Non Linear Video Editing on Linux!
And I didn’t actually have to do anything to get it working!

I was pretty amazed after upgrading to Ubuntu 10.04 on several machines, that PiTiVi, the new bundled video editor, actually started up, let me load some clips, drag them around on the time line, and render the whole lot to a video… without Crashing! I’m sure it’s probably still buggy as hell, but to actually perform some basic tasks with no issues and not installing anything was honestly pretty amazing. If you use Win32 or OSX you’re probably rolling your eyes, but I think it really is a big deal. In the 8 or so years I’ve been using desktop linux it’s amazing the progress we’ve made. I hope to dedicate a little time to desktop linux this year. I’ll probably start making more screen casts now too using XVidCap (screen recorder) and PiTiVi.

PiTiVi

Not a code post

April 6th, 2010

asdfasf

diffing miss daisy

April 6th, 2010

When you’re grading assignments in Sakai you usually have the option to mark up the text that a student submitted so you can return it to them with comments and annotations.  In the past this has been done by surrounding text you want highlighted in brackets, and then that text will turn red.

An example of doing this ( you can click on the images for bigger readable versions):


Asnn1 Marktup

Marking up comments with double brackets.

And then the contents in brackets are highlighed in red:


Viewing Feedback with brackets
The red text was surrounded with {{ }} by the instructor.

We’ve been working on a ‘more new’ Assignments application for Sakai (imaginatively called ‘Assignments 2′), and have been looking at making this process of marking up student submissions more like the sort of annotation feature you’d see in something like OpenOffice, Word, or even something like viewing a code diff.

After searching around for an html diffing tool, so far I’ve been pretty satisfied with Daisy Diff, which was a Google Summer of Code project a few years back and is used in the stable production Daisy CMS. It’s built especially for HTML Diffing, so it keeps tracking of tricky things like adjacent text nodes that appear as one in a browser, tag issues, etc etc.

So, now the instructor basically marks up their stuff by just editing the submission text:


Marking up with daisy diff
Text is marked up. In the backend we save the original student submission, the annotated instructor submission, and are currently doing the diffing on each request that requires it. It seems fast enough on initial test data, but needs some perf testing for really large submissions still, to see if maybe it should be saved to the db.

and the added/changed/removed regions are highlighted in the student view:


Viewing annotated markup from daisy diff
The added areas are in yellow highlight, and the removed areas are in yellow highlight strikethrough.

You can change the added/changed/removed sections with standard CSS styles. I actually haven’t gotten anything to be picked up as “Changed” yet, always just added and removed so I might need to look into that more, though even with added/removed it’s pretty good. Also, after a few tests, it seems like the Daisy styles really are the ones that take precedence, for instance say if you had highlighted something blue, and it was removed, it would still highlight yellow. From looking at the results, it appears to wrap a span as closely as possible around text nodes so to take effect even if it’s across several tags.

span.diff-html-removed {
  background-color: yellow;
  text-decoration: line-through;
}

span.diff-html-added {
  background-color: yellow;
}

span.diff-html-changed {
  background-color: yellow;
}

Using the actual library was a little more work than I wanted, but not too much. As is standard with Java, you end up making lots of factory code for reading Strings and whatnot. ( There may actually be a utility wrapper method in Daisy Diff for this, but I didn’t see one. )

public String diffHtml(String source, String annotated) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
       
        SAXTransformerFactory tf = (SAXTransformerFactory) TransformerFactory
        .newInstance();
       
        TransformerHandler result = null;
        try {
            result = tf.newTransformerHandler();
            result.getTransformer().setOutputProperty("omit-xml-declaration", "yes");
        } catch (TransformerConfigurationException e) {
            throw UniversalRuntimeException.accumulate(e,
            "Error creating the SAX Transformer to be used for Daisy Diffing.");
        }
        result.setResult(new StreamResult(baos));
       
        ContentHandler postProcess = result;
       
        Locale locale = Locale.getDefault();
        String prefix = "diff";
       
        HtmlCleaner cleaner = new HtmlCleaner();
       
        InputSource oldSource = new InputSource(new ByteArrayInputStream(source.getBytes()));
        InputSource newSource = new InputSource(new ByteArrayInputStream(annotated.getBytes()));
       
        DomTreeBuilder oldHandler = new DomTreeBuilder();
        DomTreeBuilder newHandler = new DomTreeBuilder();
        try {
            cleaner.cleanAndParse(oldSource, oldHandler);
            cleaner.cleanAndParse(newSource, newHandler);
        } catch (Exception e) {
            throw UniversalRuntimeException.accumulate(e,
                    "Error cleaning/parsing HTML before DaisyDiffing: source:\n"
                    + source + "\n\nannotated:\n: " + annotated);
        }
       
        TextNodeComparator leftComparator = new TextNodeComparator(
                oldHandler, locale);
       
        TextNodeComparator rightComparator = new TextNodeComparator(
                newHandler, locale);
       
        try {
            postProcess.startDocument();
            HtmlSaxDiffOutput output = new HtmlSaxDiffOutput(postProcess,
                    prefix);
            HTMLDiffer differ = new HTMLDiffer(output);
            differ.diff(leftComparator, rightComparator);
            postProcess.endDocument();
        } catch (SAXException e) {
            throw UniversalRuntimeException.accumulate(e,
                    "Error processing DaisyDiffing: source:\n"
                    + source + "\n\nannotated:\n: " + annotated);
        }
       
        return baos.toString();
    }

Although I stripped it all out for our project, daisydiff includes support for interesting javascript things, such as tabbing through all the annotations on the page and viewing each annotation and it’s information in a little popup, but we just want a simple static view.

It’s likely we’ll need to work on streamlining and improving the mechanisms on the instructor side to aide in previewing the annotations they’re doing, but so far we’ve made a pretty good start with the initial submission annotation using daisydiff.