Skip to content

Commit

Permalink
Added metric reporting functions (#898)
Browse files Browse the repository at this point in the history
Adds metric reporting functions that are platform specific that can be used in the Device Defender custom metrics

Commit change log:
* Added metric reporting functions that are platform specific that can be used in the Device Defender custom metrics
* Fixed formatting issues and unused variable issues on Mac
* Additional Mac fixes
* Moved metrics reporting to system_info
* Clang format fixes
* Changed how CPU is reported so it is clearer, detects errors faster, and handles more situations
* Clang format and clang tidy fixes
* Fixed LGTM error and unused variable issue on Mac
* Added use of aws_*_u64_checked and removed debug print statement
* Clang format fix (I need to setup Clang-format on linux...)
* Process count and memory usage now reports using int64_t
* WIP initial CPU usage sampler
* Adjusted CPU usage sampler to fit correct design
* Clang format fixes
* Further clang-format fixes - missed a couple minor issues
* Code review changes - first half
* Code review part 2 - refactored cpu_usage_sampler into OS specific folders. Added stub/default implementation for Windows and MacOS
* Clang format fix
* Further clang-format fixes
* Code review changes: Moved generic API functions for reuse, simplified code, adjusted comments
* Fixed clang format issues, fixed Windows compiling and clang-tidy
* More clang-tidy fixes
* Adjusted after code review: Added private header file for CPU sampler, changed linux to cache result on constructor, unsupported platforms return null
* Fixed unused argument issue on Windows
* Fix header guard using incorrect format
  • Loading branch information
TwistedTwigleg authored Apr 20, 2022
1 parent 7279561 commit 9b2a8b7
Show file tree
Hide file tree
Showing 10 changed files with 367 additions and 1 deletion.
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ else ()
endif()
endif()

# OS specific includes
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
list (APPEND AWS_COMMON_OS_SRC "source/linux/*.c")
elseif (APPLE)
list (APPEND AWS_COMMON_OS_SRC "source/darwin/*.c")
endif()

if (APPLE)
# Don't add the exact path to CoreFoundation as this would hardcode the SDK version
list(APPEND PLATFORM_LIBS dl Threads::Threads "-framework CoreFoundation")
Expand Down
52 changes: 52 additions & 0 deletions include/aws/common/cpu_usage_sampler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#ifndef AWS_COMMON_CPU_USAGE_SAMPLER_H
#define AWS_COMMON_CPU_USAGE_SAMPLER_H

/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

#include <aws/common/common.h>
#include <aws/common/math.h>

/**
* A struct that contains the CPU sampler for this platform.
* Currently only Linux is supported.
*
* Note: Must be freed from memory using aws_cpu_sampler_destroy when finished.
*/
struct aws_cpu_sampler;

AWS_EXTERN_C_BEGIN

/**
* Creates a new CPU sampler using the provided allocator, or will return NULL if there is an error.
*
* Note: On unsupported platforms, the CPU sampler returned will return AWS_OP_ERR when calling
* aws_cpu_sampler_get_sample. You will still need to call aws_cpu_sampler_clean_up when finished
* to free the memory even for unsupported platforms.
*/
AWS_COMMON_API
struct aws_cpu_sampler *aws_cpu_sampler_new(struct aws_allocator *allocator);

/**
* Frees the memory used by the CPU sampler.
*/
AWS_COMMON_API
void aws_cpu_sampler_destroy(struct aws_cpu_sampler *sampler);

/**
* Gets the CPU usage and populates the given double, output, with the value. The value
* returned is a percentage from 0.0 to 100.0.
*
* Will return AWS_OP_SUCCESS if polling the CPU was successful. AWS_OP_ERR will be returned
* if the result should not be used or if there was an error polling the CPU.
*
* Will always return AWS_OP_ERR for unsupported platforms.
*/
AWS_COMMON_API
int aws_cpu_sampler_get_sample(struct aws_cpu_sampler *sampler, double *output);

AWS_EXTERN_C_END

#endif /* AWS_COMMON_CPU_USAGE_SAMPLER_H */
29 changes: 29 additions & 0 deletions include/aws/common/private/cpu_usage_sampler_private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#ifndef AWS_COMMON_PRIVATE_CPU_USAGE_SAMPLER_PRIVATE_H
#define AWS_COMMON_PRIVATE_CPU_USAGE_SAMPLER_PRIVATE_H

/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

#include <aws/common/common.h>
#include <aws/common/math.h>

/**
* The VTable for the CPU sampler in cpu_usage_sampler.h
*/
struct aws_cpu_sampler_vtable {
int (*aws_get_cpu_sample_fn)(struct aws_cpu_sampler *sampler, double *output);
void (*aws_cpu_sampler_destroy)(struct aws_cpu_sampler *sampler);
};

/**
* The CPU sampler in cpu_usage_sampler.h
*/
struct aws_cpu_sampler {
const struct aws_cpu_sampler_vtable *vtable;
struct aws_allocator *allocator;
void *impl;
};

#endif /* AWS_COMMON_PRIVATE_CPU_USAGE_SAMPLER_PRIVATE_H */
12 changes: 12 additions & 0 deletions include/aws/common/system_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ void aws_backtrace_print(FILE *fp, void *call_site_data);
AWS_COMMON_API
void aws_backtrace_log(int log_level);

/**
* Sets the passed int value as the memory usage in kilobytes. Only supported on Linux currently.
*/
AWS_COMMON_API
int aws_get_memory_usage(int64_t *output);

/**
* Sets the passed int value as the process count. Only supported on Linux currently.
*/
AWS_COMMON_API
int aws_get_process_count(int64_t *output);

AWS_EXTERN_C_END

#endif /* AWS_COMMON_SYSTEM_INFO_H */
33 changes: 33 additions & 0 deletions source/cpu_usage_sampler.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

#include <aws/common/cpu_usage_sampler.h>
#include <aws/common/private/cpu_usage_sampler_private.h>

/*********************************************************************************************************************
* Public operations
********************************************************************************************************************/

void aws_cpu_sampler_destroy(struct aws_cpu_sampler *sampler) {
if (sampler == NULL) {
return;
}
if (sampler->vtable->aws_cpu_sampler_destroy == NULL) {
return;
}
sampler->vtable->aws_cpu_sampler_destroy(sampler);
}

int aws_cpu_sampler_get_sample(struct aws_cpu_sampler *sampler, double *output) {
if (sampler == NULL) {
aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
return AWS_OP_ERR;
}
if (sampler->vtable->aws_get_cpu_sample_fn == NULL) {
aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED);
return AWS_OP_ERR;
}
return sampler->vtable->aws_get_cpu_sample_fn(sampler, output);
}
18 changes: 18 additions & 0 deletions source/darwin/cpu_usage_sampler.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

