Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

heap: add functions to get heap runtime statistics #39903

Merged
merged 3 commits into from
Nov 11, 2021

Conversation

chen-png
Copy link
Collaborator

@chen-png chen-png commented Oct 29, 2021

add functions to get the sys_heap runtime statistics,
include total free bytes, total allocated bytes.

Signed-off-by: Chen Peng1 peng1.chen@intel.com

@chen-png
Copy link
Collaborator Author

this is for #23076, get used and free bytes from heap.

@zephyrbot zephyrbot added the area: Base OS Base OS Library (lib/os) label Oct 29, 2021
if (c == h->end_chunk) {
break;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to replace "for(){}" with "do...while()".

do {
} while (c != h->end_chunk)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated it, thanks,

if (chunk_used(h, c)) {
if ((c != 0) && (c != h->end_chunk)) {
stats->allocated_bytes += chunksz_to_bytes(
h, chunk_size(h, c));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we put the two lines in one line?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the Zephyr Coding Guidelines all if-else if constructs shall be terminated with an else statement.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, I have added some empty else {; } for coding guideline.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, we can put them into one line like below,

if (chunk_used(h, c) && (c != 0) && (c != h->end_chunk)) {
...
} else if (!chunk_used(h, c) && !solo_free_header(h, c))
...
}

but I think using chunk_used(h, c) to distinguish free and allocated firstly is more obvious.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just meant lines 412 and 413.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's because the max length can't be more than 80 chars.

} else {
if (!solo_free_header(h, c)) {
free_bytes = chunksz_to_bytes(h,
chunk_size(h, c));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above?

free_bytes = chunksz_to_bytes(h,
chunk_size(h, c));
stats->free_bytes += free_bytes;
if (free_bytes > stats->max_avail_bytes) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really possible that free_byyes is larger than max_avail_byyes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a heap, there maybe several free blocks, they are not continuous, the max_avail_bytes is the largest one of them.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Thanks.

@chen-png
Copy link
Collaborator Author

chen-png commented Nov 2, 2021

@andyross Could you help take a look of this? thanks.

* @return -EINVAL if null pointers, otherwise 0
*/
int heap_runtime_stats_get(struct sys_heap *heap,
struct sys_heap_runtime_stats *stats);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it a public API? If not, I think we should declare it as a static function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a public API, because users need to invoke this function if then wants to know the heap usage.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the API sys_heap_runtime_stats_get could be used to get heap usage of any specified sys_heap,
the API heap_runtime_stats_get is only used for the default _system_heap of k_mallock/k_free,
because for k_malloc, it used the os predefined _system_heap, for malloc, it used the os predifined z_malloc_heap,

@andyross so do I need to define another two API for them (like heap_runtime_stats_get for k_malloc, using _system_heap by default), or only define a common sys_heap_runtime_stats_get, the users should specific the sys_heap by themselves?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I vote for defining one API - heap_runtime_status_get().

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think necessary to try use that API and see if it is convenient and user-friendly.

@maksimmasalski
Copy link
Collaborator

I think, we need test case for that feature too.

Copy link
Contributor

@andyross andyross left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to fix the function name.

Also, this seems to be doing a lot of work and incurring a bunch of runtime expense (which requires a locked heap, so that's a latency problem too) just to be able to get "max_available_bytes". Is that really a good thing? What do other heap implementations do? For reference, a quick glance at glibc's mallinfo() seems to say that nothing they track requires anything but O(1) overhead on heap operations.

stats->allocated_bytes = 0;
stats->max_avail_bytes = 0;

/* Iterate through all chunks */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want a O(N) implementation here? Most of the time people just want to know the sum of all the sizes passed to successful allocations. That can be tracked with a simple increment/decrement hook at allocation/free time.

* @param stats_t Pointer to struct to copy statistics into
* @return -EINVAL if null pointers, otherwise 0
*/
int heap_runtime_stats_get(struct sys_heap *heap,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming: should be "sys_heap_*()". sys_ is the prefix (one of them) for a public API.

return 0;
}

#if (CONFIG_HEAP_MEM_POOL_SIZE > 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No point in putting an extern function inside an ifdef. It won't be linked if it's not referenced.

@chen-png chen-png force-pushed the add_heap_statistics branch 2 times, most recently from 30c8463 to 5d545c5 Compare November 9, 2021 16:48
Copy link
Contributor

@andyross andyross left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some nitpicks that would be nice to see fixed, but not worth a -1. Looks good to me.

lib/os/Kconfig Outdated
@@ -46,6 +46,12 @@ config SYS_HEAP_ALLOC_LOOPS
keeps the maximum runtime at a tight bound so that the heap
is useful in locked or ISR contexts.

config SYS_HEAP_RUNTIME_STATS
bool "System heap runtime statistics"
default n
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pedantry: "n" is the default state for a bool, you can remove this line.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will remove it, thanks.

lib/os/heap.h Outdated
@@ -139,26 +143,6 @@ static inline chunksz_t chunk_size(struct z_heap *h, chunkid_t c)
return chunk_field(h, c, SIZE_AND_USED) >> 1;
}

static inline void set_chunk_used(struct z_heap *h, chunkid_t c, bool used)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did this function move? Seems like a needless change.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because in this function, my change used other functions like chunksz_to_bytes and solo_free_header, which are declared and defined after this set_chunk_used.

Copy link
Collaborator

@npitre npitre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think overloading set_chunk_used() is the best approach.
The if ((c != 0) && (c != h->end_chunk)) test will be applied every
time although it is relevant only exactly once i.e. at heap creation time.

Also, calling set_chunk_used(..., false) in the realloc case for the
count to balance is a bad sign, and doing so despite
CONFIG_SYS_HEAP_RUNTIME_STATS being disabled is wasteful.

I think it would be better and more obvious if the accounting was done
inline. This way the 2 realloc special cases could be better optimized.

Oh, and btw you don't need the empty else when there is no else if.

Copy link
Collaborator

@enjiamai enjiamai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Line 263 - 281, how about the changes like this:

#ifdef CONFIG_SYS_HEAP_RUNTIME_STATS
size_t bytes = chunksz_to_bytes(h, chunk_size(h, c));

if (used && (c != 0) && (c != h->end_chunk)) {
	h->allocated_bytes += bytes;
	h->free_bytes -= bytes;
} else if (!solo_free_header(h, c)) {
	h->allocated_bytes -= bytes;
	h->free_bytes += bytes;
} 
#endif

@chen-png
Copy link
Collaborator Author

calling set_chunk_used(..., false) in the realloc case for the count to balance is a bad sign

yes, I agree, I will update it.

the accounting was done inline.

hi @npitre Could you help review it again? I added another inline function to calculate size, this is more reasonable then overring set_chunk_used for the special case for realloc.

@chen-png chen-png requested a review from npitre November 10, 2021 07:48
Copy link
Collaborator

@npitre npitre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think overloading set_chunk_used() is the best approach.
The if ((c != 0) && (c != h->end_chunk)) test will be applied every
time although it is relevant only exactly once i.e. at heap creation time.

Also, calling set_chunk_used(..., false) in the realloc case for the
count to balance is a bad sign, and doing so despite
CONFIG_SYS_HEAP_RUNTIME_STATS being disabled is wasteful.

I think it would be better and more obvious if the accounting was done
inline. This way the 2 realloc special cases could be better optimized.

Oh, and btw you don't need the empty else when there is no else if.

Copy link
Collaborator

@npitre npitre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please disregard last comment

Copy link
Collaborator

@npitre npitre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is getting too messy.

Here's what I suggest for heap.c instead:

diff --git a/lib/os/heap.c b/lib/os/heap.c
index d78cd16420..9b22d7fa5f 100644
--- a/lib/os/heap.c
+++ b/lib/os/heap.c
@@ -38,6 +38,10 @@ static void free_list_remove_bidx(struct z_heap *h, chunkid_t c, int bidx)
 		set_next_free_chunk(h, first, second);
 		set_prev_free_chunk(h, second, first);
 	}
+
+#ifdef CONFIG_SYS_HEAP_RUNTIME_STATS
+	h->free_bytes -= chunksz_to_bytes(h, chunk_size(h, c));
+#endif
 }
 
 static void free_list_remove(struct z_heap *h, chunkid_t c)
@@ -72,6 +76,10 @@ static void free_list_add_bidx(struct z_heap *h, chunkid_t c, int bidx)
 		set_next_free_chunk(h, first, c);
 		set_prev_free_chunk(h, second, c);
 	}
