Skip to content

Commit c5b88cc

Browse files
authored
Add in-memory log buffer in Android JNI
Differential Revision: D65474006 Pull Request resolved: #6656
1 parent 836d556 commit c5b88cc

File tree

4 files changed

+88
-4
lines changed

4 files changed

+88
-4
lines changed

extension/android/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,4 +190,4 @@ target_include_directories(
190190

191191
target_compile_options(executorch_jni PUBLIC ${_common_compile_options})
192192

193-
target_link_libraries(executorch_jni ${link_libraries})
193+
target_link_libraries(executorch_jni ${link_libraries} log)

extension/android/jni/jni_layer.cpp

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,45 @@
3333
#include <fbjni/ByteBuffer.h>
3434
#include <fbjni/fbjni.h>
3535

36+
using namespace executorch::extension;
37+
using namespace torch::executor;
38+
3639
#ifdef __ANDROID__
3740
#include <android/log.h>
41+
#include <mutex>
42+
#include <sstream>
43+
44+
// Number of entries to store in the in-memory log buffer.
45+
const size_t log_buffer_length = 16;
46+
47+
struct log_entry {
48+
et_timestamp_t timestamp;
49+
et_pal_log_level_t level;
50+
std::string filename;
51+
std::string function;
52+
size_t line;
53+
std::string message;
54+
55+
log_entry(
56+
et_timestamp_t timestamp,
57+
et_pal_log_level_t level,
58+
const char* filename,
59+
const char* function,
60+
size_t line,
61+
const char* message,
62+
size_t length)
63+
: timestamp(timestamp),
64+
level(level),
65+
filename(filename),
66+
function(function),
67+
line(line),
68+
message(message, length) {}
69+
};
70+
71+
namespace {
72+
std::vector<log_entry> log_buffer_;
73+
std::mutex log_buffer_mutex_;
74+
} // namespace
3875

3976
// For Android, write to logcat
4077
void et_pal_emit_log_message(
@@ -45,6 +82,15 @@ void et_pal_emit_log_message(
4582
size_t line,
4683
const char* message,
4784
size_t length) {
85+
std::lock_guard<std::mutex> guard(log_buffer_mutex_);
86+
87+
while (log_buffer_.size() >= log_buffer_length) {
88+
log_buffer_.erase(log_buffer_.begin());
89+
}
90+
91+
log_buffer_.emplace_back(
92+
timestamp, level, filename, function, line, message, length);
93+
4894
int android_log_level = ANDROID_LOG_UNKNOWN;
4995
if (level == 'D') {
5096
android_log_level = ANDROID_LOG_DEBUG;
@@ -60,9 +106,6 @@ void et_pal_emit_log_message(
60106
}
61107
#endif
62108

63-
using namespace executorch::extension;
64-
using namespace torch::executor;
65-
66109
namespace executorch::extension {
67110
class TensorHybrid : public facebook::jni::HybridClass<TensorHybrid> {
68111
public:
@@ -391,12 +434,44 @@ class ExecuTorchJni : public facebook::jni::HybridClass<ExecuTorchJni> {
391434
return jresult;
392435
}
393436

437+
facebook::jni::local_ref<facebook::jni::JArrayClass<jstring>>
438+
readLogBuffer() {
439+
#ifdef __ANDROID__
440+
std::lock_guard<std::mutex> guard(log_buffer_mutex_);
441+
442+
const auto size = log_buffer_.size();
443+
facebook::jni::local_ref<facebook::jni::JArrayClass<jstring>> ret =
444+
facebook::jni::JArrayClass<jstring>::newArray(size);
445+
446+
for (auto i = 0u; i < size; i++) {
447+
const auto& entry = log_buffer_[i];
448+
// Format the log entry as "[TIMESTAMP FUNCTION FILE:LINE] LEVEL MESSAGE".
449+
std::stringstream ss;
450+
ss << "[" << entry.timestamp << " " << entry.function << " "
451+
<< entry.filename << ":" << entry.line << "] "
452+
<< static_cast<char>(entry.level) << " " << entry.message;
453+
454+
facebook::jni::local_ref<facebook::jni::JString> jstr_message =
455+
facebook::jni::make_jstring(ss.str().c_str());
456+
(*ret)[i] = jstr_message;
457+
}
458+
459+
return ret;
460+
#else
461+
return facebook::jni::JArrayClass<String>::newArray(0);
462+
#endif
463+
}
464+
394465
static void registerNatives() {
395466
registerHybrid({
396467
makeNativeMethod("initHybrid", ExecuTorchJni::initHybrid),
397468
makeNativeMethod("forward", ExecuTorchJni::forward),
398469
makeNativeMethod("execute", ExecuTorchJni::execute),
399470
makeNativeMethod("loadMethod", ExecuTorchJni::load_method),
471+
472+
#ifdef __ANDROID__
473+
makeNativeMethod("readLogBuffer", ExecuTorchJni::readLogBuffer),
474+
#endif
400475
});
401476
}
402477
};

extension/android/src/main/java/org/pytorch/executorch/Module.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ public int loadMethod(String methodName) {
9999
return mNativePeer.loadMethod(methodName);
100100
}
101101

102+
/** Retrieve the in-memory log buffer, containing the most recent ExecuTorch log entries. */
103+
public String[] readLogBuffer() {
104+
return mNativePeer.readLogBuffer();
105+
}
106+
102107
/**
103108
* Explicitly destroys the native torch::jit::Module. Calling this method is not required, as the
104109
* native object will be destroyed when this object is garbage-collected. However, the timing of

extension/android/src/main/java/org/pytorch/executorch/NativePeer.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,8 @@ public void resetNative() {
5454
*/
5555
@DoNotStrip
5656
public native int loadMethod(String methodName);
57+
58+
/** Retrieve the in-memory log buffer, containing the most recent ExecuTorch log entries. */
59+
@DoNotStrip
60+
public native String[] readLogBuffer();
5761
}

0 commit comments

Comments
 (0)