First release of open-appsec source code

This commit is contained in:
roybarda
2022-10-26 19:33:19 +03:00
parent 3883109caf
commit a883352f79
1353 changed files with 276290 additions and 1 deletions

View File

@@ -0,0 +1,6 @@
if("${PLATFORM_TYPE}" STREQUAL "arm32_openwrt")
ADD_DEFINITIONS(-Wno-unused-parameter)
endif()
add_library(mainloop mainloop.cc coroutine.cc)
add_subdirectory(mainloop_ut)

View File

@@ -0,0 +1,84 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (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.
#include "coroutine.h"
using namespace std;
static void defaultPull(boost::coroutines2::detail::push_coroutine<void> &) {}
RoutineWrapper::RoutineWrapper(
I_MainLoop::RoutineType _pri,
I_MainLoop::Routine func,
bool _is_primary,
const string &_routine_name)
:
pri(_pri),
pull(defaultPull), // will be replace by `invoke` on the first invokation
routine( [func] (pull_type &pull) { invoke(pull, func); } ),
is_primary(_is_primary),
routine_name(_routine_name)
{
}
RoutineWrapper::~RoutineWrapper()
{
pull = pull_type(defaultPull);
}
bool
RoutineWrapper::isActive() const
{
return static_cast<bool>(routine);
}
bool
RoutineWrapper::shouldRun(const I_MainLoop::RoutineType &limit) const
{
return !is_halt && pri<=limit;
}
void
RoutineWrapper::run()
{
active = this; // Will be used by `invoke` to set the `pull` on the first invokation
routine();
}
void
RoutineWrapper::yield()
{
pull();
}
void
RoutineWrapper::halt()
{
is_halt = true;
}
void
RoutineWrapper::resume()
{
is_halt = false;
}
void
RoutineWrapper::invoke(pull_type &pull, I_MainLoop::Routine func)
{
dbgAssert(active != nullptr) << "Trying to invoke without an active routine";
active->pull = move(pull); // First invokation (other invokaction will start inside `func`), set the `pull` object
func();
}
RoutineWrapper *RoutineWrapper::active = nullptr;

63
core/mainloop/coroutine.h Normal file
View File

@@ -0,0 +1,63 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (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.
#ifndef __COROUTINE_H__
#define __COROUTINE_H__
#include <boost/coroutine2/all.hpp>
#include "i_mainloop.h"
#include "maybe_res.h"
class RoutineWrapper
{
using pull_type = boost::coroutines2::coroutine<void>::pull_type;
using push_type = boost::coroutines2::coroutine<void>::push_type;
public:
RoutineWrapper(
I_MainLoop::RoutineType _pri,
I_MainLoop::Routine func,
bool is_primary,
const std::string &_routine_name
);
~RoutineWrapper();
RoutineWrapper(const RoutineWrapper &) = delete;
RoutineWrapper(RoutineWrapper &&) = default;
RoutineWrapper & operator=(const RoutineWrapper &) = delete;
RoutineWrapper & operator=(RoutineWrapper &&) = default;
bool isPrimary() { return is_primary; }
const std::string & getRoutineName() const { return routine_name; }
bool isActive() const;
bool shouldRun(const I_MainLoop::RoutineType &limit) const;
void run();
void yield();
void halt();
void resume();
private:
static void invoke(pull_type &pull, I_MainLoop::Routine func);
static RoutineWrapper *active; // Used by `invoke` to set the value of `pull`
I_MainLoop::RoutineType pri;
// `pull` will hold the object that returns the flow control back to the mainloop.
pull_type pull;
push_type routine;
bool is_primary;
bool is_halt = false;
std::string routine_name;
};
#endif // __COROUTINE_H__

592
core/mainloop/mainloop.cc Normal file
View File

@@ -0,0 +1,592 @@
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (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.
#include "mainloop.h"
#include <memory>
#include <system_error>
#include <map>
#include <sstream>
#include <poll.h>
#include <unistd.h>
#include "config.h"
#include "coroutine.h"
#include "singleton.h"
#include "debug.h"
#include "i_time_get.h"
#include "report/log_rest.h"
#include "mainloop/mainloop_metric.h"
using namespace std;
USE_DEBUG_FLAG(D_MAINLOOP);
bool fini_signal_flag = false;
class MainloopStop {};
class MainloopComponent::Impl : Singleton::Provide<I_MainLoop>::From<MainloopComponent>
{
using RoutineMap = map<RoutineID, RoutineWrapper>;
public:
void run() override;
RoutineID
addOneTimeRoutine(
RoutineType priority,
Routine func,
const string &routine_name,
bool is_primary
) override;
RoutineID
addRecurringRoutine(
RoutineType priority,
chrono::microseconds time,
Routine func,
const string &routine_name,
bool is_primary
) override;
RoutineID
addFileRoutine(
RoutineType priority,
int fd,
Routine func,
const string &routine_name,
bool is_primary
) override;
bool doesRoutineExist(RoutineID id) override;
Maybe<RoutineID> getCurrentRoutineId() const override;
void yield(bool force) override;
void yield(chrono::microseconds time) override;
void stopAll() override;
void stop() override;
void stop(RoutineID id) override;
void halt() override;
void halt(RoutineID id) override;
void resume(RoutineID id) override;
void
init()
{
fini_signal_flag = false;
addOneTimeRoutine(
RoutineType::Offline,
[this](){ reportStartupEvent(); },
"Nano service startup report",
false
);
metric_report_interval = chrono::seconds(
getConfigurationWithDefault<uint>(600, "Mainloop", "metric reporting interval")
);
mainloop_metric.init(
"Mainloop sleep time data",
ReportIS::AudienceTeam::AGENT_CORE,
ReportIS::IssuingEngine::AGENT_CORE,
metric_report_interval,
false
);
mainloop_metric.registerListener();
}
void
fini()
{
timer = nullptr;
fini_signal_flag = false;
}
private:
void reportStartupEvent();
void stop(const RoutineMap::iterator &iter);
void updateCurrentStress(bool is_busy);
uint32_t getCurrentTimeSlice(uint32_t current_stress);
RoutineID getNextID();
I_TimeGet *
getTimer()
{
if (timer == nullptr) timer = Singleton::Consume<I_TimeGet>::by<MainloopComponent>();
return timer;
}
I_TimeGet *timer = nullptr;
RoutineMap routines;
RoutineMap::iterator curr_iter = routines.end();
RoutineID next_routine_id = 0;
bool do_stop = false;
bool is_running = false;
chrono::microseconds stop_time;
uint32_t current_stress = 0;
chrono::seconds metric_report_interval;
MainloopEvent mainloop_event;
MainloopMetric mainloop_metric;
};
static I_MainLoop::RoutineType rounds[] = {
I_MainLoop::RoutineType::RealTime,
I_MainLoop::RoutineType::RealTime,
I_MainLoop::RoutineType::RealTime,
I_MainLoop::RoutineType::Timer,
I_MainLoop::RoutineType::RealTime,
I_MainLoop::RoutineType::RealTime,
I_MainLoop::RoutineType::RealTime,
I_MainLoop::RoutineType::System,
I_MainLoop::RoutineType::RealTime,
I_MainLoop::RoutineType::RealTime,
I_MainLoop::RoutineType::RealTime,
I_MainLoop::RoutineType::Timer,
I_MainLoop::RoutineType::RealTime,
I_MainLoop::RoutineType::RealTime,
I_MainLoop::RoutineType::RealTime,
I_MainLoop::RoutineType::Offline,
};
void
MainloopComponent::Impl::reportStartupEvent()
{
chrono::microseconds curr_time = Singleton::Consume<I_TimeGet>::by<MainloopComponent>()->getWalltime();
ReportIS::AudienceTeam audience_team = ReportIS::AudienceTeam::NONE;
auto i_env = Singleton::Consume<I_Environment>::by<MainloopComponent>();
auto team = i_env->get<ReportIS::AudienceTeam>("Audience Team");
if (team.ok()) audience_team = *team;
Report startup_message(
"Nano service successfully started",
curr_time,
ReportIS::Type::EVENT,
ReportIS::Level::LOG,
ReportIS::LogLevel::INFO,
ReportIS::Audience::INTERNAL,
audience_team,
ReportIS::Severity::INFO,
ReportIS::Priority::HIGH,
chrono::seconds(0),
LogField("agentId", Singleton::Consume<I_AgentDetails>::by<MainloopComponent>()->getAgentId()),
ReportIS::Tags::INFORMATIONAL
);
string fog_event_uri = getConfigurationWithDefault<string>(
"/api/v1/agents/events",
"Logging",
"Fog Log URI"
);
LogRest startup_message_client_rest(startup_message);
Singleton::Consume<I_Messaging>::by<MainloopComponent>()->sendObjectWithPersistence(
startup_message_client_rest,
I_Messaging::Method::POST,
fog_event_uri,
"",
true,
MessageTypeTag::REPORT
);
dbgInfo(D_MAINLOOP) << "Startup report was successfully sent to fog";
}
void
MainloopComponent::Impl::run()
{
dbgAssert(!is_running) << "MainloopComponent::Impl::run was called while it was already running";
is_running = true;
bool has_primary_routines = true;
uint round = 0;
uint64_t sleep_count = 0;
dbgInfo(D_MAINLOOP) << "Starting the Mainloop";
chrono::microseconds last_iter = getTimer()->getMonotonicTime();
const chrono::seconds one_sec(1);
string service_name = "Unnamed Nano Service";
auto name = Singleton::Consume<I_Environment>::by<MainloopComponent>()->get<string>("Service Name");
if (name.ok()) service_name = *name;
string error_prefix = "Service " + service_name + " crashed. Error details: ";
string error;
while (has_primary_routines) {
mainloop_event.setStressValue(current_stress);
int time_slice_to_use = getCurrentTimeSlice(current_stress);
mainloop_event.setTimeSlice(time_slice_to_use);
chrono::microseconds basic_time_slice(time_slice_to_use);
chrono::milliseconds large_exceeding(getConfigurationWithDefault(100u, "Mainloop", "Exceed Warning"));
auto start_time = getTimer()->getMonotonicTime();
has_primary_routines = false;
curr_iter = routines.begin();
while (curr_iter != routines.end()) {
if (fini_signal_flag) {
break;
}
if (!curr_iter->second.isActive()) {
curr_iter = routines.erase(curr_iter);
continue;
}
if (curr_iter->second.isPrimary()) has_primary_routines = true;
if (curr_iter->second.shouldRun(rounds[round])) {
// Set the time upon which `hasAdditionalTime` will yield.
stop_time = getTimer()->getMonotonicTime() + basic_time_slice;
dbgTrace(D_MAINLOOP) <<
"Starting execution of corutine. Routine named: " <<
curr_iter->second.getRoutineName();
try {
curr_iter->second.run();
} catch (const exception &e) {
error =
error_prefix
+ "Routine: '"
+ curr_iter->second.getRoutineName()
+ "' thrown exception: "
+ e.what();
} catch (...) {
error =
error_prefix
+ "Unknown generic error exception thrown during execution of mainloop. Routine name: '"
+ curr_iter->second.getRoutineName()
+ "'";
}
if (error != "") {
cerr << error << endl;
if (Singleton::exists<I_SignalHandler>()) {
Singleton::Consume<I_SignalHandler>::by<MainloopComponent>()->dumpErrorReport(error);
}
fini_signal_flag = true;
continue;
}
dbgTrace(D_MAINLOOP) <<
"Ending execution of corutine. Routine named: " <<
curr_iter->second.getRoutineName();
if (getTimer()->getMonotonicTime() > stop_time + large_exceeding) {
dbgError(D_MAINLOOP)
<< "Routine execution exceeded run time. Routine name: "
<< curr_iter->second.getRoutineName();
}
}
curr_iter++;
}
round = (round + 1) % (sizeof(rounds)/sizeof(rounds[0]));
uint64_t signed_sleep_time = 0;
chrono::microseconds current_time = getTimer()->getMonotonicTime();
if (start_time + basic_time_slice > current_time) {
chrono::microseconds sleep_time = start_time + basic_time_slice - current_time;
signed_sleep_time = sleep_time.count();
sleep_count += signed_sleep_time;
usleep(signed_sleep_time);
}
mainloop_event.setSleepTime(signed_sleep_time);
mainloop_event.notify();
if (start_time - last_iter > one_sec) {
dbgTrace(D_MAINLOOP) <<
"During the last second the process slept for " <<
sleep_count <<
" microseconds, stress: " <<
current_stress <<
", time slice: " <<
time_slice_to_use;
sleep_count = 0;
last_iter = start_time;
}
}
dbgInfo(D_MAINLOOP) << "Mainloop ended - stopping all routines";
stopAll();
routines.clear();
}
string
getRoutineTypeString(I_MainLoop::RoutineType priority)
{
switch (priority) {
case I_MainLoop::RoutineType::RealTime: return "RealTime";
case I_MainLoop::RoutineType::Timer: return "Timer";
case I_MainLoop::RoutineType::System: return "System";
case I_MainLoop::RoutineType::Offline: return "Offline";
}
return "unknown";
}
I_MainLoop::RoutineID
MainloopComponent::Impl::addOneTimeRoutine(
RoutineType priority,
Routine func,
const string &_routine_name,
bool is_primary)
{
auto id = getNextID();
string routine_name = _routine_name.empty() ? string("Generic routine, id: " + to_string(id)) : _routine_name;
auto env = Singleton::Consume<I_Environment>::by<MainloopComponent>()->createEnvironment();
Routine func_wrapper = [this, env, func, routine_name] () mutable {
Singleton::Consume<I_Environment>::by<MainloopComponent>()->loadEnvironment(move(env));
try {
if (this->do_stop) return;
func();
} catch (MainloopStop) {
return;
}
};
routines.emplace(id, RoutineWrapper(priority, func_wrapper, is_primary, routine_name));
dbgDebug(D_MAINLOOP)
<< "Added new routine. Name: "
<< routine_name
<< ", Priority: "
<< getRoutineTypeString(priority)
<< ", total routines: "
<< routines.size();
return id;
}
I_MainLoop::RoutineID
MainloopComponent::Impl::addRecurringRoutine(
RoutineType priority,
chrono::microseconds time,
Routine func,
const string &routine_name,
bool is_primary
)
{
Routine func_wrapper = [this, time, func] () {
while (true) {
func();
yield(time);
}
};
return addOneTimeRoutine(priority, func_wrapper, routine_name, is_primary);
}
I_MainLoop::RoutineID
MainloopComponent::Impl::addFileRoutine(
RoutineType priority,
int fd,
Routine func,
const string &routine_name,
bool is_primary)
{
Routine func_wrapper = [this, fd, func, priority] () {
while (true) {
struct pollfd s_poll;
s_poll.fd = fd;
s_poll.events = POLLIN;
s_poll.revents = 0;
int rc = poll(&s_poll, 1, 0);
if (rc > 0 && (s_poll.revents & POLLIN) != 0) {
func();
if (priority == I_MainLoop::RoutineType::RealTime) {
if (s_poll.revents & POLLHUP) {
updateCurrentStress(false);
} else {
updateCurrentStress(true);
}
}
} else {
if (priority == I_MainLoop::RoutineType::RealTime) updateCurrentStress(false);
}
yield(true);
}
};
return addOneTimeRoutine(priority, func_wrapper, routine_name, is_primary);
}
bool
MainloopComponent::Impl::doesRoutineExist(RoutineID id)
{
return routines.find(id) != routines.end();
}
Maybe<I_MainLoop::RoutineID>
MainloopComponent::Impl::getCurrentRoutineId() const
{
if (curr_iter == routines.end()) return genError("No routine currently runs");
return curr_iter->first;
}
void
MainloopComponent::Impl::yield(bool force)
{
dbgAssert(curr_iter != routines.end()) << "Calling 'yield' without a running current routine";
if (!force && getTimer()->getMonotonicTime() < stop_time) return;
auto env = Singleton::Consume<I_Environment>::by<MainloopComponent>()->saveEnvironment();
curr_iter->second.yield();
Singleton::Consume<I_Environment>::by<MainloopComponent>()->loadEnvironment(move(env));
if (do_stop) throw MainloopStop();
}
void
MainloopComponent::Impl::yield(chrono::microseconds time)
{
if (time == chrono::microseconds::zero()) {
yield(true);
return;
}
chrono::microseconds restart_time = getTimer()->getMonotonicTime() + time;
while (getTimer()->getMonotonicTime() < restart_time) {
yield(true);
}
}
void
MainloopComponent::Impl::stopAll()
{
for (auto iter = routines.begin(); iter != routines.end(); iter++) {
// We can't stop the current routine from inside the loop, since this will also stop the loop and we won't
// reach the routines that come after the current routine. So we skip the current routine and come back to
// it (if it exists) after the end of the loop.
if (iter != curr_iter) stop(iter);
}
try {
if (curr_iter != routines.end()) stop(curr_iter);
} catch (MainloopStop) {
}
}
void
MainloopComponent::Impl::stop()
{
dbgAssert(curr_iter != routines.end()) << "Attempting to stop a routine when none is running";
stop(curr_iter);
}
void
MainloopComponent::Impl::stop(RoutineID id)
{
auto iter = routines.find(id);
if (iter == routines.end()) {
dbgError(D_MAINLOOP) << "Attempting to stop the routine " << id << " that does not exist";
return;
}
stop(iter);
}
void
MainloopComponent::Impl::halt()
{
dbgAssert(curr_iter != routines.end()) << "Calling 'halt' without a running current routine";
curr_iter->second.halt();
yield(true);
}
void
MainloopComponent::Impl::halt(RoutineID id)
{
auto iter = routines.find(id);
dbgAssert(iter != routines.end()) << "No routine " << id << " to halt";
iter->second.halt();
if (iter == curr_iter) yield(true);
}
void
MainloopComponent::Impl::resume(RoutineID id)
{
auto iter = routines.find(id);
dbgAssert(iter != routines.end()) << "No routine " << id << " to resume";
iter->second.resume();
}
void
MainloopComponent::Impl::stop(const RoutineMap::iterator &iter)
{
if (iter == curr_iter) {
dbgDebug(D_MAINLOOP) << "Stoping the current routine " << iter->first;
throw MainloopStop();
}
if (iter->second.isActive()) {
dbgDebug(D_MAINLOOP) << "Stoping the routine " << iter->first;
do_stop = true;
// We are going to let the routine run one last time, so it can throw an exception which will cause the stack
// to clean up nicely.
iter->second.run();
do_stop = false;
}
}
I_MainLoop::RoutineID
MainloopComponent::Impl::getNextID()
{
next_routine_id++;
while (routines.find(next_routine_id) != routines.end()) {
next_routine_id++;
}
return next_routine_id;
}
void
MainloopComponent::Impl::updateCurrentStress(bool is_busy)
{
const int stress_factor = 6; // calculated by trial and error, should be revisited
if (is_busy) {
if (current_stress < 95) {
current_stress += stress_factor;
} else {
current_stress = 100;
}
} else {
if (current_stress > 0) current_stress--;
}
}
uint32_t
MainloopComponent::Impl::getCurrentTimeSlice(uint32_t current_stress)
{
int idle_time_slice = getConfigurationWithDefault<int>(1000, "Mainloop", "Idle routine time slice");
int busy_time_slice = getConfigurationWithDefault<int>(1, "Mainloop", "Busy routine time slice");
return idle_time_slice - (((idle_time_slice - busy_time_slice) * current_stress) / 100);
}
MainloopComponent::MainloopComponent() : Component("MainloopComponent"), pimpl(make_unique<Impl>())
{
}
MainloopComponent::~MainloopComponent()
{
}
void MainloopComponent::init() { pimpl->init(); }
void MainloopComponent::fini() { pimpl->fini(); }
void
MainloopComponent::preload()
{
registerExpectedConfiguration<int>("Mainloop", "Idle routine time slice");
registerExpectedConfiguration<int>("Mainloop", "Busy routine time slice");
registerExpectedConfiguration<uint>("Mainloop", "metric reporting interval");
registerExpectedConfiguration<uint>("Mainloop", "Exceed Warning");
}

View File

@@ -0,0 +1,11 @@
if ("${GEN_BUNDLE_VERSION}" STREQUAL "FALSE")
find_package(Boost REQUIRED)
endif()
link_directories(${Boost_LIBRARY_DIRS})
add_unit_test(
mainloop_ut
"mainloop_ut.cc"
"mainloop;singleton;boost_context;rest;metric;event_is;-lboost_regex"
)

View File

@@ -0,0 +1,531 @@
#include "i_mainloop.h"
#include "mainloop.h"
#include <fcntl.h>
#include <chrono>
#include "cptest.h"
#include "config.h"
#include "config_component.h"
#include "mock/mock_time_get.h"
#include "mock/mock_environment.h"
#include "mock/mock_messaging.h"
#include "mock/mock_agent_details.h"
#include "scope_exit.h"
#include "metric/all_metric_event.h"
#include "debug.h"
using namespace std;
using namespace testing;
using namespace chrono;
USE_DEBUG_FLAG(D_MAINLOOP);
class EndTest
{
};
class MainloopTest : public Test
{
public:
MainloopTest()
{
EXPECT_CALL(mock_env, getActiveContexts()).WillRepeatedly(ReturnRef(active_context));
Debug::setUnitTestFlag(D_MAINLOOP, Debug::DebugLevel::DEBUG);
Debug::setNewDefaultStdout(&capture_debug);
}
~MainloopTest()
{
Debug::setUnitTestFlag(D_MAINLOOP, Debug::DebugLevel::INFO);
Debug::setNewDefaultStdout(&cout);
mainloop_comp.fini();
}
void
expectPersistentMessage()
{
EXPECT_CALL(
mock_msg,
mockSendPersistentMessage(false, _, _, "/api/v1/agents/events", _, _, _)
).Times(2).WillRepeatedly(
WithArgs<1, 6>(
Invoke(
[this](const string &req_body, MessageTypeTag tag)
{
EXPECT_TRUE(tag == MessageTypeTag::REPORT || tag == MessageTypeTag::METRIC);
if (tag == MessageTypeTag::REPORT) startup_report_body = req_body;
static bool should_throw = false;
if (should_throw) {
should_throw = false;
throw EndTest();
} else {
should_throw = true;
}
return string();
}
)
)
);
}
I_Environment::ActiveContexts active_context;
NiceMock<MockTimeGet> mock_time;
MainloopComponent mainloop_comp;
NiceMock<MockEnvironment> mock_env;
StrictMock<MockMessaging> mock_msg;
NiceMock<MockAgentDetails> mock_agent_details;
I_MainLoop *mainloop = Singleton::Consume<I_MainLoop>::from(mainloop_comp);
ConfigComponent conf;
Config::I_Config *config = nullptr;
ostringstream capture_debug;
string startup_report_body;
bool stop_test = false;
};
TEST_F(MainloopTest, do_nothing)
{
}
TEST_F(MainloopTest, start_with_nothing_to_do)
{
mainloop->run();
}
TEST_F(MainloopTest, basic_metrics_check)
{
string startup_body_sent;
expectPersistentMessage();
mainloop_comp.init();
mainloop->addOneTimeRoutine(
I_MainLoop::RoutineType::RealTime,
[this] ()
{
while (true) {
mainloop->yield(true);
}
},
"internal test cb",
true
);
try {
mainloop->run();
} catch (...) {
}
AllMetricEvent all_mt_event;
all_mt_event.setReset(false);
all_mt_event.notify();
string mainloop_str =
"{\n"
" \"Metric\": \"Mainloop sleep time data\",\n"
" \"Reporting interval\": 600,\n"
" \"mainloopMaxTimeSliceSample\": 1000,\n"
" \"mainloopAvgTimeSliceSample\": 1000.0,\n"
" \"mainloopLastTimeSliceSample\": 1000,\n"
" \"mainloopMaxSleepTimeSample\": 1000,\n"
" \"mainloopAvgSleepTimeSample\": 1000.0,\n"
" \"mainloopLastSleepTimeSample\": 1000,\n"
" \"mainloopMaxStressValueSample\": 0,\n"
" \"mainloopAvgStressValueSample\": 0.0,\n"
" \"mainloopLastStressValueSample\": 0\n"
"}";
EXPECT_THAT(all_mt_event.performNamedQuery(), ElementsAre(Pair("Mainloop sleep time data", mainloop_str)));
static const string expected_message =
"{\n"
" \"log\": {\n"
" \"eventTime\": \"\",\n"
" \"eventName\": \"Nano service successfully started\",\n"
" \"eventSeverity\": \"Info\",\n"
" \"eventPriority\": \"High\",\n"
" \"eventType\": \"Event Driven\",\n"
" \"eventLevel\": \"Log\",\n"
" \"eventLogLevel\": \"info\",\n"
" \"eventAudience\": \"Internal\",\n"
" \"eventAudienceTeam\": \"\",\n"
" \"eventFrequency\": 0,\n"
" \"eventTags\": [\n"
" \"Informational\"\n"
" ],\n"
" \"eventSource\": {\n"
" \"agentId\": \"\",\n"
" \"eventTraceId\": \"\",\n"
" \"eventSpanId\": \"\",\n"
" \"issuingEngineVersion\": \"\",\n"
" \"serviceName\": \"Unnamed Nano Service\"\n"
" },\n"
" \"eventData\": {}\n"
" }\n"
"}";
EXPECT_EQ(startup_report_body, expected_message);
}
TEST_F(MainloopTest, no_sleep_time_metrics_check)
{
mainloop_comp.preload();
string startup_body_sent;
expectPersistentMessage();
chrono::microseconds time(0);
EXPECT_CALL(
mock_time,
getMonotonicTime()
).WillRepeatedly(InvokeWithoutArgs([&]{ time += microseconds(3000); return time; } ));
setConfiguration<int>(
2,
string("Mainloop"),
string("Idle routine time slice")
);
mainloop_comp.init();
mainloop->addOneTimeRoutine(
I_MainLoop::RoutineType::RealTime,
[this] ()
{
while (true) {
mainloop->yield(true);
}
},
"internal test cb",
true
);
try {
mainloop->run();
} catch (...) {
}
AllMetricEvent all_mt_event;
all_mt_event.setReset(false);
all_mt_event.notify();
string mainloop_str =
"{\n"
" \"Metric\": \"Mainloop sleep time data\",\n"
" \"Reporting interval\": 600,\n"
" \"mainloopMaxTimeSliceSample\": 2,\n"
" \"mainloopAvgTimeSliceSample\": 2.0,\n"
" \"mainloopLastTimeSliceSample\": 2,\n"
" \"mainloopMaxSleepTimeSample\": 0,\n"
" \"mainloopAvgSleepTimeSample\": 0.0,\n"
" \"mainloopLastSleepTimeSample\": 0,\n"
" \"mainloopMaxStressValueSample\": 0,\n"
" \"mainloopAvgStressValueSample\": 0.0,\n"
" \"mainloopLastStressValueSample\": 0\n"
"}";
EXPECT_THAT(all_mt_event.query(), ElementsAre(mainloop_str));
static const string expected_message =
"{\n"
" \"log\": {\n"
" \"eventTime\": \"\",\n"
" \"eventName\": \"Nano service successfully started\",\n"
" \"eventSeverity\": \"Info\",\n"
" \"eventPriority\": \"High\",\n"
" \"eventType\": \"Event Driven\",\n"
" \"eventLevel\": \"Log\",\n"
" \"eventLogLevel\": \"info\",\n"
" \"eventAudience\": \"Internal\",\n"
" \"eventAudienceTeam\": \"\",\n"
" \"eventFrequency\": 0,\n"
" \"eventTags\": [\n"
" \"Informational\"\n"
" ],\n"
" \"eventSource\": {\n"
" \"agentId\": \"\",\n"
" \"eventTraceId\": \"\",\n"
" \"eventSpanId\": \"\",\n"
" \"issuingEngineVersion\": \"\",\n"
" \"serviceName\": \"Unnamed Nano Service\"\n"
" },\n"
" \"eventData\": {}\n"
" }\n"
"}";
EXPECT_EQ(startup_report_body, expected_message);
}
TEST(MainloopTestWithoutComponent, register_config)
{
ConfigComponent config;
StrictMock<MockTimeGet> mock_timer;
MainloopComponent mainloop_comp;
::Environment env;
env.preload();
mainloop_comp.preload();
env.init();
string config_json =
"{\n"
" \"Mainloop\": {\n"
" \"Idle routine time slice\": [\n"
" {\n"
" \"value\": 200\n"
" }\n"
" ]\n"
" }\n"
"}\n";
istringstream ss(config_json);
Singleton::Consume<Config::I_Config>::from(config)->loadConfiguration(ss);
int time_slice = getConfigurationWithDefault<int>(100, "Mainloop", "Idle routine time slice");
EXPECT_EQ(time_slice, 200);
env.fini();
}
TEST_F(MainloopTest, call_single_cb)
{
int num_called = 0;
auto cb = [&num_called] () {
num_called++;
};
mainloop->addOneTimeRoutine(I_MainLoop::RoutineType::RealTime, cb, "call single cb test", true);
mainloop->run();
EXPECT_EQ(1, num_called);
}
TEST_F(MainloopTest, call_single_yield)
{
int num_called = 0;
auto ml = mainloop;
auto cb = [&num_called, ml] () {
num_called++;
ml->yield(true);
num_called++;
};
mainloop->addOneTimeRoutine(I_MainLoop::RoutineType::RealTime, cb, "call_single_yield test", true);
mainloop->run();
EXPECT_EQ(2, num_called);
}
TEST_F(MainloopTest, stop_from_cb)
{
bool dtor_called = false;
auto stop_cb = [&dtor_called, this] () {
auto scope_guard = make_scope_exit([&dtor_called] () { dtor_called = true; });
mainloop->stop();
ADD_FAILURE() << "Should stop before this";
};
mainloop->addOneTimeRoutine(I_MainLoop::RoutineType::RealTime, stop_cb, "stop_from_cb test", true);
mainloop->run();
EXPECT_TRUE(dtor_called); // Verifying that we exited the routine cleanly, invoking the dtor
}
TEST_F(MainloopTest, stop_other_cb)
{
int num_called = 0;
auto stoped_cb = [&num_called] () { num_called++; };
auto stop_id = mainloop->addOneTimeRoutine(
I_MainLoop::RoutineType::Offline,
stoped_cb,
"stop_other_cb test - cb to stop",
true
);
auto stopping_bc = [stop_id, this] () { mainloop->stop(stop_id); };
mainloop->addOneTimeRoutine(
I_MainLoop::RoutineType::RealTime,
stopping_bc,
"stop_other_cb test - cb that stops",
true
);
mainloop->run();
EXPECT_EQ(0, num_called);
}
TEST_F(MainloopTest, call_recurring_cb)
{
int num_called = 0;
auto cb = [&num_called, this] () {
num_called++;
if (num_called == 3) mainloop->stop();
};
mainloop->addRecurringRoutine(
I_MainLoop::RoutineType::RealTime,
chrono::microseconds(0),
cb,
"call_recurring_cb",
true
);
mainloop->run();
EXPECT_EQ(3, num_called);
}
TEST_F(MainloopTest, call_file_cb)
{
CPTestTempfile file({ "a", "b", "c" });
int fd = open(file.fname.c_str(), O_RDONLY);
ASSERT_LT(0, fd);
int num_called = 0;
auto cb = [&num_called, fd, this] () {
char ch;
ASSERT_EQ(1, read(fd, &ch, 1));
if (ch == 'c') mainloop->stop();
num_called++;
};
mainloop->addFileRoutine(I_MainLoop::RoutineType::RealTime, fd, cb, "call_file_cb test", true);
mainloop->run();
EXPECT_EQ(4, num_called);
}
TEST_F(MainloopTest, stop_while_routines_are_running)
{
int num_called = 0;
auto routine = [&num_called] () {
num_called++;
};
auto stop_cb = [this] () {
mainloop->stopAll();
};
mainloop->addOneTimeRoutine(
I_MainLoop::RoutineType::RealTime,
routine,
"stop_while_routines_are_running test - cb to stop",
true
);
mainloop->addOneTimeRoutine(
I_MainLoop::RoutineType::Timer,
stop_cb,
"stop_while_routines_are_running test - cb that stops",
true
);
mainloop->run();
// "routine" is of higher priority than "stop", so it should run at least once before "stop" is called.
EXPECT_LT(0, num_called);
}
TEST_F(MainloopTest, halt_self)
{
int num_called = 0;
auto routine = [&num_called, this] () {
mainloop->halt();
num_called++;
};
mainloop->addOneTimeRoutine(
I_MainLoop::RoutineType::RealTime,
routine,
"halt_self test - cb to stop",
true
);
auto stop_cb = [this] () {
mainloop->stopAll();
};
mainloop->addOneTimeRoutine(
I_MainLoop::RoutineType::RealTime,
stop_cb,
"halt_self test - cb that stops",
true
);
mainloop->run();
EXPECT_EQ(0, num_called);
}
TEST_F(MainloopTest, halt_resume)
{
auto stop_cb = [this] () {
mainloop->stopAll();
};
auto id = mainloop->addOneTimeRoutine(
I_MainLoop::RoutineType::Timer,
stop_cb,
"halt_resume test - cb that stops",
true
);
int num_called = 0;
auto routine = [&num_called, this, id] () {
mainloop->halt(id);
while (true) {
if (num_called == 100) mainloop->resume(id);
num_called++;
mainloop->yield(true);
}
};
mainloop->addOneTimeRoutine(
I_MainLoop::RoutineType::RealTime,
routine,
"halt_resume test - cb to stop",
true
);
mainloop->run();
EXPECT_LT(100, num_called);
}
TEST_F(MainloopTest, death_on_run_twice)
{
cptestPrepareToDie();
auto cb = [this] () {
EXPECT_DEATH(mainloop->run(), "MainloopComponent::Impl::run was called while it was already running");
};
mainloop->addOneTimeRoutine(
I_MainLoop::RoutineType::RealTime,
cb,
"death_on_run_twice test",
true
);
mainloop->run();
}
TEST_F(MainloopTest, get_routine_id)
{
cptestPrepareToDie();
auto cb = [this] () {
EXPECT_EQ(mainloop->getCurrentRoutineId().unpack(), 1);
EXPECT_DEATH(mainloop->run(), "MainloopComponent::Impl::run was called while it was already running");
};
mainloop->addOneTimeRoutine(
I_MainLoop::RoutineType::RealTime,
cb,
"get_routine_id test",
true
);
mainloop->run();
}
TEST_F(MainloopTest, check_routine_name)
{
int num_called = 0;
auto cb = [&num_called] () {
num_called++;
};
Debug::setUnitTestFlag(D_MAINLOOP, Debug::DebugLevel::TRACE);
mainloop->addOneTimeRoutine(I_MainLoop::RoutineType::RealTime, cb, "check routine name test", true);
EXPECT_THAT(capture_debug.str(), HasSubstr("Added new routine. Name: check routine name test"));
mainloop->run();
EXPECT_THAT(
capture_debug.str(),
HasSubstr("Starting execution of corutine. Routine named: check routine name test")
);
}