diff --git a/contrib/mma/README.md b/contrib/mma/README.md index 4411d1225c..30063efb17 100644 --- a/contrib/mma/README.md +++ b/contrib/mma/README.md @@ -36,9 +36,13 @@ The MachineMonitoringAgent application on Windows needs the performance counters ## Linux -Before running the Machine Monitoring Application on Linux make sure the following packages are installed: - - systat : sudo apt install sysstat - - ifstat : sudo apt install ifstat +The Machine Monitoring Application on Linux collects information via: + - /proc virtual file system + - command df + - file /etc/os-release + +These should be available on all standard Linux distributions +and even on embedded Linux distributions. ### Installation diff --git a/contrib/mma/include/linux/mma_linux.h b/contrib/mma/include/linux/mma_linux.h index 26ab5cf8b5..724006664c 100644 --- a/contrib/mma/include/linux/mma_linux.h +++ b/contrib/mma/include/linux/mma_linux.h @@ -43,21 +43,54 @@ class MMALinux : public MMAImpl **/ ~MMALinux(); - std::mutex mutex; - bool is_vrm; - int nr_of_cpu_cores; - const std::string root = "/"; - const std::string home = "/home"; - const std::string media = "/media"; - const std::string boot = "/boot"; + std::mutex mutex_; + std::string os_name_; + int nr_of_cpu_cores_; std::string cpu_pipe_result_; std::string network_pipe_result_; std::string disk_pipe_result_; std::string process_pipe_result_; - - + unsigned int cpu_pipe_count_ = 0; + unsigned int network_pipe_count_ = 0; + unsigned int disk_pipe_count_ = 0; + unsigned int process_pipe_count_ = 0; + + unsigned int cpu_prev_count_; + unsigned int net_prev_count_; + unsigned int disk_prev_count_; + unsigned int proc_prev_count_; + double cpu_prev_idle_time_; + + struct t_netIo + { + unsigned long rec; + unsigned long snd; + }; + std::unordered_map net_prev_map_; + + struct t_procItem + { + unsigned long utime{}; + unsigned int count{}; + ResourceLinux::Process process_stats; + }; + std::map proc_prev_map_; + + struct t_diskIo + { + unsigned long read; + unsigned long write; + }; + std::unordered_map disk_prev_map_; + + std::map uname_map_; + std::string root_dev_; + std::string arm_vcgencmd_; + long page_size_; + long ticks_per_second_; + /** * @brief Get machine statistics: CPU, Memory, Disk, Network * @@ -114,7 +147,7 @@ class MMALinux : public MMAImpl void SetResourceData(eCAL::pb::mma::State& state); int GetCpuCores(void); - bool CheckIfIsALinuxVRM(); + std::string GetOsName(); ResourceLinux::ProcessStatsList GetProcesses(); @@ -128,7 +161,7 @@ class MMALinux : public MMAImpl std::list TokenizeIntoLines(const std::string& str); bool SetDiskInformation(ResourceLinux::DiskStatsList& disks); bool SetDiskIOInformation(ResourceLinux::DiskStatsList& disk_stats_info); - bool MergeBootWithRootARM(ResourceLinux::DiskStatsList& disk_stats_info); + std::string GetArmvcgencmd(); std::unique_ptr cpu_pipe_; std::unique_ptr network_pipe_; diff --git a/contrib/mma/src/linux/mma_linux.cpp b/contrib/mma/src/linux/mma_linux.cpp index 07058c08c8..52fc3308f5 100644 --- a/contrib/mma/src/linux/mma_linux.cpp +++ b/contrib/mma/src/linux/mma_linux.cpp @@ -29,26 +29,36 @@ #include #include #include +#include +#include +#include #include "../include/linux/mma_linux.h" #define B_IN_KB 1024.0 #define B_IN_MB 1048576.0 +#define MMA_CPU_CMD "cat /proc/uptime" +#define MMA_NET_CMD "cat /proc/net/dev" +#define MMA_DISK_CMD "cat /proc/diskstats" +#define MMA_PS_CMD "cat /proc/[0-9]*/stat 2>/dev/null" + MMALinux::MMALinux(): - cpu_pipe_(std::make_unique("sar -u 1 1",500)), - network_pipe_(std::make_unique("ifstat 1 1",500)), - disk_pipe_(std::make_unique("iostat -N 1 2",500)), - process_pipe_(std::make_unique("ps -aux",500)) + nr_of_cpu_cores_(GetCpuCores()), + page_size_(sysconf(_SC_PAGE_SIZE)), + ticks_per_second_(sysconf(_SC_CLK_TCK)), + cpu_pipe_(std::make_unique(MMA_CPU_CMD,500)), + network_pipe_(std::make_unique(MMA_NET_CMD,500)), + disk_pipe_(std::make_unique(MMA_DISK_CMD,500)), + process_pipe_(std::make_unique(MMA_PS_CMD,500)) { cpu_pipe_->AddCallback(std::bind(&MMALinux::OnDataReceived, this, std::placeholders::_1, std::placeholders::_2)); network_pipe_->AddCallback(std::bind(&MMALinux::OnDataReceived, this, std::placeholders::_1, std::placeholders::_2)); disk_pipe_->AddCallback(std::bind(&MMALinux::OnDataReceived, this, std::placeholders::_1, std::placeholders::_2)); process_pipe_->AddCallback(std::bind(&MMALinux::OnDataReceived, this, std::placeholders::_1, std::placeholders::_2)); - is_vrm = CheckIfIsALinuxVRM(); - nr_of_cpu_cores = GetCpuCores(); - + os_name_ = GetOsName(); + arm_vcgencmd_ = GetArmvcgencmd(); } MMALinux::~MMALinux() @@ -57,15 +67,27 @@ MMALinux::~MMALinux() void MMALinux::OnDataReceived(const std::string& pipe_result, const std::string& command) { - std::lock_guard guard(mutex); - if (command == "sar -u 1 1") + const std::lock_guard guard(mutex_); + if (command == MMA_CPU_CMD) + { cpu_pipe_result_ = pipe_result; - if (command == "ifstat 1 1") + cpu_pipe_count_++; + } + else if (command == MMA_NET_CMD) + { network_pipe_result_ = pipe_result; - if (command == "iostat -N 1 2") + network_pipe_count_++; + } + else if (command == MMA_DISK_CMD) + { disk_pipe_result_ = pipe_result; - if (command == "ps -aux") + disk_pipe_count_++; + } + else if (command == MMA_PS_CMD) + { process_pipe_result_ = pipe_result; + process_pipe_count_++; + } } @@ -104,24 +126,26 @@ std::string MMALinux::FileToString(const std::string& command) double MMALinux::GetCPULoad() { std::string local_copy; + unsigned int local_count = 0; { - std::lock_guard guard(mutex); + const std::lock_guard guard(mutex_); local_copy = cpu_pipe_result_; + local_count = cpu_pipe_count_; } double cpu_time = 0.0; - auto lines = TokenizeIntoLines(local_copy); - if (lines.size() > 0) + if (local_copy.length() > 0) { - std::vector cpu_line = SplitLine(lines.back()); - double idle_time = static_cast(stod(cpu_line.back())); - - cpu_time = 100.0 - idle_time; - } - else - { - cpu_time = 0.0; + std::vector cpu_line = SplitLine(local_copy); + const double idle_time = stod(cpu_line.back()); + const unsigned int delta_count = (local_count - cpu_prev_count_) * nr_of_cpu_cores_; + const double current_idle = idle_time - cpu_prev_idle_time_; + + if (delta_count > 0) + cpu_time = 100.0 * (1 - 2 * current_idle / delta_count); + cpu_prev_idle_time_ = idle_time; + cpu_prev_count_ = local_count; } return cpu_time; @@ -148,12 +172,12 @@ ResourceLinux::Memory MMALinux::GetMemory() } if (memory_map.find("MemAvailable:") != memory_map.end()) - { + { // since Linux kernel 3.14 memory.available = memory_map["MemAvailable:"]; memory.total = memory_map["MemTotal:"]; } else - { + { // before Linux kernel 3.14 memory.available = memory_map["MemFree:"] + memory_map["Buffers:"] + memory_map["Cached:"]; memory.total = memory_map["MemTotal:"]; } @@ -164,25 +188,37 @@ ResourceLinux::Memory MMALinux::GetMemory() return memory; } +std::string MMALinux::GetArmvcgencmd() +{ + std::string result; + OpenPipe("command -v vcgencmd", result); + if (result.length() != 0) + { + if (result.back() == '\n') + result.pop_back(); + result.append(" measure_temp"); + } + return result; +} + ResourceLinux::Temperature MMALinux::GetTemperature() { ResourceLinux::Temperature temperature; -#ifdef __arm__ - - std::string result; - OpenPipe("/opt/vc/bin/vcgencmd measure_temp", result); + if (arm_vcgencmd_.length() != 0) + { + std::string result; + OpenPipe(arm_vcgencmd_, result); - std::smatch match; - const std::regex expression("\\d{2,}.\\d{1,}"); + std::smatch match; + const std::regex expression("\\d{2,}.\\d{1,}"); - if (std::regex_search(result, match, expression) && match.size() > 0) - { - temperature.cpu_temperature = stof(match.str(0)); + if (std::regex_search(result, match, expression) && !match.empty()) + { + temperature.cpu_temperature = stof(match.str(0)); + } } -#endif - return temperature; } @@ -200,9 +236,11 @@ ResourceLinux::DiskStatsList MMALinux::GetDisks() ResourceLinux::NetworkStatsList MMALinux::GetNetworks() { std::string local_copy; + unsigned int local_count = 0; { - std::lock_guard guard(mutex); + const std::lock_guard guard(mutex_); local_copy = network_pipe_result_; + local_count = network_pipe_count_; } ResourceLinux::NetworkStatsList networks; @@ -211,44 +249,68 @@ ResourceLinux::NetworkStatsList MMALinux::GetNetworks() auto lines = TokenizeIntoLines(local_copy); if (lines.size() > 0) { - std::vector network_names = SplitLine(lines.front()); + const unsigned int delta_count = local_count - net_prev_count_; // skip the first 2 lines auto iterator = lines.begin(); - std::advance(iterator, 2); - - std::vector network_io = SplitLine(*iterator); - - int kb_in_index = 0; - int kb_out_index = 1; - - size_t size = network_names.size(); - for (size_t i = 0; i < size; i++) + for(std::advance(iterator, 2); iterator != lines.end(); iterator++) { - network_stats.name = network_names[i]; - try { network_stats.receive = static_cast(stod(network_io[kb_in_index])); } - catch (...) { network_stats.receive = 0.00; } - - try { network_stats.send = static_cast(stod(network_io[kb_out_index])); } - catch (...) { network_stats.send = 0.00; } + std::vector network_io = SplitLine(*iterator); + t_netIo cur = {0, 0}; + t_netIo prev{}; + network_stats.name=network_io[0]; + if (network_stats.name == "lo:") + continue; + // remove the colon at the end + network_stats.name.pop_back(); + + try { cur.rec = stoul(network_io[1]); } + catch (...) { } + + try { cur.snd = stoul(network_io[9]); } + catch (...) { } + + if (net_prev_map_.find(network_stats.name) != net_prev_map_.end()) + { + prev = net_prev_map_[network_stats.name]; + } + else + { + prev = cur; + } - kb_in_index += 2; - kb_out_index += 2; + if (delta_count > 0) + { + network_stats.receive = (cur.rec - prev.rec) / (0.5 * delta_count); + network_stats.send = (cur.snd - prev.snd) / (0.5 * delta_count); + } + else + { + network_stats.receive = 0; + network_stats.send = 0; + } - network_stats.receive *= B_IN_KB; - network_stats.send *= B_IN_KB; - networks.push_back(network_stats); + if ( (cur.snd != 0) || + (cur.rec != 0) || + (prev.snd != 0) || + (prev.rec != 0) ) + networks.push_back(network_stats); + net_prev_map_[network_stats.name] = cur; } + net_prev_count_ = local_count; } return networks; } + ResourceLinux::ProcessStatsList MMALinux::GetProcesses() { std::string local_copy; + t_procItem item; { - std::lock_guard guard(mutex); + const std::lock_guard guard(mutex_); local_copy = process_pipe_result_; + item.count = process_pipe_count_; } ResourceLinux::ProcessStatsList processes; @@ -257,19 +319,73 @@ ResourceLinux::ProcessStatsList MMALinux::GetProcesses() auto lines = TokenizeIntoLines(local_copy); if (lines.size() > 0) { - // skip the first 1 lines - auto iterator = lines.begin(); - std::advance(iterator, 1); - while(iterator!=lines.end()) + for(auto& line : lines) { - std::vector process_data = SplitLine(*iterator); - process_stats.id=std::stoi(process_data[1]); - process_stats.user=process_data[0]; - process_stats.memory.current_used_memory=stoull(process_data[5]) * 1024LL; - process_stats.commandline=process_data[10]; - process_stats.cpu_load.cpu_load=std::stof(process_data[2]); - processes.push_back(process_stats); - std::advance(iterator, 1); + std::vector process_data = SplitLine(line); + process_stats.id=std::stoi(process_data[0]); + // process utime + const unsigned long cur = std::stoul(process_data[13]); + unsigned long prev = 0; + if (proc_prev_map_.find(process_stats.id) != proc_prev_map_.end()) + { + prev = proc_prev_map_[process_stats.id].utime; + process_stats = proc_prev_map_[process_stats.id].process_stats; + } + else + { + prev = cur; + struct stat info{}; + const std::string filename = "/proc/" + process_data[0]; + if (0 == stat(filename.c_str(), &info)) + { + if (uname_map_.find(info.st_uid) == uname_map_.end()) + { + struct passwd *pw = getpwuid(info.st_uid); + if (pw != nullptr) + { + process_stats.user=pw->pw_name; + uname_map_[info.st_uid] = pw->pw_name; + } + else + { + process_stats.user = ""; + } + } + else + { + process_stats.user = uname_map_[info.st_uid]; + } + } + } + + process_stats.memory.current_used_memory=stoull(process_data[23]) * page_size_; + if (process_data[1].length() > 2) + process_stats.commandline=process_data[1].substr(1, process_data[1].length()-2); + else + process_stats.commandline = process_data[1]; + + const unsigned int delta_count = (item.count - proc_prev_count_) * ticks_per_second_ * nr_of_cpu_cores_; + + if (delta_count > 0) + process_stats.cpu_load.cpu_load = (cur - prev) / (0.005 * delta_count); + item.utime = cur; + item.process_stats = process_stats; + proc_prev_map_[process_stats.id] = item; + } + proc_prev_count_ = item.count; + + // garbage collection + for (auto it = proc_prev_map_.begin(), next = it; it != proc_prev_map_.end(); it = next) + { + next++; + if (it->second.count < item.count) + { // process disappeared + proc_prev_map_.erase(it); + } + else + { // push to sorted list + processes.push_back(it->second.process_stats); + } } } return processes; @@ -315,16 +431,42 @@ bool MMALinux::FormatListData(std::vector& the_list) iterator = the_list.begin(); std::string name = *iterator; + // /dev/root needs special handling + if (name == "/dev/root") + { + if (root_dev_.empty()) + { + std::string result; + OpenPipe("cat /proc/cmdline", result); + std::size_t found = result.find("root="); + result = result.substr(found + 5); + found = result.find(' '); + root_dev_ = result.substr(0, found); + } + name = root_dev_; + } + // if last character of string is digit, delete the digit + // because it is the partition number if (IsDigit(name.back())) { - name.pop_back(); + // exception: mtdblock + if (name.find("mtdblock") == std::string::npos) + { + name.pop_back(); + // delete last character p as well + if (name.back() == 'p') + name.pop_back(); + } } *iterator = name; // format the path to include just the name std::size_t found = name.find_last_of("/"); + // only accept devices which are indicated by the / in name + if (found == std::string::npos) + return_value = false; *iterator = name.substr(found + 1); } else @@ -353,13 +495,13 @@ std::list MMALinux::TokenizeIntoLines(const std::string& str) bool MMALinux::SetDiskInformation(ResourceLinux::DiskStatsList& disks) { - bool return_value = false; + const bool return_value = true; std::string result; - OpenPipe("df -B1", result); + OpenPipe("df", result); - // partitions for disks - std::map> partition_map; + // disks sorted in map + std::map disks_map; auto lines = TokenizeIntoLines(result); @@ -375,113 +517,106 @@ bool MMALinux::SetDiskInformation(ResourceLinux::DiskStatsList& disks) { if (partition.size() == 4) { - if ((partition.back() == root) || (partition.back() == home) || (partition.back().find(media) != std::string::npos) || (partition.back() == boot)) + const uint64_t KiB = 1024; + ResourceLinux::DiskStats disk_stats; + const uint64_t used = stoll(partition[1]) * KiB; + disk_stats.name = partition[0]; + disk_stats.available = stoll(partition[2]) * KiB; + disk_stats.capacity = disk_stats.available + used; + disk_stats.mount_point = partition[3]; + // ensure initialization of transfer rates + disk_stats.read = 0.0; + disk_stats.write = 0.0; + if (disks_map.find(partition[0]) == disks_map.end()) { - partition_map[partition.back()] = partition; + disks_map[partition[0]] = disk_stats; + } + else + { + // sum up partitions of same disk + disks_map[partition[0]].available += disk_stats.available; + disks_map[partition[0]].capacity += disk_stats.capacity; } - } - else - { - return_value = true; } } } - for (const auto& partition : partition_map) + for (const auto& disk : disks_map) { - ResourceLinux::DiskStats disk_stats; - disk_stats.name = partition.second[0]; - uint64_t used = stoll(partition.second[1]); - disk_stats.available = stoll(partition.second[2]); - disk_stats.capacity = disk_stats.available + used; - disk_stats.mount_point = partition.second[3]; - - disks.push_back(disk_stats); + disks.push_back(disk.second); } -#ifdef __arm__ - return_value |= MergeBootWithRootARM(disks); -#endif - return return_value; } bool MMALinux::SetDiskIOInformation(ResourceLinux::DiskStatsList& disk_stats_info) { std::string local_copy; + unsigned int local_count = 0; { - std::lock_guard guard(mutex); + const std::lock_guard guard(mutex_); local_copy = disk_pipe_result_; + local_count = disk_pipe_count_; } bool return_value = true; + const unsigned int delta_count = local_count - disk_prev_count_; auto lines = TokenizeIntoLines(local_copy); - auto it = lines.begin(); - std::advance(it, (lines.size() / 2) + 4); - lines.erase(lines.begin(), it); - for (const auto& line : lines) { std::vector partition = SplitLine(line); for (auto& disk : disk_stats_info) { - if (disk.name.find(partition[0]) != std::string::npos) + if (disk.name.find(partition[2]) != std::string::npos) { - try { disk.read = static_cast(stod(partition[2])); } - catch (...) { disk.read = 0.00; } + disk.mount_point = ""; - try { disk.write = static_cast(stod(partition[3])); } - catch (...) { disk.write = 0.00; } + t_diskIo cur = {0, 0}; + t_diskIo prev{}; + try { cur.read = stod(partition[5]); } + catch (...) { } - disk.read *= B_IN_KB; - disk.write *= B_IN_KB; - } - } - } + try { cur.write = stod(partition[9]); } + catch (...) { } - return return_value; -} + if (disk_prev_map_.find(disk.name) != disk_prev_map_.end()) + { + prev = disk_prev_map_[disk.name]; + } + else + { + prev = cur; + } -#ifdef __arm__ -bool MMALinux::MergeBootWithRootARM(ResourceLinux::DiskStatsList& disk_stats_info) -#else -bool MMALinux::MergeBootWithRootARM(ResourceLinux::DiskStatsList& /*disk_stats_info*/) -#endif -{ - bool return_val = false; -#ifdef __arm__ - ResourceLinux::DiskStats disk_boot; - for (const auto& disk : disk_stats_info) - { - if (disk.mount_point == boot) - { - disk_boot.name = disk.name; - break; + if (delta_count > 0) + { // the factor 4 is a 2 * 2 because: + // 2 reads every second and + // value is in sectors of 512 bytes + disk.read = (cur.read - prev.read) * 4 * B_IN_KB / delta_count; + disk.write = (cur.write - prev.write) * 4 * B_IN_KB / delta_count; + } + + disk_prev_map_[disk.name] = cur; + } } } + disk_prev_count_ = local_count; - for (auto& disk_st : disk_stats_info) + // remove unused disks + for (auto it = disk_stats_info.begin(), next = it; it != disk_stats_info.end(); it = next) { - if (disk_st.mount_point == root) - { - disk_st.name = disk_boot.name; - break; + next++; + if (!it->mount_point.empty()) + { // disk without statistics + disk_stats_info.erase(it); } } - for (auto it = disk_stats_info.begin(); it != disk_stats_info.end();) - { - if (it->mount_point == boot) - it = disk_stats_info.erase(it); - else - ++it; - } -#endif - return return_val; + return return_value; } int MMALinux::GetCpuCores(void) @@ -489,31 +624,22 @@ int MMALinux::GetCpuCores(void) return get_nprocs(); } -bool MMALinux::CheckIfIsALinuxVRM() +std::string MMALinux::GetOsName() { - std::string result; - std::lock_guard guard(mutex); - OpenPipe("dmesg",result); - std::list dmesg_command_result = TokenizeIntoLines(result); - for(auto iter1:dmesg_command_result) - { - auto temp_vector=SplitLine((iter1)); - for(auto iter2:temp_vector) - { - if(iter2.find("VMware")!= std::string::npos) - { - return true; - } - } - } - return false; + std::string os_release = FileToString("/etc/os-release"); + std::size_t found = os_release.find("PRETTY_NAME=\""); + os_release = os_release.substr(found + 13); + found = os_release.find('\"'); + std::string name = "LINUX "; + name.append(os_release.substr(0, found)); + return name; } void MMALinux::SetResourceData(eCAL::pb::mma::State& state) { state.set_cpu_load(GetCPULoad()); - state.set_number_of_cpu_cores(nr_of_cpu_cores); - state.set_operating_system(is_vrm ? "LINUX VRM": "LINUX NATIVE"); + state.set_number_of_cpu_cores(nr_of_cpu_cores_); + state.set_operating_system(os_name_); eCAL::pb::mma::Memory memory; ResourceLinux::Memory mem = GetMemory();