diff --git a/ConditionVariable.h b/ConditionVariable.h new file mode 100644 index 0000000..07aa8ff --- /dev/null +++ b/ConditionVariable.h @@ -0,0 +1,219 @@ +/* +* If not stated otherwise in this file or this component's LICENSE file the +* following copyright and licenses apply: +* +* Copyright 2023 Sky UK +* +* 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. +*/ + +#pragma once + +#include "Log.h" +#include +#include + +#include +#include +#include + +/** + * @brief A custom condition variable that uses the monotonic clock for all timed waits + * + * This is necessary due to a long-standing bug in GCC where wait_for uses the system clock, causing the condition + * variable to wake early if the system clock jumps (especially an issue during bootup on an embedded platform without + * a battery-backup RTC when NTP sync occurs) + * + */ +class ConditionVariable +{ +public: + ConditionVariable() + { + pthread_condattr_t attr; + pthread_condattr_init(&attr); +#if !defined(HAS_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP) + pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); +#endif + pthread_cond_init(&mCond, &attr); + + pthread_condattr_destroy(&attr); + } + + ConditionVariable(const ConditionVariable &) = delete; + + ConditionVariable(const ConditionVariable &&) = delete; + + ~ConditionVariable() + { + int err = pthread_cond_destroy(&mCond); + if (err != 0) + LOG_ERROR("Condition variable failed to be destroyed with error %d", errno); + } + +public: + void notify_one() + { + int err = pthread_cond_signal(&mCond); + if (err != 0) { + LOG_ERROR("Condition variable failed to be signaled with error %d", errno); + } + } + + void notify_all() + { + int err = pthread_cond_broadcast(&mCond); + if (err != 0) { + LOG_ERROR("Condition variable failed to broadcast with error %d", errno); + } + } + +private: + struct timespec calcTimeoutAbs(const std::chrono::nanoseconds &rel_time) + { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + + ts.tv_sec += std::chrono::duration_cast(rel_time).count(); + ts.tv_nsec += (rel_time % std::chrono::seconds(1)).count(); + + if (ts.tv_nsec >= 1000000000L) { + ts.tv_nsec -= 1000000000L; + ts.tv_sec += 1; + } else if (ts.tv_nsec < 0L) { + // This can happend when rel_time.tv_nsec is big negative, and ts.tv_nsec + // is small positive + ts.tv_nsec += 1000000000L; + ts.tv_sec -= 1; + } + + return ts; + } + + struct timespec calcTimeoutRel(const std::chrono::nanoseconds &rel_time) + { + struct timespec ts; + + ts.tv_sec = std::chrono::duration_cast(rel_time).count(); + ts.tv_nsec = (rel_time % std::chrono::seconds(1)).count(); + + return ts; + } + +public: + void wait(std::unique_lock &lock) + { + int err = pthread_cond_wait(&mCond, lock.mutex()->native_handle()); + if (err != 0) { + LOG_ERROR("Condition variable failed to wait with error %d", errno); + } + } + + template + void wait(std::unique_lock &lock, Predicate pred) + { + while (!pred()) { + int err = pthread_cond_wait(&mCond, lock.mutex()->native_handle()); + if (err != 0) { + LOG_ERROR("Condition variable failed to wait with error %d", errno); + } + } + } + + + template + std::cv_status wait_for(std::unique_lock &lock, + const std::chrono::duration &rel_time) + { + if (rel_time.count() < 0) { + LOG_DEBUG("Negative wait period, timeout occured"); + return std::cv_status::timeout; + } +#if defined(HAS_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP) + const struct timespec ts = calcTimeoutRel(std::chrono::duration_cast(rel_time)); + int err = pthread_cond_timedwait_relative_np(&mCond, lock.mutex()->native_handle(), &ts); +#else + const struct timespec ts = calcTimeoutAbs(std::chrono::duration_cast(rel_time)); + int err = pthread_cond_timedwait(&mCond, lock.mutex()->native_handle(), &ts); +#endif + if (err == 0) { + return std::cv_status::no_timeout; + } + if (err == ETIMEDOUT) { + return std::cv_status::timeout; + } else { + LOG_ERROR("Condition variable timed wait failed with error %d", errno); + return std::cv_status::timeout; + } + } + + template + bool wait_for(std::unique_lock &lock, + const std::chrono::duration &rel_time, + Predicate pred) + { + if (rel_time.count() < 0) { + LOG_DEBUG("Negative wait period, timeout occured"); + return pred(); + } +#if defined(HAS_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP) + const struct timespec ts = calcTimeoutRel(std::chrono::duration_cast(rel_time)); +#else + const struct timespec ts = calcTimeoutAbs(std::chrono::duration_cast(rel_time)); +#endif + + while (!pred()) { +#if defined(HAS_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP) + int err = pthread_cond_timedwait_relative_np(&mCond, lock.mutex()->native_handle(), &ts); +#else + int err = pthread_cond_timedwait(&mCond, lock.mutex()->native_handle(), &ts); +#endif + if (err == ETIMEDOUT) { + return pred(); + } else if (err != 0) { + LOG_ERROR("Condition variable error in wait_for '%d'", err); + } + } + + return true; + } + + + std::cv_status wait_until(std::unique_lock &lock, + const std::chrono::time_point &timeout_time) + { + auto rel_time = (timeout_time - std::chrono::steady_clock::now()); + return wait_for(lock, rel_time); + } + + template + bool wait_until(std::unique_lock &lock, + const std::chrono::time_point &timeout_time, + Predicate pred) + { + auto rel_time = (timeout_time - std::chrono::steady_clock::now()); + return wait_for(lock, rel_time, pred); + } + +public: + typedef pthread_cond_t *native_handle_type; + + native_handle_type native_handle() + { + return &mCond; + } + +private: + pthread_cond_t mCond; + +}; \ No newline at end of file diff --git a/MemoryMetric.cpp b/MemoryMetric.cpp index 6b495c3..e2e4a82 100644 --- a/MemoryMetric.cpp +++ b/MemoryMetric.cpp @@ -29,9 +29,9 @@ MemoryMetric::MemoryMetric(Platform platform, std::shared_ptraddDataset("BMEM", data); // Add all BMEM memory to accumulated total long double bmemSum = 0; - std::for_each(mBroadcomBmemMeasurements.begin(), mBroadcomBmemMeasurements.end(), [&](const Measurement &m) + std::for_each(mBroadcomBmemMeasurements.begin(), mBroadcomBmemMeasurements.end(), [&](const auto &m) { - bmemSum += m.GetAverage(); + bmemSum += m.second.GetAverage(); }); mReportGenerator->addToAccumulatedMemoryUsage(bmemSum); } @@ -380,10 +380,10 @@ void MemoryMetric::GetCmaMemoryUsage() measurement.Unused.AddDataPoint(unusedKb); } else { // New CMA region, create measurements - auto used = Measurement("Used KB"); + auto used = Measurement("Used_KB"); used.AddDataPoint(usedKb); - auto unused = Measurement("Unused KB"); + auto unused = Measurement("Unused_KB"); unused.AddDataPoint(unusedKb); auto measurement = cmaMeasurement(countKb, used, unused); @@ -460,7 +460,7 @@ void MemoryMetric::GetContainerMemoryUsage() auto &measurement = itr->second; measurement.AddDataPoint(memoryUsageKb); } else { - Measurement measurement("Memory Used KB"); + Measurement measurement("Memory_Used_KB"); measurement.AddDataPoint(memoryUsageKb); mContainerMeasurements.insert(std::make_pair(containerName, measurement)); } @@ -520,19 +520,15 @@ void MemoryMetric::GetBroadcomBmemUsage() // Use KB for consistency with everything else double usageKb = (regionSize * (regionUsage / 100.0)) * 1024; - auto itr = std::find_if(mBroadcomBmemMeasurements.begin(), mBroadcomBmemMeasurements.end(), - [&](const Measurement &m) - { - return m.GetName() == std::string(regionName); - }); + auto itr = mBroadcomBmemMeasurements.find(std::string(regionName)); if (itr == mBroadcomBmemMeasurements.end()) { // New region - Measurement measurement(regionName); + Measurement measurement("Memory_Usage_KB"); measurement.AddDataPoint(usageKb); - mBroadcomBmemMeasurements.emplace_back(measurement); + mBroadcomBmemMeasurements.insert(std::make_pair(std::string(regionName), measurement)); } else { - auto &measurement = *itr; + auto &measurement = itr->second; measurement.AddDataPoint(usageKb); } } @@ -619,10 +615,10 @@ void MemoryMetric::CalculateFragmentation() } else { std::vector measurements = {}; for (int i = 0; i < (int) freePages.size(); i++) { - Measurement fp("Free Pages"); + Measurement fp("Free_Pages"); fp.AddDataPoint(freePages[i]); - Measurement frag("Fragmentation %"); + Measurement frag("Fragmentation_%"); frag.AddDataPoint(fragmentationPercent[i]); memoryFragmentation fragMeasurement(fp, frag); measurements.emplace_back(fragMeasurement); @@ -725,7 +721,7 @@ void MemoryMetric::GetGpuMemoryUsageBroadcom() measurement.Used.AddDataPoint(virtualMemNumBytes / (long double) 1024.0); } else { Process process(pid); - Measurement used("Memory Usage KB"); + Measurement used("Memory_Usage_KB"); used.AddDataPoint(virtualMemNumBytes / (long double) 1024.0); auto measurement = gpuMeasurement(process, used); @@ -777,7 +773,7 @@ void MemoryMetric::GetGpuMemoryUsageAmlogic() } else { Process process(pid); - Measurement used("Memory Usage KB"); + Measurement used("Memory_Usage_KB"); used.AddDataPoint(gpuBytes / (long double) 1024.0); auto measurement = gpuMeasurement(process, used); diff --git a/MemoryMetric.h b/MemoryMetric.h index 3c6d805..4bd3111 100644 --- a/MemoryMetric.h +++ b/MemoryMetric.h @@ -126,7 +126,7 @@ class MemoryMetric : public IMetric std::map mGpuMeasurements; std::map mContainerMeasurements; - std::vector mBroadcomBmemMeasurements; + std::map mBroadcomBmemMeasurements; Measurement mCmaFree; Measurement mCmaBorrowed; diff --git a/Metadata.cpp b/Metadata.cpp index d3d53e2..3b252ec 100644 --- a/Metadata.cpp +++ b/Metadata.cpp @@ -90,18 +90,19 @@ std::string Metadata::Mac() const std::stringstream buffer; buffer << macFile.rdbuf(); - return buffer.str(); + std::string macStr = buffer.str(); + // Remove trailing \n + macStr.erase(std::remove(macStr.begin(), macStr.end(), '\n'), macStr.cend()); + return macStr; } std::string Metadata::ReportTimestamp() const { - auto t = std::time(nullptr); - auto tm = *std::localtime(&t); + std::time_t t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + std::stringstream timeStream; + timeStream << std::put_time( std::localtime( &t ), "%FT%T%z" ); - std::stringstream buffer; - buffer << std::put_time(&tm, "%Y-%m-%d %H:%M:%S"); - - return buffer.str(); + return timeStream.str(); } long Metadata::Duration() const diff --git a/main.cpp b/main.cpp index 11be1c1..bc86f34 100644 --- a/main.cpp +++ b/main.cpp @@ -30,6 +30,7 @@ #include "MemoryMetric.h" #include "Metadata.h" #include "GroupManager.h" +#include "ConditionVariable.h" #include "inja/inja.hpp" @@ -57,7 +58,7 @@ static bool gJson = false; bool gEnableGroups = false; static std::filesystem::path gGroupsFile; -std::condition_variable gStop; +ConditionVariable gStop; std::mutex gLock; bool gEarlyTermination = false; @@ -283,15 +284,8 @@ int main(int argc, char *argv[]) return ordered; }); - auto htmlTemplateString = std::string(g_templateHtml_data, g_templateHtml_data + g_templateHtml_size); - std::string result = env.render(htmlTemplateString, reportGenerator->getJson()); - - std::filesystem::path htmlFilepath = gOutputDirectory / "report.html"; - std::ofstream outputHtml(htmlFilepath, std::ios::trunc | std::ios::binary); - outputHtml << result; - - LOG_INFO("Saved report to %s", htmlFilepath.string().c_str()); - + // Write the JSON first - this is safer and is the report automation need, so if we crash + // after this point we'll still get some data if (gJson) { std::filesystem::path jsonFilepath = gOutputDirectory / "report.json"; std::ofstream outputJson(jsonFilepath, std::ios::trunc | std::ios::binary); @@ -300,5 +294,19 @@ int main(int argc, char *argv[]) LOG_INFO("Saved JSON data to %s", jsonFilepath.string().c_str()); } + try { + auto htmlTemplateString = std::string(g_templateHtml_data, g_templateHtml_data + g_templateHtml_size); + std::string result = env.render(htmlTemplateString, reportGenerator->getJson()); + + std::filesystem::path htmlFilepath = gOutputDirectory / "report.html"; + std::ofstream outputHtml(htmlFilepath, std::ios::trunc | std::ios::binary); + outputHtml << result; + + LOG_INFO("Saved report to %s", htmlFilepath.string().c_str()); + } catch (const std::exception& e) { + LOG_ERROR("Failed to save HTML report with exception %s", e.what()); + throw; + } + return EXIT_SUCCESS; }