Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions extension/android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,31 @@ list(
fbjni
)

# =============================================================================
# Android ETDump Profiling with Runtime Control
# =============================================================================
if(EXECUTORCH_ANDROID_PROFILING)
list(APPEND link_libraries etdump flatccrt)
# Add ETDump JNI implementation
target_sources(executorch_jni PRIVATE jni/jni_etdump.cpp)

# Add SDK include directories for ETDump headers
target_include_directories(
executorch_jni PRIVATE ${EXECUTORCH_ROOT}/sdk
${EXECUTORCH_ROOT}/third-party/flatcc/include
)

# Add compile definitions
target_compile_definitions(
executorch_jni PUBLIC EXECUTORCH_ANDROID_PROFILING=1
executorch_jni PUBLIC ET_EVENT_TRACER_ENABLED
EXECUTORCH_ANDROID_PROFILING=1
)
Comment on lines 97 to 111
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

ETDump.java is added unconditionally to the Android AAR, but its JNI implementation (jni_etdump.cpp) is only compiled when EXECUTORCH_ANDROID_PROFILING is ON. This combination makes the Java API unsafe to reference in non-profiling builds (native symbols won’t exist). Consider always compiling/linking JNI stubs for ETDump (returning false/no-op when profiling is disabled), or conditionally excluding the Java source from the Gradle build when the flag is OFF.

Copilot uses AI. Check for mistakes.

# Link ETDump libraries
list(APPEND link_libraries etdump flatccrt)

message(STATUS "Android ETDump profiling with runtime control: ENABLED")
else()
message(STATUS "Android ETDump profiling: DISABLED")
endif()

if(TARGET optimized_native_cpu_ops_lib)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

package org.pytorch.executorch;

import android.util.Log;

/**
* ETDump provides runtime control for ExecuTorch profiling.
*
* <p>Enable profiling before loading models to capture execution traces. Use
* Module.writeETDumpToPath() to write profiling data to custom locations.
*
* <p>Example usage:
*
* <pre>{@code
* // Enable profiling
* ETDump.enableProfiling();
*
* // Load and run model
* Module module = Module.load("model.pte");
* module.forward(inputs);
*
* // Write profiling data to custom path (no root access needed!)
* module.writeETDumpToPath(getCacheDir() + "/profile.etdump");
*
* // Disable profiling
* ETDump.disableProfiling();
* }</pre>
*/
public class ETDump {
private static final String TAG = "ExecuTorch-ETDump";

static {
try {
System.loadLibrary("executorch");
nativeInit();
} catch (UnsatisfiedLinkError e) {
Log.e(TAG, "Failed to load executorch library", e);
throw e;
}
Comment on lines +39 to +46
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

ETDump uses System.loadLibrary("executorch"), but the existing Module class uses SoLoader/NativeLoader initialization. Mixing the two can break apps that rely on SoLoader (or load order). Align with Module’s loading pattern (initialize NativeLoader if needed, then NativeLoader.loadLibrary("executorch")).

Copilot uses AI. Check for mistakes.
}

/** Initialize the ETDump subsystem. Called automatically when the class is loaded. */
private static native void nativeInit();
Comment on lines +39 to +50
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

As implemented, ETDump will throw UnsatisfiedLinkError at class-load time in builds where EXECUTORCH_ANDROID_PROFILING is OFF, because the native methods aren’t compiled/linked (and nativeInit() is invoked in the static initializer). Either (a) always ship JNI stubs for these methods and make them no-ops/return false when profiling is disabled, or (b) exclude this Java class from non-profiling builds so referencing it can’t crash the app.

Copilot uses AI. Check for mistakes.

/**
* Enable profiling for subsequently loaded models.
*
* <p>Modules loaded after calling this method will capture profiling data. This has zero runtime
* overhead until a model is actually loaded.
*
* @return true if profiling was successfully enabled
*/
public static boolean enableProfiling() {
boolean result = nativeEnableProfiling();
if (result) {
Log.i(TAG, "Profiling enabled");
} else {
Log.e(TAG, "Failed to enable profiling");
}
return result;
}
Comment on lines +52 to +68
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

PR description says ETDump.enableProfiling("/path") and ETDump.getETDumpData(), but the code adds ETDump.enableProfiling() (no path) and exposes data access on Module.getETDumpData(). Please update the PR description (or the API) so the documented usage matches what’s actually shipped.

Copilot uses AI. Check for mistakes.

/**
* Disable profiling.
*
* <p>Modules loaded after calling this method will not capture profiling data.
*/
public static void disableProfiling() {
nativeDisableProfiling();
Log.i(TAG, "Profiling disabled");
}

/**
* Check if profiling is currently enabled.
*
* @return true if profiling is enabled
*/
public static boolean isProfilingEnabled() {
return nativeIsProfilingEnabled();
}

// Native methods
private static native boolean nativeEnableProfiling();

private static native void nativeDisableProfiling();

private static native boolean nativeIsProfilingEnabled();
}
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,71 @@ public void destroy() {
+ " released.");
}
}