#include <aws/common/cpu_usage_sampler.h>
#include <aws/common/private/cpu_usage_sampler_private.h>

/*********************************************************************************************************************
* Public operations
********************************************************************************************************************/

struct aws_cpu_sampler *aws_cpu_sampler_new(struct aws_allocator *allocator) {
// OS currently not supported
(void)(allocator);
aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED);
return NULL;
}
140 changes: 140 additions & 0 deletions source/linux/cpu_usage_sampler.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

#include <aws/common/cpu_usage_sampler.h>
#include <aws/common/private/cpu_usage_sampler_private.h>

#include <inttypes.h>
#include <sys/sysinfo.h>
#include <sys/types.h>

/*********************************************************************************************************************
* Linux Specific
********************************************************************************************************************/

struct aws_cpu_sampler_linux {
struct aws_cpu_sampler base;

uint64_t cpu_last_total_user;
uint64_t cpu_last_total_user_low;
uint64_t cpu_last_total_system;
uint64_t cpu_last_total_idle;
};

static void s_get_cpu_usage_linux(
uint64_t *total_user,
uint64_t *total_user_low,
uint64_t *total_system,
uint64_t *total_idle) {

FILE *file;
int matched_results;
file = fopen("/proc/stat", "r");
matched_results = fscanf(
file,
"cpu %" SCNu64 " %" SCNu64 " %" SCNu64 " %" SCNu64 "",
(uint64_t *)total_user,
(uint64_t *)total_user_low,
(uint64_t *)total_system,
(uint64_t *)total_idle);
fclose(file);
if (matched_results == EOF || matched_results != 4) {
aws_raise_error(AWS_ERROR_INVALID_STATE);
}
}

static void aws_get_cpu_sample_fn_linux_get_uint64_delta(uint64_t first, uint64_t second, uint64_t *output) {
if (first > second) {
aws_sub_u64_checked(first, second, output);
} else {
aws_add_u64_checked((UINT64_MAX - second), first, output);
}
}

