A report could be left empty: sampling thread stacks signals every thread, and a debugger such as gdb stops the whole process on that signal before anything is written. Debugger pauses also looked like hangs, so resuming from a breakpoint produced a false report whose sampling froze the app again. The summary (phase, open scopes, input events) and application files are now written first, before any sampling. Sampling is skipped while a debugger is attached, and the report says so. When the watchdog's own 100 ms wait takes a second or more, the whole process was paused, and that time no longer counts towards the stalled phase. The input loop now counts handled events per phase, so a report shows when the UI thread kept processing a stream of events without ever leaving input handling. The watchdog clock is injectable for tests.
41 lines
979 B
C++
41 lines
979 B
C++
#pragma once
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace tp {
|
|
|
|
struct FrameSymbol {
|
|
std::string module;
|
|
uintptr_t moduleOffset = 0;
|
|
std::string function;
|
|
std::string file;
|
|
size_t line = 0;
|
|
};
|
|
|
|
struct ThreadSample {
|
|
int tid = 0;
|
|
std::string name;
|
|
std::string state;
|
|
std::string waitChannel;
|
|
bool answered = false;
|
|
std::vector<uintptr_t> frames;
|
|
};
|
|
|
|
class StackTrace {
|
|
public:
|
|
static constexpr size_t kMaxFrames = 64;
|
|
|
|
static size_t captureCallingThread(uintptr_t* frames, size_t capacity);
|
|
[[nodiscard]] static FrameSymbol symbolize(uintptr_t address);
|
|
[[nodiscard]] static std::string describe(uintptr_t address);
|
|
|
|
[[nodiscard]] static bool canSampleThreads();
|
|
[[nodiscard]] static bool isDebuggerAttached();
|
|
static void prepareThreadSampling();
|
|
[[nodiscard]] static std::vector<ThreadSample> sampleAllThreads(double answerTimeoutMs = 200);
|
|
[[nodiscard]] static int callingThreadId();
|
|
};
|
|
}
|