diff --git a/src/include/switch_utils.h b/src/include/switch_utils.h index 9877829824..649ad926b3 100644 --- a/src/include/switch_utils.h +++ b/src/include/switch_utils.h @@ -1488,6 +1488,7 @@ SWITCH_DECLARE(switch_status_t) switch_digest(const char *digest_name, unsigned SWITCH_DECLARE(switch_status_t) switch_digest_string(const char *digest_name, char **digest_str, const void *input, switch_size_t inputLen, unsigned int *outputlen); SWITCH_DECLARE(char *) switch_must_strdup(const char *_s); +SWITCH_DECLARE(const char *) switch_memory_usage_stream(switch_stream_handle_t *stream); SWITCH_END_EXTERN_C #endif diff --git a/src/mod/applications/mod_commands/mod_commands.c b/src/mod/applications/mod_commands/mod_commands.c index ad5bd51ce9..aec3097e45 100644 --- a/src/mod/applications/mod_commands/mod_commands.c +++ b/src/mod/applications/mod_commands/mod_commands.c @@ -7485,6 +7485,18 @@ SWITCH_STANDARD_API(json_function) return SWITCH_STATUS_SUCCESS; } +SWITCH_STANDARD_API(memory_function) +{ + const char *err; + if (!(err = switch_memory_usage_stream(stream))) { + stream->write_function(stream, "+OK\n"); + } else { + stream->write_function(stream, "-ERR %s\n", err); + } + + return SWITCH_STATUS_SUCCESS; +} + SWITCH_MODULE_LOAD_FUNCTION(mod_commands_load) { switch_api_interface_t *commands_api_interface; @@ -7652,6 +7664,7 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_commands_load) SWITCH_ADD_API(commands_api_interface, "file_exists", "Check if a file exists on server", file_exists_function, ""); SWITCH_ADD_API(commands_api_interface, "getcputime", "Gets CPU time in milliseconds (user,kernel)", getcputime_function, GETCPUTIME_SYNTAX); SWITCH_ADD_API(commands_api_interface, "json", "JSON API", json_function, "JSON"); + SWITCH_ADD_API(commands_api_interface, "memory", "Memory usage statistics", memory_function, "memory"); SWITCH_ADD_JSON_API(json_api_interface, "mediaStats", "JSON Media Stats", json_stats_function, ""); diff --git a/src/switch_utils.c b/src/switch_utils.c index 932e9d963c..c19a4c6b60 100644 --- a/src/switch_utils.c +++ b/src/switch_utils.c @@ -42,6 +42,7 @@ #include #include #else +#include /* SIZETMult() */ /* process.h is required for _getpid() */ #include #endif @@ -55,6 +56,10 @@ #include #endif +#ifdef __GLIBC__ +#include /* mallinfo() */ +#endif + struct switch_network_node { ip_t ip; ip_t mask; @@ -4646,6 +4651,138 @@ SWITCH_DECLARE(char *) switch_must_strdup(const char *_s) return s; } +SWITCH_DECLARE(const char *) switch_memory_usage_stream(switch_stream_handle_t *stream) +{ + const char *status = NULL; +#ifdef __GLIBC__ +/* + * The mallinfo2() function was added in glibc 2.33. + * https://man7.org/linux/man-pages/man3/mallinfo.3.html + */ +#if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 33) + struct mallinfo2 mi; + + mi = mallinfo2(); + + stream->write_function(stream, "Total non-mmapped bytes (arena): %" SWITCH_SIZE_T_FMT "\n", mi.arena); + stream->write_function(stream, "# of free chunks (ordblks): %" SWITCH_SIZE_T_FMT "\n", mi.ordblks); + stream->write_function(stream, "# of free fastbin blocks (smblks): %" SWITCH_SIZE_T_FMT "\n", mi.smblks); + stream->write_function(stream, "# of mapped regions (hblks): %" SWITCH_SIZE_T_FMT "\n", mi.hblks); + stream->write_function(stream, "Bytes in mapped regions (hblkhd): %" SWITCH_SIZE_T_FMT "\n", mi.hblkhd); + stream->write_function(stream, "Max. total allocated space (usmblks): %" SWITCH_SIZE_T_FMT "\n", mi.usmblks); + stream->write_function(stream, "Free bytes held in fastbins (fsmblks): %" SWITCH_SIZE_T_FMT "\n", mi.fsmblks); + stream->write_function(stream, "Total allocated space (uordblks): %" SWITCH_SIZE_T_FMT "\n", mi.uordblks); + stream->write_function(stream, "Total free space (fordblks): %" SWITCH_SIZE_T_FMT "\n", mi.fordblks); + stream->write_function(stream, "Topmost releasable block (keepcost): %" SWITCH_SIZE_T_FMT "\n", mi.keepcost); +#else + struct mallinfo mi; + + mi = mallinfo(); + + stream->write_function(stream, "Total non-mmapped bytes (arena): %u\n", mi.arena); + stream->write_function(stream, "# of free chunks (ordblks): %u\n", mi.ordblks); + stream->write_function(stream, "# of free fastbin blocks (smblks): %u\n", mi.smblks); + stream->write_function(stream, "# of mapped regions (hblks): %u\n", mi.hblks); + stream->write_function(stream, "Bytes in mapped regions (hblkhd): %u\n", mi.hblkhd); + stream->write_function(stream, "Max. total allocated space (usmblks): %u\n", mi.usmblks); + stream->write_function(stream, "Free bytes held in fastbins (fsmblks): %u\n", mi.fsmblks); + stream->write_function(stream, "Total allocated space (uordblks): %u\n", mi.uordblks); + stream->write_function(stream, "Total free space (fordblks): %u\n", mi.fordblks); + stream->write_function(stream, "Topmost releasable block (keepcost): %u\n", mi.keepcost); + +#endif + + switch_goto_status(NULL, done); +#else +#ifdef WIN32 + /* Based on: https://docs.microsoft.com/en-us/windows/win32/memory/enumerating-a-heap and https://docs.microsoft.com/en-us/windows/win32/memory/getting-process-heaps */ + PHANDLE aHeaps; + SIZE_T BytesToAllocate; + DWORD HeapsIndex; + DWORD HeapsLength; + DWORD NumberOfHeaps; + HRESULT Result; + HANDLE hDefaultProcessHeap; + size_t CommittedSizeTotal = 0; + size_t UnCommittedSizeTotal = 0; + size_t SizeTotal = 0; + size_t OverheadTotal = 0; + + NumberOfHeaps = GetProcessHeaps(0, NULL); + Result = SIZETMult(NumberOfHeaps, sizeof(*aHeaps), &BytesToAllocate); + if (Result != S_OK) { + switch_goto_status("SIZETMult failed.", done); + } + + hDefaultProcessHeap = GetProcessHeap(); + if (hDefaultProcessHeap == NULL) { + switch_goto_status("Failed to retrieve the default process heap", done); + } + + aHeaps = (PHANDLE)HeapAlloc(hDefaultProcessHeap, 0, BytesToAllocate); + if (aHeaps == NULL) { + switch_goto_status("HeapAlloc failed to allocate space for heaps", done); + } + + HeapsLength = NumberOfHeaps; + NumberOfHeaps = GetProcessHeaps(HeapsLength, aHeaps); + + if (NumberOfHeaps == 0) { + switch_goto_status("Failed to retrieve heaps", cleanup); + } else if (NumberOfHeaps > HeapsLength) { + /* + * Compare the latest number of heaps with the original number of heaps. + * If the latest number is larger than the original number, another + * component has created a new heap and the buffer is too small. + */ + switch_goto_status("Another component created a heap between calls.", cleanup); + } + + stream->write_function(stream, "Process has %d heaps.\n", HeapsLength); + for (HeapsIndex = 0; HeapsIndex < HeapsLength; ++HeapsIndex) { + PROCESS_HEAP_ENTRY Entry; + HANDLE hHeap = aHeaps[HeapsIndex]; + + stream->write_function(stream, "Heap %d at address: %#p.\n", HeapsIndex, aHeaps[HeapsIndex]); + + /* Lock the heap to prevent other threads from accessing the heap during enumeration. */ + if (HeapLock(hHeap) == FALSE) { + switch_goto_status("Failed to lock heap.", cleanup); + } + + Entry.lpData = NULL; + while (HeapWalk(hHeap, &Entry) != FALSE) { + if ((Entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) { + } else if ((Entry.wFlags & PROCESS_HEAP_REGION) != 0) { + CommittedSizeTotal += Entry.Region.dwCommittedSize; + UnCommittedSizeTotal += Entry.Region.dwUnCommittedSize; + } + + SizeTotal += Entry.cbData; + OverheadTotal += Entry.cbOverhead; + } + + /* Unlock the heap to allow other threads to access the heap after enumeration has completed. */ + if (HeapUnlock(hHeap) == FALSE) { + abort(); + } + } + + stream->write_function(stream, "Committed bytes: %" SWITCH_SIZE_T_FMT "\n", CommittedSizeTotal); + stream->write_function(stream, "Uncommited bytes: %" SWITCH_SIZE_T_FMT "\n", UnCommittedSizeTotal); + stream->write_function(stream, "Size: %" SWITCH_SIZE_T_FMT "\n", SizeTotal); + stream->write_function(stream, "Overhead: %" SWITCH_SIZE_T_FMT"\n", OverheadTotal); + +cleanup: + HeapFree(hDefaultProcessHeap, 0, aHeaps); +#else + switch_goto_status("Memory usage statistics is not implemented on the current platform.", done); +#endif +#endif +done: + return status; +} + /* For Emacs: * Local Variables: * mode:c