// Copyright 2006 Google Inc. All Rights Reserved.
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
// you may not use this file except in compliance with the License.
|
// You may obtain a copy of the License at
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
// Unless required by applicable law or agreed to in writing, software
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// See the License for the specific language governing permissions and
|
// limitations under the License.
|
|
// sat.cc : a stress test for stressful testing
|
|
// stressapptest (or SAT, from Stressful Application Test) is a test
|
// designed to stress the system, as well as provide a comprehensive
|
// memory interface test.
|
|
// stressapptest can be run using memory only, or using many system components.
|
|
#include <errno.h>
|
#include <pthread.h>
|
#include <signal.h>
|
#include <stdarg.h>
|
#include <stdio.h>
|
#include <stdlib.h>
|
#include <string.h>
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
#include <sys/times.h>
|
|
// #define __USE_GNU
|
// #define __USE_LARGEFILE64
|
#include <fcntl.h>
|
|
#include <list>
|
#include <string>
|
|
// This file must work with autoconf on its public version,
|
// so these includes are correct.
|
#include "disk_blocks.h"
|
#include "logger.h"
|
#include "os.h"
|
#include "sat.h"
|
#include "sattypes.h"
|
#include "worker.h"
|
|
// stressapptest versioning here.
|
#ifndef PACKAGE_VERSION
|
static const char* kVersion = "1.0.0";
|
#else
|
static const char* kVersion = PACKAGE_VERSION;
|
#endif
|
|
// Global stressapptest reference, for use by signal handler.
|
// This makes Sat objects not safe for multiple instances.
|
namespace {
|
Sat *g_sat = NULL;
|
|
// Signal handler for catching break or kill.
|
//
|
// This must be installed after g_sat is assigned and while there is a single
|
// thread.
|
//
|
// This must be uninstalled while there is only a single thread, and of course
|
// before g_sat is cleared or deleted.
|
void SatHandleBreak(int signal) {
|
g_sat->Break();
|
}
|
}
|
|
// Opens the logfile for writing if necessary
|
bool Sat::InitializeLogfile() {
|
// Open logfile.
|
if (use_logfile_) {
|
logfile_ = open(logfilename_,
|
#if defined(O_DSYNC)
|
O_DSYNC |
|
#elif defined(O_SYNC)
|
O_SYNC |
|
#elif defined(O_FSYNC)
|
O_FSYNC |
|
#endif
|
O_WRONLY | O_CREAT,
|
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
if (logfile_ < 0) {
|
printf("Fatal Error: cannot open file %s for logging\n",
|
logfilename_);
|
bad_status();
|
return false;
|
}
|
// We seek to the end once instead of opening in append mode because no
|
// other processes should be writing to it while this one exists.
|
if (lseek(logfile_, 0, SEEK_END) == -1) {
|
printf("Fatal Error: cannot seek to end of logfile (%s)\n",
|
logfilename_);
|
bad_status();
|
return false;
|
}
|
Logger::GlobalLogger()->SetLogFd(logfile_);
|
}
|
return true;
|
}
|
|
// Check that the environment is known and safe to run on.
|
// Return 1 if good, 0 if unsuppported.
|
bool Sat::CheckEnvironment() {
|
// Check that this is not a debug build. Debug builds lack
|
// enough performance to stress the system.
|
#if !defined NDEBUG
|
if (run_on_anything_) {
|
logprintf(1, "Log: Running DEBUG version of SAT, "
|
"with significantly reduced coverage.\n");
|
} else {
|
logprintf(0, "Process Error: Running DEBUG version of SAT, "
|
"with significantly reduced coverage.\n");
|
logprintf(0, "Log: Command line option '-A' bypasses this error.\n");
|
bad_status();
|
return false;
|
}
|
#elif !defined CHECKOPTS
|
#error Build system regression - COPTS disregarded.
|
#endif
|
|
// Check if the cpu frequency test is enabled and able to run.
|
if (cpu_freq_test_) {
|
if (!CpuFreqThread::CanRun()) {
|
logprintf(0, "Process Error: This platform does not support this "
|
"test.\n");
|
bad_status();
|
return false;
|
} else if (cpu_freq_threshold_ <= 0) {
|
logprintf(0, "Process Error: The cpu frequency test requires "
|
"--cpu_freq_threshold set to a value > 0\n");
|
bad_status();
|
return false;
|
} else if (cpu_freq_round_ < 0) {
|
logprintf(0, "Process Error: The --cpu_freq_round option must be greater"
|
" than or equal to zero. A value of zero means no rounding.\n");
|
bad_status();
|
return false;
|
}
|
}
|
|
// Use all CPUs if nothing is specified.
|
if (memory_threads_ == -1) {
|
memory_threads_ = os_->num_cpus();
|
logprintf(7, "Log: Defaulting to %d copy threads\n", memory_threads_);
|
}
|
|
// Use all memory if no size is specified.
|
if (size_mb_ == 0)
|
size_mb_ = os_->FindFreeMemSize() / kMegabyte;
|
size_ = static_cast<int64>(size_mb_) * kMegabyte;
|
|
// Autodetect file locations.
|
if (findfiles_ && (file_threads_ == 0)) {
|
// Get a space separated sting of disk locations.
|
list<string> locations = os_->FindFileDevices();
|
|
// Extract each one.
|
while (!locations.empty()) {
|
// Copy and remove the disk name.
|
string disk = locations.back();
|
locations.pop_back();
|
|
logprintf(12, "Log: disk at %s\n", disk.c_str());
|
file_threads_++;
|
filename_.push_back(disk + "/sat_disk.a");
|
file_threads_++;
|
filename_.push_back(disk + "/sat_disk.b");
|
}
|
}
|
|
// We'd better have some memory by this point.
|
if (size_ < 1) {
|
logprintf(0, "Process Error: No memory found to test.\n");
|
bad_status();
|
return false;
|
}
|
|
if (tag_mode_ && ((file_threads_ > 0) ||
|
(disk_threads_ > 0) ||
|
(net_threads_ > 0))) {
|
logprintf(0, "Process Error: Memory tag mode incompatible "
|
"with disk/network DMA.\n");
|
bad_status();
|
return false;
|
}
|
|
// If platform is 32 bit Xeon, floor memory size to multiple of 4.
|
if (address_mode_ == 32) {
|
size_mb_ = (size_mb_ / 4) * 4;
|
size_ = size_mb_ * kMegabyte;
|
logprintf(1, "Log: Flooring memory allocation to multiple of 4: %lldMB\n",
|
size_mb_);
|
}
|
|
// Check if this system is on the whitelist for supported systems.
|
if (!os_->IsSupported()) {
|
if (run_on_anything_) {
|
logprintf(1, "Log: Unsupported system. Running with reduced coverage.\n");
|
// This is ok, continue on.
|
} else {
|
logprintf(0, "Process Error: Unsupported system, "
|
"no error reporting available\n");
|
logprintf(0, "Log: Command line option '-A' bypasses this error.\n");
|
bad_status();
|
return false;
|
}
|
}
|
|
return true;
|
}
|
|
// Allocates memory to run the test on
|
bool Sat::AllocateMemory() {
|
// Allocate our test memory.
|
bool result = os_->AllocateTestMem(size_, paddr_base_);
|
if (!result) {
|
logprintf(0, "Process Error: failed to allocate memory\n");
|
bad_status();
|
return false;
|
}
|
return true;
|
}
|
|
// Sets up access to data patterns
|
bool Sat::InitializePatterns() {
|
// Initialize pattern data.
|
patternlist_ = new PatternList();
|
if (!patternlist_) {
|
logprintf(0, "Process Error: failed to allocate patterns\n");
|
bad_status();
|
return false;
|
}
|
if (!patternlist_->Initialize()) {
|
logprintf(0, "Process Error: failed to initialize patternlist\n");
|
bad_status();
|
return false;
|
}
|
return true;
|
}
|
|
// Get any valid page, no tag specified.
|
bool Sat::GetValid(struct page_entry *pe) {
|
return GetValid(pe, kDontCareTag);
|
}
|
|
|
// Fetch and return empty and full pages into the empty and full pools.
|
bool Sat::GetValid(struct page_entry *pe, int32 tag) {
|
bool result = false;
|
// Get valid page depending on implementation.
|
if (pe_q_implementation_ == SAT_FINELOCK)
|
result = finelock_q_->GetValid(pe, tag);
|
else if (pe_q_implementation_ == SAT_ONELOCK)
|
result = valid_->PopRandom(pe);
|
|
if (result) {
|
pe->addr = os_->PrepareTestMem(pe->offset, page_length_); // Map it.
|
|
// Tag this access and current pattern.
|
pe->ts = os_->GetTimestamp();
|
pe->lastpattern = pe->pattern;
|
|
return (pe->addr != 0); // Return success or failure.
|
}
|
return false;
|
}
|
|
bool Sat::PutValid(struct page_entry *pe) {
|
if (pe->addr != 0)
|
os_->ReleaseTestMem(pe->addr, pe->offset, page_length_); // Unmap the page.
|
pe->addr = 0;
|
|
// Put valid page depending on implementation.
|
if (pe_q_implementation_ == SAT_FINELOCK)
|
return finelock_q_->PutValid(pe);
|
else if (pe_q_implementation_ == SAT_ONELOCK)
|
return valid_->Push(pe);
|
else
|
return false;
|
}
|
|
// Get an empty page with any tag.
|
bool Sat::GetEmpty(struct page_entry *pe) {
|
return GetEmpty(pe, kDontCareTag);
|
}
|
|
bool Sat::GetEmpty(struct page_entry *pe, int32 tag) {
|
bool result = false;
|
// Get empty page depending on implementation.
|
if (pe_q_implementation_ == SAT_FINELOCK)
|
result = finelock_q_->GetEmpty(pe, tag);
|
else if (pe_q_implementation_ == SAT_ONELOCK)
|
result = empty_->PopRandom(pe);
|
|
if (result) {
|
pe->addr = os_->PrepareTestMem(pe->offset, page_length_); // Map it.
|
return (pe->addr != 0); // Return success or failure.
|
}
|
return false;
|
}
|
|
bool Sat::PutEmpty(struct page_entry *pe) {
|
if (pe->addr != 0)
|
os_->ReleaseTestMem(pe->addr, pe->offset, page_length_); // Unmap the page.
|
pe->addr = 0;
|
|
// Put empty page depending on implementation.
|
if (pe_q_implementation_ == SAT_FINELOCK)
|
return finelock_q_->PutEmpty(pe);
|
else if (pe_q_implementation_ == SAT_ONELOCK)
|
return empty_->Push(pe);
|
else
|
return false;
|
}
|
|
// Set up the bitmap of physical pages in case we want to see which pages were
|
// accessed under this run of SAT.
|
void Sat::AddrMapInit() {
|
if (!do_page_map_)
|
return;
|
// Find about how much physical mem is in the system.
|
// TODO(nsanders): Find some way to get the max
|
// and min phys addr in the system.
|
uint64 maxsize = os_->FindFreeMemSize() * 4;
|
sat_assert(maxsize != 0);
|
|
// Make a bitmask of this many pages. Assume that the memory is relatively
|
// zero based. This is true on x86, typically.
|
// This is one bit per page.
|
uint64 arraysize = maxsize / 4096 / 8;
|
unsigned char *bitmap = new unsigned char[arraysize];
|
sat_assert(bitmap);
|
|
// Mark every page as 0, not seen.
|
memset(bitmap, 0, arraysize);
|
|
page_bitmap_size_ = maxsize;
|
page_bitmap_ = bitmap;
|
}
|
|
// Add the 4k pages in this block to the array of pages SAT has seen.
|
void Sat::AddrMapUpdate(struct page_entry *pe) {
|
if (!do_page_map_)
|
return;
|
|
// Go through 4k page blocks.
|
uint64 arraysize = page_bitmap_size_ / 4096 / 8;
|
|
char *base = reinterpret_cast<char*>(pe->addr);
|
for (int i = 0; i < page_length_; i += 4096) {
|
uint64 paddr = os_->VirtualToPhysical(base + i);
|
|
uint32 offset = paddr / 4096 / 8;
|
unsigned char mask = 1 << ((paddr / 4096) % 8);
|
|
if (offset >= arraysize) {
|
logprintf(0, "Process Error: Physical address %#llx is "
|
"greater than expected %#llx.\n",
|
paddr, page_bitmap_size_);
|
sat_assert(0);
|
}
|
page_bitmap_[offset] |= mask;
|
}
|
}
|
|
// Print out the physical memory ranges that SAT has accessed.
|
void Sat::AddrMapPrint() {
|
if (!do_page_map_)
|
return;
|
|
uint64 pages = page_bitmap_size_ / 4096;
|
|
uint64 last_page = 0;
|
bool valid_range = false;
|
|
logprintf(4, "Log: Printing tested physical ranges.\n");
|
|
for (uint64 i = 0; i < pages; i ++) {
|
int offset = i / 8;
|
unsigned char mask = 1 << (i % 8);
|
|
bool touched = page_bitmap_[offset] & mask;
|
if (touched && !valid_range) {
|
valid_range = true;
|
last_page = i * 4096;
|
} else if (!touched && valid_range) {
|
valid_range = false;
|
logprintf(4, "Log: %#016llx - %#016llx\n", last_page, (i * 4096) - 1);
|
}
|
}
|
logprintf(4, "Log: Done printing physical ranges.\n");
|
}
|
|
// Initializes page lists and fills pages with data patterns.
|
bool Sat::InitializePages() {
|
int result = 1;
|
// Calculate needed page totals.
|
int64 neededpages = memory_threads_ +
|
invert_threads_ +
|
check_threads_ +
|
net_threads_ +
|
file_threads_;
|
|
// Empty-valid page ratio is adjusted depending on queue implementation.
|
// since fine-grain-locked queue keeps both valid and empty entries in the
|
// same queue and randomly traverse to find pages, the empty-valid ratio
|
// should be more even.
|
if (pe_q_implementation_ == SAT_FINELOCK)
|
freepages_ = pages_ / 5 * 2; // Mark roughly 2/5 of all pages as Empty.
|
else
|
freepages_ = (pages_ / 100) + (2 * neededpages);
|
|
if (freepages_ < neededpages) {
|
logprintf(0, "Process Error: freepages < neededpages.\n");
|
logprintf(1, "Stats: Total: %lld, Needed: %lld, Marked free: %lld\n",
|
static_cast<int64>(pages_),
|
static_cast<int64>(neededpages),
|
static_cast<int64>(freepages_));
|
bad_status();
|
return false;
|
}
|
|
if (freepages_ > pages_/2) {
|
logprintf(0, "Process Error: not enough pages for IO\n");
|
logprintf(1, "Stats: Total: %lld, Needed: %lld, Available: %lld\n",
|
static_cast<int64>(pages_),
|
static_cast<int64>(freepages_),
|
static_cast<int64>(pages_/2));
|
bad_status();
|
return false;
|
}
|
logprintf(12, "Log: Allocating pages, Total: %lld Free: %lld\n",
|
pages_,
|
freepages_);
|
|
// Initialize page locations.
|
for (int64 i = 0; i < pages_; i++) {
|
struct page_entry pe;
|
init_pe(&pe);
|
pe.offset = i * page_length_;
|
result &= PutEmpty(&pe);
|
}
|
|
if (!result) {
|
logprintf(0, "Process Error: while initializing empty_ list\n");
|
bad_status();
|
return false;
|
}
|
|
// Fill valid pages with test patterns.
|
// Use fill threads to do this.
|
WorkerStatus fill_status;
|
WorkerVector fill_vector;
|
|
logprintf(12, "Starting Fill threads: %d threads, %d pages\n",
|
fill_threads_, pages_);
|
// Initialize the fill threads.
|
for (int i = 0; i < fill_threads_; i++) {
|
FillThread *thread = new FillThread();
|
thread->InitThread(i, this, os_, patternlist_, &fill_status);
|
if (i != fill_threads_ - 1) {
|
logprintf(12, "Starting Fill Threads %d: %d pages\n",
|
i, pages_ / fill_threads_);
|
thread->SetFillPages(pages_ / fill_threads_);
|
// The last thread finishes up all the leftover pages.
|
} else {
|
logprintf(12, "Starting Fill Threads %d: %d pages\n",
|
i, pages_ - pages_ / fill_threads_ * i);
|
thread->SetFillPages(pages_ - pages_ / fill_threads_ * i);
|
}
|
fill_vector.push_back(thread);
|
}
|
|
// Spawn the fill threads.
|
fill_status.Initialize();
|
for (WorkerVector::const_iterator it = fill_vector.begin();
|
it != fill_vector.end(); ++it)
|
(*it)->SpawnThread();
|
|
// Reap the finished fill threads.
|
for (WorkerVector::const_iterator it = fill_vector.begin();
|
it != fill_vector.end(); ++it) {
|
(*it)->JoinThread();
|
if ((*it)->GetStatus() != 1) {
|
logprintf(0, "Thread %d failed with status %d at %.2f seconds\n",
|
(*it)->ThreadID(), (*it)->GetStatus(),
|
(*it)->GetRunDurationUSec() * 1.0/1000000);
|
bad_status();
|
return false;
|
}
|
delete (*it);
|
}
|
fill_vector.clear();
|
fill_status.Destroy();
|
logprintf(12, "Log: Done filling pages.\n");
|
logprintf(12, "Log: Allocating pages.\n");
|
|
AddrMapInit();
|
|
// Initialize page locations.
|
for (int64 i = 0; i < pages_; i++) {
|
struct page_entry pe;
|
// Only get valid pages with uninitialized tags here.
|
if (GetValid(&pe, kInvalidTag)) {
|
int64 paddr = os_->VirtualToPhysical(pe.addr);
|
int32 region = os_->FindRegion(paddr);
|
region_[region]++;
|
pe.paddr = paddr;
|
pe.tag = 1 << region;
|
region_mask_ |= pe.tag;
|
|
// Generate a physical region map
|
AddrMapUpdate(&pe);
|
|
// Note: this does not allocate free pages among all regions
|
// fairly. However, with large enough (thousands) random number
|
// of pages being marked free in each region, the free pages
|
// count in each region end up pretty balanced.
|
if (i < freepages_) {
|
result &= PutEmpty(&pe);
|
} else {
|
result &= PutValid(&pe);
|
}
|
} else {
|
logprintf(0, "Log: didn't tag all pages. %d - %d = %d\n",
|
pages_, i, pages_ - i);
|
return false;
|
}
|
}
|
logprintf(12, "Log: Done allocating pages.\n");
|
|
AddrMapPrint();
|
|
for (int i = 0; i < 32; i++) {
|
if (region_mask_ & (1 << i)) {
|
region_count_++;
|
logprintf(12, "Log: Region %d: %d.\n", i, region_[i]);
|
}
|
}
|
logprintf(5, "Log: Region mask: 0x%x\n", region_mask_);
|
|
return true;
|
}
|
|
// Print SAT version info.
|
bool Sat::PrintVersion() {
|
logprintf(1, "Stats: SAT revision %s, %d bit binary\n",
|
kVersion, address_mode_);
|
logprintf(5, "Log: %s from %s\n", Timestamp(), BuildChangelist());
|
|
return true;
|
}
|
|
|
// Initializes the resources that SAT needs to run.
|
// This needs to be called before Run(), and after ParseArgs().
|
// Returns true on success, false on error, and will exit() on help message.
|
bool Sat::Initialize() {
|
g_sat = this;
|
|
// Initializes sync'd log file to ensure output is saved.
|
if (!InitializeLogfile())
|
return false;
|
Logger::GlobalLogger()->SetTimestampLogging(log_timestamps_);
|
Logger::GlobalLogger()->StartThread();
|
|
logprintf(5, "Log: Commandline - %s\n", cmdline_.c_str());
|
PrintVersion();
|
|
std::map<std::string, std::string> options;
|
|
GoogleOsOptions(&options);
|
|
// Initialize OS/Hardware interface.
|
os_ = OsLayerFactory(options);
|
if (!os_) {
|
bad_status();
|
return false;
|
}
|
|
if (min_hugepages_mbytes_ > 0)
|
os_->SetMinimumHugepagesSize(min_hugepages_mbytes_ * kMegabyte);
|
|
if (reserve_mb_ > 0)
|
os_->SetReserveSize(reserve_mb_);
|
|
if (channels_.size() > 0) {
|
logprintf(6, "Log: Decoding memory: %dx%d bit channels,"
|
"%d modules per channel (x%d), decoding hash 0x%x\n",
|
channels_.size(), channel_width_, channels_[0].size(),
|
channel_width_/channels_[0].size(), channel_hash_);
|
os_->SetDramMappingParams(channel_hash_, channel_width_, &channels_);
|
}
|
|
if (!os_->Initialize()) {
|
logprintf(0, "Process Error: Failed to initialize OS layer\n");
|
bad_status();
|
delete os_;
|
return false;
|
}
|
|
// Checks that OS/Build/Platform is supported.
|
if (!CheckEnvironment())
|
return false;
|
|
if (error_injection_)
|
os_->set_error_injection(true);
|
|
// Run SAT in monitor only mode, do not continue to allocate resources.
|
if (monitor_mode_) {
|
logprintf(5, "Log: Running in monitor-only mode. "
|
"Will not allocate any memory nor run any stress test. "
|
"Only polling ECC errors.\n");
|
return true;
|
}
|
|
// Allocate the memory to test.
|
if (!AllocateMemory())
|
return false;
|
|
logprintf(5, "Stats: Starting SAT, %dM, %d seconds\n",
|
static_cast<int>(size_/kMegabyte),
|
runtime_seconds_);
|
|
if (!InitializePatterns())
|
return false;
|
|
// Initialize memory allocation.
|
pages_ = size_ / page_length_;
|
|
// Allocate page queue depending on queue implementation switch.
|
if (pe_q_implementation_ == SAT_FINELOCK) {
|
finelock_q_ = new FineLockPEQueue(pages_, page_length_);
|
if (finelock_q_ == NULL)
|
return false;
|
finelock_q_->set_os(os_);
|
os_->set_err_log_callback(finelock_q_->get_err_log_callback());
|
} else if (pe_q_implementation_ == SAT_ONELOCK) {
|
empty_ = new PageEntryQueue(pages_);
|
valid_ = new PageEntryQueue(pages_);
|
if ((empty_ == NULL) || (valid_ == NULL))
|
return false;
|
}
|
|
if (!InitializePages()) {
|
logprintf(0, "Process Error: Initialize Pages failed\n");
|
return false;
|
}
|
|
return true;
|
}
|
|
// Constructor and destructor.
|
Sat::Sat() {
|
// Set defaults, command line might override these.
|
runtime_seconds_ = 20;
|
page_length_ = kSatPageSize;
|
disk_pages_ = kSatDiskPage;
|
pages_ = 0;
|
size_mb_ = 0;
|
size_ = size_mb_ * kMegabyte;
|
reserve_mb_ = 0;
|
min_hugepages_mbytes_ = 0;
|
freepages_ = 0;
|
paddr_base_ = 0;
|
channel_hash_ = kCacheLineSize;
|
channel_width_ = 64;
|
|
user_break_ = false;
|
verbosity_ = 8;
|
Logger::GlobalLogger()->SetVerbosity(verbosity_);
|
print_delay_ = 10;
|
strict_ = 1;
|
warm_ = 0;
|
run_on_anything_ = 0;
|
use_logfile_ = 0;
|
logfile_ = 0;
|
log_timestamps_ = true;
|
// Detect 32/64 bit binary.
|
void *pvoid = 0;
|
address_mode_ = sizeof(pvoid) * 8;
|
error_injection_ = false;
|
crazy_error_injection_ = false;
|
max_errorcount_ = 0; // Zero means no early exit.
|
stop_on_error_ = false;
|
error_poll_ = true;
|
findfiles_ = false;
|
|
do_page_map_ = false;
|
page_bitmap_ = 0;
|
page_bitmap_size_ = 0;
|
|
// Cache coherency data initialization.
|
cc_test_ = false; // Flag to trigger cc threads.
|
cc_cacheline_count_ = 2; // Two datastructures of cache line size.
|
cc_cacheline_size_ = 0; // Size of a cacheline (0 for auto-detect).
|
cc_inc_count_ = 1000; // Number of times to increment the shared variable.
|
cc_cacheline_data_ = 0; // Cache Line size datastructure.
|
|
// Cpu frequency data initialization.
|
cpu_freq_test_ = false; // Flag to trigger cpu frequency thread.
|
cpu_freq_threshold_ = 0; // Threshold, in MHz, at which a cpu fails.
|
cpu_freq_round_ = 10; // Round the computed frequency to this value.
|
|
sat_assert(0 == pthread_mutex_init(&worker_lock_, NULL));
|
file_threads_ = 0;
|
net_threads_ = 0;
|
listen_threads_ = 0;
|
// Default to autodetect number of cpus, and run that many threads.
|
memory_threads_ = -1;
|
invert_threads_ = 0;
|
fill_threads_ = 8;
|
check_threads_ = 0;
|
cpu_stress_threads_ = 0;
|
disk_threads_ = 0;
|
total_threads_ = 0;
|
|
region_mask_ = 0;
|
region_count_ = 0;
|
for (int i = 0; i < 32; i++) {
|
region_[i] = 0;
|
}
|
region_mode_ = 0;
|
|
errorcount_ = 0;
|
statuscount_ = 0;
|
|
valid_ = 0;
|
empty_ = 0;
|
finelock_q_ = 0;
|
// Default to use fine-grain lock for better performance.
|
pe_q_implementation_ = SAT_FINELOCK;
|
|
os_ = 0;
|
patternlist_ = 0;
|
logfilename_[0] = 0;
|
|
read_block_size_ = 512;
|
write_block_size_ = -1;
|
segment_size_ = -1;
|
cache_size_ = -1;
|
blocks_per_segment_ = -1;
|
read_threshold_ = -1;
|
write_threshold_ = -1;
|
non_destructive_ = 1;
|
monitor_mode_ = 0;
|
tag_mode_ = 0;
|
random_threads_ = 0;
|
|
pause_delay_ = 600;
|
pause_duration_ = 15;
|
}
|
|
// Destructor.
|
Sat::~Sat() {
|
// We need to have called Cleanup() at this point.
|
// We should probably enforce this.
|
}
|
|
|
#define ARG_KVALUE(argument, variable, value) \
|
if (!strcmp(argv[i], argument)) { \
|
variable = value; \
|
continue; \
|
}
|
|
#define ARG_IVALUE(argument, variable) \
|
if (!strcmp(argv[i], argument)) { \
|
i++; \
|
if (i < argc) \
|
variable = strtoull(argv[i], NULL, 0); \
|
continue; \
|
}
|
|
#define ARG_SVALUE(argument, variable) \
|
if (!strcmp(argv[i], argument)) { \
|
i++; \
|
if (i < argc) \
|
snprintf(variable, sizeof(variable), "%s", argv[i]); \
|
continue; \
|
}
|
|
// Configures SAT from command line arguments.
|
// This will call exit() given a request for
|
// self-documentation or unexpected args.
|
bool Sat::ParseArgs(int argc, char **argv) {
|
int i;
|
uint64 filesize = page_length_ * disk_pages_;
|
|
// Parse each argument.
|
for (i = 1; i < argc; i++) {
|
// Switch to fall back to corase-grain-lock queue. (for benchmarking)
|
ARG_KVALUE("--coarse_grain_lock", pe_q_implementation_, SAT_ONELOCK);
|
|
// Set number of megabyte to use.
|
ARG_IVALUE("-M", size_mb_);
|
|
// Specify the amount of megabytes to be reserved for system.
|
ARG_IVALUE("--reserve_memory", reserve_mb_);
|
|
// Set minimum megabytes of hugepages to require.
|
ARG_IVALUE("-H", min_hugepages_mbytes_);
|
|
// Set number of seconds to run.
|
ARG_IVALUE("-s", runtime_seconds_);
|
|
// Set number of memory copy threads.
|
ARG_IVALUE("-m", memory_threads_);
|
|
// Set number of memory invert threads.
|
ARG_IVALUE("-i", invert_threads_);
|
|
// Set number of check-only threads.
|
ARG_IVALUE("-c", check_threads_);
|
|
// Set number of cache line size datastructures.
|
ARG_IVALUE("--cc_inc_count", cc_inc_count_);
|
|
// Set number of cache line size datastructures
|
ARG_IVALUE("--cc_line_count", cc_cacheline_count_);
|
|
// Override the detected or assumed cache line size.
|
ARG_IVALUE("--cc_line_size", cc_cacheline_size_);
|
|
// Flag set when cache coherency tests need to be run
|
ARG_KVALUE("--cc_test", cc_test_, true);
|
|
// Set when the cpu_frequency test needs to be run
|
ARG_KVALUE("--cpu_freq_test", cpu_freq_test_, true);
|
|
// Set the threshold in MHz at which the cpu frequency test will fail.
|
ARG_IVALUE("--cpu_freq_threshold", cpu_freq_threshold_);
|
|
// Set the rounding value for the cpu frequency test. The default is to
|
// round to the nearest 10s value.
|
ARG_IVALUE("--cpu_freq_round", cpu_freq_round_);
|
|
// Set number of CPU stress threads.
|
ARG_IVALUE("-C", cpu_stress_threads_);
|
|
// Set logfile name.
|
ARG_SVALUE("-l", logfilename_);
|
|
// Verbosity level.
|
ARG_IVALUE("-v", verbosity_);
|
|
// Chatty printout level.
|
ARG_IVALUE("--printsec", print_delay_);
|
|
// Turn off timestamps logging.
|
ARG_KVALUE("--no_timestamps", log_timestamps_, false);
|
|
// Set maximum number of errors to collect. Stop running after this many.
|
ARG_IVALUE("--max_errors", max_errorcount_);
|
|
// Set pattern block size.
|
ARG_IVALUE("-p", page_length_);
|
|
// Set pattern block size.
|
ARG_IVALUE("--filesize", filesize);
|
|
// NUMA options.
|
ARG_KVALUE("--local_numa", region_mode_, kLocalNuma);
|
ARG_KVALUE("--remote_numa", region_mode_, kRemoteNuma);
|
|
// Autodetect tempfile locations.
|
ARG_KVALUE("--findfiles", findfiles_, 1);
|
|
// Inject errors to force miscompare code paths
|
ARG_KVALUE("--force_errors", error_injection_, true);
|
ARG_KVALUE("--force_errors_like_crazy", crazy_error_injection_, true);
|
if (crazy_error_injection_)
|
error_injection_ = true;
|
|
// Stop immediately on any arror, for debugging HW problems.
|
ARG_KVALUE("--stop_on_errors", stop_on_error_, 1);
|
|
// Don't use internal error polling, allow external detection.
|
ARG_KVALUE("--no_errors", error_poll_, 0);
|
|
// Never check data as you go.
|
ARG_KVALUE("-F", strict_, 0);
|
|
// Warm the cpu as you go.
|
ARG_KVALUE("-W", warm_, 1);
|
|
// Allow runnign on unknown systems with base unimplemented OsLayer
|
ARG_KVALUE("-A", run_on_anything_, 1);
|
|
// Size of read blocks for disk test.
|
ARG_IVALUE("--read-block-size", read_block_size_);
|
|
// Size of write blocks for disk test.
|
ARG_IVALUE("--write-block-size", write_block_size_);
|
|
// Size of segment for disk test.
|
ARG_IVALUE("--segment-size", segment_size_);
|
|
// Size of disk cache size for disk test.
|
ARG_IVALUE("--cache-size", cache_size_);
|
|
// Number of blocks to test per segment.
|
ARG_IVALUE("--blocks-per-segment", blocks_per_segment_);
|
|
// Maximum time a block read should take before warning.
|
ARG_IVALUE("--read-threshold", read_threshold_);
|
|
// Maximum time a block write should take before warning.
|
ARG_IVALUE("--write-threshold", write_threshold_);
|
|
// Do not write anything to disk in the disk test.
|
ARG_KVALUE("--destructive", non_destructive_, 0);
|
|
// Run SAT in monitor mode. No test load at all.
|
ARG_KVALUE("--monitor_mode", monitor_mode_, true);
|
|
// Run SAT in address mode. Tag all cachelines by virt addr.
|
ARG_KVALUE("--tag_mode", tag_mode_, true);
|
|
// Dump range map of tested pages..
|
ARG_KVALUE("--do_page_map", do_page_map_, true);
|
|
// Specify the physical address base to test.
|
ARG_IVALUE("--paddr_base", paddr_base_);
|
|
// Specify the frequency for power spikes.
|
ARG_IVALUE("--pause_delay", pause_delay_);
|
|
// Specify the duration of each pause (for power spikes).
|
ARG_IVALUE("--pause_duration", pause_duration_);
|
|
// Disk device names
|
if (!strcmp(argv[i], "-d")) {
|
i++;
|
if (i < argc) {
|
disk_threads_++;
|
diskfilename_.push_back(string(argv[i]));
|
blocktables_.push_back(new DiskBlockTable());
|
}
|
continue;
|
}
|
|
// Set number of disk random threads for each disk write thread.
|
ARG_IVALUE("--random-threads", random_threads_);
|
|
// Set a tempfile to use in a file thread.
|
if (!strcmp(argv[i], "-f")) {
|
i++;
|
if (i < argc) {
|
file_threads_++;
|
filename_.push_back(string(argv[i]));
|
}
|
continue;
|
}
|
|
// Set a hostname to use in a network thread.
|
if (!strcmp(argv[i], "-n")) {
|
i++;
|
if (i < argc) {
|
net_threads_++;
|
ipaddrs_.push_back(string(argv[i]));
|
}
|
continue;
|
}
|
|
// Run threads that listen for incoming SAT net connections.
|
ARG_KVALUE("--listen", listen_threads_, 1);
|
|
if (CheckGoogleSpecificArgs(argc, argv, &i)) {
|
continue;
|
}
|
|
ARG_IVALUE("--channel_hash", channel_hash_);
|
ARG_IVALUE("--channel_width", channel_width_);
|
|
if (!strcmp(argv[i], "--memory_channel")) {
|
i++;
|
if (i < argc) {
|
char *channel = argv[i];
|
channels_.push_back(vector<string>());
|
while (char* next = strchr(channel, ',')) {
|
channels_.back().push_back(string(channel, next - channel));
|
channel = next + 1;
|
}
|
channels_.back().push_back(string(channel));
|
}
|
continue;
|
}
|
|
// Default:
|
PrintVersion();
|
PrintHelp();
|
if (strcmp(argv[i], "-h") && strcmp(argv[i], "--help")) {
|
printf("\n Unknown argument %s\n", argv[i]);
|
bad_status();
|
exit(1);
|
}
|
// Forget it, we printed the help, just bail.
|
// We don't want to print test status, or any log parser stuff.
|
exit(0);
|
}
|
|
Logger::GlobalLogger()->SetVerbosity(verbosity_);
|
|
// Update relevant data members with parsed input.
|
// Translate MB into bytes.
|
size_ = static_cast<int64>(size_mb_) * kMegabyte;
|
|
// Set logfile flag.
|
if (strcmp(logfilename_, ""))
|
use_logfile_ = 1;
|
// Checks valid page length.
|
if (page_length_ &&
|
!(page_length_ & (page_length_ - 1)) &&
|
(page_length_ > 1023)) {
|
// Prints if we have changed from default.
|
if (page_length_ != kSatPageSize)
|
logprintf(12, "Log: Updating page size to %d\n", page_length_);
|
} else {
|
// Revert to default page length.
|
logprintf(6, "Process Error: "
|
"Invalid page size %d\n", page_length_);
|
page_length_ = kSatPageSize;
|
return false;
|
}
|
|
// Set disk_pages_ if filesize or page size changed.
|
if (filesize != static_cast<uint64>(page_length_) *
|
static_cast<uint64>(disk_pages_)) {
|
disk_pages_ = filesize / page_length_;
|
if (disk_pages_ == 0)
|
disk_pages_ = 1;
|
}
|
|
// Validate memory channel parameters if supplied
|
if (channels_.size()) {
|
if (channels_.size() == 1) {
|
channel_hash_ = 0;
|
logprintf(7, "Log: "
|
"Only one memory channel...deactivating interleave decoding.\n");
|
} else if (channels_.size() > 2) {
|
logprintf(6, "Process Error: "
|
"Triple-channel mode not yet supported... sorry.\n");
|
bad_status();
|
return false;
|
}
|
for (uint i = 0; i < channels_.size(); i++)
|
if (channels_[i].size() != channels_[0].size()) {
|
logprintf(6, "Process Error: "
|
"Channels 0 and %d have a different count of dram modules.\n", i);
|
bad_status();
|
return false;
|
}
|
if (channels_[0].size() & (channels_[0].size() - 1)) {
|
logprintf(6, "Process Error: "
|
"Amount of modules per memory channel is not a power of 2.\n");
|
bad_status();
|
return false;
|
}
|
if (channel_width_ < 16
|
|| channel_width_ & (channel_width_ - 1)) {
|
logprintf(6, "Process Error: "
|
"Channel width %d is invalid.\n", channel_width_);
|
bad_status();
|
return false;
|
}
|
if (channel_width_ / channels_[0].size() < 8) {
|
logprintf(6, "Process Error: Chip width x%d must be x8 or greater.\n",
|
channel_width_ / channels_[0].size());
|
bad_status();
|
return false;
|
}
|
}
|
|
|
// Print each argument.
|
for (int i = 0; i < argc; i++) {
|
if (i)
|
cmdline_ += " ";
|
cmdline_ += argv[i];
|
}
|
|
return true;
|
}
|
|
void Sat::PrintHelp() {
|
printf("Usage: ./sat(32|64) [options]\n"
|
" -M mbytes megabytes of ram to test\n"
|
" --reserve-memory If not using hugepages, the amount of memory to "
|
" reserve for the system\n"
|
" -H mbytes minimum megabytes of hugepages to require\n"
|
" -s seconds number of seconds to run\n"
|
" -m threads number of memory copy threads to run\n"
|
" -i threads number of memory invert threads to run\n"
|
" -C threads number of memory CPU stress threads to run\n"
|
" --findfiles find locations to do disk IO automatically\n"
|
" -d device add a direct write disk thread with block "
|
"device (or file) 'device'\n"
|
" -f filename add a disk thread with "
|
"tempfile 'filename'\n"
|
" -l logfile log output to file 'logfile'\n"
|
" --no_timestamps do not prefix timestamps to log messages\n"
|
" --max_errors n exit early after finding 'n' errors\n"
|
" -v level verbosity (0-20), default is 8\n"
|
" --printsec secs How often to print 'seconds remaining'\n"
|
" -W Use more CPU-stressful memory copy\n"
|
" -A run in degraded mode on incompatible systems\n"
|
" -p pagesize size in bytes of memory chunks\n"
|
" --filesize size size of disk IO tempfiles\n"
|
" -n ipaddr add a network thread connecting to "
|
"system at 'ipaddr'\n"
|
" --listen run a thread to listen for and respond "
|
"to network threads.\n"
|
" --no_errors run without checking for ECC or other errors\n"
|
" --force_errors inject false errors to test error handling\n"
|
" --force_errors_like_crazy inject a lot of false errors "
|
"to test error handling\n"
|
" -F don't result check each transaction\n"
|
" --stop_on_errors Stop after finding the first error.\n"
|
" --read-block-size size of block for reading (-d)\n"
|
" --write-block-size size of block for writing (-d). If not "
|
"defined, the size of block for writing will be defined as the "
|
"size of block for reading\n"
|
" --segment-size size of segments to split disk into (-d)\n"
|
" --cache-size size of disk cache (-d)\n"
|
" --blocks-per-segment number of blocks to read/write per "
|
"segment per iteration (-d)\n"
|
" --read-threshold maximum time (in us) a block read should "
|
"take (-d)\n"
|
" --write-threshold maximum time (in us) a block write "
|
"should take (-d)\n"
|
" --random-threads number of random threads for each disk "
|
"write thread (-d)\n"
|
" --destructive write/wipe disk partition (-d)\n"
|
" --monitor_mode only do ECC error polling, no stress load.\n"
|
" --cc_test do the cache coherency testing\n"
|
" --cc_inc_count number of times to increment the "
|
"cacheline's member\n"
|
" --cc_line_count number of cache line sized datastructures "
|
"to allocate for the cache coherency threads to operate\n"
|
" --cc_line_size override the auto-detected cache line size\n"
|
" --cpu_freq_test enable the cpu frequency test (requires the "
|
"--cpu_freq_threshold argument to be set)\n"
|
" --cpu_freq_threshold fail the cpu frequency test if the frequency "
|
"goes below this value (specified in MHz)\n"
|
" --cpu_freq_round round the computed frequency to this value, if set"
|
" to zero, only round to the nearest MHz\n"
|
" --paddr_base allocate memory starting from this address\n"
|
" --pause_delay delay (in seconds) between power spikes\n"
|
" --pause_duration duration (in seconds) of each pause\n"
|
" --local_numa choose memory regions associated with "
|
"each CPU to be tested by that CPU\n"
|
" --remote_numa choose memory regions not associated with "
|
"each CPU to be tested by that CPU\n"
|
" --channel_hash mask of address bits XORed to determine channel. "
|
"Mask 0x40 interleaves cachelines between channels\n"
|
" --channel_width bits width in bits of each memory channel\n"
|
" --memory_channel u1,u2 defines a comma-separated list of names "
|
"for dram packages in a memory channel. Use multiple times to "
|
"define multiple channels.\n");
|
}
|
|
bool Sat::CheckGoogleSpecificArgs(int argc, char **argv, int *i) {
|
// Do nothing, no google-specific argument on public stressapptest
|
return false;
|
}
|
|
void Sat::GoogleOsOptions(std::map<std::string, std::string> *options) {
|
// Do nothing, no OS-specific argument on public stressapptest
|
}
|
|
// Launch the SAT task threads. Returns 0 on error.
|
void Sat::InitializeThreads() {
|
// Memory copy threads.
|
AcquireWorkerLock();
|
|
logprintf(12, "Log: Starting worker threads\n");
|
WorkerVector *memory_vector = new WorkerVector();
|
|
// Error polling thread.
|
// This may detect ECC corrected errors, disk problems, or
|
// any other errors normally hidden from userspace.
|
WorkerVector *error_vector = new WorkerVector();
|
if (error_poll_) {
|
ErrorPollThread *thread = new ErrorPollThread();
|
thread->InitThread(total_threads_++, this, os_, patternlist_,
|
&continuous_status_);
|
|
error_vector->insert(error_vector->end(), thread);
|
} else {
|
logprintf(5, "Log: Skipping error poll thread due to --no_errors flag\n");
|
}
|
workers_map_.insert(make_pair(kErrorType, error_vector));
|
|
// Only start error poll threads for monitor-mode SAT,
|
// skip all other types of worker threads.
|
if (monitor_mode_) {
|
ReleaseWorkerLock();
|
return;
|
}
|
|
for (int i = 0; i < memory_threads_; i++) {
|
CopyThread *thread = new CopyThread();
|
thread->InitThread(total_threads_++, this, os_, patternlist_,
|
&power_spike_status_);
|
|
if ((region_count_ > 1) && (region_mode_)) {
|
int32 region = region_find(i % region_count_);
|
cpu_set_t *cpuset = os_->FindCoreMask(region);
|
sat_assert(cpuset);
|
if (region_mode_ == kLocalNuma) {
|
// Choose regions associated with this CPU.
|
thread->set_cpu_mask(cpuset);
|
thread->set_tag(1 << region);
|
} else if (region_mode_ == kRemoteNuma) {
|
// Choose regions not associated with this CPU..
|
thread->set_cpu_mask(cpuset);
|
thread->set_tag(region_mask_ & ~(1 << region));
|
}
|
} else {
|
cpu_set_t available_cpus;
|
thread->AvailableCpus(&available_cpus);
|
int cores = cpuset_count(&available_cpus);
|
// Don't restrict thread location if we have more than one
|
// thread per core. Not so good for performance.
|
if (cpu_stress_threads_ + memory_threads_ <= cores) {
|
// Place a thread on alternating cores first.
|
// This assures interleaved core use with no overlap.
|
int nthcore = i;
|
int nthbit = (((2 * nthcore) % cores) +
|
(((2 * nthcore) / cores) % 2)) % cores;
|
cpu_set_t all_cores;
|
cpuset_set_ab(&all_cores, 0, cores);
|
if (!cpuset_isequal(&available_cpus, &all_cores)) {
|
// We are assuming the bits are contiguous.
|
// Complain if this is not so.
|
logprintf(0, "Log: cores = %s, expected %s\n",
|
cpuset_format(&available_cpus).c_str(),
|
cpuset_format(&all_cores).c_str());
|
}
|
|
// Set thread affinity.
|
thread->set_cpu_mask_to_cpu(nthbit);
|
}
|
}
|
memory_vector->insert(memory_vector->end(), thread);
|
}
|
workers_map_.insert(make_pair(kMemoryType, memory_vector));
|
|
// File IO threads.
|
WorkerVector *fileio_vector = new WorkerVector();
|
for (int i = 0; i < file_threads_; i++) {
|
FileThread *thread = new FileThread();
|
thread->InitThread(total_threads_++, this, os_, patternlist_,
|
&power_spike_status_);
|
thread->SetFile(filename_[i].c_str());
|
// Set disk threads high priority. They don't take much processor time,
|
// but blocking them will delay disk IO.
|
thread->SetPriority(WorkerThread::High);
|
|
fileio_vector->insert(fileio_vector->end(), thread);
|
}
|
workers_map_.insert(make_pair(kFileIOType, fileio_vector));
|
|
// Net IO threads.
|
WorkerVector *netio_vector = new WorkerVector();
|
WorkerVector *netslave_vector = new WorkerVector();
|
if (listen_threads_ > 0) {
|
// Create a network slave thread. This listens for connections.
|
NetworkListenThread *thread = new NetworkListenThread();
|
thread->InitThread(total_threads_++, this, os_, patternlist_,
|
&continuous_status_);
|
|
netslave_vector->insert(netslave_vector->end(), thread);
|
}
|
for (int i = 0; i < net_threads_; i++) {
|
NetworkThread *thread = new NetworkThread();
|
thread->InitThread(total_threads_++, this, os_, patternlist_,
|
&continuous_status_);
|
thread->SetIP(ipaddrs_[i].c_str());
|
|
netio_vector->insert(netio_vector->end(), thread);
|
}
|
workers_map_.insert(make_pair(kNetIOType, netio_vector));
|
workers_map_.insert(make_pair(kNetSlaveType, netslave_vector));
|
|
// Result check threads.
|
WorkerVector *check_vector = new WorkerVector();
|
for (int i = 0; i < check_threads_; i++) {
|
CheckThread *thread = new CheckThread();
|
thread->InitThread(total_threads_++, this, os_, patternlist_,
|
&continuous_status_);
|
|
check_vector->insert(check_vector->end(), thread);
|
}
|
workers_map_.insert(make_pair(kCheckType, check_vector));
|
|
// Memory invert threads.
|
logprintf(12, "Log: Starting invert threads\n");
|
WorkerVector *invert_vector = new WorkerVector();
|
for (int i = 0; i < invert_threads_; i++) {
|
InvertThread *thread = new InvertThread();
|
thread->InitThread(total_threads_++, this, os_, patternlist_,
|
&continuous_status_);
|
|
invert_vector->insert(invert_vector->end(), thread);
|
}
|
workers_map_.insert(make_pair(kInvertType, invert_vector));
|
|
// Disk stress threads.
|
WorkerVector *disk_vector = new WorkerVector();
|
WorkerVector *random_vector = new WorkerVector();
|
logprintf(12, "Log: Starting disk stress threads\n");
|
for (int i = 0; i < disk_threads_; i++) {
|
// Creating write threads
|
DiskThread *thread = new DiskThread(blocktables_[i]);
|
thread->InitThread(total_threads_++, this, os_, patternlist_,
|
&power_spike_status_);
|
thread->SetDevice(diskfilename_[i].c_str());
|
if (thread->SetParameters(read_block_size_, write_block_size_,
|
segment_size_, cache_size_,
|
blocks_per_segment_,
|
read_threshold_, write_threshold_,
|
non_destructive_)) {
|
disk_vector->insert(disk_vector->end(), thread);
|
} else {
|
logprintf(12, "Log: DiskThread::SetParameters() failed\n");
|
delete thread;
|
}
|
|
for (int j = 0; j < random_threads_; j++) {
|
// Creating random threads
|
RandomDiskThread *rthread = new RandomDiskThread(blocktables_[i]);
|
rthread->InitThread(total_threads_++, this, os_, patternlist_,
|
&power_spike_status_);
|
rthread->SetDevice(diskfilename_[i].c_str());
|
if (rthread->SetParameters(read_block_size_, write_block_size_,
|
segment_size_, cache_size_,
|
blocks_per_segment_,
|
read_threshold_, write_threshold_,
|
non_destructive_)) {
|
random_vector->insert(random_vector->end(), rthread);
|
} else {
|
logprintf(12, "Log: RandomDiskThread::SetParameters() failed\n");
|
delete rthread;
|
}
|
}
|
}
|
|
workers_map_.insert(make_pair(kDiskType, disk_vector));
|
workers_map_.insert(make_pair(kRandomDiskType, random_vector));
|
|
// CPU stress threads.
|
WorkerVector *cpu_vector = new WorkerVector();
|
logprintf(12, "Log: Starting cpu stress threads\n");
|
for (int i = 0; i < cpu_stress_threads_; i++) {
|
CpuStressThread *thread = new CpuStressThread();
|
thread->InitThread(total_threads_++, this, os_, patternlist_,
|
&continuous_status_);
|
|
// Don't restrict thread location if we have more than one
|
// thread per core. Not so good for performance.
|
cpu_set_t available_cpus;
|
thread->AvailableCpus(&available_cpus);
|
int cores = cpuset_count(&available_cpus);
|
if (cpu_stress_threads_ + memory_threads_ <= cores) {
|
// Place a thread on alternating cores first.
|
// Go in reverse order for CPU stress threads. This assures interleaved
|
// core use with no overlap.
|
int nthcore = (cores - 1) - i;
|
int nthbit = (((2 * nthcore) % cores) +
|
(((2 * nthcore) / cores) % 2)) % cores;
|
cpu_set_t all_cores;
|
cpuset_set_ab(&all_cores, 0, cores);
|
if (!cpuset_isequal(&available_cpus, &all_cores)) {
|
logprintf(0, "Log: cores = %s, expected %s\n",
|
cpuset_format(&available_cpus).c_str(),
|
cpuset_format(&all_cores).c_str());
|
}
|
|
// Set thread affinity.
|
thread->set_cpu_mask_to_cpu(nthbit);
|
}
|
|
|
cpu_vector->insert(cpu_vector->end(), thread);
|
}
|
workers_map_.insert(make_pair(kCPUType, cpu_vector));
|
|
// CPU Cache Coherency Threads - one for each core available.
|
if (cc_test_) {
|
WorkerVector *cc_vector = new WorkerVector();
|
logprintf(12, "Log: Starting cpu cache coherency threads\n");
|
|
// Allocate the shared datastructure to be worked on by the threads.
|
cc_cacheline_data_ = reinterpret_cast<cc_cacheline_data*>(
|
malloc(sizeof(cc_cacheline_data) * cc_cacheline_count_));
|
sat_assert(cc_cacheline_data_ != NULL);
|
|
// Initialize the strucutre.
|
memset(cc_cacheline_data_, 0,
|
sizeof(cc_cacheline_data) * cc_cacheline_count_);
|
|
int num_cpus = CpuCount();
|
char *num;
|
// Calculate the number of cache lines needed just to give each core
|
// its own counter.
|
int line_size = cc_cacheline_size_;
|
if (line_size <= 0) {
|
line_size = CacheLineSize();
|
if (line_size < kCacheLineSize)
|
line_size = kCacheLineSize;
|
logprintf(12, "Log: Using %d as cache line size\n", line_size);
|
}
|
// The number of cache lines needed to hold an array of num_cpus.
|
// "num" must be the same type as cc_cacheline_data[X].num or the memory
|
// size calculations will fail.
|
int needed_lines = (sizeof(*num) * num_cpus + line_size - 1) / line_size;
|
// Allocate all the nums once so that we get a single chunk
|
// of contiguous memory.
|
#ifdef HAVE_POSIX_MEMALIGN
|
int err_result = posix_memalign(
|
reinterpret_cast<void**>(&num),
|
line_size, line_size * needed_lines * cc_cacheline_count_);
|
#else
|
num = reinterpret_cast<char*>(memalign(
|
line_size, line_size * needed_lines * cc_cacheline_count_));
|
int err_result = (num == 0);
|
#endif
|
sat_assert(err_result == 0);
|
|
int cline;
|
for (cline = 0; cline < cc_cacheline_count_; cline++) {
|
memset(num, 0, sizeof(*num) * num_cpus);
|
cc_cacheline_data_[cline].num = num;
|
num += (line_size * needed_lines) / sizeof(*num);
|
}
|
|
int tnum;
|
for (tnum = 0; tnum < num_cpus; tnum++) {
|
CpuCacheCoherencyThread *thread =
|
new CpuCacheCoherencyThread(cc_cacheline_data_, cc_cacheline_count_,
|
tnum, num_cpus, cc_inc_count_);
|
thread->InitThread(total_threads_++, this, os_, patternlist_,
|
&continuous_status_);
|
// Pin the thread to a particular core.
|
thread->set_cpu_mask_to_cpu(tnum);
|
|
// Insert the thread into the vector.
|
cc_vector->insert(cc_vector->end(), thread);
|
}
|
workers_map_.insert(make_pair(kCCType, cc_vector));
|
}
|
|
if (cpu_freq_test_) {
|
// Create the frequency test thread.
|
logprintf(5, "Log: Running cpu frequency test: threshold set to %dMHz.\n",
|
cpu_freq_threshold_);
|
CpuFreqThread *thread = new CpuFreqThread(CpuCount(), cpu_freq_threshold_,
|
cpu_freq_round_);
|
// This thread should be paused when other threads are paused.
|
thread->InitThread(total_threads_++, this, os_, NULL,
|
&power_spike_status_);
|
|
WorkerVector *cpu_freq_vector = new WorkerVector();
|
cpu_freq_vector->insert(cpu_freq_vector->end(), thread);
|
workers_map_.insert(make_pair(kCPUFreqType, cpu_freq_vector));
|
}
|
|
ReleaseWorkerLock();
|
}
|
|
// Return the number of cpus actually present in the machine.
|
int Sat::CpuCount() {
|
return sysconf(_SC_NPROCESSORS_CONF);
|
}
|
|
// Return the worst case (largest) cache line size of the various levels of
|
// cache actually prsent in the machine.
|
int Sat::CacheLineSize() {
|
int max_linesize = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
|
int linesize = sysconf(_SC_LEVEL2_CACHE_LINESIZE);
|
if (linesize > max_linesize) max_linesize = linesize;
|
linesize = sysconf(_SC_LEVEL3_CACHE_LINESIZE);
|
if (linesize > max_linesize) max_linesize = linesize;
|
linesize = sysconf(_SC_LEVEL4_CACHE_LINESIZE);
|
if (linesize > max_linesize) max_linesize = linesize;
|
return max_linesize;
|
}
|
|
// Notify and reap worker threads.
|
void Sat::JoinThreads() {
|
logprintf(12, "Log: Joining worker threads\n");
|
power_spike_status_.StopWorkers();
|
continuous_status_.StopWorkers();
|
|
AcquireWorkerLock();
|
for (WorkerMap::const_iterator map_it = workers_map_.begin();
|
map_it != workers_map_.end(); ++map_it) {
|
for (WorkerVector::const_iterator it = map_it->second->begin();
|
it != map_it->second->end(); ++it) {
|
logprintf(12, "Log: Joining thread %d\n", (*it)->ThreadID());
|
(*it)->JoinThread();
|
}
|
}
|
ReleaseWorkerLock();
|
|
QueueStats();
|
|
// Finish up result checking.
|
// Spawn 4 check threads to minimize check time.
|
logprintf(12, "Log: Finished countdown, begin to result check\n");
|
WorkerStatus reap_check_status;
|
WorkerVector reap_check_vector;
|
|
// No need for check threads for monitor mode.
|
if (!monitor_mode_) {
|
// Initialize the check threads.
|
for (int i = 0; i < fill_threads_; i++) {
|
CheckThread *thread = new CheckThread();
|
thread->InitThread(total_threads_++, this, os_, patternlist_,
|
&reap_check_status);
|
logprintf(12, "Log: Finished countdown, begin to result check\n");
|
reap_check_vector.push_back(thread);
|
}
|
}
|
|
reap_check_status.Initialize();
|
// Check threads should be marked to stop ASAP.
|
reap_check_status.StopWorkers();
|
|
// Spawn the check threads.
|
for (WorkerVector::const_iterator it = reap_check_vector.begin();
|
it != reap_check_vector.end(); ++it) {
|
logprintf(12, "Log: Spawning thread %d\n", (*it)->ThreadID());
|
(*it)->SpawnThread();
|
}
|
|
// Join the check threads.
|
for (WorkerVector::const_iterator it = reap_check_vector.begin();
|
it != reap_check_vector.end(); ++it) {
|
logprintf(12, "Log: Joining thread %d\n", (*it)->ThreadID());
|
(*it)->JoinThread();
|
}
|
|
// Reap all children. Stopped threads should have already ended.
|
// Result checking threads will end when they have finished
|
// result checking.
|
logprintf(12, "Log: Join all outstanding threads\n");
|
|
// Find all errors.
|
errorcount_ = GetTotalErrorCount();
|
|
AcquireWorkerLock();
|
for (WorkerMap::const_iterator map_it = workers_map_.begin();
|
map_it != workers_map_.end(); ++map_it) {
|
for (WorkerVector::const_iterator it = map_it->second->begin();
|
it != map_it->second->end(); ++it) {
|
logprintf(12, "Log: Reaping thread status %d\n", (*it)->ThreadID());
|
if ((*it)->GetStatus() != 1) {
|
logprintf(0, "Process Error: Thread %d failed with status %d at "
|
"%.2f seconds\n",
|
(*it)->ThreadID(), (*it)->GetStatus(),
|
(*it)->GetRunDurationUSec()*1.0/1000000);
|
bad_status();
|
}
|
int priority = 12;
|
if ((*it)->GetErrorCount())
|
priority = 5;
|
logprintf(priority, "Log: Thread %d found %lld hardware incidents\n",
|
(*it)->ThreadID(), (*it)->GetErrorCount());
|
}
|
}
|
ReleaseWorkerLock();
|
|
|
// Add in any errors from check threads.
|
for (WorkerVector::const_iterator it = reap_check_vector.begin();
|
it != reap_check_vector.end(); ++it) {
|
logprintf(12, "Log: Reaping thread status %d\n", (*it)->ThreadID());
|
if ((*it)->GetStatus() != 1) {
|
logprintf(0, "Process Error: Thread %d failed with status %d at "
|
"%.2f seconds\n",
|
(*it)->ThreadID(), (*it)->GetStatus(),
|
(*it)->GetRunDurationUSec()*1.0/1000000);
|
bad_status();
|
}
|
errorcount_ += (*it)->GetErrorCount();
|
int priority = 12;
|
if ((*it)->GetErrorCount())
|
priority = 5;
|
logprintf(priority, "Log: Thread %d found %lld hardware incidents\n",
|
(*it)->ThreadID(), (*it)->GetErrorCount());
|
delete (*it);
|
}
|
reap_check_vector.clear();
|
reap_check_status.Destroy();
|
}
|
|
// Print queuing information.
|
void Sat::QueueStats() {
|
finelock_q_->QueueAnalysis();
|
}
|
|
void Sat::AnalysisAllStats() {
|
float max_runtime_sec = 0.;
|
float total_data = 0.;
|
float total_bandwidth = 0.;
|
float thread_runtime_sec = 0.;
|
|
for (WorkerMap::const_iterator map_it = workers_map_.begin();
|
map_it != workers_map_.end(); ++map_it) {
|
for (WorkerVector::const_iterator it = map_it->second->begin();
|
it != map_it->second->end(); ++it) {
|
thread_runtime_sec = (*it)->GetRunDurationUSec()*1.0/1000000.;
|
total_data += (*it)->GetMemoryCopiedData();
|
total_data += (*it)->GetDeviceCopiedData();
|
if (thread_runtime_sec > max_runtime_sec) {
|
max_runtime_sec = thread_runtime_sec;
|
}
|
}
|
}
|
|
total_bandwidth = total_data / max_runtime_sec;
|
|
logprintf(0, "Stats: Completed: %.2fM in %.2fs %.2fMB/s, "
|
"with %d hardware incidents, %d errors\n",
|
total_data,
|
max_runtime_sec,
|
total_bandwidth,
|
errorcount_,
|
statuscount_);
|
}
|
|
void Sat::MemoryStats() {
|
float memcopy_data = 0.;
|
float memcopy_bandwidth = 0.;
|
WorkerMap::const_iterator mem_it = workers_map_.find(
|
static_cast<int>(kMemoryType));
|
WorkerMap::const_iterator file_it = workers_map_.find(
|
static_cast<int>(kFileIOType));
|
sat_assert(mem_it != workers_map_.end());
|
sat_assert(file_it != workers_map_.end());
|
for (WorkerVector::const_iterator it = mem_it->second->begin();
|
it != mem_it->second->end(); ++it) {
|
memcopy_data += (*it)->GetMemoryCopiedData();
|
memcopy_bandwidth += (*it)->GetMemoryBandwidth();
|
}
|
for (WorkerVector::const_iterator it = file_it->second->begin();
|
it != file_it->second->end(); ++it) {
|
memcopy_data += (*it)->GetMemoryCopiedData();
|
memcopy_bandwidth += (*it)->GetMemoryBandwidth();
|
}
|
GoogleMemoryStats(&memcopy_data, &memcopy_bandwidth);
|
logprintf(4, "Stats: Memory Copy: %.2fM at %.2fMB/s\n",
|
memcopy_data,
|
memcopy_bandwidth);
|
}
|
|
void Sat::GoogleMemoryStats(float *memcopy_data,
|
float *memcopy_bandwidth) {
|
// Do nothing, should be implemented by subclasses.
|
}
|
|
void Sat::FileStats() {
|
float file_data = 0.;
|
float file_bandwidth = 0.;
|
WorkerMap::const_iterator file_it = workers_map_.find(
|
static_cast<int>(kFileIOType));
|
sat_assert(file_it != workers_map_.end());
|
for (WorkerVector::const_iterator it = file_it->second->begin();
|
it != file_it->second->end(); ++it) {
|
file_data += (*it)->GetDeviceCopiedData();
|
file_bandwidth += (*it)->GetDeviceBandwidth();
|
}
|
logprintf(4, "Stats: File Copy: %.2fM at %.2fMB/s\n",
|
file_data,
|
file_bandwidth);
|
}
|
|
void Sat::CheckStats() {
|
float check_data = 0.;
|
float check_bandwidth = 0.;
|
WorkerMap::const_iterator check_it = workers_map_.find(
|
static_cast<int>(kCheckType));
|
sat_assert(check_it != workers_map_.end());
|
for (WorkerVector::const_iterator it = check_it->second->begin();
|
it != check_it->second->end(); ++it) {
|
check_data += (*it)->GetMemoryCopiedData();
|
check_bandwidth += (*it)->GetMemoryBandwidth();
|
}
|
logprintf(4, "Stats: Data Check: %.2fM at %.2fMB/s\n",
|
check_data,
|
check_bandwidth);
|
}
|
|
void Sat::NetStats() {
|
float net_data = 0.;
|
float net_bandwidth = 0.;
|
WorkerMap::const_iterator netio_it = workers_map_.find(
|
static_cast<int>(kNetIOType));
|
WorkerMap::const_iterator netslave_it = workers_map_.find(
|
static_cast<int>(kNetSlaveType));
|
sat_assert(netio_it != workers_map_.end());
|
sat_assert(netslave_it != workers_map_.end());
|
for (WorkerVector::const_iterator it = netio_it->second->begin();
|
it != netio_it->second->end(); ++it) {
|
net_data += (*it)->GetDeviceCopiedData();
|
net_bandwidth += (*it)->GetDeviceBandwidth();
|
}
|
for (WorkerVector::const_iterator it = netslave_it->second->begin();
|
it != netslave_it->second->end(); ++it) {
|
net_data += (*it)->GetDeviceCopiedData();
|
net_bandwidth += (*it)->GetDeviceBandwidth();
|
}
|
logprintf(4, "Stats: Net Copy: %.2fM at %.2fMB/s\n",
|
net_data,
|
net_bandwidth);
|
}
|
|
void Sat::InvertStats() {
|
float invert_data = 0.;
|
float invert_bandwidth = 0.;
|
WorkerMap::const_iterator invert_it = workers_map_.find(
|
static_cast<int>(kInvertType));
|
sat_assert(invert_it != workers_map_.end());
|
for (WorkerVector::const_iterator it = invert_it->second->begin();
|
it != invert_it->second->end(); ++it) {
|
invert_data += (*it)->GetMemoryCopiedData();
|
invert_bandwidth += (*it)->GetMemoryBandwidth();
|
}
|
logprintf(4, "Stats: Invert Data: %.2fM at %.2fMB/s\n",
|
invert_data,
|
invert_bandwidth);
|
}
|
|
void Sat::DiskStats() {
|
float disk_data = 0.;
|
float disk_bandwidth = 0.;
|
WorkerMap::const_iterator disk_it = workers_map_.find(
|
static_cast<int>(kDiskType));
|
WorkerMap::const_iterator random_it = workers_map_.find(
|
static_cast<int>(kRandomDiskType));
|
sat_assert(disk_it != workers_map_.end());
|
sat_assert(random_it != workers_map_.end());
|
for (WorkerVector::const_iterator it = disk_it->second->begin();
|
it != disk_it->second->end(); ++it) {
|
disk_data += (*it)->GetDeviceCopiedData();
|
disk_bandwidth += (*it)->GetDeviceBandwidth();
|
}
|
for (WorkerVector::const_iterator it = random_it->second->begin();
|
it != random_it->second->end(); ++it) {
|
disk_data += (*it)->GetDeviceCopiedData();
|
disk_bandwidth += (*it)->GetDeviceBandwidth();
|
}
|
|
logprintf(4, "Stats: Disk: %.2fM at %.2fMB/s\n",
|
disk_data,
|
disk_bandwidth);
|
}
|
|
// Process worker thread data for bandwidth information, and error results.
|
// You can add more methods here just subclassing SAT.
|
void Sat::RunAnalysis() {
|
AnalysisAllStats();
|
MemoryStats();
|
FileStats();
|
NetStats();
|
CheckStats();
|
InvertStats();
|
DiskStats();
|
}
|
|
// Get total error count, summing across all threads..
|
int64 Sat::GetTotalErrorCount() {
|
int64 errors = 0;
|
|
AcquireWorkerLock();
|
for (WorkerMap::const_iterator map_it = workers_map_.begin();
|
map_it != workers_map_.end(); ++map_it) {
|
for (WorkerVector::const_iterator it = map_it->second->begin();
|
it != map_it->second->end(); ++it) {
|
errors += (*it)->GetErrorCount();
|
}
|
}
|
ReleaseWorkerLock();
|
return errors;
|
}
|
|
|
void Sat::SpawnThreads() {
|
logprintf(12, "Log: Initializing WorkerStatus objects\n");
|
power_spike_status_.Initialize();
|
continuous_status_.Initialize();
|
logprintf(12, "Log: Spawning worker threads\n");
|
for (WorkerMap::const_iterator map_it = workers_map_.begin();
|
map_it != workers_map_.end(); ++map_it) {
|
for (WorkerVector::const_iterator it = map_it->second->begin();
|
it != map_it->second->end(); ++it) {
|
logprintf(12, "Log: Spawning thread %d\n", (*it)->ThreadID());
|
(*it)->SpawnThread();
|
}
|
}
|
}
|
|
// Delete used worker thread objects.
|
void Sat::DeleteThreads() {
|
logprintf(12, "Log: Deleting worker threads\n");
|
for (WorkerMap::const_iterator map_it = workers_map_.begin();
|
map_it != workers_map_.end(); ++map_it) {
|
for (WorkerVector::const_iterator it = map_it->second->begin();
|
it != map_it->second->end(); ++it) {
|
logprintf(12, "Log: Deleting thread %d\n", (*it)->ThreadID());
|
delete (*it);
|
}
|
delete map_it->second;
|
}
|
workers_map_.clear();
|
logprintf(12, "Log: Destroying WorkerStatus objects\n");
|
power_spike_status_.Destroy();
|
continuous_status_.Destroy();
|
}
|
|
namespace {
|
// Calculates the next time an action in Sat::Run() should occur, based on a
|
// schedule derived from a start point and a regular frequency.
|
//
|
// Using frequencies instead of intervals with their accompanying drift allows
|
// users to better predict when the actions will occur throughout a run.
|
//
|
// Arguments:
|
// frequency: seconds
|
// start: unixtime
|
// now: unixtime
|
//
|
// Returns: unixtime
|
inline time_t NextOccurance(time_t frequency, time_t start, time_t now) {
|
return start + frequency + (((now - start) / frequency) * frequency);
|
}
|
}
|
|
// Run the actual test.
|
bool Sat::Run() {
|
// Install signal handlers to gracefully exit in the middle of a run.
|
//
|
// Why go through this whole rigmarole? It's the only standards-compliant
|
// (C++ and POSIX) way to handle signals in a multithreaded program.
|
// Specifically:
|
//
|
// 1) (C++) The value of a variable not of type "volatile sig_atomic_t" is
|
// unspecified upon entering a signal handler and, if modified by the
|
// handler, is unspecified after leaving the handler.
|
//
|
// 2) (POSIX) After the value of a variable is changed in one thread, another
|
// thread is only guaranteed to see the new value after both threads have
|
// acquired or released the same mutex or rwlock, synchronized to the
|
// same barrier, or similar.
|
//
|
// #1 prevents the use of #2 in a signal handler, so the signal handler must
|
// be called in the same thread that reads the "volatile sig_atomic_t"
|
// variable it sets. We enforce that by blocking the signals in question in
|
// the worker threads, forcing them to be handled by this thread.
|
logprintf(12, "Log: Installing signal handlers\n");
|
sigset_t new_blocked_signals;
|
sigemptyset(&new_blocked_signals);
|
sigaddset(&new_blocked_signals, SIGINT);
|
sigaddset(&new_blocked_signals, SIGTERM);
|
sigset_t prev_blocked_signals;
|
pthread_sigmask(SIG_BLOCK, &new_blocked_signals, &prev_blocked_signals);
|
sighandler_t prev_sigint_handler = signal(SIGINT, SatHandleBreak);
|
sighandler_t prev_sigterm_handler = signal(SIGTERM, SatHandleBreak);
|
|
// Kick off all the worker threads.
|
logprintf(12, "Log: Launching worker threads\n");
|
InitializeThreads();
|
SpawnThreads();
|
pthread_sigmask(SIG_SETMASK, &prev_blocked_signals, NULL);
|
|
logprintf(12, "Log: Starting countdown with %d seconds\n", runtime_seconds_);
|
|
// In seconds.
|
static const time_t kSleepFrequency = 5;
|
// All of these are in seconds. You probably want them to be >=
|
// kSleepFrequency and multiples of kSleepFrequency, but neither is necessary.
|
static const time_t kInjectionFrequency = 10;
|
// print_delay_ determines "seconds remaining" chatty update.
|
|
const time_t start = time(NULL);
|
const time_t end = start + runtime_seconds_;
|
time_t now = start;
|
time_t next_print = start + print_delay_;
|
time_t next_pause = start + pause_delay_;
|
time_t next_resume = 0;
|
time_t next_injection;
|
if (crazy_error_injection_) {
|
next_injection = start + kInjectionFrequency;
|
} else {
|
next_injection = 0;
|
}
|
|
while (now < end) {
|
// This is an int because it's for logprintf().
|
const int seconds_remaining = end - now;
|
|
if (user_break_) {
|
// Handle early exit.
|
logprintf(0, "Log: User exiting early (%d seconds remaining)\n",
|
seconds_remaining);
|
break;
|
}
|
|
// If we have an error limit, check it here and see if we should exit.
|
if (max_errorcount_ != 0) {
|
uint64 errors = GetTotalErrorCount();
|
if (errors > max_errorcount_) {
|
logprintf(0, "Log: Exiting early (%d seconds remaining) "
|
"due to excessive failures (%lld)\n",
|
seconds_remaining,
|
errors);
|
break;
|
}
|
}
|
|
if (now >= next_print) {
|
// Print a count down message.
|
logprintf(5, "Log: Seconds remaining: %d\n", seconds_remaining);
|
next_print = NextOccurance(print_delay_, start, now);
|
}
|
|
if (next_injection && now >= next_injection) {
|
// Inject an error.
|
logprintf(4, "Log: Injecting error (%d seconds remaining)\n",
|
seconds_remaining);
|
struct page_entry src;
|
GetValid(&src);
|
src.pattern = patternlist_->GetPattern(0);
|
PutValid(&src);
|
next_injection = NextOccurance(kInjectionFrequency, start, now);
|
}
|
|
if (next_pause && now >= next_pause) {
|
// Tell worker threads to pause in preparation for a power spike.
|
logprintf(4, "Log: Pausing worker threads in preparation for power spike "
|
"(%d seconds remaining)\n", seconds_remaining);
|
power_spike_status_.PauseWorkers();
|
logprintf(12, "Log: Worker threads paused\n");
|
next_pause = 0;
|
next_resume = now + pause_duration_;
|
}
|
|
if (next_resume && now >= next_resume) {
|
// Tell worker threads to resume in order to cause a power spike.
|
logprintf(4, "Log: Resuming worker threads to cause a power spike (%d "
|
"seconds remaining)\n", seconds_remaining);
|
power_spike_status_.ResumeWorkers();
|
logprintf(12, "Log: Worker threads resumed\n");
|
next_pause = NextOccurance(pause_delay_, start, now);
|
next_resume = 0;
|
}
|
|
sat_sleep(NextOccurance(kSleepFrequency, start, now) - now);
|
now = time(NULL);
|
}
|
|
JoinThreads();
|
|
logprintf(0, "Stats: Found %lld hardware incidents\n", errorcount_);
|
|
if (!monitor_mode_)
|
RunAnalysis();
|
|
DeleteThreads();
|
|
logprintf(12, "Log: Uninstalling signal handlers\n");
|
signal(SIGINT, prev_sigint_handler);
|
signal(SIGTERM, prev_sigterm_handler);
|
|
return true;
|
}
|
|
// Clean up all resources.
|
bool Sat::Cleanup() {
|
g_sat = NULL;
|
Logger::GlobalLogger()->StopThread();
|
Logger::GlobalLogger()->SetStdoutOnly();
|
if (logfile_) {
|
close(logfile_);
|
logfile_ = 0;
|
}
|
if (patternlist_) {
|
patternlist_->Destroy();
|
delete patternlist_;
|
patternlist_ = 0;
|
}
|
if (os_) {
|
os_->FreeTestMem();
|
delete os_;
|
os_ = 0;
|
}
|
if (empty_) {
|
delete empty_;
|
empty_ = 0;
|
}
|
if (valid_) {
|
delete valid_;
|
valid_ = 0;
|
}
|
if (finelock_q_) {
|
delete finelock_q_;
|
finelock_q_ = 0;
|
}
|
if (page_bitmap_) {
|
delete[] page_bitmap_;
|
}
|
|
for (size_t i = 0; i < blocktables_.size(); i++) {
|
delete blocktables_[i];
|
}
|
|
if (cc_cacheline_data_) {
|
// The num integer arrays for all the cacheline structures are
|
// allocated as a single chunk. The pointers in the cacheline struct
|
// are populated accordingly. Hence calling free on the first
|
// cacheline's num's address is going to free the entire array.
|
// TODO(aganti): Refactor this to have a class for the cacheline
|
// structure (currently defined in worker.h) and clean this up
|
// in the destructor of that class.
|
if (cc_cacheline_data_[0].num) {
|
free(cc_cacheline_data_[0].num);
|
}
|
free(cc_cacheline_data_);
|
}
|
|
sat_assert(0 == pthread_mutex_destroy(&worker_lock_));
|
|
return true;
|
}
|
|
|
// Pretty print really obvious results.
|
bool Sat::PrintResults() {
|
bool result = true;
|
|
logprintf(4, "\n");
|
if (statuscount_) {
|
logprintf(4, "Status: FAIL - test encountered procedural errors\n");
|
result = false;
|
} else if (errorcount_) {
|
logprintf(4, "Status: FAIL - test discovered HW problems\n");
|
result = false;
|
} else {
|
logprintf(4, "Status: PASS - please verify no corrected errors\n");
|
}
|
logprintf(4, "\n");
|
|
return result;
|
}
|
|
// Helper functions.
|
void Sat::AcquireWorkerLock() {
|
sat_assert(0 == pthread_mutex_lock(&worker_lock_));
|
}
|
void Sat::ReleaseWorkerLock() {
|
sat_assert(0 == pthread_mutex_unlock(&worker_lock_));
|
}
|
|
void logprintf(int priority, const char *format, ...) {
|
va_list args;
|
va_start(args, format);
|
Logger::GlobalLogger()->VLogF(priority, format, args);
|
va_end(args);
|
}
|
|
// Stop the logging thread and verify any pending data is written to the log.
|
void logstop() {
|
Logger::GlobalLogger()->StopThread();
|
}
|