1826 lines
70 KiB
Plaintext
1826 lines
70 KiB
Plaintext
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Microsoft Research Singularity
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// File: SPECWeb99.sg
|
|
//
|
|
// Note: The SPECWeb99 web app
|
|
//
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Diagnostics;
|
|
using Microsoft.SingSharp;
|
|
using Microsoft.SingSharp.Runtime;
|
|
using Microsoft.Singularity.Applications;
|
|
using Microsoft.Singularity.Channels;
|
|
using Microsoft.Singularity.Directory;
|
|
using Microsoft.Singularity.WebApps;
|
|
using Microsoft.Singularity.WebApps.Contracts;
|
|
using Microsoft.Singularity.Diagnostics.Contracts;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Web;
|
|
using FileSystem.Utils;
|
|
using System.Net.IP;
|
|
using Microsoft.Singularity.V1.Services;
|
|
using Microsoft.Contracts;
|
|
|
|
using Microsoft.Singularity.Io;
|
|
using Microsoft.Singularity.Configuration;
|
|
using Microsoft.SingSharp.Reflection;
|
|
using Microsoft.Singularity.Applications;
|
|
[assembly: Transform(typeof(WebAppResourceTransform))]
|
|
|
|
namespace Microsoft.Singularity.WebApps
|
|
{
|
|
[Category("WebApp")]
|
|
internal sealed class Parameters
|
|
{
|
|
[Endpoint]
|
|
public readonly TRef<WebAppContract.Exp:Start> webAppRef;
|
|
|
|
#if false
|
|
[Endpoint]
|
|
public readonly TRef<DirectoryServiceContract.Imp:Start> nsRef;
|
|
#endif
|
|
reflective private Parameters();
|
|
}
|
|
|
|
public class SPECWeb99WebApp : IWebApp
|
|
{
|
|
private const string ContentDir = "/fs/specweb";
|
|
private const string PostLogFile = ContentDir + "/sing_dyn/postlog";
|
|
private const string CustomAdsDir = ContentDir + "/sing_dyn";
|
|
private const string CustomAdsFile = CustomAdsDir + "/Custom.Ads";
|
|
private const string UserPersonalityDir = ContentDir + "/sing_dyn";
|
|
private const string UserPersonalityFile = UserPersonalityDir + "/User.Personality";
|
|
|
|
private const string CookieHeaderName = "Cookie";
|
|
private const string CookiePushHeaderName = "Set-Cookie";
|
|
|
|
private const uint GENDER_MASK = 0x30000000;
|
|
private const uint AGE_GROUP_MASK = 0x0f000000;
|
|
private const uint REGION_MASK = 0x00f00000;
|
|
private const uint INTEREST1_MASK = 0x000ffc00;
|
|
private const uint INTEREST2_MASK = 0x000003ff;
|
|
|
|
private const int CustomAdsLines = 360;
|
|
private const int CustomAdsLineLength = 39; // Including \n
|
|
private const int CustomAdsFileBytes = CustomAdsLines * CustomAdsLineLength;
|
|
private const int UserPersonalityLines = 360;
|
|
private const int UserPersonalityLineLength = 15; // Including \n
|
|
private const int UserPersonalityFileBytes = UserPersonalityLines * UserPersonalityLineLength;
|
|
|
|
private const long PostLogHeaderSize = 11; // Including \n
|
|
private const long PostLogRecordSize = 139; // Including \n
|
|
|
|
private const int SubstitutionPatternLength = 48; // <!WEB99CAD><IMG SRC="/file_set/dirNNNNN/classX_Y
|
|
private const int SubstituteNum1Pos = 34; // 0123456789012345678901234567890123^ ^ ^
|
|
private const int SubstituteNum2Pos = 45; // 012345678901234567890123456789012345678901234| |
|
|
private const int SubstituteNum3Pos = 47; // 01234567890123456789012345678901234567890123456|
|
|
|
|
private byte[] HtmlPreamble1, HtmlPreamble2, HtmlPreamble3, HtmlPostamble;
|
|
private byte[] PostLogInitData;
|
|
private byte[] MatchPattern;
|
|
|
|
private struct CustomAdsRecord
|
|
{ public uint demographics, weightings, minmatch, expiry; }
|
|
|
|
private CachedFile! userPersonalityFile;
|
|
private CachedFile! customAdsFile;
|
|
private Parameters! config;
|
|
|
|
// Used for TaskTimes.
|
|
private ProcessData [] processData;
|
|
private ProcessData [] processData2;
|
|
private TimeSpan startTime;
|
|
private TimeSpan endTime;
|
|
private TRef<ProcessContract.Imp:ReadyState> processRef;
|
|
|
|
|
|
private TRef<DirectoryServiceContract.Imp:Ready> epNS;
|
|
|
|
// HACKHACK to accelerate fetching from the filesystem, we pre-form a pool of
|
|
// connections directly to the FS. But in order to do that, we need to know where
|
|
// the filesystem is mounted. Currently, we hardcode that.
|
|
private Queue! fsContractPool = new Queue(); // lock on the queue object for consistency
|
|
private const string FSMountPoint = "/fs/";
|
|
|
|
|
|
[NotDelayed]
|
|
internal SPECWeb99WebApp(Parameters! config)
|
|
{
|
|
HtmlPreamble1 = Encoding.ASCII.GetBytes("<html>\n<head><title>SPECweb99 Dynamic GET & POST Test</title></head>\n<body>\n<p>SERVER_SOFTWARE = Singularity Cassini web server\n<p>REMOTE_ADDR = ");
|
|
HtmlPreamble2 = Encoding.ASCII.GetBytes("\n<p>SCRIPT_NAME = SPECWeb99WebApp\n<p>QUERY_STRING = ");
|
|
HtmlPreamble3 = Encoding.ASCII.GetBytes("\n<pre>\n");
|
|
HtmlPostamble = Encoding.ASCII.GetBytes("\n</pre>\n</body></html>\n");
|
|
|
|
PostLogInitData = Encoding.ASCII.GetBytes(" 0\n");
|
|
|
|
MatchPattern = Encoding.ASCII.GetBytes("<!WEB99CAD><IMG SRC=\"/file_set/dir");
|
|
|
|
customAdsFile = new CachedFile(CustomAdsFile, CustomAdsFileBytes);
|
|
userPersonalityFile = new CachedFile(UserPersonalityFile, UserPersonalityFileBytes);
|
|
this.config = config;
|
|
|
|
base();
|
|
assert PostLogInitData.Length == (int)PostLogHeaderSize;
|
|
start = DateTime.Now;
|
|
|
|
// Get a new directory service contract
|
|
epNS = new TRef<DirectoryServiceContract.Imp:Ready>(DirectoryService.NewClientEndpoint());
|
|
processRef = new TRef<ProcessContract.Imp:ReadyState>(GetProcessChannel());
|
|
}
|
|
|
|
private ProcessContract.Imp:ReadyState! GetProcessChannel()
|
|
{
|
|
DirectoryServiceContract.Imp:Ready nsImp = epNS.Acquire();
|
|
try {
|
|
ProcessContract.Imp! dsImp;
|
|
ProcessContract.Exp! dsExp;
|
|
ProcessContract.NewChannel(out dsImp, out dsExp);
|
|
ErrorCode errorOut = ErrorCode.NoError;
|
|
bool bound = SdsUtils.BindDSP(ProcessContract.ModuleName, nsImp, dsExp, out errorOut);
|
|
|
|
if (!bound) {
|
|
delete dsImp;
|
|
throw new Exception("Failed to bind to filesystem prefix");
|
|
}
|
|
else {
|
|
dsImp.RecvReady();
|
|
return dsImp;
|
|
}
|
|
}
|
|
finally {
|
|
epNS.Release(nsImp);
|
|
}
|
|
}
|
|
|
|
private void RecycleFSChannel([Claims] DirectoryServiceContract.Imp:Ready! dsImp)
|
|
{
|
|
lock (fsContractPool) {
|
|
fsContractPool.Enqueue(new TRef<DirectoryServiceContract.Imp:Ready> (dsImp));
|
|
Monitor.Pulse(fsContractPool);
|
|
}
|
|
}
|
|
|
|
private DirectoryServiceContract.Imp:Ready! GetFSChannel()
|
|
{
|
|
ulong startCycles = Processor.CycleCount;
|
|
ulong endCycles, ms;
|
|
|
|
lock(fsContractPool) {
|
|
endCycles = Processor.CycleCount;
|
|
ms = ((endCycles - startCycles) * 1000ul) / (ulong)Processor.CyclesPerSecond;
|
|
|
|
if (ms > 5) {
|
|
//DebugStub.WriteLine("Contention for file contracts queue: {0}",
|
|
//__arglist(ms));
|
|
}
|
|
|
|
if (fsContractPool.Count > 0) {
|
|
return ((TRef<DirectoryServiceContract.Imp:Ready>!)fsContractPool.Dequeue()).Acquire();
|
|
}
|
|
}
|
|
|
|
// All FS channels are in use; make a new one
|
|
startCycles = Processor.CycleCount;
|
|
DirectoryServiceContract.Imp:Ready nsImp = epNS.Acquire();
|
|
|
|
endCycles = Processor.CycleCount;
|
|
ms = ((endCycles - startCycles) * 1000ul) / (ulong)Processor.CyclesPerSecond;
|
|
|
|
if (ms > 5) {
|
|
//DebugStub.WriteLine("Contention for master namespace contract: {0}",
|
|
//__arglist(ms));
|
|
}
|
|
|
|
try {
|
|
DirectoryServiceContract.Imp! dsImp;
|
|
DirectoryServiceContract.Exp! dsExp;
|
|
DirectoryServiceContract.NewChannel(out dsImp, out dsExp);
|
|
ErrorCode errorOut = ErrorCode.NoError;
|
|
bool bound = SdsUtils.BindDSP(FSMountPoint, nsImp, dsExp, out errorOut);
|
|
|
|
if (!bound) {
|
|
delete dsImp;
|
|
throw new Exception("Failed to bind to filesystem prefix");
|
|
}
|
|
else {
|
|
dsImp.RecvSuccess();
|
|
return dsImp;
|
|
}
|
|
}
|
|
finally {
|
|
epNS.Release(nsImp);
|
|
}
|
|
}
|
|
|
|
private FileContract.Imp OpenFile(string! filePath)
|
|
{
|
|
//
|
|
// Take advantage of our cached FS connections for all requests
|
|
// heading for the file system
|
|
//
|
|
if (filePath.IndexOf(FSMountPoint) == 0) {
|
|
string suffix = filePath.Substring(FSMountPoint.Length,
|
|
filePath.Length - FSMountPoint.Length);
|
|
|
|
DirectoryServiceContract.Imp:Ready! nsImp = GetFSChannel();
|
|
|
|
try {
|
|
return FileUtils.OpenFile(suffix, nsImp);
|
|
}
|
|
finally {
|
|
RecycleFSChannel(nsImp);
|
|
}
|
|
} else {
|
|
return FileUtils.OpenFile(filePath);
|
|
}
|
|
}
|
|
|
|
DateTime start;
|
|
bool isRecording = false;
|
|
bool isDone = false;
|
|
public void ProcessRequest(IHttpRequest! request)
|
|
{
|
|
string uri = request.GetUriPath();
|
|
|
|
if ((uri == null) || (uri.Length == 0)) {
|
|
uri = "/";
|
|
}
|
|
|
|
if (uri == "/dynamic-content") {
|
|
HandleDynamicRequest(request);
|
|
} else {
|
|
// Treat as a request for a static file
|
|
|
|
if (uri.IndexOf(FSMountPoint) == 0) {
|
|
string suffix = uri.Substring(FSMountPoint.Length, uri.Length - FSMountPoint.Length);
|
|
// Serve using a FS contract from the pool
|
|
DirectoryServiceContract.Imp:Ready! nsImp = GetFSChannel(); // blocks
|
|
|
|
try {
|
|
WebAppFSUtils.ServeFSPath(suffix, request, nsImp);
|
|
}
|
|
finally {
|
|
RecycleFSChannel(nsImp);
|
|
}
|
|
|
|
} else {
|
|
// Serve with the single, general-purpose directory service channel
|
|
DebugStub.Break(); // TODO remove me
|
|
DirectoryServiceContract.Imp:Ready! nsImp = epNS.Acquire(); // blocks
|
|
|
|
try {
|
|
WebAppFSUtils.ServeFSPath(uri, request, nsImp);
|
|
}
|
|
finally {
|
|
epNS.Release(nsImp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Helper code
|
|
//
|
|
private void WriteErrorResponse(IHttpRequest! request, int error, string! message)
|
|
{
|
|
switch (error) {
|
|
case 400:
|
|
request.SendStatus(error, "Bad Request");
|
|
break;
|
|
case 404:
|
|
request.SendStatus(error, "Not Found");
|
|
break;
|
|
case 500:
|
|
request.SendStatus(error, "Internal Server Error");
|
|
break;
|
|
default:
|
|
request.SendStatus(error, "Error");
|
|
break;
|
|
}
|
|
request.SendHeader("Content-type", "text/html");
|
|
|
|
request.SendBodyData(HtmlPreamble1);
|
|
request.SendBodyData(Encoding.ASCII.GetBytes(request.GetRemoteAddress()));
|
|
request.SendBodyData(HtmlPreamble2);
|
|
|
|
string queryString = request.GetQueryString();
|
|
|
|
if (queryString != null) {
|
|
request.SendBodyData(Encoding.ASCII.GetBytes(queryString));
|
|
} else {
|
|
request.SendBodyData(Encoding.ASCII.GetBytes("<no query string supplied>"));
|
|
}
|
|
|
|
request.SendBodyData(HtmlPreamble3);
|
|
|
|
if ((message != null) && (message.Length > 0)) {
|
|
request.SendBodyData(Encoding.ASCII.GetBytes(message));
|
|
}
|
|
WriteResponseHtmlPostamble(request);
|
|
}
|
|
|
|
private void WriteResponseHtmlPreamble(IHttpRequest! request)
|
|
{
|
|
request.SendStatus(200, "OK");
|
|
request.SendHeader("Content-type", "text/html");
|
|
|
|
request.SendBodyData(HtmlPreamble1);
|
|
request.SendBodyData(Encoding.ASCII.GetBytes(request.GetRemoteAddress()));
|
|
request.SendBodyData(HtmlPreamble2);
|
|
|
|
string queryString = request.GetQueryString();
|
|
|
|
if (queryString != null) {
|
|
request.SendBodyData(Encoding.ASCII.GetBytes(queryString));
|
|
} else {
|
|
request.SendBodyData(Encoding.ASCII.GetBytes("<no query string supplied>"));
|
|
}
|
|
|
|
request.SendBodyData(HtmlPreamble3);
|
|
}
|
|
|
|
private void WriteResponseHtmlPostamble(IHttpRequest! request)
|
|
{
|
|
request.SendBodyData(HtmlPostamble);
|
|
}
|
|
|
|
private void WriteMessageResponse(IHttpRequest! request, string! message)
|
|
{
|
|
WriteResponseHtmlPreamble(request);
|
|
|
|
if ((message != null) && (message.Length > 0)) {
|
|
request.SendBodyData(Encoding.ASCII.GetBytes(message));
|
|
}
|
|
|
|
WriteResponseHtmlPostamble(request);
|
|
}
|
|
|
|
private void WriteFileResponse(IHttpRequest! request, string! filePath)
|
|
{
|
|
FileContract.Imp fileImp = OpenFile(filePath);
|
|
|
|
if (fileImp != null) {
|
|
WriteFileResponse(request, fileImp);
|
|
delete fileImp;
|
|
}
|
|
else {
|
|
WriteErrorResponse(request, 404, "Couldn't open file: " + filePath);
|
|
}
|
|
}
|
|
|
|
private void WriteFileResponse(IHttpRequest! request, FileContract.Imp! fileImp)
|
|
{
|
|
WriteResponseHtmlPreamble(request);
|
|
WebAppFSUtils.TransmitFileContents(fileImp, request);
|
|
WriteResponseHtmlPostamble(request);
|
|
}
|
|
|
|
private void HandleDynamicRequest(IHttpRequest! request)
|
|
{
|
|
string verb = request.GetVerb();
|
|
|
|
if (verb == "GET") {
|
|
HandleGET(request);
|
|
}
|
|
else if (verb == "POST") {
|
|
HandlePOST(request);
|
|
}
|
|
else {
|
|
WriteErrorResponse(request, 400, "Unknown HTTP verb: " + verb);
|
|
}
|
|
|
|
request.Done();
|
|
}
|
|
|
|
private bool ParseCookie(string! cookie, out uint myUser, out uint lastAd,
|
|
out string rejectString)
|
|
{
|
|
const string cookiePreamble = "my_cookie=user_id=";
|
|
const string lastAdPreamble = "last_ad=";
|
|
|
|
rejectString = null;
|
|
myUser = 0;
|
|
lastAd = 0;
|
|
|
|
if (! cookie.StartsWith(cookiePreamble)) {
|
|
rejectString = "Invalid cookie format (bad preamble): \"" + cookie + "\"";
|
|
return false;
|
|
}
|
|
|
|
int nextAmp = cookie.IndexOf('&', cookiePreamble.Length);
|
|
|
|
if (nextAmp == -1) {
|
|
rejectString = "Invalid cookie format (no ampersand): \"" + cookie + "\"";
|
|
return false;
|
|
}
|
|
|
|
string myUserStr = cookie.Substring(cookiePreamble.Length, nextAmp - cookiePreamble.Length);
|
|
|
|
try {
|
|
myUser = UInt32.Parse(myUserStr);
|
|
}
|
|
catch (Exception) {
|
|
rejectString = "Invalid cookie format (bad user_id value): \"" + cookie + "\"";
|
|
return false;
|
|
}
|
|
|
|
string remainderStr = cookie.Substring(nextAmp + 1);
|
|
|
|
if (!remainderStr.StartsWith(lastAdPreamble)) {
|
|
rejectString = "Invalid cookie format (no last_ad field): \"" + cookie + "\"";
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
lastAd = UInt32.Parse(remainderStr.Substring(lastAdPreamble.Length));
|
|
}
|
|
catch (Exception) {
|
|
rejectString = "Invalid cookie format (bad last_ad value): \"" + cookie + "\"";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private uint Get1970Time()
|
|
{
|
|
TimeSpan delta1970 = DateTime.Now - new DateTime(1970, 1, 1);
|
|
return (uint)delta1970.TotalSeconds;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Dynamic GET
|
|
//
|
|
|
|
private void HandleGET(IHttpRequest! request)
|
|
{
|
|
const string commandPrefix = "command/";
|
|
string queryString = request.GetQueryString();
|
|
|
|
if ((queryString != null) && (queryString.Length > 0)) {
|
|
if (queryString.StartsWith(commandPrefix)) {
|
|
HandleCommand(request, queryString.Substring(commandPrefix.Length));
|
|
} else {
|
|
// We must serve the file corresponding to
|
|
// the queryString. We may or may not have to
|
|
// perform substitution, depending on whether
|
|
// a cookie is provided.
|
|
string filePath = String.Format("{0}{1}", ContentDir, queryString);
|
|
string cookie = request.GetHeader(CookieHeaderName);
|
|
|
|
if ((cookie == null) || (cookie.Length == 0)) {
|
|
// Just serve the file normally
|
|
WriteFileResponse(request, filePath);
|
|
} else {
|
|
HandleSubstitution(request, filePath, cookie);
|
|
}
|
|
}
|
|
} else {
|
|
WriteErrorResponse(request, 400,
|
|
"A query string is required with a GET operation");
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Housekeeping commands
|
|
//
|
|
|
|
// Validate string only contains integers
|
|
private static string CheckDigits(string s)
|
|
{
|
|
if (s == null) return null;
|
|
for (int i = 0; i < s.Length; i++) {
|
|
if (s[i] < '0' || s[i] > '9') {
|
|
return null;
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
private bool RunCommand(string[]! commandLine, StringBuilder! output)
|
|
{
|
|
try {
|
|
// Load the manifest for the given command
|
|
DirectoryServiceContract.Imp ds = epNS.Acquire();
|
|
Manifest manifest = Binder.LoadManifest(ds, commandLine[0]);
|
|
epNS.Release(ds);
|
|
|
|
if (manifest == null) {
|
|
return false; // bad command
|
|
}
|
|
|
|
Process process;
|
|
|
|
if (manifest.HasParameters()) {
|
|
ParameterProcessor processor = new ParameterProcessor();
|
|
string action;
|
|
|
|
if (!processor.ProcessParameters(commandLine,
|
|
manifest, out process, out action)) {
|
|
// bad command line
|
|
return false;
|
|
}
|
|
|
|
if (manifest.SetEndpoints(process, action, false) < 0) {
|
|
// couldn't prepare service endpoints
|
|
return false;
|
|
}
|
|
} else {
|
|
process = new Process(commandLine, null, 2);
|
|
}
|
|
|
|
if (process == null) {
|
|
// Failed to create the process object
|
|
return false;
|
|
}
|
|
|
|
// Feed the process do-nothing input and output pipes
|
|
UnicodePipeContract.Imp! stdInImp, stdOutImp;
|
|
UnicodePipeContract.Exp! stdInExp, stdOutExp;
|
|
|
|
UnicodePipeContract.NewChannel(out stdInImp, out stdInExp);
|
|
UnicodePipeContract.NewChannel(out stdOutImp, out stdOutExp);
|
|
|
|
process.SetStartupEndpoint(0, (Endpoint * in ExHeap) stdInExp);
|
|
process.SetStartupEndpoint(1, (Endpoint * in ExHeap) stdOutImp);
|
|
|
|
process.Start();
|
|
|
|
// Capture the process output (this also prevents the child
|
|
// from hanging trying to write to the console)
|
|
bool stillAlive = true;
|
|
|
|
while (stillAlive) {
|
|
switch receive {
|
|
case stdOutExp.Write(char[]! in ExHeap buffer, offset, count) :
|
|
output.Append(Bitter.ToString2(buffer, offset, count));
|
|
stdOutExp.SendAckWrite(buffer);
|
|
break;
|
|
|
|
case unsatisfiable:
|
|
stillAlive = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
process.Join();
|
|
process.Dispose(true);
|
|
|
|
delete stdInImp;
|
|
delete stdOutExp;
|
|
|
|
return true;
|
|
}
|
|
catch (ProcessCreateException) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private void HandleReset(IHttpRequest! request, string[]! arguments)
|
|
{
|
|
string maxLoad = null;
|
|
string pointTime = null;
|
|
string maxThreads = null;
|
|
string expired1 = null;
|
|
string expired2 = null;
|
|
string urlRoot = null;
|
|
|
|
for (int i = 0; i < arguments.Length; i++) {
|
|
int remain = arguments.Length - i - 1;
|
|
|
|
// All values are key-value, except expiry list which
|
|
// has two values. If there's not at least one argument
|
|
// it's a bogus request.
|
|
if (remain == 0)
|
|
break;
|
|
|
|
if (arguments[i] == "maxload") {
|
|
maxLoad = CheckDigits(arguments[++i]);
|
|
if (maxLoad == null) {
|
|
WriteErrorResponse(request, 400, "Bad 'maxload' element in GET request");
|
|
return;
|
|
}
|
|
}
|
|
else if (arguments[i] == "pttime") {
|
|
pointTime = CheckDigits(arguments[++i]);
|
|
if (pointTime == null) {
|
|
WriteErrorResponse(request, 400, "Bad 'pttime' in element GET request");
|
|
return;
|
|
}
|
|
}
|
|
else if (arguments[i] == "maxthread") {
|
|
maxThreads = CheckDigits(arguments[++i]);
|
|
if (maxThreads == null) {
|
|
WriteErrorResponse(request, 400, "Bad 'maxthread' in element GET request");
|
|
return;
|
|
}
|
|
}
|
|
else if (arguments[i] == "exp") {
|
|
if (remain < 2) {
|
|
WriteErrorResponse(request, 400, "Bad 'expired_list' element in GET request");
|
|
return;
|
|
}
|
|
expired1 = CheckDigits(arguments[++i]);
|
|
if (expired1 == null) {
|
|
WriteErrorResponse(request, 400, "Bad 'expired_list[0]' element in GET request");
|
|
return;
|
|
}
|
|
expired2 = CheckDigits(arguments[++i]);
|
|
if (expired2 == null) {
|
|
WriteErrorResponse(request, 400, "Bad 'expired_list[1]' element in GET request");
|
|
return;
|
|
}
|
|
}
|
|
else if (arguments[i] == "urlroot") {
|
|
urlRoot = arguments[++i];
|
|
}
|
|
}
|
|
|
|
if (maxLoad == null) {
|
|
WriteErrorResponse(request, 400, "Missing 'maxload' element in GET request.");
|
|
return;
|
|
}
|
|
else if (pointTime == null) {
|
|
WriteErrorResponse(request, 400, "Missing 'pttime' element in GET request.");
|
|
return;
|
|
}
|
|
else if (maxThreads == null) {
|
|
WriteErrorResponse(request, 400, "Missing 'maxthreads' element in GET request.");
|
|
return;
|
|
}
|
|
else if ((expired1 == null) || (expired2 == null)) {
|
|
WriteErrorResponse(request, 400, "Missing 'expired_list' element in GET request.");
|
|
return;
|
|
}
|
|
else if (urlRoot == null) {
|
|
WriteErrorResponse(request, 400, "Missing 'url_root' element in GET request.");
|
|
return;
|
|
}
|
|
|
|
// These are allowed to fail
|
|
DirectoryUtils.CreateDirectory(CustomAdsDir);
|
|
FileUtils.DeleteFile(CustomAdsFile);
|
|
|
|
string []! cadgen = new string[] { "cadgen99",
|
|
"-d=" + CustomAdsDir,
|
|
"-e=" + pointTime,
|
|
"-t=" + maxThreads,
|
|
"-exp1=" + expired1,
|
|
"-exp2=" + expired2};
|
|
|
|
StringBuilder capturedOutput = new StringBuilder();
|
|
|
|
if (RunCommand(cadgen, capturedOutput) == false) {
|
|
WriteErrorResponse(request, 500, "Failed to run cadgen:\n\n" +
|
|
capturedOutput.ToString());
|
|
return;
|
|
}
|
|
|
|
if (!customAdsFile.Refresh()) {
|
|
WriteErrorResponse(request, 500, "Failed to load ads file");
|
|
return;
|
|
}
|
|
|
|
if (((!)customAdsFile.FileData).Length < CustomAdsFileBytes) {
|
|
WriteErrorResponse(request, 500, "Custom ads file was too small after generation");
|
|
return;
|
|
}
|
|
|
|
// It's OK for these to fail
|
|
DirectoryUtils.CreateDirectory(UserPersonalityDir);
|
|
FileUtils.DeleteFile(UserPersonalityFile);
|
|
|
|
string []! upfgen = new string[] {"/init/upfgen99",
|
|
"-c=" + UserPersonalityDir,
|
|
"-n=" + maxLoad,
|
|
"-t=" + maxThreads};
|
|
|
|
capturedOutput = new StringBuilder();
|
|
|
|
if (RunCommand(upfgen, capturedOutput) == false) {
|
|
WriteErrorResponse(request, 500, "Failed to run upfgen:\n\n" +
|
|
capturedOutput.ToString());
|
|
return;
|
|
}
|
|
|
|
if (!userPersonalityFile.Refresh()) {
|
|
WriteErrorResponse(request, 500, "Failed to load user personality file");
|
|
return;
|
|
}
|
|
|
|
if (!ResetPostLog()) {
|
|
WriteErrorResponse(request, 500, "Failed to reset the PostLog file");
|
|
return;
|
|
}
|
|
|
|
// Signal success with a blank message page
|
|
WriteMessageResponse(request, "");
|
|
|
|
// As the last part of reset, we set up the performance counters.
|
|
|
|
DebugStub.WriteLine("SPECweb99: Started Benchmark");
|
|
|
|
ProcessContract.Imp imp = processRef.Acquire();
|
|
processData = GetProcessData(imp);
|
|
processRef.Release(imp);
|
|
startTime = ProcessService.GetUpTime();
|
|
|
|
#if CAPTURE_PERFORMANCE_COUNTERS
|
|
for (uint u = 0; u < 16; u++) {
|
|
DebugStub.WritePerfCounter(0, 0);
|
|
}
|
|
|
|
if (IsAmd()) {
|
|
for (uint pmc = 0; pmc < 4; pmc++) {
|
|
Processor.WriteMsr(0xc0010004 + pmc, 0);
|
|
}
|
|
}
|
|
DebugStub.WritePerfCounter(7, Processor.GetCycleCount());
|
|
#endif
|
|
}
|
|
|
|
private static bool IsAmd()
|
|
{
|
|
uint eax;
|
|
uint ebx;
|
|
uint ecx;
|
|
uint edx;
|
|
Processor.ReadCpuid(0, out eax, out ebx, out ecx, out edx);
|
|
return (ebx == 0x68747541 && ecx == 0x444d4163 && edx == 0x69746e65);
|
|
}
|
|
|
|
private static void MyWrite(IHttpRequest! request, string! msg, params Object[]! args)
|
|
{
|
|
String text = String.Format(msg, args);
|
|
request.SendBodyData(Encoding.ASCII.GetBytes(text));
|
|
}
|
|
|
|
private void HandleFinish(IHttpRequest! request, string[]! arguments)
|
|
{
|
|
request.SendStatus(200, "OK");
|
|
request.SendHeader("Content-type", "text/plain");
|
|
|
|
#if CAPTURE_PERFORMANCE_COUNTERS
|
|
// Read the AMD and Singularity performance counters.
|
|
ulong b0 = 0;
|
|
ulong t0 = 0;
|
|
ulong e0 = 0;
|
|
ulong e1 = 0;
|
|
ulong e2 = 0;
|
|
ulong e3 = 0;
|
|
ulong p0 = 0;
|
|
ulong p1 = 0;
|
|
ulong p2 = 0;
|
|
ulong p3 = 0;
|
|
ulong z0 = 0;
|
|
ulong z1 = 0;
|
|
ulong z2 = 0;
|
|
ulong z3 = 0;
|
|
ulong z4 = 0;
|
|
ulong z5 = 0;
|
|
ulong z6 = 0;
|
|
ulong z8 = 0;
|
|
ulong z9 = 0;
|
|
ulong z10 = 0;
|
|
ulong z11 = 0;
|
|
ulong z12 = 0;
|
|
ulong z13 = 0;
|
|
ulong z14 = 0;
|
|
ulong z15 = 0;
|
|
if (IsAmd()) {
|
|
b0 = DebugStub.ReadPerfCounter(7);
|
|
t0 = Processor.GetCycleCount();
|
|
e0 = Processor.ReadMsr(0xc0010000);
|
|
e1 = Processor.ReadMsr(0xc0010001);
|
|
e2 = Processor.ReadMsr(0xc0010002);
|
|
e3 = Processor.ReadMsr(0xc0010003);
|
|
p0 = Processor.ReadPmc(0);
|
|
p1 = Processor.ReadPmc(1);
|
|
p2 = Processor.ReadPmc(2);
|
|
p3 = Processor.ReadPmc(3);
|
|
}
|
|
else {
|
|
b0 = DebugStub.ReadPerfCounter(7);
|
|
t0 = Processor.GetCycleCount();
|
|
}
|
|
z0 = DebugStub.ReadPerfCounter(0);
|
|
z1 = DebugStub.ReadPerfCounter(1);
|
|
z2 = DebugStub.ReadPerfCounter(2);
|
|
z3 = DebugStub.ReadPerfCounter(3);
|
|
z4 = DebugStub.ReadPerfCounter(4);
|
|
z5 = DebugStub.ReadPerfCounter(5);
|
|
z6 = DebugStub.ReadPerfCounter(6);
|
|
z8 = DebugStub.ReadPerfCounter(8);
|
|
z9 = DebugStub.ReadPerfCounter(9);
|
|
z10 = DebugStub.ReadPerfCounter(10);
|
|
z11 = DebugStub.ReadPerfCounter(11);
|
|
z12 = DebugStub.ReadPerfCounter(12);
|
|
z13 = DebugStub.ReadPerfCounter(13);
|
|
z14 = DebugStub.ReadPerfCounter(14);
|
|
z15 = DebugStub.ReadPerfCounter(15);
|
|
|
|
endTime = ProcessService.GetUpTime();
|
|
ProcessContract.Imp imp = processRef.Acquire();
|
|
processData2 = GetProcessData(imp);
|
|
processRef.Release(imp);
|
|
|
|
|
|
t0 = t0 - b0;
|
|
z5 += z8 + z9 + z10 + z11 + z12 + z13;
|
|
|
|
ulong n0 = t0;
|
|
if ((e0 & 0xff) == PerfEvtSel.CyclesNotHalted) {
|
|
n0 = p0;
|
|
}
|
|
else if ((e1 & 0xff) == PerfEvtSel.CyclesNotHalted) {
|
|
n0 = p1;
|
|
}
|
|
else if ((e2 & 0xff) == PerfEvtSel.CyclesNotHalted) {
|
|
n0 = p2;
|
|
}
|
|
else if ((e3 & 0xff) == PerfEvtSel.CyclesNotHalted) {
|
|
n0 = p3;
|
|
}
|
|
|
|
MyWrite(request, "{0,20}: {1,16} {2,16}\n", "Cycles", t0, n0);
|
|
MyWrite(request, "{0,20}: {1,16:f3}% {2,16:f3}%\n",
|
|
PerfEvtSel.ToString(e0),
|
|
(p0 * 100.0) / t0,
|
|
(p0 * 100.0) / n0);
|
|
MyWrite(request, "{0,20}: {1,16:f3}% {2,16:f3}%\n",
|
|
PerfEvtSel.ToString(e1),
|
|
(p1 * 100.0) / t0,
|
|
(p1 * 100.0) / n0);
|
|
MyWrite(request, "{0,20}: {1,16:f3}% {2,16:f3}%\n",
|
|
PerfEvtSel.ToString(e2),
|
|
(p2 * 100.0) / t0,
|
|
(p2 * 100.0) / n0);
|
|
MyWrite(request, "{0,20}: {1,16:f3}% {2,16:f3}%\n",
|
|
PerfEvtSel.ToString(e3),
|
|
(p3 * 100.0) / t0,
|
|
(p3 * 100.0) / n0);
|
|
MyWrite(request, "{0,20}: {1,16:f3}% {2,16:f3}%\n",
|
|
"App GC",
|
|
(z5 * 100.0) / t0,
|
|
(z5 * 100.0) / n0);
|
|
MyWrite(request, "{0,20}: {1,16:f3}% {2,16:f3}%\n",
|
|
"SpecWeb GC",
|
|
(z8 * 100.0) / t0,
|
|
(z8 * 100.0) / n0);
|
|
MyWrite(request, "{0,20}: {1,16:f3}% {2,16:f3}%\n",
|
|
"Cassini GC",
|
|
(z9 * 100.0) / t0,
|
|
(z9 * 100.0) / n0);
|
|
MyWrite(request, "{0,20}: {1,16:f3}% {2,16:f3}%\n",
|
|
"NetStack GC",
|
|
(z10 * 100.0) / t0,
|
|
(z10 * 100.0) / n0);
|
|
MyWrite(request, "{0,20}: {1,16:f3}% {2,16:f3}%\n",
|
|
"NIC GC",
|
|
(z11 * 100.0) / t0,
|
|
(z11 * 100.0) / n0);
|
|
MyWrite(request, "{0,20}: {1,16:f3}% {2,16:f3}%\n",
|
|
"FS GC",
|
|
(z12 * 100.0) / t0,
|
|
(z12 * 100.0) / n0);
|
|
MyWrite(request, "{0,20}: {1,16:f3}% {2,16:f3}%\n",
|
|
"Disk GC",
|
|
(z13 * 100.0) / t0,
|
|
(z13 * 100.0) / n0);
|
|
MyWrite(request, "{0,20}: {1,16:f3}% {2,16:f3}%\n",
|
|
"Kernel GC",
|
|
(z6 * 100.0) / t0,
|
|
(z6 * 100.0) / n0);
|
|
MyWrite(request, "{0,20}: {1,16}\n", "FAT Found", z0);
|
|
MyWrite(request, "{0,20}: {1,16}\n", "FAT In Use", z1);
|
|
MyWrite(request, "{0,20}: {1,16}\n", "FAT Miss Free", z2);
|
|
MyWrite(request, "{0,20}: {1,16}\n", "FAT Miss Evict", z3);
|
|
MyWrite(request, "{0,20}: {1,16}\n", "Threads", z4);
|
|
MyWrite(request, "{0,20}: {1,16}\n", "PerfCounter 14", z14);
|
|
MyWrite(request, "{0,20}: {1,16}\n", "PerfCounter 15", z15);
|
|
|
|
if (!IsAmd()) {
|
|
MyWrite(request, "Intel processor, PMC values will be zero.\n");
|
|
}
|
|
#endif
|
|
|
|
// Prep for display
|
|
if (this.processData != null) {
|
|
Hashtable processName = new Hashtable(processData.Length);
|
|
for (int i = 0; i < processData.Length; i++) {
|
|
ProcessData pd = processData[i];
|
|
if (pd == null) {
|
|
continue;
|
|
}
|
|
processName.Add(pd.id, pd);
|
|
}
|
|
|
|
// Display the results
|
|
double actualSpan = (double) ((endTime - startTime).TotalMilliseconds);
|
|
MyWrite(request, "\n");
|
|
MyWrite(request, "PID Task Name total(ms) % tot\n");
|
|
MyWrite(request, "=== =================== ========== =====\n");
|
|
double totalPercent = 0;
|
|
//long durationMs = monitorSeconds*1000;
|
|
double durationMs = actualSpan;
|
|
double totalTime;
|
|
double deltaTotal;
|
|
string pattern = "###,##0.00";
|
|
string percentPattern = "#0.00";
|
|
|
|
for (int i = 0; i < processData2.Length; i++) {
|
|
ProcessData pd2 = processData2[i];
|
|
if (pd2 == null) {
|
|
continue;
|
|
}
|
|
|
|
totalTime = (double)pd2.totalTime;
|
|
|
|
double percent = 0;
|
|
ProcessData p = (ProcessData) processName[pd2.id];
|
|
if (p != null) {
|
|
if (p.name == pd2.name) {
|
|
totalTime -= p.totalTime;
|
|
}
|
|
}
|
|
|
|
totalTime /= DateTime.TicksPerMillisecond;
|
|
|
|
percent = durationMs == 0 ? 0 : (double) (totalTime / durationMs) * 100 ;
|
|
|
|
//percent of time of this process spent in GC
|
|
if (totalTime > 0) {
|
|
MyWrite(request, "{0,3} {1,-19} {2,10} {3,5}\n",
|
|
pd2.id,
|
|
pd2.name,
|
|
totalTime.ToString(pattern),
|
|
percent.ToString(percentPattern));
|
|
totalPercent += percent;
|
|
}
|
|
}
|
|
|
|
MyWrite(request, " ======\n");
|
|
MyWrite(request, " {0,5} accounted for\n",
|
|
totalPercent.ToString("00.00"));
|
|
}
|
|
DebugStub.WriteLine("SPECweb99: Finished Benchmark");
|
|
}
|
|
|
|
private void HandleCommand(IHttpRequest! request, string! command)
|
|
{
|
|
if (command == "Fetch") {
|
|
// Lock on this to protect the PostLog
|
|
lock (this) {
|
|
FileContract.Imp fileImp = OpenFile(PostLogFile);
|
|
|
|
if (fileImp != null) {
|
|
WriteFileResponse(request, fileImp);
|
|
delete fileImp;
|
|
}
|
|
else {
|
|
WriteErrorResponse(request, 404, "Error opening PostLog file");
|
|
}
|
|
}
|
|
}
|
|
else if (command.StartsWith("Reset&")) {
|
|
HandleReset(request,
|
|
command.Split(new char [] { '&', '=', ',' }));
|
|
}
|
|
else if (command.StartsWith("Finish")) {
|
|
HandleFinish(request,
|
|
command.Split(new char [] { '&', '=', ',' }));
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Dynamic POST support
|
|
//
|
|
|
|
private void HandlePOST(IHttpRequest! request)
|
|
{
|
|
string cookie = request.GetHeader(CookieHeaderName);
|
|
|
|
if ((cookie == null) || (cookie.Length == 0)) {
|
|
WriteErrorResponse(request, 400, "POST requires a 'Cookie' header");
|
|
return;
|
|
}
|
|
|
|
byte[] bodyData = request.GetBodyData();
|
|
|
|
if ((bodyData == null) || (bodyData.Length == 0)) {
|
|
WriteErrorResponse(request, 400, "POST requires body data");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Parse the POST data
|
|
//
|
|
string urlRoot = null;
|
|
int dirNum = -1, classNum = -1, fileNum = -1, clientNum = -1;
|
|
string body = Encoding.ASCII.GetString(bodyData);
|
|
string[] fragments = body.Split(new char[] { '=', '&' } );
|
|
|
|
for (int i = 0; i < fragments.Length; i += 2) {
|
|
if (fragments[i] == "urlroot") {
|
|
urlRoot = fragments[i + 1];
|
|
}
|
|
else if (fragments[i] == "dir") {
|
|
try {
|
|
dirNum = Int32.Parse(fragments[i + 1]);
|
|
}
|
|
catch (Exception) {
|
|
WriteErrorResponse(request, 400, "Bad 'dir' element in POST data");
|
|
return;
|
|
}
|
|
}
|
|
else if (fragments[i] == "class") {
|
|
try {
|
|
classNum = Int32.Parse(fragments[i + 1]);
|
|
}
|
|
catch (Exception) {
|
|
WriteErrorResponse(request, 400, "Bad 'class' element in POST data");
|
|
return;
|
|
}
|
|
}
|
|
else if (fragments[i] == "num") {
|
|
try {
|
|
fileNum = Int32.Parse(fragments[i + 1]);
|
|
}
|
|
catch (Exception) {
|
|
WriteErrorResponse(request, 400, "Bad 'num' element in POST data");
|
|
return;
|
|
}
|
|
|
|
}
|
|
else if (fragments[i] == "client") {
|
|
try {
|
|
clientNum = Int32.Parse(fragments[i + 1]);
|
|
}
|
|
catch (Exception) {
|
|
WriteErrorResponse(request, 400, "Bad 'client' element in POST data");
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
WriteErrorResponse(request, 400, "Invalid POST data item \"" + fragments[i] + "\"");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (urlRoot == null) {
|
|
WriteErrorResponse(request, 400, "Missing 'urlroot' item in POST data");
|
|
return;
|
|
}
|
|
else if (dirNum == -1) {
|
|
WriteErrorResponse(request, 400, "Missing 'dir' item in POST data");
|
|
return;
|
|
}
|
|
else if (classNum == -1) {
|
|
WriteErrorResponse(request, 400, "Missing 'class' item in POST data");
|
|
return;
|
|
}
|
|
else if (fileNum == -1) {
|
|
WriteErrorResponse(request, 400, "Missing 'num' item in POST data");
|
|
return;
|
|
}
|
|
else if (clientNum == -1) {
|
|
WriteErrorResponse(request, 400, "Missing 'client' item in POST data");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Parse the cookie data
|
|
//
|
|
string cookieReject;
|
|
uint myCookie, lastAd;
|
|
|
|
if (!ParseCookie(cookie, out myCookie, out lastAd, out cookieReject)) {
|
|
WriteErrorResponse(request, 400, (!)cookieReject);
|
|
return;
|
|
}
|
|
|
|
string filePath = String.Format("{0}{1}dir{2}/class{3}_{4}", ContentDir, urlRoot,
|
|
dirNum.ToString("d05"), classNum, fileNum);
|
|
|
|
// Write out all the parsed data
|
|
if (!AppendToPostLog((uint)dirNum, (uint)classNum, (uint)fileNum,
|
|
(uint)clientNum, filePath, (uint)myCookie)) {
|
|
WriteErrorResponse(request, 400, "Error writing to PostLog file");
|
|
return;
|
|
}
|
|
|
|
FileContract.Imp fileImp = OpenFile(filePath);
|
|
|
|
if(fileImp != null) {
|
|
request.SendHeader(CookiePushHeaderName, "my_cookie=" + myCookie.ToString());
|
|
WriteFileResponse(request, fileImp);
|
|
delete fileImp;
|
|
} else {
|
|
WriteErrorResponse(request, 404, "Couldn't open file: \"" + filePath + "\"");
|
|
return;
|
|
}
|
|
}
|
|
|
|
private bool ResetPostLog()
|
|
{
|
|
lock (this) {
|
|
// OK to ignore the result of this; the file
|
|
// may not exist.
|
|
FileUtils.DeleteFile(PostLogFile);
|
|
|
|
if (FileUtils.CreateFile(PostLogFile) != 0) {
|
|
return false;
|
|
}
|
|
|
|
FileContract.Imp fileImp = OpenFile(PostLogFile);
|
|
|
|
if (fileImp == null) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
long written = FileUtils.Write(fileImp, 0, PostLogInitData);
|
|
|
|
if (written < PostLogInitData.Length) {
|
|
return false;
|
|
}
|
|
} finally {
|
|
delete fileImp;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool AppendToPostLog(uint dirNum, uint classNum, uint fileNum,
|
|
uint clientNum, string fileName, uint cookieNum)
|
|
{
|
|
lock (this) {
|
|
FileContract.Imp fileImp = OpenFile(PostLogFile);
|
|
|
|
if (fileImp == null) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
// First, read in the header so we can increment the record count
|
|
byte[] header = new byte[PostLogHeaderSize];
|
|
if (FileUtils.Read(fileImp, 0,PostLogHeaderSize , 0, header) != PostLogHeaderSize) {
|
|
return false;
|
|
}
|
|
|
|
int numExistingRecords;
|
|
|
|
try {
|
|
numExistingRecords = Int32.Parse(
|
|
Encoding.ASCII.GetString(header, 0, PostLogHeaderSize - 1));
|
|
}
|
|
catch(Exception) {
|
|
return false;
|
|
}
|
|
|
|
// Increment the record count and rewrite the header
|
|
string newHeaderStr = String.Format("{0,10}\n", numExistingRecords + 1);
|
|
byte[] newHeader = Encoding.ASCII.GetBytes(newHeaderStr);
|
|
assert newHeader.Length == (int)PostLogHeaderSize;
|
|
|
|
if (FileUtils.Write(fileImp, 0, newHeader) != PostLogHeaderSize) {
|
|
return false;
|
|
}
|
|
|
|
// Write a new record to the appropriate offset in the file
|
|
long newRecordOffset = (numExistingRecords * PostLogRecordSize) + PostLogHeaderSize;
|
|
string newEntryStr = String.Format(
|
|
"{0,10} {1,10} {2,10} {3,5} {4,2} {5,2} {6,10} {7,-60} {8,10} {9,10}\n",
|
|
numExistingRecords + 1, Get1970Time(), ProcessService.GetCurrentProcessId(),
|
|
dirNum, classNum, fileNum, clientNum, fileName, ProcessService.GetCurrentProcessId(),
|
|
cookieNum);
|
|
|
|
byte[] newEntry = Encoding.ASCII.GetBytes(newEntryStr);
|
|
assert newEntry.Length == (int)PostLogRecordSize;
|
|
|
|
if (FileUtils.Write(fileImp, newRecordOffset, newEntry) != PostLogRecordSize) {
|
|
return false;
|
|
}
|
|
}
|
|
finally {
|
|
delete fileImp;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Substitution / ad-rotation support
|
|
//
|
|
|
|
private void SubstituteAt(byte[]! in ExHeap buffer, int offset, uint adId)
|
|
{
|
|
assert offset + SubstitutionPatternLength <= buffer.Length;
|
|
string! num1 = (!)(adId / 36).ToString("d05");
|
|
string! num2 = (!)((adId % 36) / 9).ToString();
|
|
string! num3 = (!)(adId % 9).ToString();
|
|
|
|
for (int i = 0; i < 5; i++) {
|
|
buffer[offset + SubstituteNum1Pos + i] = (byte)num1[i];
|
|
}
|
|
|
|
buffer[offset + SubstituteNum2Pos] = (byte)num2[0]; // should be a single char
|
|
buffer[offset + SubstituteNum3Pos] = (byte)num3[0]; // ditto
|
|
}
|
|
|
|
private int SubstituteInBlock(byte[]! in ExHeap buffer, uint maxBytes, uint adId)
|
|
{
|
|
// NOTE the idea here is to find the randomly placed
|
|
// markers in the file block and doctor them. The only
|
|
// difficulty arises if there is a match of the prefix
|
|
// of our search pattern at the end of a block (that is,
|
|
// the pattern to match straddles a block boundary).
|
|
// As a naive solution to this, if we detect such a case, we
|
|
// return an amount to step back in the file so the next block
|
|
// will contain the entire match-pattern. If we were smarter
|
|
// we could keep state between invocations, instead.
|
|
int startMatch = 0;
|
|
int matchDepth = 0;
|
|
int matchPatternLength = MatchPattern.Length;
|
|
|
|
// Make this unsafe to skip the insane overhead of non-hoisted
|
|
// ExHeap vector accesses
|
|
unsafe {
|
|
fixed (byte *pSrc = &buffer[0]) {
|
|
fixed (byte *pMatch = &MatchPattern[0]) {
|
|
while (startMatch < maxBytes) {
|
|
if (pSrc[startMatch] == pMatch[0]) {
|
|
do {
|
|
++matchDepth;
|
|
|
|
if (matchDepth == matchPatternLength) {
|
|
// Found a match!
|
|
SubstituteAt(buffer, startMatch, adId);
|
|
}
|
|
|
|
if (startMatch + matchDepth >= maxBytes) {
|
|
// We were in the middle of a match
|
|
// but hit the end of the buffer.
|
|
// Step back by one disk sector
|
|
return 512;
|
|
}
|
|
} while (pSrc[startMatch + matchDepth] == pMatch[matchDepth]);
|
|
|
|
startMatch += matchDepth;
|
|
matchDepth = 0;
|
|
}
|
|
else {
|
|
startMatch++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
private void WriteSubstitutedFile(IHttpRequest! request,
|
|
string! filePath, uint adId)
|
|
{
|
|
FileContract.Imp fileImp = OpenFile(filePath);
|
|
|
|
if (fileImp == null) {
|
|
WriteErrorResponse(request, 404, "Couldn't open file " + filePath);
|
|
return;
|
|
}
|
|
|
|
WriteResponseHtmlPreamble(request);
|
|
|
|
try {
|
|
long readSize = 1024 * 16; // 16KB
|
|
long readOffset = 0;
|
|
bool keepReading = true;
|
|
|
|
do {
|
|
byte[] in ExHeap buf = new[ExHeap] byte[readSize];
|
|
fileImp.SendRead(buf, 0, readOffset, readSize);
|
|
|
|
switch receive {
|
|
case fileImp.AckRead( _buf, long bytesRead, int error) :
|
|
if ((error != 0) || (_buf == null)) {
|
|
if (_buf != null)
|
|
{ delete _buf; }
|
|
|
|
keepReading = false;
|
|
}
|
|
else {
|
|
byte[]! in ExHeap original = _buf;
|
|
bool atEnd = (bytesRead < readSize);
|
|
|
|
if (bytesRead > 0) {
|
|
int stepBack = SubstituteInBlock(original, (uint)bytesRead, adId);
|
|
|
|
// Ignore any partial matches that occur at the very
|
|
// end of the file
|
|
if (atEnd) {
|
|
stepBack = 0;
|
|
}
|
|
|
|
if ((stepBack > 0) || (bytesRead < readSize)) {
|
|
// Discard the excess portion of the block
|
|
delete
|
|
Bitter.SplitOff(
|
|
ref original,
|
|
(int)(bytesRead - stepBack)
|
|
);
|
|
}
|
|
|
|
request.SendBodyData(original);
|
|
readOffset += (bytesRead - stepBack);
|
|
} else {
|
|
// By coincidence, our previous read took us to
|
|
// exactly the end of the file. We should
|
|
// optimize this away somehow
|
|
delete original;
|
|
}
|
|
|
|
keepReading = !atEnd;
|
|
}
|
|
break;
|
|
|
|
case fileImp.ChannelClosed() :
|
|
keepReading = false;
|
|
break;
|
|
}
|
|
} while(keepReading);
|
|
}
|
|
finally {
|
|
delete fileImp;
|
|
}
|
|
|
|
WriteResponseHtmlPostamble(request);
|
|
}
|
|
|
|
|
|
private bool RetrieveAdData(uint adIndex, out uint demographics,
|
|
out uint weightings, out uint minMatch,
|
|
out uint expiry)
|
|
{
|
|
demographics = weightings = minMatch = expiry = 0;
|
|
|
|
byte[] fileData = customAdsFile.FileData;
|
|
|
|
if (fileData == null) {
|
|
return false;
|
|
}
|
|
|
|
if (fileData.Length < CustomAdsFileBytes) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
int startLine = (int)adIndex * (int)CustomAdsLineLength;
|
|
|
|
// debug!
|
|
string wholeLine = Encoding.ASCII.GetString(fileData, startLine, CustomAdsLineLength);
|
|
|
|
if (wholeLine == null) {
|
|
DebugStub.Break();
|
|
}
|
|
|
|
string demoStr = Encoding.ASCII.GetString(fileData, startLine + 6, 8);
|
|
string weightStr = Encoding.ASCII.GetString(fileData, startLine + 15, 8);
|
|
string minMatchStr = Encoding.ASCII.GetString(fileData, startLine + 24, 3);
|
|
string expiryStr = Encoding.ASCII.GetString(fileData, startLine + 28, 10);
|
|
|
|
demographics = UInt32.Parse(demoStr,
|
|
System.Globalization.NumberStyles.AllowHexSpecifier |
|
|
System.Globalization.NumberStyles.AllowLeadingWhite);
|
|
|
|
weightings = UInt32.Parse(weightStr,
|
|
System.Globalization.NumberStyles.AllowHexSpecifier |
|
|
System.Globalization.NumberStyles.AllowLeadingWhite);
|
|
|
|
minMatch = UInt32.Parse(minMatchStr, System.Globalization.NumberStyles.AllowLeadingWhite);
|
|
expiry = UInt32.Parse(expiryStr, System.Globalization.NumberStyles.AllowLeadingWhite);
|
|
}
|
|
catch (Exception) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool RetrieveUserPersonality(uint userIndex, out uint demographics)
|
|
{
|
|
demographics = 0;
|
|
byte[] fileData = userPersonalityFile.FileData;
|
|
|
|
if (fileData == null) {
|
|
return false;
|
|
}
|
|
|
|
if (fileData.Length < UserPersonalityFileBytes) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
int startLine = (int)userIndex * (int)UserPersonalityLineLength;
|
|
|
|
// debug!
|
|
string wholeLine = Encoding.ASCII.GetString(fileData, startLine,
|
|
UserPersonalityLineLength);
|
|
|
|
if (wholeLine == null) {
|
|
DebugStub.Break();
|
|
}
|
|
|
|
string demoStr = Encoding.ASCII.GetString(fileData, startLine + 6, 8);
|
|
|
|
demographics = UInt32.Parse(demoStr,
|
|
System.Globalization.NumberStyles.AllowHexSpecifier |
|
|
System.Globalization.NumberStyles.AllowLeadingWhite);
|
|
}
|
|
catch (Exception) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private uint GenderWt(uint demVal)
|
|
{ return (demVal & 0x000f0000) >> 16; }
|
|
|
|
private uint AgeGroupWt(uint demVal)
|
|
{ return (demVal & 0x0000f000) >> 12; }
|
|
|
|
private uint RegionWt(uint demVal)
|
|
{ return (demVal & 0x00000f00) >> 8; }
|
|
|
|
private uint InterestWt(uint demVal)
|
|
{ return (demVal & 0x000000f0) >> 4; }
|
|
|
|
private uint Interest2Wt(uint demVal)
|
|
{ return (demVal & 0x0000000f); }
|
|
|
|
private void HandleSubstitution(IHttpRequest! request,
|
|
string! filePath, string! cookie)
|
|
{
|
|
bool isSmallFile =
|
|
(filePath.IndexOf("class1") != -1) ||
|
|
(filePath.IndexOf("class2") != -1);
|
|
|
|
string cookieReject;
|
|
uint userId, lastAd;
|
|
|
|
if (! ParseCookie(cookie, out userId, out lastAd, out cookieReject)) {
|
|
WriteErrorResponse(request, 400, (!)cookieReject);
|
|
return;
|
|
}
|
|
|
|
if (userId < 10000) {
|
|
WriteErrorResponse(request, 400, "UserID was less than 10,000");
|
|
return;
|
|
}
|
|
|
|
uint userIndex = userId - 10000;
|
|
uint userPersonality;
|
|
uint adIndex = lastAd + 1;
|
|
|
|
if (adIndex >= CustomAdsLines) {
|
|
adIndex = 0;
|
|
}
|
|
|
|
if (!RetrieveUserPersonality(userIndex, out userPersonality)) {
|
|
// BUGBUG: the spec is unclear here; there is no adId to use.
|
|
request.SendHeader(CookiePushHeaderName,
|
|
"found_cookie=Ad_Id=-1&Ad_weight=00&Expired=1");
|
|
WriteFileResponse(request, filePath);
|
|
}
|
|
|
|
uint adWeight = 0;
|
|
bool expired = false;
|
|
|
|
while (adIndex != lastAd) {
|
|
adWeight = 0;
|
|
expired = false;
|
|
uint adDemographics, weightings, minMatch, expiry;
|
|
|
|
bool gotAd = RetrieveAdData(adIndex, out adDemographics,
|
|
out weightings, out minMatch, out expiry);
|
|
assert gotAd; // we checked the cached adfile above
|
|
|
|
uint combinedDemographics = userPersonality & adDemographics;
|
|
|
|
if ( (combinedDemographics & GENDER_MASK) != 0 ) {
|
|
adWeight = adWeight + GenderWt(adDemographics);
|
|
}
|
|
|
|
if ( (combinedDemographics & AGE_GROUP_MASK) != 0 ) {
|
|
adWeight = adWeight + AgeGroupWt(adDemographics);
|
|
}
|
|
|
|
if ( (combinedDemographics & REGION_MASK) != 0 ) {
|
|
adWeight = adWeight + RegionWt(adDemographics);
|
|
}
|
|
|
|
if ( (combinedDemographics & INTEREST1_MASK) != 0 ) {
|
|
adWeight = adWeight + InterestWt(adDemographics);
|
|
}
|
|
|
|
if ( (combinedDemographics & INTEREST2_MASK) != 0 ) {
|
|
adWeight = adWeight + Interest2Wt(adDemographics);
|
|
}
|
|
|
|
if ( adWeight >= minMatch ) {
|
|
// Found an acceptable match
|
|
uint now1970Delta = Get1970Time();
|
|
|
|
if (now1970Delta > expiry) {
|
|
expired = true;
|
|
}
|
|
|
|
request.SendHeader(CookiePushHeaderName,
|
|
"found_cookie=Ad_id=" + adIndex +
|
|
"&Ad_weight=" + adWeight +
|
|
"&Expired=" + (expired ? "1" : "0"));
|
|
|
|
if (isSmallFile) {
|
|
WriteSubstitutedFile(request, filePath, adIndex);
|
|
return;
|
|
}
|
|
else {
|
|
WriteFileResponse(request, filePath);
|
|
return;
|
|
}
|
|
}
|
|
|
|
adIndex++;
|
|
if (adIndex >= CustomAdsLines) {
|
|
adIndex = 0; // wrap around
|
|
}
|
|
}
|
|
|
|
request.SendHeader(CookiePushHeaderName,
|
|
"found_cookie=Ad_id=" + adIndex +
|
|
"&Ad_weight=" + adWeight +
|
|
"&Expired=" + (expired ? "1" : "0"));
|
|
|
|
if (isSmallFile) {
|
|
WriteSubstitutedFile(request, filePath, adIndex);
|
|
}
|
|
else {
|
|
WriteFileResponse(request, filePath);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// The code below gets used when this webapp is compiled
|
|
// to a stand-alone executable
|
|
//
|
|
|
|
internal static int AppMain(Parameters! config)
|
|
{
|
|
WebAppContract.Exp conn = config.webAppRef.Acquire();
|
|
conn.SendWebAppReady();
|
|
|
|
SPECWeb99WebApp webApp = new SPECWeb99WebApp(config);
|
|
Driver.ServiceChannel(webApp, conn);
|
|
delete conn;
|
|
return 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Code used to retrieve thread times.
|
|
//
|
|
public class ProcessData
|
|
{
|
|
public int id;
|
|
public string name;
|
|
public long memBytes;
|
|
public long peakMemBytes;
|
|
public long commMemBlocks;
|
|
public long commMemBytes;
|
|
public long handlePages;
|
|
public int[] tids;
|
|
public long totalTime;
|
|
public long deadThreadTime;
|
|
public long deadThreadCount;
|
|
public int gcCount;
|
|
public long gcTotalTime;
|
|
public long gcTotalBytes;
|
|
|
|
public ProcessData() {
|
|
tids = null;
|
|
}
|
|
}
|
|
|
|
public static int []! GetProcessIDs(ProcessContract.Imp:ReadyState! imp)
|
|
{
|
|
int [] ids = null;
|
|
int[]! in ExHeap xids;
|
|
imp.SendGetProcessIDs();
|
|
imp.RecvProcessIDs(out xids);
|
|
// REVIEW: The kernel process is being returned twice so we're
|
|
// skipping one of the entries if the process ID matches.
|
|
int startIndex = 0;
|
|
if (xids[0] == xids[1])
|
|
startIndex++;
|
|
ids = new int[xids.Length - startIndex];
|
|
for (int i = startIndex; i < xids.Length; i++)
|
|
{
|
|
ids[i - startIndex] = xids[i];
|
|
}
|
|
delete xids;
|
|
return ids;
|
|
}
|
|
|
|
public static int [] GetProcessThreadIDs(ProcessContract.Imp:ReadyState! imp, int procID)
|
|
{
|
|
imp.SendGetProcessThreadIDs(procID);
|
|
int [] retVal = null;
|
|
|
|
switch receive
|
|
{
|
|
case imp.NotFound() :
|
|
break;
|
|
|
|
case imp.ProcessThreadIDs(int[]! in ExHeap tids) :
|
|
retVal = new int[tids.Length];
|
|
for (int i=0; i<tids.Length; i++) {
|
|
retVal[i] = tids[i];
|
|
}
|
|
delete tids;
|
|
break;
|
|
|
|
case imp.ChannelClosed() :
|
|
throw new Exception("GetProcessThreadIDs: imp channel closed");
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
|
|
public static void GetProcessInfo(ProcessContract.Imp:ReadyState! imp,
|
|
ProcessData[]! processData
|
|
)
|
|
{
|
|
for (int i = 0; i < processData.Length; i++) {
|
|
ProcessData! pd = (!)processData[i];
|
|
imp.SendGetProcessTimes(pd.id);
|
|
imp.RecvProcessTimes(out pd.totalTime, out pd.deadThreadTime, out pd.deadThreadCount);
|
|
pd.totalTime += pd.deadThreadTime;
|
|
imp.SendGetProcessGcStats(pd.id);
|
|
imp.RecvProcessGcStats(out pd.gcCount, out pd.gcTotalTime, out pd.gcTotalBytes);
|
|
}
|
|
}
|
|
|
|
public static string []! GetProcessNames(ProcessContract.Imp:ReadyState! imp, int []! ids)
|
|
{
|
|
string [] names = new string[ids.Length];
|
|
for (int i = 0; i < ids.Length; i++)
|
|
{
|
|
imp.SendGetProcessName(ids[i]);
|
|
|
|
switch receive
|
|
{
|
|
case imp.NotFound() :
|
|
break;
|
|
|
|
case imp.ProcessName(char[]! in ExHeap procName) :
|
|
names[i] = Bitter.ToString(procName);
|
|
delete procName;
|
|
break;
|
|
|
|
case imp.ChannelClosed() :
|
|
throw new Exception("GetProcessNames: imp channel closed");
|
|
}
|
|
}
|
|
return names;
|
|
}
|
|
|
|
public static void GetMemoryUsage(MemoryContract.Imp:ReadyState! imp, ProcessData[]! processData)
|
|
{
|
|
for (int i = 0; i < processData.Length; i++)
|
|
{
|
|
ProcessData! pd = (!)processData[i];
|
|
imp.SendGetProcessUsedMemory(pd.id);
|
|
imp.RecvMemory(out pd.memBytes);
|
|
|
|
imp.SendGetProcessPeakMemory(pd.id);
|
|
imp.RecvMemory(out pd.peakMemBytes);
|
|
|
|
imp.SendProcessUsedCommunicationHeap(pd.id);
|
|
imp.RecvBlocksAndTotal(out pd.commMemBlocks, out pd.commMemBytes);
|
|
|
|
imp.SendGetProcessHandlePages(pd.id);
|
|
imp.RecvPages(out pd.handlePages);
|
|
}
|
|
}
|
|
|
|
public static ProcessData[]! GetProcessData(ProcessContract.Imp:ReadyState! imp)
|
|
{
|
|
int [] ids = GetProcessIDs(imp);
|
|
string [] names = GetProcessNames(imp, ids);
|
|
|
|
|
|
ProcessData [] processData= new ProcessData[ids.Length];
|
|
for (int i = 0; i < ids.Length; i++)
|
|
{
|
|
ProcessData! pd = new ProcessData();
|
|
pd.id = ids[i];
|
|
pd.name = names[i];
|
|
pd.tids = GetProcessThreadIDs(imp, ids[i]);
|
|
processData[i] = pd;
|
|
}
|
|
|
|
GetProcessInfo(imp, processData);
|
|
|
|
return processData;
|
|
}
|
|
}
|
|
|
|
// AMD Performance Counter Code
|
|
|
|
[CLSCompliant(false)]
|
|
public struct PerfEvtSel
|
|
{
|
|
// Bits and Flags
|
|
public const uint CNT_MASK = 0xff000000;
|
|
public const uint INV = 0x00800000;
|
|
public const uint EN = 0x00400000;
|
|
public const uint INT = 0x00100000;
|
|
public const uint PC = 0x00080000;
|
|
public const uint E = 0x00040000;
|
|
public const uint OS = 0x00020000;
|
|
public const uint USR = 0x00010000;
|
|
public const uint UNIT_MASK = 0x0000ff00;
|
|
public const uint SELECTOR = 0x000000ff;
|
|
|
|
// Common setting: Count all, but don't interrupt,
|
|
public const uint COUNT = (EN | PC | OS | USR);
|
|
// public const uint COUNT = (EN | PC | E | OS | USR);
|
|
|
|
// Selector values.
|
|
public const uint DCacheRefillFromL2OrSys = 0x42; // Speculative
|
|
public const uint DCacheRefillFromSystem = 0x43; // Speculative
|
|
public const uint DtlbL1MissL2Hit = 0x45; // Speculative
|
|
public const uint DtlbL1MissL2Miss = 0x46; // Speculative
|
|
public const uint CyclesNotHalted = 0x76; // No E
|
|
public const uint RequestsToL2Cache = 0x7d;
|
|
public const uint L2CacheMiss = 0x7e;
|
|
public const uint ItlbL1MissL2Hit = 0x84;
|
|
public const uint ItlbL1MissL2Miss = 0x85;
|
|
public const uint RetiredInstructions = 0xc0; // No E
|
|
public const uint RetiredBranchInstructions = 0xc2; // No E
|
|
public const uint RetiredBranchesMispredicted = 0xc3; // No E
|
|
public const uint RetiredBranchesTaken = 0xc4; // No E
|
|
public const uint CyclesInterruptsMasked = 0xcd; // No E
|
|
public const uint CyclesInterruptsBlocked = 0xce; // No E
|
|
|
|
public ulong value; // Required so Bartok doesn't die.
|
|
|
|
public static string ToString(ulong value)
|
|
{
|
|
switch (value & 0xff) {
|
|
case PerfEvtSel.DCacheRefillFromL2OrSys: return "DCache_Refill_L2";
|
|
case PerfEvtSel.DCacheRefillFromSystem: return "DCache_Refill_Sys";
|
|
case PerfEvtSel.DtlbL1MissL2Hit: return "DTLB_L2_Hit";
|
|
case PerfEvtSel.DtlbL1MissL2Miss: return "DTBL_L2_Miss";
|
|
case PerfEvtSel.CyclesNotHalted: return "CyclesNotHalted";
|
|
case PerfEvtSel.RequestsToL2Cache:
|
|
if ((value & 0x400) != 0) {
|
|
return "TLB_L2_Requests";
|
|
}
|
|
return "Req_L2_Cache";
|
|
case PerfEvtSel.L2CacheMiss:
|
|
if ((value & 0x400) != 0) {
|
|
return "TLB_L2_Miss";
|
|
}
|
|
return "L2_Cache_Miss";
|
|
case PerfEvtSel.ItlbL1MissL2Hit: return "ITLB_L2_Hit";
|
|
case PerfEvtSel.ItlbL1MissL2Miss: return "ITLB_L2_Miss";
|
|
case PerfEvtSel.RetiredInstructions: return "Retired_Inst.";
|
|
case PerfEvtSel.RetiredBranchInstructions: return "Branches";
|
|
case PerfEvtSel.RetiredBranchesMispredicted:return "Br_Mispredicted";
|
|
case PerfEvtSel.RetiredBranchesTaken: return "Br_Taken";
|
|
case PerfEvtSel.CyclesInterruptsMasked: return "Ints_Masked (cyc)";
|
|
case PerfEvtSel.CyclesInterruptsBlocked: return "Ints_Blocked (cyc)";
|
|
default:
|
|
return String.Format("{0:x16}", value);
|
|
}
|
|
}
|
|
}
|
|
}
|