/////////////////////////////////////////////////////////////////////////////// // // 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 webAppRef; #if false [Endpoint] public readonly TRef 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; // \nSPECweb99 Dynamic GET & POST Test\n\n

SERVER_SOFTWARE = Singularity Cassini web server\n

REMOTE_ADDR = "); HtmlPreamble2 = Encoding.ASCII.GetBytes("\n

SCRIPT_NAME = SPECWeb99WebApp\n

QUERY_STRING = "); HtmlPreamble3 = Encoding.ASCII.GetBytes("\n

\n");
            HtmlPostamble = Encoding.ASCII.GetBytes("\n
\n\n"); PostLogInitData = Encoding.ASCII.GetBytes(" 0\n"); MatchPattern = Encoding.ASCII.GetBytes("(DirectoryService.NewClientEndpoint()); processRef = new TRef(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 (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!)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("")); } 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("")); } 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