//////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: MonNet.cs // // Note: Tool to transfer monitoring log entries over network // using System; using System.IO; using Microsoft.Singularity; using Microsoft.Singularity.Channels; using Microsoft.Singularity.Naming; using System.Net.IP; using NetStack.Contracts; using NetStack.Channels.Public; using Microsoft.Singularity.Applications.Network; using Microsoft.Singularity.Diagnostics.Contracts; using Microsoft.Singularity.V1.Types; /* For contract type handle dumping: */ using Microsoft.Singularity.Io; using Microsoft.Singularity.Io.Net; using Microsoft.Singularity.Directory; using Microsoft.Singularity.FileSystem; using Thread = System.Threading.Thread; using Microsoft.Singularity.Channels; using Microsoft.Contracts; using Microsoft.SingSharp.Reflection; using Microsoft.Singularity.Applications; using Microsoft.Singularity.Io; using Microsoft.Singularity.Configuration; [assembly: Transform(typeof(ApplicationResourceTransform))] namespace Microsoft.Singularity.Applications { [ConsoleCategory(DefaultAction=true)] internal class Parameters { [InputEndpoint("data")] public readonly TRef Stdin; [OutputEndpoint("data")] public readonly TRef Stdout; [BoolParameter( "help", Default=false, HelpMessage="Display Extended help message.")] internal bool doHelp; [StringArrayParameter( "args", HelpMessage="arg bucket")] internal string[] args; reflective internal Parameters(); internal int AppMain() { return Monnet.AppMain(this); } } public class Monnet { public class Proc { public static ProcessContract.Imp:ReadyState BindToProcessDiagnostics() { ProcessContract.Exp! exp; ProcessContract.Imp! imp; ProcessContract.NewChannel(out imp, out exp); // get NS endpoint DirectoryServiceContract.Imp ns = DirectoryService.NewClientEndpoint(); try { ErrorCode errorOut; bool ok = SdsUtils.Bind(ProcessContract.ModuleName, ns, exp, out errorOut); if (!ok) { delete imp; Console.WriteLine("Bind of {0} failed. reason: {1}\n", MemoryContract.ModuleName,SdsUtils.ErrorCodeToString(errorOut) ); if (errorOut == ErrorCode.ChannelClosed) { throw new Exception("Encountered a ChannelClosed to NameServer"); } return null; } else { imp.RecvReady(); } } finally { delete ns; } return imp; } 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> 32); postSysInfoEvent(ref ev, null); dumpContractTypeHandles(); dumpPidEntries(); // TODO: disk ID -> name rundowns } private static void dumpPidEntries() { Monitoring.LogEntry ev = newSysInfoEvent(SysInfoEvent.PidInfo); Monitoring.LogEntry ev2 = newSysInfoEvent(SysInfoEvent.ThreadInfo); ProcessContract.Imp:ReadyState pDiag = Proc.BindToProcessDiagnostics(); if (pDiag == null) { } else { int [] procIds = Proc.GetProcessIDs(pDiag); string [] names = Proc.GetProcessNames(pDiag,procIds); for (int i=0; i= 0) smallname = fullname.Substring(idx+1); else smallname = fullname; postSysInfoEvent(ref ev, smallname); } } public static byte[]! buf; // event buffer public static int bufoffset; // current position in event buffer public static byte[] text; // buffer for event string public static int textlen; // length of event string public static TcpConnectionContract.Imp:Connected! OpenConnection(IPv4 address, ushort port) { TcpConnectionContract.Imp tcpConn = Utils.GetNewTcpEndPoint(); if (tcpConn == null) { Console.WriteLine("Could not initialize TCP endpoint."); throw new ArgumentException("Could not initialize TCP endpoint."); } Console.Write("Connecting..."); // Try to connect to the remote host tcpConn.SendConnect((uint)address, port); switch receive { case tcpConn.CouldNotConnect(TcpError error) : Console.WriteLine("Failed to connect: TcpError = " + System.Net.Sockets.TcpException.GetMessageForTcpError(error)); delete tcpConn; throw new ArgumentException("Failed to connect."); break; case tcpConn.OK() : // success; break; case tcpConn.ChannelClosed() : // how rude Console.WriteLine("Netstack channel closed unexpectedly"); delete tcpConn; throw new ArgumentException("Netstack channel closed unexpectedly"); break; } Console.WriteLine("Connected"); return tcpConn; } public static TcpConnectionContract.Imp:Connected SetupNetwork(string sIp, string sPort) { IPv4 host; try { host = IPv4.Parse(sIp); } catch (FormatException e) { Console.WriteLine("{0}: {1}", e, sIp); return null; } ushort port; try { port = UInt16.Parse(sPort); } catch (FormatException) { Console.WriteLine("Malformed port number: {0}", sPort); return null; } catch (OverflowException) { Console.WriteLine("Port number out of range: {0}", sPort); return null; } TcpConnectionContract.Imp! conn = OpenConnection(host, port); return conn; } // we segment our large buffer into COMMSZ chunks for punting through // to the network stack private static int COMMSZ = 64*1024; // 64KB public static int SendLogBuffer(TcpConnectionContract.Imp! conn) { int sendidx = 0; int sendlen; byte[]! in ExHeap data; // copy data to exheap in chunks and send Console.Write("Sending"); while (sendidx < bufoffset) { sendlen = Math.Min(COMMSZ, bufoffset - sendidx); data = Bitter.FromByteArray(buf, sendidx, sendlen); conn.SendWrite(data); switch receive { case conn.OK() : Console.Write("."); break; case conn.CantSend() : Console.WriteLine(": Connection closed unexpectedly (CantSend)"); return -1; break; case conn.ConnectionClosed(): Console.WriteLine(": Connection closed unexpectedly (ConnectionClosed)"); return -1; break; } sendidx += sendlen; } Console.WriteLine("OK"); return 0; } public static int marshalled; // count of events marshalled public static int Marshall(ref Monitoring.LogEntry e) { int tl = (textlen > 0)? textlen : 0; if (sizeof(Monitoring.LogEntry) + bufoffset + tl > buf.Length) { Console.WriteLine("Marshall: buffer full"); return -1; } unsafe { fixed (byte *bp = &buf[bufoffset]) { /* Use a simple cast */ Monitoring.LogEntry *lep = (Monitoring.LogEntry *)bp; *lep = e; } } bufoffset += sizeof(Monitoring.LogEntry); if (textlen > 0) { Array.Copy(text, 0, buf, bufoffset, textlen); bufoffset += textlen; } textlen = 0; marshalled++; return 0; } // Fill event buffer until either duration has passed or // we have maxevents public static int FillEventBuffer(ulong duration, ulong maxevents, ulong evstart) { int ret = -1; ulong counter = evstart; ulong old_counter = 0; ulong count_lost = 0; ulong sum_lost = 0; ulong hz = (ulong)Processor.CyclesPerSecond; ulong now = Processor.CycleCount; ulong end = now + duration * hz; ulong evcount = 0; Monitoring.LogEntry e = new Monitoring.LogEntry(); Console.WriteLine("MonNet: collecting with duration={0}secs, maxevents={1}", duration, maxevents); bool done = false; while (!done) { // Get an event while (true) { ret = -1; now = Processor.CycleCount; if (now > end || evcount >= maxevents) { done = true; if (now > end) Console.WriteLine("Timed out ({0}ms late)", (now-end)*1000/hz); if (evcount >= maxevents) Console.WriteLine("Got enough events"); break; } unsafe { ret = Monitoring.GetEntry(ref counter, out e); } if (old_counter != counter) { count_lost++; sum_lost += counter - old_counter; //Console.Write("X{0}", counter - old_counter); } old_counter = counter; if (ret == 0) {// Got an event evcount++; break; } else { // No event to get, sleep and try again // are we just using defaults? if (duration == ((ulong)DEF_DUR) && maxevents == ((ulong)DEF_MAXEV)) { done = true; Console.WriteLine("No more events"); break; } else { Thread.Sleep(100); } } } counter++; if (ret == 0) { if (e.cycleCount == 0) { // Console.Write('^'); evcount--; continue; // got illegal entry, so try next } // Try to get text buffer for event if (e.text != null) { int size; unsafe { assume text != null; fixed (byte * b = text) { size = Monitoring.FillTextEntry(e.text, e.cycleCount, b, text.Length); } } if (size != 0) // fixup size in event structure { if (size < 0) // error: dropped text string Console.WriteLine("size={0}", size); unsafe { e.text = (byte *)size; } textlen = size; } } // put event in buffer ret = Marshall(ref e); if (ret != 0) { Console.WriteLine("BUFFER FULL: exiting EARLY!"); done = true; break; } } } Console.WriteLine("Got {0} events", evcount); return ret; } /* We can't actually "clear" the kernel log; what this does * is find the counter number for the log's current end, * so we can pass it into FillEventBuffer() later. */ private static ulong clearLog() { int ret; ulong counter = 0; Monitoring.LogEntry e = new Monitoring.LogEntry(); int i = 0; while (true) { unsafe { ret = Monitoring.GetEntry(ref counter, out e); } if (ret == 0) { counter++; // got an event; try next one } else { break; // failed to get an event, we're done } /* sanity check */ if (i++ > 5000000) { Console.WriteLine("error: clearLog() looped 5000000 times; bailing"); break; } } return counter; } /************************************************/ /* Command line gubbins */ private static int doTransfer(string sIp, string sPort) { int ret; // Setup the networking stuff. // austind: due to a race in the netstack, must open the connection // and start sending data on it promptly, otherwise you'll get // a RST and an exception. TcpConnectionContract.Imp conn = SetupNetwork(sIp, sPort); if (conn == null) return -1; // Transmit the buffer ret = SendLogBuffer(conn); // Tidy up conn.SendClose(); delete conn; return ret; } private static int doSlurp(long runTime, long maxevents, ulong evstart) { // Allocate the buffers buf = new Byte[16<<20]; // should be big enough bufoffset = 0; text = new byte[256]; textlen = 0; marshalled = 0; // num events copied to buf // Put preamble into buffer dumpSysInfo(); return FillEventBuffer((ulong)runTime, (ulong)maxevents, evstart); } public static bool ParseNumber(string/*!*/ arg, string/*!*/ name, out long value) { // arg should look like "[-][A-z]:[0-9]*" if (arg.Length >= 4) { try { value = Int64.Parse(arg.Substring(3)); return true; } catch (FormatException) {Console.WriteLine("format ex"); } catch (OverflowException) {Console.WriteLine("overflow ex");} } Console.WriteLine("Could not parse {0}, {1}", name,arg.Substring(3)); value = 0; return false; } internal static int usage() { Console.WriteLine("Usage: monnet /d /c /w:WARMUP /e /r:RUNTIME /n:MAXEVENTS /s /t:IP:PORT"); Console.WriteLine(" /d disable logging"); Console.WriteLine(" /c \"clear\" log: ie note current end"); Console.WriteLine(" /w:WARMUP pause for WARMUP seconds"); Console.WriteLine(" /e enable logging"); Console.WriteLine(" /r:RUNTIME set event slurp limit to RUNTIME seconds"); Console.WriteLine(" /n:MAXEVENTS set event slurp limit to MAXEVENTS"); Console.WriteLine(" (without /r or /n options: slurp until log empty or marshal buffer full)"); Console.WriteLine(" /s slurp events according to limits"); Console.WriteLine(" /t:IP:PORT transfer via TCP to IPADDR:PORT"); Console.WriteLine("All options can occur in any order, zero or more times"); Console.WriteLine("They are executed in order. Example:"); Console.WriteLine("monnet /d /c /w:300 /e /r:300 /s /d /t:10.99.99.1:5000"); return -1; } private static long DEF_DUR = 999999999; // seconds ie forever private static long DEF_MAXEV = 999999999; internal static int AppMain(Parameters! config) { int ret = 0; long runTime = DEF_DUR, maxevents = DEF_MAXEV; ulong evstart = 0; string[] args = config.args; if (config.doHelp) return usage(); if (args == null) { return usage(); } bool needHelp = (args.Length == 0); int i; for (i = 0; !needHelp && i < args.Length; i++) { string arg = args[i]; if (arg == null || arg.Length < 2 || (arg[0] != '-' && arg[0] != '/') ) { Console.WriteLine("Invalid argument: {0}", arg); return usage(); } long temp; switch (arg[1]) { case 'd': case 'e': bool active = (arg[1] == 'e'); Console.WriteLine(active?"Enabling logging":"Disabling logging"); Monitoring.setActive(active); break; case '?': case 'h': needHelp = true; break; case 'c': Console.WriteLine("Clearing log"); evstart = clearLog(); break; case 'w': ParseNumber(arg, "Warmup time", out temp); Console.WriteLine("Pausing {0}secs for warmup", temp); Thread.Sleep(((int)temp) * 1000); break; case 'r': ParseNumber(arg, "Run time", out runTime); break; case 'n': ParseNumber(arg, "Maxevents", out maxevents); break; case 's': doSlurp(runTime, maxevents, evstart); break; case 't': int colon = arg.IndexOf(':', 3); if (colon < 0) { Console.WriteLine("argument error: missing colon in '{0}': expecting IPADDR:PORT", arg); return usage(); } string ipaddr = arg.Substring(3, colon-3); string port = arg.Substring(colon+1); ret = doTransfer(ipaddr, port); if (ret != 0) return ret; break; default: Console.WriteLine("Unknown option {0}", arg); needHelp = true; break; } } // for args if (needHelp) { return usage(); } return 0; } } }