385 lines
12 KiB
C#
385 lines
12 KiB
C#
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Microsoft Research Singularity
|
||
|
//
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//
|
||
|
// Note: Perform short (bvt) and long running stress of OutOfMemory
|
||
|
// behavior of the system.
|
||
|
//
|
||
|
|
||
|
//
|
||
|
// Tests to support:
|
||
|
//
|
||
|
// - Overcommit of heap memory within a SIP
|
||
|
//
|
||
|
// Create lots of managed array objects until the OS can no
|
||
|
// longer satisfy the request and terminates the SIP.
|
||
|
//
|
||
|
//
|
||
|
// - Overcommit of stack memory within a SIP
|
||
|
//
|
||
|
// Implement a recursive function that results in stack overflow
|
||
|
// and termination of the SIP.
|
||
|
//
|
||
|
// - Overcommit of heap memory within the kernel
|
||
|
//
|
||
|
// Since we can't directly allocate kernel heap memory,
|
||
|
// we do this by creating lots of kernel objects that occupy
|
||
|
// the heap (such as new threads)
|
||
|
//
|
||
|
// - Overcommit kernel heap memory from within the kernel in multiple
|
||
|
// threads to find races in any resource reservation strategies that
|
||
|
// may be timing dependent.
|
||
|
//
|
||
|
|
||
|
using System;
|
||
|
using System.Threading;
|
||
|
|
||
|
using Microsoft.Contracts;
|
||
|
using Microsoft.SingSharp.Reflection;
|
||
|
using Microsoft.Singularity.UnitTest;
|
||
|
using Microsoft.Singularity.V1.Services;
|
||
|
using Microsoft.Singularity.Channels;
|
||
|
using Microsoft.Singularity.Directory;
|
||
|
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<UnicodePipeContract.Exp:READY> Stdin;
|
||
|
|
||
|
[OutputEndpoint("data")]
|
||
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;
|
||
|
|
||
|
[Endpoint]
|
||
|
public readonly TRef<DirectoryServiceContract.Imp:Start> nsRef;
|
||
|
|
||
|
[BoolParameter( "sip_so", Default=false, HelpMessage="Create Stack Overflow in SIP")]
|
||
|
internal bool sipSo;
|
||
|
|
||
|
[BoolParameter( "sip_oom", Default=false, HelpMessage="Create Out Of Memory in SIP")]
|
||
|
internal bool sipOom;
|
||
|
|
||
|
[BoolParameter( "kernel_oom", Default=false, HelpMessage="Create Kernel Out Of Memory")]
|
||
|
internal bool kernelOom;
|
||
|
|
||
|
[BoolParameter( "kernel_oom_stress", Default=false, HelpMessage="Create Kernel Out Of Memory Stress")]
|
||
|
internal bool kernelOomStress;
|
||
|
|
||
|
//
|
||
|
// The noProcess parameter runs the test without creating a sub-process.
|
||
|
//
|
||
|
// Since success for this test is running out of memory and having the SIP
|
||
|
// die, there is no way to return a success status to a top level test script.
|
||
|
// The script thinks that the SIP failure due to OOM is a failure of the test.
|
||
|
//
|
||
|
// So by default, the tests are run in a sub-process which we can then monitor
|
||
|
// and return a proper result to the top level test script.
|
||
|
//
|
||
|
// This parameter allows the same executable to be used for both parts
|
||
|
// of the test.
|
||
|
//
|
||
|
[BoolParameter( "noprocess", Default=false, HelpMessage="Run directly without a sub-process")]
|
||
|
internal bool noProcess;
|
||
|
|
||
|
reflective internal Parameters();
|
||
|
|
||
|
internal int AppMain() {
|
||
|
return MemStress.AppMain(this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class MemStress
|
||
|
{
|
||
|
internal static int AppMain(Parameters! config)
|
||
|
{
|
||
|
|
||
|
#if PAGING
|
||
|
if (true) {
|
||
|
Console.WriteLine("MemStress does not work on PAGING builds");
|
||
|
return -1;
|
||
|
}
|
||
|
#endif
|
||
|
DumpMemInfo();
|
||
|
|
||
|
SipMemoryStresser sipms = new SipMemoryStresser();
|
||
|
|
||
|
if (!config.noProcess) {
|
||
|
if (config.sipSo) {
|
||
|
sipms.RunTestAsProcess(config, "-sip_so");
|
||
|
}
|
||
|
else if (config.sipOom) {
|
||
|
sipms.RunTestAsProcess(config, "-sip_oom");
|
||
|
}
|
||
|
else if (config.kernelOom) {
|
||
|
sipms.RunTestAsProcess(config, "-kernel_oom");
|
||
|
}
|
||
|
else if (config.kernelOomStress) {
|
||
|
sipms.RunTestAsProcess(config, "-kernel_oom_stress");
|
||
|
}
|
||
|
else {
|
||
|
Console.WriteLine("Usage: MemStress [-sip_oom] [-sip_so] [-kernel_oom] [-kernel_oom_stress]");
|
||
|
Console.WriteLine("Must pick one option");
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
if (config.sipSo) {
|
||
|
sipms.RunSOTest();
|
||
|
}
|
||
|
else if (config.sipOom) {
|
||
|
sipms.RunOOMTest();
|
||
|
}
|
||
|
else if (config.kernelOom) {
|
||
|
sipms.KernelOOMTest();
|
||
|
}
|
||
|
else if (config.kernelOomStress) {
|
||
|
sipms.KernelOOMStress();
|
||
|
}
|
||
|
else {
|
||
|
Console.WriteLine("Usage: MemStress [-sip_oom] [-sip_so] [-kernel_oom] [-kernel_oom_stress]");
|
||
|
Console.WriteLine("Must pick one option");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
public static void DumpMemInfo()
|
||
|
{
|
||
|
int result;
|
||
|
|
||
|
ulong totalMemoryFree = 0;
|
||
|
ulong totalMemoryInUse = 0;
|
||
|
ulong kernelHeapInUse = 0;
|
||
|
ulong kernelStackInUse = 0;
|
||
|
ulong totalSIPHeapInUse = 0;
|
||
|
ulong totalSIPStackInUse = 0;
|
||
|
ulong kernelStackReservation = 0;
|
||
|
ulong kernelHeapReservation = 0;
|
||
|
|
||
|
result = MemoryInfoService.MemoryUsageInfo(
|
||
|
out totalMemoryFree,
|
||
|
out totalMemoryInUse,
|
||
|
out kernelHeapInUse,
|
||
|
out kernelStackInUse,
|
||
|
out totalSIPHeapInUse,
|
||
|
out totalSIPStackInUse,
|
||
|
out kernelStackReservation,
|
||
|
out kernelHeapReservation
|
||
|
);
|
||
|
|
||
|
// TODO: Use standard ErrorCode's
|
||
|
if (result != 0) {
|
||
|
Console.WriteLine("Error {0} retrieving MemoryUsageInfo");
|
||
|
}
|
||
|
else {
|
||
|
Console.WriteLine("TotalMemoryFree 0x{0:x8}, TotalMemoryInUse {1:x8}", totalMemoryFree, totalMemoryInUse);
|
||
|
Console.WriteLine("KernelHeapInUse {0:x8}, KernelStackInUse {1:x8}", kernelHeapInUse, kernelStackInUse);
|
||
|
Console.WriteLine("TotalSIPHeapInUse {0:x8}, TotalSIPStackInUse {1:x8}", totalSIPHeapInUse, totalSIPStackInUse);
|
||
|
Console.WriteLine("KernelStackReservation {0:x8}, KernelHeapReservation {1:x8}", kernelStackReservation, kernelHeapReservation);
|
||
|
Console.WriteLine("");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class SipMemoryStresser
|
||
|
{
|
||
|
//
|
||
|
// Run out of memory test, eventually terminates the SIP due
|
||
|
// to fail fast.
|
||
|
//
|
||
|
internal void RunTestAsProcess(Parameters! config, string test) {
|
||
|
|
||
|
Process p = null;
|
||
|
|
||
|
//
|
||
|
// Create a subprocess run of "memstress -testparameter"
|
||
|
//
|
||
|
string[] args = new string[3];
|
||
|
args[0] = "memstress";
|
||
|
args[1] = test;
|
||
|
args[2] = "-noprocess";
|
||
|
|
||
|
Console.WriteLine("Creating subprocess memstress {0} -noprocess", test);
|
||
|
|
||
|
try {
|
||
|
p = ProcessLauncher.CreateSubProcess(config, args);
|
||
|
}
|
||
|
catch (Exception e) {
|
||
|
Console.WriteLine("Exception from CreateSubProcess: " + e.Message);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (p == null) {
|
||
|
Console.WriteLine("Error creating process");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Console.WriteLine("Process returned with ExitCode={0}", p.ExitCode);
|
||
|
}
|
||
|
|
||
|
public void RunOOMTest() {
|
||
|
|
||
|
object[] TopLevel;
|
||
|
object[] tmp;
|
||
|
|
||
|
TopLevel = new Object[1];
|
||
|
|
||
|
//
|
||
|
// Currrently the test handles the OOM exception showing
|
||
|
// that this works. Additional testing should create multiple
|
||
|
// threads and have OOM failures at random places in the runtime
|
||
|
// without try/except handlers so we can test the case when
|
||
|
// the exception bubbles up to the top of the stack.
|
||
|
//
|
||
|
try {
|
||
|
for (int count = 65535;; count *= 2) {
|
||
|
|
||
|
tmp = new object[count];
|
||
|
|
||
|
tmp[0] = TopLevel;
|
||
|
TopLevel = tmp;
|
||
|
|
||
|
for (int i = 1; i < count; i++) {
|
||
|
TopLevel[i] = new object[count];
|
||
|
}
|
||
|
|
||
|
MemStress.DumpMemInfo();
|
||
|
}
|
||
|
}
|
||
|
catch (OutOfMemoryException e) {
|
||
|
e=e;
|
||
|
//Thread.CurrentProcess.Stop(1);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Run stack overflow test, terminates the SIP
|
||
|
public void RunSOTest() {
|
||
|
|
||
|
// Test the fail fast path directly
|
||
|
//Microsoft.Singularity.V1.Services.StackService.StackOverflow();
|
||
|
|
||
|
RunSOTest();
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Invoke a kernel heap out of memory condition by creating
|
||
|
// lots of kernel objects
|
||
|
//
|
||
|
public void KernelOOMTest() {
|
||
|
|
||
|
IdleThread[] threads;
|
||
|
|
||
|
int threadCount = 1000;
|
||
|
|
||
|
while (true) {
|
||
|
|
||
|
System.Console.WriteLine("Creating {0} threads", threadCount);
|
||
|
|
||
|
threads = new IdleThread[threadCount];
|
||
|
if (threads == null) {
|
||
|
System.Console.WriteLine("Error allocating Thread[]");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < threadCount; i++) {
|
||
|
threads[i] = new IdleThread(i);
|
||
|
if (threads[i] == null) {
|
||
|
System.Console.WriteLine("Error creating thread");
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Start them running
|
||
|
for (int i = 0; i < threadCount; i++) {
|
||
|
((!)threads[i]).RunThread();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Use multiple threads to stress the system and induce
|
||
|
// kernel heap OOM to test the ability for the system
|
||
|
// to remain running.
|
||
|
//
|
||
|
public void KernelOOMStress() {
|
||
|
Console.WriteLine("Not implemented yet");
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Generic class that creates the thread
|
||
|
public class ThreadRunner
|
||
|
{
|
||
|
private Thread thread;
|
||
|
|
||
|
public ThreadRunner() {
|
||
|
}
|
||
|
|
||
|
public Thread Thread {
|
||
|
get {
|
||
|
return thread;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Creates thread, returns with it running
|
||
|
public Thread RunThread() {
|
||
|
|
||
|
thread = new Thread(new ThreadStart(ThreadMain));
|
||
|
|
||
|
thread.Start();
|
||
|
|
||
|
return thread;
|
||
|
}
|
||
|
|
||
|
public virtual void ThreadMain() {
|
||
|
|
||
|
// This exits the thread
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Implementation classes represent different thread creation
|
||
|
// parameters, and actions
|
||
|
//
|
||
|
|
||
|
//
|
||
|
// This creates an idle thread that just waits.
|
||
|
//
|
||
|
// The goal is to create lots of kernel objects and use up
|
||
|
// memory, not jam the system with many threads until
|
||
|
// it stalls.
|
||
|
//
|
||
|
public class IdleThread : ThreadRunner
|
||
|
{
|
||
|
private long argValue;
|
||
|
|
||
|
public IdleThread(long arg) : base() {
|
||
|
argValue = arg;
|
||
|
}
|
||
|
|
||
|
public override void ThreadMain() {
|
||
|
|
||
|
// Wait for ever
|
||
|
while (true) {
|
||
|
Thread.Sleep(60*60*1000);
|
||
|
}
|
||
|
|
||
|
// This exits the thread
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|