+
+#ifdef CONFIG_SYS_HEAP_RUNTIME_STATS
+	h->free_bytes += chunksz_to_bytes(h, chunk_size(h, c));
+#endif
 }
 
 static void free_list_add(struct z_heap *h, chunkid_t c)
@@ -164,6 +172,9 @@ void sys_heap_free(struct sys_heap *heap, void *mem)
 		 mem);
 
 	set_chunk_used(h, c, false);
+#ifdef CONFIG_SYS_HEAP_RUNTIME_STATS
+	h->allocated_bytes -= chunksz_to_bytes(h, chunk_size(h, c)); 
+#endif
 	free_chunk(h, c);
 }
 
@@ -251,6 +262,9 @@ void *sys_heap_alloc(struct sys_heap *heap, size_t bytes)
 	}
 
 	set_chunk_used(h, c, true);
+#ifdef CONFIG_SYS_HEAP_RUNTIME_STATS
+	h->allocated_bytes += chunksz_to_bytes(h, chunk_size(h, c));
+#endif
 	return chunk_mem(h, c);
 }
 
@@ -318,6 +332,9 @@ void *sys_heap_aligned_alloc(struct sys_heap *heap, size_t align, size_t bytes)
 	}
 
 	set_chunk_used(h, c, true);
+#ifdef CONFIG_SYS_HEAP_RUNTIME_STATS
+	h->allocated_bytes += chunksz_to_bytes(h, chunk_size(h, c));
+#endif
 	return mem;
 }
 
