// 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 "cpu.h" #include #include #include #include "debug.h" #include "log_generator.h" using namespace std; USE_DEBUG_FLAG(D_MONITORING); static const int micro_seconds_in_second = 1000000; CPUCalculator::CPUCalculator() : Component("CPUCalculator") { last_cpu_process_time = chrono::microseconds(0); last_cpu_general_time = chrono::microseconds(0); last_cpu_general_time_active = 0; i_time_get = nullptr; } void CPUCalculator::init() { i_time_get = Singleton::Consume::by(); } void CPUCalculator::fini() { i_time_get = nullptr; } // LCOV_EXCL_START Reason: Compilation server dependency double CPUCalculator::GetGeneralCPUActiveTime(const cpu_data_array &cpu_data) { double current_time_active = cpu_data[CPUCalculator::CPUGeneralDataEntryType::USER] + cpu_data[CPUCalculator::CPUGeneralDataEntryType::NICE] + cpu_data[CPUCalculator::CPUGeneralDataEntryType::SYS] + cpu_data[CPUCalculator::CPUGeneralDataEntryType::IRQ] + cpu_data[CPUCalculator::CPUGeneralDataEntryType::SOFTIRQ] + cpu_data[CPUCalculator::CPUGeneralDataEntryType::STEAL] + cpu_data[CPUCalculator::CPUGeneralDataEntryType::GUEST] + cpu_data[CPUCalculator::CPUGeneralDataEntryType::GUEST_NICE]; return (current_time_active - last_cpu_general_time_active); } Maybe CPUCalculator::getGeneralCPUData() { static const string cpu_data_file = "/proc/stat"; ifstream fileStat(cpu_data_file); string line; static const int max_lines_nead_to_read = 9; int lines_count = 0; while (lines_count < max_lines_nead_to_read && getline(fileStat, line)) { lines_count++; static const string cpu_str = "cpu"; if (line.compare(0, cpu_str.size(), cpu_str)) continue; istringstream iss(line); string ignore; iss >> ignore; cpu_data_array tmp_cpu_data; for (CPUGeneralDataEntryType cpu_type : NGEN::Range() ) { string entry; iss >> entry; tmp_cpu_data[cpu_type] = atof(entry.c_str()); } return tmp_cpu_data; } return genError("Could not fill general cpu data array."); } Maybe CPUCalculator::getCurrentGeneralCPUUsage() { Maybe current_cpu_data = getGeneralCPUData(); if (!current_cpu_data.ok()) return genError(current_cpu_data.getErr()); if (last_cpu_general_time == chrono::microseconds(0)) { last_cpu_general_time = i_time_get->getMonotonicTime(); last_cpu_general_time_active = GetGeneralCPUActiveTime(current_cpu_data.unpack()); return 0; } auto current_time = i_time_get->getMonotonicTime(); auto elapsed_time = current_time - last_cpu_general_time; double cpu_usage_active_time = GetGeneralCPUActiveTime(current_cpu_data.unpack()); double elapsed_time_count = static_cast(elapsed_time.count()); double general_cpu_perc = cpu_usage_active_time/elapsed_time_count; last_cpu_general_time = current_time; last_cpu_general_time_active += cpu_usage_active_time; return general_cpu_perc * 100; } double CPUCalculator::getCurrentProcessCPUUsage() { struct rusage usage; if (last_cpu_process_time == chrono::microseconds(0)) { last_cpu_process_time = i_time_get->getMonotonicTime(); getrusage (RUSAGE_SELF, &usage); last_cpu_usage_time_in_user_mod = usage.ru_utime; last_cpu_usage_time_in_kernel = usage.ru_stime; return 0; } auto current_time = i_time_get->getMonotonicTime(); auto elapsed_time = current_time - last_cpu_process_time; getrusage(RUSAGE_SELF, &usage); chrono::microseconds cpu_usage_time_in_user_mod = calcTimeDiff(usage.ru_utime, last_cpu_usage_time_in_user_mod); chrono::microseconds cpu_usage_time_in_kernel = calcTimeDiff(usage.ru_stime, last_cpu_usage_time_in_kernel); double general_cpu_time = static_cast(cpu_usage_time_in_kernel.count() + cpu_usage_time_in_user_mod.count()); double elapsed_time_count = static_cast(elapsed_time.count()); double general_cpu_perc = general_cpu_time/elapsed_time_count; last_cpu_process_time = current_time; last_cpu_usage_time_in_user_mod = usage.ru_utime; last_cpu_usage_time_in_kernel = usage.ru_stime; return general_cpu_perc * 100; } chrono::microseconds CPUCalculator::calcTimeDiff(const timeval ¤t_cpu_time, const timeval &last_cpu_time) const { auto diff_in_usec = current_cpu_time.tv_usec - last_cpu_time.tv_usec; auto diff_in_sec = current_cpu_time.tv_sec - last_cpu_time.tv_sec; if (diff_in_usec < 0) { diff_in_usec += micro_seconds_in_second; diff_in_sec -= 1; } return static_cast(diff_in_sec * micro_seconds_in_second + diff_in_usec); } // LCOV_EXCL_STOP void CPUManager::loadCPUConfig() { high_watermark = getConfigurationWithDefault(85, "CPU", "high watermark"); low_watermark = getConfigurationWithDefault(60, "CPU", "low watermark"); watermark_period = chrono::seconds(getConfigurationWithDefault(30, "CPU", "watermark period")); sampling_interval = chrono::seconds(getConfigurationWithDefault(5, "CPU", "sampling interval")); debug_period = chrono::seconds(getConfigurationWithDefault(30, "CPU", "debug period")); metric_report_interval = chrono::seconds( getConfigurationWithDefault(600, "CPU", "metric reporting interval") ); failopen_counter = watermark_period/sampling_interval; } void CPUManager::init() { loadCPUConfig(); i_mainloop = Singleton::Consume::by(); i_time_get = Singleton::Consume::by(); i_cpu = Singleton::Consume::by(); i_env = Singleton::Consume::by(); current_counter = 0; is_failopen_mode = false; i_env->registerValue("Failopen Status", is_failopen_mode); cpu_process_metric.init( "CPU process usage", ReportIS::AudienceTeam::AGENT_CORE, ReportIS::IssuingEngine::AGENT_CORE, metric_report_interval, true ); cpu_process_metric.registerListener(); if (Singleton::exists()) { auto name = Singleton::Consume::by()->get("Service Name"); string orch_service_name = getConfigurationWithDefault( "Orchestration", "orchestration", "Service name" ); if (name.ok() && *name == orch_service_name) { cpu_general_metric.init( "CPU general usage", ReportIS::AudienceTeam::AGENT_CORE, ReportIS::IssuingEngine::AGENT_CORE, metric_report_interval, false ); cpu_general_metric.registerContext("Service Name", "all"); cpu_general_metric.registerListener(); } } i_mainloop->addOneTimeRoutine( I_MainLoop::RoutineType::Timer, [this]() { checkCPUStatus(); }, "CPU manager status check", false ); } bool CPUManager::isFailOpenMode() const { return is_failopen_mode; } void CPUManager::preload() { registerExpectedConfiguration("CPU", "high watermark"); registerExpectedConfiguration("CPU", "low watermark"); registerExpectedConfiguration("CPU", "watermark period"); registerExpectedConfiguration("CPU", "sampling interval"); registerExpectedConfiguration("CPU", "metric reporting interval"); registerExpectedConfiguration("CPU", "debug period"); registerExpectedConfiguration("orchestration", "Service name"); } bool CPUManager::isCPUAboveHighWatermark(double current_cpu) const { return current_cpu > high_watermark && current_counter >= 0 && current_counter < failopen_counter; } bool CPUManager::isCPUUnderHighWatermark(double current_cpu) const { return current_cpu < high_watermark && current_counter > 0 && !is_failopen_mode; } bool CPUManager::isCPUUnderLowWatermark(double current_cpu) const { return current_cpu <= low_watermark && is_failopen_mode; } void CPUManager::checkCPUStatus() { while (true) { loadCPUConfig(); auto is_orchestrator = Singleton::Consume::by()->get("Is Orchestrator"); if (is_orchestrator.ok() && is_orchestrator.unpack()) { Maybe current_general_cpu = i_cpu->getCurrentGeneralCPUUsage(); if (!current_general_cpu.ok()) { dbgWarning(D_MONITORING) << current_general_cpu.getErr(); } else { CPUEvent(current_general_cpu.unpack(), true).notify(); } } auto current_process_cpu = i_cpu->getCurrentProcessCPUUsage(); dbgTrace(D_MONITORING) << "Current process CPU usage: " << current_process_cpu; CPUEvent(current_process_cpu, false).notify(); if (isCPUAboveHighWatermark(current_process_cpu)) { current_counter++; } else { if (isCPUUnderHighWatermark(current_process_cpu)) { current_counter=0; } else { if (isCPUUnderLowWatermark(current_process_cpu)) { current_counter--; } } } if (current_counter == failopen_counter && !is_failopen_mode) { is_failopen_mode = true; i_env->registerValue("Failopen Status", is_failopen_mode); failopen_mode_event.setFailopenMode(is_failopen_mode); failopen_mode_event.notify(); dbgInfo(D_MONITORING) << "Failopen mode is ON, CPU usage is above " << high_watermark << "% for " << watermark_period.count() << " seconds"; if (debug_period == chrono::seconds::zero()) { dbgInfo(D_MONITORING) << "Debug period for Failopen mode is zero seconds"; } else { Debug::failOpenDebugMode(debug_period); } } if (current_counter == 0 && is_failopen_mode) { is_failopen_mode = false; i_env->registerValue("Failopen Status", is_failopen_mode); failopen_mode_event.setFailopenMode(is_failopen_mode); failopen_mode_event.notify(); dbgInfo(D_MONITORING) << "Failopen mode is OFF, CPU usage is below " << low_watermark << "% for " << watermark_period.count() << " seconds"; } i_mainloop->yield(sampling_interval); } }