static int aws_get_cpu_sample_fn_linux(struct aws_cpu_sampler *sampler, double *output) {
struct aws_cpu_sampler_linux *sampler_linux = sampler->impl;

int return_result = AWS_OP_ERR;
uint64_t total_user, total_user_low, total_system, total_idle, total;
s_get_cpu_usage_linux(&total_user, &total_user_low, &total_system, &total_idle);
// total_combined needs to be double to allow for fractions
double percent, total_combined;

uint64_t total_user_delta = 0, total_user_low_delta = 0, total_system_delta = 0, total_idle_delta = 0;
aws_get_cpu_sample_fn_linux_get_uint64_delta(total_user, sampler_linux->cpu_last_total_user, &total_user_delta);
aws_get_cpu_sample_fn_linux_get_uint64_delta(
total_user_low, sampler_linux->cpu_last_total_user_low, &total_user_low_delta);
aws_get_cpu_sample_fn_linux_get_uint64_delta(
total_system, sampler_linux->cpu_last_total_system, &total_system_delta);
aws_get_cpu_sample_fn_linux_get_uint64_delta(total_idle, sampler_linux->cpu_last_total_idle, &total_idle_delta);

total_combined = (double)(total_user_delta) + (double)(total_user_low_delta) + (double)(total_system_delta);
total = total_combined + (double)(total_idle_delta);

if (total == 0) {
*output = 0;
return_result = AWS_OP_ERR;
goto cleanup;
}

percent = (total_combined / total) * 100;

// If negative, there was an error (overflow?)
if (percent < 0) {
*output = 0;
return_result = AWS_OP_ERR;
goto cleanup;
}

*output = percent;
return_result = AWS_OP_SUCCESS;

cleanup:
// Cache results
sampler_linux->cpu_last_total_user = total_user;
sampler_linux->cpu_last_total_user_low = total_user_low;
sampler_linux->cpu_last_total_system = total_system;
sampler_linux->cpu_last_total_idle = total_idle;

return return_result;
}

static void aws_cpu_sampler_destroy_linux(struct aws_cpu_sampler *sampler) {
if (sampler == NULL) {
return;
}
struct aws_cpu_sampler_linux *sampler_linux = (struct aws_cpu_sampler_linux *)sampler->impl;
aws_mem_release(sampler->allocator, sampler_linux);
}

static struct aws_cpu_sampler_vtable aws_cpu_sampler_vtable_linux = {
.aws_get_cpu_sample_fn = aws_get_cpu_sample_fn_linux,
.aws_cpu_sampler_destroy = aws_cpu_sampler_destroy_linux,
};

/*********************************************************************************************************************
* Public operations
********************************************************************************************************************/

struct aws_cpu_sampler *aws_cpu_sampler_new(struct aws_allocator *allocator) {
if (allocator == NULL) {
aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
return NULL;
}

struct aws_cpu_sampler_linux *output_linux = aws_mem_calloc(allocator, 1, sizeof(struct aws_cpu_sampler_linux));
if (output_linux == NULL) {
return NULL;
}
output_linux->base.allocator = allocator;
output_linux->base.vtable = &aws_cpu_sampler_vtable_linux;
output_linux->base.impl = output_linux;

// CPU reporting is done via deltas, so we need to cache the initial CPU values
double tmp = 0;
aws_get_cpu_sample_fn_linux(&output_linux->base, &tmp);

return &output_linux->base;
}
42 changes: 42 additions & 0 deletions source/posix/system_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
# define __BSD_VISIBLE 1
#endif

#if defined(__linux__) || defined(__unix__)
# include <sys/sysinfo.h>
# include <sys/types.h>
#endif

#include <unistd.h>

#if defined(HAVE_SYSCONF)
Expand Down Expand Up @@ -445,3 +450,40 @@ enum aws_platform_os aws_get_platform_build_os(void) {
return AWS_PLATFORM_OS_UNIX;
}
#endif /* AWS_OS_APPLE */

#if defined(__linux__) || defined(__unix__)
static const uint64_t S_BYTES_TO_KILO_BYTES = 1024;
#endif

int aws_get_memory_usage(int64_t *output) {
// Get the Memory usage from Linux
#if defined(__linux__) || defined(__unix__)
struct sysinfo memory_info;
sysinfo(&memory_info);
uint64_t physical_memory_used = memory_info.totalram - memory_info.freeram;
physical_memory_used *= memory_info.mem_unit;
// Return data in Kilobytes
physical_memory_used = physical_memory_used / S_BYTES_TO_KILO_BYTES;
*output = physical_memory_used;
return AWS_OP_SUCCESS;
#endif

// OS not supported? Just return an error and set the output to 0
*output = 0;
aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED);
return AWS_OP_ERR;
}

int aws_get_process_count(int64_t *output) {
// Get the process count from Linux
#if defined(__linux__) || defined(__unix__)
struct sysinfo system_info;
sysinfo(&system_info);
*output = (int64_t)system_info.procs;
return AWS_OP_SUCCESS;
#endif
// OS not supported? Just return an error and set the output to 0
*output = 0;
aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED);
return AWS_OP_ERR;
}
18 changes: 18 additions & 0 deletions source/windows/cpu_usage_sampler.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

#include <aws/common/cpu_usage_sampler.h>
#include <aws/common/private/cpu_usage_sampler_private.h>

/*********************************************************************************************************************
* Public operations
********************************************************************************************************************/

struct aws_cpu_sampler *aws_cpu_sampler_new(struct aws_allocator *allocator) {
// OS currently not supported
(void)(allocator);
aws_raise_error(AWS_ERROR_PLATFORM_NOT_SUPPORTED);
return NULL;
}
Loading

0 comments on commit 9b2a8b7

Please sign in to comment.