/**
* Write ETDump profiling data to a specific path.
*
* <p>This allows writing profiling data to custom locations such as app cache directory or
* external storage. No root access required.
*
* <p>Example:
*
* <pre>{@code
* Module module = Module.load("model.pte");
* module.forward(inputs);
* module.writeETDumpToPath(getCacheDir() + "/profile.etdump");
* }</pre>
*
* @param outputPath The file path where ETDump data will be written. Must be a writable location.
* @return true if the data was successfully written, false otherwise
*/
@DoNotStrip
public boolean writeETDumpToPath(String outputPath) {
if (outputPath == null || outputPath.isEmpty()) {
android.util.Log.e("ExecuTorch-Module", "Output path cannot be null or empty");
return false;
}

// Validate that parent directory exists
java.io.File file = new java.io.File(outputPath);
java.io.File parentDir = file.getParentFile();
if (parentDir != null && !parentDir.exists()) {
if (!parentDir.mkdirs()) {
android.util.Log.e("ExecuTorch-Module", "Failed to create parent directory: " + parentDir);
return false;
}
}

return nativeWriteETDumpToPath(outputPath);
}

/**
* Get ETDump profiling data as a byte array.
*
* <p>This is useful for custom handling of profiling data, such as uploading to a server or
* processing in-memory without writing to disk.
*
* <p>Example:
*
* <pre>{@code
* Module module = Module.load("model.pte");
* module.forward(inputs);
* byte[] profileData = module.getETDumpData();
* if (profileData != null) {
* uploadToServer(profileData);
* }
* }</pre>
*
* @return byte array containing the ETDump data, or null if no data is available
*/
@DoNotStrip
public byte[] getETDumpData() {
return nativeGetETDumpData();
}

@DoNotStrip
private native boolean nativeWriteETDumpToPath(String outputPath);

@DoNotStrip
private native byte[] nativeGetETDumpData();
}
80 changes: 80 additions & 0 deletions extension/android/jni/jni_etdump.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

#include <fbjni/fbjni.h>
#include <jni.h>

#ifdef EXECUTORCH_ANDROID_PROFILING

#include "jni_etdump.h"

namespace executorch {
namespace extension {
namespace jni {

// ============================================================================
// Global ETDump Manager
// ============================================================================

// Global ETDump manager (one per process)
static std::unique_ptr<ETDumpManager> g_etdump_manager = nullptr;
Comment on lines +9 to +25
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

This file uses std::unique_ptr / std::make_unique but doesn’t include <memory>, which will fail to compile on toolchains that don’t pull it in transitively. Add the missing standard header include.

Copilot uses AI. Check for mistakes.

extern "C" ETDumpManager* getGlobalETDumpManager() {
return g_etdump_manager.get();
}

// ============================================================================
// JNI Methods for ETDump Java class
// ============================================================================

// Initialize ETDump manager
extern "C" JNIEXPORT void JNICALL
Java_org_pytorch_executorch_ETDump_nativeInit(JNIEnv* env, jclass clazz) {
if (!g_etdump_manager) {
g_etdump_manager = std::make_unique<ETDumpManager>();
}
}

// Enable profiling
extern "C" JNIEXPORT jboolean JNICALL
Java_org_pytorch_executorch_ETDump_nativeEnableProfiling(
JNIEnv* env,
jclass clazz) {
if (!g_etdump_manager) {
g_etdump_manager = std::make_unique<ETDumpManager>();
}
g_etdump_manager->enableProfiling();
return JNI_TRUE;
}

// Disable profiling
extern "C" JNIEXPORT void JNICALL
Java_org_pytorch_executorch_ETDump_nativeDisableProfiling(
JNIEnv* env,
jclass clazz) {
if (g_etdump_manager) {
g_etdump_manager->disableProfiling();
}
}

// Check if profiling is enabled
extern "C" JNIEXPORT jboolean JNICALL
Java_org_pytorch_executorch_ETDump_nativeIsProfilingEnabled(
JNIEnv* env,
jclass clazz) {
if (!g_etdump_manager) {
return JNI_FALSE;
}
return g_etdump_manager->isProfilingEnabled() ? JNI_TRUE : JNI_FALSE;
}

} // namespace jni
} // namespace extension
} // namespace executorch

#endif // EXECUTORCH_ANDROID_PROFILING
63 changes: 63 additions & 0 deletions extension/android/jni/jni_etdump.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#ifdef EXECUTORCH_ANDROID_PROFILING
#include <executorch/devtools/etdump/etdump_flatcc.h>

namespace executorch {
namespace extension {
namespace jni {

/**
* ETDumpManager manages the global profiling enabled state.
* This is a singleton that controls whether newly loaded modules
* should capture profiling data.
*/
class ETDumpManager {
public:
ETDumpManager() : profiling_enabled_(false) {}

/**
* Enable profiling for subsequently loaded modules.
*/
void enableProfiling() {
profiling_enabled_ = true;
}

/**
* Disable profiling.
*/
void disableProfiling() {
profiling_enabled_ = false;
}

/**
* Check if profiling is currently enabled.
* @return true if profiling is enabled
*/
bool isProfilingEnabled() const {
return profiling_enabled_;
}

private:
bool profiling_enabled_;
};
Comment on lines +45 to +51
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

profiling_enabled_ is accessed from both Java (enable/disable) and C++ module loading without synchronization, which creates a data race if toggled concurrently with module loads. Use std::atomic<bool> (or a mutex) for profiling_enabled_ and ensure g_etdump_manager initialization/access is also thread-safe.

Copilot uses AI. Check for mistakes.

/**
* Get the global ETDump manager instance.
* @return Pointer to the global ETDumpManager, or nullptr if not initialized
*/
extern "C" ETDumpManager* getGlobalETDumpManager();

} // namespace jni
} // namespace extension
} // namespace executorch

#endif // EXECUTORCH_ANDROID_PROFILING
Loading
Loading