Skip to content

Commit

Permalink
GC page profiler (#52567)
Browse files Browse the repository at this point in the history
Piggybacks in the sweeping phase of the GC to pretty-print a JSON
representation of every page in the pool allocator.

Usage:

```bash
julia> using Profile

julia> Profile.take_page_profile("/tmp/test-profile.out")
"/tmp/test-profile.out"
```

Output (truncated to one page & after pretty printing):

```json
{
	"address": "0x109dd0000",
	"object_size": 400,
	"objects": [
		"Task",
		"Task",
		"Task",
		"garbage",
		"GenericMemory",
		"garbage",
		"GenericMemory",
		"GenericMemory",
		"garbage",
		"GenericMemory",
		"GenericMemory",
		"Task",
		"Task",
		"Task",
		"garbage",
		"garbage",
		"garbage",
		"String",
		"garbage",
		"garbage",
		"String",
		"GenericMemory",
		"GenericMemory",
		"garbage",
		"garbage",
		"garbage",
		"garbage",
		"garbage",
		"garbage",
		"garbage",
		"garbage",
		"garbage",
		"garbage",
		"garbage",
		"garbage",
		"garbage",
		"garbage",
		"garbage",
		"garbage",
		"garbage",
	]
}
```

This proved particularly useful for us when studying fragmentation in
some of our workloads, though this profiler may possibly have some
repeated functionality compared to the other profilers we already have
in `stdlib`.
  • Loading branch information
d-netto authored Dec 23, 2023
1 parent b97ffd0 commit 4975a78
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 15 deletions.
2 changes: 1 addition & 1 deletion src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ SRCS := \
jltypes gf typemap smallintset ast builtins module interpreter symbol \
dlload sys init task array genericmemory staticdata toplevel jl_uv datatype \
simplevector runtime_intrinsics precompile jloptions mtarraylist \
threading scheduler stackwalk gc gc-debug gc-pages gc-stacks gc-alloc-profiler method \
threading scheduler stackwalk gc gc-debug gc-pages gc-stacks gc-alloc-profiler gc-page-profiler method \
jlapi signal-handling safepoint timing subtype rtutils gc-heap-snapshot \
crc32c APInt-C processor ircode opaque_closure codegen-stubs coverage runtime_ccall

Expand Down
167 changes: 167 additions & 0 deletions src/gc-page-profiler.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// This file is a part of Julia. License is MIT: https://julialang.org/license

#include "gc-page-profiler.h"

#ifdef __cplusplus
extern "C" {
#endif

// whether page profiling is enabled
int page_profile_enabled;
// number of pages written
size_t page_profile_pages_written;
// stream to write page profile to
ios_t *page_profile_stream;
// mutex for page profile
uv_mutex_t page_profile_lock;

gc_page_profiler_serializer_t gc_page_serializer_create(void) JL_NOTSAFEPOINT
{
gc_page_profiler_serializer_t serializer;
if (__unlikely(page_profile_enabled)) {
arraylist_new(&serializer.typestrs, GC_PAGE_SZ);
}
else {
serializer.typestrs.len = 0;
}
return serializer;
}

void gc_page_serializer_init(gc_page_profiler_serializer_t *serializer,
jl_gc_pagemeta_t *pg) JL_NOTSAFEPOINT
{
if (__unlikely(page_profile_enabled)) {
serializer->typestrs.len = 0;
serializer->data = (char *)pg->data;
serializer->osize = pg->osize;
}
}

void gc_page_serializer_destroy(gc_page_profiler_serializer_t *serializer) JL_NOTSAFEPOINT
{
if (__unlikely(page_profile_enabled)) {
arraylist_free(&serializer->typestrs);
}
}

void gc_page_serializer_write(gc_page_profiler_serializer_t *serializer,
const char *str) JL_NOTSAFEPOINT
{
if (__unlikely(page_profile_enabled)) {
arraylist_push(&serializer->typestrs, (void *)str);
}
}

void gc_enable_page_profile(void) JL_NOTSAFEPOINT
{
page_profile_enabled = 1;
}

void gc_disable_page_profile(void) JL_NOTSAFEPOINT
{
page_profile_enabled = 0;
}

int gc_page_profile_is_enabled(void) JL_NOTSAFEPOINT
{
return page_profile_enabled;
}

void gc_page_profile_write_preamble(gc_page_profiler_serializer_t *serializer)
JL_NOTSAFEPOINT
{
if (__unlikely(page_profile_enabled)) {
char str[GC_TYPE_STR_MAXLEN];
snprintf(str, GC_TYPE_STR_MAXLEN,
"{\"address\": \"%p\",\"object_size\": %d,\"objects\": [",
serializer->data, serializer->osize);
ios_write(page_profile_stream, str, strlen(str));
}
}

void gc_page_profile_write_epilogue(gc_page_profiler_serializer_t *serializer)
JL_NOTSAFEPOINT
{
if (__unlikely(page_profile_enabled)) {
const char *str = "]}";
ios_write(page_profile_stream, str, strlen(str));
}
}

void gc_page_profile_write_comma(gc_page_profiler_serializer_t *serializer) JL_NOTSAFEPOINT
{
if (__unlikely(page_profile_enabled)) {
// write comma if not first page
if (page_profile_pages_written > 0) {
const char *str = ",";
ios_write(page_profile_stream, str, strlen(str));
}
}
}

void gc_page_profile_write_to_file(gc_page_profiler_serializer_t *serializer)
JL_NOTSAFEPOINT
{
if (__unlikely(page_profile_enabled)) {
// write to file
uv_mutex_lock(&page_profile_lock);
gc_page_profile_write_comma(serializer);
gc_page_profile_write_preamble(serializer);
char str[GC_TYPE_STR_MAXLEN];
for (size_t i = 0; i < serializer->typestrs.len; i++) {
const char *name = (const char *)serializer->typestrs.items[i];
if (name == GC_SERIALIZER_EMPTY) {
snprintf(str, GC_TYPE_STR_MAXLEN, "\"empty\",");
}
else if (name == GC_SERIALIZER_GARBAGE) {
snprintf(str, GC_TYPE_STR_MAXLEN, "\"garbage\",");
}
else {
snprintf(str, GC_TYPE_STR_MAXLEN, "\"%s\",", name);
}
// remove trailing comma for last element
if (i == serializer->typestrs.len - 1) {
str[strlen(str) - 1] = '\0';
}
ios_write(page_profile_stream, str, strlen(str));
}
gc_page_profile_write_epilogue(serializer);
page_profile_pages_written++;
uv_mutex_unlock(&page_profile_lock);
}
}

void gc_page_profile_write_json_preamble(ios_t *stream) JL_NOTSAFEPOINT
{
if (__unlikely(page_profile_enabled)) {
uv_mutex_lock(&page_profile_lock);
const char *str = "{\"pages\": [";
ios_write(stream, str, strlen(str));
uv_mutex_unlock(&page_profile_lock);
}
}

void gc_page_profile_write_json_epilogue(ios_t *stream) JL_NOTSAFEPOINT
{
if (__unlikely(page_profile_enabled)) {
uv_mutex_lock(&page_profile_lock);
const char *str = "]}";
ios_write(stream, str, strlen(str));
uv_mutex_unlock(&page_profile_lock);
}
}

JL_DLLEXPORT void jl_gc_take_page_profile(ios_t *stream)
{
gc_enable_page_profile();
page_profile_pages_written = 0;
page_profile_stream = stream;
gc_page_profile_write_json_preamble(stream);
jl_gc_collect(JL_GC_FULL);
gc_page_profile_write_json_epilogue(stream);
gc_disable_page_profile();
}

#ifdef __cplusplus
}
#endif
63 changes: 63 additions & 0 deletions src/gc-page-profiler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// This file is a part of Julia. License is MIT: https://julialang.org/license

#ifndef GC_PAGE_PROFILER_H
#define GC_PAGE_PROFILER_H

#include "gc.h"

#ifdef __cplusplus
extern "C" {
#endif

#define GC_TYPE_STR_MAXLEN (512)

typedef struct {
arraylist_t typestrs;
char *data;
int osize;
} gc_page_profiler_serializer_t;

// mutex for page profile
extern uv_mutex_t page_profile_lock;

// Serializer functions
gc_page_profiler_serializer_t gc_page_serializer_create(void) JL_NOTSAFEPOINT;
void gc_page_serializer_init(gc_page_profiler_serializer_t *serializer, jl_gc_pagemeta_t *pg) JL_NOTSAFEPOINT;
void gc_page_serializer_destroy(gc_page_profiler_serializer_t *serializer) JL_NOTSAFEPOINT;
void gc_page_serializer_write(gc_page_profiler_serializer_t *serializer, const char *str) JL_NOTSAFEPOINT;
// Page profile functions
#define GC_SERIALIZER_EMPTY ((const char *)0x1)
#define GC_SERIALIZER_GARBAGE ((const char *)0x2)
STATIC_INLINE void gc_page_profile_write_empty_page(gc_page_profiler_serializer_t *serializer,
int enabled) JL_NOTSAFEPOINT
{
if (__unlikely(enabled)) {
gc_page_serializer_write(serializer, GC_SERIALIZER_EMPTY);
}
}
STATIC_INLINE void gc_page_profile_write_garbage(gc_page_profiler_serializer_t *serializer,
int enabled) JL_NOTSAFEPOINT
{
if (__unlikely(enabled)) {
gc_page_serializer_write(serializer, GC_SERIALIZER_GARBAGE);
}
}
STATIC_INLINE void gc_page_profile_write_live_obj(gc_page_profiler_serializer_t *serializer,
jl_taggedvalue_t *v,
int enabled) JL_NOTSAFEPOINT
{
if (__unlikely(enabled)) {
const char *name = jl_typeof_str(jl_valueof(v));
gc_page_serializer_write(serializer, name);
}
}
void gc_enable_page_profile(void) JL_NOTSAFEPOINT;
void gc_disable_page_profile(void) JL_NOTSAFEPOINT;
int gc_page_profile_is_enabled(void) JL_NOTSAFEPOINT;
void gc_page_profile_write_to_file(gc_page_profiler_serializer_t *serializer) JL_NOTSAFEPOINT;

#ifdef __cplusplus
}
#endif

#endif // GC_PAGE_PROFILER_H
Loading

0 comments on commit 4975a78

Please sign in to comment.