pw_thread: Add estimated max stack usage support
This adds a estimated max or stack usage to the thread snapshot proto.
For the embOS thread backend, store the max stack pointer in the new
field.
Fix: b/199208763
No-Docs-Update-Reason: Minor enhancement
Testing: Able to for verify by running production SW, forcing a crash,
and retrieving snapshot.
Unit tests are updated and pass.
Change-Id: Iaafe540201d8e7e16b2bfa4e83458893f2ba8dd6
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/60246
Commit-Queue: Jennifer Silva <jennifersilva@google.com>
Commit-Queue: Keir Mierle <keir@google.com>
Reviewed-by: Armando Montanez <amontanez@google.com>
diff --git a/pw_cpu_exception_cortex_m/snapshot.cc b/pw_cpu_exception_cortex_m/snapshot.cc
index db64016..d350bac 100644
--- a/pw_cpu_exception_cortex_m/snapshot.cc
+++ b/pw_cpu_exception_cortex_m/snapshot.cc
@@ -67,6 +67,7 @@
.stack_low_addr = stack_low_addr,
.stack_high_addr = stack_high_addr,
.stack_pointer = stack_pointer,
+ .stack_pointer_est_peak = std::nullopt,
};
return thread::SnapshotStack(thread_ctx, encoder, thread_stack_callback);
}
diff --git a/pw_thread/public/pw_thread/snapshot.h b/pw_thread/public/pw_thread/snapshot.h
index ed3f4ef..215429f 100644
--- a/pw_thread/public/pw_thread/snapshot.h
+++ b/pw_thread/public/pw_thread/snapshot.h
@@ -34,6 +34,7 @@
uintptr_t stack_low_addr;
uintptr_t stack_high_addr;
uintptr_t stack_pointer;
+ std::optional<uintptr_t> stack_pointer_est_peak;
};
// Takes the provided StackContext, and writes stack context to the provided
diff --git a/pw_thread/pw_thread_protos/thread.proto b/pw_thread/pw_thread_protos/thread.proto
index 32b1df7..230a515 100644
--- a/pw_thread/pw_thread_protos/thread.proto
+++ b/pw_thread/pw_thread_protos/thread.proto
@@ -95,6 +95,12 @@
// CPU usage info. This is the percentage of CPU time the thread has been
// active in hundredths of a percent. (e.g. 5.00% = 500u)
optional uint32 cpu_usage_hundredths = 10;
+
+ // The address of highest estimated currently used in the thread stack.
+ // Percentage of bytes used can be calculated by:
+ // (stack_estimate_max_addr-stack_start_pointer) /
+ // (stack_end_pointer-stack_start_pointer) * 100%
+ optional uint64 stack_pointer_est_peak = 11;
}
// This message overlays the pw.snapshot.Snapshot proto. It's valid to encode
diff --git a/pw_thread/py/pw_thread/thread_analyzer.py b/pw_thread/py/pw_thread/thread_analyzer.py
index 3ca209a..1bd8a38 100644
--- a/pw_thread/py/pw_thread/thread_analyzer.py
+++ b/pw_thread/py/pw_thread/thread_analyzer.py
@@ -67,6 +67,18 @@
used_str += f', {100*self.stack_used()/self.stack_size_limit():.2f}%'
return used_str
+ def _stack_pointer_est_peak_str(self) -> str:
+ if not self.has_stack_pointer_est_peak():
+ return 'size unknown'
+
+ high_used_str = f'{self.stack_pointer_est_peak()} bytes'
+ if not self.has_stack_size_limit():
+ return high_used_str
+ high_water_mark_percent = (100 * self.stack_pointer_est_peak() /
+ self.stack_size_limit())
+ high_used_str += f', {high_water_mark_percent:.2f}%'
+ return high_used_str
+
def _stack_used_range_str(self) -> str:
start_str = (f'0x{self._thread.stack_start_pointer:08x}'
if self._thread.HasField('stack_start_pointer') else
@@ -124,11 +136,29 @@
return abs(self._thread.stack_start_pointer -
self._thread.stack_pointer)
+ def has_stack_pointer_est_peak(self) -> bool:
+ """Returns true if there's enough info to calculate estimate
+ used stack.
+ """
+ return (self._thread.HasField('stack_start_pointer')
+ and self._thread.HasField('stack_pointer_est_peak'))
+
+ def stack_pointer_est_peak(self) -> int:
+ """Returns the max estimated used stack usage in bytes.
+
+ Precondition:
+ has_stack_estimated_used_bytes() must be true.
+ """
+ assert self.has_stack_pointer_est_peak(), 'Missing stack est. peak'
+ return abs(self._thread.stack_start_pointer -
+ self._thread.stack_pointer_est_peak)
+
def __str__(self) -> str:
output = [
'Stack info',
- f' Stack used: {self._stack_used_range_str()}',
- f' Stack limits: {self._stack_limit_range_str()}',
+ f' Stack cur used: {self._stack_used_range_str()}',
+ f' Stack max used: {self._stack_pointer_est_peak_str()}',
+ f' Stack limits: {self._stack_limit_range_str()}',
]
return '\n'.join(output)
diff --git a/pw_thread/py/thread_analyzer_test.py b/pw_thread/py/thread_analyzer_test.py
index 431f97e..59918fe 100644
--- a/pw_thread/py/thread_analyzer_test.py
+++ b/pw_thread/py/thread_analyzer_test.py
@@ -25,8 +25,9 @@
stack_info = StackInfo(thread_pb2.Thread())
expected = '\n'.join(
('Stack info',
- ' Stack used: 0x???????? - 0x???????? (size unknown)',
- ' Stack limits: 0x???????? - 0x???????? (size unknown)'))
+ ' Stack cur used: 0x???????? - 0x???????? (size unknown)',
+ ' Stack max used: size unknown',
+ ' Stack limits: 0x???????? - 0x???????? (size unknown)'))
self.assertFalse(stack_info.has_stack_size_limit())
self.assertFalse(stack_info.has_stack_used())
self.assertEqual(expected, str(stack_info))
@@ -38,8 +39,9 @@
expected = '\n'.join(
('Stack info',
- ' Stack used: 0x???????? - 0x5ac6a86c (size unknown)',
- ' Stack limits: 0x???????? - 0x???????? (size unknown)'))
+ ' Stack cur used: 0x???????? - 0x5ac6a86c (size unknown)',
+ ' Stack max used: size unknown',
+ ' Stack limits: 0x???????? - 0x???????? (size unknown)'))
self.assertFalse(stack_info.has_stack_size_limit())
self.assertFalse(stack_info.has_stack_used())
self.assertEqual(expected, str(stack_info))
@@ -52,8 +54,9 @@
expected = '\n'.join(
('Stack info',
- ' Stack used: 0x5ac6b86c - 0x5ac6a86c (4096 bytes)',
- ' Stack limits: 0x5ac6b86c - 0x???????? (size unknown)'))
+ ' Stack cur used: 0x5ac6b86c - 0x5ac6a86c (4096 bytes)',
+ ' Stack max used: size unknown',
+ ' Stack limits: 0x5ac6b86c - 0x???????? (size unknown)'))
self.assertFalse(stack_info.has_stack_size_limit())
self.assertTrue(stack_info.has_stack_used())
self.assertEqual(expected, str(stack_info))
@@ -67,8 +70,9 @@
expected = '\n'.join(
('Stack info',
- ' Stack used: 0x5ac6b86c - 0x5ac6a86c (4096 bytes, 50.00%)',
- ' Stack limits: 0x5ac6b86c - 0x5ac6986c (8192 bytes)'))
+ ' Stack cur used: 0x5ac6b86c - 0x5ac6a86c (4096 bytes, 50.00%)',
+ ' Stack max used: size unknown',
+ ' Stack limits: 0x5ac6b86c - 0x5ac6986c (8192 bytes)'))
self.assertTrue(stack_info.has_stack_size_limit())
self.assertTrue(stack_info.has_stack_used())
self.assertEqual(expected, str(stack_info))
@@ -89,8 +93,9 @@
'',
'Thread (UNKNOWN): [unnamed thread]',
'Stack info',
- ' Stack used: 0x???????? - 0x???????? (size unknown)',
- ' Stack limits: 0x???????? - 0x???????? (size unknown)',
+ ' Stack cur used: 0x???????? - 0x???????? (size unknown)',
+ ' Stack max used: size unknown',
+ ' Stack limits: 0x???????? - 0x???????? (size unknown)',
'',
))
analyzer = ThreadSnapshotAnalyzer(snapshot)
@@ -107,6 +112,7 @@
temp_thread.stack_start_pointer = 0x2001ac00
temp_thread.stack_end_pointer = 0x2001aa00
temp_thread.stack_pointer = 0x2001ab0c
+ temp_thread.stack_pointer_est_peak = 0x2001aa00
snapshot.threads.append(temp_thread)
temp_thread = thread_pb2.Thread()
@@ -122,13 +128,15 @@
'',
'Thread (READY): Idle',
'Stack info',
- ' Stack used: 0x2001ac00 - 0x2001ab0c (244 bytes, 47.66%)',
- ' Stack limits: 0x2001ac00 - 0x2001aa00 (512 bytes)',
+ ' Stack cur used: 0x2001ac00 - 0x2001ab0c (244 bytes, 47.66%)',
+ ' Stack max used: 512 bytes, 100.00%',
+ ' Stack limits: 0x2001ac00 - 0x2001aa00 (512 bytes)',
'',
'Thread (INTERRUPT_HANDLER): Main/Handler',
'Stack info',
- ' Stack used: 0x2001b000 - 0x2001ae20 (480 bytes)',
- ' Stack limits: 0x2001b000 - 0x???????? (size unknown)',
+ ' Stack cur used: 0x2001b000 - 0x2001ae20 (480 bytes)',
+ ' Stack max used: size unknown',
+ ' Stack limits: 0x2001b000 - 0x???????? (size unknown)',
'',
))
analyzer = ThreadSnapshotAnalyzer(snapshot)
@@ -145,6 +153,7 @@
temp_thread.stack_start_pointer = 0x2001ac00
temp_thread.stack_end_pointer = 0x2001aa00
temp_thread.stack_pointer = 0x2001ab0c
+ temp_thread.stack_pointer_est_peak = 0x2001ac00 + 0x100
snapshot.threads.append(temp_thread)
temp_thread = thread_pb2.Thread()
@@ -152,6 +161,7 @@
temp_thread.active = True
temp_thread.stack_start_pointer = 0x2001b000
temp_thread.stack_pointer = 0x2001ae20
+ temp_thread.stack_pointer_est_peak = 0x2001b000 + 0x200
temp_thread.state = thread_pb2.ThreadState.Enum.INTERRUPT_HANDLER
snapshot.threads.append(temp_thread)
@@ -163,13 +173,15 @@
# Ensure the active thread is moved to the top of the list.
'Thread (INTERRUPT_HANDLER): Main/Handler <-- [ACTIVE]',
'Stack info',
- ' Stack used: 0x2001b000 - 0x2001ae20 (480 bytes)',
- ' Stack limits: 0x2001b000 - 0x???????? (size unknown)',
+ ' Stack cur used: 0x2001b000 - 0x2001ae20 (480 bytes)',
+ ' Stack max used: 512 bytes',
+ ' Stack limits: 0x2001b000 - 0x???????? (size unknown)',
'',
'Thread (READY): Idle',
'Stack info',
- ' Stack used: 0x2001ac00 - 0x2001ab0c (244 bytes, 47.66%)',
- ' Stack limits: 0x2001ac00 - 0x2001aa00 (512 bytes)',
+ ' Stack cur used: 0x2001ac00 - 0x2001ab0c (244 bytes, 47.66%)',
+ ' Stack max used: 256 bytes, 50.00%',
+ ' Stack limits: 0x2001ac00 - 0x2001aa00 (512 bytes)',
'',
))
analyzer = ThreadSnapshotAnalyzer(snapshot)
diff --git a/pw_thread/snapshot.cc b/pw_thread/snapshot.cc
index 67fb8b7..ec59ed9 100644
--- a/pw_thread/snapshot.cc
+++ b/pw_thread/snapshot.cc
@@ -35,6 +35,9 @@
encoder.WriteStackStartPointer(stack.stack_high_addr);
encoder.WriteStackEndPointer(stack.stack_low_addr);
encoder.WriteStackPointer(stack.stack_pointer);
+ if (stack.stack_pointer_est_peak.has_value()) {
+ encoder.WriteStackPointerEstPeak(stack.stack_pointer_est_peak.value());
+ }
PW_LOG_DEBUG("Active stack: 0x%08x-0x%08x (%ld bytes)",
stack.stack_high_addr,
stack.stack_pointer,
diff --git a/pw_thread_embos/snapshot.cc b/pw_thread_embos/snapshot.cc
index d535ab5..1a9e17d 100644
--- a/pw_thread_embos/snapshot.cc
+++ b/pw_thread_embos/snapshot.cc
@@ -132,6 +132,8 @@
.stack_pointer = reinterpret_cast<uintptr_t>(
ThreadIsRunning(thread) ? running_thread_stack_pointer
: thread.pStack),
+ .stack_pointer_est_peak = reinterpret_cast<uintptr_t>(thread.pStackBot) +
+ thread.StackSize - OS_GetStackUsed(&thread),
};
return SnapshotStack(thread_ctx, encoder, thread_stack_callback);
diff --git a/pw_thread_freertos/snapshot.cc b/pw_thread_freertos/snapshot.cc
index 3c2b98b..d597715 100644
--- a/pw_thread_freertos/snapshot.cc
+++ b/pw_thread_freertos/snapshot.cc
@@ -132,6 +132,7 @@
.stack_low_addr = stack_low_addr,
.stack_high_addr = reinterpret_cast<uintptr_t>(tcb.pxEndOfStack),
.stack_pointer = stack_pointer,
+ .stack_pointer_est_peak = std::nullopt,
};
return SnapshotStack(thread_ctx, encoder, thread_stack_callback);
#else
diff --git a/pw_thread_threadx/snapshot.cc b/pw_thread_threadx/snapshot.cc
index cfd79a4..f38e16d 100644
--- a/pw_thread_threadx/snapshot.cc
+++ b/pw_thread_threadx/snapshot.cc
@@ -144,6 +144,7 @@
.stack_pointer = reinterpret_cast<uintptr_t>(
ThreadIsRunning(thread) ? running_thread_stack_pointer
: thread.tx_thread_stack_ptr),
+ .stack_pointer_est_peak = std::nullopt,
};
return SnapshotStack(thread_ctx, encoder, thread_stack_callback);