Skip to content

Commit

Permalink
Use std::optional for sysctl header.
Browse files Browse the repository at this point in the history
  • Loading branch information
ryouze committed Sep 25, 2024
1 parent 3dc412e commit a31fbc6
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 91 deletions.
74 changes: 50 additions & 24 deletions src/core/sysctl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,34 @@
#pragma once

#include <cstddef> // for std::size_t
#include <stdexcept> // for std::runtime_error
#include <optional> // for std::optional
#include <string> // for std::string
#include <sys/sysctl.h> // for sysctlbyname
#include <type_traits> // for std::is_arithmetic_v

#include <fmt/core.h> // for fmt::format
#include <sys/sysctl.h> // for ::sysctl, ::sysctlbyname
#include <type_traits> // for std::is_arithmetic_v, std::is_standard_layout_v, std::is_trivial_v

namespace core::sysctl {

/**
* @brief Get the value of a sysctl variable.
* @brief Get the value of a sysctl variable of arithmetic type.
*
* Template function that handles various types of sysctl values.
* Template function that handles various arithmetic types of sysctl values.
*
* @tparam T Type of the sysctl value to retrieve (e.g., "std::uint64_t").
* @param name Name of the sysctl variable (e.g., "hw.memsize").
*
* @return Value of the sysctl variable of the specified type (e.g., '17179869184").
*
* @throws std::runtime_error If failed to get the sysctl value.
* @return Value if succeeded (e.g., '17179869184"), std::nullopt otherwise.
*/
template <typename T>
[[nodiscard]] inline T get_value(const std::string &name)
[[nodiscard]] inline std::optional<T> get_value(const std::string &name)
{
// Compile-time check for arithmetic type
static_assert(std::is_arithmetic_v<T>, "get_value() requires an arithmetic type");

T value{};
std::size_t size = sizeof(T);

if (sysctlbyname(name.c_str(), &value, &size, nullptr, 0) != 0) {
throw std::runtime_error(fmt::format("Failed to get sysctl value for '{}'", name));
if (::sysctlbyname(name.c_str(), &value, &size, nullptr, 0) != 0) {
return std::nullopt;
}

return value;
Expand All @@ -50,30 +47,59 @@ template <typename T>
*
* @param name Name of the sysctl variable (e.g., "kern.osproductversion").
*
* @return Value of the sysctl variable as a string (e.g., "14.6.1").
*
* @throws std::runtime_error If failed to get the sysctl value.
* @return Value if succeeded (e.g., "14.6.1"), std::nullopt otherwise.
*/
[[nodiscard]] inline std::string get_value(const std::string &name)
[[nodiscard]] inline std::optional<std::string> get_value(const std::string &name)
{
std::size_t size;
std::size_t size = 0;
// First call with nullptr to determine required buffer size
if (sysctlbyname(name.c_str(), nullptr, &size, nullptr, 0) != 0) {
throw std::runtime_error(fmt::format("Failed to get sysctl value size for '{}'", name));
if (::sysctlbyname(name.c_str(), nullptr, &size, nullptr, 0) != 0) {
return std::nullopt;
}

// Allocate buffer based on required size
std::string buffer(size, '\0');

// Second call to get the actual data
if (sysctlbyname(name.c_str(), buffer.data(), &size, nullptr, 0) != 0) {
throw std::runtime_error(fmt::format("Failed to get sysctl value for '{}'", name));
if (::sysctlbyname(name.c_str(), buffer.data(), &size, nullptr, 0) != 0) {
return std::nullopt;
}

// Remove null terminator from buffer
buffer.resize(size - 1);
// Remove null terminator if present
if (!buffer.empty() && buffer.back() == '\0') {
buffer.pop_back();
}

return buffer;
}

/**
* @brief Get sysctl value using MIB array.
*
* Template function that handles sysctl values accessed via MIB arrays.
*
* @tparam T Type of the sysctl value to retrieve.
* @param mib Pointer to MIB array.
* @param mib_len Length of the MIB array.
*
* @return Value if succeeded (e.g., "14"), std::nullopt otherwise.
*/
template <typename T>
[[nodiscard]] inline std::optional<T> get_value(const int *mib,
const std::size_t mib_len)
{
// Ensure that T is a POD (Plain Old Data) type
// A POD type in C++ is a type that is compatible with C-style data structures
static_assert(std::is_standard_layout_v<T> && std::is_trivial_v<T>, "get_value() requires a POD type");

T value{};
std::size_t size = sizeof(T);

if (::sysctl(const_cast<int *>(mib), static_cast<u_int>(mib_len), &value, &size, nullptr, 0) != 0) {
return std::nullopt;
}

return value;
}

} // namespace core::sysctl
14 changes: 6 additions & 8 deletions src/modules/cpu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
* @file cpu.cpp
*/

#include <stdexcept> // for std::runtime_error
#include <string> // for std::string

#include <fmt/core.h>
#include <optional> // for std::optional
#include <string> // for std::string

#include "core/sysctl.hpp"
#include "cpu.hpp"
Expand All @@ -14,11 +12,11 @@ namespace modules::cpu {

std::string get_cpu_model()
{
try {
return core::sysctl::get_value("machdep.cpu.brand_string");
if (const auto cpu_model_opt = core::sysctl::get_value("machdep.cpu.brand_string")) {
return *cpu_model_opt;
}
catch (const std::runtime_error &e) {
return fmt::format("Unknown CPU model {}", e.what());
else {
return "Unknown CPU model (Failed to get machdep.cpu.brand_string)";
}
}

Expand Down
45 changes: 21 additions & 24 deletions src/modules/host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,8 @@
#include <algorithm> // for std::remove_if
#include <array> // for std::array
#include <cctype> // for std::isspace, std::isdigit
#include <cstddef> // for std::size_t
#include <exception> // for std::exception
#include <stdexcept> // for std::runtime_error
#include <string> // for std::string, std::stoi, std::to_string
#include <sys/sysctl.h> // for sysctl
#include <sys/time.h> // for timeval, time_t, time, difftime
#include <ctime> // for std::time_t, std::time, std::difftime
#include <string> // for std::string
#include <sys/utsname.h> // for utsname, uname

#include <fmt/core.h>
Expand All @@ -33,21 +29,21 @@ namespace modules::host {

std::string get_version()
{
try {
return fmt::format("macOS {}", core::sysctl::get_value("kern.osproductversion"));
if (const auto version_opt = core::sysctl::get_value("kern.osproductversion")) {
return fmt::format("macOS {}", *version_opt);
}
catch (const std::runtime_error &e) {
return fmt::format("Unknown macOS version ({})", e.what());
else {
return "Unknown macOS version (Failed to get kern.osproductversion)";
}
}

std::string get_model_identifier()
{
try {
return core::sysctl::get_value("hw.model");
if (const auto model_opt = core::sysctl::get_value("hw.model")) {
return *model_opt;
}
catch (const std::runtime_error &e) {
return fmt::format("Unknown model identifier ({})", e.what());
else {
return "Unknown model identifier (Failed to get hw.model)";
}
}

Expand All @@ -62,18 +58,19 @@ std::string get_architecture()

std::string get_uptime()
{
struct timeval boottime;
std::size_t len = sizeof(boottime);
std::array<int, 2> mib = {CTL_KERN, KERN_BOOTTIME};
const int mib[] = {CTL_KERN, KERN_BOOTTIME};
const std::size_t mib_len = sizeof(mib) / sizeof(int);

if (sysctl(mib.data(), mib.size(), &boottime, &len, nullptr, 0) != 0) {
return "Unknown uptime (Failed to get boottime)";
const auto boottime_opt = core::sysctl::get_value<struct timeval>(mib, mib_len);
if (!boottime_opt) {
return "Unknown uptime (Failed to get kern.boottime)";
}

const time_t bsec = boottime.tv_sec;
const time_t now = time(nullptr);
const struct timeval boottime = *boottime_opt;
const std::time_t bsec = boottime.tv_sec;
const std::time_t now = std::time(nullptr);

const int seconds = static_cast<int>(difftime(now, bsec));
const int seconds = static_cast<int>(std::difftime(now, bsec));
const int days = seconds / (60 * 60 * 24);
const int hours = (seconds % (60 * 60 * 24)) / (60 * 60);
const int minutes = (seconds % (60 * 60)) / 60;
Expand All @@ -86,13 +83,13 @@ std::string get_packages()
// Attempt to get the number of packages, assuming brew is installed
if (auto output = core::shell::get_output("brew list | wc -l")) {
// Trim whitespace from the output
output->erase(std::remove_if(output->begin(), output->end(), std::isspace), output->cend());
output->erase(std::remove_if(output->begin(), output->end(), std::isspace), output->end());

// Check if the output is a valid number
if (!output->empty() && std::all_of(output->begin(), output->end(), std::isdigit)) {
try {
// Convert to integer and back to string to normalize
const int package_count = std::stoi(output.value());
const int package_count = std::stoi(*output);
return std::to_string(package_count);
}
catch (const std::exception &) {
Expand Down
70 changes: 35 additions & 35 deletions src/modules/memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
*/

#include <cstdint> // for std::uint64_t
#include <exception> // for std::exception
#include <mach/mach.h> // for mach_port_t, mach_host_self, host_page_size, vm_size_t, vm_statistics64_data_t, mach_msg_type_number_t, host_statistics64, HOST_VM_INFO64, HOST_VM_INFO64_COUNT, KERN_SUCCESS
#include <stdexcept> // for std::runtime_error
#include <mach/mach.h> // for mach_port_t, mach_host_self, vm_size_t, vm_statistics64_data_t, mach_msg_type_number_t, host_statistics64, HOST_VM_INFO64, HOST_VM_INFO64_COUNT, KERN_SUCCESS
#include <string> // for std::string

#include <fmt/core.h>
Expand All @@ -17,40 +15,42 @@ namespace modules::memory {

std::string get_memory_usage()
{
try {
// Fetch total physical memory using the sysctl abstraction
const std::uint64_t total_memory = core::sysctl::get_value<std::uint64_t>("hw.memsize");

// Get page size
const mach_port_t host_port = mach_host_self();
vm_size_t page_size;
if (host_page_size(host_port, &page_size) != KERN_SUCCESS) {
throw std::runtime_error("Failed to get page size");
}

// Fetch VM statistics
vm_statistics64_data_t vm_stats;
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;

if (host_statistics64(host_port, HOST_VM_INFO64, reinterpret_cast<host_info64_t>(&vm_stats), &count) != KERN_SUCCESS) {
throw std::runtime_error("Failed to get VM statistics");
}

// Calculate used memory: active + wired + compressed
const std::uint64_t used_memory = (vm_stats.active_count + vm_stats.wire_count + vm_stats.compressor_page_count) * page_size;

// Calculate used memory percentage
const int used_memory_percentage = static_cast<int>((used_memory * 100) / total_memory);

// Format the output as "<used_memory>GiB / <total_memory>GiB"
return fmt::format("{:.2f}GiB / {:.2f}GiB ({}%)",
used_memory / (1024.0 * 1024.0 * 1024.0),
total_memory / (1024.0 * 1024.0 * 1024.0),
used_memory_percentage);
// Fetch total physical memory using the sysctl abstraction
const auto total_memory_opt = core::sysctl::get_value<std::uint64_t>("hw.memsize");
if (!total_memory_opt) {
return "Unknown memory usage (Failed to get hw.memsize)";
}
catch (const std::exception &e) {
return fmt::format("Unknown memory usage ({})", e.what());
const std::uint64_t total_memory = *total_memory_opt;

// Get page size
const mach_port_t host_port = mach_host_self();
vm_size_t page_size = 0;
if (host_page_size(host_port, &page_size) != KERN_SUCCESS) {
return "Unknown memory usage (Failed to get page size)";
}

// Fetch VM statistics
vm_statistics64_data_t vm_stats{};
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;

if (host_statistics64(host_port, HOST_VM_INFO64, reinterpret_cast<host_info64_t>(&vm_stats), &count) != KERN_SUCCESS) {
return "Unknown memory usage (Failed to get VM statistics)";
}

// Calculate used memory: active + wired + compressed
const std::uint64_t used_memory = (static_cast<std::uint64_t>(vm_stats.active_count) +
static_cast<std::uint64_t>(vm_stats.wire_count) +
static_cast<std::uint64_t>(vm_stats.compressor_page_count)) *
page_size;

// Calculate used memory percentage
const int used_memory_percentage = static_cast<int>((used_memory * 100) / total_memory);

// Format the output as "<used_memory>GiB / <total_memory>GiB"
return fmt::format("{:.2f}GiB / {:.2f}GiB ({}%)",
used_memory / (1024.0 * 1024.0 * 1024.0),
total_memory / (1024.0 * 1024.0 * 1024.0),
used_memory_percentage);
}

} // namespace modules::memory

0 comments on commit a31fbc6

Please sign in to comment.