Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 25fd117d authored by Zimuzo Ezeozue's avatar Zimuzo Ezeozue Committed by Android (Google) Code Review
Browse files

Merge "Switch from GetUTFStringChars to GetStringRegion" into main

parents deaba264 50adc086
Loading
Loading
Loading
Loading
+17 −4
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import dalvik.annotation.optimization.FastNative;

import libcore.util.NativeAllocationRegistry;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;

@@ -50,6 +52,7 @@ public final class PerfettoTrackEventExtra {
    private static final Supplier<FieldString> sFieldStringSupplier = FieldString::new;
    private static final Supplier<FieldNested> sFieldNestedSupplier = FieldNested::new;

    private final List<PerfettoPointer> mPendingPointers = new ArrayList<>();
    private CounterInt64 mCounterInt64;
    private CounterDouble mCounterDouble;
    private Proto mProto;
@@ -592,7 +595,7 @@ public final class PerfettoTrackEventExtra {
            checkContainer();
            FieldInt64 field = mFieldInt64Cache.get(sFieldInt64Supplier);
            field.setValue(id, val);
            mCurrentContainer.addField(field);
            mExtra.addPerfettoPointer(mCurrentContainer, field);
            return this;
        }

@@ -601,7 +604,7 @@ public final class PerfettoTrackEventExtra {
            checkContainer();
            FieldDouble field = mFieldDoubleCache.get(sFieldDoubleSupplier);
            field.setValue(id, val);
            mCurrentContainer.addField(field);
            mExtra.addPerfettoPointer(mCurrentContainer, field);
            return this;
        }

@@ -610,7 +613,7 @@ public final class PerfettoTrackEventExtra {
            checkContainer();
            FieldString field = mFieldStringCache.get(sFieldStringSupplier);
            field.setValue(id, val);
            mCurrentContainer.addField(field);
            mExtra.addPerfettoPointer(mCurrentContainer, field);
            return this;
        }

@@ -635,7 +638,7 @@ public final class PerfettoTrackEventExtra {
            checkContainer();
            FieldNested field = mFieldNestedCache.get(sFieldNestedSupplier);
            field.setId(id);
            mCurrentContainer.addField(field);
            mExtra.addPerfettoPointer(mCurrentContainer, field);
            return mBuilderCache.get(sBuilderSupplier).initInternal(this, field);
        }

@@ -735,6 +738,15 @@ public final class PerfettoTrackEventExtra {
     */
    public void addPerfettoPointer(PerfettoPointer extra) {
        native_add_arg(mPtr, extra.getPtr());
        mPendingPointers.add(extra);
    }

    /**
     * Adds a pointer representing a track event parameter to the {@code container}.
     */
    public void addPerfettoPointer(FieldContainer container, PerfettoPointer extra) {
        container.addField(extra);
        mPendingPointers.add(extra);
    }

    /**
@@ -742,6 +754,7 @@ public final class PerfettoTrackEventExtra {
     */
    public void reset() {
        native_clear_args(mPtr);
        mPendingPointers.clear();
    }

    private CounterInt64 getCounterInt64() {
+143 −23
Original line number Diff line number Diff line
@@ -23,7 +23,8 @@
#include <nativehelper/utils.h>
#include <tracing_sdk.h>

static constexpr ssize_t kMaxStrLen = 4096;
#include <list>

namespace android {
template <typename T>
inline static T* toPointer(jlong ptr) {
@@ -35,24 +36,145 @@ inline static jlong toJLong(T* ptr) {
    return static_cast<jlong>(reinterpret_cast<uintptr_t>(ptr));
}

/**
 * @brief A thread-safe utility class for converting Java UTF-16 strings to ASCII in JNI
 * environment.
 *
 * StringBuffer provides efficient conversion of Java strings to ASCII with optimized memory
 * handling.
 * It uses a two-tiered buffering strategy:
 * 1. A fast path using pre-allocated thread-local buffers for strings up to 128 characters
 * 2. A fallback path using dynamic allocation for longer strings
 *
 * Non-ASCII characters (>255) are replaced with '?' during conversion. The class maintains
 * thread safety through thread-local storage and provides zero-copy string views for optimal
 * performance.
 *
 * Memory Management:
 * - Uses fixed-size thread-local buffers for both UTF-16 and ASCII characters
 * - Overflow strings are stored in a thread-local list to maintain valid string views
 * - Avoids unnecessary allocations in the common case of small strings
 *
 * Usage example:
 * @code
 * JNIEnv* env = ...;
 * jstring java_string = ...;
 * std::string_view ascii = StringBuffer::utf16_to_ascii(env, java_string);
 * // Use the ASCII string...
 * StringBuffer::reset(); // Clean up when done
 * @endcode
 *
 * Thread Safety: All methods are thread-safe due to thread-local storage.
 */
class StringBuffer {
private:
    static constexpr size_t BASE_SIZE = 128;
    // Temporarily stores the UTF-16 characters retrieved from the Java
    // string before they are converted to ASCII.
    static thread_local inline char char_buffer[BASE_SIZE];
    // For fast-path conversions when the resulting ASCII string fits within
    // the pre-allocated space. All ascii strings in a trace event will be stored
    // here until emitted.
    static thread_local inline jchar jchar_buffer[BASE_SIZE];
    // When the fast-path conversion is not possible (because char_buffer
    // doesn't have enough space), the converted ASCII string is stored
    // in this list. We use list here to avoid moving the strings on resize
    // with vector. This way, we can give out string_views from the stored strings.
    // The additional overhead from list node allocations is fine cos we are already
    // in an extremely unlikely path here and there are other bigger problems if here.
    static thread_local inline std::list<std::string> overflow_strings;
    // current offset into the char_buffer.
    static thread_local inline size_t current_offset{0};
    // This allows us avoid touching the overflow_strings directly in the fast path.
    // Touching it causes some thread local init routine to run which shows up in profiles.
    static thread_local inline bool is_overflow_strings_empty = true;

    static void copy_utf16_to_ascii(const jchar* src, size_t len, char* dst, JNIEnv* env,
                                    jstring str) {
        std::transform(src, src + len, dst,
                       [](jchar c) { return (c <= 0xFF) ? static_cast<char>(c) : '?'; });

        if (src != jchar_buffer) {
            // We hit the slow path to populate src, so we have to release.
            env->ReleaseStringCritical(str, src);
        }
    }

public:
    static void reset() {
        if (!is_overflow_strings_empty) {
            overflow_strings.clear();
            is_overflow_strings_empty = true;
        }
        current_offset = 0;
    }

    // Converts a Java string (jstring) to an ASCII string_view. Characters
    // outside the ASCII range (0-255) are replaced with '?'.
    //
    // @param env The JNI environment.
    // @param val The Java string to convert.
    // @return A string_view representing the ASCII version of the string.
    //         Returns an empty string_view if the input is null or empty.
    static std::string_view utf16_to_ascii(JNIEnv* env, jstring val) {
        if (!val) return "";

        const jsize len = env->GetStringLength(val);
        if (len == 0) return "";

        const jchar* temp_buffer;

        // Fast path: Enough space in jchar_buffer
        if (static_cast<size_t>(len) <= BASE_SIZE) {
            env->GetStringRegion(val, 0, len, jchar_buffer);
            temp_buffer = jchar_buffer;
        } else {
            // Slow path: Fallback to asking ART for the string which will likely
            // allocate and return a copy.
            temp_buffer = env->GetStringCritical(val, nullptr);
        }

        const size_t next_offset = current_offset + len + 1;
        // Fast path: Enough space in char_buffer
        if (BASE_SIZE > next_offset) {
            const size_t start_offset = current_offset;

            copy_utf16_to_ascii(temp_buffer, len, char_buffer + current_offset, env, val);
            char_buffer[current_offset + len] = '\0';

            auto res = std::string_view(char_buffer + current_offset, len);
            current_offset = next_offset;
            return res;
        } else {
            // Slow path: Not enough space in char_buffer. Use overflow_strings.
            // This will cause a string alloc but should be very unlikely to hit.
            std::string& str = overflow_strings.emplace_back(len + 1, '\0');

            copy_utf16_to_ascii(temp_buffer, len, str.data(), env, val);
            is_overflow_strings_empty = false;
            return std::string_view(str);
        }
    }
};

static jlong android_os_PerfettoTrackEventExtraArgInt64_init(JNIEnv* env, jclass, jstring name) {
    ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
    return toJLong(new tracing_perfetto::DebugArg<int64_t>(name_chars.c_str()));
    return toJLong(new tracing_perfetto::DebugArg<int64_t>(
            StringBuffer::utf16_to_ascii(env, name).data()));
}

static jlong android_os_PerfettoTrackEventExtraArgBool_init(JNIEnv* env, jclass, jstring name) {
    ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
    return toJLong(new tracing_perfetto::DebugArg<bool>(name_chars.c_str()));
    return toJLong(
            new tracing_perfetto::DebugArg<bool>(StringBuffer::utf16_to_ascii(env, name).data()));
}

static jlong android_os_PerfettoTrackEventExtraArgDouble_init(JNIEnv* env, jclass, jstring name) {
    ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
    return toJLong(new tracing_perfetto::DebugArg<double>(name_chars.c_str()));
    return toJLong(
            new tracing_perfetto::DebugArg<double>(StringBuffer::utf16_to_ascii(env, name).data()));
}

static jlong android_os_PerfettoTrackEventExtraArgString_init(JNIEnv* env, jclass, jstring name) {
    ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
    return toJLong(new tracing_perfetto::DebugArg<const char*>(name_chars.c_str()));
    return toJLong(new tracing_perfetto::DebugArg<const char*>(
            StringBuffer::utf16_to_ascii(env, name).data()));
}

static jlong android_os_PerfettoTrackEventExtraArgInt64_delete() {
@@ -109,11 +231,9 @@ static void android_os_PerfettoTrackEventExtraArgDouble_set_value(jlong ptr, jdo

static void android_os_PerfettoTrackEventExtraArgString_set_value(JNIEnv* env, jclass, jlong ptr,
                                                                  jstring val) {
    ScopedUtfChars val_chars = GET_UTF_OR_RETURN_VOID(env, val);

    tracing_perfetto::DebugArg<const char*>* arg =
            toPointer<tracing_perfetto::DebugArg<const char*>>(ptr);
    arg->set_value(strdup(val_chars.c_str()));
    arg->set_value(StringBuffer::utf16_to_ascii(env, val).data());
}

static jlong android_os_PerfettoTrackEventExtraFieldInt64_init() {
@@ -186,11 +306,9 @@ static void android_os_PerfettoTrackEventExtraFieldDouble_set_value(jlong ptr, j

static void android_os_PerfettoTrackEventExtraFieldString_set_value(JNIEnv* env, jclass, jlong ptr,
                                                                    jlong id, jstring val) {
    ScopedUtfChars val_chars = GET_UTF_OR_RETURN_VOID(env, val);

    tracing_perfetto::ProtoField<const char*>* field =
            toPointer<tracing_perfetto::ProtoField<const char*>>(ptr);
    field->set_value(id, strdup(val_chars.c_str()));
    field->set_value(id, StringBuffer::utf16_to_ascii(env, val).data());
}

static void android_os_PerfettoTrackEventExtraFieldNested_add_field(jlong field_ptr,
@@ -231,8 +349,9 @@ static jlong android_os_PerfettoTrackEventExtraFlow_get_extra_ptr(jlong ptr) {

static jlong android_os_PerfettoTrackEventExtraNamedTrack_init(JNIEnv* env, jclass, jlong id,
                                                               jstring name, jlong parent_uuid) {
    ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);
    return toJLong(new tracing_perfetto::NamedTrack(id, parent_uuid, name_chars.c_str()));
    return toJLong(
            new tracing_perfetto::NamedTrack(id, parent_uuid,
                                             StringBuffer::utf16_to_ascii(env, name).data()));
}

static jlong android_os_PerfettoTrackEventExtraNamedTrack_delete() {
@@ -246,9 +365,10 @@ static jlong android_os_PerfettoTrackEventExtraNamedTrack_get_extra_ptr(jlong pt

static jlong android_os_PerfettoTrackEventExtraCounterTrack_init(JNIEnv* env, jclass, jstring name,
                                                                 jlong parent_uuid) {
    ScopedUtfChars name_chars = GET_UTF_OR_RETURN(env, name);

    return toJLong(new tracing_perfetto::RegisteredTrack(1, parent_uuid, name_chars.c_str(), true));
    return toJLong(
            new tracing_perfetto::RegisteredTrack(1, parent_uuid,
                                                  StringBuffer::utf16_to_ascii(env, name).data(),
                                                  true));
}

static jlong android_os_PerfettoTrackEventExtraCounterTrack_delete() {
@@ -318,11 +438,11 @@ static void android_os_PerfettoTrackEventExtra_clear_args(jlong ptr) {

static void android_os_PerfettoTrackEventExtra_emit(JNIEnv* env, jclass, jint type, jlong cat_ptr,
                                                    jstring name, jlong extra_ptr) {
    ScopedUtfChars name_chars = GET_UTF_OR_RETURN_VOID(env, name);

    tracing_perfetto::Category* category = toPointer<tracing_perfetto::Category>(cat_ptr);
    tracing_perfetto::trace_event(type, category->get(), name_chars.c_str(),
    tracing_perfetto::trace_event(type, category->get(),
                                  StringBuffer::utf16_to_ascii(env, name).data(),
                                  toPointer<tracing_perfetto::Extra>(extra_ptr));
    StringBuffer::reset();
}

static jlong android_os_PerfettoTrackEventExtraProto_init() {
+49 −36
Original line number Diff line number Diff line
@@ -77,6 +77,8 @@ public class PerfettoTraceTest {
    private static final String TAG = "PerfettoTraceTest";
    private static final String FOO = "foo";
    private static final String BAR = "bar";
    private static final String TEXT_ABOVE_4K_SIZE =
            new String(new char[8192]).replace('\0', 'a');

    private static final Category FOO_CATEGORY = new Category(FOO);
    private static final int MESSAGE = 1234567;
@@ -153,41 +155,6 @@ public class PerfettoTraceTest {
        assertThat(mDebugAnnotationNames).contains("string_val");
    }

    @Test
    @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
    public void testDebugAnnotationsWithLambda() throws Exception {
        TraceConfig traceConfig = getTraceConfig(FOO);

        PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());

        PerfettoTrace.instant(FOO_CATEGORY, "event").addArg("long_val", 123L).emit();

        byte[] traceBytes = session.close();

        Trace trace = Trace.parseFrom(traceBytes);

        boolean hasTrackEvent = false;
        boolean hasDebugAnnotations = false;
        for (TracePacket packet: trace.getPacketList()) {
            TrackEvent event;
            if (packet.hasTrackEvent()) {
                hasTrackEvent = true;
                event = packet.getTrackEvent();

                if (TrackEvent.Type.TYPE_INSTANT.equals(event.getType())
                        && event.getDebugAnnotationsCount() == 1) {
                    hasDebugAnnotations = true;

                    List<DebugAnnotation> annotations = event.getDebugAnnotationsList();
                    assertThat(annotations.get(0).getIntValue()).isEqualTo(123L);
                }
            }
        }

        assertThat(hasTrackEvent).isTrue();
        assertThat(hasDebugAnnotations).isTrue();
    }

    @Test
    @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
    public void testNamedTrack() throws Exception {
@@ -440,7 +407,6 @@ public class PerfettoTraceTest {

        boolean hasTrackEvent = false;
        boolean hasSourceLocation = false;

        for (TracePacket packet: trace.getPacketList()) {
            TrackEvent event;
            if (packet.hasTrackEvent()) {
@@ -465,6 +431,53 @@ public class PerfettoTraceTest {
        assertThat(mCategoryNames).contains(FOO);
    }

    @Test
    @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
    public void testProtoWithSlowPath() throws Exception {
        TraceConfig traceConfig = getTraceConfig(FOO);

        PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray());

        PerfettoTrace.instant(FOO_CATEGORY, "event_proto")
                .beginProto()
                .beginNested(33L)
                .addField(4L, 2L)
                .addField(3, TEXT_ABOVE_4K_SIZE)
                .endNested()
                .addField(2001, "AIDL::IActivityManager")
                .endProto()
                .emit();

        byte[] traceBytes = session.close();

        Trace trace = Trace.parseFrom(traceBytes);

        boolean hasTrackEvent = false;
        boolean hasSourceLocation = false;
        for (TracePacket packet: trace.getPacketList()) {
            TrackEvent event;
            if (packet.hasTrackEvent()) {
                hasTrackEvent = true;
                event = packet.getTrackEvent();

                if (TrackEvent.Type.TYPE_INSTANT.equals(event.getType())
                        && event.hasSourceLocation()) {
                    SourceLocation loc = event.getSourceLocation();
                    if (TEXT_ABOVE_4K_SIZE.equals(loc.getFunctionName())
                            && loc.getLineNumber() == 2) {
                        hasSourceLocation = true;
                    }
                }
            }

            collectInternedData(packet);
        }

        assertThat(hasTrackEvent).isTrue();
        assertThat(hasSourceLocation).isTrue();
        assertThat(mCategoryNames).contains(FOO);
    }

    @Test
    @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
    public void testProtoNested() throws Exception {