From b61c12e9e68885b61ab3968c0797caab8727dd82 Mon Sep 17 00:00:00 2001 From: "Jonathan M. Henson" Date: Tue, 17 Oct 2023 13:50:29 -0700 Subject: [PATCH] Add an environment loader API and implementation for some DMI related fields (#1063) Co-authored-by: Michael Graeb --- CMakeLists.txt | 9 +++ bin/system_info/CMakeLists.txt | 18 +++++ bin/system_info/print_system_info.c | 48 +++++++++++ include/aws/common/private/system_info_priv.h | 37 +++++++++ include/aws/common/system_info.h | 47 +++++++++++ source/linux/system_info.c | 24 ++++++ source/platform_fallback_stubs/system_info.c | 21 +++++ source/system_info.c | 80 +++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/system_info_tests.c | 19 +++++ 10 files changed, 304 insertions(+) create mode 100644 bin/system_info/CMakeLists.txt create mode 100644 bin/system_info/print_system_info.c create mode 100644 include/aws/common/private/system_info_priv.h create mode 100644 source/linux/system_info.c create mode 100644 source/platform_fallback_stubs/system_info.c create mode 100644 source/system_info.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 8375ea518..0ff1d9e7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,6 +70,7 @@ if (WIN32) file(GLOB AWS_COMMON_OS_SRC "source/windows/*.c" + "source/platform_fallback_stubs/system_info.c" ) if (MSVC) @@ -108,19 +109,26 @@ else () # Don't add the exact path to CoreFoundation as this would hardcode the SDK version list(APPEND PLATFORM_LIBS dl Threads::Threads "-framework CoreFoundation") list (APPEND AWS_COMMON_OS_SRC "source/darwin/*.c") # OS specific includes + list (APPEND AWS_COMMON_OS_SRC "source/platform_fallback_stubs/system_info.c") elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") # Android does not link to libpthread nor librt, so this is fine list(APPEND PLATFORM_LIBS dl m Threads::Threads rt) list (APPEND AWS_COMMON_OS_SRC "source/linux/*.c") # OS specific includes elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") list(APPEND PLATFORM_LIBS dl m thr execinfo) + list (APPEND AWS_COMMON_OS_SRC "source/platform_fallback_stubs/system_info.c") elseif(CMAKE_SYSTEM_NAME STREQUAL "NetBSD") list(APPEND PLATFORM_LIBS dl m Threads::Threads execinfo) + list (APPEND AWS_COMMON_OS_SRC "source/platform_fallback_stubs/system_info.c") elseif(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") list(APPEND PLATFORM_LIBS m Threads::Threads execinfo) + list (APPEND AWS_COMMON_OS_SRC "source/platform_fallback_stubs/system_info.c") elseif(CMAKE_SYSTEM_NAME STREQUAL "Android") list(APPEND PLATFORM_LIBS log) file(GLOB ANDROID_SRC "source/android/*.c") list(APPEND AWS_COMMON_OS_SRC "${ANDROID_SRC}") + list (APPEND AWS_COMMON_OS_SRC "source/platform_fallback_stubs/system_info.c") + else() + list (APPEND AWS_COMMON_OS_SRC "source/platform_fallback_stubs/system_info.c") endif() endif() @@ -298,6 +306,7 @@ configure_file(${CONFIG_HEADER_TEMPLATE} if (ALLOW_CROSS_COMPILED_TESTS OR NOT CMAKE_CROSSCOMPILING) if (BUILD_TESTING) add_subdirectory(tests) + add_subdirectory(bin/system_info) endif() endif() diff --git a/bin/system_info/CMakeLists.txt b/bin/system_info/CMakeLists.txt new file mode 100644 index 000000000..cb4ca0081 --- /dev/null +++ b/bin/system_info/CMakeLists.txt @@ -0,0 +1,18 @@ +project(print-sys-info C) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_INSTALL_PREFIX}/lib/cmake") + +file(GLOB SI_SRC + "*.c" + ) + +set(SI_PROJECT_NAME print-sys-info) +add_executable(${SI_PROJECT_NAME} ${SI_SRC}) +aws_set_common_properties(${SI_PROJECT_NAME}) + + +target_include_directories(${SI_PROJECT_NAME} PUBLIC + $ + $) + +target_link_libraries(${SI_PROJECT_NAME} PRIVATE aws-c-common) diff --git a/bin/system_info/print_system_info.c b/bin/system_info/print_system_info.c new file mode 100644 index 000000000..c29877086 --- /dev/null +++ b/bin/system_info/print_system_info.c @@ -0,0 +1,48 @@ + + +#include +#include +#include + +int main(void) { + struct aws_allocator *allocator = aws_default_allocator(); + aws_common_library_init(allocator); + struct aws_logger_standard_options options = { + .file = stderr, + .level = AWS_LOG_LEVEL_TRACE, + }; + + struct aws_logger logger; + aws_logger_init_standard(&logger, allocator, &options); + aws_logger_set(&logger); + + struct aws_system_environment *env = aws_system_environment_load(allocator); + + fprintf(stdout, "crt-detected env: {\n"); + + struct aws_byte_cursor virtualization_vendor = aws_system_environment_get_virtualization_vendor(env); + fprintf( + stdout, + " 'virtualization vendor': '" PRInSTR "',\n", + (int)virtualization_vendor.len, + virtualization_vendor.ptr); + struct aws_byte_cursor product_name = aws_system_environment_get_virtualization_product_name(env); + fprintf(stdout, " 'product name': '" PRInSTR "',\n", (int)product_name.len, product_name.ptr); + fprintf( + stdout, " 'number of processors': '%lu',\n", (unsigned long)aws_system_environment_get_processor_count(env)); + size_t numa_nodes = aws_system_environment_get_cpu_group_count(env); + + if (numa_nodes > 1) { + fprintf(stdout, " 'numa architecture': 'true',\n"); + fprintf(stdout, " 'number of numa nodes': '%lu'\n", (unsigned long)numa_nodes); + } else { + fprintf(stdout, " 'numa architecture': 'false'\n"); + } + + fprintf(stdout, "}\n"); + aws_system_environment_release(env); + aws_logger_clean_up(&logger); + + aws_common_library_clean_up(); + return 0; +} diff --git a/include/aws/common/private/system_info_priv.h b/include/aws/common/private/system_info_priv.h new file mode 100644 index 000000000..27b1d4ad1 --- /dev/null +++ b/include/aws/common/private/system_info_priv.h @@ -0,0 +1,37 @@ +#ifndef AWS_COMMON_PRIVATE_SYSTEM_INFO_PRIV_H +#define AWS_COMMON_PRIVATE_SYSTEM_INFO_PRIV_H +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include +#include +#include + +struct aws_system_environment { + struct aws_allocator *allocator; + struct aws_ref_count ref_count; + struct aws_byte_buf virtualization_vendor; + struct aws_byte_buf product_name; + enum aws_platform_os os; + size_t cpu_count; + size_t cpu_group_count; + void *impl; +}; + +/** + * For internal implementors. Fill in info in env that you're able to grab, such as dmi info, os version strings etc... + * in here. The default just returns AWS_OP_SUCCESS. This is currently only implemented for linux. + * + * Returns AWS_OP_ERR if the implementation wasn't able to fill in required information for the platform. + */ +int aws_system_environment_load_platform_impl(struct aws_system_environment *env); + +/** + * For internal implementors. Cleans up anything allocated in aws_system_environment_load_platform_impl, + * but does not release the memory for env. + */ +void aws_system_environment_destroy_platform_impl(struct aws_system_environment *env); + +#endif // AWS_COMMON_PRIVATE_SYSTEM_INFO_PRIV_H diff --git a/include/aws/common/system_info.h b/include/aws/common/system_info.h index fe7604120..91da41f9d 100644 --- a/include/aws/common/system_info.h +++ b/include/aws/common/system_info.h @@ -6,6 +6,7 @@ * SPDX-License-Identifier: Apache-2.0. */ +#include #include AWS_PUSH_SANE_WARNING_LEVEL @@ -21,8 +22,54 @@ struct aws_cpu_info { bool suspected_hyper_thread; }; +struct aws_system_environment; + AWS_EXTERN_C_BEGIN +/** + * Allocates and initializes information about the system the current process is executing on. + * If successful returns an instance of aws_system_environment. If it fails, it will return NULL. + * + * Note: This api is used internally and is still early in its evolution. + * It may change in incompatible ways in the future. + */ +AWS_COMMON_API +struct aws_system_environment *aws_system_environment_load(struct aws_allocator *allocator); + +AWS_COMMON_API +struct aws_system_environment *aws_system_environment_acquire(struct aws_system_environment *env); + +AWS_COMMON_API +void aws_system_environment_release(struct aws_system_environment *env); + +/** + * Returns the virtualization vendor for the specified compute environment, e.g. "Xen, Amazon EC2, etc..." + * + * The return value may be empty and in that case no vendor was detected. + */ +AWS_COMMON_API +struct aws_byte_cursor aws_system_environment_get_virtualization_vendor(const struct aws_system_environment *env); + +/** + * Returns the product name for the specified compute environment. For example, the Amazon EC2 Instance type. + * + * The return value may be empty and in that case no vendor was detected. + */ +AWS_COMMON_API +struct aws_byte_cursor aws_system_environment_get_virtualization_product_name(const struct aws_system_environment *env); + +/** + * Returns the number of processors for the specified compute environment. + */ +AWS_COMMON_API +size_t aws_system_environment_get_processor_count(struct aws_system_environment *env); + +/** + * Returns the number of separate cpu groupings (multi-socket configurations or NUMA). + */ +AWS_COMMON_API +size_t aws_system_environment_get_cpu_group_count(const struct aws_system_environment *env); + /* Returns the OS this was built under */ AWS_COMMON_API enum aws_platform_os aws_get_platform_build_os(void); diff --git a/source/linux/system_info.c b/source/linux/system_info.c new file mode 100644 index 000000000..2d9c5a120 --- /dev/null +++ b/source/linux/system_info.c @@ -0,0 +1,24 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include +#include + +int aws_system_environment_load_platform_impl(struct aws_system_environment *env) { + /* provide size_hint when reading "special files", since some platforms mis-report these files' size as 4KB */ + aws_byte_buf_init_from_file_with_size_hint( + &env->virtualization_vendor, env->allocator, "/sys/devices/virtual/dmi/id/sys_vendor", 32 /*size_hint*/); + + /* whether this one works depends on if this is a sysfs filesystem. If it fails, it will just be empty + * and these APIs are a best effort at the moment. We can add fallbacks as the loaders get more complicated. */ + aws_byte_buf_init_from_file_with_size_hint( + &env->product_name, env->allocator, "/sys/devices/virtual/dmi/id/product_name", 32 /*size_hint*/); + + return AWS_OP_SUCCESS; +} + +void aws_system_environment_destroy_platform_impl(struct aws_system_environment *env) { + aws_byte_buf_clean_up(&env->virtualization_vendor); + aws_byte_buf_clean_up(&env->product_name); +} diff --git a/source/platform_fallback_stubs/system_info.c b/source/platform_fallback_stubs/system_info.c new file mode 100644 index 000000000..2b81469a8 --- /dev/null +++ b/source/platform_fallback_stubs/system_info.c @@ -0,0 +1,21 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include + +#include + +int aws_system_environment_load_platform_impl(struct aws_system_environment *env) { + (void)env; + AWS_LOGF_DEBUG( + AWS_LS_COMMON_GENERAL, + "id=%p: platform specific environment loading is not implemented for this platform.", + (void *)env); + + return AWS_OP_SUCCESS; +} + +void aws_system_environment_destroy_platform_impl(struct aws_system_environment *env) { + (void)env; +} diff --git a/source/system_info.c b/source/system_info.c new file mode 100644 index 000000000..4b721f63a --- /dev/null +++ b/source/system_info.c @@ -0,0 +1,80 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +#include + +#include + +void s_destroy_env(void *arg) { + struct aws_system_environment *env = arg; + + if (env) { + aws_system_environment_destroy_platform_impl(env); + aws_mem_release(env->allocator, env); + } +} + +struct aws_system_environment *aws_system_environment_load(struct aws_allocator *allocator) { + struct aws_system_environment *env = aws_mem_calloc(allocator, 1, sizeof(struct aws_system_environment)); + env->allocator = allocator; + aws_ref_count_init(&env->ref_count, env, s_destroy_env); + + if (aws_system_environment_load_platform_impl(env)) { + AWS_LOGF_ERROR( + AWS_LS_COMMON_GENERAL, + "id=%p: failed to load system environment with error %s.", + (void *)env, + aws_error_debug_str(aws_last_error())); + goto error; + } + + AWS_LOGF_TRACE( + AWS_LS_COMMON_GENERAL, + "id=%p: virtualization vendor detected as \"" PRInSTR "\"", + (void *)env, + AWS_BYTE_CURSOR_PRI(aws_system_environment_get_virtualization_vendor(env))); + AWS_LOGF_TRACE( + AWS_LS_COMMON_GENERAL, + "id=%p: virtualization product name detected as \"" PRInSTR " \"", + (void *)env, + AWS_BYTE_CURSOR_PRI(aws_system_environment_get_virtualization_vendor(env))); + + env->os = aws_get_platform_build_os(); + env->cpu_count = aws_system_info_processor_count(); + env->cpu_group_count = aws_get_cpu_group_count(); + + return env; +error: + s_destroy_env(env); + return NULL; +} + +struct aws_system_environment *aws_system_environment_acquire(struct aws_system_environment *env) { + aws_ref_count_acquire(&env->ref_count); + return env; +} + +void aws_system_environment_release(struct aws_system_environment *env) { + aws_ref_count_release(&env->ref_count); +} + +struct aws_byte_cursor aws_system_environment_get_virtualization_vendor(const struct aws_system_environment *env) { + struct aws_byte_cursor vendor_string = aws_byte_cursor_from_buf(&env->virtualization_vendor); + return aws_byte_cursor_trim_pred(&vendor_string, aws_char_is_space); +} + +struct aws_byte_cursor aws_system_environment_get_virtualization_product_name( + const struct aws_system_environment *env) { + struct aws_byte_cursor product_name_str = aws_byte_cursor_from_buf(&env->product_name); + return aws_byte_cursor_trim_pred(&product_name_str, aws_char_is_space); +} + +size_t aws_system_environment_get_processor_count(struct aws_system_environment *env) { + return env->cpu_count; +} + +AWS_COMMON_API +size_t aws_system_environment_get_cpu_group_count(const struct aws_system_environment *env) { + return env->cpu_group_count; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 185a83532..4681e04c3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -304,6 +304,7 @@ add_test_case(test_cpu_count_at_least_works_superficially) add_test_case(test_stack_trace_decoding) add_test_case(test_platform_build_os) add_test_case(test_sanity_check_numa_discovery) +add_test_case(test_sanity_check_environment_loader) add_test_case(test_realloc_fallback) add_test_case(test_realloc_passthrough) diff --git a/tests/system_info_tests.c b/tests/system_info_tests.c index 8bd01ca4f..7a4324797 100644 --- a/tests/system_info_tests.c +++ b/tests/system_info_tests.c @@ -166,3 +166,22 @@ static int s_test_sanity_check_numa_discovery(struct aws_allocator *allocator, v } AWS_TEST_CASE(test_sanity_check_numa_discovery, s_test_sanity_check_numa_discovery) + +static int s_test_sanity_check_environment_loader(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + aws_common_library_init(allocator); + struct aws_system_environment *env = aws_system_environment_load(allocator); + ASSERT_NOT_NULL(env); + struct aws_byte_cursor virt_vendor = aws_system_environment_get_virtualization_vendor(env); + ASSERT_TRUE(aws_byte_cursor_is_valid(&virt_vendor)); + struct aws_byte_cursor virt_product = aws_system_environment_get_virtualization_product_name(env); + ASSERT_TRUE(aws_byte_cursor_is_valid(&virt_product)); + + aws_system_environment_release(env); + + aws_common_library_clean_up(); + return 0; +} + +AWS_TEST_CASE(test_sanity_check_environment_loader, s_test_sanity_check_environment_loader)