diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4dd4e15ca7d..068e5417aca 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -25,3 +25,5 @@ /src/trace_processor/perfetto_sql/stdlib/chrome/**/* @google/chromium-stdlib-owners /test/trace_processor/diff_tests/stdlib/chrome/**/* @google/chromium-stdlib-owners /test/data/chrome/**.sha256 @google/chromium-stdlib-owners + +/contrib/rust-sdk/ @dreveman @jon-rv @zytyz \ No newline at end of file diff --git a/.github/workflows/chromium-copybara-import.yml b/.github/workflows/chromium-copybara-import.yml index 3e49376dfee..347cf453f37 100644 --- a/.github/workflows/chromium-copybara-import.yml +++ b/.github/workflows/chromium-copybara-import.yml @@ -22,14 +22,14 @@ on: permissions: pull-requests: write # Required to edit PR body and read PR data - contents: write # Required to enable auto-merge + contents: read # Required to read commit history jobs: - include-git-origin-rev-id-and-enable-auto-merge: + include-git-origin-rev-id: runs-on: ubuntu-latest if: github.head_ref == 'dev/copybara/chromium_tmp' steps: - - name: Include GitOrigin-RevId in Copybara PR body and enable auto-merge + - name: Include GitOrigin-RevId in Copybara PR body uses: actions/github-script@v6 with: script: | @@ -76,62 +76,23 @@ jobs: console.log("PR body already contains the GitOrigin-RevId."); } - // STEP 3: Enable auto-merge and set the commit message. - console.log("Attempting to enable auto-merge with commit message..."); - try { - const mutation = ` - mutation EnableAutoMerge($pullRequestId: ID!, $commitHeadline: String!, $commitBody: String!) { - enablePullRequestAutoMerge(input: { - pullRequestId: $pullRequestId, - commitHeadline: $commitHeadline, - commitBody: $commitBody, - mergeMethod: SQUASH - }) { - pullRequest { - id # We only need to know it succeeded, so we ask for the ID. - } - } - }`; - await github.graphql(mutation, { - pullRequestId: pr.node_id, - commitHeadline: pr.title, - commitBody: prBodyWithRevId - }); - } catch (error) { - core.setFailed(`Failed to enable auto-merge: ${error.message}`); - return; - } - console.log("Auto-merge enabled."); - - // STEP 4: Verify that the scheduled merge commit message includes the GitOrigin-RevId. - console.log("Verifying scheduled merge commit message..."); - try { - const query = ` - query GetAutoMergeMessage($pullRequestId: ID!) { - node(id: $pullRequestId) { - ... on PullRequest { - autoMergeRequest { - commitHeadline - commitBody - } - } - } - }`; - const result = await github.graphql(query, { pullRequestId: pr.node_id }); - const { commitHeadline, commitBody } = result.node.autoMergeRequest; - const fullScheduledMessage = `${commitHeadline}\n\n${commitBody}`; - if (fullScheduledMessage.includes(revId)) { - console.log(`Verification successful: The scheduled merge commit message contains "${revId}".`); - } else { - const errorLines = [ - `Verification FAILED: The scheduled merge commit message does not contain "${revId}".`, - 'The scheduled message was:', + // STEP 3: Verify that the merge commit will contain GitOrigin-RevId. + console.log("Verifying that the merge commit will contain GitOrigin-RevId..."); + const { data: updatedPr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.number, + }); + const defaultMergeCommitMessage = `${updatedPr.title}\n\n${updatedPr.body}`; + if (defaultMergeCommitMessage.includes(revId)) { + console.log(`Verification successful: The default merge commit message contains "${revId}".`); + } else { + const errorLines = [ + `Verification FAILED: The default merge commit message does not contain "${revId}".`, + 'The default merge commit message is:', '---', - fullScheduledMessage, + defaultMergeCommitMessage, '---' - ]; - core.setFailed(errorLines.join('\n')); - } - } catch (error) { - core.setFailed(`Failed to verify the scheduled merge commit message: ${error.message}`); + ]; + core.setFailed(errorLines.join('\n')); } diff --git a/.gitignore b/.gitignore index e0ac0a625d8..924d715f306 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .aider* .android_config .cache +.cargo .ccls .ccls-cache .clangd @@ -12,6 +13,7 @@ .eslintcache .gclient* .gemini +.gemini/ .gm-actuals .gradle .idea @@ -22,7 +24,9 @@ .venv .vscode *.code-workspace +*.core *.iml +*.LOCAL.md *.pyc *.sock *.swp @@ -35,12 +39,11 @@ /venv/* depot_tools/ examples/sdk/build/ +gha-creds-*.json GPATH GRTAGS GTAGS mise.toml perf.data* +rfcs TAGS - -.gemini/ -gha-creds-*.json diff --git a/Android.bp b/Android.bp index 277c02a095d..1fdf6e70a2d 100644 --- a/Android.bp +++ b/Android.bp @@ -871,8 +871,10 @@ cc_library { ":perfetto_src_protozero_filtering_message_filter", ":perfetto_src_protozero_filtering_string_filter", ":perfetto_src_protozero_protozero", - ":perfetto_src_shared_lib_intern_map", + ":perfetto_src_shared_lib_for_testing", ":perfetto_src_shared_lib_shared_lib", + ":perfetto_src_shared_lib_track_event_intern_map", + ":perfetto_src_shared_lib_track_event_track_event", ":perfetto_src_tracing_client_api_without_backends", ":perfetto_src_tracing_common", ":perfetto_src_tracing_core_core", @@ -1609,6 +1611,7 @@ java_library { "protos/perfetto/config/trace_config.proto", "protos/perfetto/config/track_event/track_event_config.proto", ], + host_supported: true, proto: { type: "lite", canonical_path_from_root: false, @@ -2465,6 +2468,11 @@ filegroup { name: "perfetto_include_perfetto_trace_processor_trace_processor", } +// GN: //include/perfetto/trace_processor:util +filegroup { + name: "perfetto_include_perfetto_trace_processor_util", +} + // GN: //include/perfetto/tracing/core:core filegroup { name: "perfetto_include_perfetto_tracing_core_core", @@ -2510,6 +2518,7 @@ cc_test { ":perfetto_include_perfetto_trace_processor_basic_types", ":perfetto_include_perfetto_trace_processor_storage", ":perfetto_include_perfetto_trace_processor_trace_processor", + ":perfetto_include_perfetto_trace_processor_util", ":perfetto_include_perfetto_tracing_core_core", ":perfetto_include_perfetto_tracing_core_forward_decls", ":perfetto_include_perfetto_tracing_tracing", @@ -2672,11 +2681,13 @@ cc_test { ":perfetto_src_protozero_proto_ring_buffer", ":perfetto_src_protozero_protozero", ":perfetto_src_protozero_text_to_proto_text_to_proto", - ":perfetto_src_shared_lib_intern_map", + ":perfetto_src_shared_lib_for_testing", ":perfetto_src_shared_lib_shared_lib", ":perfetto_src_shared_lib_test_integrationtests", ":perfetto_src_shared_lib_test_protos_protos", ":perfetto_src_shared_lib_test_utils", + ":perfetto_src_shared_lib_track_event_intern_map", + ":perfetto_src_shared_lib_track_event_track_event", ":perfetto_src_trace_processor_containers_containers", ":perfetto_src_trace_processor_dataframe_dataframe", ":perfetto_src_trace_processor_dataframe_impl_impl", @@ -2715,6 +2726,7 @@ cc_test { ":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr", ":perfetto_src_trace_processor_importers_proto_proto_importer_module", ":perfetto_src_trace_processor_importers_proto_winscope_full", + ":perfetto_src_trace_processor_importers_simpleperf_proto_simpleperf_proto", ":perfetto_src_trace_processor_importers_syscalls_full", ":perfetto_src_trace_processor_importers_systrace_full", ":perfetto_src_trace_processor_importers_systrace_systrace_line", @@ -2745,6 +2757,7 @@ cc_test { ":perfetto_src_trace_processor_trace_summary_trace_summary", ":perfetto_src_trace_processor_types_types", ":perfetto_src_trace_processor_util_args_utils", + ":perfetto_src_trace_processor_util_blob", ":perfetto_src_trace_processor_util_build_id", ":perfetto_src_trace_processor_util_bump_allocator", ":perfetto_src_trace_processor_util_clock", @@ -3204,6 +3217,7 @@ filegroup { "protos/perfetto/trace/ftrace/ftrace_event.proto", "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto", "protos/perfetto/trace/ftrace/ftrace_stats.proto", + "protos/perfetto/trace/ftrace/fwtp_ftrace.proto", "protos/perfetto/trace/ftrace/g2d.proto", "protos/perfetto/trace/ftrace/generic.proto", "protos/perfetto/trace/ftrace/google_icc_trace.proto", @@ -7826,6 +7840,7 @@ genrule { "protos/perfetto/trace/ftrace/ftrace_event.proto", "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto", "protos/perfetto/trace/ftrace/ftrace_stats.proto", + "protos/perfetto/trace/ftrace/fwtp_ftrace.proto", "protos/perfetto/trace/ftrace/g2d.proto", "protos/perfetto/trace/ftrace/generic.proto", "protos/perfetto/trace/ftrace/google_icc_trace.proto", @@ -8262,6 +8277,7 @@ filegroup { "protos/perfetto/trace/ftrace/ftrace_event.proto", "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto", "protos/perfetto/trace/ftrace/ftrace_stats.proto", + "protos/perfetto/trace/ftrace/fwtp_ftrace.proto", "protos/perfetto/trace/ftrace/g2d.proto", "protos/perfetto/trace/ftrace/generic.proto", "protos/perfetto/trace/ftrace/google_icc_trace.proto", @@ -8359,6 +8375,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/ftrace_stats.gen.cc", + "external/perfetto/protos/perfetto/trace/ftrace/fwtp_ftrace.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/g2d.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/generic.gen.cc", "external/perfetto/protos/perfetto/trace/ftrace/google_icc_trace.gen.cc", @@ -8456,6 +8473,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event_bundle.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/ftrace_stats.gen.h", + "external/perfetto/protos/perfetto/trace/ftrace/fwtp_ftrace.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/g2d.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/generic.gen.h", "external/perfetto/protos/perfetto/trace/ftrace/google_icc_trace.gen.h", @@ -8549,6 +8567,7 @@ filegroup { "protos/perfetto/trace/ftrace/ftrace_event.proto", "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto", "protos/perfetto/trace/ftrace/ftrace_stats.proto", + "protos/perfetto/trace/ftrace/fwtp_ftrace.proto", "protos/perfetto/trace/ftrace/g2d.proto", "protos/perfetto/trace/ftrace/generic.proto", "protos/perfetto/trace/ftrace/google_icc_trace.proto", @@ -8645,6 +8664,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event_bundle.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/ftrace_stats.pb.cc", + "external/perfetto/protos/perfetto/trace/ftrace/fwtp_ftrace.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/g2d.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/generic.pb.cc", "external/perfetto/protos/perfetto/trace/ftrace/google_icc_trace.pb.cc", @@ -8741,6 +8761,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event_bundle.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/ftrace_stats.pb.h", + "external/perfetto/protos/perfetto/trace/ftrace/fwtp_ftrace.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/g2d.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/generic.pb.h", "external/perfetto/protos/perfetto/trace/ftrace/google_icc_trace.pb.h", @@ -8834,6 +8855,7 @@ filegroup { "protos/perfetto/trace/ftrace/ftrace_event.proto", "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto", "protos/perfetto/trace/ftrace/ftrace_stats.proto", + "protos/perfetto/trace/ftrace/fwtp_ftrace.proto", "protos/perfetto/trace/ftrace/g2d.proto", "protos/perfetto/trace/ftrace/generic.proto", "protos/perfetto/trace/ftrace/google_icc_trace.proto", @@ -8931,6 +8953,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/ftrace_stats.pbzero.cc", + "external/perfetto/protos/perfetto/trace/ftrace/fwtp_ftrace.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/g2d.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/generic.pbzero.cc", "external/perfetto/protos/perfetto/trace/ftrace/google_icc_trace.pbzero.cc", @@ -9028,6 +9051,7 @@ genrule { "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/ftrace_stats.pbzero.h", + "external/perfetto/protos/perfetto/trace/ftrace/fwtp_ftrace.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/g2d.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/generic.pbzero.h", "external/perfetto/protos/perfetto/trace/ftrace/google_icc_trace.pbzero.h", @@ -12000,6 +12024,22 @@ genrule { ], } +// GN: //protos/third_party/chromium:extension_descriptor +genrule { + name: "perfetto_protos_third_party_chromium_extension_descriptor", + srcs: [ + ":perfetto_protos_perfetto_trace_descriptor", + ":perfetto_protos_third_party_chromium_descriptor", + ], + tools: [ + "perfetto_src_protozero_descriptor_diff_protozero_descriptor_diff", + ], + cmd: "$(location perfetto_src_protozero_descriptor_diff_protozero_descriptor_diff) --out $(out) --minuend $(location :perfetto_protos_third_party_chromium_descriptor) --subtrahend $(location :perfetto_protos_perfetto_trace_descriptor)", + out: [ + "protos/third_party/chromium/chrome_track_event_extension.descriptor", + ], +} + // GN: //protos/third_party/chromium:zero filegroup { name: "perfetto_protos_third_party_chromium_zero", @@ -12097,6 +12137,7 @@ genrule { filegroup { name: "perfetto_protos_third_party_simpleperf_zero", srcs: [ + "protos/third_party/simpleperf/cmd_report_sample.proto", "protos/third_party/simpleperf/record_file.proto", ], } @@ -12113,6 +12154,7 @@ genrule { ], cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_third_party_simpleperf_zero)", out: [ + "external/perfetto/protos/third_party/simpleperf/cmd_report_sample.pbzero.cc", "external/perfetto/protos/third_party/simpleperf/record_file.pbzero.cc", ], } @@ -12129,6 +12171,7 @@ genrule { ], cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(locations :perfetto_protos_third_party_simpleperf_zero)", out: [ + "external/perfetto/protos/third_party/simpleperf/cmd_report_sample.pbzero.h", "external/perfetto/protos/third_party/simpleperf/record_file.pbzero.h", ], export_include_dirs: [ @@ -12747,6 +12790,7 @@ filegroup { srcs: [ "src/perfetto_cmd/config_unittest.cc", "src/perfetto_cmd/packet_writer_unittest.cc", + "src/perfetto_cmd/perfetto_cmd_unittest.cc", ], } @@ -13127,6 +13171,50 @@ filegroup { ], } +// GN: //src/protozero/descriptor_diff:lib +filegroup { + name: "perfetto_src_protozero_descriptor_diff_lib", + srcs: [ + "src/protozero/descriptor_diff/descriptor_diff.cc", + ], +} + +// GN: //src/protozero/descriptor_diff:protozero_descriptor_diff +cc_binary_host { + name: "perfetto_src_protozero_descriptor_diff_protozero_descriptor_diff", + srcs: [ + ":perfetto_base_default_platform", + ":perfetto_include_perfetto_base_base", + ":perfetto_include_perfetto_ext_base_base", + ":perfetto_include_perfetto_ext_base_version", + ":perfetto_include_perfetto_protozero_protozero", + ":perfetto_include_perfetto_public_abi_base", + ":perfetto_include_perfetto_public_base", + ":perfetto_include_perfetto_public_protozero", + ":perfetto_protos_perfetto_common_zero_gen", + ":perfetto_src_base_base", + ":perfetto_src_base_version", + ":perfetto_src_protozero_descriptor_diff_lib", + ":perfetto_src_protozero_protozero", + "src/protozero/descriptor_diff/main.cc", + ], + generated_headers: [ + "perfetto_protos_perfetto_common_zero_gen_headers", + "perfetto_src_base_version_gen_h", + ], + defaults: [ + "perfetto_defaults", + ], +} + +// GN: //src/protozero/descriptor_diff:unittests +filegroup { + name: "perfetto_src_protozero_descriptor_diff_unittests", + srcs: [ + "src/protozero/descriptor_diff/descriptor_diff_unittest.cc", + ], +} + // GN: //src/protozero/filtering:bytecode_common filegroup { name: "perfetto_src_protozero_filtering_bytecode_common", @@ -13720,12 +13808,9 @@ filegroup { ], } -// GN: //src/shared_lib:intern_map +// GN: //src/shared_lib:for_testing filegroup { - name: "perfetto_src_shared_lib_intern_map", - srcs: [ - "src/shared_lib/intern_map.cc", - ], + name: "perfetto_src_shared_lib_for_testing", } // GN: //src/shared_lib:shared_lib @@ -13739,7 +13824,6 @@ filegroup { "src/shared_lib/stream_writer.cc", "src/shared_lib/thread_utils.cc", "src/shared_lib/tracing_session.cc", - "src/shared_lib/track_event.cc", ], } @@ -13764,11 +13848,34 @@ filegroup { ], } -// GN: //src/shared_lib:unittests +// GN: //src/shared_lib/track_event:intern_map +filegroup { + name: "perfetto_src_shared_lib_track_event_intern_map", + srcs: [ + "src/shared_lib/track_event/intern_map.cc", + ], +} + +// GN: //src/shared_lib/track_event:track_event +filegroup { + name: "perfetto_src_shared_lib_track_event_track_event", + srcs: [ + "src/shared_lib/track_event/category_impl.cc", + "src/shared_lib/track_event/category_utils.cc", + "src/shared_lib/track_event/ds.cc", + "src/shared_lib/track_event/global_state.cc", + "src/shared_lib/track_event/hl.cc", + "src/shared_lib/track_event/ll.cc", + "src/shared_lib/track_event/serialization.cc", + "src/shared_lib/track_event/track_event.cc", + ], +} + +// GN: //src/shared_lib/track_event:unittests filegroup { - name: "perfetto_src_shared_lib_unittests", + name: "perfetto_src_shared_lib_track_event_unittests", srcs: [ - "src/shared_lib/intern_map_unittest.cc", + "src/shared_lib/track_event/intern_map_unittest.cc", ], } @@ -14013,6 +14120,7 @@ filegroup { "src/trace_processor/importers/common/event_tracker.cc", "src/trace_processor/importers/common/flow_tracker.cc", "src/trace_processor/importers/common/global_args_tracker.cc", + "src/trace_processor/importers/common/import_logs_tracker.cc", "src/trace_processor/importers/common/jit_cache.cc", "src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.cc", "src/trace_processor/importers/common/machine_tracker.cc", @@ -14240,7 +14348,7 @@ filegroup { srcs: [ "src/trace_processor/importers/perf/perf_counter.cc", "src/trace_processor/importers/perf/perf_event_attr.cc", - "src/trace_processor/importers/perf/perf_session.cc", + "src/trace_processor/importers/perf/perf_invocation.cc", ], } @@ -14271,7 +14379,7 @@ filegroup { name: "perfetto_src_trace_processor_importers_perf_unittests", srcs: [ "src/trace_processor/importers/perf/aux_stream_manager_unittest.cc", - "src/trace_processor/importers/perf/perf_session_unittest.cc", + "src/trace_processor/importers/perf/perf_invocation_unittest.cc", "src/trace_processor/importers/perf/reader_unittest.cc", ], } @@ -14299,6 +14407,7 @@ filegroup { "src/trace_processor/importers/proto/android_probes_tracker.cc", "src/trace_processor/importers/proto/content_analyzer.cc", "src/trace_processor/importers/proto/deobfuscation_module.cc", + "src/trace_processor/importers/proto/deobfuscation_tracker.cc", "src/trace_processor/importers/proto/frame_timeline_event_parser.cc", "src/trace_processor/importers/proto/gpu_event_parser.cc", "src/trace_processor/importers/proto/graphics_event_module.cc", @@ -14342,11 +14451,11 @@ genrule { genrule { name: "perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor", srcs: [ - ":perfetto_protos_third_party_chromium_descriptor", + ":perfetto_protos_third_party_chromium_extension_descriptor", ], cmd: "$(location tools/gen_cc_proto_descriptor.py) --gen_dir=$(genDir) --cpp_out=$(out) $(in)", out: [ - "src/trace_processor/importers/proto/chrome_track_event.descriptor.h", + "src/trace_processor/importers/proto/chrome_track_event_extension.descriptor.h", ], tool_files: [ "tools/gen_cc_proto_descriptor.py", @@ -14518,6 +14627,15 @@ filegroup { ], } +// GN: //src/trace_processor/importers/simpleperf_proto:simpleperf_proto +filegroup { + name: "perfetto_src_trace_processor_importers_simpleperf_proto_simpleperf_proto", + srcs: [ + "src/trace_processor/importers/simpleperf_proto/simpleperf_proto_parser.cc", + "src/trace_processor/importers/simpleperf_proto/simpleperf_proto_tokenizer.cc", + ], +} + // GN: //src/trace_processor/importers/syscalls:full filegroup { name: "perfetto_src_trace_processor_importers_syscalls_full", @@ -15123,7 +15241,7 @@ genrule { "src/trace_processor/perfetto_sql/stdlib/android/cpu/cluster_type.sql", "src/trace_processor/perfetto_sql/stdlib/android/cpu/cpu_per_uid.sql", "src/trace_processor/perfetto_sql/stdlib/android/critical_blocking_calls.sql", - "src/trace_processor/perfetto_sql/stdlib/android/cujs/sysui_cuj_counters.sql", + "src/trace_processor/perfetto_sql/stdlib/android/cujs/cujs_base.sql", "src/trace_processor/perfetto_sql/stdlib/android/cujs/sysui_cujs.sql", "src/trace_processor/perfetto_sql/stdlib/android/desktop_mode.sql", "src/trace_processor/perfetto_sql/stdlib/android/device.sql", @@ -15226,9 +15344,15 @@ genrule { "src/trace_processor/perfetto_sql/stdlib/pixel/camera.sql", "src/trace_processor/perfetto_sql/stdlib/pkvm/hypervisor.sql", "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/casts.sql", + "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/core.sql", + "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/counters.sql", + "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/cpu_scheduling.sql", + "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/events.sql", "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/indexes.sql", + "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/memory.sql", "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/slices.sql", "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/tables_views.sql", + "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/tracks.sql", "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/views.sql", "src/trace_processor/perfetto_sql/stdlib/prelude/before_eof/tables.sql", "src/trace_processor/perfetto_sql/stdlib/prelude/before_eof/trace_bounds.sql", @@ -15405,7 +15529,6 @@ filegroup { name: "perfetto_src_trace_processor_storage_minimal", srcs: [ "src/trace_processor/forwarding_trace_parser.cc", - "src/trace_processor/trace_blob.cc", "src/trace_processor/trace_processor_context.cc", "src/trace_processor/trace_processor_storage.cc", "src/trace_processor/trace_processor_storage_impl.cc", @@ -15562,6 +15685,14 @@ filegroup { ], } +// GN: //src/trace_processor/util:blob +filegroup { + name: "perfetto_src_trace_processor_util_blob", + srcs: [ + "src/trace_processor/util/trace_blob.cc", + ], +} + // GN: //src/trace_processor/util:build_id filegroup { name: "perfetto_src_trace_processor_util_build_id", @@ -15771,6 +15902,7 @@ filegroup { srcs: [ "src/trace_redaction/add_synth_threads_to_process_trees.cc", "src/trace_redaction/broadphase_packet_filter.cc", + "src/trace_redaction/collect_clocks.cc", "src/trace_redaction/collect_frame_cookies.cc", "src/trace_redaction/collect_system_info.cc", "src/trace_redaction/collect_timeline_events.cc", @@ -15783,9 +15915,11 @@ filegroup { "src/trace_redaction/process_thread_timeline.cc", "src/trace_redaction/proto_util.cc", "src/trace_redaction/prune_package_list.cc", + "src/trace_redaction/prune_perf_events.cc", "src/trace_redaction/redact_ftrace_events.cc", "src/trace_redaction/redact_process_events.cc", "src/trace_redaction/redact_sched_events.cc", + "src/trace_redaction/redactor_clock_converter.cc", "src/trace_redaction/reduce_threads_in_process_trees.cc", "src/trace_redaction/scrub_process_stats.cc", "src/trace_redaction/trace_redaction_framework.cc", @@ -15799,6 +15933,7 @@ filegroup { name: "perfetto_src_trace_redaction_unittests", srcs: [ "src/trace_redaction/broadphase_packet_filter_unittest.cc", + "src/trace_redaction/collect_clocks_unittest.cc", "src/trace_redaction/collect_frame_cookies_unittest.cc", "src/trace_redaction/collect_system_info_unittest.cc", "src/trace_redaction/collect_timeline_events_unittest.cc", @@ -16735,6 +16870,7 @@ java_library { "protos/perfetto/trace/track_event/track_descriptor.proto", "protos/perfetto/trace/track_event/track_event.proto", ], + host_supported: true, proto: { type: "lite", canonical_path_from_root: false, @@ -16891,6 +17027,7 @@ java_library { "protos/perfetto/trace/ftrace/ftrace_event.proto", "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto", "protos/perfetto/trace/ftrace/ftrace_stats.proto", + "protos/perfetto/trace/ftrace/fwtp_ftrace.proto", "protos/perfetto/trace/ftrace/g2d.proto", "protos/perfetto/trace/ftrace/generic.proto", "protos/perfetto/trace/ftrace/google_icc_trace.proto", @@ -17008,6 +17145,7 @@ java_library { "protos/perfetto/trace/trigger.proto", "protos/perfetto/trace/ui_state.proto", ], + host_supported: true, proto: { type: "lite", canonical_path_from_root: false, @@ -17234,6 +17372,7 @@ cc_test { ":perfetto_include_perfetto_trace_processor_basic_types", ":perfetto_include_perfetto_trace_processor_storage", ":perfetto_include_perfetto_trace_processor_trace_processor", + ":perfetto_include_perfetto_trace_processor_util", ":perfetto_include_perfetto_tracing_core_core", ":perfetto_include_perfetto_tracing_core_forward_decls", ":perfetto_include_perfetto_tracing_tracing", @@ -17423,6 +17562,8 @@ cc_test { ":perfetto_src_protovm_protovm", ":perfetto_src_protovm_test_messages_lite_gen", ":perfetto_src_protovm_unittests", + ":perfetto_src_protozero_descriptor_diff_lib", + ":perfetto_src_protozero_descriptor_diff_unittests", ":perfetto_src_protozero_filtering_bytecode_common", ":perfetto_src_protozero_filtering_bytecode_generator", ":perfetto_src_protozero_filtering_bytecode_parser", @@ -17443,10 +17584,12 @@ cc_test { ":perfetto_src_protozero_testing_messages_zero_gen", ":perfetto_src_protozero_text_to_proto_text_to_proto", ":perfetto_src_protozero_unittests", - ":perfetto_src_shared_lib_intern_map", + ":perfetto_src_shared_lib_for_testing", ":perfetto_src_shared_lib_shared_lib", ":perfetto_src_shared_lib_test_utils", - ":perfetto_src_shared_lib_unittests", + ":perfetto_src_shared_lib_track_event_intern_map", + ":perfetto_src_shared_lib_track_event_track_event", + ":perfetto_src_shared_lib_track_event_unittests", ":perfetto_src_trace_config_utils_pb_to_txt", ":perfetto_src_trace_config_utils_txt_to_pb", ":perfetto_src_trace_config_utils_unittests", @@ -17500,6 +17643,7 @@ cc_test { ":perfetto_src_trace_processor_importers_proto_unittests", ":perfetto_src_trace_processor_importers_proto_winscope_full", ":perfetto_src_trace_processor_importers_proto_winscope_unittests", + ":perfetto_src_trace_processor_importers_simpleperf_proto_simpleperf_proto", ":perfetto_src_trace_processor_importers_syscalls_full", ":perfetto_src_trace_processor_importers_syscalls_unittests", ":perfetto_src_trace_processor_importers_systrace_full", @@ -17549,6 +17693,7 @@ cc_test { ":perfetto_src_trace_processor_types_unittests", ":perfetto_src_trace_processor_unittests", ":perfetto_src_trace_processor_util_args_utils", + ":perfetto_src_trace_processor_util_blob", ":perfetto_src_trace_processor_util_build_id", ":perfetto_src_trace_processor_util_bump_allocator", ":perfetto_src_trace_processor_util_clock", @@ -18253,6 +18398,7 @@ cc_library_static { ":perfetto_include_perfetto_trace_processor_basic_types", ":perfetto_include_perfetto_trace_processor_storage", ":perfetto_include_perfetto_trace_processor_trace_processor", + ":perfetto_include_perfetto_trace_processor_util", ":perfetto_protos_perfetto_common_cpp_gen", ":perfetto_protos_perfetto_common_zero_gen", ":perfetto_protos_perfetto_config_android_zero_gen", @@ -18342,6 +18488,7 @@ cc_library_static { ":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr", ":perfetto_src_trace_processor_importers_proto_proto_importer_module", ":perfetto_src_trace_processor_importers_proto_winscope_full", + ":perfetto_src_trace_processor_importers_simpleperf_proto_simpleperf_proto", ":perfetto_src_trace_processor_importers_syscalls_full", ":perfetto_src_trace_processor_importers_systrace_full", ":perfetto_src_trace_processor_importers_systrace_systrace_line", @@ -18372,6 +18519,7 @@ cc_library_static { ":perfetto_src_trace_processor_trace_summary_trace_summary", ":perfetto_src_trace_processor_types_types", ":perfetto_src_trace_processor_util_args_utils", + ":perfetto_src_trace_processor_util_blob", ":perfetto_src_trace_processor_util_build_id", ":perfetto_src_trace_processor_util_bump_allocator", ":perfetto_src_trace_processor_util_clock", @@ -18580,6 +18728,7 @@ cc_binary { ":perfetto_include_perfetto_trace_processor_basic_types", ":perfetto_include_perfetto_trace_processor_storage", ":perfetto_include_perfetto_trace_processor_trace_processor", + ":perfetto_include_perfetto_trace_processor_util", ":perfetto_protos_perfetto_common_cpp_gen", ":perfetto_protos_perfetto_common_zero_gen", ":perfetto_protos_perfetto_config_android_zero_gen", @@ -18676,6 +18825,7 @@ cc_binary { ":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr", ":perfetto_src_trace_processor_importers_proto_proto_importer_module", ":perfetto_src_trace_processor_importers_proto_winscope_full", + ":perfetto_src_trace_processor_importers_simpleperf_proto_simpleperf_proto", ":perfetto_src_trace_processor_importers_syscalls_full", ":perfetto_src_trace_processor_importers_systrace_full", ":perfetto_src_trace_processor_importers_systrace_systrace_line", @@ -18709,6 +18859,7 @@ cc_binary { ":perfetto_src_trace_processor_trace_summary_trace_summary", ":perfetto_src_trace_processor_types_types", ":perfetto_src_trace_processor_util_args_utils", + ":perfetto_src_trace_processor_util_blob", ":perfetto_src_trace_processor_util_build_id", ":perfetto_src_trace_processor_util_bump_allocator", ":perfetto_src_trace_processor_util_clock", @@ -18856,14 +19007,12 @@ cc_binary { ":perfetto_base_default_platform", ":perfetto_include_perfetto_base_base", ":perfetto_include_perfetto_ext_base_base", - ":perfetto_include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker", ":perfetto_include_perfetto_protozero_protozero", ":perfetto_include_perfetto_public_abi_base", ":perfetto_include_perfetto_public_base", ":perfetto_include_perfetto_public_protozero", ":perfetto_include_perfetto_trace_processor_basic_types", - ":perfetto_include_perfetto_trace_processor_storage", - ":perfetto_include_perfetto_trace_processor_trace_processor", + ":perfetto_include_perfetto_trace_processor_util", ":perfetto_protos_perfetto_common_zero_gen", ":perfetto_protos_perfetto_config_android_zero_gen", ":perfetto_protos_perfetto_config_ftrace_zero_gen", @@ -18879,7 +19028,6 @@ cc_binary { ":perfetto_protos_perfetto_config_system_info_zero_gen", ":perfetto_protos_perfetto_config_track_event_zero_gen", ":perfetto_protos_perfetto_config_zero_gen", - ":perfetto_protos_perfetto_perfetto_sql_zero_gen", ":perfetto_protos_perfetto_trace_android_winscope_common_zero_gen", ":perfetto_protos_perfetto_trace_android_winscope_regular_zero_gen", ":perfetto_protos_perfetto_trace_android_zero_gen", @@ -18894,62 +19042,22 @@ cc_binary { ":perfetto_protos_perfetto_trace_non_minimal_zero_gen", ":perfetto_protos_perfetto_trace_perfetto_zero_gen", ":perfetto_protos_perfetto_trace_power_zero_gen", - ":perfetto_protos_perfetto_trace_processor_zero_gen", ":perfetto_protos_perfetto_trace_profiling_zero_gen", ":perfetto_protos_perfetto_trace_ps_zero_gen", ":perfetto_protos_perfetto_trace_statsd_zero_gen", - ":perfetto_protos_perfetto_trace_summary_zero_gen", ":perfetto_protos_perfetto_trace_sys_stats_zero_gen", ":perfetto_protos_perfetto_trace_system_info_zero_gen", ":perfetto_protos_perfetto_trace_track_event_zero_gen", ":perfetto_protos_perfetto_trace_translation_zero_gen", - ":perfetto_protos_third_party_chromium_zero_gen", ":perfetto_src_base_base", ":perfetto_src_protozero_protozero", - ":perfetto_src_trace_processor_containers_containers", - ":perfetto_src_trace_processor_dataframe_dataframe", - ":perfetto_src_trace_processor_dataframe_impl_impl", - ":perfetto_src_trace_processor_dataframe_specs", - ":perfetto_src_trace_processor_importers_android_bugreport_android_dumpstate_event", - ":perfetto_src_trace_processor_importers_android_bugreport_android_log_event", - ":perfetto_src_trace_processor_importers_common_common", - ":perfetto_src_trace_processor_importers_common_parser_types", - ":perfetto_src_trace_processor_importers_common_synthetic_tid_hdr", - ":perfetto_src_trace_processor_importers_etw_minimal", - ":perfetto_src_trace_processor_importers_ftrace_minimal", - ":perfetto_src_trace_processor_importers_fuchsia_fuchsia_record", - ":perfetto_src_trace_processor_importers_memory_tracker_graph_processor", - ":perfetto_src_trace_processor_importers_perf_text_perf_text_sample_line_parser", - ":perfetto_src_trace_processor_importers_proto_minimal", - ":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr", - ":perfetto_src_trace_processor_importers_proto_proto_importer_module", - ":perfetto_src_trace_processor_importers_systrace_systrace_line", - ":perfetto_src_trace_processor_sorter_sorter", - ":perfetto_src_trace_processor_storage_minimal", - ":perfetto_src_trace_processor_storage_storage", - ":perfetto_src_trace_processor_tables_tables", - ":perfetto_src_trace_processor_types_types", - ":perfetto_src_trace_processor_util_build_id", - ":perfetto_src_trace_processor_util_bump_allocator", + ":perfetto_src_trace_processor_util_blob", ":perfetto_src_trace_processor_util_clock", - ":perfetto_src_trace_processor_util_descriptors", - ":perfetto_src_trace_processor_util_elf_elf", - ":perfetto_src_trace_processor_util_glob", - ":perfetto_src_trace_processor_util_gzip", - ":perfetto_src_trace_processor_util_interned_message_view", - ":perfetto_src_trace_processor_util_json_parser", - ":perfetto_src_trace_processor_util_profiler_util", - ":perfetto_src_trace_processor_util_proto_to_args_parser", - ":perfetto_src_trace_processor_util_protozero_to_text", - ":perfetto_src_trace_processor_util_regex", - ":perfetto_src_trace_processor_util_trace_blob_view_reader", - ":perfetto_src_trace_processor_util_trace_type", ":perfetto_src_trace_redaction_trace_redaction", "src/trace_redaction/main.cc", ], shared_libs: [ "liblog", - "libz", ], static_libs: [ "perfetto_flags_c_lib", @@ -18970,7 +19078,6 @@ cc_binary { "perfetto_protos_perfetto_config_system_info_zero_gen_headers", "perfetto_protos_perfetto_config_track_event_zero_gen_headers", "perfetto_protos_perfetto_config_zero_gen_headers", - "perfetto_protos_perfetto_perfetto_sql_zero_gen_headers", "perfetto_protos_perfetto_trace_android_winscope_common_zero_gen_headers", "perfetto_protos_perfetto_trace_android_winscope_regular_zero_gen_headers", "perfetto_protos_perfetto_trace_android_zero_gen_headers", @@ -18985,27 +19092,17 @@ cc_binary { "perfetto_protos_perfetto_trace_non_minimal_zero_gen_headers", "perfetto_protos_perfetto_trace_perfetto_zero_gen_headers", "perfetto_protos_perfetto_trace_power_zero_gen_headers", - "perfetto_protos_perfetto_trace_processor_zero_gen_headers", "perfetto_protos_perfetto_trace_profiling_zero_gen_headers", "perfetto_protos_perfetto_trace_ps_zero_gen_headers", "perfetto_protos_perfetto_trace_statsd_zero_gen_headers", - "perfetto_protos_perfetto_trace_summary_zero_gen_headers", "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers", "perfetto_protos_perfetto_trace_system_info_zero_gen_headers", "perfetto_protos_perfetto_trace_track_event_zero_gen_headers", "perfetto_protos_perfetto_trace_translation_zero_gen_headers", - "perfetto_protos_third_party_chromium_zero_gen_headers", - "perfetto_src_trace_processor_importers_proto_gen_cc_android_track_event_descriptor", - "perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor", - "perfetto_src_trace_processor_importers_proto_gen_cc_track_event_descriptor", - "perfetto_src_trace_processor_tables_tables_python", ], defaults: [ "perfetto_defaults", ], - cflags: [ - "-DZLIB_IMPLEMENTATION", - ], apex_available: [ "//apex_available:platform", "com.android.profiling", @@ -19034,6 +19131,7 @@ cc_binary_host { ":perfetto_include_perfetto_trace_processor_basic_types", ":perfetto_include_perfetto_trace_processor_storage", ":perfetto_include_perfetto_trace_processor_trace_processor", + ":perfetto_include_perfetto_trace_processor_util", ":perfetto_protos_perfetto_common_cpp_gen", ":perfetto_protos_perfetto_common_zero_gen", ":perfetto_protos_perfetto_config_android_zero_gen", @@ -19128,6 +19226,7 @@ cc_binary_host { ":perfetto_src_trace_processor_importers_proto_packet_sequence_state_generation_hdr", ":perfetto_src_trace_processor_importers_proto_proto_importer_module", ":perfetto_src_trace_processor_importers_proto_winscope_full", + ":perfetto_src_trace_processor_importers_simpleperf_proto_simpleperf_proto", ":perfetto_src_trace_processor_importers_syscalls_full", ":perfetto_src_trace_processor_importers_systrace_full", ":perfetto_src_trace_processor_importers_systrace_systrace_line", @@ -19158,6 +19257,7 @@ cc_binary_host { ":perfetto_src_trace_processor_trace_summary_trace_summary", ":perfetto_src_trace_processor_types_types", ":perfetto_src_trace_processor_util_args_utils", + ":perfetto_src_trace_processor_util_blob", ":perfetto_src_trace_processor_util_build_id", ":perfetto_src_trace_processor_util_bump_allocator", ":perfetto_src_trace_processor_util_clock", @@ -20061,7 +20161,7 @@ gensrcs { "--javastream_opt=include_filter:perfetto.protos.TracePacket,perfetto.protos.ShellTransition,perfetto.protos.ShellHandlerMappings,perfetto.protos.ProtoLogMessage,perfetto.protos.ProtoLogViewerConfig,perfetto.protos.ShellHandlerMapping,perfetto.protos.ShellHandlerMappings,perfetto.protos.ProtoLogGroup,perfetto.protos.ProtoLogConfig,perfetto.protos.DataSourceConfig,perfetto.protos.InternedString,perfetto.protos.InternedData,perfetto.protos.ProtoLogLevel,perfetto.protos.TestEvent,perfetto.protos.TestEvent.TestPayload,perfetto.protos.TestConfig,perfetto.protos.TestConfig.DummyFields,perfetto.protos.WinscopeExtensionsImpl,perfetto.protos.ViewCapture,perfetto.protos.AppWakelocksConfig,perfetto.protos.AppWakelockInfo,perfetto.protos.AppWakelockBundle," + // IME protos - "perfetto.protos.ClientSideProto,perfetto.protos.DisplayCutoutProto,perfetto.protos.EditorInfoProto,perfetto.protos.ImeFocusControllerProto,perfetto.protos.InputConnectionCallProto,perfetto.protos.InputConnectionProto,perfetto.protos.InputMethodClientsTraceProto,perfetto.protos.InputMethodManagerProto,perfetto.protos.InputMethodManagerServiceProto,perfetto.protos.InputMethodManagerServiceTraceProto,perfetto.protos.InputMethodServiceProto,perfetto.protos.InputMethodServiceTraceProto,perfetto.protos.InsetsAnimationControlImplProto,perfetto.protos.InsetsControllerProto,perfetto.protos.InsetsProto,perfetto.protos.InsetsSourceConsumerProto,perfetto.protos.InsetsSourceControlProto,perfetto.protos.InsetsSourceProto,perfetto.protos.InsetsStateProto,perfetto.protos.RectProto,perfetto.protos.SoftInputWindowProto,perfetto.protos.SurfaceControlProto,perfetto.protos.ViewRootImplProto,perfetto.protos.WindowLayoutParamsProto," + + "perfetto.protos.ClientSideProto,perfetto.protos.DisplayCutoutProto,perfetto.protos.EditorInfoProto,perfetto.protos.ImeFocusControllerProto,perfetto.protos.InputConnectionCallProto,perfetto.protos.InputConnectionProto,perfetto.protos.InputMethodClientsTraceProto,perfetto.protos.InputMethodClientsTraceFileProto,perfetto.protos.InputMethodManagerProto,perfetto.protos.InputMethodManagerServiceProto,perfetto.protos.InputMethodManagerServiceTraceProto,perfetto.protos.InputMethodManagerServiceTraceFileProto,perfetto.protos.InputMethodServiceProto,perfetto.protos.InputMethodServiceTraceProto,perfetto.protos.InputMethodServiceTraceFileProto,perfetto.protos.InsetsAnimationControlImplProto,perfetto.protos.InsetsControllerProto,perfetto.protos.InsetsProto,perfetto.protos.InsetsSourceConsumerProto,perfetto.protos.InsetsSourceControlProto,perfetto.protos.InsetsSourceProto,perfetto.protos.InsetsStateProto,perfetto.protos.RectProto,perfetto.protos.SoftInputWindowProto,perfetto.protos.SurfaceControlProto,perfetto.protos.ViewRootImplProto,perfetto.protos.WindowLayoutParamsProto," + // WindowManager protos "perfetto.protos.ActivityInfoProto,perfetto.protos.ActivityRecordProto,perfetto.protos.AlphaAnimationSpecProto,perfetto.protos.AnimationAdapterProto,perfetto.protos.AnimationSpecProto,perfetto.protos.AppTransitionProto,perfetto.protos.BackNavigationProto,perfetto.protos.ConfigurationContainerProto,perfetto.protos.ConfigurationProto,perfetto.protos.DeviceConfigurationProto,perfetto.protos.DisplayAreaProto,perfetto.protos.DisplayContentProto,perfetto.protos.DisplayInfoProto,perfetto.protos.DisplayRotationProto,perfetto.protos.GlobalConfigurationProto,perfetto.protos.IdentifierProto,perfetto.protos.ImeInsetsSourceProviderProto,perfetto.protos.InsetsSourceProviderProto,perfetto.protos.KeyguardControllerProto,perfetto.protos.KeyguardPerDisplayProto,perfetto.protos.KeyguardServiceDelegateProto,perfetto.protos.LocalAnimationAdapterProto,perfetto.protos.LocaleProto,perfetto.protos.MoveAnimationSpecProto,perfetto.protos.PointProto,perfetto.protos.RemoteAnimationTargetProto,perfetto.protos.RemoteInsetsControlTargetProto,perfetto.protos.ResourcesConfigurationProto,perfetto.protos.RootWindowContainerProto,perfetto.protos.SurfaceAnimatorProto,perfetto.protos.TaskFragmentProto,perfetto.protos.TaskProto,perfetto.protos.WindowAnimationSpecProto,perfetto.protos.WindowConfigurationProto,perfetto.protos.WindowContainerChildProto,perfetto.protos.WindowContainerProto,perfetto.protos.WindowFramesProto,perfetto.protos.WindowManagerConfig,perfetto.protos.WindowManagerPolicyProto,perfetto.protos.WindowManagerServiceDumpProto,perfetto.protos.WindowManagerTraceFileProto,perfetto.protos.WindowOrientationListenerProto,perfetto.protos.WindowStateAnimatorProto,perfetto.protos.WindowStateProto,perfetto.protos.WindowSurfaceControllerProto,perfetto.protos.WindowTokenProto " + @@ -20093,6 +20193,7 @@ java_library { "libprotobuf-java-lite", ], sdk_version: "current", + host_supported: true, } java_library { diff --git a/Android.bp.extras b/Android.bp.extras index c40a4c81903..0bb509ca81b 100644 --- a/Android.bp.extras +++ b/Android.bp.extras @@ -222,7 +222,7 @@ gensrcs { "--javastream_opt=include_filter:perfetto.protos.TracePacket,perfetto.protos.ShellTransition,perfetto.protos.ShellHandlerMappings,perfetto.protos.ProtoLogMessage,perfetto.protos.ProtoLogViewerConfig,perfetto.protos.ShellHandlerMapping,perfetto.protos.ShellHandlerMappings,perfetto.protos.ProtoLogGroup,perfetto.protos.ProtoLogConfig,perfetto.protos.DataSourceConfig,perfetto.protos.InternedString,perfetto.protos.InternedData,perfetto.protos.ProtoLogLevel,perfetto.protos.TestEvent,perfetto.protos.TestEvent.TestPayload,perfetto.protos.TestConfig,perfetto.protos.TestConfig.DummyFields,perfetto.protos.WinscopeExtensionsImpl,perfetto.protos.ViewCapture,perfetto.protos.AppWakelocksConfig,perfetto.protos.AppWakelockInfo,perfetto.protos.AppWakelockBundle," + // IME protos - "perfetto.protos.ClientSideProto,perfetto.protos.DisplayCutoutProto,perfetto.protos.EditorInfoProto,perfetto.protos.ImeFocusControllerProto,perfetto.protos.InputConnectionCallProto,perfetto.protos.InputConnectionProto,perfetto.protos.InputMethodClientsTraceProto,perfetto.protos.InputMethodManagerProto,perfetto.protos.InputMethodManagerServiceProto,perfetto.protos.InputMethodManagerServiceTraceProto,perfetto.protos.InputMethodServiceProto,perfetto.protos.InputMethodServiceTraceProto,perfetto.protos.InsetsAnimationControlImplProto,perfetto.protos.InsetsControllerProto,perfetto.protos.InsetsProto,perfetto.protos.InsetsSourceConsumerProto,perfetto.protos.InsetsSourceControlProto,perfetto.protos.InsetsSourceProto,perfetto.protos.InsetsStateProto,perfetto.protos.RectProto,perfetto.protos.SoftInputWindowProto,perfetto.protos.SurfaceControlProto,perfetto.protos.ViewRootImplProto,perfetto.protos.WindowLayoutParamsProto," + + "perfetto.protos.ClientSideProto,perfetto.protos.DisplayCutoutProto,perfetto.protos.EditorInfoProto,perfetto.protos.ImeFocusControllerProto,perfetto.protos.InputConnectionCallProto,perfetto.protos.InputConnectionProto,perfetto.protos.InputMethodClientsTraceProto,perfetto.protos.InputMethodClientsTraceFileProto,perfetto.protos.InputMethodManagerProto,perfetto.protos.InputMethodManagerServiceProto,perfetto.protos.InputMethodManagerServiceTraceProto,perfetto.protos.InputMethodManagerServiceTraceFileProto,perfetto.protos.InputMethodServiceProto,perfetto.protos.InputMethodServiceTraceProto,perfetto.protos.InputMethodServiceTraceFileProto,perfetto.protos.InsetsAnimationControlImplProto,perfetto.protos.InsetsControllerProto,perfetto.protos.InsetsProto,perfetto.protos.InsetsSourceConsumerProto,perfetto.protos.InsetsSourceControlProto,perfetto.protos.InsetsSourceProto,perfetto.protos.InsetsStateProto,perfetto.protos.RectProto,perfetto.protos.SoftInputWindowProto,perfetto.protos.SurfaceControlProto,perfetto.protos.ViewRootImplProto,perfetto.protos.WindowLayoutParamsProto," + // WindowManager protos "perfetto.protos.ActivityInfoProto,perfetto.protos.ActivityRecordProto,perfetto.protos.AlphaAnimationSpecProto,perfetto.protos.AnimationAdapterProto,perfetto.protos.AnimationSpecProto,perfetto.protos.AppTransitionProto,perfetto.protos.BackNavigationProto,perfetto.protos.ConfigurationContainerProto,perfetto.protos.ConfigurationProto,perfetto.protos.DeviceConfigurationProto,perfetto.protos.DisplayAreaProto,perfetto.protos.DisplayContentProto,perfetto.protos.DisplayInfoProto,perfetto.protos.DisplayRotationProto,perfetto.protos.GlobalConfigurationProto,perfetto.protos.IdentifierProto,perfetto.protos.ImeInsetsSourceProviderProto,perfetto.protos.InsetsSourceProviderProto,perfetto.protos.KeyguardControllerProto,perfetto.protos.KeyguardPerDisplayProto,perfetto.protos.KeyguardServiceDelegateProto,perfetto.protos.LocalAnimationAdapterProto,perfetto.protos.LocaleProto,perfetto.protos.MoveAnimationSpecProto,perfetto.protos.PointProto,perfetto.protos.RemoteAnimationTargetProto,perfetto.protos.RemoteInsetsControlTargetProto,perfetto.protos.ResourcesConfigurationProto,perfetto.protos.RootWindowContainerProto,perfetto.protos.SurfaceAnimatorProto,perfetto.protos.TaskFragmentProto,perfetto.protos.TaskProto,perfetto.protos.WindowAnimationSpecProto,perfetto.protos.WindowConfigurationProto,perfetto.protos.WindowContainerChildProto,perfetto.protos.WindowContainerProto,perfetto.protos.WindowFramesProto,perfetto.protos.WindowManagerConfig,perfetto.protos.WindowManagerPolicyProto,perfetto.protos.WindowManagerServiceDumpProto,perfetto.protos.WindowManagerTraceFileProto,perfetto.protos.WindowOrientationListenerProto,perfetto.protos.WindowStateAnimatorProto,perfetto.protos.WindowStateProto,perfetto.protos.WindowSurfaceControllerProto,perfetto.protos.WindowTokenProto " + @@ -254,6 +254,7 @@ java_library { "libprotobuf-java-lite", ], sdk_version: "current", + host_supported: true, } java_library { diff --git a/BUILD b/BUILD index 70cdc8863be..a517aa16d93 100644 --- a/BUILD +++ b/BUILD @@ -43,6 +43,7 @@ load( "perfetto_android_jni_library", "perfetto_android_library", "perfetto_android_instrumentation_test", + "perfetto_protozero_descriptor_diff", ) package(default_visibility = [PERFETTO_CONFIG.root + ":__subpackages__"]) @@ -167,8 +168,10 @@ perfetto_cc_library( ":src_protozero_filtering_bytecode_parser", ":src_protozero_filtering_message_filter", ":src_protozero_filtering_string_filter", - ":src_shared_lib_intern_map", + ":src_shared_lib_for_testing", ":src_shared_lib_shared_lib", + ":src_shared_lib_track_event_intern_map", + ":src_shared_lib_track_event_track_event", ":src_tracing_client_api_without_backends", ":src_tracing_common", ":src_tracing_core_core", @@ -374,6 +377,7 @@ perfetto_cc_library( ":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr", ":src_trace_processor_importers_proto_proto_importer_module", ":src_trace_processor_importers_proto_winscope_full", + ":src_trace_processor_importers_simpleperf_proto_simpleperf_proto", ":src_trace_processor_importers_syscalls_full", ":src_trace_processor_importers_systrace_full", ":src_trace_processor_importers_systrace_systrace_line", @@ -408,6 +412,7 @@ perfetto_cc_library( ":src_trace_processor_trace_summary_trace_summary", ":src_trace_processor_types_types", ":src_trace_processor_util_args_utils", + ":src_trace_processor_util_blob", ":src_trace_processor_util_build_id", ":src_trace_processor_util_bump_allocator", ":src_trace_processor_util_clock", @@ -448,6 +453,7 @@ perfetto_cc_library( ":include_perfetto_trace_processor_basic_types", ":include_perfetto_trace_processor_storage", ":include_perfetto_trace_processor_trace_processor", + ":include_perfetto_trace_processor_util", ], deps = [ ":protos_perfetto_common_cpp", @@ -542,6 +548,7 @@ perfetto_cc_library( ":include_perfetto_trace_processor_basic_types", ":include_perfetto_trace_processor_storage", ":include_perfetto_trace_processor_trace_processor", + ":include_perfetto_trace_processor_util", ], visibility = PERFETTO_CONFIG.public_visibility, deps = [ @@ -1137,9 +1144,6 @@ perfetto_filegroup( perfetto_filegroup( name = "include_perfetto_trace_processor_storage", srcs = [ - "include/perfetto/trace_processor/ref_counted.h", - "include/perfetto/trace_processor/trace_blob.h", - "include/perfetto/trace_processor/trace_blob_view.h", "include/perfetto/trace_processor/trace_processor_storage.h", ], ) @@ -1155,6 +1159,16 @@ perfetto_filegroup( ], ) +# GN target: //include/perfetto/trace_processor:util +perfetto_filegroup( + name = "include_perfetto_trace_processor_util", + srcs = [ + "include/perfetto/trace_processor/ref_counted.h", + "include/perfetto/trace_processor/trace_blob.h", + "include/perfetto/trace_processor/trace_blob_view.h", + ], +) + # GN target: //include/perfetto/tracing/core:core perfetto_filegroup( name = "include_perfetto_tracing_core_core", @@ -1226,6 +1240,16 @@ perfetto_filegroup( ], ) +# GN target: //protos/third_party/chromium:extension_descriptor +perfetto_protozero_descriptor_diff( + name = "protos_third_party_chromium_extension_descriptor", + outs = [ + "protos/third_party/chromium/chrome_track_event_extension.descriptor", + ], + minuend = "protos_third_party_chromium_descriptor", + subtrahend = "protos_perfetto_trace_descriptor", +) + # GN target: //src/android_internal:headers perfetto_filegroup( name = "src_android_internal_headers", @@ -1538,6 +1562,30 @@ perfetto_filegroup( ], ) +# GN target: //src/protozero/descriptor_diff:lib +perfetto_filegroup( + name = "src_protozero_descriptor_diff_lib", + srcs = [ + "src/protozero/descriptor_diff/descriptor_diff.cc", + "src/protozero/descriptor_diff/descriptor_diff.h", + ], +) + +# GN target: //src/protozero/descriptor_diff:protozero_descriptor_diff +perfetto_cc_binary( + name = "src_protozero_descriptor_diff_protozero_descriptor_diff", + srcs = [ + ":src_protozero_descriptor_diff_lib", + "src/protozero/descriptor_diff/main.cc", + ], + deps = [ + ":protos_perfetto_common_zero", + ":protozero", + ":src_base_base", + ":src_base_version", + ], +) + # GN target: //src/protozero/filtering:bytecode_common perfetto_filegroup( name = "src_protozero_filtering_bytecode_common", @@ -1609,12 +1657,40 @@ perfetto_filegroup( ], ) -# GN target: //src/shared_lib:intern_map +# GN target: //src/shared_lib/track_event:intern_map +perfetto_filegroup( + name = "src_shared_lib_track_event_intern_map", + srcs = [ + "src/shared_lib/track_event/intern_map.cc", + "src/shared_lib/track_event/intern_map.h", + ], +) + +# GN target: //src/shared_lib/track_event:track_event +perfetto_filegroup( + name = "src_shared_lib_track_event_track_event", + srcs = [ + "src/shared_lib/track_event/category_impl.cc", + "src/shared_lib/track_event/category_impl.h", + "src/shared_lib/track_event/category_utils.cc", + "src/shared_lib/track_event/category_utils.h", + "src/shared_lib/track_event/ds.cc", + "src/shared_lib/track_event/ds.h", + "src/shared_lib/track_event/global_state.cc", + "src/shared_lib/track_event/global_state.h", + "src/shared_lib/track_event/hl.cc", + "src/shared_lib/track_event/ll.cc", + "src/shared_lib/track_event/serialization.cc", + "src/shared_lib/track_event/serialization.h", + "src/shared_lib/track_event/track_event.cc", + ], +) + +# GN target: //src/shared_lib:for_testing perfetto_filegroup( - name = "src_shared_lib_intern_map", + name = "src_shared_lib_for_testing", srcs = [ - "src/shared_lib/intern_map.cc", - "src/shared_lib/intern_map.h", + "src/shared_lib/reset_for_testing.h", ], ) @@ -1626,12 +1702,10 @@ perfetto_filegroup( "src/shared_lib/heap_buffer.cc", "src/shared_lib/pb_decoder.cc", "src/shared_lib/producer.cc", - "src/shared_lib/reset_for_testing.h", "src/shared_lib/stream_writer.cc", "src/shared_lib/stream_writer.h", "src/shared_lib/thread_utils.cc", "src/shared_lib/tracing_session.cc", - "src/shared_lib/track_event.cc", ], ) @@ -1844,6 +1918,8 @@ perfetto_filegroup( "src/trace_processor/importers/common/flow_tracker.h", "src/trace_processor/importers/common/global_args_tracker.cc", "src/trace_processor/importers/common/global_args_tracker.h", + "src/trace_processor/importers/common/import_logs_tracker.cc", + "src/trace_processor/importers/common/import_logs_tracker.h", "src/trace_processor/importers/common/jit_cache.cc", "src/trace_processor/importers/common/jit_cache.h", "src/trace_processor/importers/common/legacy_v8_cpu_profile_tracker.cc", @@ -2138,8 +2214,8 @@ perfetto_filegroup( "src/trace_processor/importers/perf/perf_event.h", "src/trace_processor/importers/perf/perf_event_attr.cc", "src/trace_processor/importers/perf/perf_event_attr.h", - "src/trace_processor/importers/perf/perf_session.cc", - "src/trace_processor/importers/perf/perf_session.h", + "src/trace_processor/importers/perf/perf_invocation.cc", + "src/trace_processor/importers/perf/perf_invocation.h", "src/trace_processor/importers/perf/reader.h", "src/trace_processor/importers/perf/record.h", ], @@ -2272,6 +2348,8 @@ perfetto_filegroup( "src/trace_processor/importers/proto/content_analyzer.h", "src/trace_processor/importers/proto/deobfuscation_module.cc", "src/trace_processor/importers/proto/deobfuscation_module.h", + "src/trace_processor/importers/proto/deobfuscation_tracker.cc", + "src/trace_processor/importers/proto/deobfuscation_tracker.h", "src/trace_processor/importers/proto/frame_timeline_event_parser.cc", "src/trace_processor/importers/proto/frame_timeline_event_parser.h", "src/trace_processor/importers/proto/gpu_event_parser.cc", @@ -2332,10 +2410,10 @@ perfetto_cc_proto_descriptor( perfetto_cc_proto_descriptor( name = "src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor", deps = [ - ":protos_third_party_chromium_descriptor", + ":protos_third_party_chromium_extension_descriptor", ], outs = [ - "src/trace_processor/importers/proto/chrome_track_event.descriptor.h", + "src/trace_processor/importers/proto/chrome_track_event_extension.descriptor.h", ], ) @@ -2447,6 +2525,18 @@ perfetto_filegroup( ], ) +# GN target: //src/trace_processor/importers/simpleperf_proto:simpleperf_proto +perfetto_filegroup( + name = "src_trace_processor_importers_simpleperf_proto_simpleperf_proto", + srcs = [ + "src/trace_processor/importers/simpleperf_proto/simpleperf_proto_parser.cc", + "src/trace_processor/importers/simpleperf_proto/simpleperf_proto_parser.h", + "src/trace_processor/importers/simpleperf_proto/simpleperf_proto_tokenizer.cc", + "src/trace_processor/importers/simpleperf_proto/simpleperf_proto_tokenizer.h", + "src/trace_processor/importers/simpleperf_proto/simpleperf_proto_tracker.h", + ], +) + # GN target: //src/trace_processor/importers/syscalls:full perfetto_filegroup( name = "src_trace_processor_importers_syscalls_full", @@ -3000,7 +3090,7 @@ perfetto_filegroup( perfetto_filegroup( name = "src_trace_processor_perfetto_sql_stdlib_android_cujs_cujs", srcs = [ - "src/trace_processor/perfetto_sql/stdlib/android/cujs/sysui_cuj_counters.sql", + "src/trace_processor/perfetto_sql/stdlib/android/cujs/cujs_base.sql", "src/trace_processor/perfetto_sql/stdlib/android/cujs/sysui_cujs.sql", ], ) @@ -3290,9 +3380,15 @@ perfetto_filegroup( name = "src_trace_processor_perfetto_sql_stdlib_prelude_after_eof_after_eof", srcs = [ "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/casts.sql", + "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/core.sql", + "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/counters.sql", + "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/cpu_scheduling.sql", + "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/events.sql", "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/indexes.sql", + "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/memory.sql", "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/slices.sql", "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/tables_views.sql", + "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/tracks.sql", "src/trace_processor/perfetto_sql/stdlib/prelude/after_eof/views.sql", ], ) @@ -3700,6 +3796,14 @@ perfetto_filegroup( ], ) +# GN target: //src/trace_processor/util:blob +perfetto_filegroup( + name = "src_trace_processor_util_blob", + srcs = [ + "src/trace_processor/util/trace_blob.cc", + ], +) + # GN target: //src/trace_processor/util:build_id perfetto_filegroup( name = "src_trace_processor_util_build_id", @@ -3966,7 +4070,6 @@ perfetto_filegroup( srcs = [ "src/trace_processor/forwarding_trace_parser.cc", "src/trace_processor/forwarding_trace_parser.h", - "src/trace_processor/trace_blob.cc", "src/trace_processor/trace_processor_context.cc", "src/trace_processor/trace_processor_storage.cc", "src/trace_processor/trace_processor_storage_impl.cc", @@ -6474,6 +6577,7 @@ perfetto_proto_library( "protos/perfetto/trace/ftrace/ftrace_event.proto", "protos/perfetto/trace/ftrace/ftrace_event_bundle.proto", "protos/perfetto/trace/ftrace/ftrace_stats.proto", + "protos/perfetto/trace/ftrace/fwtp_ftrace.proto", "protos/perfetto/trace/ftrace/g2d.proto", "protos/perfetto/trace/ftrace/generic.proto", "protos/perfetto/trace/ftrace/google_icc_trace.proto", @@ -7223,6 +7327,7 @@ perfetto_cc_protozero_library( perfetto_proto_library( name = "protos_third_party_simpleperf_protos", srcs = [ + "protos/third_party/simpleperf/cmd_report_sample.proto", "protos/third_party/simpleperf/record_file.proto", ], visibility = [ @@ -7531,6 +7636,7 @@ perfetto_cc_library( ":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr", ":src_trace_processor_importers_proto_proto_importer_module", ":src_trace_processor_importers_proto_winscope_full", + ":src_trace_processor_importers_simpleperf_proto_simpleperf_proto", ":src_trace_processor_importers_syscalls_full", ":src_trace_processor_importers_systrace_full", ":src_trace_processor_importers_systrace_systrace_line", @@ -7564,6 +7670,7 @@ perfetto_cc_library( ":src_trace_processor_trace_summary_trace_summary", ":src_trace_processor_types_types", ":src_trace_processor_util_args_utils", + ":src_trace_processor_util_blob", ":src_trace_processor_util_build_id", ":src_trace_processor_util_bump_allocator", ":src_trace_processor_util_clock", @@ -7602,6 +7709,7 @@ perfetto_cc_library( ":include_perfetto_trace_processor_basic_types", ":include_perfetto_trace_processor_storage", ":include_perfetto_trace_processor_trace_processor", + ":include_perfetto_trace_processor_util", ], visibility = [ "//visibility:public", @@ -7695,6 +7803,7 @@ perfetto_cc_binary( ":include_perfetto_trace_processor_basic_types", ":include_perfetto_trace_processor_storage", ":include_perfetto_trace_processor_trace_processor", + ":include_perfetto_trace_processor_util", ":src_kernel_utils_kernel_wakelock_errors", ":src_kernel_utils_syscall_table", ":src_profiling_deobfuscator", @@ -7742,6 +7851,7 @@ perfetto_cc_binary( ":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr", ":src_trace_processor_importers_proto_proto_importer_module", ":src_trace_processor_importers_proto_winscope_full", + ":src_trace_processor_importers_simpleperf_proto_simpleperf_proto", ":src_trace_processor_importers_syscalls_full", ":src_trace_processor_importers_systrace_full", ":src_trace_processor_importers_systrace_systrace_line", @@ -7778,6 +7888,7 @@ perfetto_cc_binary( ":src_trace_processor_trace_summary_trace_summary", ":src_trace_processor_types_types", ":src_trace_processor_util_args_utils", + ":src_trace_processor_util_blob", ":src_trace_processor_util_build_id", ":src_trace_processor_util_bump_allocator", ":src_trace_processor_util_clock", @@ -7898,6 +8009,7 @@ perfetto_cc_binary( ":include_perfetto_trace_processor_basic_types", ":include_perfetto_trace_processor_storage", ":include_perfetto_trace_processor_trace_processor", + ":include_perfetto_trace_processor_util", ":src_kernel_utils_kernel_wakelock_errors", ":src_kernel_utils_syscall_table", ":src_profiling_deobfuscator", @@ -7945,6 +8057,7 @@ perfetto_cc_binary( ":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr", ":src_trace_processor_importers_proto_proto_importer_module", ":src_trace_processor_importers_proto_winscope_full", + ":src_trace_processor_importers_simpleperf_proto_simpleperf_proto", ":src_trace_processor_importers_syscalls_full", ":src_trace_processor_importers_systrace_full", ":src_trace_processor_importers_systrace_systrace_line", @@ -7978,6 +8091,7 @@ perfetto_cc_binary( ":src_trace_processor_trace_summary_trace_summary", ":src_trace_processor_types_types", ":src_trace_processor_util_args_utils", + ":src_trace_processor_util_blob", ":src_trace_processor_util_build_id", ":src_trace_processor_util_bump_allocator", ":src_trace_processor_util_clock", diff --git a/CHANGELOG b/CHANGELOG index 819b60d2c5d..319fa4c8567 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,109 @@ Unreleased: SDK: * +v53.0 - 2025-11-12: + SDK: + * Introduced an initial Rust SDK (`perfetto-sdk` on crates.io). This is the + first project to be hosted in the new `contrib/` directory as a + community-maintained effort. + * Java SDK: Allow users to provide custom identifiers for named tracks. + Tracing service and probes: + * Added initial support for building and running Perfetto on FreeBSD, + enabling both the tracing service and SDK. This includes + platform-specific implementations for threading, time, and memory + management, as well as toolchain adjustments. + * "linux.ftrace": Enabled `FtraceConfig.denser_generic_event_encoding` by + default. This reduces trace size for events unknown at compile time. + * SharedMemoryArbiter: Reduced CPU usage when tracing high-throughput + workloads by preventing duplicate immediate flush tasks when the shared + memory buffer is more than half full. + * traced_probes: Increased shared memory buffer request to 2MB (was 1MB) + when connecting to the tracing service (traced). + * traced_perf: Added support for user-space, kernel, and hypervisor + counting event modifiers. + * Updated gpu_scheduler trace events for Linux 6.17. + * perfetto cmd: Added `--notify-fd` flag. This allows waiting for all data + sources to be started without running in background mode, which is useful + for capturing command output or ensuring termination with the calling + process. + * Android: Switched to RtFutex to fix deadlock when `Tracing::Initialize` is + called from a static initializer. + * Enabled LockFreeTaskRunner on non-Android platforms to reduce lock + contention. + SQL Standard library: + * android_anrs: Added ANR timer event data and default durations. + * Improved thread creation spam analysis with new per-process and renamed + per-thread functions. + Trace Processor: + * Added support for importing pprof profiles. + * Added support for ingesting simpleperf's protobuf format. + * Added support for parsing process_sort_index and thread_sort_index + metadata from JSON traces. These are stored as process_sort_index_hint + and thread_sort_index_hint in the args table for processes and threads. + * Added an `arg_set_id` column to the thread table. Queries joining + `thread` with other tables now need to disambiguate the column or they + may fail with "ambiguous column name: arg_set_id". Qualify the column + with the table name (e.g. `slice.arg_set_id` or `counter.arg_set_id`) to + resolve this. + * Added support for dynamically loading custom protobuf descriptors at + runtime. + * Added support for FILE_IO events and thread wait reasons in CSwitch + events for more detailed ETW traces. + * Added `unhex` SQL function to convert hexadecimal strings to signed + 64-bit integers. + * Added support for Python trace_processor to expose trace metadata. + * Improved error handling and logging for clock synchronization and track + events. + UI: + * Perf sample callstack tracks: Added capability to expand thread and + process level callstack tracks to distinguish samples from different + timebases or leader counters when multiple callstack data sources are + present. + * Added support for sorting processes and threads using sort_index hints + from JSON traces. Processes and threads with lower sort index values + will appear first in the UI. + * Added support for attaching callstacks to TrackEvents, including both + slices and instant events. When an event with a callstack is selected, the + callstack is displayed in the details panel. Additionally, an area + selection will aggregate all callstacks of events in that area into a + flamegraph. + * Added a new plugin for displaying pprof profiles, which introduces a + dedicated page in the UI for visualizing and analyzing pprof data. + * Overhauled the "Info and Stats" page, renaming it to "Overview" and + transforming it into a modern, tabbed dashboard. This new page provides a + high-level summary of the trace and exposes the new `trace_import_logs` + table from the Trace Processor, making it easier to debug trace import + issues. + * Added resize handle to pinned tracks area. Drag to set fixed height; + double-click to restore default auto-sizing. + * Debug tracks: Added support for custom slice coloring via SQL column + values. + * Recording page: Added 'Network' section for WiFi debugging. + * Added path-based filtering to all `*ByRegex` commands (`Pin`, `Expand`, + `Collapse`, `CopyTracksToWorkspace`, + `CopyTracksToWorkspaceWithAncestors`). This allows for more precise track + selection by filtering on the full track path (e.g., `process_name > + thread_name`) in addition to just the track name. + * Android Logcat: Improved column ordering, case-insensitive search, and UI + clarity. + * Grouped settings on the settings page by the plugin that added them, with + core settings placed in a "core" section, to improve organization. + * Increased wasm stack size to 2MB to reduce stack overflows. + * Added ability to pin and copy tracks using SQL queries. + * SQL table viewer: Added "Analyze" submenu for data analysis + and transformation, including column casting and various string + manipulations. + * Added support for visually marking inlined functions within flamegraphs. + These markers appear as a visual indicator on a frame and in its tooltip, + making it easier to identify compiler optimizations like inlining in CPU + profiles. + Misc: + * Introduced a `contrib/` directory for community-maintained code, allowing + external contributors to own and maintain projects that are technically + decoupled from official Perfetto distributions. These projects have + relaxed review requirements and are not officially supported by the core + Perfetto team. (https://github.com/google/perfetto/discussions/3404) + v52.0 - 2025-09-16: Tracing service and probes: * Added support for exclusive single-tenant features in ftrace data source. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 0144e1c575a..aa3c5678edb 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -42,3 +42,18 @@ Power analysis in Trace Processor and UI: Advanced query/data features in UI: Alexander Timin - @altimin + +Rust SDK: + David Reveman - @dreveman + + +Community maintainers +--------------------- +Maintainers for community-maintained code found in contrib/ directory. + +See /rfcs/0007-community-maintained-code.md for details. + +Rust SDK maintainers: + David Reveman - @dreveman + Jonathan Bailey - @jon-rv + Elisa Tai - @zytyz diff --git a/GEMINI.md b/GEMINI.md new file mode 120000 index 00000000000..dd20853b659 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1 @@ +docs/AGENTS.md \ No newline at end of file diff --git a/README.md b/README.md index 0ed327db7c0..3a3370b813c 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,12 @@ experienced developer. why it's useful, and who uses it, see our main documentation page: - [**Perfetto Documentation Home**](https://perfetto.dev/docs/) +## Debian Distribution + +For users interested in the Debian distribution of Perfetto, the official source +of truth and packaging efforts are maintained at +[Debian Perfetto Salsa Repository](https://salsa.debian.org/debian/perfetto) + ## Community & Support Have questions? Need help? diff --git a/TEST_MAPPING b/TEST_MAPPING index 070a4604ebf..cf10eb3f36a 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -13,6 +13,9 @@ { "name": "CtsPerfettoTestCases" }, + { + "name": "CtsPerfettoReporterTestCases" + }, // Additional presubmit tests that explicitly exercise // Perfetto's backend { diff --git a/bazel/BUILD b/bazel/BUILD index b6acc66725a..a96cfcafadd 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -41,6 +41,14 @@ config_setting( visibility = ["//visibility:public"], ) +config_setting( + name = "os_freebsd", + constraint_values = [ + "@platforms//os:freebsd", + ], + visibility = ["//visibility:public"], +) + # Note this config does not imply MSVC. config_setting( name = "os_windows", diff --git a/bazel/rules.bzl b/bazel/rules.bzl index 5be095af0ac..563382a18bf 100644 --- a/bazel/rules.bzl +++ b/bazel/rules.bzl @@ -31,6 +31,7 @@ def default_cc_args(): "includes": ["include"], "linkopts": select({ "@perfetto//bazel:os_linux": ["-ldl", "-lrt", "-lpthread"], + "@perfetto//bazel:os_freebsd": ["-ldl", "-lrt", "-lpthread"], "@perfetto//bazel:os_osx": [], "@perfetto//bazel:os_windows": ["ws2_32.lib"], "//conditions:default": ["-ldl"], @@ -358,6 +359,28 @@ def perfetto_cc_proto_descriptor(name, deps, outs, **kwargs): **kwargs ) +def perfetto_protozero_descriptor_diff(name, minuend, subtrahend, outs, **kwargs): + cmd = [ + "$(location src_protozero_descriptor_diff_protozero_descriptor_diff)", + "--minuend=$(location " + minuend + ")", + "--subtrahend=$(location " + subtrahend + ")", + "--out", + "$@", + ] + perfetto_genrule( + name = name, + cmd = " ".join(cmd), + tools = [ + ":src_protozero_descriptor_diff_protozero_descriptor_diff", + ], + srcs = [ + minuend, + subtrahend, + ], + outs = outs, + **kwargs + ) + def perfetto_cc_amalgamated_sql(name, deps, outs, namespace, **kwargs): if PERFETTO_CONFIG.root[:2] != "//": fail("Expected PERFETTO_CONFIG.root to start with //") diff --git a/buildtools/.gitignore b/buildtools/.gitignore index 53edc846f6d..02f18f882fb 100644 --- a/buildtools/.gitignore +++ b/buildtools/.gitignore @@ -39,6 +39,7 @@ buildtools_hash_revision_for_cache_on_ci.txt /open_csd/ /pigweed/ /protobuf/ +/rustup/ /sqlglot/ /sqlite_src/ /sqlite/ diff --git a/codereview.settings b/codereview.settings deleted file mode 100644 index 06bf28768f6..00000000000 --- a/codereview.settings +++ /dev/null @@ -1,5 +0,0 @@ -# For depot_tools / git cl upload workflow. -GERRIT_HOST: True -CODE_REVIEW_SERVER: https://android-review.googlesource.com -PROJECT: platform/external/perfetto -USE_PYTHON3: True diff --git a/contrib/README.md b/contrib/README.md new file mode 100644 index 00000000000..d0a4ccab60c --- /dev/null +++ b/contrib/README.md @@ -0,0 +1,33 @@ +# Community Maintained Code + +This directory contains code maintained by the Perfetto community, +not the core Perfetto team. + +Code in this directory is: +- **Community maintained**: Owned and supported by contributors listed in + OWNERS.github (please stick to this naming to avoid internal rollout + complications). +- **Not officially supported**: The Perfetto core team does not guarantee + maintenance +- **Not part of Android/Google builds**: Not included in Android or + Google-internal repositories +- **Experimental**: May have different stability and compatibility guarantees + than core Perfetto. + +## Using contrib/ code + +If you depend on code from `contrib/`, understand that: +- Breaking changes may occur without the same deprecation periods as core APIs. +- Bugs may take longer to fix depending on maintainer availability. +- Features may be removed if maintainers are no longer active. + +## Contributing to contrib/ + +To add a new project to `contrib/`: +1. Open a GitHub issue proposing the addition +2. Demonstrate community need and maintainer commitment +3. Get approval from Perfetto maintainers +4. Submit PR with initial code and OWNERS.github file + +See [Contributing Guide](https://perfetto.dev/docs/contributing/getting-started) +for general contribution guidelines. diff --git a/contrib/rust-sdk/.gitignore b/contrib/rust-sdk/.gitignore new file mode 100644 index 00000000000..186942a45ee --- /dev/null +++ b/contrib/rust-sdk/.gitignore @@ -0,0 +1,10 @@ +# build artifacts +target + +# lockfiles +# (only the top-level workspace lockfile should be committed) +**/Cargo.lock +!Cargo.lock + +# amalgamated shared library source +perfetto-sys/libperfetto_c diff --git a/contrib/rust-sdk/Cargo.lock b/contrib/rust-sdk/Cargo.lock new file mode 100644 index 00000000000..791e1032487 --- /dev/null +++ b/contrib/rust-sdk/Cargo.lock @@ -0,0 +1,299 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "cc" +version = "1.2.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "perfetto-sdk" +version = "0.1.2" +dependencies = [ + "bitflags", + "paste", + "perfetto-sdk-sys", + "thiserror", +] + +[[package]] +name = "perfetto-sdk-derive" +version = "0.1.2" +dependencies = [ + "perfetto-sdk", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "perfetto-sdk-protos-gpu" +version = "0.1.2" +dependencies = [ + "paste", + "perfetto-sdk", +] + +[[package]] +name = "perfetto-sdk-sys" +version = "0.1.1" +dependencies = [ + "bindgen", + "cc", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" diff --git a/contrib/rust-sdk/Cargo.toml b/contrib/rust-sdk/Cargo.toml new file mode 100644 index 00000000000..cce5d41b369 --- /dev/null +++ b/contrib/rust-sdk/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "2" +members = ["perfetto", "perfetto-derive", "perfetto-protos-gpu", "perfetto-sys"] diff --git a/contrib/rust-sdk/OWNERS.github b/contrib/rust-sdk/OWNERS.github new file mode 100644 index 00000000000..aeba5b335df --- /dev/null +++ b/contrib/rust-sdk/OWNERS.github @@ -0,0 +1,8 @@ +# This file is intended to define code ownership within the GitHub repository. +# This file should be called OWNERS.github to prevent processing by internal tooling +# that enforces stricter validation on OWNERS files, such as requiring internal +# email domains. + +dreveman@gmail.com +jon.bailey45@gmail.com +ziyi.elisa.tai@gmail.com diff --git a/contrib/rust-sdk/README.md b/contrib/rust-sdk/README.md new file mode 100644 index 00000000000..a554afaada3 --- /dev/null +++ b/contrib/rust-sdk/README.md @@ -0,0 +1,82 @@ +# Perfetto Rust SDK + +The **Perfetto Rust SDK** provides safe and idiomatic Rust bindings for the +[Perfetto tracing framework](https://perfetto.dev). +It allows Rust applications to produce and consume trace data, record track +events, and integrate with existing Perfetto infrastructure. + +This SDK is designed to closely mirror the C API while providing the +ergonomics and safety guarantees of modern Rust. + +--- + +## Overview + +This workspace consists of three crates: + +| Crate | Description | +|-------|--------------| +| [`perfetto-sdk-sys`](./perfetto-sys) | Low-level FFI bindings to the C API (`perfetto_c`). Can link against system or vendored builds. | +| [`perfetto-sdk`](./perfetto) | Safe and ergonomic wrapper around the raw FFI. Exposes the tracing session, data source, and track event APIs. | +| [`perfetto-sdk-derive`](./perfetto-derive) | Procedural macros for tracing the scope of function calls and automatically capturing all input parameters. | +| [`perfetto-sdk-protos-gpu`](./perfetto-protos-gpu) | Extra protobuf bindings for GPU events. | + +--- + +## Features + +- **Low-overhead tracing** — native atomics, branch prediction hints, and proto serialization using optimized rust code. +- **Safe API layer** — builder patterns, lifetime-checked handles, and error handling. +- **Track Event macros** — ergonomic category definition and event emission macros. +- **Protozero integration** — auto-generated Rust code from Perfetto `.proto` files via a protoc plugin. +- **Vendored or system builds** — link against a bundled `perfetto_c` library or use an external one. +- **FFI isolation** — `perfetto-sys` is the only crate exposing an API with `unsafe` code. +- **Cross-platform support** — Linux and macOS are tested. + +--- + +## Building + +### Prerequisites + +- **Rust** ≥ 1.85 (edition 2024) +- Optionally: GN + Ninja for regenerating proto bindings + +### Using Cargo + +Build and run all tests: + +```bash +# Links against a bundled `perfetto_c` library: +cargo test --manifest-path contrib/rust-sdk/Cargo.toml + +# Links against an external `perfetto_c` library: +export PERFETTO_SYS_LIB_DIR=/path/to/lib +export PERFETTO_SYS_INCLUDE_DIR=/path/to/include +cargo test --no-default-features --manifest-path contrib/rust-sdk/Cargo.toml +``` + +#### Cargo Features + +| Feature | Default | Description | +|----------|----------|-------------| +| `vendored` | True | Builds and statically links the bundled `perfetto_c` library. | +| `intrinsics` | False | Enables branch-prediction and fast-path intrinsics (`likely()`, `unlikely()`) to reduce trace overhead. | + +--- + +## Developer Notes + +Regenerating Proto Bindings + +The Rust SDK uses a protoc plugin to generate Rust protozero code: + +```bash +# Setup build with Rust SDK enabled: +gn gen out/rust + +# Build protoc plugin and generate rust code from perfetto `.proto` files: +contrib/rust-sdk/tools/gen_rust_protos out/rust +``` + +This produces `*.pz.rs` files under `contrib/rust-sdk/perfetto/protos`. diff --git a/contrib/rust-sdk/perfetto-derive/Cargo.toml b/contrib/rust-sdk/perfetto-derive/Cargo.toml new file mode 100644 index 00000000000..0652ec3c221 --- /dev/null +++ b/contrib/rust-sdk/perfetto-derive/Cargo.toml @@ -0,0 +1,32 @@ +[package] +edition = "2024" +name = "perfetto-sdk-derive" +version = "0.1.2" +authors = ["David Reveman "] +description = "Procedural macros for Perfetto" +readme = "README.md" +keywords = [ + "tracing", + "perfetto", +] +categories = ["development-tools::profiling"] +license = "Apache-2.0" +homepage = "https://www.perfetto.dev" +repository = "https://github.com/google/perfetto" + +[lib] +proc-macro = true + +[features] +default = ["vendored"] +vendored = ["perfetto-sdk/vendored"] + +[dependencies] +perfetto-sdk = { path = "../perfetto", version = "0.1", default-features = false } +syn = { version = "2", features = ["full"] } +quote = "1.0.8" +proc-macro2 = "1.0" + +[[example]] +name = "derive" +path = "examples/derive.rs" diff --git a/contrib/rust-sdk/perfetto-derive/README.md b/contrib/rust-sdk/perfetto-derive/README.md new file mode 120000 index 00000000000..a1d27b23024 --- /dev/null +++ b/contrib/rust-sdk/perfetto-derive/README.md @@ -0,0 +1 @@ +../perfetto/README.md \ No newline at end of file diff --git a/contrib/rust-sdk/perfetto-derive/examples/derive.rs b/contrib/rust-sdk/perfetto-derive/examples/derive.rs new file mode 100644 index 00000000000..b5682b3f532 --- /dev/null +++ b/contrib/rust-sdk/perfetto-derive/examples/derive.rs @@ -0,0 +1,67 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use perfetto_sdk::{producer::*, track_event::TrackEvent, track_event_categories}; +use perfetto_sdk_derive::tracefn; +use std::error::Error; + +track_event_categories! { + pub mod example_te_ns { + ( "cat1", "Test category 1", [ "tag1" ] ), + ( "cat2", "Test category 2", [ "tag2", "tag3" ] ), + } +} + +use example_te_ns as perfetto_te_ns; + +#[tracefn("cat1", prefix = "parse")] +fn example_function(int_arg: i32, string_arg: String) { + assert_eq!(int_arg, string_arg.parse::().unwrap()); + std::thread::sleep(std::time::Duration::from_secs(1)); +} + +#[derive(Debug)] +struct ExampleData { + field_int32: Option, + field_string: Option, +} + +#[tracefn("cat2", flush = true)] +fn another_example_function(struct_arg: &ExampleData) { + assert_eq!( + struct_arg.field_int32, + struct_arg + .field_string + .clone() + .map(|v| v.parse::().unwrap()) + ); + std::thread::sleep(std::time::Duration::from_secs(1)); +} + +fn main() -> Result<(), Box> { + let producer_args = ProducerInitArgsBuilder::new().backends(Backends::SYSTEM); + Producer::init(producer_args.build()); + TrackEvent::init(); + perfetto_te_ns::register()?; + let mut counter: i32 = 1; + loop { + example_function(counter, counter.to_string()); + another_example_function(&ExampleData { + field_int32: Some(counter), + field_string: Some(counter.to_string()), + }); + std::thread::sleep(std::time::Duration::from_secs(1)); + counter += 1; + } +} diff --git a/contrib/rust-sdk/perfetto-derive/src/lib.rs b/contrib/rust-sdk/perfetto-derive/src/lib.rs new file mode 100644 index 00000000000..1c8d7ed491f --- /dev/null +++ b/contrib/rust-sdk/perfetto-derive/src/lib.rs @@ -0,0 +1,197 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use { + quote::quote, + syn::{Error, Expr, ExprLit, ItemFn, Lit, Token, parse_macro_input, punctuated::Punctuated}, +}; + +#[derive(Debug, Default)] +struct MacroArgs { + category: Option, + prefix: Option, + flush: bool, +} + +impl MacroArgs { + fn from_exprs(exprs: &Punctuated) -> Result { + let mut category: Option = None; + let mut prefix: Option = None; + let mut flush = false; + for expr in exprs { + match expr { + Expr::Lit(ExprLit { + lit: Lit::Str(s), .. + }) => { + if category.is_some() { + return Err(Error::new_spanned(s, "duplicate `category` argument")); + } + category = Some(s.value()); + } + Expr::Assign(assign) => { + if let Expr::Path(path) = &*assign.left { + if path.path.is_ident("prefix") { + if let Expr::Lit(ExprLit { + lit: Lit::Str(s), .. + }) = &*assign.right + { + prefix = Some(s.value()); + } else { + return Err(Error::new_spanned( + &*assign.right, + "expected string literal, e.g., prefix = \"toplevel\"", + )); + } + } else if path.path.is_ident("flush") { + if let Expr::Lit(ExprLit { + lit: Lit::Bool(b), .. + }) = &*assign.right + { + flush = b.value(); + } else { + return Err(Error::new_spanned( + &*assign.right, + "expected boolean literal, e.g., flush = true", + )); + } + } else { + return Err(Error::new_spanned(path, "invalid attribute argument")); + } + } else { + return Err(Error::new_spanned( + &*assign.left, + "invalid left-hand side; expected identifier", + )); + } + } + _ => { + return Err(Error::new_spanned(expr, "unknown attribute expression")); + } + } + } + Ok(Self { + category, + prefix, + flush, + }) + } +} + +/// This provides a helper proc macro to trace function calls. +/// +/// Example: +/// +/// ``` +/// use perfetto_sdk::*; +/// +/// track_event_categories! { +/// pub mod my_derive_te_ns { +/// ( "c1", "Category 1", [] ), +/// } +/// } +/// +/// use my_derive_te_ns as perfetto_te_ns; +/// +/// use perfetto_sdk_derive::tracefn; +/// +/// #[tracefn("c1")] +/// fn atoi(string_arg: String) -> Result { +/// string_arg.parse::() +/// } +/// +/// use std::error::Error; +/// +/// fn main() -> Result<(), Box> { +/// producer::Producer::init( +/// producer::ProducerInitArgsBuilder::new() +/// .backends(producer::Backends::SYSTEM) +/// .build(), +/// ); +/// track_event::TrackEvent::init(); +/// perfetto_te_ns::register()?; +/// let result = atoi(1234.to_string())?; +/// assert_eq!(result, 1234); +/// Ok(()) +/// } +/// ``` +#[proc_macro_attribute] +pub fn tracefn( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let input = parse_macro_input!(item as ItemFn); + let attr_exprs = parse_macro_input!(attr with Punctuated::parse_terminated); + let macro_args = match MacroArgs::from_exprs(&attr_exprs) { + Ok(v) => v, + Err(e) => return e.to_compile_error().into(), + }; + let fn_name = &input.sig.ident; + let fn_args = &input.sig.inputs; + let fn_output = &input.sig.output; + let fn_body = &input.block; + let fn_abi = &input.sig.abi; + let fn_vis = &input.vis; + let fn_attrs = &input.attrs; + let Some(category) = macro_args.category else { + return Error::new_spanned(&input.sig.ident, "missing required `category` argument") + .to_compile_error() + .into(); + }; + let name = if let Some(prefix) = macro_args.prefix { + prefix + &fn_name.to_string() + } else { + fn_name.to_string() + }; + let flush = macro_args.flush; + let args = fn_args.iter().map(|arg| match arg { + syn::FnArg::Typed(pat_type) => { + let arg_name = &pat_type.pat; + quote! { + (stringify!(#arg_name).to_string(), format!("{:?}", #arg_name)) + } + } + _ => panic!("unhandled arg type"), + }); + let result = quote! { + #( #fn_attrs )* + #fn_vis #fn_abi fn #fn_name(#fn_args) #fn_output { + use perfetto_sdk::track_event::*; + use std::os::raw::c_char; + const CATEGORY_INDEX: usize = perfetto_te_ns::category_index(#category); + let is_category_enabled = perfetto_te_ns::is_category_enabled(CATEGORY_INDEX); + if is_category_enabled { + let mut ctx = EventContext::default(); + let args = [#(#args),*]; + for arg in &args { + ctx.add_debug_arg(&arg.0, TrackEventDebugArg::String(&arg.1)); + } + perfetto_te_ns::emit( + CATEGORY_INDEX, + TrackEventType::SliceBegin(concat!(#name, "\0").as_ptr() as *const c_char), + &mut ctx, + ); + } + let result = (|| #fn_body)(); + if is_category_enabled { + let mut ctx = EventContext::default(); + if #flush { + ctx.set_flush(); + } + perfetto_te_ns::emit(CATEGORY_INDEX, TrackEventType::SliceEnd, &mut ctx); + } + result + } + }; + result.into() +} diff --git a/contrib/rust-sdk/perfetto-protos-gpu/Cargo.toml b/contrib/rust-sdk/perfetto-protos-gpu/Cargo.toml new file mode 100644 index 00000000000..2f97780ace9 --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/Cargo.toml @@ -0,0 +1,27 @@ +[package] +edition = "2024" +name = "perfetto-sdk-protos-gpu" +version = "0.1.2" +authors = ["David Reveman "] +description = "Extra protobuf bindings for GPU events" +readme = "README.md" +keywords = [ + "tracing", + "perfetto", +] +categories = ["development-tools::profiling"] +license = "Apache-2.0" +homepage = "https://www.perfetto.dev" +repository = "https://github.com/google/perfetto" + +[features] +default = ["vendored"] +vendored = ["perfetto-sdk/vendored"] + +[dependencies] +perfetto-sdk = { path = "../perfetto", version = "0.1.2", default-features = false } +paste = "1" + +[[example]] +name = "gpu_counters" +path = "examples/gpu_counters.rs" diff --git a/contrib/rust-sdk/perfetto-protos-gpu/README.md b/contrib/rust-sdk/perfetto-protos-gpu/README.md new file mode 120000 index 00000000000..a1d27b23024 --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/README.md @@ -0,0 +1 @@ +../perfetto/README.md \ No newline at end of file diff --git a/contrib/rust-sdk/perfetto-protos-gpu/examples/gpu_counters.rs b/contrib/rust-sdk/perfetto-protos-gpu/examples/gpu_counters.rs new file mode 100644 index 00000000000..a40d6b8583c --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/examples/gpu_counters.rs @@ -0,0 +1,128 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use perfetto_sdk::{ + data_source::*, pb_decoder::*, producer::*, protos::trace::trace_packet::TracePacket, +}; + +use perfetto_sdk_protos_gpu::protos::{ + common::gpu_counter_descriptor::*, config::data_source_config::*, + config::gpu::gpu_counter_config::*, trace::gpu::gpu_counter_event::*, + trace::trace_packet::prelude::*, +}; + +use std::{ + error::Error, + sync::{Arc, Mutex}, + time::{Duration, Instant}, +}; + +#[derive(Debug, Default)] +struct GpuCounterConfig { + counter_period_ns: Option, + counter_ids: Vec, + instrumented_sampling: Option, + fix_gpu_clock: Option, +} + +impl GpuCounterConfig { + fn decode(&mut self, data: &[u8]) -> &mut Self { + use PbDecoderField::*; + const COUNTER_PERIOD_NS_ID: u32 = GpuCounterConfigFieldNumber::CounterPeriodNs as u32; + const COUNTER_IDS_ID: u32 = GpuCounterConfigFieldNumber::CounterIds as u32; + const INSTRUMENTED_SAMPLING_ID: u32 = + GpuCounterConfigFieldNumber::InstrumentedSampling as u32; + const FIX_GPU_CLOCK_ID: u32 = GpuCounterConfigFieldNumber::FixGpuClock as u32; + for item in PbDecoder::new(data) { + match item.as_ref().unwrap_or_else(|e| panic!("Error: {}", e)) { + (COUNTER_PERIOD_NS_ID, Varint(v)) => self.counter_period_ns = Some(*v), + (COUNTER_IDS_ID, Varint(v)) => self.counter_ids.push(*v as u32), + (INSTRUMENTED_SAMPLING_ID, Varint(v)) => self.instrumented_sampling = Some(*v != 0), + (FIX_GPU_CLOCK_ID, Varint(v)) => self.fix_gpu_clock = Some(*v != 0), + _ => println!("WARNING: unknown GpuCounterConfig field: {:?}", item), + } + } + self + } +} + +fn main() -> Result<(), Box> { + const GPU_COUNTER_CONFIG_ID: u32 = DataSourceConfigExtFieldNumber::GpuCounterConfig as u32; + let producer_args = ProducerInitArgsBuilder::new().backends(Backends::SYSTEM); + Producer::init(producer_args.build()); + let mut data_source = DataSource::new(); + let gpu_counter_config = Arc::new(Mutex::new(GpuCounterConfig::default())); + let gpu_counter_config_for_setup = Arc::clone(&gpu_counter_config); + let data_source_args = DataSourceArgsBuilder::new().on_setup(move |inst_id, config, _args| { + let mut gpu_counter_config = gpu_counter_config_for_setup.lock().unwrap(); + for item in PbDecoder::new(config) { + if let (GPU_COUNTER_CONFIG_ID, PbDecoderField::Delimited(value)) = + item.unwrap_or_else(|e| panic!("Error: {}", e)) + { + gpu_counter_config.decode(value); + } + } + println!( + "OnSetup id: {} gpu_conter_config: {:?}", + inst_id, gpu_counter_config + ); + }); + let start_time = Instant::now(); + data_source.register("gpu.counters.rig", data_source_args.build())?; + loop { + data_source.trace(|ctx: &mut TraceContext| { + // Fixed set of counters: sin, cos, tan. + const COUNTER_IDS: [u32; 3] = [1, 2, 3]; + let elapsed_secs = start_time.elapsed().as_secs_f64(); + ctx.with_incremental_state(|ctx: &mut TraceContext, state| { + let was_cleared = std::mem::replace(&mut state.was_cleared, false); + ctx.add_packet(|packet: &mut TracePacket| { + packet.set_gpu_counter_event(|event: &mut GpuCounterEvent| { + for i in COUNTER_IDS.iter() { + event.set_counters(|counter: &mut GpuCounter| { + counter.set_counter_id(*i); + match i { + 1 => counter.set_double_value(elapsed_secs.sin()), + 2 => counter.set_double_value(elapsed_secs.cos()), + _ => counter.set_double_value(elapsed_secs.tan()), + }; + }); + } + if was_cleared { + event.set_counter_descriptor(|desc: &mut GpuCounterDescriptor| { + for i in COUNTER_IDS.iter() { + desc.set_specs(|desc: &mut GpuCounterSpec| { + desc.set_counter_id(*i); + match i { + 1 => desc.set_name("sin"), + 2 => desc.set_name("cos"), + _ => desc.set_name("tan"), + }; + }); + } + }); + } + }); + }); + }); + }); + let counter_period = gpu_counter_config + .lock() + .unwrap() + .counter_period_ns + .map(Duration::from_nanos) + .unwrap_or(Duration::from_secs(1)); + std::thread::sleep(counter_period); + } +} diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/lib.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/lib.rs new file mode 100644 index 00000000000..337c2400334 --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/lib.rs @@ -0,0 +1,25 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Re-export pb_msg macro from this crate. +pub use perfetto_sdk::pb_msg; + +/// Re-export pb_msg_ext macro from this crate. +pub use perfetto_sdk::pb_msg_ext; + +/// Re-export pb_enum macro from this crate. +pub use perfetto_sdk::pb_enum; + +/// Protobuf bindings module. +pub mod protos; diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/common/data_source_descriptor.pz.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/common/data_source_descriptor.pz.rs new file mode 100644 index 00000000000..3f7e8c32f1d --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/common/data_source_descriptor.pz.rs @@ -0,0 +1,31 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Manually generated with bindings for an extra set of +// DataSourceDescriptor fields. + +use crate::pb_msg; +use crate::pb_msg_ext; +use crate::protos::common::gpu_counter_descriptor::*; + +use perfetto_sdk::protos::common::data_source_descriptor::DataSourceDescriptor; + +pb_msg_ext!(DataSourceDescriptor { + gpu_counter_descriptor: GpuCounterDescriptor, msg, 5, +}); + +/// Import this to use the extra `DataSourceDescriptor` fields. +pub mod prelude { + pub use super::DataSourceDescriptorExt; +} diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/common/gpu_counter_descriptor.pz.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/common/gpu_counter_descriptor.pz.rs new file mode 100644 index 00000000000..7bf73081813 --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/common/gpu_counter_descriptor.pz.rs @@ -0,0 +1,103 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; + +pb_enum!(GpuCounterDescriptorGpuCounterGroup { + UNCLASSIFIED: 0, + SYSTEM: 1, + VERTICES: 2, + FRAGMENTS: 3, + PRIMITIVES: 4, + MEMORY: 5, + COMPUTE: 6, + RAY_TRACING: 7, +}); + +pb_enum!(GpuCounterDescriptorMeasureUnit { + NONE: 0, + BIT: 1, + KILOBIT: 2, + MEGABIT: 3, + GIGABIT: 4, + TERABIT: 5, + PETABIT: 6, + BYTE: 7, + KILOBYTE: 8, + MEGABYTE: 9, + GIGABYTE: 10, + TERABYTE: 11, + PETABYTE: 12, + HERTZ: 13, + KILOHERTZ: 14, + MEGAHERTZ: 15, + GIGAHERTZ: 16, + TERAHERTZ: 17, + PETAHERTZ: 18, + NANOSECOND: 19, + MICROSECOND: 20, + MILLISECOND: 21, + SECOND: 22, + MINUTE: 23, + HOUR: 24, + VERTEX: 25, + PIXEL: 26, + TRIANGLE: 27, + PRIMITIVE: 38, + FRAGMENT: 39, + MILLIWATT: 28, + WATT: 29, + KILOWATT: 30, + JOULE: 31, + VOLT: 32, + AMPERE: 33, + CELSIUS: 34, + FAHRENHEIT: 35, + KELVIN: 36, + PERCENT: 37, + INSTRUCTION: 40, +}); + +pb_msg!(GpuCounterDescriptor { + specs: GpuCounterSpec, msg, 1, + blocks: GpuCounterBlock, msg, 2, + min_sampling_period_ns: u64, primitive, 3, + max_sampling_period_ns: u64, primitive, 4, + supports_instrumented_sampling: bool, primitive, 5, +}); + +pb_msg!(GpuCounterBlock { + block_id: u32, primitive, 1, + block_capacity: u32, primitive, 2, + name: String, primitive, 3, + description: String, primitive, 4, + counter_ids: u32, primitive, 5, +}); + +pb_msg!(GpuCounterSpec { + counter_id: u32, primitive, 1, + name: String, primitive, 2, + description: String, primitive, 3, + int_peak_value: i64, primitive, 5, + double_peak_value: f64, primitive, 6, + numerator_units: GpuCounterDescriptorMeasureUnit, enum, 7, + denominator_units: GpuCounterDescriptorMeasureUnit, enum, 8, + select_by_default: bool, primitive, 9, + groups: GpuCounterDescriptorGpuCounterGroup, enum, 10, +}); diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/common/mod.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/common/mod.rs new file mode 100644 index 00000000000..10de9d3ad06 --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/common/mod.rs @@ -0,0 +1,24 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the gen_rust_protos script. +// DO NOT EDIT. + +/// `data_source_descriptor` protos. +#[path = "data_source_descriptor.pz.rs"] +pub mod data_source_descriptor; + +/// `gpu_counter_descriptor` protos. +#[path = "gpu_counter_descriptor.pz.rs"] +pub mod gpu_counter_descriptor; diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/data_source_config.pz.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/data_source_config.pz.rs new file mode 100644 index 00000000000..13a3405e7b4 --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/data_source_config.pz.rs @@ -0,0 +1,35 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Manually generated with bindings for an extra set of DataSourceConfig +// fields. + +use crate::pb_msg; +use crate::pb_msg_ext; +use crate::protos::config::gpu::gpu_counter_config::*; +use crate::protos::config::gpu::gpu_renderstages_config::*; +use crate::protos::config::gpu::vulkan_memory_config::*; + +use perfetto_sdk::protos::config::data_source_config::DataSourceConfig; + +pb_msg_ext!(DataSourceConfig { + gpu_counter_config: GpuCounterConfig, msg, 108, + vulkan_memory_config: VulkanMemoryConfig, msg, 112, + gpu_renderstages_config: GpuRenderStagesConfig, msg, 133, +}); + +/// Import this to use the extra `DataSourceConfig` fields. +pub mod prelude { + pub use super::DataSourceConfigExt; +} diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/gpu/gpu_counter_config.pz.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/gpu/gpu_counter_config.pz.rs new file mode 100644 index 00000000000..cf8774cd71f --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/gpu/gpu_counter_config.pz.rs @@ -0,0 +1,26 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(GpuCounterConfig { + counter_period_ns: u64, primitive, 1, + counter_ids: u32, primitive, 2, + instrumented_sampling: bool, primitive, 3, + fix_gpu_clock: bool, primitive, 4, +}); diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/gpu/gpu_renderstages_config.pz.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/gpu/gpu_renderstages_config.pz.rs new file mode 100644 index 00000000000..3be8cf52c3d --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/gpu/gpu_renderstages_config.pz.rs @@ -0,0 +1,25 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(GpuRenderStagesConfig { + full_loadstore: bool, primitive, 1, + low_overhead: bool, primitive, 2, + trace_metrics: String, primitive, 3, +}); diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/gpu/mod.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/gpu/mod.rs new file mode 100644 index 00000000000..564add8c5c0 --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/gpu/mod.rs @@ -0,0 +1,28 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the gen_rust_protos script. +// DO NOT EDIT. + +/// `gpu_counter_config` protos. +#[path = "gpu_counter_config.pz.rs"] +pub mod gpu_counter_config; + +/// `gpu_renderstages_config` protos. +#[path = "gpu_renderstages_config.pz.rs"] +pub mod gpu_renderstages_config; + +/// `vulkan_memory_config` protos. +#[path = "vulkan_memory_config.pz.rs"] +pub mod vulkan_memory_config; diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/gpu/vulkan_memory_config.pz.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/gpu/vulkan_memory_config.pz.rs new file mode 100644 index 00000000000..b1991f6fc2d --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/gpu/vulkan_memory_config.pz.rs @@ -0,0 +1,24 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(VulkanMemoryConfig { + track_driver_memory_usage: bool, primitive, 1, + track_device_memory_usage: bool, primitive, 2, +}); diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/mod.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/mod.rs new file mode 100644 index 00000000000..2b5245668fb --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/config/mod.rs @@ -0,0 +1,23 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the gen_rust_protos script. +// DO NOT EDIT. + +/// `data_source_config` protos. +#[path = "data_source_config.pz.rs"] +pub mod data_source_config; + +/// `gpu` protos. +pub mod gpu; diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/mod.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/mod.rs new file mode 100644 index 00000000000..bd249c93acc --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/mod.rs @@ -0,0 +1,23 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// `common` protobufs. +pub mod common; + +/// `config` protobufs. +pub mod config; + +/// `trace` protobufs. +#[allow(clippy::module_inception)] +pub mod trace; diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/gpu_counter_event.pz.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/gpu_counter_event.pz.rs new file mode 100644 index 00000000000..76af9c54367 --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/gpu_counter_event.pz.rs @@ -0,0 +1,32 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; +use crate::protos::common::gpu_counter_descriptor::*; + +pb_msg!(GpuCounterEvent { + counter_descriptor: GpuCounterDescriptor, msg, 1, + counters: GpuCounter, msg, 2, + gpu_id: i32, primitive, 3, +}); + +pb_msg!(GpuCounter { + counter_id: u32, primitive, 1, + int_value: i64, primitive, 2, + double_value: f64, primitive, 3, +}); diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/gpu_log.pz.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/gpu_log.pz.rs new file mode 100644 index 00000000000..a911a3453c8 --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/gpu_log.pz.rs @@ -0,0 +1,35 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; + +pb_enum!(GpuLogSeverity { + LOG_SEVERITY_UNSPECIFIED: 0, + LOG_SEVERITY_VERBOSE: 1, + LOG_SEVERITY_DEBUG: 2, + LOG_SEVERITY_INFO: 3, + LOG_SEVERITY_WARNING: 4, + LOG_SEVERITY_ERROR: 5, +}); + +pb_msg!(GpuLog { + severity: GpuLogSeverity, enum, 1, + tag: String, primitive, 2, + log_message: String, primitive, 3, +}); diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/gpu_render_stage_event.pz.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/gpu_render_stage_event.pz.rs new file mode 100644 index 00000000000..c5c2d074179 --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/gpu_render_stage_event.pz.rs @@ -0,0 +1,86 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; + +pb_enum!(InternedGpuRenderStageSpecificationRenderStageCategory { + OTHER: 0, + GRAPHICS: 1, + COMPUTE: 2, +}); + +pb_enum!(InternedGraphicsContextApi { + UNDEFINED: 0, + OPEN_GL: 1, + VULKAN: 2, + OPEN_CL: 3, +}); + +pb_msg!(InternedGpuRenderStageSpecification { + iid: u64, primitive, 1, + name: String, primitive, 2, + description: String, primitive, 3, + category: InternedGpuRenderStageSpecificationRenderStageCategory, enum, 4, +}); + +pb_msg!(InternedGraphicsContext { + iid: u64, primitive, 1, + pid: i32, primitive, 2, + api: InternedGraphicsContextApi, enum, 3, +}); + +pb_msg!(GpuRenderStageEvent { + event_id: u64, primitive, 1, + duration: u64, primitive, 2, + hw_queue_iid: u64, primitive, 13, + stage_iid: u64, primitive, 14, + gpu_id: i32, primitive, 11, + context: u64, primitive, 5, + render_target_handle: u64, primitive, 8, + submission_id: u32, primitive, 10, + extra_data: ExtraData, msg, 6, + render_pass_handle: u64, primitive, 9, + render_pass_instance_id: u64, primitive, 16, + render_subpass_index_mask: u64, primitive, 15, + command_buffer_handle: u64, primitive, 12, + specifications: Specifications, msg, 7, + hw_queue_id: i32, primitive, 3, + stage_id: i32, primitive, 4, +}); + +pb_msg!(Specifications { + context_spec: ContextSpec, msg, 1, + hw_queue: Description, msg, 2, + stage: Description, msg, 3, +}); + +pb_msg!(Description { + name: String, primitive, 1, + description: String, primitive, 2, +}); + +pb_msg!(ContextSpec { + context: u64, primitive, 1, + pid: i32, primitive, 2, +}); + +pb_msg!(ExtraData { + name: String, primitive, 1, + value: String, primitive, 2, +}); diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/mod.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/mod.rs new file mode 100644 index 00000000000..3745760d3ff --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/mod.rs @@ -0,0 +1,36 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the gen_rust_protos script. +// DO NOT EDIT. + +/// `gpu_counter_event` protos. +#[path = "gpu_counter_event.pz.rs"] +pub mod gpu_counter_event; + +/// `gpu_log` protos. +#[path = "gpu_log.pz.rs"] +pub mod gpu_log; + +/// `gpu_render_stage_event` protos. +#[path = "gpu_render_stage_event.pz.rs"] +pub mod gpu_render_stage_event; + +/// `vulkan_api_event` protos. +#[path = "vulkan_api_event.pz.rs"] +pub mod vulkan_api_event; + +/// `vulkan_memory_event` protos. +#[path = "vulkan_memory_event.pz.rs"] +pub mod vulkan_memory_event; diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/vulkan_api_event.pz.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/vulkan_api_event.pz.rs new file mode 100644 index 00000000000..b290671fd66 --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/vulkan_api_event.pz.rs @@ -0,0 +1,41 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(VulkanApiEvent { + vk_debug_utils_object_name: VkDebugUtilsObjectName, msg, 1, + vk_queue_submit: VkQueueSubmit, msg, 2, +}); + +pb_msg!(VkQueueSubmit { + duration_ns: u64, primitive, 1, + pid: u32, primitive, 2, + tid: u32, primitive, 3, + vk_queue: u64, primitive, 4, + vk_command_buffers: u64, primitive, 5, + submission_id: u32, primitive, 6, +}); + +pb_msg!(VkDebugUtilsObjectName { + pid: u32, primitive, 1, + vk_device: u64, primitive, 2, + object_type: i32, primitive, 3, + object: u64, primitive, 4, + object_name: String, primitive, 5, +}); diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/vulkan_memory_event.pz.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/vulkan_memory_event.pz.rs new file mode 100644 index 00000000000..3dfcec83848 --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/gpu/vulkan_memory_event.pz.rs @@ -0,0 +1,71 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; + +pb_enum!(VulkanMemoryEventSource { + SOURCE_UNSPECIFIED: 0, + SOURCE_DRIVER: 1, + SOURCE_DEVICE: 2, + SOURCE_DEVICE_MEMORY: 3, + SOURCE_BUFFER: 4, + SOURCE_IMAGE: 5, +}); + +pb_enum!(VulkanMemoryEventOperation { + OP_UNSPECIFIED: 0, + OP_CREATE: 1, + OP_DESTROY: 2, + OP_BIND: 3, + OP_DESTROY_BOUND: 4, + OP_ANNOTATIONS: 5, +}); + +pb_enum!(VulkanMemoryEventAllocationScope { + SCOPE_UNSPECIFIED: 0, + SCOPE_COMMAND: 1, + SCOPE_OBJECT: 2, + SCOPE_CACHE: 3, + SCOPE_DEVICE: 4, + SCOPE_INSTANCE: 5, +}); + +pb_msg!(VulkanMemoryEvent { + source: VulkanMemoryEventSource, enum, 1, + operation: VulkanMemoryEventOperation, enum, 2, + timestamp: i64, primitive, 3, + pid: u32, primitive, 4, + memory_address: u64, primitive, 5, + memory_size: u64, primitive, 6, + caller_iid: u64, primitive, 7, + allocation_scope: VulkanMemoryEventAllocationScope, enum, 8, + annotations: VulkanMemoryEventAnnotation, msg, 9, + device: u64, primitive, 16, + device_memory: u64, primitive, 17, + memory_type: u32, primitive, 18, + heap: u32, primitive, 19, + object_handle: u64, primitive, 20, +}); + +pb_msg!(VulkanMemoryEventAnnotation { + key_iid: u64, primitive, 1, + int_value: i64, primitive, 2, + double_value: f64, primitive, 3, + string_iid: u64, primitive, 4, +}); diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/interned_data/interned_data.pz.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/interned_data/interned_data.pz.rs new file mode 100644 index 00000000000..c4fe7845dcf --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/interned_data/interned_data.pz.rs @@ -0,0 +1,33 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Manually generated with bindings for an extra set of InternedData fields. + +use crate::pb_msg; +use crate::pb_msg_ext; +use crate::protos::trace::gpu::gpu_render_stage_event::*; + +use perfetto_sdk::protos::trace::interned_data::interned_data::InternedData; +use perfetto_sdk::protos::trace::profiling::profile_common::InternedString; + +pb_msg_ext!(InternedData { + vulkan_memory_keys: InternedString, msg, 22, + graphics_contexts: InternedGraphicsContext, msg, 23, + gpu_specifications: InternedGpuRenderStageSpecification, msg, 24, +}); + +/// Import this to use the extra `InternedData` fields. +pub mod prelude { + pub use super::InternedDataExt; +} diff --git a/ui/src/assets/widgets/virtual_scroll_container.scss b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/interned_data/mod.rs similarity index 75% rename from ui/src/assets/widgets/virtual_scroll_container.scss rename to contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/interned_data/mod.rs index cae6b755d72..402955c987d 100644 --- a/ui/src/assets/widgets/virtual_scroll_container.scss +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/interned_data/mod.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2023 The Android Open Source Project +// Copyright (C) 2025 Rivos Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -@import "../theme"; +// Autogenerated by the gen_rust_protos script. +// DO NOT EDIT. -.pf-virtual-scroll-container { - overflow: auto; - height: 100%; -} +/// `interned_data` protos. +#[path = "interned_data.pz.rs"] +pub mod interned_data; diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/mod.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/mod.rs new file mode 100644 index 00000000000..0fef7d02b80 --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/mod.rs @@ -0,0 +1,26 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the gen_rust_protos script. +// DO NOT EDIT. + +/// `gpu` protos. +pub mod gpu; + +/// `interned_data` protos. +pub mod interned_data; + +/// `trace_packet` protos. +#[path = "trace_packet.pz.rs"] +pub mod trace_packet; diff --git a/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/trace_packet.pz.rs b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/trace_packet.pz.rs new file mode 100644 index 00000000000..87f07e6a4b1 --- /dev/null +++ b/contrib/rust-sdk/perfetto-protos-gpu/src/protos/trace/trace_packet.pz.rs @@ -0,0 +1,38 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Manually generated with bindings for an extra set of TracePacket fields. + +use crate::pb_msg; +use crate::pb_msg_ext; +use crate::protos::trace::gpu::gpu_counter_event::*; +use crate::protos::trace::gpu::gpu_log::*; +use crate::protos::trace::gpu::gpu_render_stage_event::*; +use crate::protos::trace::gpu::vulkan_api_event::*; +use crate::protos::trace::gpu::vulkan_memory_event::*; + +use perfetto_sdk::protos::trace::trace_packet::TracePacket; + +pb_msg_ext!(TracePacket { + gpu_counter_event: GpuCounterEvent, msg, 52, + gpu_render_stage_event: GpuRenderStageEvent, msg, 53, + vulkan_memory_event: VulkanMemoryEvent, msg, 62, + gpu_log: GpuLog, msg, 63, + vulkan_api_event: VulkanApiEvent, msg, 65, +}); + +/// Import this to use the extra `TracePacket` fields. +pub mod prelude { + pub use super::TracePacketExt; +} diff --git a/contrib/rust-sdk/perfetto-sys/Cargo.toml b/contrib/rust-sdk/perfetto-sys/Cargo.toml new file mode 100644 index 00000000000..9373a1c5f5f --- /dev/null +++ b/contrib/rust-sdk/perfetto-sys/Cargo.toml @@ -0,0 +1,28 @@ +[package] +edition = "2024" +name = "perfetto-sdk-sys" +version = "0.1.1" +authors = ["David Reveman "] +build = "build.rs" +description = "Low-level FFI bindings for Perfetto" +readme = "README.md" +keywords = [ + "tracing", + "perfetto", + "ffi", +] +categories = ["external-ffi-bindings"] +license = "Apache-2.0" +homepage = "https://www.perfetto.dev" +repository = "https://github.com/google/perfetto" +exclude = ["include/perfetto/public/abi/BUILD.gn"] + +[features] +default = ["vendored"] +vendored = [] + +[dependencies] + +[build-dependencies] +bindgen = "0.71" +cc = "1.1" diff --git a/contrib/rust-sdk/perfetto-sys/README.md b/contrib/rust-sdk/perfetto-sys/README.md new file mode 120000 index 00000000000..a1d27b23024 --- /dev/null +++ b/contrib/rust-sdk/perfetto-sys/README.md @@ -0,0 +1 @@ +../perfetto/README.md \ No newline at end of file diff --git a/contrib/rust-sdk/perfetto-sys/build.rs b/contrib/rust-sdk/perfetto-sys/build.rs new file mode 100644 index 00000000000..6039f8228a0 --- /dev/null +++ b/contrib/rust-sdk/perfetto-sys/build.rs @@ -0,0 +1,94 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::env; +use std::path::{Path, PathBuf}; + +fn main() { + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let include_path = env::var("PERFETTO_SYS_INCLUDE_DIR").unwrap_or_else(|_| { + PathBuf::from(&crate_dir) + .join("include") + .display() + .to_string() + }); + + // Vendored bindings can be generated using: + if cfg!(feature = "vendored") { + let source_file = Path::new("libperfetto_c/perfetto_c.cc"); + if !source_file.exists() { + panic!( + "\n\ + ⌠Missing amalgamated source file: {}.\n\n\ + To fix this, run:\n\ + \n\ + $ tools/gen_amalgamated --gn_args \"is_debug=false \ + is_clang=true use_custom_libcxx=false \ + enable_perfetto_ipc=true \ + perfetto_enable_git_rev_version_header=true \ + is_perfetto_build_generator=true \ + enable_perfetto_zlib=false\" \ + --output contrib/rust-sdk/perfetto-sys/libperfetto_c/perfetto_c \ + //src/shared_lib:libperfetto_c\n\ + \n\ + 💡 Tip: invoke cargo with --no-default-features to use an external library\n", + source_file.display() + ); + } + let mut build = cc::Build::new(); + // `PERFETTO_SYS_LIB_DEBUG=true` enables debug build of the shared library. + let lib_debug = env::var("PERFETTO_SYS_LIB_DEBUG").ok().as_deref() == Some("true"); + if !lib_debug { + build.define("NDEBUG", None); + } + build + .cpp(true) + .file(source_file) + .std("c++17") + .debug(lib_debug) + .warnings(false) + .compile("libperfetto_c"); + println!("cargo:rerun-if-changed=libperfetto_c/perfetto_c.cc"); + println!("cargo:rerun-if-changed=libperfetto_c/perfetto_c.h"); + println!("cargo:rerun-if-env-changed=PERFETTO_SYS_LIB_DEBUG"); + } else { + let lib_path = env::var("PERFETTO_SYS_LIB_DIR") + .expect("Set PERFETTO_SYS_LIB_DIR for non-vendored builds"); + println!("cargo:rustc-link-search=native={}", lib_path); + println!("cargo:rustc-link-lib=dylib=perfetto_c"); + println!("cargo:rerun-if-env-changed=PERFETTO_SYS_LIB_DIR"); + } + + println!("cargo:rerun-if-env-changed=PERFETTO_SYS_INCLUDE_DIR"); + println!("cargo:rerun-if-changed=wrapper.h"); + + let bindings = bindgen::Builder::default() + .header("wrapper.h") + .clang_arg(format!("-I{}", include_path)) + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .allowlist_type("(?:Perfetto|perfetto).*") + .allowlist_function("(?:Perfetto|perfetto).*") + .allowlist_var("(?:PERFETTO|perfetto)_.*") + .layout_tests(false) + .derive_default(false) + .derive_eq(false) + .blocklist_type("max_align_t") + .generate() + .expect("Unable to generate bindings"); + + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} diff --git a/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/atomic.h b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/atomic.h new file mode 120000 index 00000000000..a7250416e37 --- /dev/null +++ b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/atomic.h @@ -0,0 +1 @@ +../../../../../../../include/perfetto/public/abi/atomic.h \ No newline at end of file diff --git a/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/backend_type.h b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/backend_type.h new file mode 120000 index 00000000000..b5608f27108 --- /dev/null +++ b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/backend_type.h @@ -0,0 +1 @@ +../../../../../../../include/perfetto/public/abi/backend_type.h \ No newline at end of file diff --git a/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/data_source_abi.h b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/data_source_abi.h new file mode 120000 index 00000000000..2771145a4e2 --- /dev/null +++ b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/data_source_abi.h @@ -0,0 +1 @@ +../../../../../../../include/perfetto/public/abi/data_source_abi.h \ No newline at end of file diff --git a/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/export.h b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/export.h new file mode 120000 index 00000000000..ee9729e7512 --- /dev/null +++ b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/export.h @@ -0,0 +1 @@ +../../../../../../../include/perfetto/public/abi/export.h \ No newline at end of file diff --git a/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/heap_buffer.h b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/heap_buffer.h new file mode 120000 index 00000000000..bc95c79363b --- /dev/null +++ b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/heap_buffer.h @@ -0,0 +1 @@ +../../../../../../../include/perfetto/public/abi/heap_buffer.h \ No newline at end of file diff --git a/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/pb_decoder_abi.h b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/pb_decoder_abi.h new file mode 120000 index 00000000000..882a5d77d98 --- /dev/null +++ b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/pb_decoder_abi.h @@ -0,0 +1 @@ +../../../../../../../include/perfetto/public/abi/pb_decoder_abi.h \ No newline at end of file diff --git a/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/producer_abi.h b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/producer_abi.h new file mode 120000 index 00000000000..83017498af1 --- /dev/null +++ b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/producer_abi.h @@ -0,0 +1 @@ +../../../../../../../include/perfetto/public/abi/producer_abi.h \ No newline at end of file diff --git a/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/stream_writer_abi.h b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/stream_writer_abi.h new file mode 120000 index 00000000000..27d0c8ddb6e --- /dev/null +++ b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/stream_writer_abi.h @@ -0,0 +1 @@ +../../../../../../../include/perfetto/public/abi/stream_writer_abi.h \ No newline at end of file diff --git a/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/tracing_session_abi.h b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/tracing_session_abi.h new file mode 120000 index 00000000000..e327b4455c7 --- /dev/null +++ b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/tracing_session_abi.h @@ -0,0 +1 @@ +../../../../../../../include/perfetto/public/abi/tracing_session_abi.h \ No newline at end of file diff --git a/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/track_event_abi.h b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/track_event_abi.h new file mode 120000 index 00000000000..6195d22d31f --- /dev/null +++ b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/track_event_abi.h @@ -0,0 +1 @@ +../../../../../../../include/perfetto/public/abi/track_event_abi.h \ No newline at end of file diff --git a/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/track_event_hl_abi.h b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/track_event_hl_abi.h new file mode 120000 index 00000000000..64e6d4d5844 --- /dev/null +++ b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/track_event_hl_abi.h @@ -0,0 +1 @@ +../../../../../../../include/perfetto/public/abi/track_event_hl_abi.h \ No newline at end of file diff --git a/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/track_event_ll_abi.h b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/track_event_ll_abi.h new file mode 120000 index 00000000000..f0a349278f8 --- /dev/null +++ b/contrib/rust-sdk/perfetto-sys/include/perfetto/public/abi/track_event_ll_abi.h @@ -0,0 +1 @@ +../../../../../../../include/perfetto/public/abi/track_event_ll_abi.h \ No newline at end of file diff --git a/contrib/rust-sdk/perfetto-sys/src/lib.rs b/contrib/rust-sdk/perfetto-sys/src/lib.rs new file mode 100644 index 00000000000..2fa35f7fbc9 --- /dev/null +++ b/contrib/rust-sdk/perfetto-sys/src/lib.rs @@ -0,0 +1,74 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Safety +//! +//! This crate provides raw FFI bindings to **libperfetto_c**. +//! - All `extern "C"` functions are `unsafe` to call. Callers must uphold the +//! preconditions documented by the upstream C headers/manual. +//! - Pointers must obey C rules: correct lifetimes, alignment, nullability, +//! and initialization. Opaque handles follow the library’s ownership rules. +//! - Unless explicitly noted, functions are **not** thread-safe, **not** +//! async-signal-safe, and may not be reentrant. +//! - Struct layouts with `#[repr(C)]` mirror the C ABI of the linked version. +//! Using headers and a different linked binary may cause UB. +//! - This crate does not validate string encodings or lengths; pass NUL-terminated +//! buffers as required by the C API. +//! +//! Prefer the higher-level safe wrapper crate `perfetto`. + +#![allow(non_camel_case_types)] +#![allow(non_upper_case_globals)] +#![allow(non_snake_case)] + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Once; + + static INIT_TEST_ENVIRONMENT: Once = Once::new(); + + fn setup_test_environment() { + INIT_TEST_ENVIRONMENT.call_once(|| unsafe { + let backend_args = PerfettoProducerBackendInitArgsCreate(); + PerfettoProducerSystemInit(backend_args); + PerfettoProducerBackendInitArgsDestroy(backend_args); + }); + } + + #[test] + fn track_event_init() { + setup_test_environment(); + unsafe { + PerfettoTeInit(); + #[allow(static_mut_refs)] + let te_process_track_uuid = perfetto_te_process_track_uuid; + assert_ne!(te_process_track_uuid, 0); + } + } + + #[test] + fn data_source_init() { + setup_test_environment(); + let mut enabled: *mut bool = std::ptr::null_mut(); + let success = unsafe { + let ds = PerfettoDsImplCreate(); + PerfettoDsImplRegister(ds, &raw mut enabled, std::ptr::null_mut(), 0) + }; + assert!(success); + assert_ne!(enabled, std::ptr::null_mut()); + } +} diff --git a/contrib/rust-sdk/perfetto-sys/wrapper.h b/contrib/rust-sdk/perfetto-sys/wrapper.h new file mode 100644 index 00000000000..2cd3aec775d --- /dev/null +++ b/contrib/rust-sdk/perfetto-sys/wrapper.h @@ -0,0 +1,24 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/contrib/rust-sdk/perfetto/Cargo.toml b/contrib/rust-sdk/perfetto/Cargo.toml new file mode 100644 index 00000000000..e07cf34cd1e --- /dev/null +++ b/contrib/rust-sdk/perfetto/Cargo.toml @@ -0,0 +1,34 @@ +[package] +edition = "2024" +name = "perfetto-sdk" +version = "0.1.2" +authors = ["David Reveman "] +description = "Bindings for the Perfetto tracing framework" +readme = "README.md" +keywords = [ + "tracing", + "perfetto", +] +categories = ["development-tools::profiling"] +license = "Apache-2.0" +homepage = "https://www.perfetto.dev" +repository = "https://github.com/google/perfetto" + +[features] +default = ["vendored"] +intrinsics = [] +vendored = ["perfetto-sdk-sys/vendored"] + +[dependencies] +perfetto-sdk-sys = { path = "../perfetto-sys", version = "0.1", default-features = false } +bitflags = "2" +paste = "1" +thiserror = "1" + +[[example]] +name = "track_event" +path = "examples/track_event.rs" + +[[example]] +name = "data_source" +path = "examples/data_source.rs" diff --git a/contrib/rust-sdk/perfetto/README.md b/contrib/rust-sdk/perfetto/README.md new file mode 100644 index 00000000000..ee5be88c488 --- /dev/null +++ b/contrib/rust-sdk/perfetto/README.md @@ -0,0 +1,3 @@ +# perfetto + +Perfetto bindings for the Rust programming language. diff --git a/contrib/rust-sdk/perfetto/examples/data_source.rs b/contrib/rust-sdk/perfetto/examples/data_source.rs new file mode 100644 index 00000000000..95855f89c8a --- /dev/null +++ b/contrib/rust-sdk/perfetto/examples/data_source.rs @@ -0,0 +1,238 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use perfetto_sdk::{ + data_source::*, + pb_decoder::*, + producer::*, + protos::{ + config::{data_source_config::*, test_config::*}, + trace::{ + test_event::*, + trace_packet::TracePacket, + track_event::debug_annotation::{DebugAnnotation, NestedValue, NestedValueNestedType}, + }, + }, +}; +use std::{ + error::Error, + sync::{Arc, Mutex}, +}; + +#[derive(Debug, Default)] +struct DummyFields { + field_uint32: Option, + field_int32: Option, + field_uint64: Option, + field_int64: Option, + field_fixed64: Option, + field_sfixed64: Option, + field_fixed32: Option, + field_sfixed32: Option, + field_double: Option, + field_float: Option, + field_sint64: Option, + field_sint32: Option, + field_string: Option, + field_bytes: Vec, +} + +impl DummyFields { + fn decode(&mut self, data: &[u8]) -> &mut Self { + use PbDecoderField::*; + const UINT32_ID: u32 = DummyFieldsFieldNumber::FieldUint32 as u32; + const INT32_ID: u32 = DummyFieldsFieldNumber::FieldInt32 as u32; + const UINT64_ID: u32 = DummyFieldsFieldNumber::FieldUint64 as u32; + const INT64_ID: u32 = DummyFieldsFieldNumber::FieldInt64 as u32; + const FIXED64_ID: u32 = DummyFieldsFieldNumber::FieldFixed64 as u32; + const SFIXED64_ID: u32 = DummyFieldsFieldNumber::FieldSfixed64 as u32; + const FIXED32_ID: u32 = DummyFieldsFieldNumber::FieldFixed32 as u32; + const SFIXED32_ID: u32 = DummyFieldsFieldNumber::FieldSfixed32 as u32; + const DOUBLE_ID: u32 = DummyFieldsFieldNumber::FieldDouble as u32; + const FLOAT_ID: u32 = DummyFieldsFieldNumber::FieldFloat as u32; + const SINT64_ID: u32 = DummyFieldsFieldNumber::FieldSint64 as u32; + const SINT32_ID: u32 = DummyFieldsFieldNumber::FieldSint32 as u32; + const STRING_ID: u32 = DummyFieldsFieldNumber::FieldString as u32; + const BYTES_ID: u32 = DummyFieldsFieldNumber::FieldBytes as u32; + for item in PbDecoder::new(data) { + match item.as_ref().unwrap_or_else(|e| panic!("Error: {}", e)) { + (UINT32_ID, Varint(v)) => self.field_uint32 = Some(*v as u32), + (INT32_ID, Varint(v)) => self.field_int32 = Some(*v as i32), + (UINT64_ID, Varint(v)) => self.field_uint64 = Some(*v), + (INT64_ID, Varint(v)) => self.field_int64 = Some(*v as i64), + (FIXED64_ID, Fixed64(v)) => self.field_fixed64 = Some(*v), + (SFIXED64_ID, Fixed64(v)) => self.field_sfixed64 = Some(*v as i64), + (FIXED32_ID, Fixed32(v)) => self.field_fixed32 = Some(*v), + (SFIXED32_ID, Fixed32(v)) => self.field_sfixed32 = Some(*v as i32), + (DOUBLE_ID, Fixed64(v)) => self.field_double = Some(f64::from_bits(*v)), + (FLOAT_ID, Fixed32(v)) => self.field_float = Some(f32::from_bits(*v)), + (SINT64_ID, Varint(v)) => self.field_sint64 = Some(*v as i64), + (SINT32_ID, Varint(v)) => self.field_sint32 = Some(*v as i32), + (STRING_ID, Delimited(v)) => { + self.field_string = Some(String::from_utf8(v.to_vec()).unwrap()) + } + (BYTES_ID, Delimited(v)) => self.field_bytes = v.to_vec(), + _ => println!("WARNING: unknown DummyFields field: {:?}", item), + } + } + self + } +} + +#[derive(Debug, Default)] +struct TestConfig { + message_count: Option, + max_messages_per_second: Option, + seed: Option, + message_size: Option, + send_batch_on_register: Option, + dummy_fields: Option, +} + +impl TestConfig { + fn decode(&mut self, data: &[u8]) -> &mut Self { + use PbDecoderField::*; + const MESSAGE_COUNT_ID: u32 = TestConfigFieldNumber::MessageCount as u32; + const MAX_MESSAGES_PER_SECOND_ID: u32 = TestConfigFieldNumber::MaxMessagesPerSecond as u32; + const SEED_ID: u32 = TestConfigFieldNumber::Seed as u32; + const MESSAGE_SIZE_ID: u32 = TestConfigFieldNumber::MessageSize as u32; + const SEND_BATCH_ON_REGISTER_ID: u32 = TestConfigFieldNumber::SendBatchOnRegister as u32; + const DUMMY_FIELDS_ID: u32 = TestConfigFieldNumber::DummyFields as u32; + for item in PbDecoder::new(data) { + match item.as_ref().unwrap_or_else(|e| panic!("Error: {}", e)) { + (MESSAGE_COUNT_ID, Varint(v)) => self.message_count = Some(*v as u32), + (MAX_MESSAGES_PER_SECOND_ID, Varint(v)) => { + self.max_messages_per_second = Some(*v as u32) + } + (SEED_ID, Varint(v)) => self.seed = Some(*v as u32), + (MESSAGE_SIZE_ID, Varint(v)) => self.message_size = Some(*v as u32), + (SEND_BATCH_ON_REGISTER_ID, Varint(v)) => { + self.send_batch_on_register = Some(*v != 0) + } + (DUMMY_FIELDS_ID, Delimited(v)) => { + let mut dummy_fields = DummyFields::default(); + dummy_fields.decode(v); + self.dummy_fields = Some(dummy_fields); + } + _ => println!("WARNING: unknown TestConfig field: {:?}", item), + } + } + self + } +} + +fn main() -> Result<(), Box> { + const FOR_TESTING_ID: u32 = DataSourceConfigFieldNumber::ForTesting as u32; + let producer_args = ProducerInitArgsBuilder::new().backends(Backends::SYSTEM); + Producer::init(producer_args.build()); + let mut data_source = DataSource::new(); + let setup_data = 1234; + let test_configs: Arc; 8]>> = + Arc::new(Mutex::new([None, None, None, None, None, None, None, None])); + let test_configs_for_on_setup = Arc::clone(&test_configs); + let stop_guards: Arc; 8]>> = + Arc::new(Mutex::new([None, None, None, None, None, None, None, None])); + let stop_guards_for_on_stop = Arc::clone(&stop_guards); + let data_source_args = DataSourceArgsBuilder::new() + .on_setup(move |inst_id, config, _| { + let mut test_configs = test_configs_for_on_setup.lock().unwrap(); + let mut test_config = TestConfig::default(); + for item in PbDecoder::new(config) { + if let (FOR_TESTING_ID, PbDecoderField::Delimited(value)) = + item.unwrap_or_else(|e| panic!("Error: {}", e)) + { + test_config.decode(value); + } + } + test_configs[inst_id as usize] = Some(test_config); + println!("OnSetup id: {} data: {}", inst_id, setup_data); + }) + .on_start(move |inst_id, _| { + println!( + "OnStart id: {} {:?}", + inst_id, + test_configs.lock().unwrap()[inst_id as usize] + ); + }) + .on_stop(move |inst_id, args| { + let mut stop_guards = stop_guards_for_on_stop.lock().unwrap(); + stop_guards[inst_id as usize] = Some(args.postpone()); + println!("OnStop id: {}", inst_id); + }); + data_source.register("com.example.custom_data_source", data_source_args.build())?; + loop { + data_source.trace(|ctx: &mut TraceContext| { + let inst_id = ctx.instance_index(); + ctx.with_incremental_state(|ctx: &mut TraceContext, state| { + if state.was_cleared { + ctx.add_packet(|packet: &mut TracePacket| { + packet + .set_timestamp(10) + .set_for_testing(|for_testing: &mut TestEvent| { + for_testing.set_str(format!( + "Incremental state was cleared for inst_id: {}", + inst_id + )); + }); + }); + state.was_cleared = false; + } + }); + ctx.add_packet(|packet: &mut TracePacket| { + packet + .set_timestamp(42) + .set_for_testing(|for_testing: &mut TestEvent| { + for_testing.set_str("This is a long string"); + for_testing.set_counter(10); + for_testing.set_payload(|payload: &mut TestPayload| { + payload.set_debug_annotations( + |debug_annotation: &mut DebugAnnotation| { + debug_annotation.set_name("This is a payload debug annotation"); + debug_annotation.set_nested_value( + |nested_value: &mut NestedValue| { + nested_value.set_nested_type( + NestedValueNestedType::Unspecified, + ); + nested_value.set_string_value( + "This is a nested debug annotation value", + ); + }, + ); + }, + ); + for _ in 0..10 { + payload.set_str("nested"); + } + }); + }); + }); + if let Some(stop_guard) = stop_guards.lock().unwrap()[inst_id as usize].take() { + ctx.add_packet(|packet: &mut TracePacket| { + packet + .set_timestamp(10) + .set_for_testing(|for_testing: &mut TestEvent| { + for_testing + .set_str(format!("Asynchronous stop for inst_id: {}", inst_id)); + }); + }); + ctx.flush(|| {}); + // Signal that the data source stop operation is complete. The explicit drop + // call is just for documentation purposes as the guard would go out of scope + // here and the behavior would be the same. + drop(stop_guard); + } + }); + std::thread::sleep(std::time::Duration::from_secs(1)); + } +} diff --git a/contrib/rust-sdk/perfetto/examples/track_event.rs b/contrib/rust-sdk/perfetto/examples/track_event.rs new file mode 100644 index 00000000000..6f1b80220b1 --- /dev/null +++ b/contrib/rust-sdk/perfetto/examples/track_event.rs @@ -0,0 +1,142 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr( + feature = "intrinsics", + allow(internal_features), + feature(core_intrinsics) +)] + +use perfetto_sdk::{ + producer::*, + protos::trace::track_event::{ + source_location::SourceLocationFieldNumber, track_descriptor::TrackDescriptorFieldNumber, + track_event::TrackEventFieldNumber, + }, + track_event::*, + track_event_begin, track_event_categories, track_event_counter, track_event_end, + track_event_instant, track_event_set_category_callback, +}; +use std::error::Error; + +track_event_categories! { + pub mod example_te_ns { + ( "cat1", "Test category 1", [ "tag1" ] ), + ( "cat2", "Test category 2", [ "tag2", "tag3" ] ), + } +} + +use example_te_ns as perfetto_te_ns; + +fn main() -> Result<(), Box> { + let producer_args = ProducerInitArgsBuilder::new().backends(Backends::SYSTEM); + Producer::init(producer_args.build()); + TrackEvent::init(); + perfetto_te_ns::register()?; + + let user_arg: i64 = 12345; + track_event_set_category_callback!("cat1", move |inst_id, enabled, global_state_changed| { + println!( + "Callback: id: {} on: {}, global_state_changed: {}, user_arg: {}", + inst_id, enabled, global_state_changed, user_arg + ); + if enabled { + track_event_instant!("cat1", "callback", |ctx: &mut EventContext| { + ctx.add_debug_arg("user_arg", TrackEventDebugArg::Int64(12345)); + ctx.set_flush(); + }); + } + }); + let my_track = + TrackEventTrack::register_named_track("mytrack", 0, TrackEventTrack::process_track_uuid())?; + let my_counter = TrackEventTrack::register_counter_track( + "mycounter", + TrackEventTrack::process_track_uuid(), + )?; + let mut flow_counter: u64 = 0; + loop { + track_event_instant!("cat1", "name1"); + track_event_instant!("cat1", "name2", |ctx: &mut EventContext| { + ctx.add_debug_arg("dbg_arg", TrackEventDebugArg::Bool(false)); + ctx.add_debug_arg("dbg_arg", TrackEventDebugArg::String("mystring")); + }); + track_event_begin!("cat1", "name3"); + track_event_end!("cat1"); + flow_counter += 1; + let flow = TrackEventFlow::process_scoped_flow(flow_counter); + track_event_begin!("cat1", "name4", |ctx: &mut EventContext| { + ctx.set_track(&my_track); + ctx.set_flow(&flow); + }); + track_event_end!("cat1", |ctx: &mut EventContext| { + ctx.set_track(&my_track); + }); + track_event_instant!("cat1", "name5", |ctx: &mut EventContext| { + ctx.set_timestamp(TrackEventTimestamp::now()); + }); + track_event_instant!("cat1", "name6", |ctx: &mut EventContext| { + ctx.set_terminating_flow(&flow); + }); + track_event_counter!("cat1", |ctx: &mut EventContext| { + ctx.set_track(&my_counter); + ctx.set_counter(TrackEventCounter::Int64(79)); + }); + track_event_instant!("cat1", "name8", |ctx: &mut EventContext| { + ctx.set_named_track("dynamictrack", 2, TrackEventTrack::process_track_uuid()); + ctx.set_timestamp(TrackEventTimestamp::now()); + }); + track_event_instant!("cat1", "name9", |ctx: &mut EventContext| { + ctx.set_proto_fields(&TrackEventProtoFields { + fields: &[TrackEventProtoField::Nested( + TrackEventFieldNumber::SourceLocation as u32, + &[ + TrackEventProtoField::Cstr( + SourceLocationFieldNumber::FileName as u32, + file!(), + ), + TrackEventProtoField::VarInt( + SourceLocationFieldNumber::LineNumber as u32, + line!() as u64, + ), + ], + )], + }); + }); + track_event_counter!("cat1", |ctx: &mut EventContext| { + ctx.set_proto_track(&TrackEventProtoTrack { + uuid: TrackEventTrack::counter_track_uuid( + "mycounter", + TrackEventTrack::process_track_uuid(), + ), + fields: &[ + TrackEventProtoField::VarInt( + TrackDescriptorFieldNumber::ParentUuid as u32, + TrackEventTrack::process_track_uuid(), + ), + TrackEventProtoField::Cstr( + TrackDescriptorFieldNumber::Name as u32, + "mycounter", + ), + TrackEventProtoField::Bytes(TrackDescriptorFieldNumber::Counter as u32, &[]), + ], + }); + ctx.set_counter(TrackEventCounter::Int64(89)); + }); + track_event_counter!("cat1", |ctx: &mut EventContext| { + ctx.set_track(&my_counter); + ctx.set_counter(TrackEventCounter::Double(std::f64::consts::PI)); + }); + std::thread::sleep(std::time::Duration::from_secs(1)); + } +} diff --git a/contrib/rust-sdk/perfetto/src/data_source.rs b/contrib/rust-sdk/perfetto/src/data_source.rs new file mode 100644 index 00000000000..717c3f89af5 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/data_source.rs @@ -0,0 +1,876 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + heap_buffer::HeapBuffer, + pb_msg::{PbMsg, PbMsgWriter}, + protos::{ + common::data_source_descriptor::DataSourceDescriptor, trace::trace_packet::TracePacket, + }, + stream_writer::StreamWriter, +}; +use perfetto_sdk_sys::*; +use std::{ + cell::RefCell, + collections::HashMap, + default::Default, + marker::PhantomData, + os::raw::c_void, + ptr, + sync::{ + Mutex, OnceLock, + atomic::{AtomicBool, AtomicU64, Ordering}, + }, +}; +use thiserror::Error; + +/// Data source errors. +#[derive(Error, Debug, PartialEq)] +pub enum DataSourceError { + /// Data source has already been registered. + #[error("Data source have already been registered.")] + AlreadyRegisteredError, + /// Unknown error occured when trying to register data source. + #[error("Failed to register data source.")] + RegisterError, +} + +/// Opaque handle used to perform operations from the OnSetup callback. Unused +/// for now. +pub struct OnSetupArgs { + _args: *mut PerfettoDsOnSetupArgs, +} + +type OnSetupCallback = Box; + +/// Opaque handle used to perform operations from the OnSetup callback. Unused +/// for now. +pub struct OnStartArgs { + _args: *mut PerfettoDsOnStartArgs, +} + +type OnStartCallback = Box; + +/// A scope-based guard to signal that the data source stop operation is +/// complete when dropped. +#[must_use = "dropping StopGuard immediately defeats its purpose"] +pub struct StopGuard { + async_stopper: *mut PerfettoDsAsyncStopper, +} + +impl Drop for StopGuard { + fn drop(&mut self) { + // SAFETY: `self.async_stopper` must have been created using + // `PerfettoDsOnStopArgsPostpone`. + unsafe { + PerfettoDsStopDone(self.async_stopper); + } + } +} + +// SAFETY: The underlying PerfettoDsAsyncStopper is thread-safe. +unsafe impl Send for StopGuard {} + +// SAFETY: The underlying PerfettoDsAsyncStopper is thread-safe. +unsafe impl Sync for StopGuard {} + +/// Opaque handle used to perform operations from the OnStop callback. +pub struct OnStopArgs { + args: *mut PerfettoDsOnStopArgs, +} + +impl OnStopArgs { + /// Tells the tracing service to postpone the stopping of a data source + /// instance. The returned handle can be used to signal the tracing + /// service when the data source instance can be stopped. + #[must_use = "StopGuard must be kept alive until the desired stop point"] + pub fn postpone(&mut self) -> StopGuard { + assert!(!self.args.is_null()); + // SAFETY: `self.args` must be pointing to a valid PerfettoDsOnStopArgs handle. + let async_stopper = unsafe { PerfettoDsOnStopArgsPostpone(self.args) }; + StopGuard { async_stopper } + } +} + +type OnStopCallback = Box; + +/// A scope-based guard to signal that the data source flush operation is +/// complete when dropped. +#[must_use = "dropping FlushGuard immediately defeats its purpose"] +pub struct FlushGuard { + async_flusher: *mut PerfettoDsAsyncFlusher, +} + +impl Drop for FlushGuard { + fn drop(&mut self) { + // SAFETY: `self.async_flusher` must have been created using + // `PerfettoDsOnFlushArgsPostpone`. + unsafe { + PerfettoDsFlushDone(self.async_flusher); + } + } +} + +// SAFETY: The underlying PerfettoDsAsyncFlusher is thread-safe. +unsafe impl Send for FlushGuard {} + +// SAFETY: The underlying PerfettoDsAsyncFlusher is thread-safe. +unsafe impl Sync for FlushGuard {} + +/// Opaque handle used to perform operations from the OnStop callback. +pub struct OnFlushArgs { + args: *mut PerfettoDsOnFlushArgs, +} + +impl OnFlushArgs { + /// Tells the tracing service to postpone acknowledging the flushing of a data + /// source instance. The returned guard can be used to signal the tracing + /// service when the data source instance flushing has completed. + #[must_use = "FlushGuard must be kept alive until the desired stop point"] + pub fn postpone(&mut self) -> FlushGuard { + assert!(!self.args.is_null()); + // SAFETY: `self.args` must be pointing to a valid PerfettoDsOnFlushArgs handle. + let async_flusher = unsafe { PerfettoDsOnFlushArgsPostpone(self.args) }; + FlushGuard { async_flusher } + } +} + +type OnFlushCallback = Box; + +/// Data source buffer exhausted policy. +#[derive(Default, PartialEq)] +pub enum DataSourceBufferExhaustedPolicy { + /// If the data source runs out of space when trying to acquire a new chunk, + /// it will drop data. + #[default] + Drop, + /// If the data source runs out of space when trying to acquire a new chunk, + /// it will stall, retry and eventually abort if a free chunk is not acquired + /// after a few seconds. + StallAndAbort, + /// If the data source runs out of space when trying to acquire a new chunk, + /// it will stall, retry and eventually drop data if a free chunk is not + /// acquired after a few seconds. + StallAndDrop, +} + +pub(crate) trait ToDsBufferExhaustedPolicy { + fn to_ds_policy(&self) -> PerfettoDsBufferExhaustedPolicy; +} + +impl ToDsBufferExhaustedPolicy for DataSourceBufferExhaustedPolicy { + fn to_ds_policy(&self) -> PerfettoDsBufferExhaustedPolicy { + use DataSourceBufferExhaustedPolicy::*; + match self { + Drop => PerfettoDsBufferExhaustedPolicy_PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP, + StallAndAbort => { + PerfettoDsBufferExhaustedPolicy_PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT + } + StallAndDrop => { + PerfettoDsBufferExhaustedPolicy_PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_DROP + } + } + } +} + +#[derive(Default)] +struct DsCallbacks { + on_setup: Option, + on_start: Option, + on_stop: Option, + on_flush: Option, +} + +/// Data source arguments struct. +#[derive(Default)] +pub struct DataSourceArgs { + callbacks: DsCallbacks, + buffer_exhausted_policy: DataSourceBufferExhaustedPolicy, + buffer_exhausted_policy_configurable: bool, + will_notify_on_stop: bool, + handles_incremental_state_clear: bool, +} + +/// Data source arguments builder. +#[derive(Default)] +#[must_use = "This is a builder; remember to call `.build()` (or keep chaining)."] +pub struct DataSourceArgsBuilder { + args: DataSourceArgs, +} + +impl DataSourceArgsBuilder { + /// Create new data source arguments builder. + pub fn new() -> Self { + Self::default() + } + + /// Set buffer exhausted policy. + #[must_use = "Builder methods return an updated builder; use the returned value or keep chaining."] + pub fn buffer_exhausted_policy( + mut self, + buffer_exhausted_policy: DataSourceBufferExhaustedPolicy, + ) -> Self { + self.args.buffer_exhausted_policy = buffer_exhausted_policy; + self + } + + /// Set buffer exhausted policy configurable flag. + #[must_use = "Builder methods return an updated builder; use the returned value or keep chaining."] + pub fn buffer_exhausted_policy_configurable( + mut self, + buffer_exhausted_policy_configurable: bool, + ) -> Self { + self.args.buffer_exhausted_policy_configurable = buffer_exhausted_policy_configurable; + self + } + + /// Set notify on stop flag. + #[must_use = "Builder methods return an updated builder; use the returned value or keep chaining."] + pub fn will_notify_on_stop(mut self, will_notify_on_stop: bool) -> Self { + self.args.will_notify_on_stop = will_notify_on_stop; + self + } + + /// Set incremental state clear flag. + #[must_use = "Builder methods return an updated builder; use the returned value or keep chaining."] + pub fn handles_incremental_state_clear( + mut self, + handles_incremental_state_clear: bool, + ) -> Self { + self.args.handles_incremental_state_clear = handles_incremental_state_clear; + self + } + + /// Set setup callback. + #[must_use = "Builder methods return an updated builder; use the returned value or keep chaining."] + pub fn on_setup(mut self, cb: F) -> Self + where + F: FnMut(u32, &[u8], &mut OnSetupArgs) + Send + Sync + 'static, + { + self.args.callbacks.on_setup = Some(Box::new(cb)); + self + } + + /// Set start callback. + #[must_use = "Builder methods return an updated builder; use the returned value or keep chaining."] + pub fn on_start(mut self, cb: F) -> Self + where + F: FnMut(u32, &mut OnStartArgs) + Send + Sync + 'static, + { + self.args.callbacks.on_start = Some(Box::new(cb)); + self + } + + /// Set stop callback. + #[must_use = "Builder methods return an updated builder; use the returned value or keep chaining."] + pub fn on_stop(mut self, cb: F) -> Self + where + F: FnMut(u32, &mut OnStopArgs) + Send + Sync + 'static, + { + self.args.callbacks.on_stop = Some(Box::new(cb)); + self + } + + /// Set flush callback. + #[must_use = "Builder methods return an updated builder; use the returned value or keep chaining."] + pub fn on_flush(mut self, cb: F) -> Self + where + F: FnMut(u32, &mut OnFlushArgs) + Send + Sync + 'static, + { + self.args.callbacks.on_flush = Some(Box::new(cb)); + self + } + + /// Returns data source arguments struct. + pub fn build(self) -> DataSourceArgs { + self.args + } +} + +type FlushCallback = Box; + +// Flush callbacks are not guaranteed to be called so store them in a global +// map to prevent them from leaking. Uncalled callbacks are not currently +// removed from the map as it's hard to determine when it is safe to do so, +// which should be fine as an occurrence of such a callback is rare. +static NEXT_FLUSH_ID: AtomicU64 = AtomicU64::new(1); +static FLUSH_CALLBACKS: OnceLock>> = OnceLock::new(); + +fn flush_callbacks() -> &'static Mutex> { + FLUSH_CALLBACKS.get_or_init(|| Mutex::new(HashMap::new())) +} + +// Register a flush callback, returns the sequence ID. +fn register_flush_callback(cb: FlushCallback) -> u64 { + let id = NEXT_FLUSH_ID.fetch_add(1, Ordering::Relaxed); + flush_callbacks().lock().unwrap().insert(id, cb); + id +} + +// Remove the flush callback (if present) and return it. +fn take_flush_callback(id: u64) -> Option { + flush_callbacks().lock().unwrap().remove(&id) +} + +unsafe extern "C" fn flush_callback_trampoline(user_arg: *mut c_void) { + let result = std::panic::catch_unwind(|| { + // Decode the callback `id`. + let id = user_arg as usize as u64; + // Remove flush callback, which will be dropped at the end of the scope. + if let Some(mut cb) = take_flush_callback(id) { + cb(); + } + }); + if let Err(err) = result { + eprintln!("Fatal panic: {:?}", err); + std::process::abort(); + } +} + +/// Trace context base struct with passed to data source and track event trace callbacks. +pub struct TraceContextBase { + pub(crate) iterator: PerfettoDsImplTracerIterator, +} + +impl TraceContextBase { + /// Creates new trace packets and calls `cb` to write data to each of the packets. + pub fn add_packet(&mut self, mut cb: F) + where + F: FnMut(&mut TracePacket), + { + let writer = PbMsgWriter { + writer: StreamWriter { + // Returns a writer that must be freed using `PerfettoDsTracerImplPacketEnd`. + // + // SAFETY: + // + // - `self.iterator.tracer` must be a pointer provided by a call to + // PerfettoDsImplTraceIterateBegin/Next. + writer: RefCell::new(unsafe { + PerfettoDsTracerImplPacketBegin(self.iterator.tracer) + }), + }, + }; + let mut msg = PbMsg::new(&writer).unwrap(); + let mut packet = TracePacket { msg: &mut msg }; + + cb(&mut packet); + + packet.msg.finalize(); + + let mut inner_writer = writer.writer.writer.borrow_mut(); + // SAFETY: + // + // Free writer created above using `PerfettoDsTracerImplPacketBegin`. + unsafe { + PerfettoDsTracerImplPacketEnd(self.iterator.tracer, &mut *inner_writer as *mut _); + } + } + + /// Forces a commit of the thread-local tracing data written so far to the + /// service. + /// + /// `cb` is called on a dedicated internal thread, when flushing is complete. + /// It may never be called (e.g. if the tracing service disconnects). + /// + /// This is almost never required (tracing data is periodically committed as + /// trace pages are filled up) and has a non-negligible performance hit. + pub fn flush(&mut self, cb: F) + where + F: FnMut() + Send + Sync + 'static, + { + let id = register_flush_callback(Box::new(cb)); + // Encode the callback `id` as a `*mut c_void`. + let user_arg = id as usize as *mut c_void; + // SAFETY: callback identified by `id` must be safe to call on any thread. + unsafe { + PerfettoDsTracerImplFlush( + self.iterator.tracer, + Some(flush_callback_trampoline), + user_arg, + ) + }; + } + + /// Returns the index of the current instance. + pub fn instance_index(&self) -> u32 { + self.iterator.inst_id + } +} + +/// Default incremental state struct used if not specified. +pub struct IncrementalState { + /// Set to true when incremental state has been cleared and not yet acknowledged by + /// a call to with_incremental_state that sets it to false. + pub was_cleared: bool, +} + +impl Default for IncrementalState { + fn default() -> Self { + Self { was_cleared: true } + } +} + +/// Trace context struct passed to data source trace callbacks. +pub struct TraceContext<'a, IncrT: Default = IncrementalState> { + base: TraceContextBase, + pub(crate) impl_: *mut PerfettoDsImpl, + pub(crate) _marker: PhantomData<&'a IncrT>, +} + +impl TraceContext<'_, IncrT> { + /// Calls `cb` with the incremental state for the instance. + pub fn with_incremental_state(&mut self, mut cb: F) + where + F: FnMut(&mut Self, &mut IncrT), + { + assert!(!self.impl_.is_null()); + // SAFETY: + // + // - `self.impl_` must be non-null. + // - `self.iterator.tracer` must be a pointer provided by a call to + // PerfettoDsImplTraceIterateBegin/Next. + // - `self.iterator.inst_id` must be set by a call to + // PerfettoDsImplTraceIterateBegin/Next. + let ptr = unsafe { + PerfettoDsImplGetIncrementalState( + self.impl_, + self.base.iterator.tracer, + self.base.iterator.inst_id, + ) + }; + if ptr.is_null() { + panic!("missing incremental state"); + } + // SAFETY: + // + // - `buf` must be non-null. + // - `IncrT` must match the generic type used for on_create_incr_trampoline. + let state: &mut IncrT = unsafe { &mut *(ptr as *mut IncrT) }; + cb(self, state); + } +} + +impl std::ops::Deref for TraceContext<'_, IncrT> { + type Target = TraceContextBase; + fn deref(&self) -> &Self::Target { + &self.base + } +} + +impl std::ops::DerefMut for TraceContext<'_, IncrT> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.base + } +} + +/// Data source struct. +pub struct DataSource<'a: 'static, IncrT: Default = IncrementalState> { + enabled: *mut bool, + impl_: *mut PerfettoDsImpl, + callbacks: Mutex>>, + _marker: PhantomData<&'a IncrT>, +} + +unsafe extern "C" fn on_setup_callback_trampoline( + _ds: *mut PerfettoDsImpl, + inst_id: PerfettoDsInstanceIndex, + ds_config: *mut c_void, + ds_config_size: usize, + user_arg: *mut c_void, + args: *mut PerfettoDsOnSetupArgs, +) -> *mut c_void { + let result = std::panic::catch_unwind(|| { + // SAFETY: `user_arg` must be a pointer to a boxed DsCallbacks struct. + let callbacks: &mut DsCallbacks = unsafe { &mut *(user_arg as *mut _) }; + if let Some(f) = &mut callbacks.on_setup { + // SAFETY: + // - `ds_config` must be non-null. + // - `ds_config_size` bytes starting at `ptr` must be valid for **reads**. + let config = + unsafe { std::slice::from_raw_parts(ds_config as *const u8, ds_config_size) }; + let mut on_setup_args = OnSetupArgs { _args: args }; + f(inst_id, config, &mut on_setup_args); + } + }); + if let Err(err) = result { + eprintln!("Fatal panic: {:?}", err); + std::process::abort(); + } + // Instance contexts are not supported as preferably handled by the + // client in Rust code. + ptr::null_mut() +} + +unsafe extern "C" fn on_start_callback_trampoline( + _ds: *mut PerfettoDsImpl, + inst_id: PerfettoDsInstanceIndex, + user_arg: *mut c_void, + _inst_ctx: *mut c_void, + args: *mut PerfettoDsOnStartArgs, +) { + let result = std::panic::catch_unwind(|| { + // SAFETY: `user_arg` must be a pointer to a boxed DsCallbacks struct. + let callbacks: &mut DsCallbacks = unsafe { &mut *(user_arg as *mut _) }; + if let Some(f) = &mut callbacks.on_start { + let mut on_start_args = OnStartArgs { _args: args }; + f(inst_id, &mut on_start_args); + } + }); + if let Err(err) = result { + eprintln!("Fatal panic: {:?}", err); + std::process::abort(); + } +} + +unsafe extern "C" fn on_stop_callback_trampoline( + _ds: *mut PerfettoDsImpl, + inst_id: PerfettoDsInstanceIndex, + user_arg: *mut c_void, + _inst_ctx: *mut c_void, + args: *mut PerfettoDsOnStopArgs, +) { + let result = std::panic::catch_unwind(|| { + // SAFETY: `user_arg` must be a pointer to a boxed DsCallbacks struct. + let callbacks: &mut DsCallbacks = unsafe { &mut *(user_arg as *mut _) }; + if let Some(f) = &mut callbacks.on_stop { + let mut on_stop_args = OnStopArgs { args }; + f(inst_id, &mut on_stop_args); + } + }); + if let Err(err) = result { + eprintln!("Fatal panic: {:?}", err); + std::process::abort(); + } +} + +unsafe extern "C" fn on_flush_callback_trampoline( + _ds: *mut PerfettoDsImpl, + inst_id: PerfettoDsInstanceIndex, + user_arg: *mut c_void, + _inst_ctx: *mut c_void, + args: *mut PerfettoDsOnFlushArgs, +) { + let result = std::panic::catch_unwind(|| { + // SAFETY: `user_arg` must be a pointer to a boxed DsCallbacks struct. + let callbacks: &mut DsCallbacks = unsafe { &mut *(user_arg as *mut _) }; + if let Some(f) = &mut callbacks.on_flush { + let mut on_flush_args = OnFlushArgs { args }; + f(inst_id, &mut on_flush_args); + } + }); + if let Err(err) = result { + eprintln!("Fatal panic: {:?}", err); + std::process::abort(); + } +} + +unsafe extern "C" fn on_create_incr_trampoline( + _ds: *mut PerfettoDsImpl, + _inst_id: PerfettoDsInstanceIndex, + _tracer: *mut PerfettoDsTracerImpl, + _user_arg: *mut c_void, +) -> *mut c_void { + let boxed = Box::new(IncrT::default()); + Box::into_raw(boxed) as *mut c_void +} + +unsafe extern "C" fn on_delete_incr_trampoline(data: *mut c_void) { + // Reclaims the Box and calls drop. + // + // SAFETY: `data` must be a pointer to a boxed IncrT struct. + unsafe { drop(Box::from_raw(data as *mut IncrT)) }; +} + +impl<'a: 'static, IncrT: Default> DataSource<'a, IncrT> { + /// Create new data source type with a non-default `IncrT` type. + pub fn new_with_incremental_state_type() -> Self { + Self::default() + } + + /// Registers the data source type named `name` with the global ewperfetto producer. + pub fn register(&mut self, name: &str, args: DataSourceArgs) -> Result<(), DataSourceError> { + use DataSourceError::*; + let mut callbacks = self.callbacks.lock().unwrap(); + if callbacks.is_some() { + return Err(AlreadyRegisteredError); + } + let mut boxed_callbacks = Box::new(args.callbacks); + let user_arg = crate::__box_as_mut_ptr(&mut boxed_callbacks) as *mut c_void; + + let writer = PbMsgWriter::new(); + let hb = HeapBuffer::new(&writer.writer); + let mut msg = PbMsg::new(&writer).unwrap(); + { + let mut desc = DataSourceDescriptor { msg: &mut msg }; + desc.set_name(name); + desc.set_will_notify_on_stop(args.will_notify_on_stop); + desc.set_handles_incremental_state_clear(args.handles_incremental_state_clear); + } + msg.finalize(); + let desc_size = writer.writer.get_written_size(); + let mut desc_buffer: Vec = vec![0u8; desc_size]; + hb.copy_into(&mut desc_buffer); + // SAFETY: + // - `self.enabled` must be a pointer to a primitive with layout that matches C11 + // atomic_bool. + // - `desc_buffer` must be an encoded DataSourceDescriptor messaage. + let ds_impl = unsafe { + let ds_impl = PerfettoDsImplCreate(); + PerfettoDsSetOnSetupCallback(ds_impl, Some(on_setup_callback_trampoline)); + PerfettoDsSetOnStartCallback(ds_impl, Some(on_start_callback_trampoline)); + PerfettoDsSetOnStopCallback(ds_impl, Some(on_stop_callback_trampoline)); + PerfettoDsSetOnFlushCallback(ds_impl, Some(on_flush_callback_trampoline)); + PerfettoDsSetOnCreateIncr(ds_impl, Some(on_create_incr_trampoline::)); + PerfettoDsSetOnDeleteIncr(ds_impl, Some(on_delete_incr_trampoline::)); + PerfettoDsSetCbUserArg(ds_impl, user_arg); + PerfettoDsSetBufferExhaustedPolicy( + ds_impl, + args.buffer_exhausted_policy.to_ds_policy(), + ); + PerfettoDsSetBufferExhaustedPolicyConfigurable( + ds_impl, + args.buffer_exhausted_policy_configurable, + ); + let success = PerfettoDsImplRegister( + ds_impl, + &raw mut self.enabled, + desc_buffer.as_mut_ptr() as *mut c_void, + desc_size, + ); + if !success { + return Err(RegisterError); + } + ds_impl + }; + self.impl_ = ds_impl; + callbacks.replace(boxed_callbacks); + Ok(()) + } + + /// Returns true if any active instance exists of data source type. + pub fn is_enabled(&self) -> bool { + // SAFETY: `self.enabled` must be a pointer to a primitive with layout that + // matches C11 atomic_bool. + unsafe { + let atomic_ptr = self.enabled as *const AtomicBool; + (*atomic_ptr).load(Ordering::Relaxed) + } + } + + /// Call `cb` for all the active instances (on this thread) of a data source type. + pub fn trace(&self, mut cb: F) + where + F: FnMut(&mut TraceContext<'_, IncrT>), + { + // It is safe to call this prior to registering the data source as self.is_enabled() + // will return false in that case. + if crate::__unlikely!(self.is_enabled()) { + assert!(!self.impl_.is_null()); + let mut ctx = TraceContext::<'_, IncrT> { + base: TraceContextBase { + // SAFETY: `self.impl_` must be a pointer to a registered data source. Ie. + // non-null and passed to a successful PerfettoDsImplRegister() call. Guaranteed + // to be the case as is_enabled() will always return false otherwise and this + // cannot be reached. + iterator: unsafe { PerfettoDsImplTraceIterateBegin(self.impl_) }, + }, + impl_: self.impl_, + _marker: PhantomData, + }; + loop { + if ctx.base.iterator.tracer.is_null() { + break; + } + + cb(&mut ctx); + + // SAFETY: `self.impl_` must be a pointer to a registered data source. Guaranteed + // to be the case as is_enabled() will always return false otherwise and this + // cannot be reached. + unsafe { PerfettoDsImplTraceIterateNext(self.impl_, &raw mut ctx.base.iterator) }; + } + } + } +} + +// Monomorphic `new()` on the defaulted type. +impl<'a: 'static> DataSource<'a, IncrementalState> { + /// Create new data source type. + pub fn new() -> Self { + Self::default() + } +} + +impl<'a: 'static, IncrT: Default> Default for DataSource<'a, IncrT> { + fn default() -> Self { + Self { + // `perfetto_atomic_false` is a pointer to a primitive with layout that + // matches C11 atomic_bool and set to false. + enabled: &raw mut perfetto_atomic_false, + impl_: ptr::null_mut(), + callbacks: Mutex::new(None), + _marker: PhantomData, + } + } +} + +/// SAFETY: Internal handle must be thread-safe. +unsafe impl<'a: 'static, IncrT: Default> Send for DataSource<'a, IncrT> {} + +/// SAFETY: Internal handle must be thread-safe. +unsafe impl<'a: 'static, IncrT: Default> Sync for DataSource<'a, IncrT> {} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::{ + PRODUCER_SHMEM_SIZE_HINT_KB, TracingSessionBuilder, acquire_test_environment, + }; + use std::{error::Error, sync::OnceLock}; + + const DATA_SOURCE_NAME: &str = "com.example.custom_data_source"; + static DATA_SOURCE: OnceLock = OnceLock::new(); + + fn get_data_source() -> &'static DataSource<'static> { + DATA_SOURCE.get_or_init(|| { + let data_source_args = DataSourceArgsBuilder::new() + .buffer_exhausted_policy(DataSourceBufferExhaustedPolicy::StallAndAbort); + let mut data_source = DataSource::new(); + data_source + .register(DATA_SOURCE_NAME, data_source_args.build()) + .expect("failed to register data source"); + data_source + }) + } + + #[test] + fn is_enabled() -> Result<(), Box> { + let _lock = acquire_test_environment(); + let data_source = get_data_source(); + assert!(!data_source.is_enabled()); + let mut session = TracingSessionBuilder::new() + .set_data_source_name(DATA_SOURCE_NAME) + .build()?; + session.start_blocking(); + assert!(data_source.is_enabled()); + session.stop_blocking(); + Ok(()) + } + + #[test] + fn trace() -> Result<(), Box> { + use crate::pb_decoder::{PbDecoder, PbDecoderField}; + use crate::protos::trace::{test_event::*, trace::*, trace_packet::*}; + use std::sync::{Arc, Mutex}; + let _lock = acquire_test_environment(); + let data_source = get_data_source(); + let mut session = TracingSessionBuilder::new() + .set_data_source_name(DATA_SOURCE_NAME) + .build()?; + session.start_blocking(); + data_source.trace(|ctx: &mut TraceContext| { + ctx.add_packet(|packet: &mut TracePacket| { + packet.set_for_testing(|for_testing: &mut TestEvent| { + for_testing.set_str("123"); + }); + }); + }); + session.stop_blocking(); + let trace_data = Arc::new(Mutex::new(vec![])); + let trace_data_for_write = Arc::clone(&trace_data); + session.read_trace_blocking(move |data, _end| { + let mut written_data = trace_data_for_write.lock().unwrap(); + written_data.extend_from_slice(data); + }); + let data = trace_data.lock().unwrap(); + assert!(!data.is_empty()); + let mut test_str = String::new(); + for trace_field in PbDecoder::new(&data) { + const PACKET_ID: u32 = TraceFieldNumber::Packet as u32; + if let (PACKET_ID, PbDecoderField::Delimited(data)) = trace_field.unwrap() { + for packet_field in PbDecoder::new(data) { + const FOR_TESTING_ID: u32 = TracePacketFieldNumber::ForTesting as u32; + if let (FOR_TESTING_ID, PbDecoderField::Delimited(data)) = packet_field.unwrap() + { + for test_event_field in PbDecoder::new(data) { + const STR_ID: u32 = TestEventFieldNumber::Str as u32; + if let (STR_ID, PbDecoderField::Delimited(value)) = + test_event_field.unwrap() + { + test_str = String::from_utf8(value.to_vec()).unwrap() + } + } + } + } + } + } + assert_eq!(&test_str, "123"); + Ok(()) + } + + #[test] + fn trace_large_packet() -> Result<(), Box> { + use crate::pb_decoder::{PbDecoder, PbDecoderField}; + use crate::protos::trace::{test_event::*, trace::*, trace_packet::*}; + use std::sync::{Arc, Mutex}; + let _lock = acquire_test_environment(); + let data_source = get_data_source(); + let mut session = TracingSessionBuilder::new() + .set_data_source_name(DATA_SOURCE_NAME) + .build()?; + session.start_blocking(); + // Large enough to exceed the producer shmem size. + let super_long_test_string = "a".repeat(1024 * (PRODUCER_SHMEM_SIZE_HINT_KB as usize + 10)); + data_source.trace(|ctx: &mut TraceContext| { + ctx.add_packet(|packet: &mut TracePacket| { + packet.set_for_testing(|for_testing: &mut TestEvent| { + for_testing.set_str(&super_long_test_string); + }); + }); + }); + session.stop_blocking(); + let trace_data = Arc::new(Mutex::new(vec![])); + let trace_data_for_write = Arc::clone(&trace_data); + session.read_trace_blocking(move |data, _end| { + let mut written_data = trace_data_for_write.lock().unwrap(); + written_data.extend_from_slice(data); + }); + let data = trace_data.lock().unwrap(); + assert!(!data.is_empty()); + let mut test_str = String::new(); + for trace_field in PbDecoder::new(&data) { + const PACKET_ID: u32 = TraceFieldNumber::Packet as u32; + if let (PACKET_ID, PbDecoderField::Delimited(data)) = trace_field.unwrap() { + for packet_field in PbDecoder::new(data) { + const FOR_TESTING_ID: u32 = TracePacketFieldNumber::ForTesting as u32; + if let (FOR_TESTING_ID, PbDecoderField::Delimited(data)) = packet_field.unwrap() + { + for test_event_field in PbDecoder::new(data) { + const STR_ID: u32 = TestEventFieldNumber::Str as u32; + + if let (STR_ID, PbDecoderField::Delimited(value)) = + test_event_field.unwrap() + { + test_str = String::from_utf8(value.to_vec()).unwrap(); + } + } + } + } + } + } + assert_eq!(&test_str, &super_long_test_string); + Ok(()) + } +} diff --git a/contrib/rust-sdk/perfetto/src/heap_buffer.rs b/contrib/rust-sdk/perfetto/src/heap_buffer.rs new file mode 100644 index 00000000000..b49f4be816a --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/heap_buffer.rs @@ -0,0 +1,80 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::stream_writer::StreamWriter; +use perfetto_sdk_sys::*; +use std::os::raw::c_void; + +/// A HeapBuffer can be used to serialize protobuf data using the +/// StreamWriter interface. Stores data on heap allocated buffers, which +/// can be read back with copy_into(). +pub struct HeapBuffer<'a> { + buffer: *mut PerfettoHeapBuffer, + writer: &'a StreamWriter, +} + +impl<'a> HeapBuffer<'a> { + /// Creates a HeapBuffer. Takes a reference to a StreamWriter. + /// The StreamWriter can be used later to serialize protobuf data. + pub fn new(writer: &'a StreamWriter) -> Self { + let mut inner_writer = writer.writer.borrow_mut(); + // SAFETY: + // - `inner_writer` must be a pointer to a properly initialized + // PerfettoStreamWriter struct, which the StreamWriter interface + // is guaranteed to provide. + let buffer = unsafe { PerfettoHeapBufferCreate(&mut *inner_writer as *mut _) }; + HeapBuffer { buffer, writer } + } + + /// Copies data from the heap buffer to `dst`. + pub fn copy_into(&self, dst: &mut [u8]) { + let mut inner_writer = self.writer.writer.borrow_mut(); + // SAFETY: + // - `self.buffer` must have been created with PerfettoHeapBufferCreate. + // - `inner_writer` must be a pointer to a properly initialized + // PerfettoStreamWriter struct, which the StreamWriter interface + // is guaranteed to provide. + unsafe { + PerfettoHeapBufferCopyInto( + self.buffer, + &mut *inner_writer as *mut _, + dst.as_mut_ptr() as *mut c_void, + dst.len(), + ) + }; + } +} + +impl Drop for HeapBuffer<'_> { + fn drop(&mut self) { + let mut inner_writer = self.writer.writer.borrow_mut(); + // SAFETY: + // - `self.buffer` must have been created with PerfettoHeapBufferCreate. + // - `inner_writer` must be a pointer to a properly initialized + // PerfettoStreamWriter struct, which the StreamWriter interface + // is guaranteed to provide. + unsafe { PerfettoHeapBufferDestroy(self.buffer, &mut *inner_writer as *mut _) }; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn init() { + let writer = StreamWriter::new(); + let _hb = HeapBuffer::new(&writer); + } +} diff --git a/contrib/rust-sdk/perfetto/src/lib.rs b/contrib/rust-sdk/perfetto/src/lib.rs new file mode 100644 index 00000000000..128d593b317 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/lib.rs @@ -0,0 +1,235 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # perfetto +//! +//! This crate provides Rust bindings for Perfetto. +//! +//! It is uses the public ABI under the hood and has been designed for safe +//! and efficient usage in Rust projects. Performance critical operations +//! such as checking if a track event category is enabled is done in Rust +//! code as well as encoding of proto messages. + +#![deny(missing_docs)] +#![warn(clippy::undocumented_unsafe_blocks)] +#![cfg_attr( + feature = "intrinsics", + allow(internal_features), + feature(core_intrinsics) +)] + +/// Data source module. +pub mod data_source; + +/// Heap buffer module. +pub mod heap_buffer; + +/// Protobuf decoder module. +pub mod pb_decoder; + +/// Protobuf message module. +pub mod pb_msg; + +/// Protobuf utils module. +pub mod pb_utils; + +/// Producer module. +pub mod producer; + +/// Protobuf bindings module. +pub mod protos; + +/// Stream writer module. +pub mod stream_writer; + +/// Tracing session module. +pub mod tracing_session; + +/// Track event module. +pub mod track_event; + +// FNV-1a 64-bit constants +const FNV64_OFFSET: u64 = 0xcbf29ce484222325; +const FNV64_PRIME: u64 = 0x00000100000001B3; + +/// Computes the FNV-1a hash of `bytes`. +pub const fn fnv1a(bytes: &[u8]) -> u64 { + let mut hash = FNV64_OFFSET; + let mut i = 0; + while i < bytes.len() { + hash ^= bytes[i] as u64; + hash = hash.wrapping_mul(FNV64_PRIME); + i += 1; + } + hash +} + +/// Helper macro that use `likely` intrinsic branch prediction hint. +#[cfg(feature = "intrinsics")] +#[doc(hidden)] +#[macro_export] +macro_rules! __likely { + ($e:expr) => {{ std::intrinsics::likely($e) }}; +} + +/// Helper macro that ignores branch prediction hint. +#[cfg(not(feature = "intrinsics"))] +#[doc(hidden)] +#[macro_export] +macro_rules! __likely { + ($e:expr) => {{ $e }}; +} + +/// Helper macro that use `unlikely` intrinsic branch prediction hint. +#[cfg(feature = "intrinsics")] +#[doc(hidden)] +#[macro_export] +macro_rules! __unlikely { + ($e:expr) => {{ std::intrinsics::unlikely($e) }}; +} + +/// Helper macro that ignores branch prediction hint. +#[cfg(not(feature = "intrinsics"))] +#[doc(hidden)] +#[macro_export] +macro_rules! __unlikely { + ($e:expr) => {{ $e }}; +} + +/// Internal utility function that converts `Box` to `*mut T`. +#[doc(hidden)] +pub fn __box_as_mut_ptr(b: &mut Box) -> *mut T { + // TODO(reveman): Use Box::as_mut_ptr() instead when stable. + &raw mut **b +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::producer::{Backends, Producer, ProducerInitArgsBuilder}; + use crate::tracing_session::*; + use std::sync::{Mutex, MutexGuard, Once}; + + static INIT_TEST_ENVIRONMENT: Once = Once::new(); + static TEST_ENVIRONMENT_MUTEX: Mutex<()> = Mutex::new(()); + + pub(crate) const PRODUCER_SHMEM_SIZE_HINT_KB: u32 = 64; + + // Perfetto uses global state internally that cannot be uninitialized so the + // test environment and registered data sources must also be global. + pub(crate) fn acquire_test_environment() -> MutexGuard<'static, ()> { + INIT_TEST_ENVIRONMENT.call_once(|| { + let producer_args = ProducerInitArgsBuilder::new() + .backends(Backends::IN_PROCESS) + .shmem_size_hint_kb(PRODUCER_SHMEM_SIZE_HINT_KB); + Producer::init(producer_args.build()); + }); + TEST_ENVIRONMENT_MUTEX.lock().unwrap() + } + + #[derive(Default)] + #[must_use = "This is a builder; remember to call `.build()` (or keep chaining)."] + pub(crate) struct TracingSessionBuilder { + data_source_name: String, + enabled_categories: Vec, + disabled_categories: Vec, + } + + impl TracingSessionBuilder { + pub fn new() -> Self { + Self::default() + } + + #[must_use = "Builder methods return an updated builder; use the returned value or keep chaining."] + pub fn set_data_source_name(mut self, name: impl Into) -> Self { + self.data_source_name = name.into(); + self + } + + #[must_use = "Builder methods return an updated builder; use the returned value or keep chaining."] + pub fn add_enabled_category(mut self, category: impl Into) -> Self { + self.enabled_categories.push(category.into()); + self + } + + #[must_use = "Builder methods return an updated builder; use the returned value or keep chaining."] + pub fn add_disabled_category(mut self, category: impl Into) -> Self { + self.disabled_categories.push(category.into()); + self + } + + fn build_proto_config(&self) -> Vec { + use crate::{ + heap_buffer::HeapBuffer, + pb_msg::{PbMsg, PbMsgWriter}, + protos::config::{ + data_source_config::DataSourceConfig, + trace_config::{BufferConfig, DataSource, TraceConfig}, + track_event::track_event_config::TrackEventConfig, + }, + }; + let writer = PbMsgWriter::new(); + let hb = HeapBuffer::new(&writer.writer); + let mut msg = PbMsg::new(&writer).unwrap(); + { + let mut cfg = TraceConfig { msg: &mut msg }; + cfg.set_buffers(|buf_cfg: &mut BufferConfig| { + buf_cfg.set_size_kb(1024); + }); + cfg.set_data_sources(|data_sources: &mut DataSource| { + data_sources.set_config(|ds_cfg: &mut DataSourceConfig| { + ds_cfg.set_name(&self.data_source_name); + if !self.enabled_categories.is_empty() + || !self.disabled_categories.is_empty() + { + ds_cfg.set_track_event_config(|te_cfg: &mut TrackEventConfig| { + for enabled_catagory in &self.enabled_categories { + te_cfg.set_enabled_categories(enabled_catagory); + } + for disabled_catagory in &self.disabled_categories { + te_cfg.set_disabled_categories(disabled_catagory); + } + }); + } + }); + }); + } + msg.finalize(); + let cfg_size = writer.writer.get_written_size(); + let mut cfg_buffer: Vec = vec![0u8; cfg_size]; + hb.copy_into(&mut cfg_buffer); + + cfg_buffer + } + + pub fn build(&self) -> Result { + let config = self.build_proto_config(); + let mut ts = TracingSession::in_process()?; + ts.setup(&config); + Ok(ts) + } + } + + #[test] + fn fnv1a_hash() { + assert_eq!(fnv1a("mytrack".as_bytes()), 9332035348890697650); + } + + #[test] + fn unlikely_conditional() { + if __unlikely!(fnv1a("mystring".as_bytes()) == 0) { + unreachable!(); + } + } +} diff --git a/contrib/rust-sdk/perfetto/src/pb_decoder.rs b/contrib/rust-sdk/perfetto/src/pb_decoder.rs new file mode 100644 index 00000000000..17646768ba4 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/pb_decoder.rs @@ -0,0 +1,144 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::pb_utils::PbWireType; +use perfetto_sdk_sys::*; +use thiserror::Error; + +/// Protobuf decoder errors. +#[derive(Error, Debug, PartialEq)] +pub enum PbDecoderError { + /// Encountered an invalid wire type. + #[error("Invalid wire type: {0}.")] + InvalidWireType(u32), +} + +/// Protobuf decoder field types. +#[derive(Debug, PartialEq)] +pub enum PbDecoderField<'a> { + /// Varint field. + Varint(u64), + /// Fixed64 field. + Fixed64(u64), + /// Delimited field, e.g. nested message or string. + Delimited(&'a [u8]), + /// Fixed32 field. + Fixed32(u32), +} + +/// Decoder for parsing protobuf messages. +/// +/// Example: +/// +/// ``` +/// static MSG: &[u8] = b"\x18\x05\x2a\x12\x0a\x05\x68\x65\x6c\x6c\x6f\ +/// \x28\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"; +/// +/// for item in perfetto_sdk::pb_decoder::PbDecoder::new(MSG) { +/// // Do something with item +/// } +/// ``` +pub struct PbDecoder<'a> { + decoder: PerfettoPbDecoder, + _data: &'a [u8], +} + +impl<'a> PbDecoder<'a> { + /// Create a new decoder instance from data. + pub fn new(data: &'a [u8]) -> Self { + let read_ptr = data.as_ptr(); + PbDecoder { + decoder: PerfettoPbDecoder { + read_ptr, + // SAFETY: `data.len()` must be ≤ slice length. + end_ptr: unsafe { read_ptr.add(data.len()) }, + }, + _data: data, + } + } +} + +impl<'a> Iterator for PbDecoder<'a> { + type Item = Result<(u32, PbDecoderField<'a>), PbDecoderError>; + + fn next(&mut self) -> Option { + // SAFETY: `self.decoder` must be properly initialized PerfettoPbDecoder struct + // and done by PbDecoder::new(). + let next: PerfettoPbDecoderField = + unsafe { PerfettoPbDecoderParseField(&raw mut self.decoder) }; + if next.status != PerfettoPbDecoderStatus_PERFETTO_PB_DECODER_OK { + return None; + } + + // SAFETY: `next.wire_type` must match the data stored in `next.value` union, + // which is expected from a successful call to PerfettoPbDecoderParseField. + let field = unsafe { + match PbWireType::try_from(next.wire_type) { + Ok(PbWireType::Varint) => PbDecoderField::Varint(next.value.integer64), + Ok(PbWireType::Fixed64) => PbDecoderField::Fixed64(next.value.integer64), + Ok(PbWireType::Delimited) => { + let data = std::slice::from_raw_parts( + next.value.delimited.start, + next.value.delimited.len, + ); + PbDecoderField::Delimited(data) + } + Ok(PbWireType::Fixed32) => PbDecoderField::Fixed32(next.value.integer32), + Err(_) => { + return Some(Err(PbDecoderError::InvalidWireType(next.wire_type))); + } + } + }; + + Some(Ok((next.id, field))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn empty() { + let decoder = PbDecoder::new(&[]); + let items: Vec<_> = decoder.collect(); + assert_eq!(items.len(), 0); + } + + // # proto-message: perfetto.protos.TestEvent + // counter: 5 + // payload { + // str: "hello" + // single_int: -1 + // } + static MSG: &[u8] = b"\x18\x05\x2a\x12\x0a\x05\x68\x65\x6c\x6c\x6f\ + \x28\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01"; + + #[test] + fn test_event() { + use PbDecoderField::*; + let decoder = PbDecoder::new(MSG); + let items: Vec<_> = decoder.collect(); + assert_eq!(items[0], Ok((3, Varint(5)))); + match &items[1] { + Ok((5, Delimited(data))) => { + let payload_decoder = PbDecoder::new(data); + let payload_items: Vec<_> = payload_decoder.collect(); + assert_eq!(payload_items[0], Ok((1, Delimited(b"hello")))); + assert_eq!(payload_items[1], Ok((5, Varint(-1i64 as u64)))); + } + other => panic!("unexpected item: {:?}", other), + } + } +} diff --git a/contrib/rust-sdk/perfetto/src/pb_msg.rs b/contrib/rust-sdk/perfetto/src/pb_msg.rs new file mode 100644 index 00000000000..639f148b5a8 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/pb_msg.rs @@ -0,0 +1,293 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::pb_utils::*; +use crate::stream_writer::StreamWriter; +use perfetto_sdk_sys::*; +use std::{ + cell::RefCell, + ptr, + rc::{Rc, Weak}, +}; +use thiserror::Error; + +/// Protobuf message errors. +#[derive(Error, Debug, PartialEq)] +pub enum PbMsgError { + /// No output for writer. + #[error("Message writer is missing an output.")] + MissingOutputForWriter, +} + +/// Reference to the memory used by a `PbMsg` for writing. +#[derive(Default)] +pub struct PbMsgWriter { + pub(crate) writer: StreamWriter, +} + +impl PbMsgWriter { + /// Creates a new protobuf message writer. + pub fn new() -> Self { + Self::default() + } +} + +// The number of bytes reserved by this implementation to encode a protobuf type +// 2 field size as var-int. Keep this in sync with kMessageLengthFieldSize in +// proto_utils.h. +const PROTOZERO_MESSAGE_LENGTH_FIELD_SIZE: usize = 4; + +struct PbMsgSizeField { + ptr: *mut u8, + parent: Weak>, +} + +impl PbMsgSizeField { + pub fn patch(&mut self, writer: &PbMsgWriter) { + assert!(!self.ptr.is_null()); + let mut writer = writer.writer.writer.borrow_mut(); + // SAFETY: + // - `writer` must be a properly initialized PerfettoStreamWriter struct. + // - `self.ptr` must be pointing to a `PROTOZERO_MESSAGE_LENGTH_FIELD_SIZE` sized buffer. + self.ptr = unsafe { PerfettoStreamWriterAnnotatePatch(&mut *writer as *mut _, self.ptr) }; + } + + pub fn patch_stack(&mut self, writer: &PbMsgWriter) { + let (range_begin, range_end) = { + let inner_writer = writer.writer.writer.borrow(); + ( + inner_writer.begin as *const u8, + inner_writer.end as *const u8, + ) + }; + if range_begin <= self.ptr && (self.ptr as *const u8) < range_end { + self.patch(writer); + if let Some(parent) = self.parent.upgrade() { + parent.borrow_mut().patch_stack(writer); + } + } + } +} + +/// Protobuf message struct. +pub struct PbMsg<'a> { + size_field: Rc>, + size: usize, + writer: &'a PbMsgWriter, +} + +impl<'a> PbMsg<'a> { + /// Creates a new message struct using `writer`. + pub fn new(writer: &'a PbMsgWriter) -> Result { + if !writer.writer.has_valid_writer() { + return Err(PbMsgError::MissingOutputForWriter); + } + Ok(Self { + size_field: Rc::new(RefCell::new(PbMsgSizeField { + ptr: std::ptr::null_mut(), + parent: Weak::>::default(), + })), + size: 0, + writer, + }) + } + + /// Append bytes to message. + pub fn append_bytes(&mut self, bytes: &[u8]) { + if crate::__unlikely!(bytes.len() > self.writer.writer.available_bytes()) { + self.size_field.borrow_mut().patch_stack(self.writer); + } + self.writer.writer.append_bytes(bytes); + self.size += bytes.len(); + } + + /// Append byte to message. + pub fn append_byte(&mut self, value: u8) { + self.append_bytes(&[value]); + } + + /// Append varint to message. + pub fn append_varint(&mut self, value: u64) { + let mut buf: [u8; PB_VARINT_MAX_SIZE_64] = [0; PB_VARINT_MAX_SIZE_64]; + let written = pb_write_varint(value, &mut buf); + self.append_bytes(&buf[..written]); + } + + /// Append fixed32 to message. + pub fn append_fixed32(&mut self, value: u32) { + let mut buf: [u8; 4] = [0; 4]; + pb_write_fixed32(value, &mut buf); + self.append_bytes(&buf); + } + + /// Append fixed64 to message. + pub fn append_fixed64(&mut self, value: u64) { + let mut buf: [u8; 8] = [0; 8]; + pb_write_fixed64(value, &mut buf); + self.append_bytes(&buf); + } + + /// Append varint field to message. + pub fn append_type0_field(&mut self, field_id: u32, value: u64) { + const BUF_SIZE: usize = PB_VARINT_MAX_SIZE_32 + PB_VARINT_MAX_SIZE_64; + let mut buf: [u8; BUF_SIZE] = [0; BUF_SIZE]; + let tag = pb_make_tag(field_id, PbWireType::Varint); + let mut written = pb_write_varint(tag.into(), &mut buf); + written += pb_write_varint(value, &mut buf[written..]); + self.append_bytes(&buf[..written]); + } + + /// Append delimited field to message. + pub fn append_type2_field(&mut self, field_id: u32, data: &[u8]) { + const BUF_SIZE: usize = PB_VARINT_MAX_SIZE_32 + PB_VARINT_MAX_SIZE_64; + let mut buf: [u8; BUF_SIZE] = [0; BUF_SIZE]; + let tag = pb_make_tag(field_id, PbWireType::Delimited); + let mut written = pb_write_varint(tag.into(), &mut buf); + written += pb_write_varint(data.len() as u64, &mut buf[written..]); + self.append_bytes(&buf[..written]); + self.append_bytes(data); + } + + /// Append fixed32 field to message. + pub fn append_fixed32_field(&mut self, field_id: u32, value: u32) { + const BUF_SIZE: usize = PB_VARINT_MAX_SIZE_32 + 4; + let mut buf: [u8; BUF_SIZE] = [0; BUF_SIZE]; + let tag = pb_make_tag(field_id, PbWireType::Fixed32); + let mut written = pb_write_varint(tag.into(), &mut buf); + written += pb_write_fixed32(value, &mut buf[written..]); + self.append_bytes(&buf[..written]); + } + + /// Append float field to message. + pub fn append_float_field(&mut self, field_id: u32, value: f32) { + self.append_fixed32_field(field_id, pb_float_to_fixed32(value)); + } + + /// Append fixed64 field to message. + pub fn append_fixed64_field(&mut self, field_id: u32, value: u64) { + const BUF_SIZE: usize = PB_VARINT_MAX_SIZE_32 + 8; + let mut buf: [u8; BUF_SIZE] = [0; BUF_SIZE]; + let tag = pb_make_tag(field_id, PbWireType::Fixed64); + let mut written = pb_write_varint(tag.into(), &mut buf); + written += pb_write_fixed64(value, &mut buf[written..]); + self.append_bytes(&buf[..written]); + } + + /// Append doubles field to message. + pub fn append_double_field(&mut self, field_id: u32, value: f64) { + self.append_fixed64_field(field_id, pb_double_to_fixed64(value)); + } + + /// Append C string field to message. + pub fn append_cstr_field(&mut self, field_id: u32, c_str: &str) { + self.append_type2_field(field_id, c_str.as_bytes()); + } + + /// Append nested message to message. + pub fn append_nested(&mut self, field_id: u32, mut cb: F) + where + F: FnMut(&mut PbMsg), + { + let tag = pb_make_tag(field_id, PbWireType::Delimited); + self.append_varint(tag.into()); + if crate::__unlikely!( + PROTOZERO_MESSAGE_LENGTH_FIELD_SIZE > self.writer.writer.available_bytes() + ) { + self.size_field.borrow_mut().patch_stack(self.writer); + } + let size_field_bytes = self + .writer + .writer + .reserve_bytes(PROTOZERO_MESSAGE_LENGTH_FIELD_SIZE); + self.size += PROTOZERO_MESSAGE_LENGTH_FIELD_SIZE; + let mut nested = PbMsg { + size_field: Rc::new(RefCell::new(PbMsgSizeField { + ptr: size_field_bytes.as_mut_ptr(), + parent: Rc::downgrade(&self.size_field), + })), + size: 0, + writer: self.writer, + }; + cb(&mut nested); + self.size += nested.finalize(); + } + + /// Finalize message and return size. + pub fn finalize(&mut self) -> usize { + // Write the length of the nested message a posteriori, using a leading-zero + // redundant varint encoding. + if !self.size_field.borrow().ptr.is_null() { + let mut size_to_write = self.size; + for i in 0..PROTOZERO_MESSAGE_LENGTH_FIELD_SIZE { + let msb: u8 = if i < 3 { 0x80 } else { 0 }; + // SAFETY: `self.size_field` must point to a + // `PROTOZERO_MESSAGE_LENGTH_FIELD_SIZE` sized buffer. + unsafe { + self.size_field + .borrow_mut() + .ptr + .add(i) + .write((size_to_write & 0xff) as u8 | msb) + }; + size_to_write >>= 7; + } + self.size_field.borrow_mut().ptr = ptr::null_mut(); + } + self.size + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::heap_buffer::HeapBuffer; + use std::error::Error; + + #[test] + fn append_bytes() -> Result<(), Box> { + let writer = PbMsgWriter::new(); + let hb = HeapBuffer::new(&writer.writer); + let mut msg = PbMsg::new(&writer)?; + msg.append_bytes(b"ok"); + let size = msg.finalize(); + assert_eq!(size, 2); + let written_size = writer.writer.get_written_size(); + assert_eq!(written_size, 2); + let mut result: Vec = vec![0u8; written_size]; + hb.copy_into(&mut result); + assert_eq!(result, [111, 107]); + Ok(()) + } + + #[test] + fn append_nested() -> Result<(), Box> { + let writer = PbMsgWriter::new(); + let hb = HeapBuffer::new(&writer.writer); + let mut msg = PbMsg::new(&writer)?; + msg.append_bytes(b"foo"); + msg.append_nested(3, |msg| { + msg.append_cstr_field(10, "bar"); + }); + let size = msg.finalize(); + assert_eq!(size, 13); + let written_size = writer.writer.get_written_size(); + assert_eq!(written_size, 13); + let mut result: Vec = vec![0u8; written_size]; + hb.copy_into(&mut result); + assert_eq!(result, [ + 102, 111, 111, 26, 133, 128, 128, 0, 82, 3, 98, 97, 114 + ]); + Ok(()) + } +} diff --git a/contrib/rust-sdk/perfetto/src/pb_utils.rs b/contrib/rust-sdk/perfetto/src/pb_utils.rs new file mode 100644 index 00000000000..38b704e860c --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/pb_utils.rs @@ -0,0 +1,199 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::convert::TryFrom; + +/// Type of fields that can be found in a protobuf serialized message. +#[repr(u32)] +pub enum PbWireType { + /// Variable-length integer. + Varint = 0, + /// Fixed 8-byte value. + Fixed64 = 1, + /// Length-delimited. Prefixed by a varint length, followed by that many bytes. + Delimited = 2, + /// Fixed 4-byte value. + Fixed32 = 5, +} + +impl TryFrom for PbWireType { + type Error = (); + + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(PbWireType::Varint), + 1 => Ok(PbWireType::Fixed64), + 2 => Ok(PbWireType::Delimited), + 5 => Ok(PbWireType::Fixed32), + _ => Err(()), + } + } +} + +/// Creates a field tag, which encodes the field type and the field id. +pub fn pb_make_tag(field_id: u32, wire_type: PbWireType) -> u32 { + (field_id << 3) | wire_type as u32 +} + +/// Maximum bytes size of a 64-bit integer encoded as a VarInt. +pub const PB_VARINT_MAX_SIZE_64: usize = 10; + +/// Maximum bytes size of a 32-bit integer encoded as a VarInt. +pub const PB_VARINT_MAX_SIZE_32: usize = 5; + +/// Encodes `value` as a VarInt into `*dst`. +/// +/// `dst` must be large enough to represent `value`: +/// PERFETTO_PB_VARINT_MAX_SIZE_* can help. +pub fn pb_write_varint(value: u64, dst: &mut [u8]) -> usize { + let mut cur_value = value; + let mut offset: usize = 0; + let mut byte: u8; + while cur_value >= 0x80 { + byte = ((cur_value & 0x7f) | 0x80) as u8; + dst[offset] = byte; + offset += 1; + cur_value >>= 7; + } + byte = (cur_value & 0x7f) as u8; + dst[offset] = byte; + offset += 1; + offset +} + +/// Encodes `value` as a fixed32 (little endian) into `*dst`. +/// +/// `dst` must have at least 4 bytes of space. +pub fn pb_write_fixed32(value: u32, dst: &mut [u8]) -> usize { + dst[0] = value as u8; + dst[1] = (value >> 8) as u8; + dst[2] = (value >> 16) as u8; + dst[3] = (value >> 24) as u8; + 4 +} + +/// Encodes `value` as a fixed64 (little endian) into `*dst`. +/// +/// `dst` must have at least 8 bytes of space. +pub fn pb_write_fixed64(value: u64, dst: &mut [u8]) -> usize { + dst[0] = value as u8; + dst[1] = (value >> 8) as u8; + dst[2] = (value >> 16) as u8; + dst[3] = (value >> 24) as u8; + dst[4] = (value >> 32) as u8; + dst[5] = (value >> 40) as u8; + dst[6] = (value >> 48) as u8; + dst[7] = (value >> 56) as u8; + 8 +} + +/// Parses a VarInt from the encoded buffer |src|. +/// The parsed int value is returned in the output arg |value|. Returns the +/// parsed int and the number of consumed bytes, or a pair of zeros if the +/// VarInt could not be fully parsed because there was not enough space in the +/// buffer. +pub fn pb_parse_varint(src: &[u8]) -> (u64, usize) { + let mut offset: usize = 0; + let mut value: u64 = 0; + let mut shift: u32 = 0; + while offset < src.len() && shift < 64 { + let byte = src[offset]; + offset += 1; + value |= ((byte & 0x7f) as u64) << shift; + if (byte & 0x80) == 0 { + // In valid cases we get here. + return (value, offset); + } + shift += 7; + } + (0, 0) +} + +/// ZigZag encodes 4-byte `value`. +pub fn pb_zigzag_encode32(value: i32) -> u32 { + ((value as u32) << 1) ^ (value >> 31) as u32 +} + +/// ZigZag encodes 8-byte `value`. +pub fn pb_zigzag_encode64(value: i64) -> u64 { + ((value as u64) << 1) ^ (value >> 63) as u64 +} + +/// ZigZag decodes 4-byte `value`. +pub fn pb_zigzag_decode32(value: u32) -> i32 { + let mask: u32 = (-((value & 1) as i32)) as u32; + ((value >> 1) ^ mask) as i32 +} + +/// ZigZag decodes 8-byte `value`. +pub fn pb_zigzag_decode64(value: u64) -> i64 { + let mask: u64 = (-((value & 1) as i64)) as u64; + ((value >> 1) ^ mask) as i64 +} + +/// Converts `value` to fixed32. +pub fn pb_float_to_fixed32(value: f32) -> u32 { + u32::from_ne_bytes(value.to_ne_bytes()) +} + +/// Converts `value` to fixed64. +pub fn pb_double_to_fixed64(value: f64) -> u64 { + u64::from_ne_bytes(value.to_ne_bytes()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn make_tag() { + assert_eq!(pb_make_tag(4, PbWireType::Fixed32), 37); + } + + #[test] + fn write_varint() { + let mut buf: [u8; PB_VARINT_MAX_SIZE_64] = [0; PB_VARINT_MAX_SIZE_64]; + assert_eq!(pb_write_varint(1234, &mut buf), 2); + assert_eq!(buf, [210, 9, 0, 0, 0, 0, 0, 0, 0, 0]); + let (value, size) = pb_parse_varint(&buf); + assert_eq!(value, 1234); + assert_eq!(size, 2); + } + + #[test] + fn write_fixed32() { + let mut buf: [u8; 4] = [0; 4]; + assert_eq!(pb_write_fixed32(0xfffffff, &mut buf), 4); + assert_eq!(buf, [255, 255, 255, 15]); + } + + #[test] + fn write_fixed64() { + let mut buf: [u8; 8] = [0; 8]; + assert_eq!(pb_write_fixed64(0xffffffffffffff, &mut buf), 8); + assert_eq!(buf, [255, 255, 255, 255, 255, 255, 255, 0]); + } + + #[test] + fn zigzag32() { + assert_eq!(pb_zigzag_encode32(-132323), 264645); + assert_eq!(pb_zigzag_decode32(264645), -132323); + } + + #[test] + fn zigzag64() { + assert_eq!(pb_zigzag_encode64(82783), 165566); + assert_eq!(pb_zigzag_decode64(165566), 82783); + } +} diff --git a/contrib/rust-sdk/perfetto/src/producer.rs b/contrib/rust-sdk/perfetto/src/producer.rs new file mode 100644 index 00000000000..44b4b1293b2 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/producer.rs @@ -0,0 +1,148 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use bitflags::bitflags; +use perfetto_sdk_sys::*; +use std::{ffi::CString, ptr, time::Duration}; +use thiserror::Error; + +/// Producer errors. +#[derive(Error, Debug, PartialEq)] +pub enum ProducerError { + /// Invalid C string. + #[error("Invalid string: {0}.")] + InvalidString(std::ffi::NulError), + /// Invalid TTL value. + #[error("Invalid TTL: {0}.")] + InvalidTTL(std::num::TryFromIntError), +} + +bitflags! { + /// Producer backend flags. + #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub struct Backends: u32 { + /// The in-process tracing backend. Keeps trace buffers in the process memory. + const IN_PROCESS = 0b00000001; + /// The system tracing backend. Connects to the system tracing service (e.g. + /// on Linux/Android/Mac uses a named UNIX socket). + const SYSTEM = 0b00000010; + } +} + +/// Producer arguments struct. +#[derive(Default)] +pub struct ProducerInitArgs { + backends: Backends, + shmem_size_hint_kb: u32, +} + +/// Producer arguments builder. +#[derive(Default)] +#[must_use = "This is a builder; remember to call `.build()` (or keep chaining)."] +pub struct ProducerInitArgsBuilder { + args: ProducerInitArgs, +} + +impl ProducerInitArgsBuilder { + /// Create new producer arguments builder. + pub fn new() -> Self { + Self::default() + } + + /// Set backends, or-combination of one or more of the above `Backends` flags. + #[must_use = "Builder methods return an updated builder; use the returned value or keep chaining."] + pub fn backends(mut self, backends: Backends) -> Self { + self.args.backends = backends; + self + } + + /// Tunes the size of the shared memory buffer between the current + /// process and the service backend(s). This is a trade-off between memory + /// footprint and the ability to sustain bursts of trace writes. + /// If set, the value must be a multiple of 4KB. The value can be ignored if + /// larger than kMaxShmSize (32MB) or not a multiple of 4KB. + #[must_use = "Builder methods return an updated builder; use the returned value or keep chaining."] + pub fn shmem_size_hint_kb(mut self, shmem_size_hint_kb: u32) -> Self { + self.args.shmem_size_hint_kb = shmem_size_hint_kb; + self + } + + /// Returns producer arguments struct. + pub fn build(&self) -> &ProducerInitArgs { + &self.args + } +} + +/// Opaque struct to an object that stores the initialization params. +pub struct Producer {} + +impl Producer { + /// Initializes the global perfetto producer. + /// + /// It's ok to call this function multiple times, but if a backend was already + /// initialized, most of `args` would be ignored. + pub fn init(args: &ProducerInitArgs) { + // SAFETY: FFI call with no outstanding preconditions. + let backend_args = unsafe { PerfettoProducerBackendInitArgsCreate() }; + // SAFETY: `backend_args` must have been created using + // PerfettoProducerBackendInitArgsCreate. + unsafe { + PerfettoProducerBackendInitArgsSetShmemSizeHintKb( + backend_args, + args.shmem_size_hint_kb, + ); + if args.backends.contains(Backends::IN_PROCESS) { + PerfettoProducerInProcessInit(backend_args); + } + if args.backends.contains(Backends::SYSTEM) { + PerfettoProducerSystemInit(backend_args); + } + } + // SAFETY: `backend_args` must have been created using + // PerfettoProducerBackendInitArgsCreate. + unsafe { PerfettoProducerBackendInitArgsDestroy(backend_args) }; + } + + /// Informs the tracing services to activate the single trigger `trigger_name` if + /// any tracing session was waiting for it. + /// + /// Sends the trigger signal to all the initialized backends that are currently + /// connected and that connect in the next `ttl_ms` milliseconds (but + /// returns immediately anyway). + pub fn activate_trigger(trigger_name: &str, ttl: Duration) -> Result<(), ProducerError> { + let ctrigger_name = CString::new(trigger_name).map_err(ProducerError::InvalidString)?; + let mut trigger_names = [ctrigger_name.as_ptr(), ptr::null_mut()]; + let ttl_ms = ttl + .as_millis() + .try_into() + .map_err(ProducerError::InvalidTTL)?; + // SAFETY: `trigger_names` must be a null terminated array of C strings. + unsafe { PerfettoProducerActivateTriggers(trigger_names.as_mut_ptr(), ttl_ms) }; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::acquire_test_environment; + use std::error::Error; + + #[test] + fn activate_trigger() -> Result<(), Box> { + let _lock = acquire_test_environment(); + Producer::activate_trigger("trigger_name", Duration::from_millis(10))?; + Ok(()) + } +} diff --git a/contrib/rust-sdk/perfetto/src/protos/common/builtin_clock.pz.rs b/contrib/rust-sdk/perfetto/src/protos/common/builtin_clock.pz.rs new file mode 100644 index 00000000000..f99593b57ec --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/common/builtin_clock.pz.rs @@ -0,0 +1,32 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; + +pb_enum!(BuiltinClock { + BUILTIN_CLOCK_UNKNOWN: 0, + BUILTIN_CLOCK_REALTIME: 1, + BUILTIN_CLOCK_REALTIME_COARSE: 2, + BUILTIN_CLOCK_MONOTONIC: 3, + BUILTIN_CLOCK_MONOTONIC_COARSE: 4, + BUILTIN_CLOCK_MONOTONIC_RAW: 5, + BUILTIN_CLOCK_BOOTTIME: 6, + BUILTIN_CLOCK_TSC: 9, + BUILTIN_CLOCK_PERF: 10, + BUILTIN_CLOCK_MAX_ID: 63, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/common/data_source_descriptor.pz.rs b/contrib/rust-sdk/perfetto/src/protos/common/data_source_descriptor.pz.rs new file mode 100644 index 00000000000..4c831a1beeb --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/common/data_source_descriptor.pz.rs @@ -0,0 +1,29 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Manually generated with bindings for a limited set of +// DataSourceDescriptor fields to limit core proto bindings. + +use crate::pb_msg; +use crate::protos::common::track_event_descriptor::*; + +pb_msg!(DataSourceDescriptor { + name: String, primitive, 1, + id: u64, primitive, 7, + will_notify_on_stop: bool, primitive, 2, + will_notify_on_start: bool, primitive, 3, + handles_incremental_state_clear: bool, primitive, 4, + no_flush: bool, primitive, 9, + track_event_descriptor: TrackEventDescriptor, msg, 6, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/common/mod.rs b/contrib/rust-sdk/perfetto/src/protos/common/mod.rs new file mode 100644 index 00000000000..b59c14c95cc --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/common/mod.rs @@ -0,0 +1,28 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the gen_rust_protos script. +// DO NOT EDIT. + +/// `builtin_clock` protos. +#[path = "builtin_clock.pz.rs"] +pub mod builtin_clock; + +/// `data_source_descriptor` protos. +#[path = "data_source_descriptor.pz.rs"] +pub mod data_source_descriptor; + +/// `track_event_descriptor` protos. +#[path = "track_event_descriptor.pz.rs"] +pub mod track_event_descriptor; diff --git a/contrib/rust-sdk/perfetto/src/protos/common/track_event_descriptor.pz.rs b/contrib/rust-sdk/perfetto/src/protos/common/track_event_descriptor.pz.rs new file mode 100644 index 00000000000..98e63f3ea9e --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/common/track_event_descriptor.pz.rs @@ -0,0 +1,29 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(TrackEventDescriptor { + available_categories: TrackEventCategory, msg, 1, +}); + +pb_msg!(TrackEventCategory { + name: String, primitive, 1, + description: String, primitive, 2, + tags: String, primitive, 3, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/config/data_source_config.pz.rs b/contrib/rust-sdk/perfetto/src/protos/config/data_source_config.pz.rs new file mode 100644 index 00000000000..c2e93685d99 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/config/data_source_config.pz.rs @@ -0,0 +1,47 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Manually generated with bindings for a limited set of DataSourceConfig +// fields to limit core proto bindings. + +use crate::pb_enum; +use crate::pb_msg; +use crate::protos::config::test_config::*; +use crate::protos::config::track_event::track_event_config::*; + +pb_enum!(DataSourceConfigSessionInitiator { + SESSION_INITIATOR_UNSPECIFIED: 0, + SESSION_INITIATOR_TRUSTED_SYSTEM: 1, +}); + +pb_enum!(DataSourceConfigBufferExhaustedPolicy { + BUFFER_EXHAUSTED_UNSPECIFIED: 0, + BUFFER_EXHAUSTED_DROP: 1, + BUFFER_EXHAUSTED_STALL_THEN_ABORT: 2, + BUFFER_EXHAUSTED_STALL_THEN_DROP: 3, +}); + +pb_msg!(DataSourceConfig { + name: String, primitive, 1, + target_buffer: u32, primitive, 2, + trace_duration_ms: u32, primitive, 3, + prefer_suspend_clock_for_duration: bool, primitive, 122, + stop_timeout_ms: u32, primitive, 7, + enable_extra_guardrails: bool, primitive, 6, + session_initiator: DataSourceConfigSessionInitiator, enum, 8, + tracing_session_id: u64, primitive, 4, + buffer_exhausted_policy: DataSourceConfigBufferExhaustedPolicy, enum, 9, + track_event_config: TrackEventConfig, msg, 113, + for_testing: TestConfig, msg, 1001, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/config/mod.rs b/contrib/rust-sdk/perfetto/src/protos/config/mod.rs new file mode 100644 index 00000000000..0461bda8b45 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/config/mod.rs @@ -0,0 +1,34 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the gen_rust_protos script. +// DO NOT EDIT. + +/// `data_source_config` protos. +#[path = "data_source_config.pz.rs"] +pub mod data_source_config; + +/// `priority_boost` protos. +pub mod priority_boost; + +/// `test_config` protos. +#[path = "test_config.pz.rs"] +pub mod test_config; + +/// `trace_config` protos. +#[path = "trace_config.pz.rs"] +pub mod trace_config; + +/// `track_event` protos. +pub mod track_event; diff --git a/contrib/rust-sdk/perfetto/src/protos/config/priority_boost/mod.rs b/contrib/rust-sdk/perfetto/src/protos/config/priority_boost/mod.rs new file mode 100644 index 00000000000..2249762f58a --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/config/priority_boost/mod.rs @@ -0,0 +1,20 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the gen_rust_protos script. +// DO NOT EDIT. + +/// `priority_boost_config` protos. +#[path = "priority_boost_config.pz.rs"] +pub mod priority_boost_config; diff --git a/contrib/rust-sdk/perfetto/src/protos/config/priority_boost/priority_boost_config.pz.rs b/contrib/rust-sdk/perfetto/src/protos/config/priority_boost/priority_boost_config.pz.rs new file mode 100644 index 00000000000..cc6218213b7 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/config/priority_boost/priority_boost_config.pz.rs @@ -0,0 +1,31 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; + +pb_enum!(PriorityBoostConfigBoostPolicy { + POLICY_UNSPECIFIED: 0, + POLICY_SCHED_OTHER: 1, + POLICY_SCHED_FIFO: 2, +}); + +pb_msg!(PriorityBoostConfig { + policy: PriorityBoostConfigBoostPolicy, enum, 1, + priority: u32, primitive, 2, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/config/test_config.pz.rs b/contrib/rust-sdk/perfetto/src/protos/config/test_config.pz.rs new file mode 100644 index 00000000000..0e89e8bf70d --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/config/test_config.pz.rs @@ -0,0 +1,45 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(TestConfig { + message_count: u32, primitive, 1, + max_messages_per_second: u32, primitive, 2, + seed: u32, primitive, 3, + message_size: u32, primitive, 4, + send_batch_on_register: bool, primitive, 5, + dummy_fields: DummyFields, msg, 6, +}); + +pb_msg!(DummyFields { + field_uint32: u32, primitive, 1, + field_int32: i32, primitive, 2, + field_uint64: u64, primitive, 3, + field_int64: i64, primitive, 4, + field_fixed64: u64, primitive, 5, + field_sfixed64: i64, primitive, 6, + field_fixed32: u32, primitive, 7, + field_sfixed32: i32, primitive, 8, + field_double: f64, primitive, 9, + field_float: f32, primitive, 10, + field_sint64: i64, primitive, 11, + field_sint32: i32, primitive, 12, + field_string: String, primitive, 13, + field_bytes: String, primitive, 14, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/config/trace_config.pz.rs b/contrib/rust-sdk/perfetto/src/protos/config/trace_config.pz.rs new file mode 100644 index 00000000000..afa74f645a8 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/config/trace_config.pz.rs @@ -0,0 +1,206 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; +use crate::protos::common::builtin_clock::*; +use crate::protos::config::data_source_config::*; +use crate::protos::config::priority_boost::priority_boost_config::*; + +pb_enum!(TraceConfigLockdownModeOperation { + LOCKDOWN_UNCHANGED: 0, + LOCKDOWN_CLEAR: 1, + LOCKDOWN_SET: 2, +}); + +pb_enum!(TraceConfigCompressionType { + COMPRESSION_TYPE_UNSPECIFIED: 0, + COMPRESSION_TYPE_DEFLATE: 1, +}); + +pb_enum!(TraceConfigStatsdLogging { + STATSD_LOGGING_UNSPECIFIED: 0, + STATSD_LOGGING_ENABLED: 1, + STATSD_LOGGING_DISABLED: 2, +}); + +pb_enum!(TraceFilterStringFilterPolicy { + SFP_UNSPECIFIED: 0, + SFP_MATCH_REDACT_GROUPS: 1, + SFP_ATRACE_MATCH_REDACT_GROUPS: 2, + SFP_MATCH_BREAK: 3, + SFP_ATRACE_MATCH_BREAK: 4, + SFP_ATRACE_REPEATED_SEARCH_REDACT_GROUPS: 5, +}); + +pb_enum!(TriggerConfigTriggerMode { + UNSPECIFIED: 0, + START_TRACING: 1, + STOP_TRACING: 2, + CLONE_SNAPSHOT: 4, +}); + +pb_enum!(BufferConfigFillPolicy { + UNSPECIFIED: 0, + RING_BUFFER: 1, + DISCARD: 2, +}); + +pb_msg!(TraceConfig { + buffers: BufferConfig, msg, 1, + data_sources: DataSource, msg, 2, + builtin_data_sources: BuiltinDataSource, msg, 20, + duration_ms: u32, primitive, 3, + prefer_suspend_clock_for_duration: bool, primitive, 36, + enable_extra_guardrails: bool, primitive, 4, + lockdown_mode: TraceConfigLockdownModeOperation, enum, 5, + producers: ProducerConfig, msg, 6, + statsd_metadata: StatsdMetadata, msg, 7, + write_into_file: bool, primitive, 8, + output_path: String, primitive, 29, + file_write_period_ms: u32, primitive, 9, + max_file_size_bytes: u64, primitive, 10, + guardrail_overrides: GuardrailOverrides, msg, 11, + deferred_start: bool, primitive, 12, + flush_period_ms: u32, primitive, 13, + flush_timeout_ms: u32, primitive, 14, + data_source_stop_timeout_ms: u32, primitive, 23, + notify_traceur: bool, primitive, 16, + bugreport_score: i32, primitive, 30, + bugreport_filename: String, primitive, 38, + trigger_config: TriggerConfig, msg, 17, + activate_triggers: String, primitive, 18, + incremental_state_config: IncrementalStateConfig, msg, 21, + allow_user_build_tracing: bool, primitive, 19, + unique_session_name: String, primitive, 22, + compression_type: TraceConfigCompressionType, enum, 24, + incident_report_config: IncidentReportConfig, msg, 25, + statsd_logging: TraceConfigStatsdLogging, enum, 31, + trace_uuid_msb: i64, primitive, 27, + trace_uuid_lsb: i64, primitive, 28, + trace_filter: TraceFilter, msg, 33, + android_report_config: AndroidReportConfig, msg, 34, + cmd_trace_start_delay: CmdTraceStartDelay, msg, 35, + session_semaphores: SessionSemaphore, msg, 39, + priority_boost: PriorityBoostConfig, msg, 40, + exclusive_prio: u32, primitive, 41, + no_flush_before_write_into_file: bool, primitive, 42, +}); + +pb_msg!(SessionSemaphore { + name: String, primitive, 1, + max_other_session_count: u64, primitive, 2, +}); + +pb_msg!(CmdTraceStartDelay { + min_delay_ms: u32, primitive, 1, + max_delay_ms: u32, primitive, 2, +}); + +pb_msg!(AndroidReportConfig { + reporter_service_package: String, primitive, 1, + reporter_service_class: String, primitive, 2, + skip_report: bool, primitive, 3, + use_pipe_in_framework_for_testing: bool, primitive, 4, +}); + +pb_msg!(TraceFilter { + bytecode: String, primitive, 1, + bytecode_v2: String, primitive, 2, + string_filter_chain: StringFilterChain, msg, 3, +}); + +pb_msg!(StringFilterChain { + rules: StringFilterRule, msg, 1, +}); + +pb_msg!(StringFilterRule { + policy: TraceFilterStringFilterPolicy, enum, 1, + regex_pattern: String, primitive, 2, + atrace_payload_starts_with: String, primitive, 3, +}); + +pb_msg!(IncidentReportConfig { + destination_package: String, primitive, 1, + destination_class: String, primitive, 2, + privacy_level: i32, primitive, 3, + skip_incidentd: bool, primitive, 5, + skip_dropbox: bool, primitive, 4, +}); + +pb_msg!(IncrementalStateConfig { + clear_period_ms: u32, primitive, 1, +}); + +pb_msg!(TriggerConfig { + trigger_mode: TriggerConfigTriggerMode, enum, 1, + use_clone_snapshot_if_available: bool, primitive, 5, + triggers: Trigger, msg, 2, + trigger_timeout_ms: u32, primitive, 3, +}); + +pb_msg!(Trigger { + name: String, primitive, 1, + producer_name_regex: String, primitive, 2, + stop_delay_ms: u32, primitive, 3, + max_per_24_h: u32, primitive, 4, + skip_probability: f64, primitive, 5, +}); + +pb_msg!(GuardrailOverrides { + max_upload_per_day_bytes: u64, primitive, 1, + max_tracing_buffer_size_kb: u32, primitive, 2, +}); + +pb_msg!(StatsdMetadata { + triggering_alert_id: i64, primitive, 1, + triggering_config_uid: i32, primitive, 2, + triggering_config_id: i64, primitive, 3, + triggering_subscription_id: i64, primitive, 4, +}); + +pb_msg!(ProducerConfig { + producer_name: String, primitive, 1, + shm_size_kb: u32, primitive, 2, + page_size_kb: u32, primitive, 3, +}); + +pb_msg!(BuiltinDataSource { + disable_clock_snapshotting: bool, primitive, 1, + disable_trace_config: bool, primitive, 2, + disable_system_info: bool, primitive, 3, + disable_service_events: bool, primitive, 4, + primary_trace_clock: BuiltinClock, enum, 5, + snapshot_interval_ms: u32, primitive, 6, + prefer_suspend_clock_for_snapshot: bool, primitive, 7, + disable_chunk_usage_histograms: bool, primitive, 8, +}); + +pb_msg!(DataSource { + config: DataSourceConfig, msg, 1, + producer_name_filter: String, primitive, 2, + producer_name_regex_filter: String, primitive, 3, + machine_name_filter: String, primitive, 4, +}); + +pb_msg!(BufferConfig { + size_kb: u32, primitive, 1, + fill_policy: BufferConfigFillPolicy, enum, 4, + transfer_on_clone: bool, primitive, 5, + clear_before_clone: bool, primitive, 6, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/config/track_event/mod.rs b/contrib/rust-sdk/perfetto/src/protos/config/track_event/mod.rs new file mode 100644 index 00000000000..8f526b44f1a --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/config/track_event/mod.rs @@ -0,0 +1,20 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the gen_rust_protos script. +// DO NOT EDIT. + +/// `track_event_config` protos. +#[path = "track_event_config.pz.rs"] +pub mod track_event_config; diff --git a/contrib/rust-sdk/perfetto/src/protos/config/track_event/track_event_config.pz.rs b/contrib/rust-sdk/perfetto/src/protos/config/track_event/track_event_config.pz.rs new file mode 100644 index 00000000000..1cf70636a63 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/config/track_event/track_event_config.pz.rs @@ -0,0 +1,32 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(TrackEventConfig { + disabled_categories: String, primitive, 1, + enabled_categories: String, primitive, 2, + disabled_tags: String, primitive, 3, + enabled_tags: String, primitive, 4, + disable_incremental_timestamps: bool, primitive, 5, + timestamp_unit_multiplier: u64, primitive, 6, + filter_debug_annotations: bool, primitive, 7, + enable_thread_time_sampling: bool, primitive, 8, + thread_time_subsampling_ns: u64, primitive, 10, + filter_dynamic_event_names: bool, primitive, 9, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/mod.rs b/contrib/rust-sdk/perfetto/src/protos/mod.rs new file mode 100644 index 00000000000..1741969678b --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/mod.rs @@ -0,0 +1,282 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// `common` protobufs. +pub mod common; + +/// `config` protobufs. +pub mod config; + +/// `trace` protobufs. +#[allow(clippy::module_inception)] +pub mod trace; + +/// Defines a protobuf enum. +#[macro_export] +macro_rules! pb_enum { + ( + $name:ident { + $( $entry:ident : $id:literal ),+ $(,)? + } + ) => { + paste::paste! { + #[doc = concat!("Protobuf enum for `", stringify!($name), "`")] + #[allow(non_camel_case_types)] + #[repr(u32)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum $name { + $( + #[doc = concat!("Variant for `", stringify!($entry), "`")] + [<$entry:camel>] = $id + ),* + } + + impl From<$name> for u32 { + #[inline] + fn from(v: $name) -> u32 { v as u32 } + } + + impl TryFrom for $name { + type Error = (); + fn try_from(v: u32) -> Result { + match v { + $( + $id => Ok([<$name>]::[<$entry:camel>]), + )* + _ => Err(()), + } + } + } + } + }; +} + +/// Defines a protobuf message. +/// +/// Defines the type for a protobuf message. `name` is the name of the message type. +#[macro_export] +macro_rules! pb_msg { + ( + $name:ident { + $( $field:ident : $tp:tt, $kind:ident, $id:literal ),+ $(,)? + } + ) => { + paste::paste! { + #[doc = concat!("Protobuf field numbers for `", stringify!($name), "`")] + #[repr(u32)] + pub enum [<$name:camel FieldNumber>] { + $( + #[doc = concat!("Field number for `", stringify!($field), "`")] + [<$field:camel>] = $id + ),* + } + } + + paste::paste! { + #[doc = concat!("Protobuf message struct for `", stringify!($name), "`")] + #[allow(non_camel_case_types)] + pub struct $name<'a, 'b> { + #[doc = concat!("PbMsg for protobuf message `", stringify!($name), "`")] + pub msg: &'a mut $crate::pb_msg::PbMsg<'b>, + } + } + + impl<'a, 'b> $name<'a, 'b> { + $( + pb_msg!(@setter pub fn $name, $field, $id, $kind, $tp); + )* + } + }; + + // Cstr + (@decl $vis:vis fn $name:ident, $field:ident, $id: literal, primitive, String) => { + paste::paste! { + #[doc = concat!("Set `", stringify!($field), "` field")] + $vis fn [] (&mut self, value: impl Into) -> &mut Self; + } + }; + (@setter $vis:vis fn $name:ident, $field:ident, $id: literal, primitive, String) => { + paste::paste! { + #[doc = concat!("Set `", stringify!($field), "` field")] + $vis fn [] (&mut self, value: impl Into) -> &mut Self { + let s: String = value.into(); + self.msg.append_type2_field($id, s.as_bytes()); + self + } + } + }; + + // float + (@decl $vis:vis fn $name:ident, $field:ident, $id: literal, primitive, String) => { + paste::paste! { + #[doc = concat!("Set `", stringify!($field), "` field")] + $vis fn [] (&mut self, value: f32) -> &mut Self; + } + }; + (@setter $vis:vis fn $name:ident, $field:ident, $id: literal, primitive, f32) => { + paste::paste! { + #[doc = concat!("Set `", stringify!($field), "` field")] + $vis fn [] (&mut self, value: f32) -> &mut Self { + self.msg.append_float_field($id, value); + self + } + } + }; + + // double + (@decl $vis:vis fn $name:ident, $field:ident, $id: literal, primitive, String) => { + paste::paste! { + #[doc = concat!("Set `", stringify!($field), "` field")] + $vis fn [] (&mut self, value: f64) -> &mut Self; + } + }; + (@setter $vis:vis fn $name:ident, $field:ident, $id: literal, primitive, f64) => { + paste::paste! { + #[doc = concat!("Set `", stringify!($field), "` field")] + $vis fn [] (&mut self, value: f64) -> &mut Self { + self.msg.append_double_field($id, value); + self + } + } + }; + + // Varint + (@decl $vis:vis fn $name:ident, $field:ident, $id: literal, primitive, u32) => { + pb_msg!(@varint_decl $vis fn $name, $field, $id, u32); + }; + (@setter $vis:vis fn $name:ident, $field:ident, $id: literal, primitive, u32) => { + pb_msg!(@varint_setter $vis fn $name, $field, $id, u32); + }; + (@decl $vis:vis fn $name:ident, $field:ident, $id: literal, primitive, u64) => { + pb_msg!(@varint_decl $vis fn $name, $field, $id, u64); + }; + (@setter $vis:vis fn $name:ident, $field:ident, $id: literal, primitive, u64) => { + pb_msg!(@varint_setter $vis fn $name, $field, $id, u64); + }; + (@decl $vis:vis fn $name:ident, $field:ident, $id: literal, primitive, i32) => { + pb_msg!(@varint_decl $vis fn $name, $field, $id, i32); + }; + (@setter $vis:vis fn $name:ident, $field:ident, $id: literal, primitive, i32) => { + pb_msg!(@varint_setter $vis fn $name, $field, $id, i32); + }; + (@decl $vis:vis fn $name:ident, $field:ident, $id: literal, primitive, i64) => { + pb_msg!(@varint_decl $vis fn $name, $field, $id, i64); + }; + (@setter $vis:vis fn $name:ident, $field:ident, $id: literal, primitive, i64) => { + pb_msg!(@varint_setter $vis fn $name, $field, $id, i64); + }; + (@decl $vis:vis fn $name:ident, $field:ident, $id: literal, primitive, bool) => { + pb_msg!(@varint_decl $vis fn $name, $field, $id, bool); + }; + (@setter $vis:vis fn $name:ident, $field:ident, $id: literal, primitive, bool) => { + pb_msg!(@varint_setter $vis fn $name, $field, $id, bool); + }; + + (@varint_decl $vis:vis fn $name:ident, $field:ident, $id: literal, $tp:tt) => { + paste::paste! { + #[doc = concat!("Set `", stringify!($field), "` field")] + $vis fn [] (&mut self, value: $tp) -> &mut Self; + } + }; + (@varint_setter $vis:vis fn $name:ident, $field:ident, $id: literal, $tp:tt) => { + paste::paste! { + #[doc = concat!("Set `", stringify!($field), "` field")] + $vis fn [] (&mut self, value: $tp) -> &mut Self { + self.msg.append_type0_field($id, value as u64); + self + } + } + }; + + // Enum + (@decl $vis:vis fn $name:ident, $field:ident, $id: literal, enum, $tp:tt) => { + paste::paste! { + #[doc = concat!("Set `", stringify!($field), "` field")] + $vis fn [] (&mut self, value: $tp) -> &mut Self; + } + }; + (@setter $vis:vis fn $name:ident, $field:ident, $id: literal, enum, $tp:tt) => { + paste::paste! { + #[doc = concat!("Set `", stringify!($field), "` field")] + $vis fn [] (&mut self, value: $tp) -> &mut Self { + self.msg.append_type0_field($id, value as u64); + self + } + } + }; + + // Fallback to message + (@decl $vis:vis fn $name:ident, $field:ident, $id: literal, msg, $tp:tt) => { + paste::paste! { + #[doc = concat!("Set `", stringify!($field), "` field")] + $vis fn [] (&mut self, cb: F) -> &mut Self + where + F: for<'p> Fn(&'p mut $tp); + } + }; + (@setter $vis:vis fn $name:ident, $field:ident, $id: literal, msg, $tp:tt) => { + paste::paste! { + #[doc = concat!("Set `", stringify!($field), "` field")] + $vis fn [] (&mut self, cb: F) -> &mut Self + where + F: for<'p> Fn(&'p mut $tp), + { + self.msg.append_nested($id, |nested_msg| { + let mut msg_field: $tp<'_, '_> = $tp { + msg: nested_msg, + }; + cb(&mut msg_field); + }); + self + } + } + }; +} + +/// Defines extra fields for a protobuf message. +#[macro_export] +macro_rules! pb_msg_ext { + ( + $name:ident { + $( $field:ident : $tp:tt, $kind:ident, $id:literal ),+ $(,)? + } + ) => { + paste::paste! { + #[doc = concat!("Protobuf extra field numbers for `", stringify!($name), "`")] + #[repr(u32)] + pub enum [<$name:camel ExtFieldNumber>] { + $( + #[doc = concat!("Field number for `", stringify!($field), "`")] + [<$field:camel>] = $id + ),* + } + } + + paste::paste! { + #[doc = concat!("Protobuf extra message trait for `", stringify!($name), "`")] + #[allow(non_camel_case_types)] + pub trait [<$name Ext>]<'a, 'b> { + $( + pb_msg!(@decl fn $name, $field, $id, $kind, $tp); + )* + } + + impl<'a, 'b> [<$name Ext>]<'_, '_> for $name<'a, 'b> { + $( + pb_msg!(@setter fn $name, $field, $id, $kind, $tp); + )* + } + } + }; +} diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/clock_snapshot.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/clock_snapshot.pz.rs new file mode 100644 index 00000000000..0f1f56d4147 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/clock_snapshot.pz.rs @@ -0,0 +1,44 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; +use crate::protos::common::builtin_clock::*; + +pb_enum!(ClockBuiltinClocks { + UNKNOWN: 0, + REALTIME: 1, + REALTIME_COARSE: 2, + MONOTONIC: 3, + MONOTONIC_COARSE: 4, + MONOTONIC_RAW: 5, + BOOTTIME: 6, + BUILTIN_CLOCK_MAX_ID: 63, +}); + +pb_msg!(ClockSnapshot { + clocks: Clock, msg, 1, + primary_trace_clock: BuiltinClock, enum, 2, +}); + +pb_msg!(Clock { + clock_id: u32, primitive, 1, + timestamp: u64, primitive, 2, + is_incremental: bool, primitive, 3, + unit_multiplier_ns: u64, primitive, 4, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/interned_data/interned_data.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/interned_data/interned_data.pz.rs new file mode 100644 index 00000000000..4418168c325 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/interned_data/interned_data.pz.rs @@ -0,0 +1,34 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Manually generated with bindings for a limited set of InternedData +// fields to limit core proto bindings. + +use crate::pb_msg; +use crate::protos::trace::track_event::chrome_histogram_sample::*; +use crate::protos::trace::track_event::debug_annotation::*; +use crate::protos::trace::track_event::log_message::*; +use crate::protos::trace::track_event::source_location::*; +use crate::protos::trace::track_event::track_event::*; + +pb_msg!(InternedData { + event_categories: EventCategory, msg, 1, + event_names: EventName, msg, 2, + debug_annotation_names: DebugAnnotationName, msg, 3, + debug_annotation_value_type_names: DebugAnnotationValueTypeName, msg, 27, + source_locations: SourceLocation, msg, 4, + unsymbolized_source_locations: UnsymbolizedSourceLocation, msg, 28, + log_message_body: LogMessageBody, msg, 20, + histogram_names: HistogramName, msg, 25, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/interned_data/mod.rs b/contrib/rust-sdk/perfetto/src/protos/trace/interned_data/mod.rs new file mode 100644 index 00000000000..402955c987d --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/interned_data/mod.rs @@ -0,0 +1,20 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the gen_rust_protos script. +// DO NOT EDIT. + +/// `interned_data` protos. +#[path = "interned_data.pz.rs"] +pub mod interned_data; diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/mod.rs b/contrib/rust-sdk/perfetto/src/protos/trace/mod.rs new file mode 100644 index 00000000000..086d5c8d2ef --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/mod.rs @@ -0,0 +1,41 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the gen_rust_protos script. +// DO NOT EDIT. + +/// `clock_snapshot` protos. +#[path = "clock_snapshot.pz.rs"] +pub mod clock_snapshot; + +/// `interned_data` protos. +pub mod interned_data; + +/// `profiling` protos. +pub mod profiling; + +/// `test_event` protos. +#[path = "test_event.pz.rs"] +pub mod test_event; + +/// `trace` protos. +#[path = "trace.pz.rs"] +pub mod trace; + +/// `trace_packet` protos. +#[path = "trace_packet.pz.rs"] +pub mod trace_packet; + +/// `track_event` protos. +pub mod track_event; diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/profiling/mod.rs b/contrib/rust-sdk/perfetto/src/protos/trace/profiling/mod.rs new file mode 100644 index 00000000000..3c0e9f18c20 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/profiling/mod.rs @@ -0,0 +1,20 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the gen_rust_protos script. +// DO NOT EDIT. + +/// `profile_common` protos. +#[path = "profile_common.pz.rs"] +pub mod profile_common; diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/profiling/profile_common.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/profiling/profile_common.pz.rs new file mode 100644 index 00000000000..38053b9b429 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/profiling/profile_common.pz.rs @@ -0,0 +1,66 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(Callstack { + iid: u64, primitive, 1, + frame_ids: u64, primitive, 2, +}); + +pb_msg!(Frame { + iid: u64, primitive, 1, + function_name_id: u64, primitive, 2, + mapping_id: u64, primitive, 3, + rel_pc: u64, primitive, 4, + source_path_iid: u64, primitive, 5, + line_number: u32, primitive, 6, +}); + +pb_msg!(Mapping { + iid: u64, primitive, 1, + build_id: u64, primitive, 2, + exact_offset: u64, primitive, 8, + start_offset: u64, primitive, 3, + start: u64, primitive, 4, + end: u64, primitive, 5, + load_bias: u64, primitive, 6, + path_string_ids: u64, primitive, 7, +}); + +pb_msg!(ModuleSymbols { + path: String, primitive, 1, + build_id: String, primitive, 2, + address_symbols: AddressSymbols, msg, 3, +}); + +pb_msg!(AddressSymbols { + address: u64, primitive, 1, + lines: Line, msg, 2, +}); + +pb_msg!(Line { + function_name: String, primitive, 1, + source_file_name: String, primitive, 2, + line_number: u32, primitive, 3, +}); + +pb_msg!(InternedString { + iid: u64, primitive, 1, + str: String, primitive, 2, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/test_event.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/test_event.pz.rs new file mode 100644 index 00000000000..448ed3aba16 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/test_event.pz.rs @@ -0,0 +1,38 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; +use crate::protos::trace::track_event::debug_annotation::*; + +pb_msg!(TestEvent { + str: String, primitive, 1, + seq_value: u32, primitive, 2, + counter: u64, primitive, 3, + is_last: bool, primitive, 4, + payload: TestPayload, msg, 5, +}); + +pb_msg!(TestPayload { + str: String, primitive, 1, + nested: TestPayload, msg, 2, + single_string: String, primitive, 4, + single_int: i32, primitive, 5, + repeated_ints: i32, primitive, 6, + remaining_nesting_depth: u32, primitive, 3, + debug_annotations: DebugAnnotation, msg, 7, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/trace.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/trace.pz.rs new file mode 100644 index 00000000000..d1d76086e0b --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/trace.pz.rs @@ -0,0 +1,24 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; +use crate::protos::trace::trace_packet::*; + +pb_msg!(Trace { + packet: TracePacket, msg, 1, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/trace_packet.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/trace_packet.pz.rs new file mode 100644 index 00000000000..092f9d00979 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/trace_packet.pz.rs @@ -0,0 +1,32 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Manually generated with bindings for a limited set of TracePacket +// fields to limit core proto bindings. + +use crate::pb_msg; +use crate::protos::trace::clock_snapshot::*; +use crate::protos::trace::interned_data::interned_data::*; +use crate::protos::trace::test_event::*; +use crate::protos::trace::track_event::track_event::*; + +pb_msg!(TracePacket { + timestamp: u64, primitive, 8, + timestamp_clock_id: u32, primitive, 58, + clock_snapshot: ClockSnapshot, msg, 6, + track_event: TrackEvent, msg, 11, + for_testing: TestEvent, msg, 900, + interned_data: InternedData, msg, 12, + sequence_flags: u32, primitive, 13, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_active_processes.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_active_processes.pz.rs new file mode 100644 index 00000000000..c38d8c1831d --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_active_processes.pz.rs @@ -0,0 +1,23 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(ChromeActiveProcesses { + pid: i32, primitive, 1, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_application_state_info.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_application_state_info.pz.rs new file mode 100644 index 00000000000..0155fec34f5 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_application_state_info.pz.rs @@ -0,0 +1,32 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; + +pb_enum!(ChromeApplicationStateInfoChromeApplicationState { + APPLICATION_STATE_UNKNOWN: 0, + APPLICATION_STATE_HAS_RUNNING_ACTIVITIES: 1, + APPLICATION_STATE_HAS_PAUSED_ACTIVITIES: 2, + APPLICATION_STATE_HAS_STOPPED_ACTIVITIES: 3, + APPLICATION_STATE_HAS_DESTROYED_ACTIVITIES: 4, +}); + +pb_msg!(ChromeApplicationStateInfo { + application_state: ChromeApplicationStateInfoChromeApplicationState, enum, 1, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_compositor_scheduler_state.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_compositor_scheduler_state.pz.rs new file mode 100644 index 00000000000..67481a4aced --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_compositor_scheduler_state.pz.rs @@ -0,0 +1,238 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; +use crate::protos::trace::track_event::source_location::*; + +pb_enum!(ChromeCompositorSchedulerAction { + CC_SCHEDULER_ACTION_UNSPECIFIED: 0, + CC_SCHEDULER_ACTION_NONE: 1, + CC_SCHEDULER_ACTION_SEND_BEGIN_MAIN_FRAME: 2, + CC_SCHEDULER_ACTION_COMMIT: 3, + CC_SCHEDULER_ACTION_ACTIVATE_SYNC_TREE: 4, + CC_SCHEDULER_ACTION_DRAW_IF_POSSIBLE: 5, + CC_SCHEDULER_ACTION_DRAW_FORCED: 6, + CC_SCHEDULER_ACTION_DRAW_ABORT: 7, + CC_SCHEDULER_ACTION_BEGIN_LAYER_TREE_FRAME_SINK_CREATION: 8, + CC_SCHEDULER_ACTION_PREPARE_TILES: 9, + CC_SCHEDULER_ACTION_INVALIDATE_LAYER_TREE_FRAME_SINK: 10, + CC_SCHEDULER_ACTION_PERFORM_IMPL_SIDE_INVALIDATION: 11, + CC_SCHEDULER_ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_EXPECTED_UNTIL: 12, + CC_SCHEDULER_ACTION_NOTIFY_BEGIN_MAIN_FRAME_NOT_EXPECTED_SOON: 13, +}); + +pb_enum!(BeginImplFrameArgsState { + BEGIN_FRAME_FINISHED: 0, + BEGIN_FRAME_USING: 1, +}); + +pb_enum!(BeginFrameArgsBeginFrameArgsType { + BEGIN_FRAME_ARGS_TYPE_UNSPECIFIED: 0, + BEGIN_FRAME_ARGS_TYPE_INVALID: 1, + BEGIN_FRAME_ARGS_TYPE_NORMAL: 2, + BEGIN_FRAME_ARGS_TYPE_MISSED: 3, +}); + +pb_enum!(MinorStateTreePriority { + TREE_PRIORITY_UNSPECIFIED: 0, + TREE_PRIORITY_SAME_PRIORITY_FOR_BOTH_TREES: 1, + TREE_PRIORITY_SMOOTHNESS_TAKES_PRIORITY: 2, + TREE_PRIORITY_NEW_CONTENT_TAKES_PRIORITY: 3, +}); + +pb_enum!(MinorStateScrollHandlerState { + SCROLL_HANDLER_UNSPECIFIED: 0, + SCROLL_AFFECTS_SCROLL_HANDLER: 1, + SCROLL_DOES_NOT_AFFECT_SCROLL_HANDLER: 2, +}); + +pb_enum!(MajorStateBeginImplFrameState { + BEGIN_IMPL_FRAME_UNSPECIFIED: 0, + BEGIN_IMPL_FRAME_IDLE: 1, + BEGIN_IMPL_FRAME_INSIDE_BEGIN_FRAME: 2, + BEGIN_IMPL_FRAME_INSIDE_DEADLINE: 3, +}); + +pb_enum!(MajorStateBeginMainFrameState { + BEGIN_MAIN_FRAME_UNSPECIFIED: 0, + BEGIN_MAIN_FRAME_IDLE: 1, + BEGIN_MAIN_FRAME_SENT: 2, + BEGIN_MAIN_FRAME_READY_TO_COMMIT: 3, +}); + +pb_enum!(MajorStateLayerTreeFrameSinkState { + LAYER_TREE_FRAME_UNSPECIFIED: 0, + LAYER_TREE_FRAME_NONE: 1, + LAYER_TREE_FRAME_ACTIVE: 2, + LAYER_TREE_FRAME_CREATING: 3, + LAYER_TREE_FRAME_WAITING_FOR_FIRST_COMMIT: 4, + LAYER_TREE_FRAME_WAITING_FOR_FIRST_ACTIVATION: 5, +}); + +pb_enum!(MajorStateForcedRedrawOnTimeoutState { + FORCED_REDRAW_UNSPECIFIED: 0, + FORCED_REDRAW_IDLE: 1, + FORCED_REDRAW_WAITING_FOR_COMMIT: 2, + FORCED_REDRAW_WAITING_FOR_ACTIVATION: 3, + FORCED_REDRAW_WAITING_FOR_DRAW: 4, +}); + +pb_enum!(ChromeCompositorSchedulerStateBeginImplFrameDeadlineMode { + DEADLINE_MODE_UNSPECIFIED: 0, + DEADLINE_MODE_NONE: 1, + DEADLINE_MODE_IMMEDIATE: 2, + DEADLINE_MODE_REGULAR: 3, + DEADLINE_MODE_LATE: 4, + DEADLINE_MODE_BLOCKED: 5, +}); + +pb_msg!(CompositorTimingHistory { + begin_main_frame_queue_critical_estimate_delta_us: i64, primitive, 1, + begin_main_frame_queue_not_critical_estimate_delta_us: i64, primitive, 2, + begin_main_frame_start_to_ready_to_commit_estimate_delta_us: i64, primitive, 3, + commit_to_ready_to_activate_estimate_delta_us: i64, primitive, 4, + prepare_tiles_estimate_delta_us: i64, primitive, 5, + activate_estimate_delta_us: i64, primitive, 6, + draw_estimate_delta_us: i64, primitive, 7, +}); + +pb_msg!(BeginFrameSourceState { + source_id: u32, primitive, 1, + paused: bool, primitive, 2, + num_observers: u32, primitive, 3, + last_begin_frame_args: BeginFrameArgs, msg, 4, +}); + +pb_msg!(BeginFrameObserverState { + dropped_begin_frame_args: i64, primitive, 1, + last_begin_frame_args: BeginFrameArgs, msg, 2, +}); + +pb_msg!(BeginImplFrameArgs { + updated_at_us: i64, primitive, 1, + finished_at_us: i64, primitive, 2, + state: BeginImplFrameArgsState, enum, 3, + current_args: BeginFrameArgs, msg, 4, + last_args: BeginFrameArgs, msg, 5, + timestamps_in_us: TimestampsInUs, msg, 6, +}); + +pb_msg!(TimestampsInUs { + interval_delta: i64, primitive, 1, + now_to_deadline_delta: i64, primitive, 2, + frame_time_to_now_delta: i64, primitive, 3, + frame_time_to_deadline_delta: i64, primitive, 4, + now: i64, primitive, 5, + frame_time: i64, primitive, 6, + deadline: i64, primitive, 7, +}); + +pb_msg!(BeginFrameArgs { + type: BeginFrameArgsBeginFrameArgsType, enum, 1, + source_id: u64, primitive, 2, + sequence_number: u64, primitive, 3, + frame_time_us: i64, primitive, 4, + deadline_us: i64, primitive, 5, + interval_delta_us: i64, primitive, 6, + on_critical_path: bool, primitive, 7, + animate_only: bool, primitive, 8, + source_location_iid: u64, primitive, 9, + source_location: SourceLocation, msg, 10, + frames_throttled_since_last: i64, primitive, 12, +}); + +pb_msg!(ChromeCompositorStateMachine { + major_state: MajorState, msg, 1, + minor_state: MinorState, msg, 2, +}); + +pb_msg!(MinorState { + commit_count: i32, primitive, 1, + current_frame_number: i32, primitive, 2, + last_frame_number_submit_performed: i32, primitive, 3, + last_frame_number_draw_performed: i32, primitive, 4, + last_frame_number_begin_main_frame_sent: i32, primitive, 5, + did_draw: bool, primitive, 6, + did_send_begin_main_frame_for_current_frame: bool, primitive, 7, + did_notify_begin_main_frame_not_expected_until: bool, primitive, 8, + did_notify_begin_main_frame_not_expected_soon: bool, primitive, 9, + wants_begin_main_frame_not_expected: bool, primitive, 10, + did_commit_during_frame: bool, primitive, 11, + did_invalidate_layer_tree_frame_sink: bool, primitive, 12, + did_perform_impl_side_invalidaion: bool, primitive, 13, + did_prepare_tiles: bool, primitive, 14, + consecutive_checkerboard_animations: i32, primitive, 15, + pending_submit_frames: i32, primitive, 16, + submit_frames_with_current_layer_tree_frame_sink: i32, primitive, 17, + needs_redraw: bool, primitive, 18, + needs_prepare_tiles: bool, primitive, 19, + needs_begin_main_frame: bool, primitive, 20, + needs_one_begin_impl_frame: bool, primitive, 21, + visible: bool, primitive, 22, + begin_frame_source_paused: bool, primitive, 23, + can_draw: bool, primitive, 24, + resourceless_draw: bool, primitive, 25, + has_pending_tree: bool, primitive, 26, + pending_tree_is_ready_for_activation: bool, primitive, 27, + active_tree_needs_first_draw: bool, primitive, 28, + active_tree_is_ready_to_draw: bool, primitive, 29, + did_create_and_initialize_first_layer_tree_frame_sink: bool, primitive, 30, + tree_priority: MinorStateTreePriority, enum, 31, + scroll_handler_state: MinorStateScrollHandlerState, enum, 32, + critical_begin_main_frame_to_activate_is_fast: bool, primitive, 33, + main_thread_missed_last_deadline: bool, primitive, 34, + video_needs_begin_frames: bool, primitive, 36, + defer_begin_main_frame: bool, primitive, 37, + last_commit_had_no_updates: bool, primitive, 38, + did_draw_in_last_frame: bool, primitive, 39, + did_submit_in_last_frame: bool, primitive, 40, + needs_impl_side_invalidation: bool, primitive, 41, + current_pending_tree_is_impl_side: bool, primitive, 42, + previous_pending_tree_was_impl_side: bool, primitive, 43, + processing_animation_worklets_for_active_tree: bool, primitive, 44, + processing_animation_worklets_for_pending_tree: bool, primitive, 45, + processing_paint_worklets_for_pending_tree: bool, primitive, 46, +}); + +pb_msg!(MajorState { + next_action: ChromeCompositorSchedulerAction, enum, 1, + begin_impl_frame_state: MajorStateBeginImplFrameState, enum, 2, + begin_main_frame_state: MajorStateBeginMainFrameState, enum, 3, + layer_tree_frame_sink_state: MajorStateLayerTreeFrameSinkState, enum, 4, + forced_redraw_state: MajorStateForcedRedrawOnTimeoutState, enum, 5, +}); + +pb_msg!(ChromeCompositorSchedulerState { + state_machine: ChromeCompositorStateMachine, msg, 1, + observing_begin_frame_source: bool, primitive, 2, + begin_impl_frame_deadline_task: bool, primitive, 3, + pending_begin_frame_task: bool, primitive, 4, + skipped_last_frame_missed_exceeded_deadline: bool, primitive, 5, + inside_action: ChromeCompositorSchedulerAction, enum, 7, + deadline_mode: ChromeCompositorSchedulerStateBeginImplFrameDeadlineMode, enum, 8, + deadline_us: i64, primitive, 9, + deadline_scheduled_at_us: i64, primitive, 10, + now_us: i64, primitive, 11, + now_to_deadline_delta_us: i64, primitive, 12, + now_to_deadline_scheduled_at_delta_us: i64, primitive, 13, + begin_impl_frame_args: BeginImplFrameArgs, msg, 14, + begin_frame_observer_state: BeginFrameObserverState, msg, 15, + begin_frame_source_state: BeginFrameSourceState, msg, 16, + compositor_timing_history: CompositorTimingHistory, msg, 17, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_content_settings_event_info.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_content_settings_event_info.pz.rs new file mode 100644 index 00000000000..2ae69afe120 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_content_settings_event_info.pz.rs @@ -0,0 +1,23 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(ChromeContentSettingsEventInfo { + number_of_exceptions: u32, primitive, 1, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_frame_reporter.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_frame_reporter.pz.rs new file mode 100644 index 00000000000..1482b88febe --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_frame_reporter.pz.rs @@ -0,0 +1,68 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; + +pb_enum!(ChromeFrameReporterState { + STATE_NO_UPDATE_DESIRED: 0, + STATE_PRESENTED_ALL: 1, + STATE_PRESENTED_PARTIAL: 2, + STATE_DROPPED: 3, +}); + +pb_enum!(ChromeFrameReporterFrameDropReason { + REASON_UNSPECIFIED: 0, + REASON_DISPLAY_COMPOSITOR: 1, + REASON_MAIN_THREAD: 2, + REASON_CLIENT_COMPOSITOR: 3, +}); + +pb_enum!(ChromeFrameReporterScrollState { + SCROLL_NONE: 0, + SCROLL_MAIN_THREAD: 1, + SCROLL_COMPOSITOR_THREAD: 2, + SCROLL_RASTER: 3, + SCROLL_UNKNOWN: 4, +}); + +pb_enum!(ChromeFrameReporterFrameType { + FORKED: 0, + BACKFILL: 1, +}); + +pb_msg!(ChromeFrameReporter { + state: ChromeFrameReporterState, enum, 1, + reason: ChromeFrameReporterFrameDropReason, enum, 2, + frame_source: u64, primitive, 3, + frame_sequence: u64, primitive, 4, + affects_smoothness: bool, primitive, 5, + scroll_state: ChromeFrameReporterScrollState, enum, 6, + has_main_animation: bool, primitive, 7, + has_compositor_animation: bool, primitive, 8, + has_smooth_input_main: bool, primitive, 9, + has_missing_content: bool, primitive, 10, + layer_tree_host_id: u64, primitive, 11, + has_high_latency: bool, primitive, 12, + frame_type: ChromeFrameReporterFrameType, enum, 13, + high_latency_contribution_stage: String, primitive, 14, + checkerboarded_needs_raster: bool, primitive, 15, + checkerboarded_needs_record: bool, primitive, 16, + surface_frame_trace_id: i64, primitive, 17, + display_trace_id: i64, primitive, 18, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_histogram_sample.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_histogram_sample.pz.rs new file mode 100644 index 00000000000..aae7b3c94f3 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_histogram_sample.pz.rs @@ -0,0 +1,31 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(ChromeHistogramSample { + name_hash: u64, primitive, 1, + name: String, primitive, 2, + sample: i64, primitive, 3, + name_iid: u64, primitive, 4, +}); + +pb_msg!(HistogramName { + iid: u64, primitive, 1, + name: String, primitive, 2, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_keyed_service.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_keyed_service.pz.rs new file mode 100644 index 00000000000..6ca9fb508aa --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_keyed_service.pz.rs @@ -0,0 +1,23 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(ChromeKeyedService { + name: String, primitive, 1, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_latency_info.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_latency_info.pz.rs new file mode 100644 index 00000000000..c8af9f25032 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_latency_info.pz.rs @@ -0,0 +1,79 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; + +pb_enum!(ChromeLatencyInfoStep { + STEP_UNSPECIFIED: 0, + STEP_SEND_INPUT_EVENT_UI: 3, + STEP_HANDLE_INPUT_EVENT_IMPL: 5, + STEP_DID_HANDLE_INPUT_AND_OVERSCROLL: 8, + STEP_HANDLE_INPUT_EVENT_MAIN: 4, + STEP_MAIN_THREAD_SCROLL_UPDATE: 2, + STEP_HANDLE_INPUT_EVENT_MAIN_COMMIT: 1, + STEP_HANDLED_INPUT_EVENT_MAIN_OR_IMPL: 9, + STEP_HANDLED_INPUT_EVENT_IMPL: 10, + STEP_SWAP_BUFFERS: 6, + STEP_DRAW_AND_SWAP: 7, + STEP_FINISHED_SWAP_BUFFERS: 11, +}); + +pb_enum!(ChromeLatencyInfoLatencyComponentType { + COMPONENT_UNSPECIFIED: 0, + COMPONENT_INPUT_EVENT_LATENCY_BEGIN_RWH: 1, + COMPONENT_INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL: 2, + COMPONENT_INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL: 3, + COMPONENT_INPUT_EVENT_LATENCY_ORIGINAL: 4, + COMPONENT_INPUT_EVENT_LATENCY_UI: 5, + COMPONENT_INPUT_EVENT_LATENCY_RENDERER_MAIN: 6, + COMPONENT_INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_MAIN: 7, + COMPONENT_INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_IMPL: 8, + COMPONENT_INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT: 9, + COMPONENT_INPUT_EVENT_LATENCY_ACK_RWH: 10, + COMPONENT_INPUT_EVENT_LATENCY_RENDERER_SWAP: 11, + COMPONENT_DISPLAY_COMPOSITOR_RECEIVED_FRAME: 12, + COMPONENT_INPUT_EVENT_GPU_SWAP_BUFFER: 13, + COMPONENT_INPUT_EVENT_LATENCY_FRAME_SWAP: 14, +}); + +pb_enum!(ChromeLatencyInfoInputType { + UNSPECIFIED_OR_OTHER: 0, + TOUCH_MOVED: 1, + GESTURE_SCROLL_BEGIN: 2, + GESTURE_SCROLL_UPDATE: 3, + GESTURE_SCROLL_END: 4, + GESTURE_TAP: 5, + GESTURE_TAP_CANCEL: 6, +}); + +pb_msg!(ChromeLatencyInfo { + trace_id: i64, primitive, 1, + step: ChromeLatencyInfoStep, enum, 2, + frame_tree_node_id: i32, primitive, 3, + component_info: ComponentInfo, msg, 4, + is_coalesced: bool, primitive, 5, + gesture_scroll_id: i64, primitive, 6, + touch_id: i64, primitive, 7, + input_type: ChromeLatencyInfoInputType, enum, 8, +}); + +pb_msg!(ComponentInfo { + component_type: ChromeLatencyInfoLatencyComponentType, enum, 1, + time_us: u64, primitive, 2, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_legacy_ipc.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_legacy_ipc.pz.rs new file mode 100644 index 00000000000..543d6e17298 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_legacy_ipc.pz.rs @@ -0,0 +1,66 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; + +pb_enum!(ChromeLegacyIpcMessageClass { + CLASS_UNSPECIFIED: 0, + CLASS_AUTOMATION: 1, + CLASS_FRAME: 2, + CLASS_PAGE: 3, + CLASS_VIEW: 4, + CLASS_WIDGET: 5, + CLASS_INPUT: 6, + CLASS_TEST: 7, + CLASS_WORKER: 8, + CLASS_NACL: 9, + CLASS_GPU_CHANNEL: 10, + CLASS_MEDIA: 11, + CLASS_PPAPI: 12, + CLASS_CHROME: 13, + CLASS_DRAG: 14, + CLASS_PRINT: 15, + CLASS_EXTENSION: 16, + CLASS_TEXT_INPUT_CLIENT: 17, + CLASS_BLINK_TEST: 18, + CLASS_ACCESSIBILITY: 19, + CLASS_PRERENDER: 20, + CLASS_CHROMOTING: 21, + CLASS_BROWSER_PLUGIN: 22, + CLASS_ANDROID_WEB_VIEW: 23, + CLASS_NACL_HOST: 24, + CLASS_ENCRYPTED_MEDIA: 25, + CLASS_CAST: 26, + CLASS_GIN_JAVA_BRIDGE: 27, + CLASS_CHROME_UTILITY_PRINTING: 28, + CLASS_OZONE_GPU: 29, + CLASS_WEB_TEST: 30, + CLASS_NETWORK_HINTS: 31, + CLASS_EXTENSIONS_GUEST_VIEW: 32, + CLASS_GUEST_VIEW: 33, + CLASS_MEDIA_PLAYER_DELEGATE: 34, + CLASS_EXTENSION_WORKER: 35, + CLASS_SUBRESOURCE_FILTER: 36, + CLASS_UNFREEZABLE_FRAME: 37, +}); + +pb_msg!(ChromeLegacyIpc { + message_class: ChromeLegacyIpcMessageClass, enum, 1, + message_line: u32, primitive, 2, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_message_pump.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_message_pump.pz.rs new file mode 100644 index 00000000000..14f18b56ad6 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_message_pump.pz.rs @@ -0,0 +1,24 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(ChromeMessagePump { + sent_messages_in_queue: bool, primitive, 1, + io_handler_location_iid: u64, primitive, 2, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_mojo_event_info.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_mojo_event_info.pz.rs new file mode 100644 index 00000000000..11711d561ae --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_mojo_event_info.pz.rs @@ -0,0 +1,29 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(ChromeMojoEventInfo { + watcher_notify_interface_tag: String, primitive, 1, + ipc_hash: u32, primitive, 2, + mojo_interface_tag: String, primitive, 3, + mojo_interface_method_iid: u64, primitive, 4, + is_reply: bool, primitive, 5, + payload_size: u64, primitive, 6, + data_num_bytes: u64, primitive, 7, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_process_descriptor.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_process_descriptor.pz.rs new file mode 100644 index 00000000000..13dcfb75744 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_process_descriptor.pz.rs @@ -0,0 +1,27 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(ChromeProcessDescriptor { + process_type: i32, primitive, 1, + process_priority: i32, primitive, 2, + legacy_sort_index: i32, primitive, 3, + host_app_package_name: String, primitive, 4, + crash_trace_id: u64, primitive, 5, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_renderer_scheduler_state.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_renderer_scheduler_state.pz.rs new file mode 100644 index 00000000000..cbd7d9e6746 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_renderer_scheduler_state.pz.rs @@ -0,0 +1,34 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; + +pb_enum!(ChromeRAILMode { + RAIL_MODE_NONE: 0, + RAIL_MODE_RESPONSE: 1, + RAIL_MODE_ANIMATION: 2, + RAIL_MODE_IDLE: 3, + RAIL_MODE_LOAD: 4, +}); + +pb_msg!(ChromeRendererSchedulerState { + rail_mode: ChromeRAILMode, enum, 1, + is_backgrounded: bool, primitive, 2, + is_hidden: bool, primitive, 3, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_thread_descriptor.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_thread_descriptor.pz.rs new file mode 100644 index 00000000000..bb1ceb3de91 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_thread_descriptor.pz.rs @@ -0,0 +1,25 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(ChromeThreadDescriptor { + thread_type: i32, primitive, 1, + legacy_sort_index: i32, primitive, 2, + is_sandboxed_tid: bool, primitive, 3, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_user_event.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_user_event.pz.rs new file mode 100644 index 00000000000..24742c2636c --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_user_event.pz.rs @@ -0,0 +1,24 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(ChromeUserEvent { + action: String, primitive, 1, + action_hash: u64, primitive, 2, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_window_handle_event_info.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_window_handle_event_info.pz.rs new file mode 100644 index 00000000000..859c9c9d4e3 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/chrome_window_handle_event_info.pz.rs @@ -0,0 +1,25 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(ChromeWindowHandleEventInfo { + dpi: u32, primitive, 1, + message_id: u32, primitive, 2, + hwnd_ptr: u64, primitive, 3, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/counter_descriptor.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/counter_descriptor.pz.rs new file mode 100644 index 00000000000..fa6f8779fd1 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/counter_descriptor.pz.rs @@ -0,0 +1,43 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; + +pb_enum!(CounterDescriptorBuiltinCounterType { + COUNTER_UNSPECIFIED: 0, + COUNTER_THREAD_TIME_NS: 1, + COUNTER_THREAD_INSTRUCTION_COUNT: 2, +}); + +pb_enum!(CounterDescriptorUnit { + UNIT_UNSPECIFIED: 0, + UNIT_TIME_NS: 1, + UNIT_COUNT: 2, + UNIT_SIZE_BYTES: 3, +}); + +pb_msg!(CounterDescriptor { + type: CounterDescriptorBuiltinCounterType, enum, 1, + categories: String, primitive, 2, + unit: CounterDescriptorUnit, enum, 3, + unit_name: String, primitive, 6, + unit_multiplier: i64, primitive, 4, + is_incremental: bool, primitive, 5, + y_axis_share_key: String, primitive, 7, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/debug_annotation.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/debug_annotation.pz.rs new file mode 100644 index 00000000000..2ed36d49b11 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/debug_annotation.pz.rs @@ -0,0 +1,66 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; + +pb_enum!(NestedValueNestedType { + UNSPECIFIED: 0, + DICT: 1, + ARRAY: 2, +}); + +pb_msg!(DebugAnnotationValueTypeName { + iid: u64, primitive, 1, + name: String, primitive, 2, +}); + +pb_msg!(DebugAnnotationName { + iid: u64, primitive, 1, + name: String, primitive, 2, +}); + +pb_msg!(DebugAnnotation { + name_iid: u64, primitive, 1, + name: String, primitive, 10, + bool_value: bool, primitive, 2, + uint_value: u64, primitive, 3, + int_value: i64, primitive, 4, + double_value: f64, primitive, 5, + pointer_value: u64, primitive, 7, + nested_value: NestedValue, msg, 8, + legacy_json_value: String, primitive, 9, + string_value: String, primitive, 6, + string_value_iid: u64, primitive, 17, + proto_type_name: String, primitive, 16, + proto_type_name_iid: u64, primitive, 13, + proto_value: String, primitive, 14, + dict_entries: DebugAnnotation, msg, 11, + array_values: DebugAnnotation, msg, 12, +}); + +pb_msg!(NestedValue { + nested_type: NestedValueNestedType, enum, 1, + dict_keys: String, primitive, 2, + dict_values: NestedValue, msg, 3, + array_values: NestedValue, msg, 4, + int_value: i64, primitive, 5, + double_value: f64, primitive, 6, + bool_value: bool, primitive, 7, + string_value: String, primitive, 8, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/log_message.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/log_message.pz.rs new file mode 100644 index 00000000000..901db14ff35 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/log_message.pz.rs @@ -0,0 +1,42 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; + +pb_enum!(LogMessagePriority { + PRIO_UNSPECIFIED: 0, + PRIO_UNUSED: 1, + PRIO_VERBOSE: 2, + PRIO_DEBUG: 3, + PRIO_INFO: 4, + PRIO_WARN: 5, + PRIO_ERROR: 6, + PRIO_FATAL: 7, +}); + +pb_msg!(LogMessageBody { + iid: u64, primitive, 1, + body: String, primitive, 2, +}); + +pb_msg!(LogMessage { + source_location_iid: u64, primitive, 1, + body_iid: u64, primitive, 2, + prio: LogMessagePriority, enum, 3, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/mod.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/mod.rs new file mode 100644 index 00000000000..03a57549d02 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/mod.rs @@ -0,0 +1,120 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the gen_rust_protos script. +// DO NOT EDIT. + +/// `chrome_active_processes` protos. +#[path = "chrome_active_processes.pz.rs"] +pub mod chrome_active_processes; + +/// `chrome_application_state_info` protos. +#[path = "chrome_application_state_info.pz.rs"] +pub mod chrome_application_state_info; + +/// `chrome_compositor_scheduler_state` protos. +#[path = "chrome_compositor_scheduler_state.pz.rs"] +pub mod chrome_compositor_scheduler_state; + +/// `chrome_content_settings_event_info` protos. +#[path = "chrome_content_settings_event_info.pz.rs"] +pub mod chrome_content_settings_event_info; + +/// `chrome_frame_reporter` protos. +#[path = "chrome_frame_reporter.pz.rs"] +pub mod chrome_frame_reporter; + +/// `chrome_histogram_sample` protos. +#[path = "chrome_histogram_sample.pz.rs"] +pub mod chrome_histogram_sample; + +/// `chrome_keyed_service` protos. +#[path = "chrome_keyed_service.pz.rs"] +pub mod chrome_keyed_service; + +/// `chrome_latency_info` protos. +#[path = "chrome_latency_info.pz.rs"] +pub mod chrome_latency_info; + +/// `chrome_legacy_ipc` protos. +#[path = "chrome_legacy_ipc.pz.rs"] +pub mod chrome_legacy_ipc; + +/// `chrome_message_pump` protos. +#[path = "chrome_message_pump.pz.rs"] +pub mod chrome_message_pump; + +/// `chrome_mojo_event_info` protos. +#[path = "chrome_mojo_event_info.pz.rs"] +pub mod chrome_mojo_event_info; + +/// `chrome_process_descriptor` protos. +#[path = "chrome_process_descriptor.pz.rs"] +pub mod chrome_process_descriptor; + +/// `chrome_renderer_scheduler_state` protos. +#[path = "chrome_renderer_scheduler_state.pz.rs"] +pub mod chrome_renderer_scheduler_state; + +/// `chrome_thread_descriptor` protos. +#[path = "chrome_thread_descriptor.pz.rs"] +pub mod chrome_thread_descriptor; + +/// `chrome_user_event` protos. +#[path = "chrome_user_event.pz.rs"] +pub mod chrome_user_event; + +/// `chrome_window_handle_event_info` protos. +#[path = "chrome_window_handle_event_info.pz.rs"] +pub mod chrome_window_handle_event_info; + +/// `counter_descriptor` protos. +#[path = "counter_descriptor.pz.rs"] +pub mod counter_descriptor; + +/// `debug_annotation` protos. +#[path = "debug_annotation.pz.rs"] +pub mod debug_annotation; + +/// `log_message` protos. +#[path = "log_message.pz.rs"] +pub mod log_message; + +/// `process_descriptor` protos. +#[path = "process_descriptor.pz.rs"] +pub mod process_descriptor; + +/// `screenshot` protos. +#[path = "screenshot.pz.rs"] +pub mod screenshot; + +/// `source_location` protos. +#[path = "source_location.pz.rs"] +pub mod source_location; + +/// `task_execution` protos. +#[path = "task_execution.pz.rs"] +pub mod task_execution; + +/// `thread_descriptor` protos. +#[path = "thread_descriptor.pz.rs"] +pub mod thread_descriptor; + +/// `track_descriptor` protos. +#[path = "track_descriptor.pz.rs"] +pub mod track_descriptor; + +/// `track_event` protos. +#[path = "track_event.pz.rs"] +pub mod track_event; diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/process_descriptor.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/process_descriptor.pz.rs new file mode 100644 index 00000000000..d146900c82f --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/process_descriptor.pz.rs @@ -0,0 +1,43 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; + +pb_enum!(ProcessDescriptorChromeProcessType { + PROCESS_UNSPECIFIED: 0, + PROCESS_BROWSER: 1, + PROCESS_RENDERER: 2, + PROCESS_UTILITY: 3, + PROCESS_ZYGOTE: 4, + PROCESS_SANDBOX_HELPER: 5, + PROCESS_GPU: 6, + PROCESS_PPAPI_PLUGIN: 7, + PROCESS_PPAPI_BROKER: 8, +}); + +pb_msg!(ProcessDescriptor { + pid: i32, primitive, 1, + cmdline: String, primitive, 2, + process_name: String, primitive, 6, + process_priority: i32, primitive, 5, + start_timestamp_ns: i64, primitive, 7, + chrome_process_type: ProcessDescriptorChromeProcessType, enum, 4, + legacy_sort_index: i32, primitive, 3, + process_labels: String, primitive, 8, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/screenshot.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/screenshot.pz.rs new file mode 100644 index 00000000000..7e99ab1013c --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/screenshot.pz.rs @@ -0,0 +1,23 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(Screenshot { + jpg_image: String, primitive, 1, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/source_location.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/source_location.pz.rs new file mode 100644 index 00000000000..1bcb06d6868 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/source_location.pz.rs @@ -0,0 +1,32 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(SourceLocation { + iid: u64, primitive, 1, + file_name: String, primitive, 2, + function_name: String, primitive, 3, + line_number: u32, primitive, 4, +}); + +pb_msg!(UnsymbolizedSourceLocation { + iid: u64, primitive, 1, + mapping_id: u64, primitive, 2, + rel_pc: u64, primitive, 3, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/task_execution.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/task_execution.pz.rs new file mode 100644 index 00000000000..5e59ba7986a --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/task_execution.pz.rs @@ -0,0 +1,23 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_msg; + +pb_msg!(TaskExecution { + posted_from_iid: u64, primitive, 1, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/thread_descriptor.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/thread_descriptor.pz.rs new file mode 100644 index 00000000000..900bdd97533 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/thread_descriptor.pz.rs @@ -0,0 +1,48 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; + +pb_enum!(ThreadDescriptorChromeThreadType { + CHROME_THREAD_UNSPECIFIED: 0, + CHROME_THREAD_MAIN: 1, + CHROME_THREAD_IO: 2, + CHROME_THREAD_POOL_BG_WORKER: 3, + CHROME_THREAD_POOL_FG_WORKER: 4, + CHROME_THREAD_POOL_FB_BLOCKING: 5, + CHROME_THREAD_POOL_BG_BLOCKING: 6, + CHROME_THREAD_POOL_SERVICE: 7, + CHROME_THREAD_COMPOSITOR: 8, + CHROME_THREAD_VIZ_COMPOSITOR: 9, + CHROME_THREAD_COMPOSITOR_WORKER: 10, + CHROME_THREAD_SERVICE_WORKER: 11, + CHROME_THREAD_MEMORY_INFRA: 50, + CHROME_THREAD_SAMPLING_PROFILER: 51, +}); + +pb_msg!(ThreadDescriptor { + pid: i32, primitive, 1, + tid: i32, primitive, 2, + thread_name: String, primitive, 5, + chrome_thread_type: ThreadDescriptorChromeThreadType, enum, 4, + reference_timestamp_us: i64, primitive, 6, + reference_thread_time_us: i64, primitive, 7, + reference_thread_instruction_count: i64, primitive, 8, + legacy_sort_index: i32, primitive, 3, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/track_descriptor.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/track_descriptor.pz.rs new file mode 100644 index 00000000000..dc786448df3 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/track_descriptor.pz.rs @@ -0,0 +1,59 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; +use crate::protos::trace::track_event::chrome_process_descriptor::*; +use crate::protos::trace::track_event::chrome_thread_descriptor::*; +use crate::protos::trace::track_event::counter_descriptor::*; +use crate::protos::trace::track_event::process_descriptor::*; +use crate::protos::trace::track_event::thread_descriptor::*; + +pb_enum!(TrackDescriptorChildTracksOrdering { + UNKNOWN: 0, + LEXICOGRAPHIC: 1, + CHRONOLOGICAL: 2, + EXPLICIT: 3, +}); + +pb_enum!(TrackDescriptorSiblingMergeBehavior { + SIBLING_MERGE_BEHAVIOR_UNSPECIFIED: 0, + SIBLING_MERGE_BEHAVIOR_BY_TRACK_NAME: 1, + SIBLING_MERGE_BEHAVIOR_NONE: 2, + SIBLING_MERGE_BEHAVIOR_BY_SIBLING_MERGE_KEY: 3, +}); + +pb_msg!(TrackDescriptor { + uuid: u64, primitive, 1, + parent_uuid: u64, primitive, 5, + name: String, primitive, 2, + static_name: String, primitive, 10, + atrace_name: String, primitive, 13, + description: String, primitive, 14, + process: ProcessDescriptor, msg, 3, + chrome_process: ChromeProcessDescriptor, msg, 6, + thread: ThreadDescriptor, msg, 4, + chrome_thread: ChromeThreadDescriptor, msg, 7, + counter: CounterDescriptor, msg, 8, + disallow_merging_with_system_tracks: bool, primitive, 9, + child_ordering: TrackDescriptorChildTracksOrdering, enum, 11, + sibling_order_rank: i32, primitive, 12, + sibling_merge_behavior: TrackDescriptorSiblingMergeBehavior, enum, 15, + sibling_merge_key: String, primitive, 16, + sibling_merge_key_int: u64, primitive, 17, +}); diff --git a/contrib/rust-sdk/perfetto/src/protos/trace/track_event/track_event.pz.rs b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/track_event.pz.rs new file mode 100644 index 00000000000..7226938a5e7 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/protos/trace/track_event/track_event.pz.rs @@ -0,0 +1,157 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the ProtoZero Rust compiler plugin. +// Invoked by contrib/rust-sdk/tools/gen_rust_protos +// DO NOT EDIT. + +use crate::pb_enum; +use crate::pb_msg; +use crate::protos::trace::track_event::chrome_active_processes::*; +use crate::protos::trace::track_event::chrome_application_state_info::*; +use crate::protos::trace::track_event::chrome_compositor_scheduler_state::*; +use crate::protos::trace::track_event::chrome_content_settings_event_info::*; +use crate::protos::trace::track_event::chrome_frame_reporter::*; +use crate::protos::trace::track_event::chrome_histogram_sample::*; +use crate::protos::trace::track_event::chrome_keyed_service::*; +use crate::protos::trace::track_event::chrome_latency_info::*; +use crate::protos::trace::track_event::chrome_legacy_ipc::*; +use crate::protos::trace::track_event::chrome_message_pump::*; +use crate::protos::trace::track_event::chrome_mojo_event_info::*; +use crate::protos::trace::track_event::chrome_renderer_scheduler_state::*; +use crate::protos::trace::track_event::chrome_user_event::*; +use crate::protos::trace::track_event::chrome_window_handle_event_info::*; +use crate::protos::trace::track_event::debug_annotation::*; +use crate::protos::trace::track_event::log_message::*; +use crate::protos::trace::track_event::screenshot::*; +use crate::protos::trace::track_event::source_location::*; +use crate::protos::trace::track_event::task_execution::*; + +pb_enum!(TrackEventType { + TYPE_UNSPECIFIED: 0, + TYPE_SLICE_BEGIN: 1, + TYPE_SLICE_END: 2, + TYPE_INSTANT: 3, + TYPE_COUNTER: 4, +}); + +pb_enum!(LegacyEventFlowDirection { + FLOW_UNSPECIFIED: 0, + FLOW_IN: 1, + FLOW_OUT: 2, + FLOW_INOUT: 3, +}); + +pb_enum!(LegacyEventInstantEventScope { + SCOPE_UNSPECIFIED: 0, + SCOPE_GLOBAL: 1, + SCOPE_PROCESS: 2, + SCOPE_THREAD: 3, +}); + +pb_msg!(EventName { + iid: u64, primitive, 1, + name: String, primitive, 2, +}); + +pb_msg!(EventCategory { + iid: u64, primitive, 1, + name: String, primitive, 2, +}); + +pb_msg!(TrackEventDefaults { + track_uuid: u64, primitive, 11, + extra_counter_track_uuids: u64, primitive, 31, + extra_double_counter_track_uuids: u64, primitive, 45, +}); + +pb_msg!(TrackEvent { + category_iids: u64, primitive, 3, + categories: String, primitive, 22, + name_iid: u64, primitive, 10, + name: String, primitive, 23, + type: TrackEventType, enum, 9, + track_uuid: u64, primitive, 11, + counter_value: i64, primitive, 30, + double_counter_value: f64, primitive, 44, + extra_counter_track_uuids: u64, primitive, 31, + extra_counter_values: i64, primitive, 12, + extra_double_counter_track_uuids: u64, primitive, 45, + extra_double_counter_values: f64, primitive, 46, + flow_ids_old: u64, primitive, 36, + flow_ids: u64, primitive, 47, + terminating_flow_ids_old: u64, primitive, 42, + terminating_flow_ids: u64, primitive, 48, + correlation_id: u64, primitive, 52, + correlation_id_str: String, primitive, 53, + correlation_id_str_iid: u64, primitive, 54, + callstack: Callstack, msg, 55, + callstack_iid: u64, primitive, 56, + debug_annotations: DebugAnnotation, msg, 4, + task_execution: TaskExecution, msg, 5, + log_message: LogMessage, msg, 21, + cc_scheduler_state: ChromeCompositorSchedulerState, msg, 24, + chrome_user_event: ChromeUserEvent, msg, 25, + chrome_keyed_service: ChromeKeyedService, msg, 26, + chrome_legacy_ipc: ChromeLegacyIpc, msg, 27, + chrome_histogram_sample: ChromeHistogramSample, msg, 28, + chrome_latency_info: ChromeLatencyInfo, msg, 29, + chrome_frame_reporter: ChromeFrameReporter, msg, 32, + chrome_application_state_info: ChromeApplicationStateInfo, msg, 39, + chrome_renderer_scheduler_state: ChromeRendererSchedulerState, msg, 40, + chrome_window_handle_event_info: ChromeWindowHandleEventInfo, msg, 41, + chrome_content_settings_event_info: ChromeContentSettingsEventInfo, msg, 43, + chrome_active_processes: ChromeActiveProcesses, msg, 49, + screenshot: Screenshot, msg, 50, + source_location: SourceLocation, msg, 33, + source_location_iid: u64, primitive, 34, + chrome_message_pump: ChromeMessagePump, msg, 35, + chrome_mojo_event_info: ChromeMojoEventInfo, msg, 38, + timestamp_delta_us: i64, primitive, 1, + timestamp_absolute_us: i64, primitive, 16, + thread_time_delta_us: i64, primitive, 2, + thread_time_absolute_us: i64, primitive, 17, + thread_instruction_count_delta: i64, primitive, 8, + thread_instruction_count_absolute: i64, primitive, 20, + legacy_event: LegacyEvent, msg, 6, +}); + +pb_msg!(LegacyEvent { + name_iid: u64, primitive, 1, + phase: i32, primitive, 2, + duration_us: i64, primitive, 3, + thread_duration_us: i64, primitive, 4, + thread_instruction_delta: i64, primitive, 15, + unscoped_id: u64, primitive, 6, + local_id: u64, primitive, 10, + global_id: u64, primitive, 11, + id_scope: String, primitive, 7, + use_async_tts: bool, primitive, 9, + bind_id: u64, primitive, 8, + bind_to_enclosing: bool, primitive, 12, + flow_direction: LegacyEventFlowDirection, enum, 13, + instant_event_scope: LegacyEventInstantEventScope, enum, 14, + pid_override: i32, primitive, 18, + tid_override: i32, primitive, 19, +}); + +pb_msg!(Callstack { + frames: Frame, msg, 1, +}); + +pb_msg!(Frame { + function_name: String, primitive, 1, + source_file: String, primitive, 2, + line_number: u32, primitive, 3, +}); diff --git a/contrib/rust-sdk/perfetto/src/stream_writer.rs b/contrib/rust-sdk/perfetto/src/stream_writer.rs new file mode 100644 index 00000000000..cff08404d58 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/stream_writer.rs @@ -0,0 +1,189 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use perfetto_sdk_sys::*; +use std::{cell::RefCell, ptr, slice}; + +/// A `StreamWriter` owns a chunk of memory that the user can write to. +pub struct StreamWriter { + pub(crate) writer: RefCell, +} + +impl StreamWriter { + /// Create new stream writer. + pub fn new() -> Self { + Self::default() + } + + /// Returns the number of bytes available for writing in the current chunk. + pub fn available_bytes(&self) -> usize { + let writer = self.writer.borrow_mut(); + assert!(writer.end >= writer.write_ptr); + + // SAFETY: `writer.end` must be >= to `writer.write_ptr`. + unsafe { writer.end.offset_from(writer.write_ptr) as usize } + } + + /// Writes bytes from `src` to the writer. + /// + /// SAFETY: `available_bytes()` must be >= `src.len()`. + pub fn append_bytes_unchecked(&self, src: &[u8]) { + assert!(src.len() <= self.available_bytes()); + let mut writer = self.writer.borrow_mut(); + assert!(!writer.impl_.is_null()); + + // SAFETY: + // - `writer` must be valid. + // - `available_bytes()` must be >= `src.len()`. + unsafe { ptr::copy_nonoverlapping(src.as_ptr(), writer.write_ptr, src.len()) }; + + // Advance write pointer. + writer.write_ptr = writer.write_ptr.wrapping_add(src.len()); + } + + /// Writes bytes from `src` to the writer. + pub fn append_bytes(&self, src: &[u8]) { + // Use fast-path if enough bytes are already available. + if crate::__likely!(src.len() <= self.available_bytes()) { + self.append_bytes_unchecked(src); + return; + } + + let mut writer = self.writer.borrow_mut(); + assert!(!writer.impl_.is_null()); + + // SAFETY: `writer` must be valid. + unsafe { + PerfettoStreamWriterAppendBytesSlowpath(&mut *writer as *mut _, src.as_ptr(), src.len()) + }; + } + + /// Writes the single byte `value` to the writer. + pub fn append_byte(&self, value: u8) { + let mut writer = self.writer.borrow_mut(); + assert!(!writer.impl_.is_null()); + + // Create new chunk if needed. + if crate::__unlikely!(self.available_bytes() < 1) { + // SAFETY: `writer` must be valid. + unsafe { PerfettoStreamWriterNewChunk(&mut *writer as *mut _) }; + } + + assert!(1 <= self.available_bytes()); + + // SAFETY: `available_bytes()` must be >= 1. + unsafe { ptr::write(writer.write_ptr, value) }; + + // Advance write pointer. + writer.write_ptr = writer.write_ptr.wrapping_add(1); + } + + /// Returns a pointer to an area of the chunk long `size` for writing. The + /// returned area is considered already written by the writer (it will not be + /// used again). + /// + /// SAFETY: `available_bytes()` must be >= `size`. + #[allow(clippy::mut_from_ref)] + pub fn reserve_bytes_unchecked(&self, size: usize) -> &mut [u8] { + assert!(size <= self.available_bytes()); + let mut writer = self.writer.borrow_mut(); + assert!(!writer.impl_.is_null()); + + // Get current write pointer. + let start_ptr = writer.write_ptr; + + // Advance write pointer. + writer.write_ptr = writer.write_ptr.wrapping_add(size); + + // Create slice for start pointer and size. + // + // SAFETY: + // - `writer` must be valid. + // - `start_ptr` must be `size` bytes before writer end. + unsafe { slice::from_raw_parts_mut(start_ptr, size) } + } + + /// Returns a pointer to an area of the chunk long `size` for writing. The + /// returned area is considered already written by the writer (it will not be + /// used again). + /// + /// # Safety + /// + /// - `size` should be smaller than the chunk size returned by the `delegate`. + #[allow(clippy::mut_from_ref)] + pub fn reserve_bytes(&self, size: usize) -> &mut [u8] { + // Use fast-path if enough bytes are available already. + if crate::__likely!(size <= self.available_bytes()) { + return self.reserve_bytes_unchecked(size); + } + + let mut writer = self.writer.borrow_mut(); + assert!(!writer.impl_.is_null()); + + // SAFETY: `writer` must be valid. + unsafe { PerfettoStreamWriterReserveBytesSlowpath(&mut *writer as *mut _, size) }; + + // Get current write pointer. + let start_ptr = writer.write_ptr.wrapping_sub(size); + + // Create slice for start pointer and size. + // + // SAFETY: + // - `writer` must be valid. + // - `start_ptr` must be `size` bytes before writer end. + unsafe { slice::from_raw_parts_mut(start_ptr, size) } + } + + /// Returns the number of bytes written to the stream writer from the start. + pub fn get_written_size(&self) -> usize { + let writer = self.writer.borrow(); + assert!(writer.begin <= writer.write_ptr); + + // SAFETY: `writer.begin` must be <= to `writer.write_ptr`. + let bytes_written = unsafe { writer.write_ptr.offset_from(writer.begin) as usize }; + + writer.written_previously + bytes_written + } + + /// Returns true if writer is valid. + pub(crate) fn has_valid_writer(&self) -> bool { + !self.writer.borrow().impl_.is_null() + } +} + +impl Default for StreamWriter { + fn default() -> Self { + Self { + writer: RefCell::new(PerfettoStreamWriter { + impl_: ptr::null_mut(), + begin: ptr::null_mut(), + end: ptr::null_mut(), + write_ptr: ptr::null_mut(), + written_previously: 0, + }), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn initial_state() { + let writer = StreamWriter::new(); + assert_eq!(writer.available_bytes(), 0); + assert_eq!(writer.get_written_size(), 0); + } +} diff --git a/contrib/rust-sdk/perfetto/src/tracing_session.rs b/contrib/rust-sdk/perfetto/src/tracing_session.rs new file mode 100644 index 00000000000..0b75c3ac9c4 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/tracing_session.rs @@ -0,0 +1,346 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use perfetto_sdk_sys::*; +use std::{ffi::c_void, time::Duration}; +use thiserror::Error; + +/// Tracing session errors. +#[derive(Error, Debug, PartialEq)] +pub enum TracingSessionError { + /// Error creating tracing session. + #[error("Failed to create tracing session.")] + CreateError, +} + +type FlushCallback = Box; + +unsafe extern "C" fn flush_callback_trampoline( + _impl: *mut PerfettoTracingSessionImpl, + success: bool, + user_arg: *mut c_void, +) { + let result = std::panic::catch_unwind(|| { + // Take back ownership of the boxed callback, which will be dropped at the end of the + // scope. + // + // SAFETY: `user_arg` must be a boxed FlushCallback. + let f: Box = unsafe { Box::from_raw(user_arg as *mut FlushCallback) }; + f(success); + }); + if let Err(err) = result { + eprintln!("Fatal panic: {:?}", err); + std::process::abort(); + } +} + +type ReadCallback = Box; + +unsafe extern "C" fn read_callback_trampoline( + _impl: *mut PerfettoTracingSessionImpl, + data: *const c_void, + size: usize, + has_more: bool, + user_arg: *mut c_void, +) { + let result = std::panic::catch_unwind(|| { + // SAFETY: `data` must point to a buffer of `size` length. + let bytes = unsafe { std::slice::from_raw_parts(data as *const u8, size) }; + if has_more { + // SAFETY: `user_arg` must be a boxed ReadCallback. + let f: &ReadCallback = unsafe { &mut *(user_arg as *mut _) }; + f(bytes, has_more); + } else { + // Take back ownership of the boxed callback, which will be dropped at the end of the + // scope. + // + // SAFETY: `user_arg` must be a boxed ReadCallback. + let f: Box = unsafe { Box::from_raw(user_arg as *mut ReadCallback) }; + f(bytes, has_more); + } + }); + if let Err(err) = result { + eprintln!("Fatal panic: {:?}", err); + std::process::abort(); + } +} + +/// An opaque structure used as the representation of a tracing session. +pub struct TracingSession { + impl_: *mut PerfettoTracingSessionImpl, +} + +impl TracingSession { + /// Creates a system wide tracing session. + pub fn system() -> Result { + // SAFETY: FFI call with no outstanding preconditions. + let impl_ = unsafe { PerfettoTracingSessionSystemCreate() }; + if impl_.is_null() { + return Err(TracingSessionError::CreateError); + } + Ok(Self { impl_ }) + } + + /// Creates an in-process tracing session. + pub fn in_process() -> Result { + // SAFETY: FFI call with no outstanding preconditions. + let impl_ = unsafe { PerfettoTracingSessionInProcessCreate() }; + if impl_.is_null() { + return Err(TracingSessionError::CreateError); + } + Ok(Self { impl_ }) + } + + /// Setup tracing session using the provided `cfg` trace config. + /// + /// # Safety + /// + /// - `cfg` must be a properly encoded trace config. + pub fn setup(&mut self, cfg: &[u8]) { + // SAFETY: + // - `self.impl_` must be created using `PerfettoTracingSessionSystemCreate` or + // `PerfettoTracingSessionInProcessCreate`. + // - `cfg` must be a properly encoded trace config. + unsafe { PerfettoTracingSessionSetup(self.impl_, cfg.as_ptr() as *mut c_void, cfg.len()) }; + } + + /// Asynchronous start of tracing session. + pub fn start_async(&mut self) { + // SAFETY: `self.impl_` must be created using `PerfettoTracingSessionSystemCreate` or + // `PerfettoTracingSessionInProcessCreate`. + unsafe { PerfettoTracingSessionStartAsync(self.impl_) }; + } + + /// Synchronous start of tracing session. + pub fn start_blocking(&mut self) { + // SAFETY: `self.impl_` must be created using `PerfettoTracingSessionSystemCreate` or + // `PerfettoTracingSessionInProcessCreate`. + unsafe { PerfettoTracingSessionStartBlocking(self.impl_) }; + } + + /// Synchronous stop of tracing session. + pub fn stop_blocking(&mut self) { + // SAFETY: `self.impl_` must be created using `PerfettoTracingSessionSystemCreate` or + // `PerfettoTracingSessionInProcessCreate`. + unsafe { PerfettoTracingSessionStopBlocking(self.impl_) }; + } + + /// Issues a flush request, asking all data sources to ack the request, within + /// the specified timeout. A "flush" is a fence to ensure visibility of data in + /// the async tracing pipeline. It guarantees that all data written before the + /// call will be visible in the trace buffer. Returns immediately and + /// invokes a callback when the flush request is complete. + /// Args: + /// `cb`: will be invoked on an internal perfetto thread when all data + /// sources have acked, or the timeout is reached. + /// `timeout`: how much time the service will wait for data source acks. If + /// 0, the global timeout specified in the TraceConfig (flush_timeout) + /// will be used. If flush_timeout is also unspecified, a default value + /// of 5s will be used. + pub fn flush_async(&mut self, timeout: Duration, cb: F) + where + F: Fn(bool) + Send + Sync + 'static, + { + let boxed: Box = Box::new(Box::new(cb)); + let user_arg = Box::into_raw(boxed) as *mut c_void; + // SAFETY: + // - `self.impl_` must be created using `PerfettoTracingSessionSystemCreate` or + // `PerfettoTracingSessionInProcessCreate`. + // - `user_arg` must be a boxed FlushCallback. + unsafe { + PerfettoTracingSessionFlushAsync( + self.impl_, + timeout.as_millis() as u32, + Some(flush_callback_trampoline), + user_arg, + ) + }; + } + + /// Like flush_async(), but blocks until the flush is complete (i.e. every data + /// source has acknowledged or the timeout has expired). + pub fn flush_blocking(&mut self, timeout: Duration) { + // SAFETY: `self.impl_` must be created using `PerfettoTracingSessionSystemCreate` or + // `PerfettoTracingSessionInProcessCreate`. + unsafe { PerfettoTracingSessionFlushBlocking(self.impl_, timeout.as_millis() as u32) }; + } + + /// Repeatedly calls `cb` with data from the tracing session. + pub fn read_trace_blocking(&mut self, cb: F) + where + F: Fn(&[u8], bool) + Send + Sync + 'static, + { + let boxed: Box = Box::new(Box::new(cb)); + let user_arg = Box::into_raw(boxed) as *mut c_void; + // SAFETY: + // - `self.impl_` must be created using `PerfettoTracingSessionSystemCreate` or + // `PerfettoTracingSessionInProcessCreate`. + // - `user_arg` must be a boxed ReadCallback. + unsafe { + PerfettoTracingSessionReadTraceBlocking( + self.impl_, + Some(read_callback_trampoline), + user_arg, + ) + }; + } +} + +impl Drop for TracingSession { + fn drop(&mut self) { + // SAFETY: `self.impl_` must be created using `PerfettoTracingSessionSystemCreate` or + // `PerfettoTracingSessionInProcessCreate`. + unsafe { PerfettoTracingSessionDestroy(self.impl_) }; + } +} + +#[cfg(test)] +mod tests { + use crate::data_source::DataSource; + use crate::tests::{TracingSessionBuilder, acquire_test_environment}; + use crate::{track_event::TrackEvent, track_event_categories}; + use std::{ + error::Error, + sync::{MutexGuard, OnceLock}, + }; + + const DATA_SOURCE_NAME: &str = "dev.perfetto.example_data_source"; + static DATA_SOURCE: OnceLock = OnceLock::new(); + + fn get_data_source() -> &'static DataSource<'static> { + use crate::data_source::DataSourceArgsBuilder; + DATA_SOURCE.get_or_init(|| { + let data_source_args = DataSourceArgsBuilder::new(); + let mut data_source = DataSource::new(); + data_source + .register(DATA_SOURCE_NAME, data_source_args.build()) + .expect("failed to register data source"); + data_source + }) + } + + #[test] + fn data_source() -> Result<(), Box> { + use crate::data_source::*; + let _lock = acquire_test_environment(); + let data_source = get_data_source(); + let mut session = TracingSessionBuilder::new() + .set_data_source_name(DATA_SOURCE_NAME) + .build()?; + session.start_blocking(); + assert!(data_source.is_enabled()); + let mut executed: usize = 0; + data_source.trace(|_ctx: &mut TraceContext| { + executed += 1; + }); + session.stop_blocking(); + assert_eq!(executed, 1); + Ok(()) + } + + track_event_categories! { + pub mod session_test_te_ns { + ( "cat1", "Test category 1", [] ), + ( "cat2", "Test category 2", [] ), + } + } + + struct TeTestFixture { + _lock: MutexGuard<'static, ()>, + } + + impl TeTestFixture { + fn new() -> Self { + let _lock = acquire_test_environment(); + TrackEvent::init(); + session_test_te_ns::register().expect("register failed"); + Self { _lock } + } + } + + impl Drop for TeTestFixture { + fn drop(&mut self) { + session_test_te_ns::unregister().expect("unregister failed"); + } + } + + #[test] + fn track_event() -> Result<(), Box> { + use crate::{trace_for_category, track_event::TraceContext, track_event_category_enabled}; + use session_test_te_ns as perfetto_te_ns; + let _fx = TeTestFixture::new(); + let mut session = TracingSessionBuilder::new() + .set_data_source_name("track_event") + .add_enabled_category("cat1") + .add_disabled_category("*") + .build()?; + session.start_blocking(); + assert!(track_event_category_enabled!("cat1")); + assert!(!track_event_category_enabled!("cat2")); + let mut executed: usize = 0; + trace_for_category!("cat1", |_ctx: &mut TraceContext| { + executed += 1; + }); + session.stop_blocking(); + assert_eq!(executed, 1); + Ok(()) + } + + #[test] + fn read_trace() -> Result<(), Box> { + use crate::data_source::TraceContext; + use crate::pb_decoder::{PbDecoder, PbDecoderField}; + use crate::protos::trace::{test_event::*, trace::*, trace_packet::*}; + use std::sync::{Arc, Mutex}; + let _lock = acquire_test_environment(); + let data_source = get_data_source(); + let mut session = TracingSessionBuilder::new() + .set_data_source_name(DATA_SOURCE_NAME) + .build()?; + session.start_blocking(); + assert!(data_source.is_enabled()); + data_source.trace(|ctx: &mut TraceContext| { + ctx.add_packet(|packet: &mut TracePacket| { + packet + .set_timestamp(42) + .set_for_testing(|for_testing: &mut TestEvent| { + for_testing.set_str("This is a string"); + }); + }); + }); + session.stop_blocking(); + let trace_data = Arc::new(Mutex::new(vec![])); + let trace_data_for_write = Arc::clone(&trace_data); + session.read_trace_blocking(move |data, _end| { + let mut written_data = trace_data_for_write.lock().unwrap(); + written_data.extend_from_slice(data); + }); + let data = trace_data.lock().unwrap(); + assert!(!data.is_empty()); + let mut for_testing_found = false; + for trace_field in PbDecoder::new(&data) { + const PACKET_ID: u32 = TraceFieldNumber::Packet as u32; + if let (PACKET_ID, PbDecoderField::Delimited(data)) = trace_field.unwrap() { + for packet_field in PbDecoder::new(data) { + const FOR_TESTING_ID: u32 = TracePacketFieldNumber::ForTesting as u32; + if let (FOR_TESTING_ID, PbDecoderField::Delimited(_)) = packet_field.unwrap() { + for_testing_found = true; + } + } + } + } + assert!(for_testing_found); + Ok(()) + } +} diff --git a/contrib/rust-sdk/perfetto/src/track_event.rs b/contrib/rust-sdk/perfetto/src/track_event.rs new file mode 100644 index 00000000000..38bfaa29640 --- /dev/null +++ b/contrib/rust-sdk/perfetto/src/track_event.rs @@ -0,0 +1,2030 @@ +// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + data_source::TraceContextBase, + fnv1a, + heap_buffer::HeapBuffer, + pb_msg::{PbMsg, PbMsgWriter}, + protos::trace::{ + interned_data::interned_data::InternedDataFieldNumber, + track_event::{counter_descriptor::CounterDescriptor, track_descriptor::TrackDescriptor}, + }, +}; +use perfetto_sdk_sys::*; +use std::{ + ffi::{CStr, CString}, + marker::PhantomData, + os::raw::{c_char, c_void}, + ptr, + sync::atomic::{AtomicBool, Ordering}, + time::Duration, +}; +use thiserror::Error; + +/// Track event errors. +#[derive(Error, Debug, PartialEq)] +pub enum TrackEventError { + /// Failed to register categories as already registered. + #[error("Categories have already been registered.")] + CategoriesAlreadyRegisteredError, + /// Failure because categories are not yet registered. + #[error("Categories are not registered.")] + CategoriesNotRegisteredError, +} + +/// Trace context struct passed to track event trace callbacks. +pub struct TraceContext { + base: TraceContextBase, + incr: *mut PerfettoTeLlImplIncr, +} + +impl TraceContext { + /// Returns true if the track event incremental state has already seen in the + /// past the given track UUID. + pub fn track_seen(&mut self, uuid: u64) -> bool { + // SAFETY: `self.incr` must be a pointer provided by a call to + // PerfettoTeLlImplBegin/Next. + unsafe { PerfettoTeLlImplTrackSeen(self.incr, uuid) } + } + + /// Interning: + /// + /// it's possible to avoid repeating the same data over and over in a trace by + /// using "interning". + /// + /// `type` is a field id in the `perfetto.protos.InternedData` protobuf message. + /// `data` reference raw data that is potentially repeated. + /// The data referenced by `data` can be anything (e.g. a serialized protobuf + /// message, or a small integer) that uniquely identifies the potentially + /// repeated data. + /// + /// The function returns a tuple containing an integer (the iid) that can be used + /// instead of serializing the data directly in the packet and a boolean that is set + /// to false if this is the first time the library observed this data for this specific + /// type (therefore it allocated a new iid). + pub fn intern(&mut self, r#type: InternedDataFieldNumber, data: &[u8]) -> (u64, bool) { + let mut seen: bool = false; + // SAFETY: + // + // - `self.incr` must be a pointer provided by a call to + // PerfettoTeLlImplBegin/Next. + // - `seen` must be storage for a boolean return value. + let iid = unsafe { + PerfettoTeLlImplIntern( + self.incr, + r#type as i32, + data.as_ptr() as *mut c_void, + data.len(), + &raw mut seen, + ) + }; + (iid, seen) + } +} + +impl std::ops::Deref for TraceContext { + type Target = TraceContextBase; + fn deref(&self) -> &Self::Target { + &self.base + } +} + +impl std::ops::DerefMut for TraceContext { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.base + } +} + +/// An opaque struct used to represent the track event machinery. +pub struct TrackEvent {} + +impl TrackEvent { + /// Initialize global track event machinery. Safe to call multiple times + /// as a no-op when already called. + pub fn init() { + // SAFETY: FFI call with no outstanding preconditions. + unsafe { PerfettoTeInit() }; + } + + /// Tells the tracing service about newly registered categories. Must be called + /// after one or more calls to `TrackEventCategory::register()` or + /// `TrackEventCategory::unregister()`. + pub fn publish_categories() { + // SAFETY: FFI call with no outstanding preconditions. + unsafe { PerfettoTePublishCategories() }; + } +} + +/// Category callback type. +pub type CategoryCallback = Box; + +/// Struct used to represent a registered category. +/// +/// Recommended usage is the track_event_categories macro instead of using +/// this struct directly. +pub struct TrackEventCategory { + enabled: *mut bool, + impl_: *mut PerfettoTeCategoryImpl, + desc: PerfettoTeCategoryDescriptor, + cat_iid: u64, + _marker: PhantomData<&'static ()>, +} + +impl TrackEventCategory { + /// Creates a new category that can be registered. + /// + /// # Safety + /// + /// - `tags` must be a number of null-terminated C strings. + pub const unsafe fn new(name: &CStr, desc: &CStr, tags: &[*const c_char]) -> Self { + Self { + enabled: &raw mut perfetto_atomic_false, + impl_: ptr::null_mut(), + desc: PerfettoTeCategoryDescriptor { + name: name.as_ptr() as *const c_char, + desc: desc.as_ptr() as *const c_char, + tags: tags.as_ptr() as *mut *const c_char, + num_tags: tags.len(), + }, + cat_iid: 0, + _marker: PhantomData, + } + } + + /// Returns a boolean that tells if the category is enabled or not. + pub fn is_enabled(&self) -> bool { + // SAFETY: `self.enabled` must be a pointer to a primitive with layout that matches C11 + // atomic_bool. + unsafe { + let atomic_ptr = self.enabled as *const AtomicBool; + (*atomic_ptr).load(Ordering::Relaxed) + } + } + + /// Registers the category. + pub fn register(&mut self) { + assert!(!self.desc.name.is_null()); + assert!(!self.desc.desc.is_null()); + // SAFETY: + // - `self.desc` must be a pointer to a PerfettoTeCategoryDescriptor struct with: + // - name and desc fields set to null-terminated C strings. + // - tags field set to an array of null-terminated C strings. + // - num_tags field set to the number of items in the tags array. + unsafe { + self.impl_ = PerfettoTeCategoryImplCreate(&raw mut self.desc); + self.enabled = PerfettoTeCategoryImplGetEnabled(self.impl_); + self.cat_iid = PerfettoTeCategoryImplGetIid(self.impl_); + } + } + + /// Unregisters the category. Must have been previously registered. + pub fn unregister(&mut self) { + assert!(!self.impl_.is_null()); + // SAFETY: `self.impl_` must be previously created using PerfettoTeCategoryImplCreate. + unsafe { PerfettoTeCategoryImplDestroy(self.impl_) }; + self.impl_ = ptr::null_mut(); + self.enabled = &raw mut perfetto_atomic_false; + self.cat_iid = 0; + } + + unsafe extern "C" fn callback_trampoline( + _c: *mut PerfettoTeCategoryImpl, + inst_id: PerfettoDsInstanceIndex, + enabled: bool, + global_state_changed: bool, + user_arg: *mut c_void, + ) { + let result = std::panic::catch_unwind(|| { + // SAFETY: `user_arg` must be a CategoryCallback. + let f: &mut CategoryCallback = unsafe { &mut *(user_arg as *mut _) }; + f(inst_id, enabled, global_state_changed); + }); + if let Err(err) = result { + eprintln!("Fatal panic: {:?}", err); + std::process::abort(); + } + } + + /// Set category callback called when category is enabled. + /// + /// # Safety + /// + /// - `callback` must be kept alive until replaced by a new callback or + /// category has been unregistered. + pub unsafe fn set_callback(&mut self, callback: &mut CategoryCallback) { + assert!(!self.impl_.is_null()); + let user_arg = crate::__box_as_mut_ptr(callback) as *mut c_void; + // SAFETY: + // - `self.impl_` must be previously created using PerfettoTeCategoryImplCreate. + // - `user_arg` must be a CategoryCallback. + unsafe { + PerfettoTeCategoryImplSetCallback(self.impl_, Some(Self::callback_trampoline), user_arg) + }; + } + + /// Emit track event of a specific type. + pub fn emit(&mut self, variant: TrackEventType, ctx: &mut EventContext) { + assert!(!self.impl_.is_null()); + let te_type = match variant { + TrackEventType::Instant(_) => PerfettoTeType_PERFETTO_TE_TYPE_INSTANT, + TrackEventType::SliceBegin(_) => PerfettoTeType_PERFETTO_TE_TYPE_SLICE_BEGIN, + TrackEventType::SliceEnd => PerfettoTeType_PERFETTO_TE_TYPE_SLICE_END, + TrackEventType::Counter => PerfettoTeType_PERFETTO_TE_TYPE_COUNTER, + }; + let te_name = match variant { + TrackEventType::Instant(name) => Some(name), + TrackEventType::SliceBegin(name) => Some(name), + _ => None, + }; + let mut te_extras: Vec<*mut PerfettoTeHlExtra> = ctx + .extras + .iter_mut() + .map(|e| match e { + TeHlExtra::Flush(te_flush) => te_flush as *mut PerfettoTeHlExtra, + TeHlExtra::NoIntern(te_no_intern) => te_no_intern as *mut PerfettoTeHlExtra, + TeHlExtra::Timestamp(te_timstamp) => { + &mut te_timstamp.header as *mut PerfettoTeHlExtra + } + TeHlExtra::DebugArgBool(te_debug_arg, _) => { + &mut te_debug_arg.header as *mut PerfettoTeHlExtra + } + TeHlExtra::DebugArgUint64(te_debug_arg, _) => { + &mut te_debug_arg.header as *mut PerfettoTeHlExtra + } + TeHlExtra::DebugArgInt64(te_debug_arg, _) => { + &mut te_debug_arg.header as *mut PerfettoTeHlExtra + } + TeHlExtra::DebugArgDouble(te_debug_arg, _) => { + &mut te_debug_arg.header as *mut PerfettoTeHlExtra + } + TeHlExtra::DebugArgString(te_debug_arg, _, _) => { + &mut te_debug_arg.header as *mut PerfettoTeHlExtra + } + TeHlExtra::DebugArgPointer(te_debug_arg, _) => { + &mut te_debug_arg.header as *mut PerfettoTeHlExtra + } + TeHlExtra::Track(te_track) => &mut te_track.header as *mut PerfettoTeHlExtra, + TeHlExtra::NamedTrack(te_track, _) => { + &mut te_track.header as *mut PerfettoTeHlExtra + } + TeHlExtra::Flow(te_flow) => &mut te_flow.header as *mut PerfettoTeHlExtra, + TeHlExtra::CounterInt64(te_counter) => { + &mut te_counter.header as *mut PerfettoTeHlExtra + } + TeHlExtra::CounterDouble(te_counter) => { + &mut te_counter.header as *mut PerfettoTeHlExtra + } + TeHlExtra::ProtoFields(te_fields, _, _) => { + &mut te_fields.header as *mut PerfettoTeHlExtra + } + TeHlExtra::ProtoTrack(te_fields, _, _) => { + &mut te_fields.header as *mut PerfettoTeHlExtra + } + TeHlExtra::NestedTracks(te_fields, _, _) => { + &mut te_fields.header as *mut PerfettoTeHlExtra + } + }) + .collect(); + te_extras.push(ptr::null_mut()); + + // SAFETY: + // - `self.impl_` must be previously created using PerfettoTeCategoryImplCreate. + // - `te_type` must be a valid PerfettoTeType_* value. + // - `name` must be a null-terminated C string or null. + // - `te_extras` must be a null-terminated array of PerfettoTeHlExtra pointers. + unsafe { + PerfettoTeHlEmitImpl( + self.impl_, + te_type as i32, + te_name.unwrap_or(ptr::null_mut()), + te_extras.as_ptr(), + ) + }; + } + + /// Calls `cb` for all active track event data source instances for this category. + pub fn trace(&self, mut cb: F) + where + F: FnMut(&mut TraceContext), + { + // SAFETY: FFI call with no outstanding preconditions. + let timestamp = unsafe { PerfettoTeGetTimestamp() }; + + // SAFETY: + // - `self.impl_` must be previously created using PerfettoTeCategoryImplCreate. + // - `timestamp` must be timestamp from PerfettoTeGetTimestamp(). + let mut iterator = unsafe { PerfettoTeLlImplBegin(self.impl_, timestamp) }; + loop { + if iterator.ds.tracer.is_null() { + break; + } + + let mut ctx = TraceContext { + base: TraceContextBase { + iterator: iterator.ds, + }, + incr: iterator.incr, + }; + cb(&mut ctx); + + // SAFETY: + // - `self.impl_` must be previously created using PerfettoTeCategoryImplCreate. + // - `timestamp` must be timestamp from PerfettoTeGetTimestamp(). + // - `iterator` must be a value returned from PerfettoTeLlImplBegin or + // PerfettoTeLlImplNext with a non-null `iterator.ds.tracer`. + unsafe { PerfettoTeLlImplNext(self.impl_, timestamp, &raw mut iterator) }; + } + } +} + +/// Internal helper macro used to count expressions. +#[doc(hidden)] +#[macro_export] +macro_rules! __count_exprs { + ($($item:expr),* $(,)?) => { + <[()]>::len(&[$($crate::__count_exprs!(@sub $item)),*]) + }; + (@sub $item:expr) => { () }; +} + +/// Defines track event categories. +/// +/// Example: +/// +/// ``` +/// use perfetto_sdk::*; +/// +/// track_event_categories! { +/// pub mod my_categories_te_ns { +/// ( "c1", "My category 1 description", [ "tag1", "tag2" ] ), +/// ( "c2", "My category 2 description", [ "tag1" ] ), +/// ( "c3", "My category 3 description", [] ), +/// } +/// } +/// +/// use my_categories_te_ns as perfetto_te_ns; +/// +/// //... +/// +/// use std::error::Error; +/// +/// fn main() -> Result<(), Box> { +/// producer::Producer::init( +/// producer::ProducerInitArgsBuilder::new() +/// .backends(producer::Backends::SYSTEM) +/// .build(), +/// ); +/// track_event::TrackEvent::init(); +/// perfetto_te_ns::register()?; +/// //... +/// Ok(()) +/// } +/// ``` +#[macro_export] +macro_rules! track_event_categories { + ( + $vis:vis mod $modname:ident { + $( ($key:literal, $desc:literal, [$($tag:literal),* $(,)?]) ),* $(,)? + } + ) => { + $vis mod $modname { + use $crate::{ + track_event::{ + TraceContext, + TrackEvent, + CategoryCallback, + EventContext, + TrackEventCategory, + TrackEventError, + TrackEventType, + }, + }; + use std::{ffi::CStr, os::raw::c_char, sync::Mutex}; + + const CATEGORY_COUNT: usize = $crate::__count_exprs!($($key),*); + const CATEGORY_TAGS: &[(&str, &[*const c_char])] = &[ + $( + ( + $key, + &[$(concat!($tag, "\0").as_bytes().as_ptr() as *const c_char),*], + ) + ),+ + ]; + // Note: `CATEGORIES` are not guarded by a Mutex to allow `enabled` fields + // to be checked using atomics. + static mut CATEGORIES: [TrackEventCategory; CATEGORY_COUNT] = [ + $( + // SAFETY: `CATEGORY_TAGS` must be a null-terminated array of C strings. + unsafe { + TrackEventCategory::new( + CStr::from_bytes_with_nul_unchecked(concat!($key, "\0").as_bytes()), + CStr::from_bytes_with_nul_unchecked(concat!($desc, "\0").as_bytes()), + CATEGORY_TAGS[category_index($key)].1, + ) + } + ),+ + ]; + static CATEGORIES_REGISTERED: Mutex = Mutex::new(false); + static CATEGORY_CALLBACKS: Mutex<[Option; CATEGORY_COUNT]> = + Mutex::new([const { None }; CATEGORY_COUNT]); + + const fn str_eq(a: &str, b: &str) -> bool { + let a_bytes = a.as_bytes(); + let b_bytes = b.as_bytes(); + if a_bytes.len() != b_bytes.len() { + return false; + } + let mut i = 0; + while i < a_bytes.len() { + if a_bytes[i] != b_bytes[i] { + return false; + } + i += 1; + } + true + } + + #[allow(unused)] + $vis fn register() -> Result<(), TrackEventError> { + let mut registered = CATEGORIES_REGISTERED.lock().unwrap(); + if *registered { + return Err(TrackEventError::CategoriesAlreadyRegisteredError); + } + *registered = true; + // SAFETY: + // + // - Requires exclusive access to `CATEGORIES`, which is provided by + // `CATEGORIES_REGISTERED`. + unsafe { + #[allow(static_mut_refs)] + for c in &mut CATEGORIES { + c.register(); + } + } + let mut callbacks = CATEGORY_CALLBACKS.lock().unwrap(); + for category_index in 0..CATEGORY_COUNT { + if let Some(boxed_cb) = callbacks[category_index].as_mut() { + // SAFETY: + // + // - Requires exclusive access to `CATEGORIES`, which is + // provided by `CATEGORIES_REGISTERED`. + unsafe { CATEGORIES[category_index].set_callback(boxed_cb) }; + } + } + TrackEvent::publish_categories(); + Ok(()) + } + + #[allow(unused)] + $vis fn unregister() -> Result<(), TrackEventError> { + let mut registered = CATEGORIES_REGISTERED.lock().unwrap(); + if !*registered { + return Err(TrackEventError::CategoriesNotRegisteredError); + } + *registered = false; + // SAFETY: + // + // - Requires exclusive access to `CATEGORIES`, which is provided by + // `CATEGORIES_REGISTERED`. + unsafe { + #[allow(static_mut_refs)] + for c in &mut CATEGORIES { + c.unregister(); + } + } + TrackEvent::publish_categories(); + Ok(()) + } + + // allow unused_assignments for last iteration + #[allow(unused_assignments)] + $vis const fn category_index(s: &str) -> usize { + let mut i = 0; + $( + if str_eq(s, $key) { return i; } + i += 1; + )* + panic!("unknown category"); + } + + /// Safe to call this before categories have been registered. + #[allow(unused)] + $vis fn is_category_enabled(category_index: usize) -> bool { + assert!(category_index < CATEGORY_COUNT); + // SAFETY: + // + // - Safe to call on any thread as it uses atomics. + unsafe { CATEGORIES[category_index].is_enabled() } + } + + #[allow(unused)] + $vis fn set_category_callback(category_index: usize, cb: F) + where + F: FnMut(u32, bool, bool) + Send + Sync + 'static, + { + assert!(category_index < CATEGORY_COUNT); + let registered = CATEGORIES_REGISTERED.lock().unwrap(); + let boxed: Box = Box::new(Box::new(cb)); + let mut callbacks = CATEGORY_CALLBACKS.lock().unwrap(); + // Drop old callback after having set a new callback. + let _old = callbacks[category_index].replace(boxed); + if *registered { + if let Some(boxed_cb) = callbacks[category_index].as_mut() { + // SAFETY: + // + // - Requires exclusive access to `CATEGORIES`, which is + // provided by `CATEGORIES_REGISTERED`. + unsafe { CATEGORIES[category_index].set_callback(boxed_cb) }; + } + } + } + + #[allow(unused)] + $vis fn emit( + category_index: usize, + variant: TrackEventType, + ctx: &mut EventContext, + ) { + assert!(category_index < CATEGORY_COUNT); + let registered = CATEGORIES_REGISTERED.lock().unwrap(); + if *registered { + // SAFETY: + // + // - Requires exclusive access to `CATEGORIES`, which is provided by + // `CATEGORIES_REGISTERED`. + unsafe { CATEGORIES[category_index].emit(variant, ctx) }; + } + } + + #[allow(unused)] + $vis fn trace(category_index: usize, cb: F) + where + F: FnMut(&mut TraceContext) + { + assert!(category_index < CATEGORY_COUNT); + let registered = CATEGORIES_REGISTERED.lock().unwrap(); + if *registered { + // SAFETY: + // + // - Requires exclusive access to `CATEGORIES`, which is provided by + // `CATEGORIES_REGISTERED`. + unsafe { CATEGORIES[category_index].trace(cb) }; + } + } + } + } +} + +/// Determines if a category is enabled. +#[macro_export] +macro_rules! track_event_category_enabled { + ($category:literal) => {{ + const CATEGORY_INDEX: usize = perfetto_te_ns::category_index($category); + perfetto_te_ns::is_category_enabled(CATEGORY_INDEX) + }}; +} + +/// Sets the callback to invoke when a specific category is enabled. +#[macro_export] +macro_rules! track_event_set_category_callback { + ($category:literal, $cb:expr) => {{ + const CATEGORY_INDEX: usize = perfetto_te_ns::category_index($category); + perfetto_te_ns::set_category_callback(CATEGORY_INDEX, $cb); + }}; +} + +/// Track event types. +#[derive(Debug, Copy, Clone)] +pub enum TrackEventType { + /// Instant track event type with a name. + Instant(*const c_char), + /// Begin track event type with a name. + SliceBegin(*const c_char), + /// End track event type. + SliceEnd, + /// Counter track event type. + Counter, +} + +/// Track event timestamp types. +#[derive(Debug, Copy, Clone)] +pub enum TrackEventTimestamp { + /// Monotonic timestamp. + Monotonic(Duration), + /// Boot clock timestamp. + Boot(Duration), + /// Incremental timestamp. + Incremental(Duration), + /// Absolute timestamp. + Absolute(Duration), + /// Custom clock timestamp. + Custom { + /// Custom clock ID. + id: u32, + /// Timestamp value. + value: Duration, + }, +} + +impl TrackEventTimestamp { + /// Get a track event timestamp. + pub fn now() -> Self { + // SAFETY: Track event machinery must have been initialized. + let te_timestamp = unsafe { PerfettoTeGetTimestamp() }; + #[allow(non_upper_case_globals)] + match te_timestamp.clock_id { + PerfettoTeTimestampType_PERFETTO_TE_TIMESTAMP_TYPE_MONOTONIC => { + TrackEventTimestamp::Monotonic(Duration::from_nanos(te_timestamp.value)) + } + PerfettoTeTimestampType_PERFETTO_TE_TIMESTAMP_TYPE_BOOT => { + TrackEventTimestamp::Boot(Duration::from_nanos(te_timestamp.value)) + } + _ => { + panic!("unexpected clock id: {}", te_timestamp.clock_id); + } + } + } + + /// Returns the timestamp clock ID. + pub fn clock_id(&self) -> u32 { + use TrackEventTimestamp::*; + match &self { + Monotonic(_) => PerfettoTeTimestampType_PERFETTO_TE_TIMESTAMP_TYPE_MONOTONIC, + Boot(_) => PerfettoTeTimestampType_PERFETTO_TE_TIMESTAMP_TYPE_BOOT, + Incremental(_) => PerfettoTeTimestampType_PERFETTO_TE_TIMESTAMP_TYPE_INCREMENTAL, + Absolute(_) => PerfettoTeTimestampType_PERFETTO_TE_TIMESTAMP_TYPE_ABSOLUTE, + Custom { id, value: _ } => *id, + } + } + + /// Returns the timestamp value. + pub fn timestamp(&self) -> u64 { + use TrackEventTimestamp::*; + match &self { + Monotonic(value) => value.as_nanos() as u64, + Boot(value) => value.as_nanos() as u64, + Incremental(value) => value.as_nanos() as u64, + Absolute(value) => value.as_nanos() as u64, + Custom { id: _, value } => value.as_nanos() as u64, + } + } +} + +const COUNTER_MAGIC: u64 = 0xb1a4a67d7970839e; + +/// Struct used to represent a track event track. +#[derive(Debug)] +pub struct TrackEventTrack { + _descriptor: Vec, + impl_: PerfettoTeRegisteredTrackImpl, +} + +impl TrackEventTrack { + /// Get the track event UUID for a counter track. + pub const fn counter_track_uuid(name: &str, parent_uuid: u64) -> u64 { + let mut uuid = COUNTER_MAGIC; + uuid ^= parent_uuid; + uuid ^= fnv1a(name.as_bytes()); + uuid + } + + /// Get the track event UUID for a named track. + pub const fn named_track_uuid(name: &str, id: u64, parent_uuid: u64) -> u64 { + let mut uuid = parent_uuid; + uuid ^= fnv1a(name.as_bytes()); + uuid ^= id; + uuid + } + + /// Get the track event UUID for the current process. + pub fn process_track_uuid() -> u64 { + // SAFETY: Track event machinery must have been initialized. + unsafe { perfetto_te_process_track_uuid } + } + + /// Register a named track. + pub fn register_named_track( + name: &str, + id: u64, + parent_track_uuid: u64, + ) -> Result { + let uuid = Self::named_track_uuid(name, id, parent_track_uuid); + let writer = PbMsgWriter::new(); + let hb = HeapBuffer::new(&writer.writer); + let mut msg = PbMsg::new(&writer).unwrap(); + { + let mut desc = TrackDescriptor { msg: &mut msg }; + desc.set_uuid(uuid); + if parent_track_uuid != 0 { + desc.set_parent_uuid(parent_track_uuid); + } + desc.set_name(name); + } + msg.finalize(); + let descriptor_size = writer.writer.get_written_size(); + let mut descriptor: Vec = vec![0u8; descriptor_size]; + hb.copy_into(&mut descriptor); + let descriptor_ptr = descriptor.as_mut_ptr() as *mut c_void; + let track = Self { + _descriptor: descriptor, + impl_: PerfettoTeRegisteredTrackImpl { + descriptor: descriptor_ptr, + descriptor_size, + uuid, + }, + }; + Ok(track) + } + + /// Register a counter track. + pub fn register_counter_track( + name: &str, + parent_track_uuid: u64, + ) -> Result { + let uuid = Self::counter_track_uuid(name, parent_track_uuid); + let writer = PbMsgWriter::new(); + let hb = HeapBuffer::new(&writer.writer); + let mut msg = PbMsg::new(&writer).unwrap(); + { + let mut desc = TrackDescriptor { msg: &mut msg }; + desc.set_uuid(uuid); + if parent_track_uuid != 0 { + desc.set_parent_uuid(parent_track_uuid); + } + desc.set_name(name); + desc.set_counter(|counter: &mut CounterDescriptor| { + counter.set_is_incremental(false); + }); + } + msg.finalize(); + let descriptor_size = writer.writer.get_written_size(); + let mut descriptor: Vec = vec![0u8; descriptor_size]; + hb.copy_into(&mut descriptor); + let descriptor_ptr = descriptor.as_mut_ptr() as *mut c_void; + let track = Self { + _descriptor: descriptor, + impl_: PerfettoTeRegisteredTrackImpl { + descriptor: descriptor_ptr, + descriptor_size, + uuid, + }, + }; + Ok(track) + } + + /// Returns the UUID for the track. + pub fn uuid(&self) -> u64 { + self.impl_.uuid + } +} + +/// SAFETY: Internal handle must be thread-safe. +unsafe impl Send for TrackEventTrack {} + +/// SAFETY: Internal handle must be thread-safe. +unsafe impl Sync for TrackEventTrack {} + +/// Struct used to represent a track event flow. +#[derive(Debug)] +pub struct TrackEventFlow { + id: u64, +} + +impl TrackEventFlow { + /// Creates a process scoped flow. + pub fn process_scoped_flow(id: u64) -> TrackEventFlow { + TrackEventFlow { + // SAFETY: Track event machinery must have been initialized. + id: unsafe { id ^ perfetto_te_process_track_uuid }, + } + } + + /// Creates a global scoped flow. + pub fn global_flow(id: u64) -> TrackEventFlow { + TrackEventFlow { id } + } +} + +/// Debug argument types. +#[derive(Debug)] +pub enum TrackEventDebugArg<'a> { + /// Boolean argument. + Bool(bool), + /// Uint64 argument. + Uint64(u64), + /// Int64 argument. + Int64(i64), + /// Double argument. + Double(f64), + /// String argument. + String(&'a str), + /// Pointer argument. + Pointer(usize), +} + +/// Counter value types. +#[derive(Debug)] +pub enum TrackEventCounter { + /// Int64 counter value. + Int64(i64), + /// Double counter value. + Double(f64), +} + +// Allow dead code as variants hold data that need to be kept alive. +#[allow(dead_code)] +pub(crate) enum TeHlProtoField { + Nested( + PerfettoTeHlProtoFieldNested, + Vec, + Vec<*mut PerfettoTeHlProtoField>, + ), + VarInt(PerfettoTeHlProtoFieldVarInt), + Cstr(PerfettoTeHlProtoFieldCstr, CString), + Bytes(PerfettoTeHlProtoFieldBytes, Vec), +} + +pub(crate) trait AsTeHlProtoFieldPtr { + fn as_proto_field_ptr(&mut self) -> *mut PerfettoTeHlProtoField; +} + +impl AsTeHlProtoFieldPtr for TeHlProtoField { + fn as_proto_field_ptr(&mut self) -> *mut PerfettoTeHlProtoField { + use TeHlProtoField::*; + match self { + Nested(field, _, _) => &mut field.header as *mut PerfettoTeHlProtoField, + VarInt(field) => &mut field.header as *mut PerfettoTeHlProtoField, + Cstr(field, _) => &mut field.header as *mut PerfettoTeHlProtoField, + Bytes(field, _) => &mut field.header as *mut PerfettoTeHlProtoField, + } + } +} + +/// Proto field types. +#[derive(Debug)] +pub enum TrackEventProtoField<'a> { + /// Nested message type. + Nested(u32, &'a [TrackEventProtoField<'a>]), + /// VarInt type. + VarInt(u32, u64), + /// String type. + Cstr(u32, &'a str), + /// Bytes type. + Bytes(u32, &'a [u8]), +} + +pub(crate) trait ToTeHlProtoField { + fn to_proto_field(&self) -> TeHlProtoField; +} + +impl ToTeHlProtoField for TrackEventProtoField<'_> { + fn to_proto_field(&self) -> TeHlProtoField { + use TrackEventProtoField::*; + use std::{os::raw::c_void, ptr}; + match self { + Nested(id, nested_fields) => { + let mut te_fields: Vec = + nested_fields.iter().map(|f| f.to_proto_field()).collect(); + let mut te_field_ptrs: Vec<*mut PerfettoTeHlProtoField> = te_fields + .iter_mut() + .map(|f| f.as_proto_field_ptr()) + .collect(); + te_field_ptrs.push(ptr::null_mut()); + TeHlProtoField::Nested( + PerfettoTeHlProtoFieldNested { + header: PerfettoTeHlProtoField { + type_: PerfettoTeHlProtoFieldType_PERFETTO_TE_HL_PROTO_TYPE_NESTED, + id: *id, + }, + fields: te_field_ptrs.as_ptr(), + }, + te_fields, + te_field_ptrs, + ) + } + VarInt(id, value) => TeHlProtoField::VarInt(PerfettoTeHlProtoFieldVarInt { + header: PerfettoTeHlProtoField { + type_: PerfettoTeHlProtoFieldType_PERFETTO_TE_HL_PROTO_TYPE_VARINT, + id: *id, + }, + value: *value, + }), + Cstr(id, value) => { + let cvalue = CString::new(*value).unwrap(); + TeHlProtoField::Cstr( + PerfettoTeHlProtoFieldCstr { + header: PerfettoTeHlProtoField { + type_: PerfettoTeHlProtoFieldType_PERFETTO_TE_HL_PROTO_TYPE_CSTR, + id: *id, + }, + str_: cvalue.as_ptr(), + }, + cvalue, + ) + } + Bytes(id, value) => { + let bytes = value.to_vec(); + TeHlProtoField::Bytes( + PerfettoTeHlProtoFieldBytes { + header: PerfettoTeHlProtoField { + type_: PerfettoTeHlProtoFieldType_PERFETTO_TE_HL_PROTO_TYPE_BYTES, + id: *id, + }, + buf: bytes.as_ptr() as *const c_void, + len: bytes.len(), + }, + bytes, + ) + } + } + } +} + +/// Struct containing references to a number of proto fields. +#[derive(Debug)] +pub struct TrackEventProtoFields<'a> { + /// Proto fields. + pub fields: &'a [TrackEventProtoField<'a>], +} + +/// Struct describing a track using proto fields. +#[derive(Debug)] +pub struct TrackEventProtoTrack<'a> { + /// Track UUID. + pub uuid: u64, + /// Proto fields. + pub fields: &'a [TrackEventProtoField<'a>], +} + +// Allow dead code as variants hold data that need to be kept alive. +#[allow(dead_code)] +pub(crate) enum TeHlNestedTrack { + Named(PerfettoTeHlNestedTrackNamed, CString), + Proto( + PerfettoTeHlNestedTrackProto, + Vec, + Vec<*mut PerfettoTeHlProtoField>, + ), + Registered(PerfettoTeHlNestedTrackRegistered), + Thread(PerfettoTeHlNestedTrack), + Process(PerfettoTeHlNestedTrack), +} + +pub(crate) trait AsTeHlNestedTrackPtr { + fn as_nested_track_ptr(&mut self) -> *mut PerfettoTeHlNestedTrack; +} + +impl AsTeHlNestedTrackPtr for TeHlNestedTrack { + fn as_nested_track_ptr(&mut self) -> *mut PerfettoTeHlNestedTrack { + use TeHlNestedTrack::*; + match self { + Named(track, _) => &mut track.header as *mut PerfettoTeHlNestedTrack, + Proto(track, _, _) => &mut track.header as *mut PerfettoTeHlNestedTrack, + Registered(track) => &mut track.header as *mut PerfettoTeHlNestedTrack, + Thread(track) => track, + Process(track) => track, + } + } +} + +/// Nested track types. +#[derive(Debug)] +pub enum TrackEventNestedTrack<'a> { + /// Named track. + Named(&'a str, u64), + /// Track described by proto fields. + Proto(u64, &'a [TrackEventProtoField<'a>]), + /// Registered track. + Registered(&'a TrackEventTrack), + /// Current thread track. + Thread, + /// Current process track. + Process, +} + +pub(crate) trait ToTeHlNestedTrack { + fn to_nested_track(&self) -> TeHlNestedTrack; +} + +impl ToTeHlNestedTrack for TrackEventNestedTrack<'_> { + fn to_nested_track(&self) -> TeHlNestedTrack { + use TrackEventNestedTrack::*; + use std::ptr; + match self { + Named(name, id) => { + let cname = CString::new(*name).unwrap(); + TeHlNestedTrack::Named( + PerfettoTeHlNestedTrackNamed { + header: PerfettoTeHlNestedTrack { + type_: + PerfettoTeHlNestedTrackType_PERFETTO_TE_HL_NESTED_TRACK_TYPE_NAMED, + }, + name: cname.as_ptr(), + id: *id, + }, + cname, + ) + } + Proto(id, fields) => { + let mut te_fields: Vec = + fields.iter().map(|f| f.to_proto_field()).collect(); + let mut te_field_ptrs: Vec<*mut PerfettoTeHlProtoField> = te_fields + .iter_mut() + .map(|f| f.as_proto_field_ptr()) + .collect(); + te_field_ptrs.push(ptr::null_mut()); + TeHlNestedTrack::Proto( + PerfettoTeHlNestedTrackProto { + header: PerfettoTeHlNestedTrack { + type_: + PerfettoTeHlNestedTrackType_PERFETTO_TE_HL_NESTED_TRACK_TYPE_PROTO, + }, + id: *id, + fields: te_field_ptrs.as_ptr(), + }, + te_fields, + te_field_ptrs, + ) + } + Registered(track) => TeHlNestedTrack::Registered(PerfettoTeHlNestedTrackRegistered { + header: PerfettoTeHlNestedTrack { + type_: PerfettoTeHlNestedTrackType_PERFETTO_TE_HL_NESTED_TRACK_TYPE_REGISTERED, + }, + track: &raw const track.impl_, + }), + Thread => TeHlNestedTrack::Thread(PerfettoTeHlNestedTrack { + type_: PerfettoTeHlNestedTrackType_PERFETTO_TE_HL_NESTED_TRACK_TYPE_THREAD, + }), + Process => TeHlNestedTrack::Process(PerfettoTeHlNestedTrack { + type_: PerfettoTeHlNestedTrackType_PERFETTO_TE_HL_NESTED_TRACK_TYPE_PROCESS, + }), + } + } +} + +/// Struct containing references to a hierarchy of nested tracks. +#[derive(Debug)] +pub struct TrackEventNestedTracks<'a> { + /// The first reference is the outermost track (the parent track), the + /// (second to) last reference is the innermost track (the child track). + pub tracks: &'a [TrackEventNestedTrack<'a>], +} + +// Allow dead code as variants hold data that need to be kept alive. +#[allow(dead_code)] +pub(crate) enum TeHlExtra { + Flush(PerfettoTeHlExtra), + NoIntern(PerfettoTeHlExtra), + Timestamp(PerfettoTeHlExtraTimestamp), + DebugArgBool(PerfettoTeHlExtraDebugArgBool, CString), + DebugArgUint64(PerfettoTeHlExtraDebugArgUint64, CString), + DebugArgInt64(PerfettoTeHlExtraDebugArgInt64, CString), + DebugArgDouble(PerfettoTeHlExtraDebugArgDouble, CString), + DebugArgString(PerfettoTeHlExtraDebugArgString, CString, CString), + DebugArgPointer(PerfettoTeHlExtraDebugArgPointer, CString), + Track(PerfettoTeHlExtraRegisteredTrack), + NamedTrack(PerfettoTeHlExtraNamedTrack, CString), + Flow(PerfettoTeHlExtraFlow), + CounterInt64(PerfettoTeHlExtraCounterInt64), + CounterDouble(PerfettoTeHlExtraCounterDouble), + ProtoFields( + PerfettoTeHlExtraProtoFields, + Vec, + Vec<*mut PerfettoTeHlProtoField>, + ), + ProtoTrack( + PerfettoTeHlExtraProtoTrack, + Vec, + Vec<*mut PerfettoTeHlProtoField>, + ), + NestedTracks( + PerfettoTeHlExtraNestedTracks, + Vec, + Vec<*mut PerfettoTeHlNestedTrack>, + ), +} + +/// Struct with extra data for a track event instance. +#[derive(Default)] +pub struct EventContext { + pub(crate) extras: Vec, +} + +impl EventContext { + /// Add flush flag. + pub fn set_flush(&mut self) -> &mut Self { + let flush = PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_FLUSH, + }; + self.extras.push(TeHlExtra::Flush(flush)); + self + } + + /// Add "no intern" flag. + pub fn set_no_intern(&mut self) -> &mut Self { + let no_intern = PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_NO_INTERN, + }; + self.extras.push(TeHlExtra::NoIntern(no_intern)); + self + } + + /// Add timestamp. + pub fn set_timestamp(&mut self, timestamp: TrackEventTimestamp) -> &mut Self { + use TrackEventTimestamp::*; + let (clock_id, duration) = match timestamp { + Monotonic(value) => ( + PerfettoTeTimestampType_PERFETTO_TE_TIMESTAMP_TYPE_MONOTONIC, + value, + ), + Boot(value) => ( + PerfettoTeTimestampType_PERFETTO_TE_TIMESTAMP_TYPE_BOOT, + value, + ), + Incremental(value) => ( + PerfettoTeTimestampType_PERFETTO_TE_TIMESTAMP_TYPE_INCREMENTAL, + value, + ), + Absolute(value) => ( + PerfettoTeTimestampType_PERFETTO_TE_TIMESTAMP_TYPE_ABSOLUTE, + value, + ), + Custom { id, value } => (id, value), + }; + let timestamp = PerfettoTeHlExtraTimestamp { + header: PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_TIMESTAMP, + }, + timestamp: PerfettoTeTimestamp { + clock_id, + value: duration.as_nanos() as u64, + }, + }; + self.extras.push(TeHlExtra::Timestamp(timestamp)); + self + } + + /// Add debug arg. + pub fn add_debug_arg(&mut self, name: &str, arg: TrackEventDebugArg) -> &mut Self { + use TrackEventDebugArg::*; + let cname = CString::new(name).unwrap(); + match arg { + Bool(value) => { + let debug_arg = PerfettoTeHlExtraDebugArgBool { + header: PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_BOOL, + }, + name: cname.as_ptr(), + value, + }; + self.extras.push(TeHlExtra::DebugArgBool(debug_arg, cname)); + } + Uint64(value) => { + let debug_arg = PerfettoTeHlExtraDebugArgUint64 { + header: PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_UINT64, + }, + name: cname.as_ptr(), + value, + }; + self.extras + .push(TeHlExtra::DebugArgUint64(debug_arg, cname)); + } + Int64(value) => { + let debug_arg = PerfettoTeHlExtraDebugArgInt64 { + header: PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_INT64, + }, + name: cname.as_ptr(), + value, + }; + self.extras.push(TeHlExtra::DebugArgInt64(debug_arg, cname)); + } + Double(value) => { + let debug_arg = PerfettoTeHlExtraDebugArgDouble { + header: PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_DOUBLE, + }, + name: cname.as_ptr(), + value, + }; + self.extras + .push(TeHlExtra::DebugArgDouble(debug_arg, cname)); + } + String(value) => { + let cvalue = CString::new(value).unwrap(); + let debug_arg = PerfettoTeHlExtraDebugArgString { + header: PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_STRING, + }, + name: cname.as_ptr(), + value: cvalue.as_ptr(), + }; + self.extras + .push(TeHlExtra::DebugArgString(debug_arg, cname, cvalue)); + } + Pointer(value) => { + let debug_arg = PerfettoTeHlExtraDebugArgPointer { + header: PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_POINTER, + }, + name: cname.as_ptr(), + value, + }; + self.extras + .push(TeHlExtra::DebugArgPointer(debug_arg, cname)); + } + } + self + } + + /// Add counter value. + pub fn set_counter(&mut self, counter: TrackEventCounter) -> &mut Self { + use TrackEventCounter::*; + match counter { + Int64(value) => { + let int_counter = PerfettoTeHlExtraCounterInt64 { + header: PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_INT64, + }, + value, + }; + self.extras.push(TeHlExtra::CounterInt64(int_counter)); + } + Double(value) => { + let double_counter = PerfettoTeHlExtraCounterDouble { + header: PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_DOUBLE, + }, + value, + }; + self.extras.push(TeHlExtra::CounterDouble(double_counter)); + } + } + self + } + + /// Add track. + pub fn set_track(&mut self, track: &TrackEventTrack) -> &mut Self { + let registered_track = PerfettoTeHlExtraRegisteredTrack { + header: PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_REGISTERED_TRACK, + }, + track: &raw const track.impl_, + }; + self.extras.push(TeHlExtra::Track(registered_track)); + self + } + + /// Add named track. + pub fn set_named_track(&mut self, name: &str, id: u64, parent_uuid: u64) -> &mut Self { + let cname = CString::new(name).unwrap(); + let track = PerfettoTeHlExtraNamedTrack { + header: PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_NAMED_TRACK, + }, + name: cname.as_ptr(), + id, + parent_uuid, + }; + self.extras.push(TeHlExtra::NamedTrack(track, cname)); + self + } + + /// Add flow. + pub fn set_flow(&mut self, flow: &TrackEventFlow) -> &mut Self { + let begin_flow = PerfettoTeHlExtraFlow { + header: PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_FLOW, + }, + id: flow.id, + }; + self.extras.push(TeHlExtra::Flow(begin_flow)); + self + } + + /// Add terminating flow. + pub fn set_terminating_flow(&mut self, flow: &TrackEventFlow) -> &mut Self { + let terminating_flow = PerfettoTeHlExtraFlow { + header: PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_TERMINATING_FLOW, + }, + id: flow.id, + }; + self.extras.push(TeHlExtra::Flow(terminating_flow)); + self + } + + /// Add proto fields. + pub fn set_proto_fields(&mut self, fields: &TrackEventProtoFields) -> &mut Self { + use std::ptr; + + let mut te_fields: Vec = + fields.fields.iter().map(|f| f.to_proto_field()).collect(); + let mut te_field_ptrs: Vec<*mut PerfettoTeHlProtoField> = te_fields + .iter_mut() + .map(|f| f.as_proto_field_ptr()) + .collect(); + te_field_ptrs.push(ptr::null_mut()); + + let proto_fields = PerfettoTeHlExtraProtoFields { + header: PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_PROTO_FIELDS, + }, + fields: te_field_ptrs.as_ptr(), + }; + self.extras.push(TeHlExtra::ProtoFields( + proto_fields, + te_fields, + te_field_ptrs, + )); + self + } + + /// Add proto track. + pub fn set_proto_track(&mut self, track: &TrackEventProtoTrack) -> &mut Self { + use std::ptr; + + let mut te_fields: Vec = + track.fields.iter().map(|f| f.to_proto_field()).collect(); + let mut te_field_ptrs: Vec<*mut PerfettoTeHlProtoField> = te_fields + .iter_mut() + .map(|f| f.as_proto_field_ptr()) + .collect(); + te_field_ptrs.push(ptr::null_mut()); + + let proto_track = PerfettoTeHlExtraProtoTrack { + header: PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_PROTO_TRACK, + }, + uuid: track.uuid, + fields: te_field_ptrs.as_ptr(), + }; + self.extras + .push(TeHlExtra::ProtoTrack(proto_track, te_fields, te_field_ptrs)); + self + } + + /// Add nested track. + pub fn set_nested_tracks(&mut self, tracks: &TrackEventNestedTracks) -> &mut Self { + use std::ptr; + + let mut te_tracks: Vec = + tracks.tracks.iter().map(|f| f.to_nested_track()).collect(); + let mut te_track_ptrs: Vec<*mut PerfettoTeHlNestedTrack> = te_tracks + .iter_mut() + .map(|f| f.as_nested_track_ptr()) + .collect(); + te_track_ptrs.push(ptr::null_mut()); + + let nested_tracks = PerfettoTeHlExtraNestedTracks { + header: PerfettoTeHlExtra { + type_: PerfettoTeHlExtraType_PERFETTO_TE_HL_EXTRA_TYPE_NESTED_TRACKS, + }, + tracks: te_track_ptrs.as_ptr(), + }; + self.extras.push(TeHlExtra::NestedTracks( + nested_tracks, + te_tracks, + te_track_ptrs, + )); + self + } +} + +/// Emits a track event when `category` is enabled. The optional `lambda` is only called +/// when emitting an event. +#[macro_export] +macro_rules! track_event { + ($category:literal, $variant:expr) => {{ $crate::track_event!($category, $variant, |_| {}) }}; + ($category:literal, $variant:expr, $lambda:expr) => {{ + const CATEGORY_INDEX: usize = perfetto_te_ns::category_index($category); + if $crate::__unlikely!(perfetto_te_ns::is_category_enabled(CATEGORY_INDEX)) { + let mut ctx = $crate::track_event::EventContext::default(); + + $lambda(&mut ctx); + + perfetto_te_ns::emit(CATEGORY_INDEX, $variant, &mut ctx); + } + }}; +} + +/// Emits an instant track event when `category` is enabled. +#[macro_export] +macro_rules! track_event_instant { + ($category:literal, $name:literal) => {{ $crate::track_event_instant!($category, $name, |_| {}) }}; + ($category:literal, $name:literal, $lambda:expr) => {{ + $crate::track_event!( + $category, + $crate::track_event::TrackEventType::Instant( + concat!($name, "\0").as_ptr() as *const std::os::raw::c_char + ), + $lambda + ) + }}; +} + +/// Emits a begin track event when `category` is enabled. +#[macro_export] +macro_rules! track_event_begin { + ($category:literal, $name:literal) => {{ $crate::track_event_begin!($category, $name, |_| {}) }}; + ($category:literal, $name:literal, $lambda:expr) => {{ + $crate::track_event!( + $category, + $crate::track_event::TrackEventType::SliceBegin( + concat!($name, "\0").as_ptr() as *const std::os::raw::c_char + ), + $lambda + ) + }}; +} + +/// Emits an end track event when `category` is enabled. +#[macro_export] +macro_rules! track_event_end { + ($category:literal) => {{ $crate::track_event_end!($category, |_| {}) }}; + ($category:literal, $lambda:expr) => {{ + $crate::track_event!( + $category, + $crate::track_event::TrackEventType::SliceEnd, + $lambda + ) + }}; +} + +/// Emits a counter track event when `category` is enabled. +#[macro_export] +macro_rules! track_event_counter { + ($category:literal) => {{ $crate::track_event_counter!($category, |_| {}) }}; + ($category:literal, $lambda:expr) => {{ + $crate::track_event!( + $category, + $crate::track_event::TrackEventType::Counter, + $lambda + ) + }}; +} + +/// Utility struct used to emit scoped track events. +pub struct ScopeGuard(Option); + +impl ScopeGuard { + /// Create a new scope guard that calls `f` when dropped. + pub fn new(f: F) -> Self { + Self(Some(f)) + } +} + +impl Drop for ScopeGuard { + fn drop(&mut self) { + if let Some(f) = self.0.take() { + f(); + } + } +} + +/// Emits a pair of begin/end track events when `category` is enabled. +/// The end event is emitted when the current scope ends. +#[macro_export] +macro_rules! scoped_track_event { + ($category:expr, $name:literal) => { + $crate::scoped_track_event!($category, $name, |_| {}, |_| {}) + }; + ($category:expr, $name:literal, $lambda:expr) => { + $crate::scoped_track_event!($category, $name, $lambda, |_| {}) + }; + ($category:expr, $name:literal, $begin_lambda:expr, $end_lambda:expr) => { + $crate::track_event_begin!($category, $name, $begin_lambda); + let __scope_guard = $crate::track_event::ScopeGuard::new(|| { + $crate::track_event_end!($category, $end_lambda) + }); + }; +} + +/// Calls `lambda` for each active instance where `category` is enabled. +#[macro_export] +macro_rules! trace_for_category { + ($category:literal, $lambda:expr) => {{ + const CATEGORY_INDEX: usize = perfetto_te_ns::category_index($category); + if $crate::__unlikely!(perfetto_te_ns::is_category_enabled(CATEGORY_INDEX)) { + perfetto_te_ns::trace(CATEGORY_INDEX, $lambda); + } + }}; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::pb_decoder::{PbDecoder, PbDecoderField}; + use crate::protos::trace::track_event::debug_annotation::*; + use crate::protos::trace::track_event::track_event::TrackEventFieldNumber; + use crate::protos::trace::track_event::track_event::TrackEventType as EventType; + use crate::tests::{TracingSessionBuilder, acquire_test_environment}; + use crate::tracing_session::TracingSession; + use std::{error::Error, sync::MutexGuard}; + + track_event_categories! { + pub mod test_te_ns { + ( "cat1", "Test category 1", [ "tag1" ] ), + ( "cat2", "Test category 2", [ "tag2", "tag3" ] ), + } + } + + #[test] + fn category_index() { + assert_eq!(test_te_ns::category_index("cat1"), 0); + assert_eq!(test_te_ns::category_index("cat2"), 1); + } + + struct TeTestFixture { + _lock: MutexGuard<'static, ()>, + } + + impl TeTestFixture { + fn new() -> Self { + let _lock = acquire_test_environment(); + TrackEvent::init(); + test_te_ns::register().expect("register failed"); + Self { _lock } + } + } + + impl Drop for TeTestFixture { + fn drop(&mut self) { + test_te_ns::unregister().expect("unregister failed"); + } + } + + #[derive(Default, Clone)] + struct DebugAnnotation { + bool_value: Option, + uint64_value: Option, + int64_value: Option, + double_value: Option, + string_value: Option, + pointer_value: Option, + } + + impl DebugAnnotation { + fn decode(data: &[u8]) -> Self { + use PbDecoderField::*; + let mut da = DebugAnnotation::default(); + const BOOL_VALUE_ID: u32 = DebugAnnotationFieldNumber::BoolValue as u32; + const UINT_VALUE_ID: u32 = DebugAnnotationFieldNumber::UintValue as u32; + const INT_VALUE_ID: u32 = DebugAnnotationFieldNumber::IntValue as u32; + const DOUBLE_VALUE_ID: u32 = DebugAnnotationFieldNumber::DoubleValue as u32; + const STRING_VALUE_ID: u32 = DebugAnnotationFieldNumber::StringValue as u32; + const POINTER_VALUE_ID: u32 = DebugAnnotationFieldNumber::PointerValue as u32; + for field in PbDecoder::new(data) { + match field.as_ref().unwrap_or_else(|e| panic!("Error: {}", e)) { + (BOOL_VALUE_ID, Varint(v)) => da.bool_value = Some(*v != 0), + (UINT_VALUE_ID, Varint(v)) => da.uint64_value = Some(*v), + (INT_VALUE_ID, Varint(v)) => da.int64_value = Some(*v as i64), + (DOUBLE_VALUE_ID, Fixed64(v)) => da.double_value = Some(f64::from_bits(*v)), + (STRING_VALUE_ID, Delimited(v)) => { + da.string_value = Some(String::from_utf8(v.to_vec()).unwrap()) + } + (POINTER_VALUE_ID, Varint(v)) => da.pointer_value = Some(*v), + _ => println!("WARNING: unknown DebugAnnotation field: {:?}", field), + } + } + da + } + } + + #[derive(Default, Clone)] + struct Event { + timestamp: u64, + category_iids: Option, + name_iid: Option, + name: Option, + r#type: Option, + counter_value: Option, + debug_annotations: Vec, + } + + impl Event { + fn decode(data: &[u8]) -> Self { + use PbDecoderField::*; + let mut event = Event::default(); + const CATEGORY_IIDS_ID: u32 = TrackEventFieldNumber::CategoryIids as u32; + const NAME_IID_ID: u32 = TrackEventFieldNumber::NameIid as u32; + const NAME_ID: u32 = TrackEventFieldNumber::Name as u32; + const TYPE_ID: u32 = TrackEventFieldNumber::Type as u32; + const COUNTER_VALUE_ID: u32 = TrackEventFieldNumber::CounterValue as u32; + const DEBUG_ANNOTATIONS_ID: u32 = TrackEventFieldNumber::DebugAnnotations as u32; + for field in PbDecoder::new(data) { + match field.as_ref().unwrap_or_else(|e| panic!("Error: {}", e)) { + (CATEGORY_IIDS_ID, Varint(v)) => event.category_iids = Some(*v), + (NAME_IID_ID, Varint(v)) => event.name_iid = Some(*v), + (NAME_ID, Delimited(v)) => { + event.name = Some(String::from_utf8(v.to_vec()).unwrap()) + } + (TYPE_ID, Varint(v)) => { + event.r#type = Some(EventType::try_from(*v as u32).unwrap()) + } + (COUNTER_VALUE_ID, Varint(v)) => event.counter_value = Some(*v as i64), + (DEBUG_ANNOTATIONS_ID, Delimited(v)) => { + event.debug_annotations.push(DebugAnnotation::decode(v)) + } + _ => println!("WARNING: unknown TrackEvent field: {:?}", field), + } + } + event + } + } + + fn read_trace_events(tracing_session: &mut TracingSession) -> Vec { + use crate::protos::trace::{trace::*, trace_packet::*}; + use PbDecoderField::*; + use std::sync::{Arc, Mutex}; + let trace_data = Arc::new(Mutex::new(vec![])); + let trace_data_for_write = Arc::clone(&trace_data); + tracing_session.read_trace_blocking(move |data, _end| { + let mut written_data = trace_data_for_write.lock().unwrap(); + written_data.extend_from_slice(data); + }); + let data = trace_data.lock().unwrap(); + let mut events = vec![]; + const PACKET_ID: u32 = TraceFieldNumber::Packet as u32; + for trace_field in PbDecoder::new(&data) { + if let (PACKET_ID, PbDecoderField::Delimited(data)) = trace_field.unwrap() { + const TIMESTAMP_ID: u32 = TracePacketFieldNumber::Timestamp as u32; + const TRACK_EVENT_ID: u32 = TracePacketFieldNumber::TrackEvent as u32; + let mut timestamp = 0; + let mut event = None; + for packet_field in PbDecoder::new(data) { + match packet_field + .as_ref() + .unwrap_or_else(|e| panic!("Error: {}", e)) + { + (TIMESTAMP_ID, Varint(v)) => timestamp = *v, + (TRACK_EVENT_ID, Delimited(v)) => event = Some(Event::decode(v)), + // Ignore all other packet fields. + _ => {} + } + } + if let Some(event) = &mut event { + event.timestamp = timestamp; + events.push(event.clone()); + } + } + } + events + } + + #[test] + fn register_track() -> Result<(), Box> { + let _fx = TeTestFixture::new(); + let named_track = TrackEventTrack::register_named_track( + "mytrack", + 123, + TrackEventTrack::process_track_uuid(), + )?; + assert!(named_track.uuid() != 0); + let counter_track = TrackEventTrack::register_counter_track( + "mycounter", + TrackEventTrack::process_track_uuid(), + )?; + assert!(counter_track.uuid() != 0); + assert!(counter_track.uuid() != named_track.uuid()); + Ok(()) + } + + #[test] + fn is_category_enabled() -> Result<(), Box> { + use test_te_ns as perfetto_te_ns; + let _fx = TeTestFixture::new(); + let mut session = TracingSessionBuilder::new() + .set_data_source_name("track_event") + .add_enabled_category("cat1") + .add_disabled_category("*") + .build()?; + assert!(!track_event_category_enabled!("cat1")); + assert!(!track_event_category_enabled!("cat2")); + session.start_blocking(); + assert!(track_event_category_enabled!("cat1")); + assert!(!track_event_category_enabled!("cat2")); + session.stop_blocking(); + assert!(!track_event_category_enabled!("cat1")); + assert!(!track_event_category_enabled!("cat2")); + Ok(()) + } + + #[test] + fn category_callback() -> Result<(), Box> { + use std::sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }; + use test_te_ns as perfetto_te_ns; + let _fx = TeTestFixture::new(); + let mut session = TracingSessionBuilder::new() + .set_data_source_name("track_event") + .add_enabled_category("cat1") + .add_disabled_category("*") + .build()?; + session.start_blocking(); + let executed = Arc::new(AtomicUsize::new(0)); + let executed_for_callback = Arc::clone(&executed); + track_event_set_category_callback!( + "cat1", + move |_inst_id, enabled, _global_state_changed| { + if enabled { + executed_for_callback.fetch_add(1, Ordering::Relaxed); + } + } + ); + session.stop_blocking(); + assert_eq!(executed.load(Ordering::Relaxed), 1); + Ok(()) + } + + #[test] + fn instant() -> Result<(), Box> { + use test_te_ns as perfetto_te_ns; + let _fx = TeTestFixture::new(); + let mut session = TracingSessionBuilder::new() + .set_data_source_name("track_event") + .add_enabled_category("cat1") + .add_disabled_category("*") + .build()?; + session.start_blocking(); + track_event_instant!("cat1", "name1"); + session.stop_blocking(); + let events = read_trace_events(&mut session); + assert_eq!(events.len(), 1); + assert_eq!(events[0].r#type, Some(EventType::TypeInstant)); + assert!(events[0].name_iid.is_some()); + Ok(()) + } + + #[test] + fn no_intern() -> Result<(), Box> { + use test_te_ns as perfetto_te_ns; + let _fx = TeTestFixture::new(); + let mut session = TracingSessionBuilder::new() + .set_data_source_name("track_event") + .add_enabled_category("cat1") + .add_disabled_category("*") + .build()?; + session.start_blocking(); + track_event_instant!("cat1", "name1", |ctx: &mut EventContext| { + ctx.set_no_intern(); + }); + session.stop_blocking(); + let events = read_trace_events(&mut session); + assert_eq!(events.len(), 1); + assert_eq!(events[0].r#type, Some(EventType::TypeInstant)); + assert_eq!(events[0].name, Some("name1".to_string())); + Ok(()) + } + + #[test] + fn slice() -> Result<(), Box> { + use test_te_ns as perfetto_te_ns; + let _fx = TeTestFixture::new(); + let mut session = TracingSessionBuilder::new() + .set_data_source_name("track_event") + .add_enabled_category("cat1") + .add_disabled_category("*") + .build()?; + session.start_blocking(); + track_event_begin!("cat1", "name2"); + track_event_end!("cat1"); + session.stop_blocking(); + let events = read_trace_events(&mut session); + assert_eq!(events.len(), 2); + assert_eq!(events[0].r#type, Some(EventType::TypeSliceBegin)); + assert!(events[0].name_iid.is_some()); + assert_eq!(events[1].r#type, Some(EventType::TypeSliceEnd)); + assert!(events[1].name_iid.is_none()); + Ok(()) + } + + #[test] + fn counter() -> Result<(), Box> { + use test_te_ns as perfetto_te_ns; + let _fx = TeTestFixture::new(); + let mut session = TracingSessionBuilder::new() + .set_data_source_name("track_event") + .add_enabled_category("cat2") + .add_disabled_category("*") + .build()?; + session.start_blocking(); + track_event_counter!("cat2", |ctx: &mut EventContext| { + ctx.set_counter(TrackEventCounter::Int64(56)); + }); + session.stop_blocking(); + let events = read_trace_events(&mut session); + assert_eq!(events.len(), 1); + assert_eq!(events[0].r#type, Some(EventType::TypeCounter)); + assert_eq!(events[0].counter_value, Some(56)); + Ok(()) + } + + #[test] + fn with_timestamp() -> Result<(), Box> { + use test_te_ns as perfetto_te_ns; + let _fx = TeTestFixture::new(); + let mut session = TracingSessionBuilder::new() + .set_data_source_name("track_event") + .add_enabled_category("cat2") + .add_disabled_category("*") + .build()?; + session.start_blocking(); + let te_timestamp = TrackEventTimestamp::now(); + track_event_instant!("cat2", "name3", |ctx: &mut EventContext| { + ctx.set_timestamp(te_timestamp); + }); + session.stop_blocking(); + let events = read_trace_events(&mut session); + assert_eq!(events.len(), 1); + assert_eq!(events[0].r#type, Some(EventType::TypeInstant)); + assert_eq!(events[0].timestamp, te_timestamp.timestamp()); + Ok(()) + } + + #[test] + fn with_debug_args() -> Result<(), Box> { + use test_te_ns as perfetto_te_ns; + let _fx = TeTestFixture::new(); + let mut session = TracingSessionBuilder::new() + .set_data_source_name("track_event") + .add_enabled_category("cat2") + .add_disabled_category("*") + .build()?; + session.start_blocking(); + track_event_instant!("cat2", "name4", |ctx: &mut EventContext| { + // Use optional builder pattern to set debug arguments. + ctx.add_debug_arg("dbg_arg1", TrackEventDebugArg::Bool(true)) + .add_debug_arg("dbg_arg2", TrackEventDebugArg::Uint64(1234)) + .add_debug_arg("dbg_arg3", TrackEventDebugArg::Int64(-1234)) + .add_debug_arg("dbg_arg4", TrackEventDebugArg::Double(std::f64::consts::PI)) + .add_debug_arg( + "dbg_arg5", + TrackEventDebugArg::String("this is a string value"), + ) + .add_debug_arg( + "dbg_arg6", + TrackEventDebugArg::Pointer("random".as_ptr() as usize), + ); + }); + session.stop_blocking(); + let events = read_trace_events(&mut session); + assert_eq!(events.len(), 1); + assert_eq!(events[0].r#type, Some(EventType::TypeInstant)); + assert_eq!(events[0].debug_annotations.len(), 6); + assert_eq!(events[0].debug_annotations[0].bool_value, Some(true)); + assert_eq!(events[0].debug_annotations[1].uint64_value, Some(1234)); + assert_eq!(events[0].debug_annotations[2].int64_value, Some(-1234)); + assert_eq!( + events[0].debug_annotations[3].double_value, + Some(std::f64::consts::PI) + ); + assert_eq!( + events[0].debug_annotations[4].string_value, + Some("this is a string value".to_string()) + ); + assert!(events[0].debug_annotations[5].pointer_value.is_some()); + Ok(()) + } + + #[test] + fn with_dynamic_name() -> Result<(), Box> { + use test_te_ns as perfetto_te_ns; + let _fx = TeTestFixture::new(); + let mut session = TracingSessionBuilder::new() + .set_data_source_name("track_event") + .add_enabled_category("cat2") + .add_disabled_category("*") + .build()?; + session.start_blocking(); + let cname = CString::new("dynamic_name").unwrap(); + track_event!("cat2", TrackEventType::Instant(cname.as_ptr())); + session.stop_blocking(); + let events = read_trace_events(&mut session); + assert_eq!(events.len(), 1); + assert_eq!(events[0].r#type, Some(EventType::TypeInstant)); + assert!(events[0].name_iid.is_some()); + Ok(()) + } + + #[test] + fn scoped() -> Result<(), Box> { + use test_te_ns as perfetto_te_ns; + let _fx = TeTestFixture::new(); + let mut session = TracingSessionBuilder::new() + .set_data_source_name("track_event") + .add_enabled_category("cat1") + .add_disabled_category("*") + .build()?; + session.start_blocking(); + { + scoped_track_event!("cat1", "name5"); + { + scoped_track_event!("cat1", "name6", |ctx: &mut EventContext| { + ctx.add_debug_arg("scoped_dbg_arg", TrackEventDebugArg::Bool(true)); + }); + scoped_track_event!( + "cat1", + "name7", + |ctx: &mut EventContext| { + ctx.add_debug_arg("scoped_begin_dbg_arg", TrackEventDebugArg::Bool(true)); + }, + |ctx: &mut EventContext| { + ctx.add_debug_arg("scoped_end_dbg_arg", TrackEventDebugArg::Bool(false)); + } + ); + } + } + session.stop_blocking(); + let events = read_trace_events(&mut session); + assert_eq!(events.len(), 6); + assert_eq!(events[0].r#type, Some(EventType::TypeSliceBegin)); + assert_eq!(events[1].r#type, Some(EventType::TypeSliceBegin)); + assert_eq!(events[1].debug_annotations.len(), 1); + assert_eq!(events[1].debug_annotations[0].bool_value, Some(true)); + assert_eq!(events[2].r#type, Some(EventType::TypeSliceBegin)); + assert_eq!(events[2].debug_annotations.len(), 1); + assert_eq!(events[2].debug_annotations[0].bool_value, Some(true)); + assert_eq!(events[3].r#type, Some(EventType::TypeSliceEnd)); + assert_eq!(events[3].debug_annotations.len(), 1); + assert_eq!(events[3].debug_annotations[0].bool_value, Some(false)); + assert_eq!(events[4].r#type, Some(EventType::TypeSliceEnd)); + assert_eq!(events[5].r#type, Some(EventType::TypeSliceEnd)); + Ok(()) + } + + const CUSTOM_CLOCK_ID: u32 = 123456; + + #[test] + fn trace_for_category() -> Result<(), Box> { + use crate::protos::trace::{clock_snapshot::*, trace::*, trace_packet::*}; + use std::sync::{Arc, Mutex}; + use test_te_ns as perfetto_te_ns; + let _fx = TeTestFixture::new(); + let mut session = TracingSessionBuilder::new() + .set_data_source_name("track_event") + .add_enabled_category("cat1") + .add_disabled_category("*") + .build()?; + session.start_blocking(); + trace_for_category!("cat1", |ctx: &mut TraceContext| { + ctx.add_packet(|packet: &mut TracePacket| { + packet + .set_timestamp_clock_id(PerfettoTeTimestampType_PERFETTO_TE_TIMESTAMP_TYPE_BOOT) + .set_timestamp(42) + .set_clock_snapshot(|clock_snapshot: &mut ClockSnapshot| { + clock_snapshot + .set_clocks(|clock: &mut Clock| { + clock.set_clock_id( + PerfettoTeTimestampType_PERFETTO_TE_TIMESTAMP_TYPE_BOOT, + ); + clock.set_timestamp(42); + }) + .set_clocks(|clock: &mut Clock| { + clock.set_clock_id(CUSTOM_CLOCK_ID); + clock.set_timestamp(10000); + }); + }); + }); + }); + session.stop_blocking(); + let trace_data = Arc::new(Mutex::new(vec![])); + let trace_data_for_write = Arc::clone(&trace_data); + session.read_trace_blocking(move |data, _end| { + let mut written_data = trace_data_for_write.lock().unwrap(); + written_data.extend_from_slice(data); + }); + let data = trace_data.lock().unwrap(); + assert!(!data.is_empty()); + let mut clock_snapshot_found = false; + for trace_field in PbDecoder::new(&data) { + const PACKET_ID: u32 = TraceFieldNumber::Packet as u32; + if let (PACKET_ID, PbDecoderField::Delimited(data)) = trace_field.unwrap() { + for packet_field in PbDecoder::new(data) { + const CLOCK_SNAPSHOT_ID: u32 = TracePacketFieldNumber::ClockSnapshot as u32; + if let (CLOCK_SNAPSHOT_ID, PbDecoderField::Delimited(_)) = packet_field.unwrap() + { + clock_snapshot_found = true; + } + } + } + } + assert!(clock_snapshot_found); + Ok(()) + } +} diff --git a/contrib/rust-sdk/tools/gen_rust_protos b/contrib/rust-sdk/tools/gen_rust_protos new file mode 100755 index 00000000000..fbde081010b --- /dev/null +++ b/contrib/rust-sdk/tools/gen_rust_protos @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 +# Copyright (C) 2025 Rivos Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from collections import defaultdict +import argparse +import filecmp +import os +import pathlib +import shutil +import subprocess +import sys +import tempfile + +SOURCE_FILES = [ + { + 'files': [ + 'protos/perfetto/common/builtin_clock.proto', + 'protos/perfetto/common/track_event_descriptor.proto', + 'protos/perfetto/config/priority_boost/priority_boost_config.proto', + 'protos/perfetto/config/test_config.proto', + 'protos/perfetto/config/trace_config.proto', + 'protos/perfetto/config/track_event/track_event_config.proto', + 'protos/perfetto/trace/clock_snapshot.proto', + 'protos/perfetto/trace/profiling/profile_common.proto', + 'protos/perfetto/trace/test_event.proto', + 'protos/perfetto/trace/trace.proto', + 'protos/perfetto/trace/track_event/chrome_active_processes.proto', + 'protos/perfetto/trace/track_event/chrome_application_state_info.proto', + 'protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.proto', + 'protos/perfetto/trace/track_event/chrome_content_settings_event_info.proto', + 'protos/perfetto/trace/track_event/chrome_frame_reporter.proto', + 'protos/perfetto/trace/track_event/chrome_histogram_sample.proto', + 'protos/perfetto/trace/track_event/chrome_keyed_service.proto', + 'protos/perfetto/trace/track_event/chrome_latency_info.proto', + 'protos/perfetto/trace/track_event/chrome_legacy_ipc.proto', + 'protos/perfetto/trace/track_event/chrome_message_pump.proto', + 'protos/perfetto/trace/track_event/chrome_mojo_event_info.proto', + 'protos/perfetto/trace/track_event/chrome_process_descriptor.proto', + 'protos/perfetto/trace/track_event/chrome_renderer_scheduler_state.proto', + 'protos/perfetto/trace/track_event/chrome_thread_descriptor.proto', + 'protos/perfetto/trace/track_event/chrome_user_event.proto', + 'protos/perfetto/trace/track_event/chrome_window_handle_event_info.proto', + 'protos/perfetto/trace/track_event/counter_descriptor.proto', + 'protos/perfetto/trace/track_event/debug_annotation.proto', + 'protos/perfetto/trace/track_event/log_message.proto', + 'protos/perfetto/trace/track_event/process_descriptor.proto', + 'protos/perfetto/trace/track_event/screenshot.proto', + 'protos/perfetto/trace/track_event/source_location.proto', + 'protos/perfetto/trace/track_event/task_execution.proto', + 'protos/perfetto/trace/track_event/thread_descriptor.proto', + 'protos/perfetto/trace/track_event/track_descriptor.proto', + 'protos/perfetto/trace/track_event/track_event.proto', + ], + 'custom_files': [ + 'protos/perfetto/common/data_source_descriptor.proto', + 'protos/perfetto/config/data_source_config.proto', + 'protos/perfetto/trace/interned_data/interned_data.proto', + 'protos/perfetto/trace/trace_packet.proto', + ], + 'path_strip_prefix': 'protos/perfetto', + 'path_add_prefix': 'contrib/rust-sdk/perfetto/src/protos', + }, + { + 'files': [ + 'protos/perfetto/common/gpu_counter_descriptor.proto', + 'protos/perfetto/config/gpu/gpu_counter_config.proto', + 'protos/perfetto/config/gpu/gpu_renderstages_config.proto', + 'protos/perfetto/config/gpu/vulkan_memory_config.proto', + 'protos/perfetto/trace/gpu/gpu_counter_event.proto', + 'protos/perfetto/trace/gpu/gpu_log.proto', + 'protos/perfetto/trace/gpu/gpu_render_stage_event.proto', + 'protos/perfetto/trace/gpu/vulkan_api_event.proto', + 'protos/perfetto/trace/gpu/vulkan_memory_event.proto', + ], + 'custom_files': [ + 'protos/perfetto/common/data_source_descriptor.proto', + 'protos/perfetto/config/data_source_config.proto', + 'protos/perfetto/trace/interned_data/interned_data.proto', + 'protos/perfetto/trace/trace_packet.proto', + ], + 'path_strip_prefix': 'protos/perfetto', + 'path_add_prefix': 'contrib/rust-sdk/perfetto-protos-gpu/src/protos', + }, +] + +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) +IS_WIN = sys.platform.startswith('win') + +SCRIPT_PATH = 'contrib/rust-sdk/tools/gen_rust_protos' + + +def protozero_rust_plugin_path(out_directory): + path = os.path.join(out_directory, + 'protozero_rust_plugin') + ('.exe' if IS_WIN else '') + assert os.path.isfile(path) + return path + + +def protoc_path(out_directory): + path = os.path.join(out_directory, 'protoc') + ('.exe' if IS_WIN else '') + assert os.path.isfile(path) + return path + + +def call(cmd, *args): + path = os.path.join('tools', cmd) + command = ['python3', path] + list(args) + print('Running', ' '.join(command)) + try: + subprocess.check_call(command, cwd=ROOT_DIR) + except subprocess.CalledProcessError as e: + assert False, 'Command: {} failed'.format(' '.join(command)) + + +# Transforms filename extension like the ProtoZero Rust plugin +def transform_extension(filename): + old_suffix = ".proto" + new_suffix = ".pz.rs" + if filename.endswith(old_suffix): + return filename[:-len(old_suffix)] + new_suffix + return filename + + +def generate(source, outdir, protoc_path, protozero_rust_plugin_path, + path_strip_prefix, path_add_prefix): + options = { + 'path_strip_prefix': path_strip_prefix, + 'path_add_prefix': path_add_prefix, + 'invoker': SCRIPT_PATH, + } + serialized_options = ','.join( + ['{}={}'.format(name, value) for name, value in options.items()]) + subprocess.check_call([ + protoc_path, + '--proto_path=.', + '--plugin=protoc-gen-plugin={}'.format(protozero_rust_plugin_path), + '--plugin_out={}:{}'.format(serialized_options, outdir), + source, + ], + cwd=ROOT_DIR) + + +def generate_mod(tmpfilename, mods): + with open(tmpfilename, 'w') as f: + print( + """// Copyright (C) 2025 Rivos Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Autogenerated by the gen_rust_protos script. +// DO NOT EDIT.""", + file=f) + for mod in sorted(mods): + modname = transform_extension(os.path.basename(mod)) + if modname.endswith('.pz.rs'): + print(f'\n/// `{modname[:-6]}` protos.', file=f) + print(f'#[path = "{modname}"]', file=f) + print(f'pub mod {modname[:-6]};', file=f) + else: + print(f'\n/// `{modname}` protos.', file=f) + print(f'pub mod {modname};', file=f) + + +def mods_by_directory(sources, path_strip_prefix): + groups = defaultdict(set) + for path in sources: + if path.startswith(path_strip_prefix): + path = path[len(path_strip_prefix):] + modpath = pathlib.Path(path) + for parent in modpath.parents: + if parent != pathlib.Path("/"): + directory = str(parent) + groups[directory].add(path) + path = directory + return dict(groups) + + +def rust_path_for(path, path_strip_prefix, path_add_prefix): + if path.startswith(path_strip_prefix): + rust_path = path[len(path_strip_prefix):] + else: + rust_path = path + return path_add_prefix + rust_path + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--check-only', action='store_true') + parser.add_argument('OUT') + args = parser.parse_args() + out = args.OUT + + call('ninja', '-C', out, 'protoc', 'protozero_rust_plugin') + + try: + with tempfile.TemporaryDirectory() as tmpdirname: + for sources in SOURCE_FILES: + for source in sources['files']: + generate( + source, + tmpdirname, + protoc_path(out), + protozero_rust_plugin_path(out), + path_strip_prefix=sources['path_strip_prefix'], + path_add_prefix=sources['path_add_prefix'], + ) + + tmpfilename = os.path.join(tmpdirname, transform_extension(source)) + targetfilename = rust_path_for( + source, + sources['path_strip_prefix'], + sources['path_add_prefix'], + ) + targetfilename = transform_extension(targetfilename) + + if args.check_only: + if not filecmp.cmp(tmpfilename, targetfilename): + raise AssertionError('Target {} does not match', targetfilename) + else: + os.makedirs(os.path.dirname(targetfilename), exist_ok=True) + shutil.copyfile(tmpfilename, targetfilename) + + modsources = sources['files'] + sources['custom_files'] + for directory, mods in mods_by_directory( + modsources, sources['path_strip_prefix']).items(): + tmpfilename = os.path.join(tmpdirname, "mod.rs") + generate_mod(tmpfilename, mods) + targetmoddir = rust_path_for( + directory, + sources['path_strip_prefix'], + sources['path_add_prefix'], + ) + targetfilename = os.path.join(targetmoddir, "mod.rs") + + if args.check_only: + if not filecmp.cmp(tmpfilename, targetfilename): + raise AssertionError('Target {} does not match', targetfilename) + else: + os.makedirs(os.path.dirname(targetfilename), exist_ok=True) + shutil.copyfile(tmpfilename, targetfilename) + + except AssertionError as e: + if not str(e): + raise + print('Error: {}'.format(e)) + return 1 + + +if __name__ == '__main__': + exit(main()) diff --git a/debian/.gitlab-ci.yml b/debian/.gitlab-ci.yml deleted file mode 100644 index 892f3cd2be8..00000000000 --- a/debian/.gitlab-ci.yml +++ /dev/null @@ -1,3 +0,0 @@ -include: - - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml - - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml diff --git a/debian/README.source b/debian/README.source deleted file mode 100644 index 0080647cc70..00000000000 --- a/debian/README.source +++ /dev/null @@ -1,10 +0,0 @@ -perfetto for Debian ------------------- - -Perfetto is an open-source suite of SDKs, daemons and tools which use tracing to -help developers understand the behaviour of the complex systems and root-cause -functional and performance issues on client / embedded systems. - -See https://github.com/google/perfetto - - -- Perfetto Team Wed, 1 Oct 2025 19:23:25 +0000 \ No newline at end of file diff --git a/debian/changelog b/debian/changelog deleted file mode 100644 index a4bc7a7bf8f..00000000000 --- a/debian/changelog +++ /dev/null @@ -1,35 +0,0 @@ -perfetto (52.0-1) unstable; urgency=medium - - * Update packaging to use debhelper-compat (= 13). - * Move `libperfetto.so` to be installed in subdirectory perfetto within - /usr/lib. - * Add `tracebox` binary to the installation. - * Generate manpages for perfetto, tracebox, traced, and traced_probes. - * Use /run for sockets instead of /var/run. - * Migrate Vcs-Git and Vcs-Browser links to Github. - * Filter out incompatible build flags and add D_FORTIFY_SOURCE - * Remove unbuilt `traced-perf.service` systemd unit. - * Various lintian fixes. - - -- Perfetto Team Thu, 02 Oct 2025 12:00:00 +0000 - -perfetto (11.0-1) unstable; urgency=medium - * Add the Perfetto perf sampling and profiling service. - - -- Sami Kyostila Mon, 18 Jan 2021 12:10:00 +0000 - -perfetto (9.0-1) unstable; urgency=medium - - * Run traced under a dedicated system user account and set socket - permissions accordingly so that any user can write trace events, but - only the users in the "traced-consumer" group can read trace data. - * Update to debhelper 10. - * Bump version to match Perfetto release. - - -- Sami Kyostila Mon, 9 Nov 2020 15:24:00 +0000 - -perfetto (0.1-1) unstable; urgency=medium - - * Initial release - - -- Sami Kyostila Mon, 15 Jan 2018 23:23:25 +0000 diff --git a/debian/control b/debian/control deleted file mode 100644 index 2b4f76f2d64..00000000000 --- a/debian/control +++ /dev/null @@ -1,28 +0,0 @@ -Source: perfetto -Section: utils -Priority: optional -Maintainer: Perfetto Team -Build-Depends: - debhelper (>= 13), - debhelper-compat (= 13), - generate-ninja, - git, - libprotoc-dev, - ninja-build, - pandoc, - protobuf-compiler, - python3, - zlib1g-dev, - zlib1g -Standards-Version: 4.5.1 -Homepage: https://perfetto.dev -Vcs-Git: https://github.com/google/perfetto -Vcs-Browser: https://github.com/google/perfetto -Rules-Requires-Root: no - -Package: perfetto -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, zlib1g -Description: Performance instrumentation and logging framework - Perfetto is a performance instrumentation and logging framework for POSIX - systems. diff --git a/debian/copyright b/debian/copyright deleted file mode 100644 index 92e41382810..00000000000 --- a/debian/copyright +++ /dev/null @@ -1,18 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: perfetto -Source: https://github.com/google/perfetto - -Files: * -Copyright: Copyright (C) 2017 The Android Open Source Project -License: Apache-2 - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - . - /usr/share/common-licenses/Apache-2.0 - . - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/debian/perfetto-docs.docs b/debian/perfetto-docs.docs deleted file mode 100644 index 7fa95707b3f..00000000000 --- a/debian/perfetto-docs.docs +++ /dev/null @@ -1 +0,0 @@ -README.source diff --git a/debian/perfetto.install b/debian/perfetto.install deleted file mode 100644 index c75adc8244d..00000000000 --- a/debian/perfetto.install +++ /dev/null @@ -1,7 +0,0 @@ -out/release/libperfetto.so usr/lib/perfetto -out/release/traced usr/sbin -out/release/traced_probes usr/sbin -out/release/perfetto usr/bin -out/release/tracebox usr/bin -debian/traced.service usr/lib/systemd/system -debian/traced-probes.service usr/lib/systemd/system diff --git a/debian/perfetto.manpages b/debian/perfetto.manpages deleted file mode 100644 index cd437962dba..00000000000 --- a/debian/perfetto.manpages +++ /dev/null @@ -1,4 +0,0 @@ -debian/perfetto.1 -debian/tracebox.1 -debian/traced.8 -debian/traced_probes.8 \ No newline at end of file diff --git a/debian/postinst b/debian/postinst deleted file mode 100755 index a583930bc21..00000000000 --- a/debian/postinst +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -set -e -adduser --home /nonexistent --quiet --system --no-create-home --group traced -addgroup --quiet --system traced-consumer -usermod -a -G traced-consumer traced -mkdir -m 755 /run/perfetto -chown traced:traced /run/perfetto - -#DEBHELPER# diff --git a/debian/postrm b/debian/postrm deleted file mode 100755 index 8f8abc072b7..00000000000 --- a/debian/postrm +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -set -e -rm -rf /run/perfetto - -#DEBHELPER# diff --git a/debian/rules b/debian/rules deleted file mode 100755 index 67be7b06613..00000000000 --- a/debian/rules +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/make -f - -export DEB_BUILD_MAINT_OPTIONS = hardening=+all -DPKG_EXPORT_BUILDFLAGS = 1 -include /usr/share/dpkg/buildflags.mk - -# Final build flags passed to gn. -FINAL_CFLAGS := $(filter-out -Werror=implicit-function-declaration,${CFLAGS}) -D_FORTIFY_SOURCE=2 -FINAL_CXXFLAGS := ${CXXFLAGS} -D_FORTIFY_SOURCE=2 -FINAL_LDFLAGS := ${LDFLAGS} -Wl,-rpath=/usr/lib/perfetto - -# Build directory for gn/ninja. -BUILD_DIR := out/release - -PANDOC_CMD := pandoc -s -t man --shift-heading-level-by=-1 - -%: - dh $@ - -override_dh_auto_configure: MAYBE_HOST_CPU=$(shell \ - if [ "${DEB_BUILD_GNU_CPU}" = "i686" ]; then \ - echo "host_cpu=\\\"x86\\\"";\ - elif [ "${DEB_BUILD_GNU_CPU}" = "x86_64" ]; then \ - echo "host_cpu=\\\"x64\\\"";\ - elif [ "${DEB_BUILD_GNU_CPU}" = "aarch64" ]; then \ - echo "host_cpu=\\\"arm64\\\"";\ - elif [ "${DEB_BUILD_GNU_CPU}" = "arm64" ]; then \ - echo "host_cpu=\\\"arm64\\\"";\ - elif [ "${DEB_BUILD_GNU_CPU:0:3}" == "arm" ]; then \ - echo "host_cpu=\\\"arm\\\"";\ - fi\ -) -override_dh_auto_configure: - env - uname -a - gn gen out/release --args="\ - is_debug=false \ - use_custom_libcxx=false \ - is_hermetic_clang=false \ - is_system_compiler=true \ - is_clang=false \ - skip_buildtools_check=true \ - enable_perfetto_integration_tests=false \ - enable_perfetto_unittests=false \ - perfetto_use_system_protobuf=true \ - perfetto_use_system_zlib=true \ - perfetto_enable_git_rev_version_header=false \ - extra_cflags=\"${FINAL_CFLAGS}\" \ - extra_cxxflags=\"${FINAL_CXXFLAGS}\" \ - extra_ldflags=\"${FINAL_LDFLAGS}\" \ - cc=\"${CC}\" \ - cxx=\"${CXX}\" \ - ${MAYBE_HOST_CPU}" - -override_dh_auto_build: - # Build binaries. - ninja -v -C ${BUILD_DIR} perfetto tracebox traced traced_probes - - # Generate manpages. - ${PANDOC_CMD} docs/reference/perfetto-cli.md > debian/perfetto.1 - ${PANDOC_CMD} docs/reference/tracebox.md > debian/tracebox.1 - ${PANDOC_CMD} docs/getting-started/system-tracing.md > debian/traced.8 - ${PANDOC_CMD} docs/getting-started/system-tracing.md > debian/traced_probes.8 - -override_dh_auto_clean: - rm -rf ${BUILD_DIR} diff --git a/debian/source/format b/debian/source/format deleted file mode 100644 index 46ebe026659..00000000000 --- a/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (quilt) \ No newline at end of file diff --git a/debian/traced-probes.service b/debian/traced-probes.service deleted file mode 100644 index f788458acca..00000000000 --- a/debian/traced-probes.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Perfetto data sources for system tracing (ftrace and /proc pollers) -Documentation=https://perfetto.dev/docs/ - -[Service] -ExecStart=/usr/sbin/traced_probes -User=root -Group=root - -[Install] -WantedBy=multi-user.target diff --git a/debian/traced.service b/debian/traced.service deleted file mode 100644 index 8bd30f4bdd9..00000000000 --- a/debian/traced.service +++ /dev/null @@ -1,20 +0,0 @@ -[Unit] -Description=Perfetto tracing service daemon -Documentation=https://perfetto.dev/docs/ - -[Service] -ExecStart=/usr/sbin/traced \ - --set-socket-permissions traced:0666:traced-consumer:0660 -User=traced -Group=traced -PrivateTmp=no -PrivateDevices=yes -PrivateNetwork=yes -ProtectSystem=yes -ProtectHome=yes -NoNewPrivileges=yes -RestrictAddressFamilies=AF_UNIX -SystemCallArchitectures=native - -[Install] -WantedBy=multi-user.target diff --git a/AGENTS.md b/docs/AGENTS.md similarity index 67% rename from AGENTS.md rename to docs/AGENTS.md index aef3dc9f3e2..ebc07e15283 100644 --- a/AGENTS.md +++ b/docs/AGENTS.md @@ -9,24 +9,67 @@ quality. Use the following commands to build the project for different configurations. All commands should be run from the root of the repository. -### Standard Release Build +The output folder where code is built lives in out/xxx. Different people use +different output folders. Pick the output folder as follows +- The $OUT env var should contain the path of the output folder. If it exists respect that. +- If $OUT is empty/unset, set it by looking at the most recent out/ subdir, i.e. + `OUT=out/$(ls -t1 out | head -n1)` -To build the standard release version: +### Building C++ code + +Our C++ sources use tools/gn and tools/ninja to build. + +- If you touch a .gn or .gni file, rerun `tools/gn gen --check $OUT` +- Afterwards, you can build code running `tools/ninja -C $OUT TARGET_NAME" +- TARGET_NAME is: + - perfetto_unittests: for any file in src/**/*_unittest.cc + - perfetto_integrationtests: for any file in src/**/*_integrationtest.cc (and most code under test/) + - perfetto_benchmarks: for any file in src/**/*_benchmark.cc (and most code under test/) + - Other target names are usually: traced, traced_probes, perfetto, trace_processor_shell. You can discover them following the root /BUILD.gn file + +When adding/removing source files, keep BUILD.gn files updated. +Usually there is a BUILD.gn file in each directory. If not, look at closer parent dirs for precedent. + +Never bother manually updating Android.bp files or bazel BUILD files. +Those are autogenerated later when uploading the pull request via +`tools/gen_all $OUT`, but humans will take care of that. + +To build one or more targets: ```sh -tools/ninja -C out/linux_clang_release -k 10000 trace_processor_shell perfetto_unittests +tools/ninja -C $OUT -k 10000 trace_processor_shell perfetto_unittests ``` -## 2. Running Tests +### 2. Running C++ Tests -Use the following commands to run unit tests for the corresponding build -configurations. +Perfetto uses the Google Test framework. You will see c++ sources like +```cpp +TEST(ProtozeroToJsonTest, Foo) { +... +} +``` + +ProtozeroToJsonTest is the test suite name, Foo the test name. + +You can run all the tests in a test suite by doing: -### Standard Release Tests +```sh +$OUT/perfetto_unittests --gtest_brief=1 --gtest_filter="ProtozeroToJsonTest.*" +``` +Or if you touch a specific test, you can run only that one doing ```sh -out/linux_clang_release/perfetto_unittests --gtest_brief=1 --gtest_filter="" +$OUT/perfetto_unittests --gtest_brief=1 --gtest_filter="ProtozeroToJsonTest.Foo" ``` +Same goes for perfetto_integrationtests. + +For perfetto_benchmarks you need instead to run + +```sh +$OUT/perfetto_benchmarks --benchmark_filter='.*BM_RtMutex_NoContention.*' +``` + +Note that unlike Google Test, where the filter is a glob, in Google Benchmarks the filter is a regex. ### Trace Processor Diff Tests @@ -34,7 +77,7 @@ Trace Processor Diff Tests (or diff tests for short) are executed by running the following command: ```sh -tools/diff_test_trace_processor.py out/linux_clang_release/trace_processor_shell --keep-input --quiet --name-filter="" +tools/diff_test_trace_processor.py $OUT$/trace_processor_shell --keep-input --quiet --name-filter="" ``` **Note:** These tests can also be run with ASan or MSan builds by changing the @@ -44,14 +87,6 @@ using the `--name-filter` flag, do not include `test_` in the filter. The test runner automatically drops this prefix. For example, to run `test_my_cool_test`, use the filter `MyTestSuite.my_cool_test`. -### Integration Tests - -Integration tests are executed by running the `perfetto_integrationtests` -binary. For example: - -```sh -out/linux_clang_release/perfetto_integrationtests --gtest_filter="" -``` ### Test Guidelines @@ -97,7 +132,7 @@ When asked to fix GN dependencies, run the following command and fix any errors that are reported: ```sh -tools/gn check out/linux_clang_release/ +tools/gn check $OUT ``` **Note:** When fixing include errors, do not add dependencies to `public_deps` diff --git a/docs/analysis/perfetto-sql-syntax.md b/docs/analysis/perfetto-sql-syntax.md index e6ef4f03da0..268003b04ab 100644 --- a/docs/analysis/perfetto-sql-syntax.md +++ b/docs/analysis/perfetto-sql-syntax.md @@ -53,13 +53,37 @@ INCLUDE PERFETTO MODULE *; -- However, note, that both patterns are not allowed in stdlib. ``` +## Types + +PerfettoSQL supports the following types, which can be used in table and view +schemas, function arguments and return types: + +| Type | Description | +|------|-------------| +| `INT` | 64-bit signed integer | +| `DOUBLE` | Double precision floating-point number | +| `BOOLEAN` | Boolean value (true/false) | +| `STRING` | Text string | +| `BYTES` | Binary data | +| `TIMESTAMP` | Absolute timestamp in nanoseconds | +| `DURATION` | Time duration in nanoseconds | +| `ARGSETID` | An identifier for a set of arguments. This set can be obtained by joining with an `args` table on `arg_set_id` column. | +| `ID` | An ID column for this table. Each table can have only one ID column, whose values should be unique and fit into `uint32`. | +| `JOINID(table.column)` | A foreign key reference into a given table. `table` should exist, should have column named `column` of type `ID`. | +| `ID(table.column)` | A variant of the `ID` type, which is both primary key for this table and simultaneusly is a foreign key reference into another table. Useful when a given table is based on a subset of rows from another table (e.g. `slice`). | + ## Defining functions -`CREATE PEFETTO FUNCTION` allows functions to be defined in SQL. The syntax is -similar to the syntax in PostgreSQL or GoogleSQL. +`CREATE PEFETTO FUNCTION` allows functions to be defined in SQL, which can be +either scalar (returning a single value) or table-value (returning a set of rows). +The syntax is similar to the syntax in PostgreSQL or GoogleSQL: +- Scalar: `CREATE PERFETTO FUNCTION function_name(arg_list) RETURNS return_type AS sql_select_statement;` +- Table-valued: `CREATE PERFETTO FUNCTION function_name(arg_list) RETURNS TABLE(column_list) AS sql_select_statement;` - +`arg_list` and `column_list` is a comma-separated list of an arbitrary number `argument_name argument_type` pairs. -Example: +`sql_select_statement` should be a valid SQL statement, which can use `$argument_name` syntax to refer to the argument values. + +Examples: ```sql -- Create a scalar function with no arguments. CREATE PERFETTO FUNCTION constant_fn() RETURNS INT AS SELECT 1; @@ -117,7 +141,7 @@ WHERE name = 'foo'; Perfetto tables can have an optional explicit schema. The schema syntax is the same as the function argument or returned-from-a-function table, -i.e. a comma-separated list of (column name, colum type) pairs in parenthesis +i.e. a comma-separated list of (column name, column type) pairs in parenthesis after table or view name. ```sql @@ -199,13 +223,18 @@ is inspired by the macros in Rust. The following are recommended uses of macros: - Passing tables as arguments to a "function-like" snippet of SQL. +- Defining simple constants for performance-sensitive queries. Macros are powerful but also dangerous if used incorrectly, making debugging extremely difficult. For this reason, it's recommended that they are used sparingly when they are needed and only for the recommended uses described -above. If only passing around scalar SQL values, use functions as discussed above. +If only passing around scalar SQL values, functions are generally preferred +for their clarity. However, for simple constants used many times in a +performance-sensitive query, a macro can be more efficient as it avoids the +potential overhead of function calls in a large number. + NOTE: Macros are expanded with a pre-processing step *before* any execution happens. Expansion is a purely syntatic operation involves replacing the macro invocation with the SQL tokens in the macro definition. @@ -264,4 +293,4 @@ RETURNS TableOrSubquery AS SELECT input_tab.input_col + $y FROM $x AS input_tab; ) -``` \ No newline at end of file +``` diff --git a/docs/contributing/ui-plugins.md b/docs/contributing/ui-plugins.md index 18aa203ed1f..ab525b8be63 100644 --- a/docs/contributing/ui-plugins.md +++ b/docs/contributing/ui-plugins.md @@ -1647,6 +1647,13 @@ functionality gradually or providing options for advanced users. Feature flags are typically registered in the `onActivate` lifecycle hook using the `app.featureFlags` manager. +> **Note**: Feature flags are best suited for gating new or experimental +> features during development and rollout. They work well as temporary toggles +> that have a plan to be removed once the feature is stable (either by making it +> the default behavior or removing it entirely). If a feature needs ongoing user +> configuration, consider using [Custom Settings](#custom-settings) instead, as +> they provide a better user experience for permanent preferences. + To register a feature flag, you provide `FlagSettings`: - `id` (string): A unique identifier for the flag (e.g., @@ -1674,39 +1681,40 @@ interact with the flag's state: **Example:** ```typescript -import {App, Flag, FlagSettings} from '../../public'; // Adjust path as needed +import {Flag, FlagSettings} from '../../public/featureflag'; // Adjust path as needed +import {App} from '../../public/app'; +import {PerfettoPlugin} from '../../public/plugin'; +import {Trace} from '../../public/trace'; -export default class implements PerfettoPlugin { +export default class MyFeatureFlagPlugin implements PerfettoPlugin { static readonly id = 'com.example.MyFeatureFlagPlugin'; - private static myCoolFeatureFlag: Flag; + private static enableExperimentalTracks: Flag; static onActivate(app: App): void { - const flagSettings: FlagSettings = { - id: `${this.id}#myCoolFeature`, - name: 'Enable My Cool Feature', + // Register a feature flag to control experimental tracks + this.enableExperimentalTracks = app.featureFlags.register({ + id: `${this.id}#enableExperimentalTracks`, + name: 'Enable Experimental Memory Tracks', defaultValue: false, description: - 'This flag enables a super cool experimental feature that does X, Y, and Z.', - devOnly: true, // Optional: only for dev builds - }; - this.myCoolFeatureFlag = app.featureFlags.register(flagSettings); - - // You can immediately check its state or use it to gate other registrations - if (this.myCoolFeatureFlag.get()) { - console.log('My Cool Feature is enabled!'); - // Register other components that depend on this flag + 'Enables experimental memory analysis tracks that show detailed heap allocations and memory pressure events. These tracks are under active development.', + devOnly: true, // Only visible in development builds + }); + + // Register a command that's only available when the flag is enabled + if (this.enableExperimentalTracks.get()) { + app.commands.registerCommand({ + id: `${this.id}#analyzeMemoryLeaks`, + name: 'Analyze potential memory leaks', + callback: () => console.log('Running experimental leak detection...'), + }); } } async onTraceLoad(trace: Trace): Promise { - // Example of using the flag later - if (MyFeatureFlagPlugin.myCoolFeatureFlag.get()) { - // Add tracks or tabs related to this feature - trace.sidebar.addMenuItem({ - section: 'current_trace', - text: 'Cool Feature Action', - action: () => alert('Cool feature activated!'), - }); + // Only add experimental tracks if the feature flag is enabled + if (MyFeatureFlagPlugin.enableExperimentalTracks.get()) { + // ... add the track ... } } } @@ -1758,7 +1766,10 @@ the descriptor and provides methods to interact with the setting: **Example:** ```typescript -import {App, Setting, SettingDescriptor} from '../../public'; // Adjust path +import {Setting, SettingDescriptor} from '../../public/setting'; // Adjust path as needed +import {App} from '../../public/app'; +import {PerfettoPlugin} from '../../public/plugin'; +import {Trace} from '../../public/trace'; import {z} from 'zod'; import m from 'mithril'; @@ -1769,25 +1780,24 @@ const MyComplexObjectSchema = z.object({ }); type MyComplexObject = z.infer; -export default class implements PerfettoPlugin { +export default class MySettingsPlugin implements PerfettoPlugin { static readonly id = 'com.example.MySettingsPlugin'; private static simpleBooleanSetting: Setting; private static complexObjectSetting: Setting; static onActivate(app: App): void { // 1. A simple boolean setting - const boolSettingDesc: SettingDescriptor = { + this.simpleBooleanSetting = app.settings.register({ id: `${this.id}#enableSimpleFeature`, name: 'Enable Simple Feature', description: 'Toggles a basic feature on or off.', schema: z.boolean(), defaultValue: true, requiresReload: false, - }; - this.simpleBooleanSetting = app.settings.register(boolSettingDesc); + }); // 2. A more complex object-based setting with a custom renderer - const complexSettingDesc: SettingDescriptor = { + this.complexObjectSetting = app.settings.register({ id: `${this.id}#complexConfig`, name: 'Complex Configuration', description: 'Configure advanced options A and B.', @@ -1819,8 +1829,7 @@ export default class implements PerfettoPlugin { setting.isDefault ? m('span', ' (Default)') : null, ]); }, - }; - this.complexObjectSetting = app.settings.register(complexSettingDesc); + }); // Using the setting value if (this.simpleBooleanSetting.get()) { @@ -1832,7 +1841,12 @@ export default class implements PerfettoPlugin { ); } - // ... other plugin methods + async onTraceLoad(trace: Trace) { + // Use the setting in onTraceLoad + if (MySettingsPlugin.simpleBooleanSetting.get()) { + console.log('Simple feature is ON'); + } + } } ``` diff --git a/docs/getting-started/converting.md b/docs/getting-started/converting.md index c5430dd5b86..d98334eedbe 100644 --- a/docs/getting-started/converting.md +++ b/docs/getting-started/converting.md @@ -1119,13 +1119,140 @@ JOIN track ON slice.track_id = track.id WHERE track.name = 'Nested Debug Annotations'; ``` +## {#callstacks} Attaching Callstacks to Events + +Callstacks (also known as stack traces or backtraces) show the sequence of +function calls that led to a particular event. Adding callstacks to your trace +events can be invaluable for understanding the code paths that triggered +specific operations. + +There are two different ways to associate a callstack to an event: + +1. **Inline callstacks**: Embed stack frames directly in each event with + function names and optional source locations. This is simple and requires no + setup, making it ideal when trace size is not a concern or callstacks are + unique. +2. **Interned callstacks**: Define the callstack structure once and reference + it by ID from multiple events. This is much more efficient when callstacks repeat + frequently or when you need binary/mapping information for symbolization. + +This guide covers inline callstacks, which are perfect for getting started. For +repeated callstacks or when you need binary mapping information, use +[interned callstacks](/docs/reference/synthetic-track-event.md#callstacks) +instead. + +### Python Example + +Each frame includes a function name, and optionally a source file and line +number. + +Copy the following Python code into the `populate_packets(builder)` function in +your `trace_converter_template.py` script. + +
+Click to expand/collapse Python code + +```python + # Define a unique ID for this sequence of packets + TRUSTED_PACKET_SEQUENCE_ID = 7001 + + # Define a unique UUID for your custom track + CALLSTACK_TRACK_UUID = 98765432 + + def emit_track_event( + ts, + event_type, + name=None, + frames=None, + ): + """Helper to write a TrackEvent with an optional inline callstack.""" + packet = builder.add_packet() + packet.timestamp = ts + packet.track_event.type = event_type + packet.track_event.track_uuid = CALLSTACK_TRACK_UUID + if name is not None: + packet.track_event.name = name + if frames: + for function, source, line in frames: + frame = packet.track_event.callstack.frames.add() + frame.function_name = function + if source: + frame.source_file = source + if line is not None: + frame.line_number = line + packet.trusted_packet_sequence_id = TRUSTED_PACKET_SEQUENCE_ID + + # 1. Define the Custom Track + packet = builder.add_packet() + packet.track_descriptor.uuid = CALLSTACK_TRACK_UUID + packet.track_descriptor.name = "Operations with Callstacks" + + # 2. Create a slice with an inline callstack + emit_track_event( + ts=3000, + event_type=TrackEvent.TYPE_SLICE_BEGIN, + name="ProcessRequest", + frames=[ + ("main", "/src/app.cc", 42), + ("HandleIncomingRequests", "/src/server.cc", 128), + ("ProcessRequest", "/src/request_handler.cc", 256), + ], + ) + + # End the slice with a callstack captured at slice completion + emit_track_event( + ts=3500, + event_type=TrackEvent.TYPE_SLICE_END, + frames=[ + ("main", None, None), + ("HandleIncomingRequests", None, None), + ("FinalizeRequest", "/src/request_handler.cc", 512), + ], + ) + + # 3. Another slice with a minimal callstack (just function names) + emit_track_event( + ts=4000, + event_type=TrackEvent.TYPE_SLICE_BEGIN, + name="AllocateMemory", + frames=[ + ("main", None, None), + ("HandleIncomingRequests", None, None), + ("AllocateMemory", None, None), + ], + ) + + # End the slice + emit_track_event( + ts=4200, + event_type=TrackEvent.TYPE_SLICE_END, + ) +``` + +
+ +NOTE: Frames are ordered from outermost (bottom of stack, e.g., +`main()`) to innermost (top of stack, where the event occurred). + +When you provide a callstack on the slice end event, Trace Processor stores it +separately from the begin callstack (under the `end_callsite_id` argument in the +`slice` table). This is handy for quickly comparing entry/exit stacks. + +After running the script, opening the generated `my_custom_trace.pftrace` in the +[Perfetto UI](https://ui.perfetto.dev) will display the following output: + +![Inline Callstacks](/docs/images/converting-inline-callstacks.png) + +Note that you can also do an "area selection" (AKA box selection) to get a +flamegraph of the callstacks: + +![Inline Callstacks Area Select](/docs/images/inline-callstacks-flamegraph.png) + ## Next Steps -You've now seen how to convert various types of custom timestamped data into -Perfetto traces using Python and the `TrackEvent` protobuf. With these -techniques, you can represent simple activities, nested operations, asynchronous -events, counters, flows, create organized track hierarchies, and add debug -annotations to your events. +You've now seen how to convert custom timestamped data into Perfetto traces +using Python and `TrackEvent`. With these techniques, you can represent slices, +counters, flows, track hierarchies, debug annotations, and callstacks. Once you have your custom data in the Perfetto trace format (`.pftrace` file), you can: diff --git a/docs/getting-started/other-formats.md b/docs/getting-started/other-formats.md index cfe047eb1c6..a1adc3cd94a 100644 --- a/docs/getting-started/other-formats.md +++ b/docs/getting-started/other-formats.md @@ -411,6 +411,80 @@ stacks, timestamps, process/thread identifiers, CPU number, and event names. ![](/docs/images/perf-profile-in-ui.png) +## Simpleperf proto format + +**Description:** Simpleperf is Android's profiling tool built on top of the +Linux perf framework. The "Simpleperf proto format" refers to the binary +protobuf format generated by the `simpleperf report-sample --protobuf` command. +This format contains CPU samples with call stacks, process/thread information, +file mappings, and symbol tables in a compact binary representation. Android +Studio's CPU profiler uses this format internally when displaying simpleperf +profiles. + +**Common Scenarios:** This format is used for: + +- Profiling Android applications and system services using simpleperf +- Analyzing CPU performance on Android devices +- Capturing stack samples with symbol information for both native and managed + code +- Working with profiles collected by Android Studio's CPU profiler (which + converts simpleperf data to this proto format) + +**Perfetto Support:** + +- **Perfetto UI & Trace Processor:** Perfetto's Trace Processor can directly + parse simpleperf's protobuf format. + - The importer processes **CPU samples with call stacks**, file mappings, and + symbol tables + - Samples are imported into `cpu_profile_stack_sample` table with full call + stack information + - Call stacks are stored in standard profiling tables + (`stack_profile_callsite`, `stack_profile_frame`, `stack_profile_mapping`) + - Thread and process information is extracted and stored in the process/thread + tables + - This allows simpleperf profiles to be visualized as flamegraphs in the + Perfetto UI and queried using SQL +- **Limitations:** + - Context switch records in simpleperf format are not yet imported + - Event type metadata is stored but not yet used for filtering or + categorization + +**How to Generate:** + +1. **Record a profile with simpleperf:** First, capture profiling data using + `simpleperf record`. + + ```bash + # Example: Profile an Android app with call graphs + adb shell simpleperf record -p -g --duration 10 + adb pull /data/local/tmp/perf.data simpleperf.data + ``` + + Refer to `simpleperf record --help` for detailed command options. + +2. **Convert to proto format:** Use `simpleperf report-sample` to convert the + raw recording to protobuf format: + + ```bash + # Convert to proto format with call chains + simpleperf report-sample --protobuf --show-callchain \ + -i simpleperf.data -o simpleperf.proto + + # Optionally provide symbol directories for better symbolization + simpleperf report-sample --protobuf --show-callchain \ + -i simpleperf.data -o simpleperf.proto \ + --symdir /path/to/symbols + ``` + + The `--show-callchain` flag is required to include call stack information in + the output. + +3. **Open in ui.perfetto.dev** + + Navigate to [ui.perfetto.dev](https://ui.perfetto.dev) and upload the + `simpleperf.proto` file. The trace will be imported and you can view + flamegraphs of the CPU samples by selecting time ranges in the UI. + ## Linux ftrace textual format **Description:** The Linux ftrace textual format is the raw, human-readable @@ -905,3 +979,78 @@ into Zircon Virtual Memory Objects (VMOs) for efficiency. [Fuchsia trace format](https://fuchsia.dev/fuchsia-src/reference/tracing/trace-format) - **Tutorial on recording and visualizing:** [Record and visualize a trace (Fuchsia Docs)](https://fuchsia.dev/fuchsia-src/development/tracing/tutorial/record-and-visualize-a-trace) + +## pprof format + +**Description:** The `pprof` format is a protocol buffer-based format used for +storing CPU profile data. `pprof` is a tool for visualization and analysis of +profiling data. It reads a collection of profiling samples in `profile.proto` +format and generates reports to visualize and help analyze the data. It was +developed for and is used by the [Go programming language's pprof profiler](https://pkg.go.dev/runtime/pprof), +but has since been adopted by a wide range of other profilers for other +languages (e.g. Python, C++, Rust, etc.). + +**Common Scenarios:** The most common reason Perfetto users encounter this +format is for: + +- **Visualizing Go CPU profiles:** Developers often collect CPU samples using + the Go pprof profiler and need a way to visualize them. +- **Using cross-platform profiling tools:** Some profiling tools and libraries + are designed to output or convert their data into this format, facilitating + analysis with pprof-compatible tools. +- **Analyzing Linux `perf` profiles:** `pprof` can read `perf.data` files + generated by the [Linux perf](https://perf.wiki.kernel.org/index.php/Main_Page) + tool by using the `perf_to_profile` program from the + [perf_data_converter](https://github.com/google/perf_data_converter) package. + +**Perfetto Support:** + +- **Perfetto UI:** When a `pprof` file is opened, Perfetto visualizes the + profiling data as an interactive flamegraph. If the `pprof` file contains + multiple metrics (e.g., CPU time and memory allocations), the UI allows you + to switch between them, displaying a separate flamegraph for each metric. + This enables intuitive analysis of call stacks and helps identify performance + hotspots across different dimensions. + + Here's an example of what that looks like + + ![](/docs/images/pprof-in-ui.png) + +**How to Generate:** The most relevant generation path for Perfetto users +involves collecting CPU profiles from Go programs or converting `perf.data` files. + +1. **Collect a CPU profile from a Go program:** You can either use the + `runtime/pprof` package to programmatically collect a profile or use the + `net/http/pprof` package to expose a profiling endpoint on a running server. + + To collect a profile from a running server, you can use the `go tool pprof` + command: + + ```bash + go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30 + ``` + + This will collect a 30-second CPU profile and open it in the pprof tool. + You can then save the profile to a file using the `save` command in the + pprof tool. + +2. **Convert Linux `perf.data` to pprof format:** Use the `perf_to_profile` + tool from the `perf_data_converter` package. + + ```bash + perf_to_profile -i perf.data -o perf.pprof + ``` + +**External Resources:** + +- **pprof GitHub Repository:** + [https://github.com/google/pprof](https://github.com/google/pprof) +- **Go pprof documentation:** + [https://pkg.go.dev/runtime/pprof](https://pkg.go.dev/runtime/pprof) +- **pprof format specification:** + [https://github.com/google/pprof/blob/main/proto/profile.proto](https://github.com/google/pprof/blob/main/proto/profile.proto) +- **Linux `perf` tool:** + [perf Wiki](https://perf.wiki.kernel.org/index.php/Main_Page) +- **`perf_data_converter` GitHub Repository:** + [https://github.com/google/perf_data_converter](https://github.com/google/perf_data_converter) + diff --git a/docs/images/converting-inline-callstacks.png b/docs/images/converting-inline-callstacks.png new file mode 100644 index 00000000000..6e39cb7e523 Binary files /dev/null and b/docs/images/converting-inline-callstacks.png differ diff --git a/docs/images/inline-callstacks-flamegraph.png b/docs/images/inline-callstacks-flamegraph.png new file mode 100644 index 00000000000..cd80c4e9e69 Binary files /dev/null and b/docs/images/inline-callstacks-flamegraph.png differ diff --git a/docs/images/platform-tracing.png b/docs/images/platform-tracing.png new file mode 100644 index 00000000000..0423f38e668 Binary files /dev/null and b/docs/images/platform-tracing.png differ diff --git a/docs/images/pprof-in-ui.png b/docs/images/pprof-in-ui.png new file mode 100644 index 00000000000..a8a35e051ad Binary files /dev/null and b/docs/images/pprof-in-ui.png differ diff --git a/docs/images/synthetic-track-event-interned-callstack.png b/docs/images/synthetic-track-event-interned-callstack.png new file mode 100644 index 00000000000..1bc6477978f Binary files /dev/null and b/docs/images/synthetic-track-event-interned-callstack.png differ diff --git a/docs/quickstart/traceconv.md b/docs/quickstart/traceconv.md index 239aef4ca54..701dcec7c6d 100644 --- a/docs/quickstart/traceconv.md +++ b/docs/quickstart/traceconv.md @@ -52,12 +52,12 @@ with pprof. If you are extracting heaps profiles like heapprofd you can use the following: -`~/traceconv profile [input proto file] [output file]` +`~/traceconv profile [input proto file]` However if you are using callstack sampling like traced_perf then use the following instead: -`~/traceconv profile [input proto file] [output file] --perf` +`~/traceconv profile [input proto file] --perf` Note for `--perf` the output is one pprof file per process sampled in the trace. You can use pprof to merge them together if desired. diff --git a/docs/reference/synthetic-track-event.md b/docs/reference/synthetic-track-event.md index 92ae9572e0d..480df8bfcea 100644 --- a/docs/reference/synthetic-track-event.md +++ b/docs/reference/synthetic-track-event.md @@ -128,6 +128,9 @@ your `trace_converter_template.py` script. +If you only have symbolized function names, call `add_frame(...)` with just the +interned function name ID: e.g. `add_frame(packet.interned_data, FRAME_MAIN, FUNC_MAIN)`. + ![Associating Tracks with Processes](/docs/images/synthetic-track-event-process-counter.png) You can query process-associated counter data using SQL in the Perfetto UI's Query tab or with [Trace Processor](/docs/analysis/getting-started.md): @@ -675,6 +678,235 @@ your `trace_converter_template.py` script. ![Interning Data for Trace Size Optimization](/docs/images/synthetic-track-event-interning.png) +### {#callstacks} Interned Callstacks + +The [Getting Started guide](/docs/getting-started/converting.md#callstacks) +covers inline callstacks for simple use cases. This section covers interned +callstacks for efficiency when callstacks repeat or when you need binary +mapping information for symbolization. + +Interned callstacks define the callstack structure once in `InternedData` and +reference it by ID from multiple events. At a minimum you only need to define +**frames**, **callstacks**, and reference those callstacks from your events. The +other pieces are optional and can be supplied when you have that information: + +1. **Build IDs** and **Mapping Paths** → **Mappings** (binaries/libraries). You + may skip this entirely if you do not have binary metadata. +2. **Mappings** → **Frames** (function + location). `mapping_id`, `rel_pc`, + `source_file_id`, `line_number`, etc. are all optional—set only what makes + sense for your data. +3. **Frames** → **Callstacks** (frame sequences) +4. **Callstacks** → Events (via `callstack_iid`) + +#### Python Example: Interned Callstacks + +This example demonstrates the complete workflow for interning callstacks, +including mappings, frames, and callstacks. For minimal traces you can skip the +mapping entries and populate frames with just function names (and whatever +location details you have). + +Copy the following Python code into the `populate_packets(builder)` function in +your `trace_converter_template.py` script. + +
+Click to expand/collapse Python code + +```python + from perfetto.protos.perfetto.trace.perfetto_trace_pb2 import TracePacket + TRUSTED_PACKET_SEQUENCE_ID = 9001 + + # --- Define Track UUID --- + interned_callstack_track_uuid = uuid.uuid4().int & ((1 << 63) - 1) + + def add_function_name(entry, iid, name): + item = entry.function_names.add() + item.iid = iid + item.str = name.encode() + + def add_mapping(entry, iid, build_id, start, end, path_id): + mapping_entry = entry.mappings.add() + mapping_entry.iid = iid + mapping_entry.build_id = build_id + mapping_entry.exact_offset = 0 + mapping_entry.start = start + mapping_entry.end = end + mapping_entry.load_bias = 0 + mapping_entry.path_string_ids.append(path_id) + + def add_frame(entry, iid, function_name_id, mapping_id=None, rel_pc=None): + frame_entry = entry.frames.add() + frame_entry.iid = iid + frame_entry.function_name_id = function_name_id + if mapping_id is not None: + frame_entry.mapping_id = mapping_id + if rel_pc is not None: + frame_entry.rel_pc = rel_pc + + def add_callstack(entry, iid, frame_ids): + callstack_entry = entry.callstacks.add() + callstack_entry.iid = iid + callstack_entry.frame_ids.extend(frame_ids) + + def emit_track_event( + ts, + event_type, + name, + callstack_iid, + ): + packet = builder.add_packet() + packet.timestamp = ts + packet.track_event.type = event_type + packet.track_event.track_uuid = interned_callstack_track_uuid + if name is not None: + packet.track_event.name = name + if callstack_iid is not None: + packet.track_event.callstack_iid = callstack_iid + packet.sequence_flags = TracePacket.SEQ_NEEDS_INCREMENTAL_STATE + packet.trusted_packet_sequence_id = TRUSTED_PACKET_SEQUENCE_ID + + # 1. Define the track + packet = builder.add_packet() + desc = packet.track_descriptor + desc.uuid = interned_callstack_track_uuid + desc.name = "Interned Callstack Demo" + + # 2. Define interned data (mappings, frames, callstacks) + # We'll create this in a single packet that initializes the interning state + + packet = builder.add_packet() + packet.trusted_packet_sequence_id = TRUSTED_PACKET_SEQUENCE_ID + packet.sequence_flags = (TracePacket.SEQ_INCREMENTAL_STATE_CLEARED | + TracePacket.SEQ_NEEDS_INCREMENTAL_STATE) + + # Define Build IDs + BUILD_ID_APP = 1 + BUILD_ID_LIBC = 2 + + build_id_entry = packet.interned_data.build_ids.add() + build_id_entry.iid = BUILD_ID_APP + build_id_entry.str = b"a1b2c3d4e5f67890" # Hex-encoded build ID + + build_id_entry = packet.interned_data.build_ids.add() + build_id_entry.iid = BUILD_ID_LIBC + build_id_entry.str = b"1234567890abcdef" + + # Define Mapping Paths + PATH_APP = 1 + PATH_LIBC = 2 + + path_entry = packet.interned_data.mapping_paths.add() + path_entry.iid = PATH_APP + path_entry.str = b"/usr/bin/myapp" + + path_entry = packet.interned_data.mapping_paths.add() + path_entry.iid = PATH_LIBC + path_entry.str = b"/lib/x86_64-linux-gnu/libc.so.6" + + # Define Mappings + MAPPING_APP = 1 + MAPPING_LIBC = 2 + + add_mapping(packet.interned_data, MAPPING_APP, BUILD_ID_APP, 0x400000, 0x500000, PATH_APP) + add_mapping(packet.interned_data, MAPPING_LIBC, BUILD_ID_LIBC, 0x7F0000000000, 0x7F0000200000, PATH_LIBC) + + # Define Frames + FUNC_MAIN = 1 + FUNC_PROCESS_REQUESTS = 2 + FUNC_HANDLE_REQUEST = 3 + FUNC_MALLOC = 4 + + add_function_name(packet.interned_data, FUNC_MAIN, "main") + add_function_name(packet.interned_data, FUNC_PROCESS_REQUESTS, "ProcessRequests") + add_function_name(packet.interned_data, FUNC_HANDLE_REQUEST, "HandleRequest") + add_function_name(packet.interned_data, FUNC_MALLOC, "malloc") + + FRAME_MAIN = 1 + FRAME_PROCESS_REQUESTS = 2 + FRAME_HANDLE_REQUEST = 3 + FRAME_MALLOC = 4 + + add_frame(packet.interned_data, FRAME_MAIN, FUNC_MAIN, MAPPING_APP, 0x1234) + add_frame(packet.interned_data, FRAME_PROCESS_REQUESTS, FUNC_PROCESS_REQUESTS, MAPPING_APP, 0x2345) + add_frame(packet.interned_data, FRAME_HANDLE_REQUEST, FUNC_HANDLE_REQUEST, MAPPING_APP, 0x3456) + add_frame(packet.interned_data, FRAME_MALLOC, FUNC_MALLOC, MAPPING_LIBC, 0x8765) + + # Define Callstacks + # Callstack 1: main -> ProcessRequests -> HandleRequest + CALLSTACK_1 = 1 + add_callstack(packet.interned_data, CALLSTACK_1, [FRAME_MAIN, FRAME_PROCESS_REQUESTS, FRAME_HANDLE_REQUEST]) + + # Callstack 2: main -> ProcessRequests -> HandleRequest -> malloc + CALLSTACK_2 = 2 + add_callstack( + packet.interned_data, + CALLSTACK_2, + [FRAME_MAIN, FRAME_PROCESS_REQUESTS, FRAME_HANDLE_REQUEST, FRAME_MALLOC], + ) + + # 3. Create events that reference the interned callstacks + # Event 1: References CALLSTACK_1 + emit_track_event( + ts=5000, + event_type=TrackEvent.TYPE_SLICE_BEGIN, + name="HandleRequest", + callstack_iid=CALLSTACK_1, + ) + + emit_track_event( + ts=5300, + event_type=TrackEvent.TYPE_SLICE_END, + name=None, + callstack_iid=None, + ) + + # Event 2: References CALLSTACK_2 + emit_track_event( + ts=5100, + event_type=TrackEvent.TYPE_SLICE_BEGIN, + name="AllocateMemory", + callstack_iid=CALLSTACK_2, + ) + + emit_track_event( + ts=5200, + event_type=TrackEvent.TYPE_SLICE_END, + name=None, + callstack_iid=None, + ) + + # Event 3: Another event with CALLSTACK_1 (reusing the interned data) + emit_track_event( + ts=6000, + event_type=TrackEvent.TYPE_SLICE_BEGIN, + name="HandleRequest", + callstack_iid=CALLSTACK_1, + ) + + emit_track_event( + ts=6400, + event_type=TrackEvent.TYPE_SLICE_END, + name=None, + callstack_iid=None, + ) +``` + +
+ +**Notes:** + +- Sequence flags: Use `SEQ_INCREMENTAL_STATE_CLEARED | + SEQ_NEEDS_INCREMENTAL_STATE` when defining interned data (for the first time); use only + `SEQ_NEEDS_INCREMENTAL_STATE` when referencing it or defining *more* incremental data. +- Frame order: `frame_ids` are ordered outermost to innermost (same as inline + callstacks). +- Reuse: Event 3 reuses `CALLSTACK_1`, demonstrating the efficiency gain. + +After running the script, opening the generated trace in the +[Perfetto UI](https://ui.perfetto.dev) and doing an area selection will display +the following output: + +![Interned Callstacks](/docs/images/synthetic-track-event-interned-callstack.png) + ### Linking Related Events with Correlation IDs Correlation IDs provide a way to visually link slices that are part of the same diff --git a/docs/reference/traced.md b/docs/reference/traced.md new file mode 100644 index 00000000000..3f2135bff44 --- /dev/null +++ b/docs/reference/traced.md @@ -0,0 +1,113 @@ +# TRACED(8) + +## NAME + +traced - The Perfetto Tracing Service + +## DESCRIPTION + +`traced` is the central daemon in Perfetto's +[service-based architecture](/docs/concepts/service-model.md). It acts as the +grand central station for all tracing activity on the system, mediating +interactions between entities that want to record data (Producers) and entities +that want to control and read traces (Consumers). + +In a typical system-wide tracing setup (like on Android or Linux), `traced` runs +as a long-lived background daemon, often started at system boot. + +## Architecture + +Perfetto's architecture is designed for security and robustness, with `traced` +at its core. The model consists of three main components: + +* **Consumers:** Trusted clients that configure and initiate tracing sessions. + The `perfetto` command-line tool is a common example of a consumer. +* **Service (`traced`):** The central daemon that manages tracing sessions, + buffers, and the registry of data sources. +* **Producers:** Untrusted clients that produce trace data. Producers + advertise their available data sources to `traced`. A key example of a + producer is [`traced_probes`](/docs/reference/traced_probes.md), which + provides a wide range of system-level data sources. + +This decoupled architecture allows for multiple, independent producers and +consumers to interact with the tracing system simultaneously without interfering +with each other. + +## Core Responsibilities + +`traced` itself does not generate trace data. Its primary role is to manage the +logistics of one or more tracing sessions: + +* **Session Management**: It can handle multiple concurrent tracing sessions, + each with its own configuration. It multiplexes these sessions efficiently, + ensuring that data from different sessions is kept separate. +* **Buffer Management**: It owns the central trace buffers where the final + trace data is assembled. It is responsible for allocating, managing, and + freeing these buffers according to the trace configuration (e.g., ring + buffer vs. stop-when-full policies). +* **Producer and Data Source Registry**: It maintains a registry of all + connected Producers and the Data Sources they advertise. +* **Config Routing**: When a Consumer initiates a trace, it sends a trace + config to `traced`. The service then parses this config and forwards + relevant sub-configurations to the appropriate Producers to start their data + sources. +* **Data Consolidation & Security**: It facilitates the secure movement of + data from the Producers' untrusted shared memory pages into its own secure + central trace buffers. This isolation prevents a malicious or buggy producer + from corrupting the trace data of others. + +## Interaction Model + +Entities interact with `traced` primarily through two channels: + +1. **IPC Channel**: Used for relatively low-frequency control signals. + * **Producers** use it to register themselves, advertise data sources, and + receive start/stop commands. + * **Consumers** use it to send trace configs, start/stop sessions, and + read back the final trace data. + * On POSIX systems, this is typically a UNIX stream socket. +2. **Shared Memory**: Used for high-frequency, low-overhead data transport. + * Each Producer has a dedicated shared memory region shared only with + `traced`. + * Producers write trace packets into this memory without blocking. + * `traced` periodically scans these memory regions and copies valid, + completed packets into its central trace buffers. + +### Command-line options + +`traced` supports the following command-line options: + +* `--background`: Exits immediately and continues running in the background. +* `--version`: Prints the version number and exits. +* `--set-socket-permissions + :::`: Sets the group ownership + and permission mode for the producer and consumer sockets. This is important + for controlling which users and processes can connect to `traced`. +* `--enable-relay-endpoint`: Enables an endpoint for multi-machine tracing via + `traced_relay`. + +## Built-in Producer + +On Android, `traced` also includes a built-in producer with several key +responsibilities: + +* **Metatracing**: It provides the `perfetto.metatrace` data source, which + enables tracing of the `traced` service itself. This is useful for debugging + Perfetto and capturing internal statistics, such as clock snapshots and + details about connected producers. +* **Lazy Service Starting**: It can dynamically start other tracing daemons + (like `heapprofd` and `traced_perf`) on-demand. When a trace configuration + requests a data source provided by one of these daemons, the built-in + producer ensures the corresponding service is started. It also stops the + service after a delay once it's no longer needed. +* **System-level Integrations**: It handles various other integrations with + the Android platform, such as managing counters for out-of-memory heap + profiling sessions and controlling system properties to enable tracing in + graphics components. + +## Security + +The service-based architecture is designed with security in mind. Producers are +untrusted and isolated from each other and from the central service. The use of +UNIX socket permissions allows administrators to control who can connect to the +tracing service as a producer or a consumer. diff --git a/docs/reference/traced_probes.md b/docs/reference/traced_probes.md new file mode 100644 index 00000000000..ae2cd00be2c --- /dev/null +++ b/docs/reference/traced_probes.md @@ -0,0 +1,463 @@ +# TRACED_PROBES(8) + +## NAME + +traced_probes - System & OS Probes + +## DESCRIPTION + +`traced_probes` is a specialized daemon that acts as a privileged +[Producer](/docs/concepts/service-model.md#producer) in the Perfetto +architecture. While any application can act as a producer to contribute its own +trace data, `traced_probes` is specifically responsible for collecting +system-level and kernel-level data that typically requires elevated privileges. + +## Relationship with `traced` + +`traced_probes` is a client of the [`traced`](/docs/reference/traced.md) +service. It connects to `traced`'s producer socket and registers a set of data +sources. `traced` then sends requests to `traced_probes` to start or stop these +data sources as part of a tracing session. + +This separation of concerns is a key part of Perfetto's design. `traced` is the +central manager, while `traced_probes` is a specialized data provider. This +decoupled architecture allows for multiple, independent producers and consumers +to interact with the tracing system simultaneously without interfering with each +other. + +![traced_probes and traced](/docs/images/platform-tracing.png) + +## Security and Privileges + +`traced_probes` often needs to run with elevated privileges (e.g., `root` or +`system` user on Android) to access kernel interfaces like `debugfs` or `/proc`. +Separating these high-privilege probes into their own daemon is a key part of +Perfetto's security model. It ensures that only a minimal amount of code runs +with high privileges, adhering to the principle of least privilege. + +## Configuration + +The data sources provided by `traced_probes` are configured within the main +trace configuration protobuf that is sent to `traced`. For example, to enable +ftrace, you would include an `FtraceConfig` within the `DataSourceConfig` for +the `linux.ftrace` data source. + +## Data Sources + +`traced_probes` provides a wide range of data sources, collecting system-level +and kernel-level data. The configuration for these data sources is specified in +the `data_sources` section of the overall trace configuration. Each data source +has its own configuration message within a `data_source_config` block. + +Here is an example of the general structure: + +```protobuf +data_sources: { + config { + name: "linux.ftrace" + ftrace_config { + # ... ftrace-specific settings + } + } +} +data_sources: { + config { + name: "linux.process_stats" + process_stats_config { + # ... process_stats-specific settings + } + } +} +``` + +Below is a detailed list of the main data sources provided by `traced_probes`, +separated by platform. + +## Linux Data Sources + +These data sources are available on Linux-based systems, including Android. + +Here is an example of a trace config enabling several Linux data sources: +```protobuf +# Example of a trace config enabling several Linux data sources. +data_sources: { + config { + name: "linux.ftrace" + ftrace_config { + ftrace_events: "sched/sched_switch" + ftrace_events: "power/cpu_idle" + } + } +} +data_sources: { + config { + name: "linux.process_stats" + process_stats_config { + scan_all_processes_on_start: true + proc_stats_poll_ms: 1000 + } + } +} +data_sources: { + config { + name: "linux.sys_stats" + sys_stats_config { + meminfo_period_ms: 1000 + vmstat_period_ms: 1000 + } + } +} +``` + +### `linux.ftrace` (Kernel Tracing) + +* **Description**: This is the primary data source for high-frequency kernel + events. It enables and reads raw ftrace data from the Linux kernel's ftrace + interface, providing insights into process scheduling, system calls, + interrupts, and other kernel activities. +* **Configuration Example**: + ```protobuf + data_sources: { + config { + name: "linux.ftrace" + ftrace_config { + ftrace_events: "sched/sched_switch" + ftrace_events: "power/cpu_idle" + ftrace_events: "sched/sched_waking" + } + } + } + ``` +* **Configuration**: Configured via `FtraceConfig` within the + `DataSourceConfig`. Key options include: + * `ftrace_events`: List of ftrace events to enable (e.g., + `sched/sched_switch`). + * `atrace_categories`, `atrace_apps`: For Android, enables userspace + Atrace categories and apps. + * `syscall_events`: Specific syscalls to trace. + * `enable_function_graph`: Enables kernel function graph tracing. + * `compact_sched`: Enables compact encoding for scheduler events. + * `symbolize_ksyms`: Enables kernel symbolization. + * `print_filter`: Filters `ftrace/print` events based on content. + +### `linux.process_stats` (Process and Thread Statistics) + +* **Description**: Collects detailed process and thread-level statistics from + the `/proc` filesystem. It provides both a snapshot of the process tree and + periodic memory/CPU counters. +* **Configuration Example**: + ```protobuf + data_sources: { + config { + name: "linux.process_stats" + process_stats_config { + scan_all_processes_on_start: true + proc_stats_poll_ms: 1000 + } + } + } + ``` +* **Configuration**: Configured via `ProcessStatsConfig`. Key options include: + * `record_thread_names`: Record thread names. + * `scan_all_processes_on_start`: Dump the entire process tree at the + start. + * `resolve_process_fds`: Resolve file descriptor paths. + * `scan_smaps_rollup`: Read `/proc/[pid]/smaps_rollup`. + * `record_process_age`: Record process start time. + * `record_process_runtime`: Record user and kernel mode CPU times. + * `record_process_dmabuf_rss`: Record DMA buffer RSS. + * `proc_stats_poll_ms`: Polling interval for periodic stats. + * `proc_stats_cache_ttl_ms`: Time-to-live for cached stats. + * `quirks`: Special behaviors (e.g., `DISABLE_ON_DEMAND`). + +### `linux.sys_stats` (System-Wide Statistics) + +* **Description**: Collects system-wide statistics by periodically polling + various files in `/proc` and `/sys`. +* **Configuration Example**: + ```protobuf + data_sources: { + config { + name: "linux.sys_stats" + sys_stats_config { + meminfo_period_ms: 1000 + vmstat_period_ms: 1000 + stat_period_ms: 1000 + } + } + } + ``` +* **Configuration**: Configured via `SysStatsConfig`. Allows fine-grained + control over which counters to collect and their polling frequencies (e.g., + `meminfo_period_ms`, `vmstat_period_ms`, `stat_counters`). + +### `linux.sysfs_power` (Power and Battery Information) + +* **Description**: Collects power and battery statistics using the Linux sysfs + interface. +* **Configuration Example**: + ```protobuf + data_sources: { + config { + name: "linux.sysfs_power" + } + } + ``` +* **Configuration**: This data source does not have a specific configuration + proto. + +### `linux.inode_file_map` (Inode to File Path Mapping) + +* **Description**: Maps inode numbers to file paths, which is useful for + correlating I/O events with the files being accessed. +* **Configuration Example**: + ```protobuf + data_sources: { + config { + name: "linux.inode_file_map" + inode_file_config { + scan_interval_ms: 10000 + scan_delay_ms: 5000 + scan_batch_size: 1000 + } + } + } + ``` +* **Configuration**: `InodeFileConfig` allows specifying `scan_mount_points`, + `mount_point_mapping` (to remap scan roots), `scan_interval_ms`, + `scan_delay_ms`, `scan_batch_size`, and `do_not_scan`. + +### `metatrace` (Perfetto Self-Tracing) + +* **Description**: A self-tracing data source that records events within + Perfetto itself, useful for debugging and performance analysis of the + tracing system. +* **Configuration Example**: + ```protobuf + data_sources: { + config { + name: "metatrace" + } + } + ``` +* **Configuration**: No specific configuration in `DataSourceConfig`. + +### `linux.system_info` (System Information) + +* **Description**: Records general information about the system, such as CPU + details and kernel version. +* **Configuration Example**: + ```protobuf + data_sources: { + config { + name: "linux.system_info" + } + } + ``` +* **Configuration**: No specific configuration in `DataSourceConfig`. + +## Android Data Sources + +These data sources are available only on Android. + +Here is an example of a trace config enabling several Android data sources: +```protobuf +# Example of a trace config enabling several Android data sources. +data_sources: { + config { + name: "android.power" + android_power_config { + battery_poll_ms: 1000 + battery_counters: BATTERY_COUNTER_CHARGE + collect_power_rails: true + } + } +} +data_sources: { + config { + name: "android.log" + android_log_config { + log_ids: LID_DEFAULT + log_ids: LID_SYSTEM + } + } +} +data_sources: { + config { + name: "android.packages_list" + } +} +``` + +### `android.power` (Power and Battery Information) + +* **Description**: Collects power and battery statistics using Android-specific + HALs. +* **Configuration Example**: + ```protobuf + data_sources: { + config { + name: "android.power" + android_power_config { + battery_poll_ms: 1000 + battery_counters: BATTERY_COUNTER_CHARGE + battery_counters: BATTERY_COUNTER_CAPACITY_PERCENT + collect_power_rails: true + } + } + } + ``` +* **Configuration**: `AndroidPowerConfig` allows enabling specific battery + counters (`battery_counters`), power rails (`collect_power_rails`), energy + estimation breakdown (`collect_energy_estimation_breakdown`), and entity + state residency (`collect_entity_state_residency`). + +### `android.log` (Android Logcat) + +* **Description**: Streams log messages from Android's logcat buffer into the + trace. +* **Configuration Example**: + ```protobuf + data_sources: { + config { + name: "android.log" + android_log_config { + log_ids: LID_DEFAULT + log_ids: LID_SYSTEM + min_prio: PRIO_INFO + filter_tags: "ActivityManager" + } + } + } + ``` +* **Configuration**: `AndroidLogConfig` allows filtering by log IDs + (`log_ids`) and tags (`filter_tags`), and setting a minimum priority + (`min_prio`). + +### `android.system_property` (Android System Properties) + +* **Description**: Collects the state of Android system properties. +* **Configuration Example**: + ```protobuf + data_sources: { + config { + name: "android.system_property" + android_system_property_config { + poll_ms: 1000 + property_name: "debug.tracing.screen_state" + } + } + } + ``` +* **Configuration**: `AndroidSystemPropertyConfig` allows specifying + `property_name`s to monitor and a `poll_ms` interval. + +### `android.packages_list` (Android Package Information) + +* **Description**: Dumps information about installed packages on Android. +* **Configuration Example**: + ```protobuf + data_sources: { + config { + name: "android.packages_list" + packages_list_config { + package_name_filter: "com.android.systemui" + package_name_filter: "com.google.android.apps.nexuslauncher" + } + } + } + ``` +* **Configuration**: `PackagesListConfig` allows filtering by + `package_name_filter` and can be configured to + `only_write_on_cpu_use_every_ms` (polling mode) or dump all at start. + +### `android.game_interventions` (Android Game Intervention List) + +* **Description**: Dumps the game intervention list from the package manager + on Android. +* **Configuration Example**: + ```protobuf + data_sources: { + config { + name: "android.game_interventions" + android_game_intervention_list_config { + package_name_filter: "com.example.mygame" + } + } + } + ``` +* **Configuration**: `AndroidGameInterventionListConfig` allows filtering by + `package_name_filter`. + +### `android.cpu.uid` (Per-UID CPU Time) + +* **Description**: Collects per-UID CPU time from the kernel. +* **Configuration Example**: + ```protobuf + data_sources: { + config { + name: "android.cpu.uid" + cpu_per_uid_config { + poll_ms: 1000 + } + } + } + ``` +* **Configuration**: `CpuPerUidConfig` allows setting the `poll_ms` interval. + +### `android.kernel_wakelocks` (Kernel Wakelocks) + +* **Description**: Collects kernel wakelock information. +* **Configuration Example**: + ```protobuf + data_sources: { + config { + name: "android.kernel_wakelocks" + kernel_wakelocks_config { + poll_ms: 1000 + } + } + } + ``` +* **Configuration**: `KernelWakelocksConfig` allows setting the `poll_ms` + interval. + +### `android.polled_state` (Android Initial Display State) + +* **Description**: Records the initial display state (e.g., screen on/off, + brightness) on Android. +* **Configuration Example**: + ```protobuf + data_sources: { + config { + name: "android.polled_state" + android_polled_state_config { + poll_ms: 500 + } + } + } + ``` +* **Configuration**: `AndroidPolledStateConfig` allows setting a `poll_ms` + interval. + +### `android.statsd` (Android StatsD Atoms) + +* **Description**: Collects StatsD atoms from the binder interface on Android. +* **Configuration Example**: + ```protobuf + data_sources: { + config { + name: "android.statsd" + statsd_tracing_config { + pull_config { + pull_atom_id: 10000 # Example pull atom + pull_frequency_ms: 1000 + } + push_atom_id: 10037 # Example push atom + } + } + } + ``` +* **Configuration**: `StatsdTracingConfig` allows specifying `pull_config` + (for pull atoms with frequency and packages) and `push_atom_id` (for push + atoms). diff --git a/docs/toc.md b/docs/toc.md index 0b68c0c9418..ac169d20edc 100644 --- a/docs/toc.md +++ b/docs/toc.md @@ -135,7 +135,9 @@ - [Command Line Reference](#) - - [perfetto cmdline](reference/perfetto-cli.md) + - [perfetto_cmd](reference/perfetto-cli.md) + - [traced](reference/traced.md) + - [traced_probes](reference/traced_probes.md) - [heap_profile cmdline](reference/heap_profile-cli.md) - [tracebox](reference/tracebox.md) diff --git a/docs/visualization/perfetto-ui.md b/docs/visualization/perfetto-ui.md index c932d3db9f9..4b42ab98609 100644 --- a/docs/visualization/perfetto-ui.md +++ b/docs/visualization/perfetto-ui.md @@ -21,7 +21,7 @@ out, and A and D pan left and right respectively. -Alternatively you can use Shit+Drag to pan using the mouse. Ctrl+MouseWheel +Alternatively you can use Shift+Drag to pan using the mouse. Ctrl+MouseWheel zooms in and out.