From 88d8dd3214ea76ceb07ffe83c4bb06e2d32261fa Mon Sep 17 00:00:00 2001 From: Mario Hewardt Date: Mon, 11 Nov 2024 09:20:39 -0800 Subject: [PATCH] Add macOS support (#259) * Initial macOS commit * Starting updates to process functions * Initial work on CPU monitoring * Cleanup ifdef * Add thread and file count as well as initial brew formula * Update build to add make brew target * fix: copy procdump.rb to bin * Updates * Integration tests * reduce initial size of memory map * cross compilation fixes * Use zip * Cleanup * Add Mac build * Remove formula * Remove dep on stress-ng * Add diag * Add diag * Add diag * Add diag --------- Co-authored-by: Mario Hewardt --- BUILD.md | 2 +- CMakeLists.txt | 324 ++++++++++-------- azure-pipelines.yml | 18 +- include/CoreDumpWriter.h | 3 + include/GenHelpers.h | 7 + include/Handle.h | 2 +- include/ProcDumpConfiguration.h | 8 + include/Process.h | 5 + makePackages.sh | 8 +- procdump.1 | 2 +- procdump_mac.1 | 34 ++ src/CoreDumpWriter.cpp | 40 ++- src/Events.cpp | 2 +- src/GenHelpers.cpp | 10 +- src/Handle.cpp | 37 +- src/Logging.cpp | 4 +- src/Monitor.cpp | 82 +++-- src/ProcDumpConfiguration.cpp | 151 ++++---- src/Process.cpp | 222 +++++++++++- tests/integration/ProcDumpTestApplication.c | 23 +- tests/integration/run.sh | 24 +- tests/integration/runProcDumpAndValidate.sh | 28 +- .../integration/scenarios/high_fd_by_name.sh | 8 +- .../integration/scenarios/high_tc_by_name.sh | 8 +- tests/integration/scenarios_mac/high_cpu.sh | 28 ++ .../scenarios_mac/high_fd_by_name.sh | 4 + tests/integration/scenarios_mac/high_mem.sh | 27 ++ .../scenarios_mac/high_tc_by_name.sh | 2 + 28 files changed, 838 insertions(+), 275 deletions(-) create mode 100644 procdump_mac.1 create mode 100755 tests/integration/scenarios_mac/high_cpu.sh create mode 100755 tests/integration/scenarios_mac/high_fd_by_name.sh create mode 100755 tests/integration/scenarios_mac/high_mem.sh create mode 100755 tests/integration/scenarios_mac/high_tc_by_name.sh diff --git a/BUILD.md b/BUILD.md index bdc3b3a..de998c7 100644 --- a/BUILD.md +++ b/BUILD.md @@ -27,7 +27,7 @@ make ### Ubuntu ``` sudo apt update -sudo apt -y install gcc cmake make clang clang-12 gdb zlib-devel libelf-dev build-essential libbpf-dev linux-tools-common linux-tools-$(uname -r) +sudo apt -y install gcc cmake make clang clang-12 gdb zlib1g-dev libelf-dev build-essential libbpf-dev linux-tools-common linux-tools-$(uname -r) ``` ### Rocky Linux diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b3628f..1ada6c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,11 +53,19 @@ add_custom_target(procDumpManPageCompress ALL DEPENDS ${PROJECT_BINARY_DIR}/${PROCDUMP_COMPRESS_MAN} ) -add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/${PROCDUMP_COMPRESS_MAN} - COMMAND gzip -9n -f -c "${CMAKE_SOURCE_DIR}/procdump.1" > ${PROJECT_BINARY_DIR}/${PROCDUMP_COMPRESS_MAN} - COMMENT "Compressing ProcDump man page" - DEPENDS "${CMAKE_SOURCE_DIR}/procdump.1" - ) +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/${PROCDUMP_COMPRESS_MAN} + COMMAND gzip -9n -f -c "${CMAKE_SOURCE_DIR}/procdump.1" > ${PROJECT_BINARY_DIR}/${PROCDUMP_COMPRESS_MAN} + COMMENT "Compressing ProcDump man page" + DEPENDS "${CMAKE_SOURCE_DIR}/procdump.1" + ) +else() + add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/${PROCDUMP_COMPRESS_MAN} + COMMAND gzip -9n -f -c "${CMAKE_SOURCE_DIR}/procdump_mac.1" > ${PROJECT_BINARY_DIR}/${PROCDUMP_COMPRESS_MAN} + COMMENT "Compressing ProcDump man page" + DEPENDS "${CMAKE_SOURCE_DIR}/procdump.1" + ) +endif() # # Change log @@ -91,8 +99,10 @@ set(sym_SOURCE_DIR ${CMAKE_SOURCE_DIR}/sym) # Configure files # configure_file(${procdump_INC}/ProcDumpVersion.h.in ${PROJECT_BINARY_DIR}/ProcDumpVersion.h) -configure_file(dist/DEBIAN.in/control.in DEBIANcontrol) -configure_file(dist/SPECS.in/spec.in SPECS.spec) +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + configure_file(dist/DEBIAN.in/control.in DEBIANcontrol) + configure_file(dist/SPECS.in/spec.in SPECS.spec) +endif() # # Compiler @@ -102,80 +112,111 @@ set(CMAKE_CXX_COMPILER "clang++") # # Make procdump profiler # +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + message(STATUS "Building for Linux") + # Figure out which architecture we are building for + if(CMAKE_SYSTEM_PROCESSOR STREQUAL x86_64 OR CMAKE_SYSTEM_PROCESSOR STREQUAL amd64) + set(CLRHOSTDEF -DHOST_AMD64 -DHOST_64BIT) + elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL x86 OR CMAKE_SYSTEM_PROCESSOR STREQUAL i686) + set(CLRHOSTDEF -DHOST_X86) + elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL armv6 OR CMAKE_SYSTEM_PROCESSOR STREQUAL armv6l) + set(CLRHOSTDEF -DHOST_ARM -DHOST_ARMV6) + elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL arm OR CMAKE_SYSTEM_PROCESSOR STREQUAL armv7-a) + set(CLRHOSTDEF -DHOST_ARM) + elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64 OR CMAKE_SYSTEM_PROCESSOR STREQUAL arm64) + set(CLRHOSTDEF -DHOST_ARM64 -DHOST_64BIT) + elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL loongarch64) + set(CLRHOSTDEF -DHOST_LOONGARCH64 -DHOST_64BIT) + elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL riscv64) + set(CLRHOSTDEF -DHOST_RISCV64 -DHOST_64BIT) + elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL s390x) + set(CLRHOSTDEF -DHOST_S390X -DHOST_64BIT -DBIGENDIAN) + elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL mips64) + set(CLRHOSTDEF -DHOST_MIPS64 -DHOST_64BIT=1) + elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL ppc64le) + set(CLRHOSTDEF -DHOST_POWERPC64 -DHOST_64BIT) + else() + message(FATAL_ERROR "'${CMAKE_SYSTEM_PROCESSOR}' is an unsupported architecture.") + endif() + + add_library(ProcDumpProfiler SHARED + ${profiler_SRC}/ClassFactory.cpp + ${profiler_SRC}/ProcDumpProfiler.cpp + ${profiler_SRC}/dllmain.cpp + ${profiler_SRC}/corprof_i.cpp + ${profiler_SRC}/easylogging++.cc + ) -# Figure out which architecture we are building for -if(CMAKE_SYSTEM_PROCESSOR STREQUAL x86_64 OR CMAKE_SYSTEM_PROCESSOR STREQUAL amd64) - set(CLRHOSTDEF -DHOST_AMD64 -DHOST_64BIT) -elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL x86 OR CMAKE_SYSTEM_PROCESSOR STREQUAL i686) - set(CLRHOSTDEF -DHOST_X86) -elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL armv6 OR CMAKE_SYSTEM_PROCESSOR STREQUAL armv6l) - set(CLRHOSTDEF -DHOST_ARM -DHOST_ARMV6) -elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL arm OR CMAKE_SYSTEM_PROCESSOR STREQUAL armv7-a) - set(CLRHOSTDEF -DHOST_ARM) -elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64 OR CMAKE_SYSTEM_PROCESSOR STREQUAL arm64) - set(CLRHOSTDEF -DHOST_ARM64 -DHOST_64BIT) -elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL loongarch64) - set(CLRHOSTDEF -DHOST_LOONGARCH64 -DHOST_64BIT) -elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL riscv64) - set(CLRHOSTDEF -DHOST_RISCV64 -DHOST_64BIT) -elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL s390x) - set(CLRHOSTDEF -DHOST_S390X -DHOST_64BIT -DBIGENDIAN) -elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL mips64) - set(CLRHOSTDEF -DHOST_MIPS64 -DHOST_64BIT=1) -elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL ppc64le) - set(CLRHOSTDEF -DHOST_POWERPC64 -DHOST_64BIT) -else() - message(FATAL_ERROR "'${CMAKE_SYSTEM_PROCESSOR}' is an unsupported architecture.") -endif() - -add_library(ProcDumpProfiler SHARED - ${profiler_SRC}/ClassFactory.cpp - ${profiler_SRC}/ProcDumpProfiler.cpp - ${profiler_SRC}/dllmain.cpp - ${profiler_SRC}/corprof_i.cpp - ${profiler_SRC}/easylogging++.cc - ) - -target_compile_options(ProcDumpProfiler PRIVATE -DELPP_NO_DEFAULT_LOG_FILE -DELPP_THREAD_SAFE -g -pthread -Wno-pragma-pack -Wno-pointer-arith -Wno-conversion-null -Wno-write-strings -Wno-format-security -fPIC -fms-extensions ${CLRHOSTDEF} -DPAL_STDCPP_COMPAT -DPLATFORM_UNIX -std=c++11) -set_target_properties(ProcDumpProfiler PROPERTIES PREFIX "") + target_compile_options(ProcDumpProfiler PRIVATE -DELPP_NO_DEFAULT_LOG_FILE -DELPP_THREAD_SAFE -g -pthread -Wno-pragma-pack -Wno-pointer-arith -Wno-conversion-null -Wno-write-strings -Wno-format-security -fPIC -fms-extensions ${CLRHOSTDEF} -DPAL_STDCPP_COMPAT -DPLATFORM_UNIX -std=c++11) + set_target_properties(ProcDumpProfiler PROPERTIES PREFIX "") -target_include_directories(ProcDumpProfiler PUBLIC - "${profiler_INC}" - "${procdump_INC}" - /usr/include - ) + target_include_directories(ProcDumpProfiler PUBLIC + "${profiler_INC}" + "${procdump_INC}" + /usr/include + ) -add_custom_command(OUTPUT ProcDumpProfiler.o - COMMAND "${LD}" -r -b binary -o "${PROJECT_BINARY_DIR}/ProcDumpProfiler.o" ProcDumpProfiler.so - COMMENT "Packing ProcDumpProfiler.so into ProcDumpProfiler.o" - DEPENDS ProcDumpProfiler - ) + add_custom_command(OUTPUT ProcDumpProfiler.o + COMMAND "${LD}" -r -b binary -o "${PROJECT_BINARY_DIR}/ProcDumpProfiler.o" ProcDumpProfiler.so + COMMENT "Packing ProcDumpProfiler.so into ProcDumpProfiler.o" + DEPENDS ProcDumpProfiler + ) +endif() # # Make ProcDump # -add_executable(procdump - ${procdump_SRC}/CoreDumpWriter.cpp - ${procdump_SRC}/DotnetHelpers.cpp - ${procdump_SRC}/Events.cpp - ${procdump_SRC}/GenHelpers.cpp - ${procdump_SRC}/Handle.cpp - ${procdump_SRC}/Logging.cpp - ${procdump_SRC}/Monitor.cpp - ${procdump_SRC}/Procdump.cpp - ${procdump_SRC}/ProcDumpConfiguration.cpp - ${procdump_SRC}/Process.cpp - ${procdump_SRC}/ProfilerHelpers.cpp - ${procdump_SRC}/Restrack.cpp - ${sym_SOURCE_DIR}/bcc_proc.cpp - ${sym_SOURCE_DIR}/bcc_syms.cc - ${sym_SOURCE_DIR}/bcc_elf.cpp - ${sym_SOURCE_DIR}/bcc_perf_map.cpp - ${sym_SOURCE_DIR}/bcc_zip.cpp - ${PROJECT_BINARY_DIR}/ProcDumpProfiler.o - ) +if(APPLE) + # Create universal binary + set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64") +endif() + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + message(STATUS "Building for Linux") + add_executable(procdump + ${procdump_SRC}/CoreDumpWriter.cpp + ${procdump_SRC}/DotnetHelpers.cpp + ${procdump_SRC}/Events.cpp + ${procdump_SRC}/GenHelpers.cpp + ${procdump_SRC}/Handle.cpp + ${procdump_SRC}/Logging.cpp + ${procdump_SRC}/Monitor.cpp + ${procdump_SRC}/Procdump.cpp + ${procdump_SRC}/ProcDumpConfiguration.cpp + ${procdump_SRC}/Process.cpp + ${procdump_SRC}/ProfilerHelpers.cpp + ${procdump_SRC}/Restrack.cpp + ${sym_SOURCE_DIR}/bcc_proc.cpp + ${sym_SOURCE_DIR}/bcc_syms.cc + ${sym_SOURCE_DIR}/bcc_elf.cpp + ${sym_SOURCE_DIR}/bcc_perf_map.cpp + ${sym_SOURCE_DIR}/bcc_zip.cpp + ${PROJECT_BINARY_DIR}/ProcDumpProfiler.o + ) +else() + add_executable(procdump + ${procdump_SRC}/CoreDumpWriter.cpp + #${procdump_SRC}/DotnetHelpers.cpp + ${procdump_SRC}/Events.cpp + ${procdump_SRC}/GenHelpers.cpp + ${procdump_SRC}/Handle.cpp + ${procdump_SRC}/Logging.cpp + ${procdump_SRC}/Monitor.cpp + ${procdump_SRC}/Procdump.cpp + ${procdump_SRC}/ProcDumpConfiguration.cpp + ${procdump_SRC}/Process.cpp + #${procdump_SRC}/ProfilerHelpers.cpp + #${procdump_SRC}/Restrack.cpp + #${sym_SOURCE_DIR}/bcc_proc.cpp + #${sym_SOURCE_DIR}/bcc_syms.cc + #${sym_SOURCE_DIR}/bcc_elf.cpp + #${sym_SOURCE_DIR}/bcc_perf_map.cpp + #${sym_SOURCE_DIR}/bcc_zip.cpp + #${PROJECT_BINARY_DIR}/ProcDumpProfiler.o + ) +endif() -target_compile_options(procdump PRIVATE -g -pthread -fstack-protector-all -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -Werror -D_GNU_SOURCE -std=c++11 -O2) +target_compile_options(procdump PRIVATE -g -pthread -fstack-protector-all -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -Werror -D_GNU_SOURCE -std=c++11) target_include_directories(procdump PUBLIC ${procdump_INC} @@ -184,9 +225,12 @@ target_include_directories(procdump PUBLIC ${sym_SOURCE_DIR} ${procdump_ebpf_SOURCE_DIR} ) - -add_dependencies(procdump libbpf procdump_ebpf) -target_link_libraries(procdump ${libbpf_SOURCE_DIR}/src/libbpf.a elf z pthread) +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + add_dependencies(procdump libbpf procdump_ebpf) + target_link_libraries(procdump ${libbpf_SOURCE_DIR}/src/libbpf.a elf z pthread) +else() + target_link_libraries(procdump z pthread) +endif() # # Copy integration test directory @@ -215,72 +259,80 @@ target_link_libraries(ProcDumpTestApplication pthread) # # Make package(s) # -add_custom_target(deb - COMMAND "${CMAKE_SOURCE_DIR}/makePackages.sh" "${CMAKE_SOURCE_DIR}" "${PROJECT_BINARY_DIR}" "${PACKAGE_NAME}" "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}" "0" "deb" - DEPENDS "${CMAKE_SOURCE_DIR}/dist" "${PROJECT_BINARY_DIR}/procdump" -) +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + add_custom_target(deb + COMMAND "${CMAKE_SOURCE_DIR}/makePackages.sh" "${CMAKE_SOURCE_DIR}" "${PROJECT_BINARY_DIR}" "${PACKAGE_NAME}" "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}" "0" "deb" + DEPENDS "${CMAKE_SOURCE_DIR}/dist" "${PROJECT_BINARY_DIR}/procdump" + ) -add_custom_target(rpm - COMMAND "${CMAKE_SOURCE_DIR}/makePackages.sh" "${CMAKE_SOURCE_DIR}" "${PROJECT_BINARY_DIR}" "${PACKAGE_NAME}" "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}" "0" "rpm" - DEPENDS "${CMAKE_SOURCE_DIR}/dist" "${PROJECT_BINARY_DIR}/procdump" -) + add_custom_target(rpm + COMMAND "${CMAKE_SOURCE_DIR}/makePackages.sh" "${CMAKE_SOURCE_DIR}" "${PROJECT_BINARY_DIR}" "${PACKAGE_NAME}" "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}" "0" "rpm" + DEPENDS "${CMAKE_SOURCE_DIR}/dist" "${PROJECT_BINARY_DIR}/procdump" + ) +else() + add_custom_target(brew + COMMAND "${CMAKE_SOURCE_DIR}/makePackages.sh" "${CMAKE_SOURCE_DIR}" "${PROJECT_BINARY_DIR}" "${PACKAGE_NAME}" "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}" "0" "brew" + DEPENDS "${CMAKE_SOURCE_DIR}/dist" "${PROJECT_BINARY_DIR}/procdump" + ) +endif() # # Make ProcDump eBPF program # - -# Fetch libbpf -include(ExternalProject) - -ExternalProject_Add(libbpf - GIT_REPOSITORY https://github.com/libbpf/libbpf.git - GIT_TAG v1.2.2 - PREFIX ./libbpf - CONFIGURE_COMMAND "" - BUILD_COMMAND cd ../libbpf/src && bash -c "CFLAGS=\"-g -O2 -Werror -Wall -fPIC\" make" - INSTALL_COMMAND "" - ) - -# set binaries and options for clang and llc -set(CLANG "clang") -set(LLC "llc") -set(CLANG_OPTIONS -Wno-unused-value - -Wno-pointer-sign - -Wno-compare-distinct-pointer-types - -Wno-gnu-variable-sized-type-not-at-end - -Wno-address-of-packed-member - -Wno-tautological-compare - -Wno-unknown-warning-option - -g +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + # Fetch libbpf + include(ExternalProject) + + ExternalProject_Add(libbpf + GIT_REPOSITORY https://github.com/libbpf/libbpf.git + GIT_TAG v1.2.2 + PREFIX ./libbpf + CONFIGURE_COMMAND "" + BUILD_COMMAND cd ../libbpf/src && bash -c "CFLAGS=\"-g -O2 -Werror -Wall -fPIC\" make" + INSTALL_COMMAND "" + ) + + # set binaries and options for clang and llc + set(CLANG "clang") + set(LLC "llc") + set(CLANG_OPTIONS -Wno-unused-value + -Wno-pointer-sign + -Wno-compare-distinct-pointer-types + -Wno-gnu-variable-sized-type-not-at-end + -Wno-address-of-packed-member + -Wno-tautological-compare + -Wno-unknown-warning-option + -g + ) + set(CLANG_DEFINES -D __KERNEL__ + -D __BPF_TRACING__ + -D __TARGET_ARCH_x86 + -D __linux__ + ) + if (DEBUG_K) + message("Using DEBUG_K Option...") + list(APPEND CLANG_DEFINES -DDEBUG_K) + endif() + + set(CLANG_INCLUDES + -I "/usr/include" + -I "/usr/include/x86_64-linux-gnu" + -I "${CMAKE_SOURCE_DIR}" + -I "${CMAKE_BINARY_DIR}" + -I "${libbpf_SOURCE_DIR}/src" + ) + + add_custom_target(procdump_ebpf + DEPENDS procdump_ebpf.o ) -set(CLANG_DEFINES -D __KERNEL__ - -D __BPF_TRACING__ - -D __TARGET_ARCH_x86 - -D __linux__ - ) -if (DEBUG_K) - message("Using DEBUG_K Option...") - list(APPEND CLANG_DEFINES -DDEBUG_K) -endif() -set(CLANG_INCLUDES - -I "/usr/include" - -I "/usr/include/x86_64-linux-gnu" - -I "${CMAKE_SOURCE_DIR}" - -I "${CMAKE_BINARY_DIR}" - -I "${libbpf_SOURCE_DIR}/src" - ) + add_dependencies(procdump_ebpf libbpf) -add_custom_target(procdump_ebpf - DEPENDS procdump_ebpf.o - ) - -add_dependencies(procdump_ebpf libbpf) - -add_custom_command(OUTPUT procdump_ebpf.o - COMMAND "${CLANG}" -nostdinc -isystem `gcc -print-file-name=include` ${CLANG_INCLUDES} ${CLANG_DEFINES} -O2 ${CLANG_OPTIONS} -target bpf -fno-stack-protector -c "${procdump_ebpf_SOURCE_DIR}/procdump_ebpf.c" -o "procdump_ebpf.o" && bpftool gen object procdump.ebpf.o procdump_ebpf.o && bpftool gen skeleton "procdump.ebpf.o" name "procdump_ebpf" > "procdump_ebpf.skel.h" - COMMENT "Building EBPF object procdump_ebpf.o" - DEPENDS ${procdump_ebpf_SOURCE_DIR}/procdump_ebpf.c - ) + add_custom_command(OUTPUT procdump_ebpf.o + COMMAND "${CLANG}" -nostdinc -isystem `gcc -print-file-name=include` ${CLANG_INCLUDES} ${CLANG_DEFINES} -O2 ${CLANG_OPTIONS} -target bpf -fno-stack-protector -c "${procdump_ebpf_SOURCE_DIR}/procdump_ebpf.c" -o "procdump_ebpf.o" && bpftool gen object procdump.ebpf.o procdump_ebpf.o && bpftool gen skeleton "procdump.ebpf.o" name "procdump_ebpf" > "procdump_ebpf.skel.h" + COMMENT "Building EBPF object procdump_ebpf.o" + DEPENDS ${procdump_ebpf_SOURCE_DIR}/procdump_ebpf.c + ) -set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES procdump.ebpf.o) \ No newline at end of file + set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES procdump.ebpf.o) +endif() \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8bf219b..acaa8fb 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -66,4 +66,20 @@ stages: inputs: PathtoPublish: '$(Build.ArtifactStagingDirectory)/logs/procdumpprofiler.log' ArtifactName: 'procdumpprofiler.log' - publishLocation: 'Container' \ No newline at end of file + publishLocation: 'Container' + + - job: "ProcDump_Build_Mac_Run_Unit_Tests" + pool: + vmImage: "macOS-latest" + steps: + + - script: | + sw_vers + displayName: 'Diagnostics' + + - template: templates/build.yaml + + - script: | + cd procdump_build/tests/integration + sudo ./run.sh + displayName: 'Run unit tests' \ No newline at end of file diff --git a/include/CoreDumpWriter.h b/include/CoreDumpWriter.h index ebfdc7f..0703561 100644 --- a/include/CoreDumpWriter.h +++ b/include/CoreDumpWriter.h @@ -19,7 +19,10 @@ #include #include #include +#ifdef __linux__ #include +#elif __APPLE_ +#endif #define DATE_LENGTH 26 #define MAX_LINES 15 diff --git a/include/GenHelpers.h b/include/GenHelpers.h index d9c6229..d786224 100644 --- a/include/GenHelpers.h +++ b/include/GenHelpers.h @@ -10,7 +10,12 @@ #ifndef GENHELPERS_H #define GENHELPERS_H +#ifdef __linux__ #include +#elif __APPLE_ +#endif + +#include #include #include #include @@ -96,7 +101,9 @@ static inline void cancel_pthread(unsigned long* val) { if(*val!=-1) { + #ifdef __linux__ pthread_cancel(*val); + #endif } } diff --git a/include/Handle.h b/include/Handle.h index e702d6a..d3876a4 100644 --- a/include/Handle.h +++ b/include/Handle.h @@ -47,7 +47,7 @@ enum EHandleType { struct Handle { union { struct Event event; - sem_t semaphore; + sem_t* semaphore; }; enum EHandleType type; }; diff --git a/include/ProcDumpConfiguration.h b/include/ProcDumpConfiguration.h index 916ad24..88b1194 100644 --- a/include/ProcDumpConfiguration.h +++ b/include/ProcDumpConfiguration.h @@ -11,7 +11,9 @@ #define PROCDUMPCONFIGURATION_H #include +#ifdef __linux__ #include +#endif #include #include #include @@ -32,8 +34,10 @@ #include #include +#ifdef __linux__ #include "Restrack.h" #include "procdump_ebpf_common.h" +#endif #include @@ -75,7 +79,9 @@ struct ProcDumpConfiguration bool bProcessGroup; // -pgid char *ProcessName; +#ifdef __linux__ struct sysinfo SystemInfo; +#endif // Runtime Values int NumberOfDumpsCollecting; // Number of dumps we're collecting @@ -131,8 +137,10 @@ struct ProcDumpConfiguration // Keeps track of the memory allocations when -restrack is specified. // Access must be protected by memAllocMapMutex. // +#ifdef __linux__ std::unordered_map memAllocMap; pthread_mutex_t memAllocMapMutex; +#endif // multithreading // set max number of concurrent dumps on init (default to 1) diff --git a/include/Process.h b/include/Process.h index 189798f..b76004f 100644 --- a/include/Process.h +++ b/include/Process.h @@ -10,7 +10,10 @@ #ifndef PROCFSLIB_PROCESS_H #define PROCFSLIB_PROCESS_H +#ifdef __linux__ #include +#endif + #include #include #include @@ -291,5 +294,7 @@ bool LookupProcessByName(const char* procName); pid_t LookupProcessPidByName(const char* name); int GetMaximumPID(); int FilterForPid(const struct dirent *entry); +int GetCpuUsage(pid_t pid); +int GetRunningPids(pid_t** pids); #endif // PROCFSLIB_PROCESS_H \ No newline at end of file diff --git a/makePackages.sh b/makePackages.sh index 394bcd4..9b8d72c 100755 --- a/makePackages.sh +++ b/makePackages.sh @@ -39,6 +39,7 @@ PACKAGE_TYPE=$6 DEB_PACKAGE_NAME="${PACKAGE_NAME}_${PACKAGE_VER}_amd64" RPM_PACKAGE_NAME="${PACKAGE_NAME}-${PACKAGE_VER}-${PACKAGE_REL}" +BREW_PACKAGE_NAME="${PACKAGE_NAME}-mac-${PACKAGE_VER}" if [ "$PACKAGE_TYPE" = "deb" ]; then DPKGDEB=`which dpkg-deb` @@ -97,4 +98,9 @@ if [ "$PACKAGE_TYPE" = "rpm" ]; then fi fi -exit $RET +if [ "$PACKAGE_TYPE" = "brew" ]; then + + # create brew package + zip $PROJECT_BINARY_DIR/${BREW_PACKAGE_NAME}.zip procdump procdump.1.gz +fi +exit $RET \ No newline at end of file diff --git a/procdump.1 b/procdump.1 index b9e58dc..78c2bbd 100644 --- a/procdump.1 +++ b/procdump.1 @@ -50,4 +50,4 @@ Options: -pgid Process ID specified refers to a process group ID. .SH DESCRIPTION -procdump is a Linux reimagining of the class ProcDump tool from the Sysinternals suite of tools for Windows. Procdump provides a convenient way for Linux developers to create core dumps of their application based on performance triggers. +ProcDump provides a convenient way for Linux and Mac developers to create core dumps of their application based on performance triggers. ProcDump is part of Sysinternals. \ No newline at end of file diff --git a/procdump_mac.1 b/procdump_mac.1 new file mode 100644 index 0000000..da7c22a --- /dev/null +++ b/procdump_mac.1 @@ -0,0 +1,34 @@ +.\" Manpage for procdump. +.TH man 8 "2/5/2024" "1.0" "procdump manpage" +.SH NAME +procdump \- generate coredumps based off performance triggers. +.SH SYNOPSIS +procdump [-n Count] + [-s Seconds] + [-c|-cl CPU_Usage] + [-m|-ml Commit_Usage1[,Commit_Usage2...]] + [-tc Thread_Threshold] + [-fc FileDescriptor_Threshold] + [-pf Polling_Frequency] + [-o] + [-log syslog|stdout] + { + {{[-w] Process_Name} [Dump_File | Dump_Folder]} + } + +Options: + -n Number of dumps to write before exiting. + -s Consecutive seconds before dump is written (default is 10). + -c CPU threshold above which to create a dump of the process. + -cl CPU threshold below which to create a dump of the process. + -m Memory commit threshold(s) (MB) above which to create dumps. + -ml Memory commit threshold(s) (MB) below which to create dumps. + -tc Thread count threshold above which to create a dump of the process. + -fc File descriptor count threshold above which to create a dump of the process. + -pf Polling frequency. + -o Overwrite existing dump file. + -log Writes extended ProcDump tracing to the specified output stream (syslog or stdout). + -w Wait for the specified process to launch if it's not running. + +.SH DESCRIPTION +ProcDump provides a convenient way for Linux and Mac developers to create core dumps of their application based on performance triggers. ProcDump is part of Sysinternals. \ No newline at end of file diff --git a/src/CoreDumpWriter.cpp b/src/CoreDumpWriter.cpp index 3291263..7300ffd 100644 --- a/src/CoreDumpWriter.cpp +++ b/src/CoreDumpWriter.cpp @@ -161,7 +161,9 @@ char* WriteCoreDump(struct CoreDumpWriter *self) case WAIT_OBJECT_0+1: // We got a dump slot! { char* socketName = NULL; +#ifdef __linux__ IsCoreClrProcess(self->Config->ProcessId, &socketName); +#endif unsigned int currentCoreDumpFilter = -1; if(self->Config->CoreDumpMask != -1) { @@ -171,7 +173,7 @@ char* WriteCoreDump(struct CoreDumpWriter *self) if ((dumpFileName = WriteCoreDumpInternal(self, socketName)) != NULL) { // We're done here, unlock (increment) the sem - if(sem_post(&self->Config->semAvailableDumpSlots.semaphore) == -1) + if(sem_post(self->Config->semAvailableDumpSlots.semaphore) == -1) { Log(error, INTERNAL_ERROR); Trace("WriteCoreDump: failed sem_post."); @@ -234,14 +236,6 @@ char* WriteCoreDumpInternal(struct CoreDumpWriter *self, char* socketName) gcorePrefixName = GetCoreDumpPrefixName(self->Config->ProcessId, name, self->Config->CoreDumpPath, self->Config->CoreDumpName, self->Type); - // assemble the command - if(snprintf(command, BUFFER_LENGTH, "gcore -o %s %d 2>&1", gcorePrefixName, pid) < 0) - { - Log(error, INTERNAL_ERROR); - Trace("WriteCoreDumpInternal: failed sprintf gcore command"); - exit(-1); - } - // assemble filename if(snprintf(coreDumpFileName, PATH_MAX, "%s.%d", gcorePrefixName, pid) < 0) { @@ -257,6 +251,14 @@ char* WriteCoreDumpInternal(struct CoreDumpWriter *self, char* socketName) return NULL; } + // assemble the command + if(snprintf(command, BUFFER_LENGTH, "gcore -o %s %d 2>&1", coreDumpFileName, pid) < 0) + { + Log(error, INTERNAL_ERROR); + Trace("WriteCoreDumpInternal: failed sprintf gcore command"); + exit(-1); + } + // check if we're allowed to write into the target directory if(access(self->Config->CoreDumpPath, W_OK) < 0) { @@ -268,6 +270,7 @@ char* WriteCoreDumpInternal(struct CoreDumpWriter *self, char* socketName) if(socketName!=NULL) { +#ifdef __linux__ // If we have a socket name, we're dumping a .NET process.... if(GenerateCoreClrDump(socketName, coreDumpFileName)==false) { @@ -280,6 +283,7 @@ char* WriteCoreDumpInternal(struct CoreDumpWriter *self, char* socketName) self->Config->NumberOfDumpsCollected++; // safe to increment in crit section } +#endif } else { @@ -330,12 +334,23 @@ char* WriteCoreDumpInternal(struct CoreDumpWriter *self, char* socketName) // close pipe reading from gcore self->Config->gcorePid = NO_PID; // reset gcore pid so that signal handler knows we aren't dumping - int pcloseStatus = pclose(commandPipe); + int pcloseStatus = 0; +#ifdef __linux__ + pcloseStatus = pclose(commandPipe); +#endif bool gcoreFailedMsg = false; // in case error sneaks through the message output // check if gcore was able to generate the dump - if(gcoreStatus != 0 || pcloseStatus != 0 || (gcoreFailedMsg = (strstr(outputBuffer[i-1], "gcore: failed") != NULL))) + if(outputBuffer[i-1] != NULL) + { + if(strstr(outputBuffer[i-1], "gcore: failed") != NULL) + { + gcoreFailedMsg = true; + } + } + + if(gcoreStatus != 0 || pcloseStatus != 0 || (gcoreFailedMsg == true)) { Log(error, "An error occurred while generating the core dump:"); if (gcoreStatus != 0) @@ -358,7 +373,6 @@ char* WriteCoreDumpInternal(struct CoreDumpWriter *self, char* socketName) { // On WSL2 there is a delay between the core dump being written to disk and able to succesfully access it in the below check sleep(1); - // validate that core dump file was generated if(access(coreDumpFileName, F_OK) != -1) { @@ -395,7 +409,7 @@ char* WriteCoreDumpInternal(struct CoreDumpWriter *self, char* socketName) free(name); - + return strdup(coreDumpFileName); } diff --git a/src/Events.cpp b/src/Events.cpp index e06fecc..867cac4 100644 --- a/src/Events.cpp +++ b/src/Events.cpp @@ -78,7 +78,7 @@ void InitNamedEvent(struct Event *Event, bool IsManualReset, bool InitialState, Event->nWaiters = 0; if (Name == NULL) { - sprintf(Event->Name, "Unnamed Event %d", ++unamedEventId); + snprintf(Event->Name, sizeof(Event->Name), "Unnamed Event %d", ++unamedEventId); } else if (strlen(Name) >= MAX_EVENT_NAME) { strncpy(Event->Name, Name, MAX_EVENT_NAME); Event->Name[MAX_EVENT_NAME - 1] = '\0'; // null terminate diff --git a/src/GenHelpers.cpp b/src/GenHelpers.cpp index 7624836..4435053 100644 --- a/src/GenHelpers.cpp +++ b/src/GenHelpers.cpp @@ -7,7 +7,9 @@ // //-------------------------------------------------------------------- #include "Includes.h" +#ifdef __linux__ #include +#endif //-------------------------------------------------------------------- // @@ -499,7 +501,7 @@ char* GetSocketPath(char* prefix, pid_t pid, pid_t targetPid) return NULL; } - sprintf(t, "/tmp/%s%d-%d", prefix, pid, targetPid); + snprintf(t, len+1, "/tmp/%s%d-%d", prefix, pid, targetPid); } else { @@ -510,7 +512,7 @@ char* GetSocketPath(char* prefix, pid_t pid, pid_t targetPid) return NULL; } - sprintf(t, "/tmp/%s%d", prefix, pid); + snprintf(t, len+1, "/tmp/%s%d", prefix, pid); } } else @@ -524,7 +526,7 @@ char* GetSocketPath(char* prefix, pid_t pid, pid_t targetPid) return NULL; } - sprintf(t, "%s/%s%d-%d", prefixTmpFolder, prefix, pid, targetPid); + snprintf(t, len+1, "%s/%s%d-%d", prefixTmpFolder, prefix, pid, targetPid); } else { @@ -535,7 +537,7 @@ char* GetSocketPath(char* prefix, pid_t pid, pid_t targetPid) return NULL; } - sprintf(t, "%s/%s%d", prefixTmpFolder, prefix, pid); + snprintf(t, len+1, "%s/%s%d", prefixTmpFolder, prefix, pid); } } diff --git a/src/Handle.cpp b/src/Handle.cpp index 2fee721..07443d1 100644 --- a/src/Handle.cpp +++ b/src/Handle.cpp @@ -63,9 +63,40 @@ int WaitForSingleObject(struct Handle *Handle, int Milliseconds) break; case SEMAPHORE: - rc = (Milliseconds == INFINITE_WAIT) ? - sem_wait(&(Handle->semaphore)) : - sem_timedwait(&(Handle->semaphore), &ts); + if(Milliseconds == INFINITE_WAIT) + { + sem_wait(Handle->semaphore); + } + else + { +#ifdef __linux__ + sem_timedwait(Handle->semaphore, &ts); +#elif __APPLE__ + struct timespec now, sleep_time; + while(1) + { + if (sem_trywait(Handle->semaphore) == 0) + { + return 0; // Successfully acquired the semaphore + } + + clock_gettime(CLOCK_REALTIME, &now); + + // Check if the timeout has expired + if ((now.tv_sec > ts.tv_sec) || + (now.tv_sec == ts.tv_sec && now.tv_nsec >= ts.tv_nsec)) + { + break; + } + + // Calculate the time to sleep + sleep_time.tv_sec = 0; + sleep_time.tv_nsec = 1000000; // 1 millisecond + nanosleep(&sleep_time, NULL); + } +#endif + } + break; default: diff --git a/src/Logging.cpp b/src/Logging.cpp index ca7918f..b4ea374 100644 --- a/src/Logging.cpp +++ b/src/Logging.cpp @@ -37,8 +37,8 @@ void LogFormatter(enum LogLevel logLevel, enum DiagnosticsLogTarget target, cons return; } - sprintf(trace, "[%s - %s]: ", timeBuff, LogLevelStrings[logLevel]); - vsprintf(trace+traceLen, message, args); + snprintf(trace, traceLen+argsLen, "[%s - %s]: ", timeBuff, LogLevelStrings[logLevel]); + vsnprintf(trace+traceLen, traceLen+argsLen, message, args); // If a log entry is not 'debug' it simply goes to stdout. // If you want an entry to only go to the syslog, use 'debug' diff --git a/src/Monitor.cpp b/src/Monitor.cpp index 1b3f07e..c89b26b 100644 --- a/src/Monitor.cpp +++ b/src/Monitor.cpp @@ -7,7 +7,9 @@ // //-------------------------------------------------------------------- #define _Bool bool +#ifdef __linux__ #include "procdump_ebpf.skel.h" +#endif #include "Includes.h" @@ -15,6 +17,10 @@ #include #include +#ifdef __APPLE__ +#include +#endif + static pthread_t sig_thread_id; extern struct ProcDumpConfiguration g_config; @@ -103,7 +109,9 @@ void* SignalThread(void *input) if(it->second->Threads[i].trigger == Signal) { pthread_mutex_lock(&it->second->ptrace_mutex); +#ifdef __linux__ ptrace(PTRACE_DETACH, it->second->ProcessId, 0, 0); +#endif pthread_mutex_unlock(&it->second->ptrace_mutex); if ((rc = pthread_cancel(it->second->Threads[i].thread)) != 0) { @@ -211,16 +219,7 @@ void MonitorProcesses(struct ProcDumpConfiguration *self) // allocate list of configs for process monitoring int numMonitoredProcesses = 0; - // create binary map to track processes we have already tracked and closed - int maxPid = GetMaximumPID(); - if(maxPid < 0) - { - Log(error, INTERNAL_ERROR); - Trace("Unable to get MAX_PID value\n"); - return; - } - - monitoredProcessMap.reserve(maxPid); + monitoredProcessMap.reserve(5000); // assume 5000 processes // Create a signal handler thread where we handle shutdown as a result of SIGINT. // Note: We only create ONE per instance of procdump rather than per monitor. @@ -314,15 +313,24 @@ void MonitorProcesses(struct ProcDumpConfiguration *self) } // Iterate over all running processes +#ifdef __linux__ struct dirent ** nameList; int numEntries = scandir("/proc/", &nameList, FilterForPid, alphasort); +#else + pid_t *nameList; + int numEntries = GetRunningPids(&nameList); +#endif for (int i = 0; i < numEntries; i++) { pid_t procPid; +#ifdef __linux__ if(!ConvertToInt(nameList[i]->d_name, &procPid)) { continue; } +#else + procPid = nameList[i]; +#endif if(self->bProcessGroup) { @@ -391,10 +399,12 @@ void MonitorProcesses(struct ProcDumpConfiguration *self) } // clean up namelist +#ifdef __linux__ for (int i = 0; i < numEntries; i++) { free(nameList[i]); } +#endif if(numEntries!=-1) { free(nameList); @@ -766,6 +776,7 @@ int WaitForAllMonitorsToTerminate(struct ProcDumpConfiguration *self) // // If we have a restrack thread, cancel it and wait for it to exit // +#ifdef __linux__ if(CancelRestrackThread(self) != 0) { if ((rc = pthread_join(restrackThread, NULL)) != 0) @@ -774,6 +785,7 @@ int WaitForAllMonitorsToTerminate(struct ProcDumpConfiguration *self) exit(-1); } } +#endif return rc; } @@ -938,9 +950,13 @@ void *CommitMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* { if (GetProcessStat(config->ProcessId, &proc)) { +#ifdef __linux__ // Calc Commit memUsage = (proc.rss * pageSize_kb) >> 10; // get Resident Set Size memUsage += (proc.nswap * pageSize_kb) >> 10; // get Swap size +#elif __APPLE__ + memUsage = proc.rss / (1024.0 * 1024.0); // get Resident Set Size +#endif // Commit Trigger if ((config->bMemoryTriggerBelowValue && (memUsage < config->MemoryThreshold[config->MemoryCurrentThreshold])) || @@ -961,6 +977,7 @@ void *CommitMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* // // Check to see if restrack is specified, if so, save current resource usage to file. // +#ifdef __linux__ if(config->bRestrackEnabled == true) { pthread_t id = WriteRestrackSnapshot(config, writer->Type); @@ -973,6 +990,7 @@ void *CommitMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* leakReportThreads.push_back(id); } } +#endif config->MemoryCurrentThreshold++; @@ -985,7 +1003,7 @@ void *CommitMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* } else { - Log(error, "An error occurred while parsing procfs\n"); + Log(error, "An error occurred while fetching memory info\n"); exit(-1); } } @@ -1041,6 +1059,7 @@ void* ThreadCountMonitoringThread(void *thread_args /* struct ProcDumpConfigurat // // Check to see if restrack is specified, if so, save current resource usage to file. // +#ifdef __linux__ if(config->bRestrackEnabled == true) { pthread_t id = WriteRestrackSnapshot(config, writer->Type); @@ -1053,6 +1072,7 @@ void* ThreadCountMonitoringThread(void *thread_args /* struct ProcDumpConfigurat leakReportThreads.push_back(id); } } +#endif if ((rc = WaitForQuit(config, config->ThresholdSeconds * 1000)) != WAIT_TIMEOUT) { @@ -1105,7 +1125,6 @@ void* FileDescriptorCountMonitoringThread(void *thread_args /* struct ProcDumpCo { if (proc.num_filedescriptors >= config->FileDescriptorThreshold) { - Log(info, "Trigger: File descriptors:%ld on process ID: %d", proc.num_filedescriptors, config->ProcessId); if(config->bRestrackGenerateDump == true) { // Only generate core dump if user did not specify the "nodump" restrack option @@ -1119,6 +1138,7 @@ void* FileDescriptorCountMonitoringThread(void *thread_args /* struct ProcDumpCo // // Check to see if restrack is specified, if so, save current resource usage to file. // +#ifdef __linux__ if(config->bRestrackEnabled == true) { pthread_t id = WriteRestrackSnapshot(config, writer->Type); @@ -1131,6 +1151,7 @@ void* FileDescriptorCountMonitoringThread(void *thread_args /* struct ProcDumpCo leakReportThreads.push_back(id); } } +#endif if ((rc = WaitForQuit(config, config->ThresholdSeconds * 1000)) != WAIT_TIMEOUT) { @@ -1150,7 +1171,6 @@ void* FileDescriptorCountMonitoringThread(void *thread_args /* struct ProcDumpCo // Wait for the leak reporting threads to finish // WaitThreads(leakReportThreads); - Trace("FileDescriptorCountMonitoringThread: Exit [id=%d]", gettid()); return NULL; } @@ -1168,6 +1188,7 @@ void* FileDescriptorCountMonitoringThread(void *thread_args /* struct ProcDumpCo void* SignalMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */) { Trace("SignalMonitoringThread: Enter [id=%d]", gettid()); +#ifdef __linux__ struct ProcDumpConfiguration *config = (struct ProcDumpConfiguration *)thread_args; int wstatus; int signum=-1; @@ -1283,7 +1304,7 @@ void* SignalMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* // Wait for the leak reporting threads to finish // WaitThreads(leakReportThreads); - +#endif Trace("SignalMonitoringThread: Exit [id=%d]", gettid()); return NULL; } @@ -1300,11 +1321,12 @@ void *CpuMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */) unsigned long totalTime = 0; unsigned long elapsedTime = 0; - struct sysinfo sysInfo; int cpuUsage; auto_free struct CoreDumpWriter *writer = NULL; auto_free char* dumpFileName = NULL; +#ifdef __linux__ std::vector leakReportThreads; +#endif writer = NewCoreDumpWriter(CPU, config); @@ -1315,14 +1337,10 @@ void *CpuMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */) { while ((rc = WaitForQuit(config, config->PollingInterval)) == WAIT_TIMEOUT) { - sysinfo(&sysInfo); - if (GetProcessStat(config->ProcessId, &proc)) { - // Calc CPU - totalTime = (unsigned long)((proc.utime + proc.stime) / HZ); - elapsedTime = (unsigned long)(sysInfo.uptime - (long)(proc.starttime / HZ)); - cpuUsage = (int)(100 * ((double)totalTime / elapsedTime)); + cpuUsage = GetCpuUsage(config->ProcessId); + Trace("CpuMonitoringThread: CPU usage:%d%% on process ID: %d", cpuUsage, config->ProcessId); // CPU Trigger if ((config->bCpuTriggerBelowValue && (cpuUsage < config->CpuThreshold)) || @@ -1342,6 +1360,7 @@ void *CpuMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */) // // Check to see if restrack is specified, if so, save current resource usage to file. // +#ifdef __linux__ if(config->bRestrackEnabled == true) { pthread_t id = WriteRestrackSnapshot(config, writer->Type); @@ -1354,6 +1373,7 @@ void *CpuMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */) leakReportThreads.push_back(id); } } +#endif if ((rc = WaitForQuit(config, config->ThresholdSeconds * 1000)) != WAIT_TIMEOUT) { @@ -1372,9 +1392,11 @@ void *CpuMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */) // // Wait for the leak reporting threads to finish // +#ifdef __linux__ WaitThreads(leakReportThreads); +#endif - Trace("CpuTCpuMonitoringThread: Exit [id=%d]", gettid()); + Trace("CpuMonitoringThread: Exit [id=%d]", gettid()); return NULL; } @@ -1415,6 +1437,7 @@ void *TimerThread(void *thread_args /* struct ProcDumpConfiguration* */) // // Check to see if restrack is specified, if so, save current resource usage to file. // +#ifdef __linux__ if(config->bRestrackEnabled == true) { pthread_t id = WriteRestrackSnapshot(config, writer->Type); @@ -1427,6 +1450,7 @@ void *TimerThread(void *thread_args /* struct ProcDumpConfiguration* */) leakReportThreads.push_back(id); } } +#endif if ((rc = WaitForQuit(config, config->ThresholdSeconds * 1000)) != WAIT_TIMEOUT) { break; @@ -1457,6 +1481,7 @@ void *TimerThread(void *thread_args /* struct ProcDumpConfiguration* */) void *DotNetMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* */) { Trace("DotNetMonitoringThread: Enter [id=%d]", gettid()); +#ifdef __linux__ struct ProcDumpConfiguration *config = (struct ProcDumpConfiguration *)thread_args; auto_free char* fullDumpPath = NULL; auto_cancel_thread pthread_t waitForProfilerCompletion = -1; @@ -1551,7 +1576,7 @@ void *DotNetMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* pthread_join(waitForProfilerCompletion, NULL); } - +#endif Trace("DotNetMonitoringThread: Exit [id=%d]", gettid()); return NULL; } @@ -1564,6 +1589,7 @@ void *DotNetMonitoringThread(void *thread_args /* struct ProcDumpConfiguration* void *RestrackThread(void *thread_args /* struct ProcDumpConfiguration* */) { Trace("RestrackThread: Enter [id=%d]", gettid()); +#ifdef __linux__ struct ProcDumpConfiguration *config = (struct ProcDumpConfiguration *)thread_args; auto_free char* fullDumpPath = NULL; struct procdump_ebpf* skel = NULL; @@ -1611,6 +1637,7 @@ void *RestrackThread(void *thread_args /* struct ProcDumpConfiguration* */) } } +#endif Trace("RestrackThread: Exit [id=%d]", gettid()); return NULL; } @@ -1627,6 +1654,7 @@ char* GetClientData(struct ProcDumpConfiguration *self, char* fullDumpPath) { Trace("GetClientData: Entering GetClientData"); char* clientData = NULL; +#ifdef __linux__ auto_free char* exceptionFilter = NULL; auto_free char* thresholds = NULL; @@ -1680,6 +1708,7 @@ char* GetClientData(struct ProcDumpConfiguration *self, char* fullDumpPath) return NULL; } +#endif Trace("GetClientData: Exiting GetClientData"); return clientData; } @@ -1713,8 +1742,8 @@ char* GetClientDataHelper(enum TriggerType triggerType, char* path, const char* return NULL; } - sprintf(clientData, "%d;%s;%d;", triggerType, path, getpid()); - vsprintf(clientData+clientDataPrefixSize, format, args); + snprintf(clientData, clientDataSize, "%d;%s;%d;", triggerType, path, getpid()); + vsnprintf(clientData+clientDataPrefixSize, clientDataSize, format, args); va_end(args); return clientData; @@ -1797,6 +1826,7 @@ char* GetThresholds(struct ProcDumpConfiguration *self) void *WaitForProfilerCompletion(void *thread_args /* struct ProcDumpConfiguration* */) { Trace("WaitForProfilerCompletion: Enter [id=%d]", gettid()); +#ifdef __linux__ struct ProcDumpConfiguration *config = (struct ProcDumpConfiguration *)thread_args; unsigned int t, s2; struct sockaddr_un local, remote; @@ -2018,7 +2048,7 @@ void *WaitForProfilerCompletion(void *thread_args /* struct ProcDumpConfiguratio config->socketPath = NULL; ExitProcessMonitor(config, processMonitor); - +#endif Trace("WaitForProfilerCompletion: Exiting WaitForProfilerCompletion Thread [id=%d]", gettid()); return NULL; } diff --git a/src/ProcDumpConfiguration.cpp b/src/ProcDumpConfiguration.cpp index d8ef6f6..9d5451a 100644 --- a/src/ProcDumpConfiguration.cpp +++ b/src/ProcDumpConfiguration.cpp @@ -87,7 +87,7 @@ void InitProcDump() exit(-1); } - sprintf(t, "%s%s", prefixTmpFolder, "/procdump"); + snprintf(t, len, "%s%s", prefixTmpFolder, "/procdump"); createDir(t, 0777); free(t); } @@ -121,10 +121,14 @@ void InitProcDumpConfiguration(struct ProcDumpConfiguration *self) MAXIMUM_CPU = 100 * (int)sysconf(_SC_NPROCESSORS_ONLN); HZ = sysconf(_SC_CLK_TCK); +#ifdef __linux__ sysinfo(&(self->SystemInfo)); +#endif +#ifdef __linux__ pthread_mutex_init(&self->ptrace_mutex, NULL); pthread_mutex_init(&self->memAllocMapMutex, NULL); +#endif InitNamedEvent(&(self->evtCtrlHandlerCleanupComplete.event), true, false, const_cast("CtrlHandlerCleanupComplete")); self->evtCtrlHandlerCleanupComplete.type = EVENT; @@ -144,7 +148,8 @@ void InitProcDumpConfiguration(struct ProcDumpConfiguration *self) InitNamedEvent(&(self->evtStartMonitoring.event), true, false, const_cast("StartMonitoring")); self->evtStartMonitoring.type = EVENT; - sem_init(&(self->semAvailableDumpSlots.semaphore), 0, 1); + //sem_init(&(self->semAvailableDumpSlots.semaphore), 0, 1); + self->semAvailableDumpSlots.semaphore = sem_open("/procdump_sem", O_CREAT, 0644, 1); self->semAvailableDumpSlots.type = SEMAPHORE; // Additional initialization @@ -193,10 +198,12 @@ void InitProcDumpConfiguration(struct ProcDumpConfiguration *self) pthread_mutex_init(&self->dotnetMutex, NULL); pthread_cond_init(&self->dotnetCond, NULL); +#ifdef __linux__ if(self->memAllocMap.size() > 0) { self->memAllocMap.clear(); } +#endif } //-------------------------------------------------------------------- @@ -215,8 +222,13 @@ void FreeProcDumpConfiguration(struct ProcDumpConfiguration *self) DestroyEvent(&(self->evtStartMonitoring.event)); pthread_mutex_destroy(&self->ptrace_mutex); +#ifdef __linux__ pthread_mutex_destroy(&self->memAllocMapMutex); - sem_destroy(&(self->semAvailableDumpSlots.semaphore)); +#endif + //sem_destroy(&(self->semAvailableDumpSlots.semaphore)); + sem_close(self->semAvailableDumpSlots.semaphore); + sem_unlink("/procdump_sem"); + pthread_mutex_destroy(&self->dotnetMutex); pthread_cond_destroy(&self->dotnetCond); @@ -276,6 +288,7 @@ void FreeProcDumpConfiguration(struct ProcDumpConfiguration *self) self->SignalNumber = NULL; } +#ifdef __linux__ for (const auto& pair : self->memAllocMap) { if(pair.second) @@ -284,6 +297,7 @@ void FreeProcDumpConfiguration(struct ProcDumpConfiguration *self) } } self->memAllocMap.clear(); +#endif Trace("FreeProcDumpConfiguration: Exit"); } @@ -392,8 +406,9 @@ struct ProcDumpConfiguration * CopyProcDumpConfiguration(struct ProcDumpConfigur copy->socketPath = self->socketPath == NULL ? NULL : strdup(self->socketPath); copy->bDumpOnException = self->bDumpOnException; copy->statusSocket = self->statusSocket; +#ifdef __linux__ copy->memAllocMap = self->memAllocMap; - +#endif return copy; } else @@ -473,6 +488,7 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) i++; } +#ifdef __linux__ else if( 0 == strcasecmp( argv[i], "/gcm" ) || 0 == strcasecmp( argv[i], "-gcm" )) { @@ -602,32 +618,6 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) i++; } - else if( 0 == strcasecmp( argv[i], "/tc" ) || - 0 == strcasecmp( argv[i], "-tc" )) - { - if( i+1 >= argc || self->ThreadThreshold != -1 ) return PrintUsage(); - if(!ConvertToInt(argv[i+1], &self->ThreadThreshold)) return PrintUsage(); - if(self->ThreadThreshold < 0) - { - Log(error, "Invalid thread threshold count specified."); - return PrintUsage(); - } - - i++; - } - else if( 0 == strcasecmp( argv[i], "/fc" ) || - 0 == strcasecmp( argv[i], "-fc" )) - { - if( i+1 >= argc || self->FileDescriptorThreshold != -1 ) return PrintUsage(); - if(!ConvertToInt(argv[i+1], &self->FileDescriptorThreshold)) return PrintUsage(); - if(self->FileDescriptorThreshold < 0) - { - Log(error, "Invalid file descriptor threshold count specified."); - return PrintUsage(); - } - - i++; - } else if( 0 == strcasecmp( argv[i], "/sig" ) || 0 == strcasecmp( argv[i], "-sig" )) { @@ -660,6 +650,33 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) return PrintUsage(); } + i++; + } +#endif + else if( 0 == strcasecmp( argv[i], "/tc" ) || + 0 == strcasecmp( argv[i], "-tc" )) + { + if( i+1 >= argc || self->ThreadThreshold != -1 ) return PrintUsage(); + if(!ConvertToInt(argv[i+1], &self->ThreadThreshold)) return PrintUsage(); + if(self->ThreadThreshold < 0) + { + Log(error, "Invalid thread threshold count specified."); + return PrintUsage(); + } + + i++; + } + else if( 0 == strcasecmp( argv[i], "/fc" ) || + 0 == strcasecmp( argv[i], "-fc" )) + { + if( i+1 >= argc || self->FileDescriptorThreshold != -1 ) return PrintUsage(); + if(!ConvertToInt(argv[i+1], &self->FileDescriptorThreshold)) return PrintUsage(); + if(self->FileDescriptorThreshold < 0) + { + Log(error, "Invalid file descriptor threshold count specified."); + return PrintUsage(); + } + i++; } else if( 0 == strcasecmp( argv[i], "/pf" ) || @@ -728,6 +745,7 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) i++; } +#ifdef __linux__ else if( 0 == strcasecmp( argv[i], "/e" ) || 0 == strcasecmp( argv[i], "-e" )) { @@ -786,6 +804,7 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) i++; } +#endif else if( 0 == strcasecmp( argv[i], "/o" ) || 0 == strcasecmp( argv[i], "-o" )) { @@ -796,11 +815,13 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) { self->WaitingForProcessName = true; } +#ifdef __linux__ else if( 0 == strcasecmp( argv[i], "/pgid" ) || 0 == strcasecmp( argv[i], "-pgid" )) { self->bProcessGroup = true; } +#endif else { // Process targets @@ -915,12 +936,14 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) // Validate multi arguments // +#ifdef __linux__ // .NET triggers are mutually exclusive if(dotnetTriggerCount > 1) { Log(error, "Only one .NET trigger can be specified."); return PrintUsage(); } +#endif // Ensure consistency between number of thresholds specified and the -n switch if(self->MemoryThresholdCount > 1 && self->NumberOfDumpsToCollect != -1) @@ -934,6 +957,7 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) self->NumberOfDumpsToCollect = self->MemoryThresholdCount; } +#ifdef __linux__ // If exception filter is provided with no -e switch exit if((self->ExceptionFilter && self->bDumpOnException == false)) { @@ -955,7 +979,7 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) Log(error, "Please use the -restrack switch when specifying an exclude filter (-fx)"); return PrintUsage(); } - +#endif // If no path was provided, assume the current directory if (self->CoreDumpPath == NULL) { self->CoreDumpPath = strdup("."); @@ -986,6 +1010,7 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) self->bTimerThreshold = true; } +#ifdef __linux__ // Signal trigger can only be specified alone if(self->SignalCount > 0 || self->bDumpOnException) { @@ -1004,7 +1029,7 @@ int GetOptions(struct ProcDumpConfiguration *self, int argc, char *argv[]) // be attached via ptrace. self->bTimerThreshold = false; } - +#endif // If we are monitoring multiple process, setting dump name doesn't make sense (path is OK) if ((self->bProcessGroup || self->WaitingForProcessName) && self->CoreDumpName) { @@ -1097,6 +1122,28 @@ bool PrintConfiguration(struct ProcDumpConfiguration *self) { printf("%-40s%s\n", "Commit Threshold:", "n/a"); } + + // Thread + if (self->ThreadThreshold != -1) + { + printf("%-40s%d\n", "Thread Threshold:", self->ThreadThreshold); + } + else + { + printf("%-40s%s\n", "Thread Threshold:", "n/a"); + } + + // File descriptor + if (self->FileDescriptorThreshold != -1) + { + printf("%-40s%d\n", "File Descriptor Threshold:", self->FileDescriptorThreshold); + } + else + { + printf("%-40s%s\n", "File Descriptor Threshold:", "n/a"); + } + +#ifdef __linux__ // GC Generation if (self->DumpGCGeneration != -1) { @@ -1134,27 +1181,6 @@ bool PrintConfiguration(struct ProcDumpConfiguration *self) printf("%-40s%s\n", "Resource tracking:", "n/a"); printf("%-40s%s\n", "Resource tracking sample rate:", "n/a"); } - - // Thread - if (self->ThreadThreshold != -1) - { - printf("%-40s%d\n", "Thread Threshold:", self->ThreadThreshold); - } - else - { - printf("%-40s%s\n", "Thread Threshold:", "n/a"); - } - - // File descriptor - if (self->FileDescriptorThreshold != -1) - { - printf("%-40s%d\n", "File Descriptor Threshold:", self->FileDescriptorThreshold); - } - else - { - printf("%-40s%s\n", "File Descriptor Threshold:", "n/a"); - } - // Signal if (self->SignalCount > 0) { @@ -1191,6 +1217,7 @@ bool PrintConfiguration(struct ProcDumpConfiguration *self) { printf("%-40s%s\n", "Exclude filter:", self->ExcludeFilter); } +#endif // Polling inverval printf("%-40s%d\n", "Polling Interval (ms):", self->PollingInterval); @@ -1244,22 +1271,28 @@ int PrintUsage() printf(" [-s Seconds]\n"); printf(" [-c|-cl CPU_Usage]\n"); printf(" [-m|-ml Commit_Usage1[,Commit_Usage2...]]\n"); + printf(" [-tc Thread_Threshold]\n"); + printf(" [-fc FileDescriptor_Threshold]\n"); +#ifdef __linux__ printf(" [-gcm [: | LOH: | POH:]Memory_Usage1[,Memory_Usage2...]]\n"); printf(" [-gcgen Generation]\n"); printf(" [-restrack [nodump]]\n"); printf(" [-sr Sample_Rate]\n"); - printf(" [-tc Thread_Threshold]\n"); - printf(" [-fc FileDescriptor_Threshold]\n"); printf(" [-sig Signal_Number1[,Signal_Number2...]]\n"); printf(" [-e]\n"); printf(" [-f Include_Filter,...]\n"); printf(" [-fx Exclude_Filter]\n"); printf(" [-mc Custom_Dump_Mask]\n"); +#endif printf(" [-pf Polling_Frequency]\n"); printf(" [-o]\n"); printf(" [-log syslog|stdout]\n"); printf(" {\n"); +#ifdef __linux__ printf(" {{[-w] Process_Name | [-pgid] PID} [Dump_File | Dump_Folder]}\n"); +#elif defined(__APPLE__) + printf(" {{[-w] Process_Name | PID} [Dump_File | Dump_Folder]}\n"); +#endif printf(" }\n"); printf("\n"); printf("Options:\n"); @@ -1267,24 +1300,26 @@ int PrintUsage() printf(" -s Consecutive seconds before dump is written (default is 10).\n"); printf(" -c CPU threshold above which to create a dump of the process.\n"); printf(" -cl CPU threshold below which to create a dump of the process.\n"); + printf(" -tc Thread count threshold above which to create a dump of the process.\n"); + printf(" -fc File descriptor count threshold above which to create a dump of the process.\n"); +#ifdef __linux__ printf(" -m Memory commit threshold(s) (MB) above which to create dumps.\n"); printf(" -ml Memory commit threshold(s) (MB) below which to create dumps.\n"); printf(" -gcm [.NET] GC memory threshold(s) (MB) above which to create dumps for the specified generation or heap (default is total .NET memory usage).\n"); printf(" -gcgen [.NET] Create dump when the garbage collection of the specified generation starts and finishes.\n"); printf(" -restrack Enable memory leak tracking (malloc family of APIs). Use the nodump option to prevent dump generation and only produce restrack report(s).\n"); printf(" -sr Sample rate when using -restrack.\n"); - printf(" -tc Thread count threshold above which to create a dump of the process.\n"); - printf(" -fc File descriptor count threshold above which to create a dump of the process.\n"); printf(" -sig Comma separated list of signal number(s) during which any signal results in a dump of the process.\n"); printf(" -e [.NET] Create dump when the process encounters an exception.\n"); printf(" -f Filter (include) on the content of .NET exceptions (comma separated). Wildcards (*) are supported.\n"); printf(" -fx Filter (exclude) on the content of -restrack call stacks. Wildcards (*) are supported.\n"); printf(" -mc Custom core dump mask (in hex) indicating what memory should be included in the core dump. Please see 'man core' (/proc/[pid]/coredump_filter) for available options.\n"); + printf(" -pgid Process ID specified refers to a process group ID.\n"); +#endif printf(" -pf Polling frequency.\n"); printf(" -o Overwrite existing dump file.\n"); printf(" -log Writes extended ProcDump tracing to the specified output stream (syslog or stdout).\n"); printf(" -w Wait for the specified process to launch if it's not running.\n"); - printf(" -pgid Process ID specified refers to a process group ID.\n"); return -1; } diff --git a/src/Process.cpp b/src/Process.cpp index 82b33fa..2f13972 100644 --- a/src/Process.cpp +++ b/src/Process.cpp @@ -12,6 +12,14 @@ #include #include +#ifdef __APPLE__ +#include +#include +#include +#endif + +extern long HZ; + //-------------------------------------------------------------------- // // GetUids - Gets the process uids for the given pid @@ -56,6 +64,7 @@ bool GetUids(pid_t pid, struct ProcessStat* proc) //-------------------------------------------------------------------- bool GetNumFileDescriptors(pid_t pid, struct ProcessStat* proc) { +#ifdef __linux__ auto_free_dir DIR* fddir = NULL; struct dirent* entry = NULL; char procFilePath[32]; @@ -82,9 +91,49 @@ bool GetNumFileDescriptors(pid_t pid, struct ProcessStat* proc) } proc->num_filedescriptors-=2; // Account for "." and ".." +#elif __APPLE__ + int size = size = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0); + if (size <= 0) + { + Trace("[GetNumFileDescriptors] Failed to get required size for process information for pid: %d", pid); + return false; + } + + struct proc_fdinfo* fdInfo = (struct proc_fdinfo *)malloc(size); + if (fdInfo == NULL) + { + Trace("[GetNumFileDescriptors] Failed to alloc mem"); + return -1; + } + + int ret = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fdInfo, size); + if (ret <= 0) + { + Trace("[GetNumFileDescriptors] Failed to get process information for pid: %d", pid); + free(fdInfo); + return false; + } + + proc->num_filedescriptors = ret/sizeof(struct proc_fdinfo); + + free(fdInfo); +#endif + return true; +} + +#ifdef __APPLE__ +bool GetTaskInfo(struct proc_taskallinfo* taskInfo, pid_t pid) +{ + int ret = proc_pidinfo(pid, PROC_PIDTASKALLINFO, 0, taskInfo, sizeof(struct proc_taskallinfo)); + if (ret <= 0) + { + Trace("[GetTaskInfo] Failed to get process information for pid: %d", pid); + return false; + } return true; } +#endif //-------------------------------------------------------------------- // @@ -92,6 +141,7 @@ bool GetNumFileDescriptors(pid_t pid, struct ProcessStat* proc) // //-------------------------------------------------------------------- bool GetProcessStat(pid_t pid, struct ProcessStat *proc) { +#ifdef __linux__ char procFilePath[32]; char fileBuffer[1024]; char *token; @@ -105,6 +155,15 @@ bool GetProcessStat(pid_t pid, struct ProcessStat *proc) { Log(error, "Failed to get UID's"); return false; } +#endif + +#ifdef __APPLE__ + struct proc_taskallinfo taskInfo; + if(GetTaskInfo(&taskInfo, pid) == false) + { + return false; + } +#endif // Get number of file descriptors in /proc/%d/fdinfo. This directory only contains sub directories for each file descriptor. if(GetNumFileDescriptors(pid, proc) == false) @@ -113,6 +172,7 @@ bool GetProcessStat(pid_t pid, struct ProcessStat *proc) { return false; } +#ifdef __linux__ // Read /proc/[pid]/stat if(sprintf(procFilePath, "/proc/%d/stat", pid) < 0){ return false; @@ -291,7 +351,13 @@ bool GetProcessStat(pid_t pid, struct ProcessStat *proc) { } proc->num_threads = strtol(token, NULL, 10); +#endif +#ifdef __APPLE__ + proc->num_threads = taskInfo.ptinfo.pti_threadnum; +#endif + +#ifdef __linux__ // (21) itrealvalue token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ @@ -307,9 +373,12 @@ bool GetProcessStat(pid_t pid, struct ProcessStat *proc) { Trace("GetProcessStat: failed to get token from proc/[pid]/stat - starttime."); return false; } - proc->starttime = strtoull(token, NULL, 10); +#else + proc->starttime = taskInfo.pbsd.pbi_start_tvsec; +#endif +#ifdef __linux__ // (23) vsize token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ @@ -326,8 +395,13 @@ bool GetProcessStat(pid_t pid, struct ProcessStat *proc) { return false; } - proc->rss = strtol(token, NULL, 10); + proc->rss = strtol(token, NULL, 10); +#endif +#ifdef __APPLE__ + proc->rss = taskInfo.ptinfo.pti_resident_size; +#endif +#ifdef __linux__ // (25) rsslim token = strtok_r(NULL, " ", &savePtr); if(token == NULL){ @@ -579,7 +653,7 @@ bool GetProcessStat(pid_t pid, struct ProcessStat *proc) { } proc->exit_code = (int)strtol(token, NULL, 10); - +#endif return true; } @@ -613,6 +687,7 @@ char * GetProcessNameFromCmdLine(char* cmdLine) //-------------------------------------------------------------------- char * GetProcessName(pid_t pid) { +#ifdef __linux__ char procFilePath[32]; char fileBuffer[MAX_CMDLINE_LEN]; int charactersRead = 0; @@ -676,6 +751,27 @@ char * GetProcessName(pid_t pid) } } } +#elif __APPLE__ + char* pathbuf = (char*) malloc(PROC_PIDPATHINFO_MAXSIZE); + if(pathbuf == NULL) + { + return NULL; + } + + if (proc_pidpath(pid, pathbuf, PROC_PIDPATHINFO_MAXSIZE) > 0) + { + // Extract the process name from the full path + char* process_name = strrchr(pathbuf, '/'); + if (process_name) + { + process_name++; + char* proc_copy = strdup(process_name); + free(pathbuf); + return proc_copy; + } + } + +#endif return NULL; } @@ -686,9 +782,10 @@ char * GetProcessName(pid_t pid) // Returns NO_PID on error // //-------------------------------------------------------------------- -pid_t GetProcessPgid(pid_t pid){ +pid_t GetProcessPgid(pid_t pid) +{ pid_t pgid = NO_PID; - +#ifdef __linux__ char procFilePath[32]; char fileBuffer[1024]; char *token; @@ -730,7 +827,7 @@ pid_t GetProcessPgid(pid_t pid){ } pgid = (pid_t)strtol(token, NULL, 10); - +#endif return pgid; } @@ -741,6 +838,7 @@ pid_t GetProcessPgid(pid_t pid){ //-------------------------------------------------------------------- bool LookupProcessByPid(pid_t pid) { +#ifdef __linux__ char statFilePath[32]; auto_free_file FILE *fd = NULL; @@ -758,7 +856,18 @@ bool LookupProcessByPid(pid_t pid) if (fd == NULL) { return false; } - +#elif __APPLE__ + // On MacOS, we can't check if a process is running by looking at /proc + // Instead, we can use kill(pid, 0) to check if the process is running + if (kill(pid, 0) == 0) + { + return true; + } + else + { + return false; + } +#endif return true; } @@ -922,8 +1031,9 @@ pid_t LookupProcessPidByName(const char* name) //-------------------------------------------------------------------- int GetMaximumPID() { - auto_free_file FILE * pidMaxFile = NULL; int maxPIDs = -1; +#ifdef __linux__ + auto_free_file FILE * pidMaxFile = NULL; pidMaxFile = fopen(PID_MAX_KERNEL_CONFIG, "r"); if(pidMaxFile != NULL) @@ -933,6 +1043,9 @@ int GetMaximumPID() maxPIDs = -1; } } +#elif __APPLE__ + maxPIDs = INT_MAX; +#endif return maxPIDs; } @@ -947,3 +1060,96 @@ int FilterForPid(const struct dirent *entry) return IsValidNumberArg(entry->d_name); } + +//-------------------------------------------------------------------- +// +// GetCpuUsage - Gets the CPU usage of a process. +// +//-------------------------------------------------------------------- +#ifdef __linux__ +int GetCpuUsage(pid_t pid) +{ + int cpuUsage = 0; + struct sysinfo sysInfo; + unsigned long totalTime; + unsigned long elapsedTime; + struct ProcessStat procStat = {0}; + + sysinfo(&sysInfo); + GetProcessStat(pid, &procStat); + + // Calc CPU + totalTime = (unsigned long)((procStat.utime + procStat.stime) / HZ); + elapsedTime = (unsigned long)(sysInfo.uptime - (long)(procStat.starttime / HZ)); + cpuUsage = (int)(100 * ((double)totalTime / elapsedTime)); + + return cpuUsage; +} +#elif __APPLE__ +int GetCpuUsage(pid_t pid) +{ + int cpuUsage = 0; + pid_t* pids = NULL; + int ret = 0; + struct timeval boottime; + size_t size = sizeof(boottime); + int mib[2] = {CTL_KERN, KERN_BOOTTIME}; + + if (sysctl(mib, 2, &boottime, &size, NULL, 0) != 0) + { + return -1; + } + + mach_timebase_info_data_t timebaseInfo; + kern_return_t kr = mach_timebase_info(&timebaseInfo); + + struct proc_taskallinfo taskInfo; + ret = proc_pidinfo(pid, PROC_PIDTASKALLINFO, 0, &taskInfo, sizeof(taskInfo)); + if (ret <= 0) + { + Trace("[GetCpuUsage] Failed to get process information for pid: %d", pid); + free(pids); + return -1; + } + else + { + unsigned long totalTime = ((taskInfo.ptinfo.pti_total_user * timebaseInfo.numer / timebaseInfo.denom) + (taskInfo.ptinfo.pti_total_system * timebaseInfo.numer / timebaseInfo.denom)) / 1000000000; + unsigned long elapsedTime = ((time(NULL) - boottime.tv_sec)) - (taskInfo.pbsd.pbi_start_tvsec - boottime.tv_sec); + cpuUsage = (int)(100 * ((double)totalTime / elapsedTime)); + } + + return cpuUsage; +} + +//-------------------------------------------------------------------- +// +// GetRunningPids - Returns the running PIDS on the system. +// +//-------------------------------------------------------------------- +int GetRunningPids(pid_t** pids) +{ + int num_pids = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0); + *pids = (pid_t*) malloc(num_pids*sizeof(pid_t)); + num_pids = proc_listpids(PROC_ALL_PIDS, 0, *pids, num_pids*sizeof(pid_t)); + + return num_pids; +} + + +//-------------------------------------------------------------------- +// +// GetProcessStartTime - Returns the process start time. +// +//-------------------------------------------------------------------- +uint64_t GetProcessStartTime(pid_t pid) +{ + struct proc_taskallinfo taskInfo; + if(GetTaskInfo(&taskInfo, pid) == false) + { + return 0; + } + + return taskInfo.pbsd.pbi_start_tvsec; +} + +#endif \ No newline at end of file diff --git a/tests/integration/ProcDumpTestApplication.c b/tests/integration/ProcDumpTestApplication.c index c94a652..9250cf6 100644 --- a/tests/integration/ProcDumpTestApplication.c +++ b/tests/integration/ProcDumpTestApplication.c @@ -5,6 +5,7 @@ #include #include #include +#include #define FILE_DESC_COUNT 500 #define THREAD_COUNT 100 @@ -14,23 +15,39 @@ void* dFunc(int type) { if(type == 0) { - return malloc(10000); + char* alloc = malloc(10000); + for(int i=0; i<10000; i++) + { + alloc[i] = 'a'; + } + mlock(alloc, 10000); + return alloc; } else if (type == 1) { - return calloc(1, 10000); + char* callocAlloc = calloc(1, 10000); + mlock(callocAlloc, 10000); + return callocAlloc; } else if (type == 2) { void* lastAlloc = malloc(10000); void* newAlloc = realloc(lastAlloc, 20000); + for(int i=0; i<20000; i++) + { + ((char*)newAlloc)[i] = 'a'; + } + mlock(newAlloc, 20000); return newAlloc; } else if (type == 3) { +#ifdef __linux__ void* lastAlloc = malloc(10000); void* newAlloc = reallocarray(lastAlloc, 10, 20000); return newAlloc; +#endif + return NULL; } else { @@ -97,7 +114,7 @@ int main(int argc, char *argv[]) else if (strcmp("mem", argv[1]) == 0) { sleep(10); - for(int i=0; i<200; i++) + for(int i=0; i<1000; i++) { a(0); a(1); diff --git a/tests/integration/run.sh b/tests/integration/run.sh index fd1f7bd..94fda59 100755 --- a/tests/integration/run.sh +++ b/tests/integration/run.sh @@ -20,13 +20,15 @@ if [[ $EUID -ne 0 ]]; then exit 1 fi - -if [ ! -e /usr/bin/stress-ng ]; then - echo "Please install stress-ng before running this script!" - exit 1 +OS=$(uname -s) +if [ "$OS" != "Darwin" ]; then + if [ ! -e /usr/bin/stress-ng ]; then + echo "Please install stress-ng before running this script!" + exit 1 + fi fi -if [ ! -e /usr/bin/dotnet ]; then +if [ ! -e /usr/bin/dotnet ] && [ "$OS" != "Darwin" ]; then echo "Please install .NET before running this script!" exit 1 fi @@ -47,7 +49,17 @@ function runTest { fi } -for file in $DIR/scenarios/*.sh + +scenarioDir="" +if [ "$OS" = "Darwin" ]; then + scenarioDir=$DIR/scenarios_mac +else + scenarioDir=$DIR/scenarios +fi + +echo "Running tests in $scenarioDir" + +for file in $scenarioDir/*.sh; do if [ ! -z "$1" ]; then if [[ "$file" =~ "$1" ]]; then diff --git a/tests/integration/runProcDumpAndValidate.sh b/tests/integration/runProcDumpAndValidate.sh index 672979d..ea3a6d0 100755 --- a/tests/integration/runProcDumpAndValidate.sh +++ b/tests/integration/runProcDumpAndValidate.sh @@ -1,7 +1,9 @@ #!/bin/bash function runProcDumpAndValidate { DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; - PROCDUMPPATH=$(readlink -m "$DIR/../../procdump"); + PROCDUMPPATH="$DIR/../../procdump"; + + OS=$(uname -s) # In cases where the previous scenario is still writing a dump we simply want to kill it pkill -9 gdb > /dev/null @@ -28,11 +30,17 @@ function runProcDumpAndValidate { echo "PID: $pid" # Give test app opportunity to start and get into scenario state - sleep 5s + sleep 5 echo [`date +"%T.%3N"`] Done waiting for stress-ng to start - childrenpid=$(pidof -o $pid $(which stress-ng)) - echo "ChildrenPID: $childrenpid" + childrenpid="" + if [ "$OS" = "Darwin" ]; then + childrenpid=$(pgrep stress-ng | grep -v "^$pid$") + else + childrenpid=$(pidof -o $pid $(which stress-ng)) + fi + + echo "ChildrenPID: $childrenpid" childpid=$(echo $childrenpid | cut -d " " -f1) echo "ChildPID: $childpid" @@ -43,7 +51,7 @@ function runProcDumpAndValidate { $PROCDUMPPATH -log stdout $PREFIX $childpid $POSTFIX $dumpParam& pidPD=$! echo "ProcDump PID: $pidPD" - sleep 30s + sleep 30 echo [`date +"%T.%3N"`] Killing ProcDump if ps -p $pidPD > /dev/null then @@ -62,17 +70,21 @@ function runProcDumpAndValidate { echo "ProcDump PID: $pidPD" # Wait for procdump to initialize - sleep 10s + sleep 10 # Launch target process echo [`date +"%T.%3N"`] Starting $TESTPROGNAME - TESTPROGPATH=$(readlink -m "$DIR/../../$TESTPROGNAME"); + if [ "$OS" = "Darwin" ]; then + TESTPROGPATH=$DIR/../../$TESTPROGNAME; + else + TESTPROGPATH=$(readlink -m "$DIR/../../$TESTPROGNAME"); + fi ($TESTPROGPATH "$TESTPROGMODE") & pid=$! echo "Test App: $TESTPROGPATH $TESTPROGMODE" echo "PID: $pid" - sleep 30s + sleep 30 if ps -p $pidPD > /dev/null then echo [`date +"%T.%3N"`] Killing ProcDump: $pidPD diff --git a/tests/integration/scenarios/high_fd_by_name.sh b/tests/integration/scenarios/high_fd_by_name.sh index de692fa..73dfe96 100755 --- a/tests/integration/scenarios/high_fd_by_name.sh +++ b/tests/integration/scenarios/high_fd_by_name.sh @@ -1,6 +1,12 @@ #!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; -runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); +OS=$(uname -s) +if [ "$OS" = "Darwin" ]; then + runProcDumpAndValidate=$DIR/../runProcDumpAndValidate.sh; +else + runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); +fi + source $runProcDumpAndValidate TESTPROGNAME="ProcDumpTestApplication" diff --git a/tests/integration/scenarios/high_tc_by_name.sh b/tests/integration/scenarios/high_tc_by_name.sh index 209ee91..294d068 100755 --- a/tests/integration/scenarios/high_tc_by_name.sh +++ b/tests/integration/scenarios/high_tc_by_name.sh @@ -1,6 +1,12 @@ #!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; -runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); +OS=$(uname -s) +if [ "$OS" = "Darwin" ]; then + runProcDumpAndValidate=$DIR/../runProcDumpAndValidate.sh; +else + runProcDumpAndValidate=$(readlink -m "$DIR/../runProcDumpAndValidate.sh"); +fi + source $runProcDumpAndValidate TESTPROGNAME="ProcDumpTestApplication" diff --git a/tests/integration/scenarios_mac/high_cpu.sh b/tests/integration/scenarios_mac/high_cpu.sh new file mode 100755 index 0000000..d377248 --- /dev/null +++ b/tests/integration/scenarios_mac/high_cpu.sh @@ -0,0 +1,28 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; +runProcDumpAndValidate=$DIR/../runProcDumpAndValidate.sh; +source $runProcDumpAndValidate + +TESTPROGNAME="ProcDumpTestApplication" +TESTPROGMODE="burn" + +# TARGETVALUE is only used for stress-ng +#TARGETVALUE=3M + +# These are all the ProcDump switches preceeding the PID +PREFIX="-c 50" + +# This are all the ProcDump switches after the PID +POSTFIX="" + +# Indicates whether the test should result in a dump or not +SHOULDDUMP=true + +# Only applicable to stress-ng and can be either MEM or CPU +RESTYPE="" + +# The dump target +DUMPTARGET="" + +runProcDumpAndValidate + diff --git a/tests/integration/scenarios_mac/high_fd_by_name.sh b/tests/integration/scenarios_mac/high_fd_by_name.sh new file mode 100755 index 0000000..453e905 --- /dev/null +++ b/tests/integration/scenarios_mac/high_fd_by_name.sh @@ -0,0 +1,4 @@ +#!/bin/bash +source scenarios/high_fd_by_name.sh + + diff --git a/tests/integration/scenarios_mac/high_mem.sh b/tests/integration/scenarios_mac/high_mem.sh new file mode 100755 index 0000000..6c429f8 --- /dev/null +++ b/tests/integration/scenarios_mac/high_mem.sh @@ -0,0 +1,27 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"; +runProcDumpAndValidate=$DIR/../runProcDumpAndValidate.sh; +source $runProcDumpAndValidate + +TESTPROGNAME="ProcDumpTestApplication" +TESTPROGMODE="mem" + +# TARGETVALUE is only used for stress-ng +#TARGETVALUE=3M + +# These are all the ProcDump switches preceeding the PID +PREFIX="-m 1" + +# This are all the ProcDump switches after the PID +POSTFIX="" + +# Indicates whether the test should result in a dump or not +SHOULDDUMP=true + +# Only applicable to stress-ng and can be either MEM or CPU +RESTYPE="" + +# The dump target +DUMPTARGET="" + +runProcDumpAndValidate \ No newline at end of file diff --git a/tests/integration/scenarios_mac/high_tc_by_name.sh b/tests/integration/scenarios_mac/high_tc_by_name.sh new file mode 100755 index 0000000..22a5e55 --- /dev/null +++ b/tests/integration/scenarios_mac/high_tc_by_name.sh @@ -0,0 +1,2 @@ +#!/bin/bash +source scenarios/high_tc_by_name.sh \ No newline at end of file