|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
Contents
Introduction
I have always been a fan of distributed computing. With the advent of Silverlight, I felt there was an exciting opportunity to create a Grid computing framework utilizing the client-side Silverlight CLR. That is how Legion emerged. Legion is a Grid Computing framework that uses the Silverlight CLR to execute user definable tasks. Legion uses an ASP.NET application and web services to download tasks, upload result data, and provide grid-wide thread-safe operations for web clients or agents. Multiple tasks can be hosted at once, with Legion managing the delegation of tasks to agents. Client performance metrics, such as bandwidth and processor speed, may be used to tailor jobs for clients. Legion provides a management service and WPF application that is used to monitor the Legion grid. I have deployed Legion to a demonstration server here so you can see it in action.
BackgroundWhile Grid computing is not new, there has been a resurgence of interest in it due mainly to organisations looking to capitalise on their underutilised IT infrastructure, to perform computationally intensive business related tasks. Aside from large organisations jumping on the bandwagon, public interest in Grid computing has of course been stimulated by large scientific projects, such as the Seti@home project. After performing some initial research for this article I became enthused by what a volunteer grid computer is capable of achieving, and what, by offering a framework like Legion may achieve. The results of some volunteer Grids have been remarkable. For example, the Smallpox Research Grid Project managed to find 44 strong treatment candidates for smallpox; an, as yet, incurable disease. Vaccination for Smallpox ended in the 1980s, but there are fears that the virus may re-emerge. (Wikipedia, 2007; The Inquirer, 2003) The allure of harnessing unused computing power is strong, and so too is the notion of many users contributing otherwise wasted clock cycles for the benefit of humanity. One reason why so many Grid projects have garnered so many participants, is the feeling of belonging to a shared mission; it's free, and users gain a feeling of contributing to a good cause, just by running an application in the background on their computer. Could it be that we are on the cusp of a web paradigm shift, where web browsers share the load of the web server? Opening the door to smaller start-ups with little investment in infrastructure, providing an unlimited capacity; serving any number of clients. Such a capability has traditionally been the domain of monolithic companies with massive data centres. What would that do? Well, it would radically transform the web; providing a level of democratisation never before seen. Taking this idea further, imagine, if you will, what a peer to peer WebTorrent protocol might bring. Web servers and web browsers alike, serving as seeds and distributing the load. We are venturing outside the scope of the article a little, but I raise these ideas to encourage in the reader a feeling of opportunity. For that is what having a CLR in the browser now affords us. An opportunity, not only to create nice UI, but also to move to a less server-oriented application model. Not merely a thin-client model, but rather a distributed processing model. I believe security to be the main challenge in providing client-centric peer to peer volunteer computing. Protecting a user's privacy is paramount, and it's risky sending sensitive information, which may be eavesdropped or modified by some rogue client. Likewise, trusting the results from an inherently untrustworthy source presents an issue. Clearly mechanisms are required to address these issues. Likewise, providing secure cross domain browser support will bring a lot of opportunity to decentralise and share services more readily between organisations. Microsoft and others have proposed some techniques. Yet, it was inevitable that doing so within existing browser capabilities requires an assortment of hacks. Hacks, such as using IFrames to communicate across domains. While preventing cross domain browser communication seems reasonable, with today's ubiquity of webservices, we can quite easily send client data from server to server, and thus across domains, which effectively circumvents the protection mechanism anyway. Unlike other distributed computing projects, Legion allows users to participate by simply viewing a webpage. The shift to cloud computing, and web based application, may have fostered a growing reluctance in users to download and install applications. Forcing users to download software to participate, while improving the dependability of the grid (we'll discuss the pitfalls of a browser based Grid later), must surely decrease the overall participation rate. Users prefer not to install software. It's a hassle. Legion, on the other hand allows us to bring new 'volunteers' to our grid by means of providing enticing content. Thus, we are no longer dependent on a single motivating cause to bring participation. It provides developers with a system that will deploy and execute modules written using any .NET Desktop/Silverlight CLR compatible languages. The beauty of Legion is that it does not require user installation of any Legion software. It relies solely on the Silverlight CLR in the user's browser. I've heard many comparisons between Silverlight, Flash, Java applets etc. Most comparison seems to resolve around what an old project manager of mine called "pointy clicky things."; pure UI. Well, in this article my aim is to take the focus away from being solely UI, and take more of a look at what having a .NET CLR in the browser might mean. Long live ClogClog, unbeknownst to me, was a prelude to this project, and turned out to be invaluable in its development. Without Clog for Silverlight working on the concurrency elements of this project would have been made extremely difficult. I have, as it were, been eating my own dog food. It made debugging with multiple clients a breeze. Using the Legion Agent UIOn the client-side, Legion is hosted within a Silverlight module that automatically controls the downloading and execution of tasks.
The The interface displays the task name, as it is defined in the server-side config, the number of tasks completed, stored on the clients machine in Isolated Storage, and the percentage complete of the current executing task. Legion ManagerThe Legion Manager is a WPF application that allows monitoring of the grid. It provides a summary of the progress of all tasks, and the current grid capacity; including available processing power in megaflops and total bandwidth for all connected clients.
Components OverviewLegion consists of three main components. The
The WPF Manager is a WPF application that communicates with the server-side
The The
The Grid service is mostly a wrapper for the
Grid TasksWhen creating a new Legion task, we create a server-side master task, and a client-side slave task. A custom task is created by extending the
The client-side static
In order to execute a client-side task (a
ConfigurationLegion uses a <configSections>
<section name="Grid"
type="Orpius.GridComputing.Configuration.GridConfigSection"
requirePermission="false"/>
<!-- Client Logging section -->
<section name="ClientLogging"
type="Orpius.Logging.Configuration.ClientLoggingConfigSection"/>
.
.
.
</configSections>
Then we declare our grid tasks within the <Grid>
<Tasks>
<!--
slaveType: The name of the Silverlight task type.
Please note that Version, Culture,
and PublicKeyToken are required.
-->
<add name="PrimeFinder"
type="Orpius.GridComputing.Tasks.PrimeFinderMaster,
Orpius.GridComputing.Tasks.PrimeFinder"
slaveType="Orpius.GridComputing.Tasks.PrimeFinderSlave,
Orpius.GridComputing.Tasks.PrimeFinder.Silverlight,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
<add name="MutexExample"
type="Orpius.GridComputing.Tasks.MutexExampleMaster,
Orpius.GridComputing.Tasks.MutexExample"
slaveType="Orpius.GridComputing.Tasks.MutexExampleSlave,
Orpius.GridComputing.Tasks.MutexExample.Silverlight,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</Tasks>
</Grid>
It is important to note that the Agent Performance MetricsTo measure the connection bandwidth we merely call a web service that returns a result message that is approximately 100 kilobytes. We do this once when the connection is established. /// <summary>
/// Measures the bandwidth of the connection
/// with the server by calling a web service
/// method that returns a large message
/// of a predetermined size.
/// </summary>
/// <returns>The download rate of the client in KiloBytes.
/// </returns>
public static long MeasureBandwidthKBps()
{
long resultDefault = 0;
long result = resultDefault;
JsonGridService gridService = JsonGridService.GetService();
DateTime begun = DateTime.Now;
try
{
byte[] download = gridService.Download100KB();
}
catch (Exception ex)
{
log.Debug("Unable to communicate with server.", ex);
return resultDefault;
}
DateTime ended = DateTime.Now;
TimeSpan span = ended - begun;
if (span.TotalMilliseconds > 0)
{
result = 100000 / (long)span.TotalMilliseconds;
}
return result;
}
I used Fiddler to throttle the download rate by setting it to Simulate modem speeds in order to test the bandwidth measuring.
We can see in the next capture that, following the throttling of the download rate, the result is 6 KB/s, which is about right. The accuracy is sufficient for our purposes. Of course we are only running the test once for each client, and so we must take the result as being just a rough estimate.
To measure the client's processor speed, we have the client execute some floating point operations. /// <summary>
/// Measures the processor speed in megaFLOPS.
/// Not particularly accurate, but it provides
/// a rough indication of what the client can do.
/// </summary>
/// <returns>The processor speed in megaFLOPS.</returns>
public static long MeasureMFlops()
{
double x = 0, y = 0, z = 0;
DateTime startTime = DateTime.Now;
/* 60 million fp operations? */
for (int index = 0; index < 10000000; index++)
{
x = 2.34 + index;
y = 5.67 + index;
z = (x * x) + (y * y) + index;
}
DateTime endTime = DateTime.Now;
Console.WriteLine(x + y + z);
TimeSpan span = endTime - startTime;
return span.Ticks / 60;
}
When we prepare a job for an
We can also make use of various other metrics, and client information. This is passed in the form of an
Prime Finder TaskLegion includes two demo tasks: Prime Finder, and Mutex Example. The first, Prime Finder, searches for prime numbers within a specified range. Each /// <summary>
/// The client-side implementation of the Prime Finder task.
/// This task searches specified ranges for prime numbers
/// and then returns the results to the master.
/// </summary>
public class PrimeFinderSlave : SlaveTask
{
static readonly ILog log
= LogManager.GetLog(typeof(PrimeFinderSlave));
List<long> primes;
public PrimeFinderSlave()
{
Run += AddNumberTaskClient_Run;
}
/// <summary>
/// Handles the Run event of the AddNumberTaskClient control.
/// This is where we do the processing for the task.
/// </summary>
void AddNumberTaskClient_Run(object sender, EventArgs e)
{
primes = new List<long>();
long start = Descriptor.Job.Start;
long end = Descriptor.Job.End;
string infoMessage = string.Format(
"PrimeFinderSlave starting.
Job Id is {0}. Range is {1} - {2}",
Descriptor.Job.Id, start, end);
log.Info(infoMessage);
StepsCompleted = 0;
StepsGoal = end - start;
for (long i = start; i < end; i++)
{
if (IsPrime(i))
{
primes.Add(i);
}
StepsCompleted++;
/* Sleep for a bit. */
if (i % 5000 == 0)
{
int sleepTime = 200;
System.Threading.Thread.Sleep(sleepTime);
}
}
Result = primes;
}
static bool IsPrime(long candidate)
{
for (int d = 2; d <= candidate / 2; d++)
{
if (candidate % d == 0)
{
return false;
}
}
return candidate > 1;
}
}
Once the Agent Task ExecutionThe
The process of executing a task is outlined in the next diagram in more detail. Here we see that when the Silverlight Agent begins execution in a browser, it automatically initiates a client side task. The
When the client-side Client-side Mutual ExclusionIn order to allow
When an
When using the While the
The following excerpt from the /// <summary>
/// This is the client side implementation for a task
/// that demonstrates the use of the <see cref="GridMonitor"/>.
/// </summary>
public class MutexExampleSlave : SlaveTask
{
static readonly ILog log
= LogManager.GetLog(typeof(MutexExampleSlave));
const int iterationsCount = 10;
GridSync exampleSync;
public MutexExampleSlave()
{
Run += MutexExampleTask_Run;
}
void MutexExampleTask_Run(object sender, EventArgs e)
{
long id = Descriptor.Job.Id;
string infoMessage = string.Format(
"MutexExampleSlave starting. Job Id is {0}.", id);
log.Info(infoMessage);
/* Get the client id for logging purposes. */
Guid clientId = new Guid(Descriptor.Job.CustomData.ToString());
/* The GridSync dispatches calls to the web service,
* and then on to the GridManager. */
exampleSync = new GridSync(typeof(MutexExampleSlave), "test");
StepsGoal = iterationsCount * 10;
for (int i = 0; i < iterationsCount; i++)
{
/* Here we attempt to enter the monitor.
* This is done automatically when
* we instantiate a GridMonitor.
* The Monitor will Exit when it is disposed.
* We may block here for a while,
* and wait for another agent
* to leave the using statement. */
using (new GridMonitor(exampleSync))
{
log.Info(string.Format(
"Locked id:{0} iteration:{1} cliendId: {2}",
id, i, clientId));
for (int j = 1; j <= 10; j++)
{
System.Threading.Thread.Sleep(200);
StepsCompleted = i * 10 + j;
}
}
log.Info(string.Format(
"Unlocked id:{0}, and now sleeping for 2 seconds.", id));
System.Threading.Thread.Sleep(2000);
}
/* Once we leave this method, the Complete event will be raised. */
}
}
The following excerpt from the protected virtual void Dispose(bool IsDisposing)
{
if (disposed)
{
return;
}
if (IsDisposing)
{
/* Allow other agents to enter using statement. */
ReleaseLock();
}
/* Free any unmanaged resources
* in this section. */
disposed = true;
}
The /// <summary>
/// This is the server side implementation for a task
/// that demonstrates the use of the GridMonitor.
/// </summary>
class MutexExampleMaster : MasterTask
{
long taskCounter;
const int runTimes = 100;
public MutexExampleMaster()
{
StepsGoal = runTimes;
}
/// <summary>
/// Gets the run data for the <see cref="Agent"/> slave task.
/// This data should encapsulate the task segment
/// that will be worked on by the slave. <seealso cref="Job"/>
/// </summary>
/// <param name="agent">The agent requesting the run data.</param>
/// <returns>The job for the agent to work on.</returns>
public override Job GetRunData(IAgent agent)
{
Job job = new Job(++taskCounter);
job.CustomData = agent.Id;
if (taskCounter >= runTimes)
{
throw new InvalidOperationException("Task is complete.");
}
StepsCompleted = taskCounter;
return job;
}
/// <summary>
/// Joins the specified task result. This is called
/// when a slave task completes its <see cref="Job"/>,
/// after having called <see cref="GetRunData"/>;
/// returning the results to be integrated
/// by the associated <see cref="MasterTask"/>.
/// </summary>
/// <param name="taskResult">The task result.</param>
public override void Join(TaskResult taskResult)
{
if (taskCounter > 100)
{
Completed = true;
}
}
}
Expiring CollectionsThe most difficult challenge in creating a volunteer Grid computing platform that uses web clients is the volitility of the network, and the transient nature of the clients. At no time can we ever be certain that a node is still connected. When a client connects we assign it a connected window. This is a timespan in which the client must call home to be deemed still alive. If we are engaging in a concurrent activity we must retake ownership of any locks that the client has when the client does not call home within the window. Lest a deadlock may occur. Also, if we don't free up our data relating to a connection, then we will have a memory leak. We, therefore, periodically timeout
Both work the same as their compatriots in the
The
Saving and retrieving data using the Isolated StoreLegion stores the number of tasks that have been processed by a user, using the Isolated Store. The Isolated Store is a virtual file system that is specific to each user. Thus, with multiple concurrent browsers for the same user, within the same domain, we are able to share data. I have provided a static
Saving data using the Isolated Storeusing (IsolatedStorageFile storageFile
= IsolatedStorageFile.GetUserStoreForApplication())
{
if (value == null || value.ToString().Trim().Length == 0)
{
storageFile.DeleteFile(fileName);
}
else
{
using (IsolatedStorageFileStream isoStream
= new IsolatedStorageFileStream(
fileName, FileMode.Create, storageFile))
{
using (StreamWriter writer = new StreamWriter(isoStream))
{
writer.Write(value);
}
}
}
}
Retrieving data using the Isolated Storeusing (IsolatedStorageFile storageFile
= IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream isoStream
= new IsolatedStorageFileStream(
fileName, FileMode.Open, storageFile))
{
using (StreamReader reader = new StreamReader(isoStream))
{
/* Read the to the end of the file. */
String storedValue = reader.ReadToEnd();
return storedValue;
}
}
}
How to write a Grid TaskSo we've seen how Legion works internally, but what is probably of more interest to you, is how to create your own custom task. All we need to do is extend two classes, and add the task to the config. The two example tasks I have included with this release will provide you with the essentials. First, create a new regular .NET class library project, and add a reference to the main Next, create a new Silverlight class library project, and add a reference to the SilverlightAgent project. This will contain the client-side code for the task. Create a new class and extend
Finally, define a new config element for the Task in the web.config of the website. This should include the fully qualified type name of the Future enhancements
ConclusionWeb based Volunteer Grid computing offers a tremendous opportunity to harness the unused resources of visitors to a web site. We are able to leverage this resource in the form of a virtual super computer, and it affords us the opportunity to provide services in a manner outside of the traditional client-server model. This article discussed the design and use of Legion, a Grid Computing framework that uses the Silverlight CLR to execute user definable tasks. We saw how Legion uses an ASP.NET application and web services to download tasks, upload result data, and provide grid-wide thread-safe operations for agents. We also looked at how client performance metrics, such as bandwidth and processor speed, may be used to tailor the workload of I hope you find this project useful. If so, then I'd appreciate it if you would rate it and/or leave feedback below. This will help me to make my next article better. Special ThanksTo Andre de Cavaignac of decav.com and LAB 49 for kindly allowing the use of his excellent WPF Line Graph. References
HistoryDecember 2007
October 26 2008
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||