static MemoryConfig config;
static size_t limit = 0;
-static std::atomic<bool> over_limit { false };
+static bool over_limit = false;
static std::atomic<uint64_t> current_epoch { 0 };
-static THREAD_LOCAL uint64_t last_dealloc = 0;
static THREAD_LOCAL uint64_t start_dealloc = 0;
static THREAD_LOCAL uint64_t start_alloc = 0;
static THREAD_LOCAL uint64_t start_epoch = 0;
uint64_t epoch, total;
heap->get_process_total(epoch, total);
- current_epoch = epoch;
-
bool prior = over_limit;
over_limit = limit and total > limit;
if ( prior != over_limit )
trace_logf(memory_trace, nullptr, "Epoch=%lu, memory=%lu (%s)\n", epoch, total, over_limit?"over":"under");
+ if ( over_limit )
+ current_epoch = epoch;
+ else
+ current_epoch = 0;
MemoryCounts& mc = MemoryCap::get_mem_stats();
MemoryCounts& mc = get_mem_stats();
heap->get_thread_allocs(mc.allocated, mc.deallocated);
- if ( !over_limit and !start_dealloc )
- return;
-
+ // Not already pruning
if ( !start_dealloc )
{
+ // Not over the limit
+ if ( !current_epoch )
+ return;
+ // Already completed pruning in the epoch
if ( current_epoch == start_epoch )
return;
- start_dealloc = last_dealloc = mc.deallocated;
+ // Start pruning for this epoch
+ start_dealloc = mc.deallocated;
start_alloc = mc.allocated;
start_epoch = current_epoch;
mc.reap_cycles++;
}
- mc.pruned += (mc.deallocated - last_dealloc);
- last_dealloc = mc.deallocated;
-
- uint64_t alloc = mc.allocated - start_alloc;
uint64_t dealloc = mc.deallocated - start_dealloc;
- if ( dealloc > alloc and ( ( dealloc - alloc ) >= config.prune_target ) )
+ uint64_t alloc = mc.allocated - start_alloc;
+
+ // Has the process gone under the limit
+ if ( !current_epoch )
+ {
+ if ( dealloc > alloc)
+ mc.reap_decrease += dealloc - alloc;
+ else
+ mc.reap_increase += alloc - dealloc;
+ start_dealloc = 0;
+ mc.reap_aborts++;
+ return;
+ }
+ // Has this thread freed enough outside of pruning
+ else if ( dealloc > alloc and ( ( dealloc - alloc ) >= config.prune_target ) )
{
+ mc.reap_decrease += dealloc - alloc;
start_dealloc = 0;
return;
}
++mc.reap_attempts;
- if ( pruner() )
- return;
+ bool prune_success = pruner();
- ++mc.reap_failures;
+ // Updates values after pruning
+ heap->get_thread_allocs(mc.allocated, mc.deallocated);
+ alloc = mc.allocated - start_alloc;
+ dealloc = mc.deallocated - start_dealloc;
+
+ if ( prune_success )
+ {
+ // Pruned the target amount, so stop pruning for this epoch
+ if ( dealloc > alloc and ( ( dealloc - alloc ) >= config.prune_target ) )
+ {
+ mc.reap_decrease += dealloc - alloc;
+ start_dealloc = 0;
+ }
+ }
+ else
+ {
+ // Failed to prune, so stop pruning
+ if ( dealloc > alloc)
+ mc.reap_decrease += dealloc - alloc;
+ else
+ mc.reap_increase += alloc - dealloc;
+ start_dealloc = 0;
+ ++mc.reap_failures;
+ }
}
// required to capture any update in final epoch
#include <CppUTest/CommandLineTestRunner.h>
#include <CppUTest/TestHarness.h>
+#include <CppUTestExt/MockSupport.h>
using namespace memory;
static void periodic_check()
{ MemoryCap::test_main_check(); }
-static int flows;
+struct TestFlowData
+{
+ int flows = 0;
+ unsigned flow_to_alloc_factor = 1;
+};
static bool pruner()
-{ return --flows >= 0; }
+{
+ MockHeap* heap = (MockHeap*)mock().getData("heap").getObjectPointer();
+ TestFlowData* fd = (TestFlowData*)mock().getData("flows").getObjectPointer();
+ if ( heap && 0 < fd->flows)
+ heap->dealloc += fd->flow_to_alloc_factor;
+ fd->flows--;
+ return fd->flows >= 0;
+}
static bool pkt_thread = false;
TEST_GROUP(memory_off)
{
+ TestFlowData fd;
+
void setup() override
{
+ fd = {};
+ mock().setDataObject("flows", "TestFlowData", &fd);
+ mock().setDataObject("heap", "MockHeap", nullptr);
MemoryCap::init(1);
MemoryCap::set_pruner(pruner);
}
void teardown() override
{
MemoryCap::term();
- flows = 0;
}
};
free_space();
- CHECK(flows == 0);
+ CHECK(fd.flows == 0);
const MemoryCounts& mc = MemoryCap::get_mem_stats();
CHECK(mc.start_up_use == 0);
free_space();
- CHECK(flows == 0);
+ CHECK(fd.flows == 0);
const MemoryCounts& mc = MemoryCap::get_mem_stats();
CHECK(mc.start_up_use == 0);
TEST_GROUP(memory)
{
+ TestFlowData fd;
MockHeap* heap = nullptr;
void setup() override
{
+ fd = {0, 1};
+ mock().setDataObject("flows", "TestFlowData", &fd);
MemoryCap::init(1);
heap = new MockHeap;
MemoryCap::set_heap_interface(heap);
+ mock().setDataObject("heap", "MockHeap", heap);
}
void teardown() override
{
MemoryCap::term();
- heap = nullptr;
- flows = 0;
}
};
periodic_check();
CHECK(heap->epoch == 2);
- CHECK(flows == 0);
+ CHECK(fd.flows == 0);
MemoryCap::stop();
CHECK(heap->epoch == 3);
const uint64_t start = 50;
heap->total = start;
- MemoryConfig config { cap, 100, 0, 1, true };
+ MemoryConfig config { cap, 100, 0, 2, true };
MemoryCap::start(config, pruner);
MemoryCap::thread_init();
const MemoryCounts& mc = MemoryCap::get_mem_stats();
CHECK(mc.start_up_use == start);
- flows = 2;
+ fd.flows = 3;
free_space();
- CHECK(flows == 2);
+ UNSIGNED_LONGS_EQUAL(3, fd.flows);
- heap->total = cap + 1;
+ heap->total = cap + 1; // over the limit
periodic_check();
CHECK(heap->epoch == 2);
- CHECK(flows == 2);
+ UNSIGNED_LONGS_EQUAL(3, fd.flows);
+ free_space(); // this prunes 1
+ UNSIGNED_LONGS_EQUAL(2, fd.flows);
+
+ free_space(); // finish pruning
+ UNSIGNED_LONGS_EQUAL(1, fd.flows);
+ UNSIGNED_LONGS_EQUAL(2, mc.reap_decrease);
+
free_space();
- CHECK(flows == 1);
+ UNSIGNED_LONGS_EQUAL(1, fd.flows);
+ UNSIGNED_LONGS_EQUAL(2, mc.reap_decrease);
- heap->total = cap;
- periodic_check();
+ periodic_check(); // still over the limit
CHECK(heap->epoch == 3);
- heap->alloc++;
- heap->dealloc++;
+ free_space();
+ UNSIGNED_LONGS_EQUAL(0, fd.flows);
+
+ heap->total = cap; // no longer over the limit
+ periodic_check();
+ CHECK(heap->epoch == 4);
+
+ free_space(); // abort
+ UNSIGNED_LONGS_EQUAL(0, fd.flows);
+ UNSIGNED_LONGS_EQUAL(3, mc.reap_decrease);
+ heap->total = cap + 1; // over the limit
+ periodic_check();
+ CHECK(heap->epoch == 5);
+
+ fd.flows = 1;
free_space();
- CHECK(flows == 0);
+ UNSIGNED_LONGS_EQUAL(0, fd.flows);
- heap->dealloc++;
+ heap->total = cap; // no longer over the limit
+ periodic_check();
+ CHECK(heap->epoch == 6);
+
+ heap->alloc += 5;
+
+ free_space(); // abort, reap_increase update
+ UNSIGNED_LONGS_EQUAL(0, fd.flows);
+ UNSIGNED_LONGS_EQUAL(3, mc.reap_decrease);
+ UNSIGNED_LONGS_EQUAL(4, mc.reap_increase);
+
+ heap->total = cap + 1; // over the limit
+ periodic_check();
+ CHECK(heap->epoch == 7);
+
+ fd.flows = 1;
free_space();
- CHECK(flows == 0);
+ UNSIGNED_LONGS_EQUAL(0, fd.flows);
- CHECK(mc.start_up_use == start);
- CHECK(mc.max_in_use == cap + 1);
- CHECK(mc.cur_in_use == cap);
+ heap->total = cap; // no longer over the limit
+ periodic_check();
+ CHECK(heap->epoch == 8);
- CHECK(mc.epochs == heap->epoch);
- CHECK(mc.allocated == heap->alloc);
- CHECK(mc.deallocated == heap->dealloc);
+ heap->alloc += 3;
+
+ free_space(); // abort, reap_increase update
+ UNSIGNED_LONGS_EQUAL(0, fd.flows);
+ UNSIGNED_LONGS_EQUAL(3, mc.reap_decrease);
+ UNSIGNED_LONGS_EQUAL(6, mc.reap_increase);
- CHECK(mc.reap_cycles == 1);
- CHECK(mc.reap_attempts == 2);
- CHECK(mc.reap_failures == 0);
- CHECK(mc.pruned == 2);
+ UNSIGNED_LONGS_EQUAL(start, mc.start_up_use);
+ UNSIGNED_LONGS_EQUAL(cap + 1, mc.max_in_use);
+ UNSIGNED_LONGS_EQUAL(cap, mc.cur_in_use);
+
+ UNSIGNED_LONGS_EQUAL(heap->epoch, mc.epochs);
+ UNSIGNED_LONGS_EQUAL(heap->alloc, mc.allocated);
+ UNSIGNED_LONGS_EQUAL(heap->dealloc, mc.deallocated);
+
+ UNSIGNED_LONGS_EQUAL(4, mc.reap_cycles);
+ UNSIGNED_LONGS_EQUAL(5, mc.reap_attempts);
+ UNSIGNED_LONGS_EQUAL(0, mc.reap_failures);
+ UNSIGNED_LONGS_EQUAL(3, mc.reap_aborts);
heap->total = start;
MemoryCap::stop();
MemoryCap::start(config, pruner);
MemoryCap::thread_init();
- flows = 3;
+ fd.flows = 3;
+ fd.flow_to_alloc_factor = 0;
heap->total = cap + 1;
periodic_check();
CHECK(heap->epoch == 2);
- CHECK(flows == 3);
+ CHECK(fd.flows == 3);
free_space();
- CHECK(flows == 2);
+ CHECK(fd.flows == 2);
free_space();
- CHECK(flows == 1);
+ CHECK(fd.flows == 1);
+ fd.flow_to_alloc_factor = 1;
free_space();
- CHECK(flows == 0);
+ CHECK(fd.flows == 0);
heap->total = cap;
periodic_check();
CHECK(heap->epoch == 3);
- heap->dealloc++;
free_space();
- CHECK(flows == 0);
+ CHECK(fd.flows == 0);
const MemoryCounts& mc = MemoryCap::get_mem_stats();
- CHECK(mc.reap_cycles == 1);
- CHECK(mc.reap_attempts == 3);
- CHECK(mc.reap_failures == 0);
- CHECK(mc.pruned == 1);
+ UNSIGNED_LONGS_EQUAL(1, mc.reap_cycles);
+ UNSIGNED_LONGS_EQUAL(3, mc.reap_attempts);
+ UNSIGNED_LONGS_EQUAL(0, mc.reap_failures);
+ UNSIGNED_LONGS_EQUAL(1, mc.reap_decrease);
+ UNSIGNED_LONGS_EQUAL(0, mc.reap_increase);
MemoryCap::stop();
}
MemoryCap::start(config, pruner);
MemoryCap::thread_init();
- flows = 3;
+ fd.flows = 3;
heap->total = cap + 1;
periodic_check();
- CHECK(flows == 3);
- free_space(); // prune 1 flow
- CHECK(flows == 2);
-
- heap->dealloc++;
- free_space(); // reset state
- CHECK(flows == 2);
+ CHECK(fd.flows == 3);
+ free_space(); // prune 1 flow and stop pruning from target
+ CHECK(fd.flows == 2);
- free_space(); // at most 1 reap cycle per epoch
- CHECK(flows == 2);
+ free_space();
+ CHECK(fd.flows == 2);
heap->total = cap;
periodic_check();
free_space();
- CHECK(flows == 2);
+ CHECK(fd.flows == 2);
- heap->total = cap + 10;
+ fd.flow_to_alloc_factor = 10;
+ heap->total = cap + 1;
periodic_check();
free_space();
- CHECK(flows == 1);
-
- heap->total = cap;
- heap->dealloc += 10;
- periodic_check();
+ CHECK(fd.flows == 1);
free_space();
- CHECK(flows == 1);
+ CHECK(fd.flows == 1);
const MemoryCounts& mc = MemoryCap::get_mem_stats();
- CHECK(mc.reap_cycles == 2);
- CHECK(mc.reap_attempts == 2);
- CHECK(mc.reap_failures == 0);
- CHECK(mc.pruned == 11);
+ UNSIGNED_LONGS_EQUAL(2, mc.reap_cycles);
+ UNSIGNED_LONGS_EQUAL(2, mc.reap_attempts);
+ UNSIGNED_LONGS_EQUAL(0, mc.reap_failures);
+ UNSIGNED_LONGS_EQUAL(11, mc.reap_decrease);
+ UNSIGNED_LONGS_EQUAL(0, mc.reap_increase);
MemoryCap::stop();
}
MemoryCap::start(config, pruner);
MemoryCap::thread_init();
- flows = 1;
+ const MemoryCounts& mc = MemoryCap::get_mem_stats();
+
+ fd.flows = 1;
+ heap->total = cap + 1;
+ periodic_check();
+
+ free_space();
+ CHECK(fd.flows == 0);
+
+ free_space(); // reap failure
+ CHECK(fd.flows == -1);
+ UNSIGNED_LONGS_EQUAL(1, mc.reap_decrease);
+
+ fd.flows = 1;
+ heap->total = cap + 1;
+ periodic_check();
+
+ free_space();
+ CHECK(fd.flows == 0);
+
+ heap->alloc += 3;
+
+ free_space(); // reap failure, reap_increase update
+ CHECK(fd.flows == -1);
+ UNSIGNED_LONGS_EQUAL(1, mc.reap_decrease);
+ UNSIGNED_LONGS_EQUAL(2, mc.reap_increase);
+
+ fd.flows = 1;
+ heap->total = cap + 1;
+ periodic_check();
+
+ free_space();
+ CHECK(fd.flows == 0);
+
+ heap->alloc += 2;
+
+ free_space(); // reap failure, reap_increase update
+ CHECK(fd.flows == -1);
+ UNSIGNED_LONGS_EQUAL(1, mc.reap_decrease);
+ UNSIGNED_LONGS_EQUAL(3, mc.reap_increase);
+
+ UNSIGNED_LONGS_EQUAL(3, mc.reap_cycles);
+ UNSIGNED_LONGS_EQUAL(6, mc.reap_attempts);
+ UNSIGNED_LONGS_EQUAL(3, mc.reap_failures);
+
+ MemoryCap::stop();
+}
+
+TEST(memory, reap_freed_outside_of_pruning)
+{
+ const uint64_t cap = 100;
+ const uint64_t start = 50;
+ heap->total = start;
+
+ MemoryConfig config { cap, 100, 0, 2, true };
+ MemoryCap::start(config, pruner);
+ MemoryCap::thread_init();
+
+ fd.flows = 2;
heap->total = cap + 1;
periodic_check();
- CHECK(flows == 1);
+ UNSIGNED_LONGS_EQUAL(2, fd.flows);
free_space();
- CHECK(flows == 0);
+ UNSIGNED_LONGS_EQUAL(1, fd.flows);
+
+ heap->alloc++;
+
+ free_space();
+ UNSIGNED_LONGS_EQUAL(0, fd.flows);
heap->dealloc++;
+
free_space();
- CHECK(flows == -1);
+ UNSIGNED_LONGS_EQUAL(0, fd.flows);
const MemoryCounts& mc = MemoryCap::get_mem_stats();
- CHECK(mc.reap_cycles == 1);
- CHECK(mc.reap_attempts == 2);
- CHECK(mc.reap_failures == 1);
- CHECK(mc.pruned == 1);
+ UNSIGNED_LONGS_EQUAL(1, mc.reap_cycles);
+ UNSIGNED_LONGS_EQUAL(2, mc.reap_attempts);
+ UNSIGNED_LONGS_EQUAL(0, mc.reap_failures);
+ UNSIGNED_LONGS_EQUAL(2, mc.reap_decrease);
+ UNSIGNED_LONGS_EQUAL(0, mc.reap_increase);
MemoryCap::stop();
}
static BaseStats g_stats;
THREAD_LOCAL BaseStats stream_base_stats;
THREAD_LOCAL PegCount current_flows_prev;
+THREAD_LOCAL PegCount current_free_flows_prev;
THREAD_LOCAL PegCount uni_flows_prev;
THREAD_LOCAL PegCount uni_ip_flows_prev;
// Keep the NOW stats at the bottom as it requires special sum_stats logic
{ CountType::NOW, "current_flows", "current number of flows in cache" },
+ { CountType::NOW, "current_free_flows", "current number of free flows in cache" },
{ CountType::NOW, "uni_flows", "number of uni flows in cache" },
{ CountType::NOW, "uni_ip_flows", "number of uni ip flows in cache" },
{ CountType::END, nullptr, nullptr }
};
-#define NOW_PEGS_NUM 3
+#define NOW_PEGS_NUM 4
// FIXIT-L dependency on stats define in another file
void base_prep()
stream_base_stats.reload_blocked_flow_deletes= flow_con->get_deletes(FlowDeleteState::BLOCKED);
stream_base_stats.current_flows = flow_con->get_num_flows();
+ stream_base_stats.current_free_flows = flow_con->get_num_free_flows();
stream_base_stats.uni_flows = flow_con->get_uni_flows();
stream_base_stats.uni_ip_flows = flow_con->get_uni_ip_flows();
array_size(base_pegs) - 1 - NOW_PEGS_NUM);
g_stats.current_flows += (int64_t)stream_base_stats.current_flows - (int64_t)current_flows_prev;
+ g_stats.current_free_flows += (int64_t)stream_base_stats.current_free_flows - (int64_t)current_free_flows_prev;
g_stats.uni_flows += (int64_t)stream_base_stats.uni_flows - (int64_t)uni_flows_prev;
g_stats.uni_ip_flows += (int64_t)stream_base_stats.uni_ip_flows - (int64_t)uni_ip_flows_prev;
void base_reset(bool reset_all)
{
current_flows_prev = stream_base_stats.current_flows;
+ current_free_flows_prev = stream_base_stats.current_free_flows;
uni_flows_prev = stream_base_stats.uni_flows;
uni_ip_flows_prev = stream_base_stats.uni_ip_flows;