Skip to content

Commit

Permalink
Merge pull request #3465 from canonical/parallelize-info-intra-vm
Browse files Browse the repository at this point in the history
Parallelize info intra vm
  • Loading branch information
Chris Townsend authored Apr 26, 2024
2 parents 7473d6b + ebb3b0a commit 84878a6
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 42 deletions.
1 change: 1 addition & 0 deletions include/multipass/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ class Utils : public Singleton<Utils>
virtual std::vector<uint8_t> random_bytes(size_t len);
virtual QString make_uuid(const std::optional<std::string>& seed = std::nullopt) const;
virtual void sleep_for(const std::chrono::milliseconds& ms) const;
virtual bool is_ipv4_valid(const std::string& ipv4) const;
};
} // namespace multipass

Expand Down
1 change: 1 addition & 0 deletions src/daemon/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ add_library(daemon STATIC
daemon_rpc.cpp
default_vm_image_vault.cpp
instance_settings_handler.cpp
runtime_instance_info_helper.cpp
snapshot_settings_handler.cpp
ubuntu_image_host.cpp)

Expand Down
48 changes: 7 additions & 41 deletions src/daemon/daemon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "daemon.h"
#include "base_cloud_init_config.h"
#include "instance_settings_handler.h"
#include "runtime_instance_info_helper.h"
#include "snapshot_settings_handler.h"

#include <multipass/alias_definition.h>
Expand Down Expand Up @@ -1030,20 +1031,6 @@ std::string generate_unused_mac_address(std::unordered_set<std::string>& s)
max_tries, s.size())};
}

bool is_ipv4_valid(const std::string& ipv4)
{
try
{
(mp::IPAddress(ipv4));
}
catch (std::invalid_argument&)
{
return false;
}

return true;
}

