Skip to content

Commit

Permalink
Add macOS support (#259)
Browse files Browse the repository at this point in the history
* 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 <marioh@Marios-MacBook-Pro-2.local>
  • Loading branch information
MarioHewardt and Mario Hewardt authored Nov 11, 2024
1 parent 1b1edc0 commit 88d8dd3
Show file tree
Hide file tree
Showing 28 changed files with 838 additions and 275 deletions.
2 changes: 1 addition & 1 deletion BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
324 changes: 188 additions & 136 deletions CMakeLists.txt

Large diffs are not rendered by default.

18 changes: 17 additions & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,20 @@ stages:
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)/logs/procdumpprofiler.log'
ArtifactName: 'procdumpprofiler.log'
publishLocation: 'Container'
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'
3 changes: 3 additions & 0 deletions include/CoreDumpWriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
#include <sys/socket.h>
#include <sys/un.h>
#include <stdint.h>
#ifdef __linux__
#include <linux/limits.h>
#elif __APPLE_
#endif

#define DATE_LENGTH 26
#define MAX_LINES 15
Expand Down
7 changes: 7 additions & 0 deletions include/GenHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
#ifndef GENHELPERS_H
#define GENHELPERS_H

#ifdef __linux__
#include <linux/version.h>
#elif __APPLE_
#endif

#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
Expand Down Expand Up @@ -96,7 +101,9 @@ static inline void cancel_pthread(unsigned long* val)
{
if(*val!=-1)
{
#ifdef __linux__
pthread_cancel(*val);
#endif
}
}

Expand Down
2 changes: 1 addition & 1 deletion include/Handle.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ enum EHandleType {
struct Handle {
union {
struct Event event;
sem_t semaphore;
sem_t* semaphore;
};
enum EHandleType type;
};
Expand Down
8 changes: 8 additions & 0 deletions include/ProcDumpConfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
#define PROCDUMPCONFIGURATION_H

#include <stdbool.h>
#ifdef __linux__
#include <sys/sysinfo.h>
#endif
#include <zconf.h>
#include <stdio.h>
#include <stdlib.h>
Expand All @@ -32,8 +34,10 @@
#include <fcntl.h>
#include <signal.h>

#ifdef __linux__
#include "Restrack.h"
#include "procdump_ebpf_common.h"
#endif

#include <unordered_map>

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<uintptr_t, ResourceInformation*> memAllocMap;
pthread_mutex_t memAllocMapMutex;
#endif

// multithreading
// set max number of concurrent dumps on init (default to 1)
Expand Down
5 changes: 5 additions & 0 deletions include/Process.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
#ifndef PROCFSLIB_PROCESS_H
#define PROCFSLIB_PROCESS_H

#ifdef __linux__
#include <linux/version.h>
#endif

#include <unistd.h>
#include <string.h>
#include <stdbool.h>
Expand Down Expand Up @@ -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
8 changes: 7 additions & 1 deletion makePackages.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion procdump.1
Original file line number Diff line number Diff line change
Expand Up @@ -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.
34 changes: 34 additions & 0 deletions procdump_mac.1
Original file line number Diff line number Diff line change
@@ -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.
40 changes: 27 additions & 13 deletions src/CoreDumpWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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.");
Expand Down Expand Up @@ -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)
{
Expand All @@ -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)
{
Expand All @@ -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)
{
Expand All @@ -280,6 +283,7 @@ char* WriteCoreDumpInternal(struct CoreDumpWriter *self, char* socketName)

self->Config->NumberOfDumpsCollected++; // safe to increment in crit section
}
#endif
}
else
{
Expand Down Expand Up @@ -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)
Expand All @@ -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)
{
Expand Down Expand Up @@ -395,7 +409,7 @@ char* WriteCoreDumpInternal(struct CoreDumpWriter *self, char* socketName)


free(name);

return strdup(coreDumpFileName);
}

2 changes: 1 addition & 1 deletion src/Events.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions src/GenHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
//
//--------------------------------------------------------------------
#include "Includes.h"
#ifdef __linux__
#include <syscall.h>
#endif

//--------------------------------------------------------------------
//
Expand Down Expand Up @@ -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
{
Expand All @@ -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
Expand All @@ -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
{
Expand All @@ -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);
}
}

Expand Down
Loading

0 comments on commit 88d8dd3

Please sign in to comment.