@@ -353,6 +370,9 @@ void *sys_heap_aligned_realloc(struct sys_heap *heap, void *ptr,
 		return ptr;
 	} else if (chunk_size(h, c) > chunks_need) {
 		/* Shrink in place, split off and free unused suffix */
+#ifdef CONFIG_SYS_HEAP_RUNTIME_STATS
+		h->allocated_bytes -= (chunk_size(h, c) - chunks_need) * CHUNK_UNIT;
+#endif
 		split_chunks(h, c, c + chunks_need);
 		set_chunk_used(h, c, true);
 		free_chunk(h, c + chunks_need);
@@ -362,6 +382,10 @@ void *sys_heap_aligned_realloc(struct sys_heap *heap, void *ptr,
 		/* Expand: split the right chunk and append */
 		chunkid_t split_size = chunks_need - chunk_size(h, c);
 
+#ifdef CONFIG_SYS_HEAP_RUNTIME_STATS
+		h->allocated_bytes += split_size * CHUNK_UNIT;
+#endif
+
 		free_list_remove(h, rc);
 
 		if (split_size < chunk_size(h, rc)) {
@@ -414,6 +438,10 @@ void sys_heap_init(struct sys_heap *heap, void *mem, size_t bytes)
 	heap->heap = h;
 	h->end_chunk = heap_sz;
 	h->avail_buckets = 0;
+#ifdef CONFIG_SYS_HEAP_RUNTIME_STATS
+	h->free_bytes = 0;
+	h->allocated_bytes = 0;
+#endif
 
 	int nb_buckets = bucket_idx(h, heap_sz) + 1;
 	chunksz_t chunk0_size = chunksz(sizeof(struct z_heap) +

add functions to get the sys_heap runtime statistics,
include total free bytes, total allocated bytes.

Signed-off-by: Chen Peng1 <peng1.chen@intel.com>
@chen-png
Copy link
Collaborator Author

@npitre this logic is easier to understand, thanks.

@nashif
Copy link
Member

nashif commented Nov 11, 2021

@chen-png can you provide a test as well?

@npitre
Copy link
Collaborator

npitre commented Nov 11, 2021 via email

@github-actions github-actions bot added the area: Tests Issues related to a particular existing or missing test label Nov 11, 2021
@chen-png
Copy link
Collaborator Author

can you provide a test as well?

I think the proposal from @npitre is good, so according to his comments, I enabled existing heap tests to validate this sys_heap_runtime_stats_get API.

Chen Peng1 added 2 commits November 11, 2021 15:50
add operations to check sys_heap_runtime_stats_get API
in existing sys_heap_validate function.

Signed-off-by: Chen Peng1 <peng1.chen@intel.com>
Add CONFIG_SYS_HEAP_RUNTIME_STATS in existing heap tests
to enable validation for sys_heap_runtime_stats_get API.

Signed-off-by: Chen Peng1 <peng1.chen@intel.com>
@cfriedt
Copy link
Member

cfriedt commented Nov 11, 2021

This is great - I've been meaning to add something like this for a while, but I'm happy that you beat me to it @chen-png :-)

@nashif nashif merged commit f4cf484 into zephyrproject-rtos:main Nov 11, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: API Changes to public APIs area: Base OS Base OS Library (lib/os) area: Tests Issues related to a particular existing or missing test
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants