android_sdk: Add nested-track ordering and merging Expose sibling ordering and sibling merging for nested tracks end to end, from the C SDK high-level ABI through te_macros, the shared lib, JNI and the Android Java SDK. - PerfettoTeHlNestedTrackNamed gains sibling_order_rank, child_ordering, sibling_merge_behavior and a string/integer sibling merge key, with PERFETTO_TE_HL_CHILD_ORDERING_* and PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_* constants mirroring the proto enums. - te_macros: PERFETTO_TE_NESTED_TRACK_NAMED_ORDERED(NAME, ID, RANK, ORDERING) and PERFETTO_TE_NESTED_TRACK_NAMED_MERGED(NAME, ID, BEHAVIOR, KEY_STR, KEY_INT). - hl.cc: EmitNamedTrack now takes the nested-named struct and emits the new fields on the TrackDescriptor. - Java: PerfettoTrack.setSiblingOrderRank() / setChildOrdering() / setSiblingMergeBehavior() / setSiblingMergeKey(long|String) plus CHILD_ORDERING_* and SIBLING_MERGE_BEHAVIOR_* constants; the chain carries parallel arrays through JNI (direct region copies, lengths validated against the names array). Rust bindings mirror the struct additions. The example, shared-lib integration tests and Java host tests cover both features. Change-Id: I447196a0956ab9445b62a21c3bc3488c17e3e170
diff --git a/contrib/rust-sdk/perfetto-sys/src/bindings.rs b/contrib/rust-sdk/perfetto-sys/src/bindings.rs index d6497ba..3567829 100644 --- a/contrib/rust-sdk/perfetto-sys/src/bindings.rs +++ b/contrib/rust-sdk/perfetto-sys/src/bindings.rs
@@ -834,6 +834,11 @@ pub name: *const ::std::os::raw::c_char, pub id: u64, pub is_name_static: bool, + pub sibling_order_rank: i32, + pub child_ordering: u32, + pub sibling_merge_behavior: u32, + pub sibling_merge_key_str: *const ::std::os::raw::c_char, + pub sibling_merge_key_int: u64, } #[repr(C)] #[derive(Debug, Copy, Clone)]
diff --git a/contrib/rust-sdk/perfetto/src/track_event.rs b/contrib/rust-sdk/perfetto/src/track_event.rs index bfb60d2..d6d133b 100644 --- a/contrib/rust-sdk/perfetto/src/track_event.rs +++ b/contrib/rust-sdk/perfetto/src/track_event.rs
@@ -1076,6 +1076,11 @@ name: cname.as_ptr(), id: *id, is_name_static: false, + sibling_order_rank: 0, + child_ordering: 0, + sibling_merge_behavior: 0, + sibling_merge_key_str: ptr::null(), + sibling_merge_key_int: 0, }, cname, )
diff --git a/examples/shared_lib/example_shlib_track_event.c b/examples/shared_lib/example_shlib_track_event.c index bdfb3af..ec3569a 100644 --- a/examples/shared_lib/example_shlib_track_event.c +++ b/examples/shared_lib/example_shlib_track_event.c
@@ -107,6 +107,30 @@ PERFETTO_TE_NESTED_TRACK_NAMED("dynamictrack", 2), PERFETTO_TE_NESTED_TRACK_COUNTER("dynamiccounter")), PERFETTO_TE_INT_COUNTER(99)); + // "Render" orders its children explicitly: "GPU" (rank 1) sorts before + // "CPU" (rank 2) regardless of when they first appear. + PERFETTO_TE(physics, PERFETTO_TE_INSTANT("gpu_work"), + PERFETTO_TE_NESTED_TRACKS( + PERFETTO_TE_NESTED_TRACK_PROCESS(), + PERFETTO_TE_NESTED_TRACK_NAMED_ORDERED( + "Render", 0, 0, PERFETTO_TE_HL_CHILD_ORDERING_EXPLICIT), + PERFETTO_TE_NESTED_TRACK_NAMED_ORDERED("GPU", 0, 1, 0))); + PERFETTO_TE(physics, PERFETTO_TE_INSTANT("cpu_work"), + PERFETTO_TE_NESTED_TRACKS( + PERFETTO_TE_NESTED_TRACK_PROCESS(), + PERFETTO_TE_NESTED_TRACK_NAMED_ORDERED( + "Render", 0, 0, PERFETTO_TE_HL_CHILD_ORDERING_EXPLICIT), + PERFETTO_TE_NESTED_TRACK_NAMED_ORDERED("CPU", 0, 2, 0))); + // "Worker" tracks merge by an explicit key: same-keyed siblings are + // displayed as one track. + PERFETTO_TE( + physics, PERFETTO_TE_INSTANT("pool_work"), + PERFETTO_TE_NESTED_TRACKS( + PERFETTO_TE_NESTED_TRACK_PROCESS(), + PERFETTO_TE_NESTED_TRACK_NAMED_MERGED( + "Worker", 0, + PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY, + "worker_pool", 0))); sleep(1); } }
diff --git a/include/perfetto/public/abi/track_event_hl_abi.h b/include/perfetto/public/abi/track_event_hl_abi.h index 69dd2cc..1b1dec6 100644 --- a/include/perfetto/public/abi/track_event_hl_abi.h +++ b/include/perfetto/public/abi/track_event_hl_abi.h
@@ -316,6 +316,20 @@ uint32_t type; }; +// How a track orders its direct children. Values mirror +// TrackDescriptor.ChildTracksOrdering; 0 (unknown) leaves it unset. +#define PERFETTO_TE_HL_CHILD_ORDERING_UNKNOWN 0 +#define PERFETTO_TE_HL_CHILD_ORDERING_LEXICOGRAPHIC 1 +#define PERFETTO_TE_HL_CHILD_ORDERING_CHRONOLOGICAL 2 +#define PERFETTO_TE_HL_CHILD_ORDERING_EXPLICIT 3 + +// How analysis tools merge a track with its eligible siblings. Values mirror +// TrackDescriptor.SiblingMergeBehavior; 0 (unspecified) leaves it unset. +#define PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_UNSPECIFIED 0 +#define PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_BY_TRACK_NAME 1 +#define PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_NONE 2 +#define PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY 3 + // PERFETTO_TE_HL_NESTED_TRACK_TYPE_NAMED struct PerfettoTeHlNestedTrackNamed { struct PerfettoTeHlNestedTrack header; @@ -324,6 +338,26 @@ uint64_t id; // If true, `name` is a compile-time constant and is emitted as static_name. bool is_name_static; + // This track's rank among its siblings (lower sorts first). Honored only + // when the parent's `child_ordering` is EXPLICIT. 0 leaves it unset. + int32_t sibling_order_rank; + // How this track orders its own children: one of the + // PERFETTO_TE_HL_CHILD_ORDERING_* values. 0 (unknown) leaves it unset. + uint32_t child_ordering; + // How this track is merged with eligible siblings: one of the + // PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_* values. 0 (unspecified) leaves it + // unset. + uint32_t sibling_merge_behavior; + // Null terminated string key selecting which siblings this track is merged + // with. Only meaningful when `sibling_merge_behavior` is + // BY_SIBLING_MERGE_KEY; emitted as the TrackDescriptor `sibling_merge_key`. + // NULL leaves it unset, in which case `sibling_merge_key_int` is emitted + // instead (the two map onto a protobuf oneof). + const char* sibling_merge_key_str; + // Integer key selecting which siblings this track is merged with. Only + // emitted (as `sibling_merge_key_int`) when `sibling_merge_behavior` is + // BY_SIBLING_MERGE_KEY and `sibling_merge_key_str` is NULL. + uint64_t sibling_merge_key_int; }; struct PerfettoTeHlNestedTrackProto {
diff --git a/include/perfetto/public/te_macros.h b/include/perfetto/public/te_macros.h index e04632f..5a25b24 100644 --- a/include/perfetto/public/te_macros.h +++ b/include/perfetto/public/te_macros.h
@@ -397,12 +397,58 @@ // A track called `NAME` (const char *), uniquely identified by `NAME`, `ID` (a // uint64_t) and its parent hierarchy. -#define PERFETTO_TE_NESTED_TRACK_NAMED(NAME, ID) \ - PERFETTO_REINTERPRET_CAST( \ - struct PerfettoTeHlNestedTrack*, \ - PERFETTO_I_TE_COMPOUND_LITERAL_ADDR( \ - PerfettoTeHlNestedTrackNamed, \ - {{PERFETTO_TE_HL_NESTED_TRACK_TYPE_NAMED}, NAME, ID, false})) +#define PERFETTO_TE_NESTED_TRACK_NAMED(NAME, ID) \ + PERFETTO_REINTERPRET_CAST(struct PerfettoTeHlNestedTrack*, \ + PERFETTO_I_TE_COMPOUND_LITERAL_ADDR( \ + PerfettoTeHlNestedTrackNamed, \ + {{PERFETTO_TE_HL_NESTED_TRACK_TYPE_NAMED}, \ + NAME, \ + ID, \ + false, \ + 0, \ + 0, \ + 0, \ + PERFETTO_NULL, \ + 0})) + +// Like PERFETTO_TE_NESTED_TRACK_NAMED, but also sets this level's +// `sibling_order_rank` (its rank among siblings; lower sorts first, honored +// when the parent's ordering is EXPLICIT) and `child_ordering` (how this level +// orders its own children: a PERFETTO_TE_HL_CHILD_ORDERING_* value). +#define PERFETTO_TE_NESTED_TRACK_NAMED_ORDERED(NAME, ID, RANK, ORDERING) \ + PERFETTO_REINTERPRET_CAST(struct PerfettoTeHlNestedTrack*, \ + PERFETTO_I_TE_COMPOUND_LITERAL_ADDR( \ + PerfettoTeHlNestedTrackNamed, \ + {{PERFETTO_TE_HL_NESTED_TRACK_TYPE_NAMED}, \ + NAME, \ + ID, \ + false, \ + RANK, \ + ORDERING, \ + 0, \ + PERFETTO_NULL, \ + 0})) + +// Like PERFETTO_TE_NESTED_TRACK_NAMED, but also sets how this level is merged +// with its eligible siblings: `BEHAVIOR` is a +// PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_* value, `KEY_STR` (a const char*, +// can be PERFETTO_NULL) and `KEY_INT` (a uint64_t) select the siblings this +// track is merged with when `BEHAVIOR` is BY_SIBLING_MERGE_KEY (`KEY_STR`, if +// not NULL, takes precedence over `KEY_INT`). +#define PERFETTO_TE_NESTED_TRACK_NAMED_MERGED(NAME, ID, BEHAVIOR, KEY_STR, \ + KEY_INT) \ + PERFETTO_REINTERPRET_CAST(struct PerfettoTeHlNestedTrack*, \ + PERFETTO_I_TE_COMPOUND_LITERAL_ADDR( \ + PerfettoTeHlNestedTrackNamed, \ + {{PERFETTO_TE_HL_NESTED_TRACK_TYPE_NAMED}, \ + NAME, \ + ID, \ + false, \ + 0, \ + 0, \ + BEHAVIOR, \ + KEY_STR, \ + KEY_INT})) // A track uniquely identified by `ID` (a uint64_t) and its parent hierarchy. // The rest of the params should be PERFETTO_TE_PROTO_FIELD_* macros and should
diff --git a/src/android_sdk/java/main/dev/perfetto/sdk/PerfettoTrack.java b/src/android_sdk/java/main/dev/perfetto/sdk/PerfettoTrack.java index 03d835c..2ebd67b 100644 --- a/src/android_sdk/java/main/dev/perfetto/sdk/PerfettoTrack.java +++ b/src/android_sdk/java/main/dev/perfetto/sdk/PerfettoTrack.java
@@ -40,8 +40,16 @@ * the C SDK's nested-track behaviour. The track uuid is derived natively exactly * as the C SDK derives it. Emit cost grows with the nesting depth. * - * <p>This is the nesting-only shape supported by the high-level ABI; sibling - * ordering, counter units and similar are intentionally not exposed here. + * <p>By default the UI decides how a track's children are arranged. For explicit + * control, give the parent {@link #setChildOrdering} {@link + * #CHILD_ORDERING_EXPLICIT} and each child a {@link #setSiblingOrderRank} (lower + * sorts first). + * + * <p>By default sibling tracks with the same name are merged when displayed. To + * control it, set {@link #setSiblingMergeBehavior} (e.g. {@link + * #SIBLING_MERGE_BEHAVIOR_NONE} to never merge, or {@link + * #SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY} together with {@link + * #setSiblingMergeKey} to merge by an explicit key instead of by name). */ public final class PerfettoTrack { // Root scope of the chain. Mirrors RootType in tracing_sdk.h. @@ -49,13 +57,41 @@ static final int ROOT_PROCESS = 1; static final int ROOT_THREAD = 2; + // How a track orders its direct children, for {@link #setChildOrdering}. + // Values mirror TrackDescriptor.ChildTracksOrdering. + public static final int CHILD_ORDERING_UNKNOWN = 0; + public static final int CHILD_ORDERING_LEXICOGRAPHIC = 1; + public static final int CHILD_ORDERING_CHRONOLOGICAL = 2; + public static final int CHILD_ORDERING_EXPLICIT = 3; + + // How a track is merged with its eligible siblings, for + // {@link #setSiblingMergeBehavior}. Values mirror + // TrackDescriptor.SiblingMergeBehavior. + public static final int SIBLING_MERGE_BEHAVIOR_UNSPECIFIED = 0; + public static final int SIBLING_MERGE_BEHAVIOR_BY_TRACK_NAME = 1; + public static final int SIBLING_MERGE_BEHAVIOR_NONE = 2; + public static final int SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY = 3; + // Default per-level id: no disambiguation between same-named sibling tracks. private static final long DEFAULT_ID = 0; final int mRootType; - // Names and ids of the chain, outermost (closest to the root) first. + // Per-level state of the chain, outermost (closest to the root) first. The + // arrays are parallel; each setter clones the array it changes and shares + // the rest. final String[] mNames; final long[] mIds; + // This level's rank among its siblings; 0 means unset. + final int[] mSiblingOrderRanks; + // How this level orders its own children; CHILD_ORDERING_UNKNOWN means unset. + final int[] mChildOrderings; + // How this level merges with its siblings; SIBLING_MERGE_BEHAVIOR_UNSPECIFIED + // means unset. + final int[] mSiblingMergeBehaviors; + // The per-level merge key, a protobuf oneof: a non-null string key wins over + // the integer key. + final String[] mSiblingMergeKeyStrs; + final long[] mSiblingMergeKeyInts; // The handle's native nested-tracks extra, built lazily on first use and held // for the handle's lifetime. Freed by the cleaner when the handle is collected. @@ -65,10 +101,36 @@ // needs no native lib, so it is safe to hold statically. private static final PerfettoNativeMemoryCleaner sCleaner = new PerfettoNativeMemoryCleaner(); - private PerfettoTrack(int rootType, String[] names, long[] ids) { + private PerfettoTrack( + int rootType, + String[] names, + long[] ids, + int[] siblingOrderRanks, + int[] childOrderings, + int[] siblingMergeBehaviors, + String[] siblingMergeKeyStrs, + long[] siblingMergeKeyInts) { mRootType = rootType; mNames = names; mIds = ids; + mSiblingOrderRanks = siblingOrderRanks; + mChildOrderings = childOrderings; + mSiblingMergeBehaviors = siblingMergeBehaviors; + mSiblingMergeKeyStrs = siblingMergeKeyStrs; + mSiblingMergeKeyInts = siblingMergeKeyInts; + } + + /** A track named {@code name} rooted at {@code rootType}. */ + private static PerfettoTrack root(int rootType, String name) { + return new PerfettoTrack( + rootType, + new String[] {name}, + new long[] {DEFAULT_ID}, + new int[1], + new int[1], + new int[1], + new String[1], + new long[1]); } /** @@ -83,7 +145,17 @@ NestedTracks nestedTracks() { NestedTracks n = mNested; if (n == null) { - n = new NestedTracks(mRootType, mNames, mIds, sCleaner); + n = + new NestedTracks( + mRootType, + mNames, + mIds, + mSiblingOrderRanks, + mChildOrderings, + mSiblingMergeBehaviors, + mSiblingMergeKeyStrs, + mSiblingMergeKeyInts, + sCleaner); mNested = n; } return n; @@ -91,17 +163,17 @@ /** A track named {@code name} rooted at the process track. */ public static PerfettoTrack process(@CompileTimeConstant String name) { - return new PerfettoTrack(ROOT_PROCESS, new String[] {name}, new long[] {DEFAULT_ID}); + return root(ROOT_PROCESS, name); } /** A track named {@code name} rooted at the emitting thread's track. */ public static PerfettoTrack thread(@CompileTimeConstant String name) { - return new PerfettoTrack(ROOT_THREAD, new String[] {name}, new long[] {DEFAULT_ID}); + return root(ROOT_THREAD, name); } /** A track named {@code name} rooted at the global scope. */ public static PerfettoTrack global(@CompileTimeConstant String name) { - return new PerfettoTrack(ROOT_GLOBAL, new String[] {name}, new long[] {DEFAULT_ID}); + return root(ROOT_GLOBAL, name); } /** A child track named {@code name} nested under this one. */ @@ -117,10 +189,120 @@ int n = mNames.length; String[] names = new String[n + 1]; long[] ids = new long[n + 1]; + int[] ranks = new int[n + 1]; + int[] orderings = new int[n + 1]; + int[] mergeBehaviors = new int[n + 1]; + String[] mergeKeyStrs = new String[n + 1]; + long[] mergeKeyInts = new long[n + 1]; System.arraycopy(mNames, 0, names, 0, n); System.arraycopy(mIds, 0, ids, 0, n); + System.arraycopy(mSiblingOrderRanks, 0, ranks, 0, n); + System.arraycopy(mChildOrderings, 0, orderings, 0, n); + System.arraycopy(mSiblingMergeBehaviors, 0, mergeBehaviors, 0, n); + System.arraycopy(mSiblingMergeKeyStrs, 0, mergeKeyStrs, 0, n); + System.arraycopy(mSiblingMergeKeyInts, 0, mergeKeyInts, 0, n); names[n] = name; ids[n] = id; - return new PerfettoTrack(mRootType, names, ids); + return new PerfettoTrack( + mRootType, names, ids, ranks, orderings, mergeBehaviors, mergeKeyStrs, mergeKeyInts); + } + + /** + * This track's rank among its siblings; lower sorts first. Only honored when + * the parent track's {@link #setChildOrdering} is {@link + * #CHILD_ORDERING_EXPLICIT}. + */ + public PerfettoTrack setSiblingOrderRank(int rank) { + int[] ranks = mSiblingOrderRanks.clone(); + ranks[ranks.length - 1] = rank; + return new PerfettoTrack( + mRootType, + mNames, + mIds, + ranks, + mChildOrderings, + mSiblingMergeBehaviors, + mSiblingMergeKeyStrs, + mSiblingMergeKeyInts); + } + + /** + * How this track orders its own children, one of the {@code CHILD_ORDERING_*} + * values. + */ + public PerfettoTrack setChildOrdering(int childOrdering) { + int[] orderings = mChildOrderings.clone(); + orderings[orderings.length - 1] = childOrdering; + return new PerfettoTrack( + mRootType, + mNames, + mIds, + mSiblingOrderRanks, + orderings, + mSiblingMergeBehaviors, + mSiblingMergeKeyStrs, + mSiblingMergeKeyInts); + } + + /** + * How this track is merged with its eligible siblings, one of the {@code + * SIBLING_MERGE_BEHAVIOR_*} values. + */ + public PerfettoTrack setSiblingMergeBehavior(int siblingMergeBehavior) { + int[] mergeBehaviors = mSiblingMergeBehaviors.clone(); + mergeBehaviors[mergeBehaviors.length - 1] = siblingMergeBehavior; + return new PerfettoTrack( + mRootType, + mNames, + mIds, + mSiblingOrderRanks, + mChildOrderings, + mergeBehaviors, + mSiblingMergeKeyStrs, + mSiblingMergeKeyInts); + } + + /** + * The integer key selecting which siblings this track is merged with. Only + * meaningful when {@link #setSiblingMergeBehavior} is {@link + * #SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY}. Clears any previously set + * string key (the two are a protobuf oneof). + */ + public PerfettoTrack setSiblingMergeKey(long key) { + String[] mergeKeyStrs = mSiblingMergeKeyStrs.clone(); + long[] mergeKeyInts = mSiblingMergeKeyInts.clone(); + mergeKeyStrs[mergeKeyStrs.length - 1] = null; + mergeKeyInts[mergeKeyInts.length - 1] = key; + return new PerfettoTrack( + mRootType, + mNames, + mIds, + mSiblingOrderRanks, + mChildOrderings, + mSiblingMergeBehaviors, + mergeKeyStrs, + mergeKeyInts); + } + + /** + * The string key selecting which siblings this track is merged with. Only + * meaningful when {@link #setSiblingMergeBehavior} is {@link + * #SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY}. Clears any previously set + * integer key (the two are a protobuf oneof). + */ + public PerfettoTrack setSiblingMergeKey(String key) { + String[] mergeKeyStrs = mSiblingMergeKeyStrs.clone(); + long[] mergeKeyInts = mSiblingMergeKeyInts.clone(); + mergeKeyStrs[mergeKeyStrs.length - 1] = key; + mergeKeyInts[mergeKeyInts.length - 1] = 0; + return new PerfettoTrack( + mRootType, + mNames, + mIds, + mSiblingOrderRanks, + mChildOrderings, + mSiblingMergeBehaviors, + mergeKeyStrs, + mergeKeyInts); } }
diff --git a/src/android_sdk/java/main/dev/perfetto/sdk/PerfettoTrackEventExtra.java b/src/android_sdk/java/main/dev/perfetto/sdk/PerfettoTrackEventExtra.java index 0e83078..f4f055e 100644 --- a/src/android_sdk/java/main/dev/perfetto/sdk/PerfettoTrackEventExtra.java +++ b/src/android_sdk/java/main/dev/perfetto/sdk/PerfettoTrackEventExtra.java
@@ -172,8 +172,22 @@ int rootType, String[] names, long[] ids, + int[] siblingOrderRanks, + int[] childOrderings, + int[] siblingMergeBehaviors, + String[] siblingMergeKeyStrs, + long[] siblingMergeKeyInts, PerfettoNativeMemoryCleaner memoryCleaner) { - mPtr = native_init(rootType, names, ids); + mPtr = + native_init( + rootType, + names, + ids, + siblingOrderRanks, + childOrderings, + siblingMergeBehaviors, + siblingMergeKeyStrs, + siblingMergeKeyInts); mExtraPtr = native_get_extra_ptr(mPtr); memoryCleaner.registerNativeAllocation(this, mPtr, native_delete()); } @@ -184,7 +198,15 @@ } @FastNative - private static native long native_init(int rootType, String[] names, long[] ids); + private static native long native_init( + int rootType, + String[] names, + long[] ids, + int[] siblingOrderRanks, + int[] childOrderings, + int[] siblingMergeBehaviors, + String[] siblingMergeKeyStrs, + long[] siblingMergeKeyInts); @CriticalNative private static native long native_delete();
diff --git a/src/android_sdk/java/test/dev/perfetto/sdk/test/PerfettoTraceTest.java b/src/android_sdk/java/test/dev/perfetto/sdk/test/PerfettoTraceTest.java index 0546518..311876b 100644 --- a/src/android_sdk/java/test/dev/perfetto/sdk/test/PerfettoTraceTest.java +++ b/src/android_sdk/java/test/dev/perfetto/sdk/test/PerfettoTraceTest.java
@@ -345,6 +345,103 @@ } @Test + public void testSortedNestedTrack() throws Exception { + TraceConfig traceConfig = getTraceConfig(FOO); + + PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray()); + + // "render" orders its children explicitly; each child sets its rank. + PerfettoTrack render = + PerfettoTrack.process("render").setChildOrdering(PerfettoTrack.CHILD_ORDERING_EXPLICIT); + PerfettoTrack gpu = render.child("gpu").setSiblingOrderRank(1); + PerfettoTrack cpu = render.child("cpu").setSiblingOrderRank(2); + PerfettoTrace.instant(FOO_CATEGORY, "gpu_work").usingTrack(gpu).emit(); + PerfettoTrace.instant(FOO_CATEGORY, "cpu_work").usingTrack(cpu).emit(); + + Trace trace = Trace.parseFrom(session.close()); + + Map<String, TrackDescriptor> byName = indexDescriptorsByStaticName(trace); + + // The parent declares explicit child ordering. + assertThat(byName.get("render").getChildOrdering()) + .isEqualTo(TrackDescriptor.ChildTracksOrdering.EXPLICIT); + // Each child carries its sibling_order_rank. + assertThat(byName.get("gpu").getSiblingOrderRank()).isEqualTo(1); + assertThat(byName.get("cpu").getSiblingOrderRank()).isEqualTo(2); + } + + @Test + public void testMergedNestedTrack() throws Exception { + TraceConfig traceConfig = getTraceConfig(FOO); + + PerfettoTrace.Session session = new PerfettoTrace.Session(true, traceConfig.toByteArray()); + + PerfettoTrack parent = PerfettoTrack.process("merge_parent"); + // Merged with same-keyed siblings via a string key. + PerfettoTrack strKeyed = + parent + .child("str_keyed") + .setSiblingMergeBehavior(PerfettoTrack.SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY) + .setSiblingMergeKey("merge_group_a"); + // Merged with same-keyed siblings via an integer key. + PerfettoTrack intKeyed = + parent + .child("int_keyed") + .setSiblingMergeBehavior(PerfettoTrack.SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY) + .setSiblingMergeKey(42); + // Never merged with any sibling. + PerfettoTrack lone = + parent.child("lone").setSiblingMergeBehavior(PerfettoTrack.SIBLING_MERGE_BEHAVIOR_NONE); + PerfettoTrack keyWithoutBehavior = + parent.child("key_without_behavior").setSiblingMergeKey("x"); + PerfettoTrace.instant(FOO_CATEGORY, "event_a").usingTrack(strKeyed).emit(); + PerfettoTrace.instant(FOO_CATEGORY, "event_b").usingTrack(intKeyed).emit(); + PerfettoTrace.instant(FOO_CATEGORY, "event_c").usingTrack(lone).emit(); + PerfettoTrace.instant(FOO_CATEGORY, "event_d").usingTrack(keyWithoutBehavior).emit(); + + Trace trace = Trace.parseFrom(session.close()); + + Map<String, TrackDescriptor> byName = indexDescriptorsByStaticName(trace); + + TrackDescriptor strKeyedTd = byName.get("str_keyed"); + assertThat(strKeyedTd.getSiblingMergeBehavior()) + .isEqualTo(TrackDescriptor.SiblingMergeBehavior.SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY); + assertThat(strKeyedTd.getSiblingMergeKey()).isEqualTo("merge_group_a"); + assertThat(strKeyedTd.hasSiblingMergeKeyInt()).isFalse(); + + TrackDescriptor intKeyedTd = byName.get("int_keyed"); + assertThat(intKeyedTd.getSiblingMergeBehavior()) + .isEqualTo(TrackDescriptor.SiblingMergeBehavior.SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY); + assertThat(intKeyedTd.getSiblingMergeKeyInt()).isEqualTo(42); + assertThat(intKeyedTd.hasSiblingMergeKey()).isFalse(); + + TrackDescriptor loneTd = byName.get("lone"); + assertThat(loneTd.getSiblingMergeBehavior()) + .isEqualTo(TrackDescriptor.SiblingMergeBehavior.SIBLING_MERGE_BEHAVIOR_NONE); + // No merge key without BY_SIBLING_MERGE_KEY. + assertThat(loneTd.hasSiblingMergeKey()).isFalse(); + assertThat(loneTd.hasSiblingMergeKeyInt()).isFalse(); + + TrackDescriptor keyWithoutBehaviorTd = byName.get("key_without_behavior"); + assertThat(keyWithoutBehaviorTd.hasSiblingMergeKey()).isFalse(); + assertThat(keyWithoutBehaviorTd.hasSiblingMergeKeyInt()).isFalse(); + } + + /** Indexes every TrackDescriptor in {@code trace} with a static name by that name. */ + private static Map<String, TrackDescriptor> indexDescriptorsByStaticName(Trace trace) { + Map<String, TrackDescriptor> byName = new HashMap<>(); + for (TracePacket packet : trace.getPacketList()) { + if (packet.hasTrackDescriptor()) { + TrackDescriptor td = packet.getTrackDescriptor(); + if (!td.getStaticName().isEmpty()) { + byName.put(td.getStaticName(), td); + } + } + } + return byName; + } + + @Test public void testProcessThreadNamedTrack() throws Exception { TraceConfig traceConfig = getTraceConfig(FOO);
diff --git a/src/android_sdk/jni/dev_perfetto_sdk_PerfettoTrackEventExtra.cc b/src/android_sdk/jni/dev_perfetto_sdk_PerfettoTrackEventExtra.cc index d6ba625..4820328 100644 --- a/src/android_sdk/jni/dev_perfetto_sdk_PerfettoTrackEventExtra.cc +++ b/src/android_sdk/jni/dev_perfetto_sdk_PerfettoTrackEventExtra.cc
@@ -385,29 +385,67 @@ jclass, jint root_type, jobjectArray names, - jlongArray ids) { + jlongArray ids, + jintArray sibling_order_ranks, + jintArray child_orderings, + jintArray sibling_merge_behaviors, + jobjectArray merge_keys_str, + jlongArray merge_keys_int) { const jsize num_names = env->GetArrayLength(names); - const jsize num_ids = env->GetArrayLength(ids); - // names and ids are built in lockstep by PerfettoTrack, so they should match. - // Tracing must never crash the caller, so on a mismatch just log and use the - // shorter length rather than over-reading. - const jsize n = num_names < num_ids ? num_names : num_ids; - if (num_names != num_ids) { - __android_log_print(ANDROID_LOG_ERROR, "PerfettoJNI", - "nested track names (%d) and ids (%d) length mismatch", - num_names, num_ids); + // All the arrays are built in lockstep by PerfettoTrack, so the lengths + // should match. Tracing must never crash the caller, so on a mismatch just + // log and use the shortest length rather than over-reading. + jsize n = num_names; + const jsize lengths[] = {env->GetArrayLength(ids), + env->GetArrayLength(sibling_order_ranks), + env->GetArrayLength(child_orderings), + env->GetArrayLength(sibling_merge_behaviors), + env->GetArrayLength(merge_keys_str), + env->GetArrayLength(merge_keys_int)}; + for (jsize length : lengths) { + if (length != num_names) { + __android_log_print(ANDROID_LOG_ERROR, "PerfettoJNI", + "nested track parallel array length mismatch " + "(names %d vs %d)", + num_names, length); + n = n < length ? n : length; + } } std::vector<std::string> names_vec; names_vec.reserve(static_cast<size_t>(n)); + std::vector<std::optional<std::string>> merge_keys_str_vec; + merge_keys_str_vec.reserve(static_cast<size_t>(n)); for (jsize i = 0; i < n; i++) { jstring s = static_cast<jstring>(env->GetObjectArrayElement(names, i)); names_vec.emplace_back(StringBuffer::utf16_to_ascii(env, s)); env->DeleteLocalRef(s); + jstring key = + static_cast<jstring>(env->GetObjectArrayElement(merge_keys_str, i)); + if (key == nullptr) { + merge_keys_str_vec.emplace_back(std::nullopt); + } else { + merge_keys_str_vec.emplace_back(StringBuffer::utf16_to_ascii(env, key)); + env->DeleteLocalRef(key); + } } std::vector<uint64_t> ids_vec(static_cast<size_t>(n)); env->GetLongArrayRegion(ids, 0, n, reinterpret_cast<jlong*>(ids_vec.data())); + std::vector<int32_t> ranks_vec(static_cast<size_t>(n)); + env->GetIntArrayRegion(sibling_order_ranks, 0, n, + reinterpret_cast<jint*>(ranks_vec.data())); + std::vector<uint32_t> orderings_vec(static_cast<size_t>(n)); + env->GetIntArrayRegion(child_orderings, 0, n, + reinterpret_cast<jint*>(orderings_vec.data())); + std::vector<uint32_t> merge_behaviors_vec(static_cast<size_t>(n)); + env->GetIntArrayRegion(sibling_merge_behaviors, 0, n, + reinterpret_cast<jint*>(merge_behaviors_vec.data())); + std::vector<uint64_t> merge_keys_int_vec(static_cast<size_t>(n)); + env->GetLongArrayRegion(merge_keys_int, 0, n, + reinterpret_cast<jlong*>(merge_keys_int_vec.data())); return toJLong(new sdk_for_jni::NestedTracks( - static_cast<sdk_for_jni::RootType>(root_type), names_vec, ids_vec)); + static_cast<sdk_for_jni::RootType>(root_type), names_vec, ids_vec, + ranks_vec, orderings_vec, merge_behaviors_vec, merge_keys_str_vec, + merge_keys_int_vec)); } static jlong dev_perfetto_sdk_PerfettoTrackEventExtraNestedTracks_delete( @@ -638,7 +676,7 @@ }; static const JNINativeMethod gNestedTracksMethods[] = { - {"native_init", "(I[Ljava/lang/String;[J)J", + {"native_init", "(I[Ljava/lang/String;[J[I[I[I[Ljava/lang/String;[J)J", (void*)dev_perfetto_sdk_PerfettoTrackEventExtraNestedTracks_init}, {"native_delete", "()J", (void*)dev_perfetto_sdk_PerfettoTrackEventExtraNestedTracks_delete},
diff --git a/src/android_sdk/perfetto_sdk_for_jni/tracing_sdk.cc b/src/android_sdk/perfetto_sdk_for_jni/tracing_sdk.cc index d670254..028aae7 100644 --- a/src/android_sdk/perfetto_sdk_for_jni/tracing_sdk.cc +++ b/src/android_sdk/perfetto_sdk_for_jni/tracing_sdk.cc
@@ -157,10 +157,16 @@ delete ptr; } -NestedTracks::NestedTracks(RootType root_type, - const std::vector<std::string>& names, - const std::vector<uint64_t>& ids) - : names_(names), root_{}, extra_{} { +NestedTracks::NestedTracks( + RootType root_type, + const std::vector<std::string>& names, + const std::vector<uint64_t>& ids, + const std::vector<int32_t>& sibling_order_ranks, + const std::vector<uint32_t>& child_orderings, + const std::vector<uint32_t>& sibling_merge_behaviors, + const std::vector<std::optional<std::string>>& merge_keys_str, + const std::vector<uint64_t>& merge_keys_int) + : names_(names), merge_keys_str_(merge_keys_str), root_{}, extra_{} { const size_t count = names_.size(); named_.reserve(count); ptrs_.reserve(count + 2); @@ -188,6 +194,12 @@ entry.name = names_[i].c_str(); entry.id = ids[i]; entry.is_name_static = true; + entry.sibling_order_rank = sibling_order_ranks[i]; + entry.child_ordering = child_orderings[i]; + entry.sibling_merge_behavior = sibling_merge_behaviors[i]; + entry.sibling_merge_key_str = + merge_keys_str_[i] ? merge_keys_str_[i]->c_str() : nullptr; + entry.sibling_merge_key_int = merge_keys_int[i]; named_.push_back(entry); ptrs_.push_back(reinterpret_cast<PerfettoTeHlNestedTrack*>(&named_.back())); }
diff --git a/src/android_sdk/perfetto_sdk_for_jni/tracing_sdk.h b/src/android_sdk/perfetto_sdk_for_jni/tracing_sdk.h index 34547cd..1e07ab9 100644 --- a/src/android_sdk/perfetto_sdk_for_jni/tracing_sdk.h +++ b/src/android_sdk/perfetto_sdk_for_jni/tracing_sdk.h
@@ -193,12 +193,22 @@ * The chain is, outermost first: an optional root (process or thread; global * roots have none) followed by one named level per name. The native HL path * derives the per-level uuids and emits a descriptor for each level. + * + * The vectors are parallel, one entry per named level: per-level sibling + * order rank, child ordering, sibling merge behavior (values mirror the + * PERFETTO_TE_HL_* ABI constants) and sibling merge key. A level's merge key + * is the string one if set, the integer one otherwise. */ class NestedTracks { public: NestedTracks(RootType root_type, const std::vector<std::string>& names, - const std::vector<uint64_t>& ids); + const std::vector<uint64_t>& ids, + const std::vector<int32_t>& sibling_order_ranks, + const std::vector<uint32_t>& child_orderings, + const std::vector<uint32_t>& sibling_merge_behaviors, + const std::vector<std::optional<std::string>>& merge_keys_str, + const std::vector<uint64_t>& merge_keys_int); static void delete_track(NestedTracks* track); @@ -206,9 +216,11 @@ private: DISALLOW_COPY_AND_ASSIGN(NestedTracks); - // ptrs_ point into named_/root_ and named_[i].name into names_; nothing is - // resized after construction, so the pointers stay valid. + // ptrs_ point into named_/root_, named_[i].name into names_ and + // named_[i].sibling_merge_key_str into merge_keys_str_; nothing is resized + // after construction, so the pointers stay valid. std::vector<std::string> names_; + std::vector<std::optional<std::string>> merge_keys_str_; PerfettoTeHlNestedTrack root_; std::vector<PerfettoTeHlNestedTrackNamed> named_; std::vector<PerfettoTeHlNestedTrack*> ptrs_;
diff --git a/src/shared_lib/test/api_integrationtest.cc b/src/shared_lib/test/api_integrationtest.cc index b82ecd6..cdd6379 100644 --- a/src/shared_lib/test/api_integrationtest.cc +++ b/src/shared_lib/test/api_integrationtest.cc
@@ -2564,6 +2564,117 @@ EXPECT_EQ(counter_parent_uuid, registered_track_uuid); } +TEST_F(SharedLibTrackEventTest, TrackEventHlNestedTrackOrdered) { + TracingSession tracing_session = TracingSession::Builder() + .set_data_source_name("track_event") + .add_enabled_category("*") + .Build(); + + PERFETTO_TE(cat1, PERFETTO_TE_INSTANT("event"), + PERFETTO_TE_NESTED_TRACKS( + PERFETTO_TE_NESTED_TRACK_PROCESS(), + PERFETTO_TE_NESTED_TRACK_NAMED_ORDERED( + "parent", 0, 0, PERFETTO_TE_HL_CHILD_ORDERING_EXPLICIT), + PERFETTO_TE_NESTED_TRACK_NAMED_ORDERED("child", 0, 7, 0))); + + tracing_session.StopBlocking(); + std::vector<uint8_t> data = tracing_session.ReadBlocking(); + + // The parent track descriptor declares explicit child ordering. + EXPECT_THAT( + FieldView(data), + Contains(PbField( + perfetto_protos_Trace_packet_field_number, + AllFieldsWithId( + perfetto_protos_TracePacket_track_descriptor_field_number, + ElementsAre(MsgField(AllOf( + Contains( + PbField(perfetto_protos_TrackDescriptor_name_field_number, + StringField("parent"))), + Contains(PbField( + perfetto_protos_TrackDescriptor_child_ordering_field_number, + VarIntField( + perfetto_protos_TrackDescriptor_EXPLICIT)))))))))); + // The child track descriptor carries its sibling order rank. + EXPECT_THAT( + FieldView(data), + Contains(PbField( + perfetto_protos_Trace_packet_field_number, + AllFieldsWithId( + perfetto_protos_TracePacket_track_descriptor_field_number, + ElementsAre(MsgField(AllOf( + Contains( + PbField(perfetto_protos_TrackDescriptor_name_field_number, + StringField("child"))), + Contains(PbField( + perfetto_protos_TrackDescriptor_sibling_order_rank_field_number, + VarIntField(7)))))))))); +} + +TEST_F(SharedLibTrackEventTest, TrackEventHlNestedTrackMerged) { + TracingSession tracing_session = TracingSession::Builder() + .set_data_source_name("track_event") + .add_enabled_category("*") + .Build(); + + PERFETTO_TE( + cat1, PERFETTO_TE_INSTANT("event1"), + PERFETTO_TE_NESTED_TRACKS( + PERFETTO_TE_NESTED_TRACK_PROCESS(), + PERFETTO_TE_NESTED_TRACK_NAMED_MERGED( + "str_keyed", 0, + PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY, + "merge_group_a", 0))); + PERFETTO_TE( + cat1, PERFETTO_TE_INSTANT("event2"), + PERFETTO_TE_NESTED_TRACKS( + PERFETTO_TE_NESTED_TRACK_PROCESS(), + PERFETTO_TE_NESTED_TRACK_NAMED_MERGED( + "int_keyed", 1, + PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY, + PERFETTO_NULL, 42))); + + tracing_session.StopBlocking(); + std::vector<uint8_t> data = tracing_session.ReadBlocking(); + + // The string-keyed track carries the behavior and the string key. + EXPECT_THAT( + FieldView(data), + Contains(PbField( + perfetto_protos_Trace_packet_field_number, + AllFieldsWithId( + perfetto_protos_TracePacket_track_descriptor_field_number, + ElementsAre(MsgField(AllOf( + Contains( + PbField(perfetto_protos_TrackDescriptor_name_field_number, + StringField("str_keyed"))), + Contains(PbField( + perfetto_protos_TrackDescriptor_sibling_merge_behavior_field_number, + VarIntField( + perfetto_protos_TrackDescriptor_SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY))), + Contains(PbField( + perfetto_protos_TrackDescriptor_sibling_merge_key_field_number, + StringField("merge_group_a")))))))))); + // The integer-keyed track carries the behavior and the integer key. + EXPECT_THAT( + FieldView(data), + Contains(PbField( + perfetto_protos_Trace_packet_field_number, + AllFieldsWithId( + perfetto_protos_TracePacket_track_descriptor_field_number, + ElementsAre(MsgField(AllOf( + Contains( + PbField(perfetto_protos_TrackDescriptor_name_field_number, + StringField("int_keyed"))), + Contains(PbField( + perfetto_protos_TrackDescriptor_sibling_merge_behavior_field_number, + VarIntField( + perfetto_protos_TrackDescriptor_SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY))), + Contains(PbField( + perfetto_protos_TrackDescriptor_sibling_merge_key_int_field_number, + VarIntField(42)))))))))); +} + TEST_F(SharedLibTrackEventTest, TrackEventIsCategoryEnabled) { ASSERT_FALSE(PERFETTO_TE_IS_CATEGORY_ENABLED(cat1));
diff --git a/src/shared_lib/track_event/hl.cc b/src/shared_lib/track_event/hl.cc index 1f19ebb..ce54f5d 100644 --- a/src/shared_lib/track_event/hl.cc +++ b/src/shared_lib/track_event/hl.cc
@@ -278,15 +278,15 @@ } } +// Emits (once per sequence) the track descriptor for the named track described +// by `track` (its `header` is ignored) and returns the derived track uuid. uint64_t EmitNamedTrack(uint64_t parent_uuid, - const char* name, - uint64_t id, - bool is_name_static, + const PerfettoTeHlNestedTrackNamed& track, perfetto::shlib::TrackEventIncrementalState* incr_state, perfetto::TraceWriterBase* trace_writer) { uint64_t uuid = parent_uuid; - uuid ^= PerfettoFnv1a(name, strlen(name)); - uuid ^= id; + uuid ^= PerfettoFnv1a(track.name, strlen(track.name)); + uuid ^= track.id; if (incr_state->seen_track_uuids.insert(uuid).second) { auto packet = trace_writer->NewTracePacket(); auto* track_descriptor = packet->set_track_descriptor(); @@ -294,10 +294,33 @@ if (parent_uuid) { track_descriptor->set_parent_uuid(parent_uuid); } - if (is_name_static) { - track_descriptor->set_static_name(name); + if (track.is_name_static) { + track_descriptor->set_static_name(track.name); } else { - track_descriptor->set_name(name); + track_descriptor->set_name(track.name); + } + if (track.sibling_order_rank != 0) { + track_descriptor->set_sibling_order_rank(track.sibling_order_rank); + } + if (track.child_ordering != PERFETTO_TE_HL_CHILD_ORDERING_UNKNOWN) { + track_descriptor->set_child_ordering( + static_cast<protos::pbzero::TrackDescriptor::ChildTracksOrdering>( + track.child_ordering)); + } + if (track.sibling_merge_behavior != + PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_UNSPECIFIED) { + track_descriptor->set_sibling_merge_behavior( + static_cast<protos::pbzero::TrackDescriptor::SiblingMergeBehavior>( + track.sibling_merge_behavior)); + } + if (track.sibling_merge_behavior == + PERFETTO_TE_HL_SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY) { + if (track.sibling_merge_key_str) { + track_descriptor->set_sibling_merge_key(track.sibling_merge_key_str); + } else { + track_descriptor->set_sibling_merge_key_int( + track.sibling_merge_key_int); + } } } return uuid; @@ -464,9 +487,12 @@ } else if (std::holds_alternative<const PerfettoTeHlExtraNamedTrack*>( track)) { auto* named_track = std::get<const PerfettoTeHlExtraNamedTrack*>(track); - track_uuid = EmitNamedTrack(named_track->parent_uuid, named_track->name, - named_track->id, named_track->is_name_static, - incr_state, trace_writer); + PerfettoTeHlNestedTrackNamed named{}; + named.name = named_track->name; + named.id = named_track->id; + named.is_name_static = named_track->is_name_static; + track_uuid = EmitNamedTrack(named_track->parent_uuid, named, incr_state, + trace_writer); } else if (std::holds_alternative<const PerfettoTeHlExtraProtoTrack*>( track)) { auto* proto_track = std::get<const PerfettoTeHlExtraProtoTrack*>(track); @@ -487,9 +513,7 @@ case PERFETTO_TE_HL_NESTED_TRACK_TYPE_NAMED: { auto* named_track = reinterpret_cast<PerfettoTeHlNestedTrackNamed*>(*tp); - uuid = EmitNamedTrack(uuid, named_track->name, named_track->id, - named_track->is_name_static, incr_state, - trace_writer); + uuid = EmitNamedTrack(uuid, *named_track, incr_state, trace_writer); } break; case PERFETTO_TE_HL_NESTED_TRACK_TYPE_PROCESS: { uuid = perfetto_te_process_track_uuid;