diff --git a/extension/android/CMakeLists.txt b/extension/android/CMakeLists.txt index 31f24b39793..c96cfeb5d70 100644 --- a/extension/android/CMakeLists.txt +++ b/extension/android/CMakeLists.txt @@ -190,4 +190,4 @@ target_include_directories( target_compile_options(executorch_jni PUBLIC ${_common_compile_options}) -target_link_libraries(executorch_jni ${link_libraries}) +target_link_libraries(executorch_jni ${link_libraries} log) diff --git a/extension/android/jni/jni_layer.cpp b/extension/android/jni/jni_layer.cpp index a6f0045725f..479da288063 100644 --- a/extension/android/jni/jni_layer.cpp +++ b/extension/android/jni/jni_layer.cpp @@ -33,8 +33,45 @@ #include #include +using namespace executorch::extension; +using namespace torch::executor; + #ifdef __ANDROID__ #include +#include +#include + +// Number of entries to store in the in-memory log buffer. +const size_t log_buffer_length = 16; + +struct log_entry { + et_timestamp_t timestamp; + et_pal_log_level_t level; + std::string filename; + std::string function; + size_t line; + std::string message; + + log_entry( + et_timestamp_t timestamp, + et_pal_log_level_t level, + const char* filename, + const char* function, + size_t line, + const char* message, + size_t length) + : timestamp(timestamp), + level(level), + filename(filename), + function(function), + line(line), + message(message, length) {} +}; + +namespace { +std::vector log_buffer_; +std::mutex log_buffer_mutex_; +} // namespace // For Android, write to logcat void et_pal_emit_log_message( @@ -45,6 +82,15 @@ void et_pal_emit_log_message( size_t line, const char* message, size_t length) { + std::lock_guard guard(log_buffer_mutex_); + + while (log_buffer_.size() >= log_buffer_length) { + log_buffer_.erase(log_buffer_.begin()); + } + + log_buffer_.emplace_back( + timestamp, level, filename, function, line, message, length); + int android_log_level = ANDROID_LOG_UNKNOWN; if (level == 'D') { android_log_level = ANDROID_LOG_DEBUG; @@ -60,9 +106,6 @@ void et_pal_emit_log_message( } #endif -using namespace executorch::extension; -using namespace torch::executor; - namespace executorch::extension { class TensorHybrid : public facebook::jni::HybridClass { public: @@ -391,12 +434,44 @@ class ExecuTorchJni : public facebook::jni::HybridClass { return jresult; } + facebook::jni::local_ref> + readLogBuffer() { +#ifdef __ANDROID__ + std::lock_guard guard(log_buffer_mutex_); + + const auto size = log_buffer_.size(); + facebook::jni::local_ref> ret = + facebook::jni::JArrayClass::newArray(size); + + for (auto i = 0u; i < size; i++) { + const auto& entry = log_buffer_[i]; + // Format the log entry as "[TIMESTAMP FUNCTION FILE:LINE] LEVEL MESSAGE". + std::stringstream ss; + ss << "[" << entry.timestamp << " " << entry.function << " " + << entry.filename << ":" << entry.line << "] " + << static_cast(entry.level) << " " << entry.message; + + facebook::jni::local_ref jstr_message = + facebook::jni::make_jstring(ss.str().c_str()); + (*ret)[i] = jstr_message; + } + + return ret; +#else + return facebook::jni::JArrayClass::newArray(0); +#endif + } + static void registerNatives() { registerHybrid({ makeNativeMethod("initHybrid", ExecuTorchJni::initHybrid), makeNativeMethod("forward", ExecuTorchJni::forward), makeNativeMethod("execute", ExecuTorchJni::execute), makeNativeMethod("loadMethod", ExecuTorchJni::load_method), + +#ifdef __ANDROID__ + makeNativeMethod("readLogBuffer", ExecuTorchJni::readLogBuffer), +#endif }); } }; diff --git a/extension/android/src/main/java/org/pytorch/executorch/Module.java b/extension/android/src/main/java/org/pytorch/executorch/Module.java index 608439548af..879b88c5f2f 100644 --- a/extension/android/src/main/java/org/pytorch/executorch/Module.java +++ b/extension/android/src/main/java/org/pytorch/executorch/Module.java @@ -99,6 +99,11 @@ public int loadMethod(String methodName) { return mNativePeer.loadMethod(methodName); } + /** Retrieve the in-memory log buffer, containing the most recent ExecuTorch log entries. */ + public String[] readLogBuffer() { + return mNativePeer.readLogBuffer(); + } + /** * Explicitly destroys the native torch::jit::Module. Calling this method is not required, as the * native object will be destroyed when this object is garbage-collected. However, the timing of diff --git a/extension/android/src/main/java/org/pytorch/executorch/NativePeer.java b/extension/android/src/main/java/org/pytorch/executorch/NativePeer.java index 2cf2ee53d71..a5487a4702e 100644 --- a/extension/android/src/main/java/org/pytorch/executorch/NativePeer.java +++ b/extension/android/src/main/java/org/pytorch/executorch/NativePeer.java @@ -54,4 +54,8 @@ public void resetNative() { */ @DoNotStrip public native int loadMethod(String methodName); + + /** Retrieve the in-memory log buffer, containing the most recent ExecuTorch log entries. */ + @DoNotStrip + public native String[] readLogBuffer(); }