-
Hahn, Steven authoredHahn, Steven authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
DiskBufferTest.h 29.67 KiB
#ifndef MANTID_KERNEL_DISKBUFFERTEST_H_
#define MANTID_KERNEL_DISKBUFFERTEST_H_
#include "MantidKernel/DiskBuffer.h"
#include "MantidKernel/FreeBlock.h"
#include "MantidKernel/ISaveable.h"
#include "MantidKernel/CPUTimer.h"
#include "MantidKernel/MultiThreaded.h"
#include "MantidKernel/System.h"
#include "MantidKernel/Timer.h"
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <cxxtest/TestSuite.h>
using namespace Mantid;
using namespace Mantid::Kernel;
using Mantid::Kernel::CPUTimer;
//====================================================================================
/** An ISaveable that fakes writing to a fixed-size file */
class SaveableTesterWithFile : public ISaveable {
public:
SaveableTesterWithFile(uint64_t pos, uint64_t size, char ch,
bool wasSaved = true)
: ISaveable(), m_memory(size), m_ch(ch) {
// the object knows its place on file
this->setFilePosition(pos, size, wasSaved);
this->setLoaded(true);
}
// this is testing/special routine
void setSaved(bool On = true) { this->m_wasSaved = On; }
~SaveableTesterWithFile() override {}
void clearDataFromMemory() override {
this->setLoaded(false);
m_memory = 0;
}
uint64_t m_memory;
uint64_t getTotalDataSize() const override {
if (this->wasSaved()) {
if (this->isLoaded())
return m_memory;
else
return m_memory + this->getFileSize();
} else
return m_memory;
};
size_t getDataMemorySize() const override { return size_t(m_memory); }
void AddNewObjects(uint64_t nNewObj) {
if (this->wasSaved()) {
if (this->isLoaded()) {
m_memory += nNewObj;
} else {
m_memory = nNewObj;
}
} else
m_memory += nNewObj;
}
char m_ch;
void save() const override {
// Fake writing to a file
streamMutex.lock();
uint64_t mPos = this->getFilePosition();
uint64_t mMem = this->getTotalDataSize();
if (fakeFile.size() < mPos + mMem)
fakeFile.resize(mPos + mMem, ' ');
for (size_t i = mPos; i < mPos + mMem; i++)
fakeFile[i] = m_ch;
streamMutex.unlock();
// this is important function call which has to be implemented by any save
// function
this->m_wasSaved = true;
}
void load() override {
if (this->wasSaved() && !this->isLoaded()) {
m_memory += this->getFileSize();
}
// this is important function call which has to be implemented by any load
// function
this->setLoaded(true);
}
void flushData() const override {}
static std::string fakeFile;
static std::mutex streamMutex;
};
// Declare the static members here.
std::string SaveableTesterWithFile::fakeFile;
std::mutex SaveableTesterWithFile::streamMutex;
//====================================================================================
class DiskBufferTest : public CxxTest::TestSuite {
public:
std::vector<SaveableTesterWithFile *> data;
size_t num;
void setUp() override {
// Create the ISaveables
num = 10;
SaveableTesterWithFile::fakeFile = "";
data.clear();
for (size_t i = 0; i < num; i++)
data.push_back(
new SaveableTesterWithFile(uint64_t(2 * i), 2, char(i + 0x41)));
}
void tearDown() override {
for (size_t i = 0; i < data.size(); i++) {
delete data[i];
data[i] = NULL;
}
}
void xest_nothing() {
TS_WARN("Tests here were disabled for the time being");
}
/** Extreme case with nothing writable but exceeding the writable buffer */
void test_noWriteBuffer_nothingWritable() {
// Room for 4 in the write buffer
DiskBuffer dbuf(4);
for (size_t i = 0; i < 9; i++) {
data[i]->setBusy(true);
dbuf.toWrite(data[i]);
}
// We ended up with too much in the buffer since nothing could be written.
TS_ASSERT_EQUALS(dbuf.getWriteBufferUsed(), 2 * 9);
// Let's make it all writable
for (size_t i = 0; i < 9; i++) {
data[i]->setBusy(false);
data[i]->setDataChanged();
}
// Trigger a write
data[9]->setDataChanged();
dbuf.toWrite(data[9]);
TS_ASSERT_EQUALS(dbuf.getWriteBufferUsed(), 0);
// And all of these get written out at once
TS_ASSERT_EQUALS(SaveableTesterWithFile::fakeFile, "AABBCCDDEEFFGGHHIIJJ");
}
/** Extreme case with nothing writable but exceeding the writable buffer */
void test_noWriteBuffer_nothingWritableWasSaved() {
// Room for 4 in the write buffer
DiskBuffer dbuf(4);
for (size_t i = 0; i < 10; i++) {
data[i]->setBusy(true);
data[i]->setSaved(false);
dbuf.toWrite(data[i]);
}
// We ended up with too much in the buffer since nothing could be written.
TS_ASSERT_EQUALS(dbuf.getWriteBufferUsed(), 20);
// Let's make it all writable
for (size_t i = 0; i < 9; i++)
data[i]->setBusy(false);
// Trigger a write
dbuf.toWrite(data[9]);
TS_ASSERT_EQUALS(dbuf.getWriteBufferUsed(), 2);
// And all of these get written out at once
TS_ASSERT_EQUALS(SaveableTesterWithFile::fakeFile, "IIHHGGFFEEDDCCBBAA");
}
////--------------------------------------------------------------------------------
///** Sorts by file position when writing to a file */
void test_writesOutInFileOrder() {
for (size_t i = 0; i < data.size(); i++) {
data[i]->setDataChanged();
}
// Room for 2 objects of size 2 in the to-write cache
DiskBuffer dbuf(2 * 2);
// These 3 will get written out
dbuf.toWrite(data[5]);
dbuf.toWrite(data[1]);
dbuf.toWrite(data[9]);
TS_ASSERT_EQUALS(dbuf.getWriteBufferUsed(), 0);
// 0 1 2 3 4 5 6 7 8 9
TS_ASSERT_EQUALS(SaveableTesterWithFile::fakeFile, " BB FF JJ");
// These 4 at the end will be in the cache
dbuf.toWrite(data[2]);
dbuf.toWrite(data[3]);
dbuf.toWrite(data[4]);
dbuf.toWrite(data[6]);
// 1 left in the buffer
TS_ASSERT_EQUALS(dbuf.getWriteBufferUsed(), 2);
// The "file" was written out this way (sorted by file position):
// 0 1 2 3 4 5 6 7 8 9
TS_ASSERT_EQUALS(SaveableTesterWithFile::fakeFile, " BBCCDDEEFF JJ");
}
//--------------------------------------------------------------------------------
/** If a block will get deleted it needs to be taken
* out of the caches */
void test_objectDeleted() {
// Room for 6 objects of 2 in the to-write cache
DiskBuffer dbuf(12);
// Fill the buffer
for (size_t i = 0; i < 5; i++) {
dbuf.toWrite(data[i]);
data[i]->setDataChanged();
}
TS_ASSERT_EQUALS(dbuf.getWriteBufferUsed(), 10);
// First let's get rid of something in to to-write buffer
dbuf.objectDeleted(data[1]);
TS_ASSERT_EQUALS(dbuf.getWriteBufferUsed(), 8);
TSM_ASSERT_EQUALS(
"The data marked as been saved, so delete should free this",
dbuf.getFreeSpaceMap().size(), 1);
dbuf.flushCache();
// This triggers a write. 1 is no longer in the to-write buffer
// "0, 2,3,4,"
TS_ASSERT_EQUALS(SaveableTesterWithFile::fakeFile, "AA CCDDEE");
TS_ASSERT_EQUALS(dbuf.getWriteBufferUsed(), 0);
// assume now we have loaded the data back; (THIS MAY BE WRONG THOURH NOT
// AFFECT FURTHER TESTS)
size_t ic(0);
for (size_t i = 0; i < 5; i++) {
if (i == 1)
continue;
data[i]->setFilePosition(2 * ic, 2, true);
data[i]->m_memory = 2;
data[i]->setDataChanged();
dbuf.toWrite(data[i]);
ic++;
}
dbuf.objectDeleted(data[2]);
TS_ASSERT_EQUALS(dbuf.getWriteBufferUsed(), 6);
TSM_ASSERT_EQUALS("It is still free space mapping the data on hdd",
dbuf.getFreeSpaceMap().size(), 2);
TSM_ASSERT_EQUALS(" and file is still the same size: ",
dbuf.getFileLength(), 10);
}
//--------------------------------------------------------------------------------
/** Accessing the map from multiple threads simultaneously does not segfault
*/
void test_thread_safety() {
// Room for 3 in the to-write cache
DiskBuffer dbuf(3);
size_t bigNum = 1000;
std::vector<ISaveable *> bigData;
bigData.reserve(bigNum);
for (size_t i = 0; i < bigNum; i++)
bigData.push_back(new SaveableTesterWithFile(2 * i, 2, char(i + 0x41)));
PARALLEL_FOR_NO_WSP_CHECK()
for (int i = 0; i < int(bigNum); i++) {
dbuf.toWrite(bigData[i]);
}
// std::cout << ISaveableTester::fakeFile << '\n';
for (size_t i = 0; i < size_t(bigNum); i++)
delete bigData[i];
}
////--------------------------------------------------------------------------------
////--------------------------------------------------------------------------------
////----------TESTS FOR FREE SPACE MAPS
///--------------------------------------------
////--------------------------------------------------------------------------------
////--------------------------------------------------------------------------------
/** Freeing blocks get merged properly */
void test_freeBlock_mergesWithPrevious() {
DiskBuffer dbuf(3);
DiskBuffer::freeSpace_t &map = dbuf.getFreeSpaceMap();
FreeBlock b;
TS_ASSERT_EQUALS(map.size(), 0);
dbuf.freeBlock(0, 50);
TS_ASSERT_EQUALS(map.size(), 1);
// zero-sized free block does nothing
dbuf.freeBlock(1234, 0);
TS_ASSERT_EQUALS(map.size(), 1);
dbuf.freeBlock(100, 50);
TS_ASSERT_EQUALS(map.size(), 2);
// Free a block next to another one, AFTER
dbuf.freeBlock(150, 50);
TSM_ASSERT_EQUALS(
"Map remained the same size because adjacent blocks were merged",
map.size(), 2);
// Get a vector of the free blocks and sizes
std::vector<uint64_t> free;
dbuf.getFreeSpaceVector(free);
TS_ASSERT_EQUALS(free[0], 0);
TS_ASSERT_EQUALS(free[1], 50);
TS_ASSERT_EQUALS(free[2], 100);
TS_ASSERT_EQUALS(free[3], 100);
}
/** Freeing blocks get merged properly */
void test_freeBlock_mergesWithNext() {
DiskBuffer dbuf(3);
DiskBuffer::freeSpace_t &map = dbuf.getFreeSpaceMap();
FreeBlock b;
dbuf.freeBlock(0, 50);
dbuf.freeBlock(200, 50);
TS_ASSERT_EQUALS(map.size(), 2);
// Free a block next to another one, BEFORE
dbuf.freeBlock(150, 50);
TSM_ASSERT_EQUALS(
"Map remained the same size because adjacent blocks were merged",
map.size(), 2);
// Get the 2nd free block.
DiskBuffer::freeSpace_t::iterator it = map.begin();
it++;
b = *it;
TS_ASSERT_EQUALS(b.getFilePosition(), 150);
TS_ASSERT_EQUALS(b.getSize(), 100);
dbuf.freeBlock(50, 50);
TSM_ASSERT_EQUALS(
"Map remained the same size because adjacent blocks were merged",
map.size(), 2);
TS_ASSERT_EQUALS(map.begin()->getSize(), 100);
}
/** Freeing blocks get merged properly */
void test_freeBlock_mergesWithBothNeighbours() {
DiskBuffer dbuf(3);
DiskBuffer::freeSpace_t &map = dbuf.getFreeSpaceMap();
FreeBlock b;
dbuf.freeBlock(0, 50);
dbuf.freeBlock(200, 50);
dbuf.freeBlock(300, 50);
dbuf.freeBlock(400, 50); // Disconnected 4th one
TS_ASSERT_EQUALS(map.size(), 4);
// Free a block between two block
dbuf.freeBlock(250, 50);
TSM_ASSERT_EQUALS("Map shrank because three blocks were merged", map.size(),
3);
// Get the 2nd free block.
DiskBuffer::freeSpace_t::iterator it = map.begin();
it++;
b = *it;
TS_ASSERT_EQUALS(b.getFilePosition(), 200);
TS_ASSERT_EQUALS(b.getSize(), 150);
}
/** Add blocks to the free block list in parallel threads,
* should not segfault or anything */
void test_freeBlock_threadSafety() {
DiskBuffer dbuf(0);
PRAGMA_OMP( parallel for)
for (int i = 0; i < 10000; i++) {
dbuf.freeBlock(uint64_t(i) * 100, (i % 3 == 0) ? 100 : 50);
}
// 1/3 of the blocks got merged
TS_ASSERT_EQUALS(dbuf.getFreeSpaceMap().size(), 6667);
}
///** Disabled because it is not necessary to defrag since that happens on the
/// fly */
// void xtest_defragFreeBlocks()
//{
// DiskBuffer dbuf(3);
// DiskBuffer::freeSpace_t & map = dbuf.getFreeSpaceMap();
// FreeBlock b;
// dbuf.freeBlock(0, 50);
// dbuf.freeBlock(100, 50);
// dbuf.freeBlock(150, 50);
// dbuf.freeBlock(500, 50);
// dbuf.freeBlock(550, 50);
// dbuf.freeBlock(600, 50);
// dbuf.freeBlock(650, 50);
// dbuf.freeBlock(1000, 50);
// TS_ASSERT_EQUALS( map.size(), 8);
// dbuf.defragFreeBlocks();
// TS_ASSERT_EQUALS( map.size(), 4);
//}
/// You can call relocate() if an block is shrinking.
void test_relocate_when_shrinking() {
DiskBuffer dbuf(3);
DiskBuffer::freeSpace_t &map = dbuf.getFreeSpaceMap();
// You stay in the same place because that's the only free spot.
TS_ASSERT_EQUALS(dbuf.relocate(100, 10, 5), 100);
// You left a free block at 105.
TS_ASSERT_EQUALS(map.size(), 1);
// This one, instead of staying in place, will fill in that previously freed
// 5-sized block
// since that's the smallest one that fits the whole block.
TS_ASSERT_EQUALS(dbuf.relocate(200, 10, 5), 105);
// Still one free block, but its at 200-209 now.
TS_ASSERT_EQUALS(map.size(), 1);
}
/// You can call relocate() if an block is shrinking.
void test_relocate_when_growing() {
DiskBuffer dbuf(3);
DiskBuffer::freeSpace_t &map = dbuf.getFreeSpaceMap();
dbuf.freeBlock(200, 20);
dbuf.freeBlock(300, 30);
TS_ASSERT_EQUALS(map.size(), 2);
// Grab the smallest block that's big enough
TS_ASSERT_EQUALS(dbuf.relocate(100, 10, 20), 200);
// You left a free block at 100 of size 10 to replace that one.
TS_ASSERT_EQUALS(map.size(), 2);
// A zero-sized block is "relocated" by basically allocating it to the free
// spot
TS_ASSERT_EQUALS(dbuf.relocate(100, 0, 5), 100);
TS_ASSERT_EQUALS(map.size(), 2);
}
/// Various tests of allocating and relocating
void test_allocate_from_empty_freeMap() {
DiskBuffer dbuf(3);
dbuf.setFileLength(1000); // Lets say the file goes up to 1000
DiskBuffer::freeSpace_t &map = dbuf.getFreeSpaceMap();
FreeBlock b;
TS_ASSERT_EQUALS(map.size(), 0);
// No free blocks? End up at the end
TS_ASSERT_EQUALS(dbuf.allocate(20), 1000);
TS_ASSERT_EQUALS(dbuf.getFileLength(), 1020);
for (size_t i = 0; i < 100000; i++)
dbuf.allocate(20);
DiskBuffer mru2;
mru2.setFileLength(1000);
for (size_t i = 0; i < 100000; i++)
mru2.allocate(20);
}
/// Various tests of allocating and relocating
void test_allocate_and_relocate() {
DiskBuffer dbuf(3);
dbuf.setFileLength(1000); // Lets say the file goes up to 1000
DiskBuffer::freeSpace_t &map = dbuf.getFreeSpaceMap();
FreeBlock b;
dbuf.freeBlock(100, 10);
dbuf.freeBlock(200, 20);
dbuf.freeBlock(300, 30);
dbuf.freeBlock(400, 40);
TS_ASSERT_EQUALS(map.size(), 4);
// Where does the block end up?
TS_ASSERT_EQUALS(dbuf.allocate(20), 200);
// The map has shrunk by one since the new one was removed.
TS_ASSERT_EQUALS(map.size(), 3);
// OK, now look for a smaller block, size of 4
TS_ASSERT_EQUALS(dbuf.allocate(4), 100);
// This left a little chunk of space free, sized 6 at position 104. So the #
// of entries in the free space map did not change
TS_ASSERT_EQUALS(map.size(), 3);
TS_ASSERT_EQUALS(map.begin()->getFilePosition(), 104);
TS_ASSERT_EQUALS(map.begin()->getSize(), 6);
// Now try to relocate. Had a block after a 30-sized free block at 300.
// It gets freed, opening up a slot for the new chunk of memory
TS_ASSERT_EQUALS(dbuf.relocate(330, 5, 35), 300);
// One fewer free block.
TS_ASSERT_EQUALS(map.size(), 2);
// Ok, now lets ask for a block that is too big. It puts us at the end of
// the file
TS_ASSERT_EQUALS(dbuf.allocate(55), 1000);
TS_ASSERT_EQUALS(dbuf.getFileLength(), 1055);
}
//// Test for setting the DiskBuffer
void test_set_free_space_blocks() {
DiskBuffer dbuf(0);
DiskBuffer::freeSpace_t &map = dbuf.getFreeSpaceMap();
uint64_t freeSpaceBlocksArray[] = {1, 3, 6, 5};
std::vector<uint64_t> freeSpaceBlocksVector(
freeSpaceBlocksArray,
freeSpaceBlocksArray + sizeof(freeSpaceBlocksArray) / sizeof(uint64_t));
dbuf.setFreeSpaceVector(freeSpaceBlocksVector);
TS_ASSERT_EQUALS(map.size(), 2);
std::vector<uint64_t> assignedVector;
dbuf.getFreeSpaceVector(assignedVector);
TS_ASSERT_EQUALS(assignedVector, freeSpaceBlocksVector);
}
//// Test for setting the DiskBuffer with an invalid vector
void test_set_free_space_blocks_with_odd_sized_vector_throws_exception() {
DiskBuffer dbuf(0);
uint64_t freeSpaceBlocksArray[] = {1, 3, 6};
std::vector<uint64_t> freeSpaceBlocksVector(
freeSpaceBlocksArray,
freeSpaceBlocksArray + sizeof(freeSpaceBlocksArray) / sizeof(uint64_t));
TS_ASSERT_THROWS(dbuf.setFreeSpaceVector(freeSpaceBlocksVector),
std::length_error);
}
//// Test for setting the DiskBuffer with a zero sized vector
void test_set_free_space_blocks_with_zero_sized_vector() {
DiskBuffer dbuf(0);
DiskBuffer::freeSpace_t &map = dbuf.getFreeSpaceMap();
std::vector<uint64_t> freeSpaceBlocksVector;
dbuf.setFreeSpaceVector(freeSpaceBlocksVector);
TS_ASSERT_EQUALS(map.size(), 0);
}
////--------------------------------------------------------------------------------
////--------------------------------------------------------------------------------
////--------------------------------------------------------------------------------
////--------------------------------------------------------------------------------
void test_allocate_with_file_manually() {
// Start by faking a file
SaveableTesterWithFile *blockA = new SaveableTesterWithFile(0, 2, 'A');
SaveableTesterWithFile *blockB = new SaveableTesterWithFile(2, 3, 'B');
SaveableTesterWithFile *blockC = new SaveableTesterWithFile(5, 5, 'C');
blockA->save();
blockB->save();
blockC->save();
TS_ASSERT_EQUALS(SaveableTesterWithFile::fakeFile, "AABBBCCCCC");
DiskBuffer dbuf(3);
dbuf.setFileLength(10);
DiskBuffer::freeSpace_t &map = dbuf.getFreeSpaceMap();
uint64_t newPos;
// File lengths are known correctly
TS_ASSERT_EQUALS(dbuf.getFileLength(), 10);
// Asking for a new chunk of space that needs to be at the end
// This all now happens inside the writeBuffer
uint64_t oldMem = blockB->getTotalDataSize();
blockB->AddNewObjects(4);
uint64_t mPos = blockB->getFilePosition();
uint64_t newMem = blockB->getTotalDataSize();
newPos = dbuf.relocate(mPos, oldMem, newMem);
TSM_ASSERT_EQUALS("One freed block", map.size(), 1);
TS_ASSERT_EQUALS(dbuf.getFileLength(), 17);
// Simulate saving
blockB->setFilePosition(newPos, 7, true);
blockB->save();
TS_ASSERT_EQUALS(SaveableTesterWithFile::fakeFile, "AABBBCCCCCBBBBBBB");
// Now let's allocate a new block
newPos = dbuf.allocate(2);
TS_ASSERT_EQUALS(newPos, 2);
SaveableTesterWithFile *blockD = new SaveableTesterWithFile(newPos, 2, 'D');
blockD->save();
TS_ASSERT_EQUALS(SaveableTesterWithFile::fakeFile, "AADDBCCCCCBBBBBBB");
TSM_ASSERT_EQUALS("Still one freed block", map.size(), 1);
// Grow blockD by 1
blockD->AddNewObjects(1);
newPos = dbuf.relocate(2, 2, 3);
TSM_ASSERT_EQUALS(
"Block D stayed in the same place since there was room after it",
newPos, 2);
blockD->setFilePosition(newPos, 3, true);
blockD->save();
dbuf.flushCache();
TS_ASSERT_EQUALS(SaveableTesterWithFile::fakeFile, "AADDDCCCCCBBBBBBB");
// Allocate a little block at the end
newPos = dbuf.allocate(1);
TSM_ASSERT_EQUALS("The new block went to the end of the file", newPos, 17);
// Which is now longer by 1
TS_ASSERT_EQUALS(dbuf.getFileLength(), 18);
delete blockA;
delete blockB;
delete blockC;
delete blockD;
// std::cout << SaveableTesterWithFile::fakeFile << "!\n";
}
void test_allocate_with_file() {
SaveableTesterWithFile::fakeFile = "";
// filePosition has to be identified by the fileBuffer
uint64_t filePos = std::numeric_limits<uint64_t>::max();
// Start by faking a file
SaveableTesterWithFile *blockA =
new SaveableTesterWithFile(filePos, 2, 'A', false);
SaveableTesterWithFile *blockB =
new SaveableTesterWithFile(filePos, 3, 'B', false);
SaveableTesterWithFile *blockC =
new SaveableTesterWithFile(filePos, 5, 'C', false);
DiskBuffer dbuf(3);
dbuf.toWrite(blockB);
dbuf.toWrite(blockA);
dbuf.toWrite(blockC);
TS_ASSERT_EQUALS(SaveableTesterWithFile::fakeFile, "AABBBCCCCC");
DiskBuffer::freeSpace_t &map = dbuf.getFreeSpaceMap();
// Asking for a new chunk of space that needs to be at the end
blockB->AddNewObjects(4);
dbuf.toWrite(blockB);
TSM_ASSERT_EQUALS("One freed block", map.size(), 1);
TS_ASSERT_EQUALS(dbuf.getFileLength(), 17);
TS_ASSERT_EQUALS(SaveableTesterWithFile::fakeFile, "AABBBCCCCCBBBBBBB");
// Simulate saving
dbuf.toWrite(blockB);
TS_ASSERT_EQUALS(SaveableTesterWithFile::fakeFile, "AABBBCCCCCBBBBBBB");
TS_ASSERT(!blockB->isDataChanged())
//// Now let's allocate a new block
SaveableTesterWithFile *blockD =
new SaveableTesterWithFile(filePos, 2, 'D', false);
dbuf.toWrite(blockD);
// small block, nothing still sitting in the buffer
TS_ASSERT_EQUALS(SaveableTesterWithFile::fakeFile, "AABBBCCCCCBBBBBBB");
// this will remove block from the cash and place the file to sutable
// position
dbuf.flushCache();
TS_ASSERT_EQUALS(SaveableTesterWithFile::fakeFile, "AADDBCCCCCBBBBBBB");
TSM_ASSERT_EQUALS("Still one freed block", map.size(), 1);
//// Grow blockD by 1
blockD->AddNewObjects(1);
dbuf.toWrite(blockD);
// nothing happens with file
TS_ASSERT_EQUALS(SaveableTesterWithFile::fakeFile, "AADDBCCCCCBBBBBBB");
// trigger save as object will stay in buffer otherwise (only 1 block is im
// memory)
dbuf.flushCache();
TS_ASSERT_EQUALS(SaveableTesterWithFile::fakeFile, "AADDDCCCCCBBBBBBB");
TSM_ASSERT_EQUALS("Nothing left one freed block", map.size(), 0);
//// Allocate a little block at the end
blockD->AddNewObjects(1);
dbuf.toWrite(blockD);
// nothing have changed as only 1 part of the object is in the memory and 3
// are already on HDD
TS_ASSERT_EQUALS(SaveableTesterWithFile::fakeFile, "AADDDCCCCCBBBBBBB");
TSM_ASSERT_EQUALS("Nothing left one freed block", map.size(), 0);
// trigger save as object will stay in buffer otherwise (only 1 block is im
// memory)
dbuf.flushCache();
TSM_ASSERT_EQUALS("The new block went to the end of the file",
SaveableTesterWithFile::fakeFile,
"AADDDCCCCCBBBBBBBDDDD");
TS_ASSERT_EQUALS(dbuf.getFileLength(), 21);
TSM_ASSERT_EQUALS("Nothing left one freed block", map.size(), 1);
delete blockA;
delete blockB;
delete blockC;
delete blockD;
// std::cout << ISaveableTesterWithFile::fakeFile << "!\n";
}
};
//====================================================================================
// THIS TEST DOES NOT PROBABLY EXIST IN A WHILD ANY MORE; LEFT JUST IN CASE
//====================================================================================
//====================================================================================
/** An Saveable that will fake seeking to disk */
class SaveableTesterWithSeek : public ISaveable {
size_t m_memory;
public:
SaveableTesterWithSeek(size_t id) : ISaveable() {
m_memory = 1;
this->setFilePosition(10 + id, this->m_memory, true);
}
/// Method to flush the data to disk and ensure it is written.
void flushData() const override{};
/** @return the amount of memory that the object takes as a whole.
For filebased objects it should be the amount the object occupies in
memory plus the size it occupies in file if the object has not been fully
loaded
or modified.
* If the object has never been loaded, this should be equal to number of
data points in the file
*/
uint64_t getTotalDataSize() const override { return m_memory; }
/// the data size kept in memory
size_t getDataMemorySize() const override { return m_memory; };
virtual void load(DiskBuffer & /*dbuf*/) {
uint64_t myFilePos = this->getFilePosition();
// std::cout << "Block " << getFileId() << " loading at " << myFilePos <<
// '\n';
SaveableTesterWithSeek::fakeSeekAndWrite(myFilePos);
this->setLoaded(true);
}
void save() const override {
// Pretend to seek to the point and write
uint64_t myFilePos = this->getFilePosition();
// std::cout << "Block " << getFileId() << " saving at " << myFilePos <<
// '\n';
fakeSeekAndWrite(myFilePos);
}
void clearDataFromMemory() override {
m_memory = 0;
this->setLoaded(false);
}
void grow(DiskBuffer &dbuf, bool /*tellMRU*/) {
// OK first you seek to where the OLD data was and load it.
uint64_t myFilePos = this->getFilePosition();
// std::cout << "Block " << getFileId() << " loading at " << myFilePos <<
// '\n';
SaveableTesterWithSeek::fakeSeekAndWrite(myFilePos);
// Simulate that the data is growing and so needs to be written out
size_t newfilePos = dbuf.relocate(myFilePos, m_memory, m_memory + 1);
// std::cout << "Block " << getFileId() << " has moved from " << myFilePos
// << " to " << newfilePos << '\n';
myFilePos = newfilePos;
// Grow the size by 1
m_memory++;
this->setFilePosition(myFilePos, m_memory, true);
}
/// Fake a seek followed by a write
static void fakeSeekAndWrite(uint64_t newPos) {
std::lock_guard<std::mutex> lock(streamMutex);
int64_t seek = int64_t(filePos) - int64_t(newPos);
if (seek < 0)
seek = -seek;
double seekTime =
5e-3 * double(seek) / 2000.0; // 5 msec for a 2000-unit seek.
// A short write time (500 microsec) for a small block of data
seekTime += 0.5e-3;
Timer tim;
while (tim.elapsed_no_reset() < seekTime) { /*Wait*/
}
filePos = newPos;
}
void load() override {
if (this->wasSaved() && !this->isLoaded()) {
m_memory += this->getFileSize();
}
this->setLoaded(true);
}
static uint64_t filePos;
static std::string fakeFile;
static std::mutex streamMutex;
};
uint64_t SaveableTesterWithSeek::filePos;
// Declare the static members here.
std::string SaveableTesterWithSeek::fakeFile;
std::mutex SaveableTesterWithSeek::streamMutex;
//====================================================================================
class DiskBufferTestPerformance : public CxxTest::TestSuite {
public:
std::vector<SaveableTesterWithSeek *> dataSeek;
size_t num;
// This pair of boilerplate methods prevent the suite being created statically
// This means the constructor isn't called when running other xests
static DiskBufferTestPerformance *createSuite() {
return new DiskBufferTestPerformance();
}
static void destroySuite(DiskBufferTestPerformance *suite) { delete suite; }
DiskBufferTestPerformance() {
dataSeek.clear();
dataSeek.reserve(200);
for (size_t i = 0; i < 200; i++)
dataSeek.push_back(new SaveableTesterWithSeek(i));
}
void setUp() override { SaveableTesterWithSeek::fakeFile = ""; }
void xest_nothing() {
TS_WARN("Tests here were disabled for the time being");
}
/** Demonstrate that using a write buffer reduces time spent seeking on disk
*/
void test_withFakeSeeking_withWriteBuffer() {
CPUTimer tim;
DiskBuffer dbuf(10);
for (int i = 0; i < int(dataSeek.size()); i++) {
// Pretend you just loaded the data
dataSeek[i]->load(dbuf);
}
std::cout << tim << " to load " << dataSeek.size()
<< " into MRU with fake seeking. \n";
}
/** Use a 0-sized write buffer so that it constantly needs to seek and write
* out. This should be slower due to seeking. */
void test_withFakeSeeking_noWriteBuffer() {
CPUTimer tim;
DiskBuffer dbuf(0);
for (int i = 0; i < int(dataSeek.size()); i++) {
// Pretend you just loaded the data
dataSeek[i]->load(dbuf);
}
std::cout << tim << " to load " << dataSeek.size()
<< " into MRU with fake seeking. \n";
}
/** Example of a situation where vectors grew, meaning that they need to be
* relocated causing lots of seeking if no write buffer exists.*/
void test_withFakeSeeking_growingData() {
CPUTimer tim;
DiskBuffer dbuf(20);
dbuf.setFileLength(dataSeek.size());
for (int i = 0; i < int(dataSeek.size()); i++) {
// Pretend you just loaded the data
dataSeek[i]->grow(dbuf, true);
dbuf.toWrite(dataSeek[i]);
}
std::cout << "About to flush the cache to finish writes.\n";
dbuf.flushCache();
std::cout << tim << " to grow " << dataSeek.size()
<< " into MRU with fake seeking. \n";
}
/** Demonstrate that calling "save" manually without using the MRU write
* buffer will slow things down
* due to seeking. Was an issue in LoadMD */
void test_withFakeSeeking_growingData_savingWithoutUsingMRU() {
CPUTimer tim;
DiskBuffer dbuf(dataSeek.size());
for (int i = 0; i < int(dataSeek.size()); i++) {
// Pretend you just loaded the data
dataSeek[i]->grow(dbuf, false);
dataSeek[i]->save();
}
std::cout << tim << " to grow " << dataSeek.size()
<< " into MRU with fake seeking. \n";
}
/** Speed of freeing a lot of blocks and putting them in the free space map */
void test_freeBlock() {
CPUTimer tim;
DiskBuffer dbuf(0);
for (size_t i = 0; i < 100000; i++) {
dbuf.freeBlock(i * 100, (i % 3 == 0) ? 100 : 50);
}
// dbuf.defragFreeBlocks();
TS_ASSERT_EQUALS(dbuf.getFreeSpaceMap().size(), 66667);
std::cout << tim << " to add " << 100000
<< " blocks in the free space list.\n";
}
};
#endif /* MANTID_KERNEL_DISKBUFFERTEST_H_ */