struct SnapshotPick
{
std::unordered_set<std::string> pick;
Expand Down Expand Up @@ -1802,7 +1789,7 @@ try // clang-format on
std::string management_ip = vm.management_ipv4();
auto all_ipv4 = vm.get_all_ipv4();

if (is_ipv4_valid(management_ip))
if (MP_UTILS.is_ipv4_valid(management_ip))
entry->add_ipv4(management_ip);
else if (all_ipv4.empty())
entry->add_ipv4("N/A");
Expand Down Expand Up @@ -3522,32 +3509,11 @@ void mp::Daemon::populate_instance_info(VirtualMachine& vm,
timestamp->set_nanos(created_time.time().msec() * 1'000'000);

if (!no_runtime_info && MP_UTILS.is_running(present_state))
{
instance_info->set_load(vm.ssh_exec("cat /proc/loadavg | cut -d ' ' -f1-3"));
instance_info->set_memory_usage(vm.ssh_exec("free -b | grep 'Mem:' | awk '{printf $3}'"));
info->set_memory_total(vm.ssh_exec("free -b | grep 'Mem:' | awk '{printf $2}'"));
instance_info->set_disk_usage(vm.ssh_exec("df -t ext4 -t vfat --total -B1 --output=used | tail -n 1"));
info->set_disk_total(vm.ssh_exec("df -t ext4 -t vfat --total -B1 --output=size | tail -n 1"));
info->set_cpu_count(vm.ssh_exec("nproc"));

std::string management_ip = vm.management_ipv4();
auto all_ipv4 = vm.get_all_ipv4();

if (is_ipv4_valid(management_ip))
instance_info->add_ipv4(management_ip);
else if (all_ipv4.empty())
instance_info->add_ipv4("N/A");

for (const auto& extra_ipv4 : all_ipv4)
if (extra_ipv4 != management_ip)
instance_info->add_ipv4(extra_ipv4);

auto current_release = vm.ssh_exec("cat /etc/os-release | grep 'PRETTY_NAME' | cut -d \\\" -f2");
instance_info->set_current_release(!current_release.empty() ? current_release : original_release);

instance_info->set_cpu_times(vm.ssh_exec("head -n1 /proc/stat"));
instance_info->set_uptime(vm.ssh_exec("uptime -p | tail -c+4"));
}
RuntimeInstanceInfoHelper::populate_runtime_info(vm,
info,
instance_info,
original_release,
vm_specs.num_cores == 1);
}

bool mp::Daemon::is_bridged(const std::string& instance_name)
Expand Down
107 changes: 107 additions & 0 deletions src/daemon/runtime_instance_info_helper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright (C) Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

#include "runtime_instance_info_helper.h"

#include <multipass/format.h>
#include <multipass/rpc/multipass.grpc.pb.h>
#include <multipass/utils.h>
#include <multipass/virtual_machine.h>

#include <yaml-cpp/yaml.h>

#include <array>

namespace mp = multipass;

namespace
{

struct Keys
{
public:
static constexpr auto loadavg_key = "loadavg";
static constexpr auto mem_usage_key = "mem_usage";
static constexpr auto mem_total_key = "mem_total";
static constexpr auto disk_usage_key = "disk_usage";
static constexpr auto disk_total_key = "disk_total";
static constexpr auto cpus_key = "cpus";
static constexpr auto current_release_key = "current_release";
};

struct Cmds
{
private:
static constexpr auto key_val_cmd = R"(echo {}: \"$(eval "{}")\")";
static constexpr std::array key_cmds_pairs{
std::pair{Keys::loadavg_key, "cat /proc/loadavg | cut -d ' ' -f1-3"},
std::pair{Keys::mem_usage_key, R"(free -b | grep 'Mem:' | awk '{printf \$3}')"},
std::pair{Keys::mem_total_key, R"(free -b | grep 'Mem:' | awk '{printf \$2}')"},
std::pair{Keys::disk_usage_key, "df -t ext4 -t vfat --total -B1 --output=used | tail -n 1"},
std::pair{Keys::disk_total_key, "df -t ext4 -t vfat --total -B1 --output=size | tail -n 1"},
std::pair{Keys::cpus_key, "nproc"},
std::pair{Keys::current_release_key, R"(cat /etc/os-release | grep 'PRETTY_NAME' | cut -d \\\" -f2)"}};

inline static const std::array cmds = [] {
constexpr auto n = key_cmds_pairs.size();
std::array<std::string, key_cmds_pairs.size()> ret;
for (std::size_t i = 0; i < n; ++i)
{
const auto [key, cmd] = key_cmds_pairs[i];
ret[i] = fmt::format(key_val_cmd, key, cmd);
}

return ret;
}();

public:
inline static const std::string sequential_composite_cmd = fmt::to_string(fmt::join(cmds, "; "));
inline static const std::string parallel_composite_cmd = fmt::format("{} & wait", fmt::join(cmds, "& "));
};
} // namespace

void mp::RuntimeInstanceInfoHelper::populate_runtime_info(mp::VirtualMachine& vm,
mp::DetailedInfoItem* info,
mp::InstanceDetails* instance_info,
const std::string& original_release,
bool parallelize)
{
const auto& cmd = parallelize ? Cmds::parallel_composite_cmd : Cmds::sequential_composite_cmd;
auto results = YAML::Load(vm.ssh_exec(cmd));

instance_info->set_load(results[Keys::loadavg_key].as<std::string>());
instance_info->set_memory_usage(results[Keys::mem_usage_key].as<std::string>());
info->set_memory_total(results[Keys::mem_total_key].as<std::string>());
instance_info->set_disk_usage(results[Keys::disk_usage_key].as<std::string>());
info->set_disk_total(results[Keys::disk_total_key].as<std::string>());
info->set_cpu_count(results[Keys::cpus_key].as<std::string>());

auto current_release = results[Keys::current_release_key].as<std::string>();
instance_info->set_current_release(!current_release.empty() ? current_release : original_release);

std::string management_ip = vm.management_ipv4();
auto all_ipv4 = vm.get_all_ipv4();

if (MP_UTILS.is_ipv4_valid(management_ip))
instance_info->add_ipv4(management_ip);
else if (all_ipv4.empty())
instance_info->add_ipv4("N/A");

for (const auto& extra_ipv4 : all_ipv4)
if (extra_ipv4 != management_ip)
instance_info->add_ipv4(extra_ipv4);
}
41 changes: 41 additions & 0 deletions src/daemon/runtime_instance_info_helper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (C) Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

#ifndef MULTIPASS_RUNTIME_INSTANCE_INFO_HELPER_H
#define MULTIPASS_RUNTIME_INSTANCE_INFO_HELPER_H

#include <string>

namespace multipass
{
class VirtualMachine;
class DetailedInfoItem;
class InstanceDetails;

// Note: we could extract other code to info/list populating code here, but that is left as a future improvement
struct RuntimeInstanceInfoHelper
{
static void populate_runtime_info(VirtualMachine& vm,
DetailedInfoItem* info,
InstanceDetails* instance_info,
const std::string& original_release,
bool parallelize);
};

} // namespace multipass

#endif // MULTIPASS_RUNTIME_INSTANCE_INFO_HELPER_H
16 changes: 15 additions & 1 deletion src/utils/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,21 @@ mp::Path mp::Utils::derive_instances_dir(const mp::Path& data_dir,
return QDir(QDir(data_dir).filePath(backend_directory_name)).filePath(instances_subdir);
}

void multipass::Utils::sleep_for(const std::chrono::milliseconds& ms) const
void mp::Utils::sleep_for(const std::chrono::milliseconds& ms) const
{
std::this_thread::sleep_for(ms);
}

bool mp::Utils::is_ipv4_valid(const std::string& ipv4) const
{
try
{
(mp::IPAddress(ipv4));
}
catch (std::invalid_argument&)
{
return false;
}

return true;
}
1 change: 1 addition & 0 deletions tests/mock_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class MockUtils : public Utils
MOCK_METHOD(std::string, run_in_ssh_session, (SSHSession & session, const std::string& cmd), (const, override));
MOCK_METHOD(QString, make_uuid, (const std::optional<std::string>&), (const, override));
MOCK_METHOD(void, sleep_for, (const std::chrono::milliseconds&), (const, override));
MOCK_METHOD(bool, is_ipv4_valid, (const std::string& ipv4), (const, override));

MP_MOCK_SINGLETON_BOILERPLATE(MockUtils, Utils);
};
Expand Down

0 comments on commit 84878a6

Please sign in to comment.