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

Commit 14d13809 authored by Zimuzo Ezeozue's avatar Zimuzo Ezeozue
Browse files

Support a PerfettoTrace Java API

Added a layer on top of the Perfetto C SDK that allows
creating equivalent 'PerfettoTeHl' structs on the heap.

This makes it possible for Java objects to control the
lifetime of these objects through JNI.

Each PerfettoTeHl struct has an equivalent C++ class allocated
on the heap which is in turn managed by an equivalent Java
class managed by the Java heap.

This design allows us to allocate objects once and re-use them
to serialize the trace event data from Java into the shmem.

The Java layer is responsible for caching the Perfetto Java
objects to avoid GC churn.

Test: atest PerfettoTraceTest
Change-Id: Ibb08e8b6cab7d86ac2c1d57c621c9074a4cb4077
Bug: 303199244
Flag: android.os.perfetto_sdk_tracing_v2
parent 921e6125
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ cc_library_shared {
    srcs: [
        "tracing_perfetto.cpp",
        "tracing_perfetto_internal.cpp",
        "tracing_sdk.cpp",
    ],

    shared_libs: [
@@ -46,6 +47,10 @@ cc_library_shared {
        "android.os.flags-aconfig-cc-host",
    ],

    export_shared_lib_headers: [
        "libperfetto_c",
    ],

    host_supported: true,
    // for vndbinder
    vendor_available: true,
+461 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * 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.
 */

#pragma once

#include <android-base/logging.h>
#include <stdint.h>

#include <optional>
#include <vector>

#include "perfetto/public/producer.h"
#include "perfetto/public/te_category_macros.h"
#include "perfetto/public/te_macros.h"
#include "perfetto/public/track_event.h"

/**
 * The objects declared here are intended to be managed by Java.
 * This means the Java Garbage Collector is responsible for freeing the
 * underlying native resources.
 *
 * The static methods prefixed with `delete_` are special. They are designed to be
 * invoked by Java through the `NativeAllocationRegistry` when the
 * corresponding Java object becomes unreachable.  These methods act as
 * callbacks to ensure proper deallocation of native resources.
 */
namespace tracing_perfetto {
/**
 * @brief Represents extra data associated with a trace event.
 * This class manages a collection of PerfettoTeHlExtra pointers.
 */
class Extra;

/**
 * @brief Emits a trace event.
 * @param type The type of the event.
 * @param cat The category of the event.
 * @param name The name of the event.
 * @param arg_ptr Pointer to Extra data.
 */
void trace_event(int type, const PerfettoTeCategory* cat, const char* name,
                 Extra* extra);

/**
 * @brief Gets the process track UUID.
 */
uint64_t get_process_track_uuid();

/**
 * @brief Gets the thread track UUID for a given PID.
 */
uint64_t get_thread_track_uuid(pid_t tid);

/**
 * @brief Holder for all the other classes in the file.
 */
class Extra {
 public:
  Extra();
  void push_extra(PerfettoTeHlExtra* extra);
  void pop_extra();
  void clear_extras();
  static void delete_extra(Extra* extra);

  PerfettoTeHlExtra* const* get() const;

 private:
  DISALLOW_COPY_AND_ASSIGN(Extra);

  // These PerfettoTeHlExtra pointers are really pointers to all the other
  // types of extras: Category, DebugArg, Counter etc. Those objects are
  // individually managed by Java.
  std::vector<PerfettoTeHlExtra*> extras_;
};

/**
 * @brief Represents a trace event category.
 */
class Category {
 public:
  Category(const std::string& name, const std::string& tag,
           const std::string& severity);

  ~Category();

  void register_category();

  void unregister_category();

  bool is_category_enabled();

  static void delete_category(Category* category);

  const PerfettoTeCategory* get() const;

 private:
  DISALLOW_COPY_AND_ASSIGN(Category);
  PerfettoTeCategory category_;
  const std::string name_;
  const std::string tag_;
  const std::string severity_;
};

/**
 * @brief Represents one end of a flow between two events.
 */
class Flow {
 public:
  Flow();

  void set_process_flow(uint64_t id);
  void set_process_terminating_flow(uint64_t id);
  static void delete_flow(Flow* flow);

  const PerfettoTeHlExtraFlow* get() const;

 private:
  DISALLOW_COPY_AND_ASSIGN(Flow);
  PerfettoTeHlExtraFlow flow_;
};

/**
 * @brief Represents a named track.
 */
class NamedTrack {
 public:
  NamedTrack(uint64_t id, uint64_t parent_uuid, const std::string& name);

  static void delete_track(NamedTrack* track);

  const PerfettoTeHlExtraNamedTrack* get() const;

 private:
  DISALLOW_COPY_AND_ASSIGN(NamedTrack);
  const std::string name_;
  PerfettoTeHlExtraNamedTrack track_;
};

/**
 * @brief Represents a registered track.
 */
class RegisteredTrack {
 public:
  RegisteredTrack(uint64_t id, uint64_t parent_uuid, const std::string& name,
                  bool is_counter);
  ~RegisteredTrack();

  void register_track();
  void unregister_track();
  static void delete_track(RegisteredTrack* track);

  const PerfettoTeHlExtraRegisteredTrack* get() const;

 private:
  DISALLOW_COPY_AND_ASSIGN(RegisteredTrack);
  PerfettoTeRegisteredTrack registered_track_;
  PerfettoTeHlExtraRegisteredTrack track_;
  const std::string name_;
  const uint64_t id_;
  const uint64_t parent_uuid_;
  const bool is_counter_;
};

/**
 * @brief Represents a counter track event.
 * @tparam T The data type of the counter (int64_t or double).
 */
template <typename T>
class Counter {
 public:
  template <typename>
  struct always_false : std::false_type {};

  struct TypeMap {
    using type = std::invoke_result_t<decltype([]() {
      if constexpr (std::is_same_v<T, int64_t>) {
        return std::type_identity<PerfettoTeHlExtraCounterInt64>{};
      } else if constexpr (std::is_same_v<T, double>) {
        return std::type_identity<PerfettoTeHlExtraCounterDouble>{};
      } else {
        return std::type_identity<void>{};
      }
    })>::type;

    static constexpr int enum_value = []() {
      if constexpr (std::is_same_v<T, int64_t>) {
        return PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_INT64;
      } else if constexpr (std::is_same_v<T, double>) {
        return PERFETTO_TE_HL_EXTRA_TYPE_COUNTER_DOUBLE;
      } else {
        static_assert(always_false<T>::value, "Unsupported type");
        return 0;  // Never reached, just to satisfy return type
      }
    }();
  };

  Counter() {
    static_assert(!std::is_same_v<typename TypeMap::type, void>,
                  "Unsupported type for Counter");

    typename TypeMap::type counter;
    counter.header = {TypeMap::enum_value};
    counter_ = std::move(counter);
  }

  void set_value(T value) {
    if constexpr (std::is_same_v<T, int64_t>) {
      counter_.value = value;
    } else if constexpr (std::is_same_v<T, double>) {
      counter_.value = value;
    }
  }

  static void delete_counter(Counter* counter) {
    delete counter;
  }

  const TypeMap::type* get() const {
    return &counter_;
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(Counter);
  TypeMap::type counter_;
};

/**
 * @brief Represents a debug argument for a trace event.
 * @tparam T The data type of the argument (bool, int64_t, double, const char*).
 */
template <typename T>
class DebugArg {
 public:
  template <typename>
  struct always_false : std::false_type {};

  struct TypeMap {
    using type = std::invoke_result_t<decltype([]() {
      if constexpr (std::is_same_v<T, bool>) {
        return std::type_identity<PerfettoTeHlExtraDebugArgBool>{};
      } else if constexpr (std::is_same_v<T, int64_t>) {
        return std::type_identity<PerfettoTeHlExtraDebugArgInt64>{};
      } else if constexpr (std::is_same_v<T, double>) {
        return std::type_identity<PerfettoTeHlExtraDebugArgDouble>{};
      } else if constexpr (std::is_same_v<T, const char*>) {
        return std::type_identity<PerfettoTeHlExtraDebugArgString>{};
      } else {
        return std::type_identity<void>{};
      }
    })>::type;

    static constexpr int enum_value = []() {
      if constexpr (std::is_same_v<T, bool>) {
        return PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_BOOL;
      } else if constexpr (std::is_same_v<T, int64_t>) {
        return PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_INT64;
      } else if constexpr (std::is_same_v<T, double>) {
        return PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_DOUBLE;
      } else if constexpr (std::is_same_v<T, const char*>) {
        return PERFETTO_TE_HL_EXTRA_TYPE_DEBUG_ARG_STRING;
      } else {
        static_assert(always_false<T>::value, "Unsupported type");
        return 0;  // Never reached, just to satisfy return type
      }
    }();
  };

  DebugArg(const std::string& name) : name_(name) {
    static_assert(!std::is_same_v<typename TypeMap::type, void>,
                  "Unsupported type for DebugArg");

    typename TypeMap::type arg;
    arg.header = {TypeMap::enum_value};
    arg.name = name_.c_str();
    arg_ = std::move(arg);
  }

  ~DebugArg() {
    free_string_value();
  }

  void set_value(T value) {
    if constexpr (std::is_same_v<T, const char*>) {
      free_string_value();
      arg_.value = value;
    } else if constexpr (std::is_same_v<T, int64_t>) {
      arg_.value = value;
    } else if constexpr (std::is_same_v<T, bool>) {
      arg_.value = value;
    } else if constexpr (std::is_same_v<T, double>) {
      arg_.value = value;
    }
  }

  static void delete_arg(DebugArg* arg) {
    delete arg;
  }

  const TypeMap::type* get() const {
    return &arg_;
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(DebugArg);
  TypeMap::type arg_;
  const std::string name_;

  constexpr void free_string_value() {
    if constexpr (std::is_same_v<typename TypeMap::type,
                                 PerfettoTeHlExtraDebugArgString>) {
      if (arg_.value) {
        free((void*)arg_.value);
        arg_.value = nullptr;
      }
    }
  }
};

template <typename T>
class ProtoField {
 public:
  template <typename>
  struct always_false : std::false_type {};

  struct TypeMap {
    using type = std::invoke_result_t<decltype([]() {
      if constexpr (std::is_same_v<T, int64_t>) {
        return std::type_identity<PerfettoTeHlProtoFieldVarInt>{};
      } else if constexpr (std::is_same_v<T, double>) {
        return std::type_identity<PerfettoTeHlProtoFieldDouble>{};
      } else if constexpr (std::is_same_v<T, const char*>) {
        return std::type_identity<PerfettoTeHlProtoFieldCstr>{};
      } else {
        return std::type_identity<void>{};
      }
    })>::type;

    static constexpr PerfettoTeHlProtoFieldType enum_value = []() {
      if constexpr (std::is_same_v<T, int64_t>) {
        return PERFETTO_TE_HL_PROTO_TYPE_VARINT;
      } else if constexpr (std::is_same_v<T, double>) {
        return PERFETTO_TE_HL_PROTO_TYPE_DOUBLE;
      } else if constexpr (std::is_same_v<T, const char*>) {
        return PERFETTO_TE_HL_PROTO_TYPE_CSTR;
      } else {
        static_assert(always_false<T>::value, "Unsupported type");
        return 0;  // Never reached, just to satisfy return type
      }
    }();
  };

  ProtoField() {
    static_assert(!std::is_same_v<typename TypeMap::type, void>,
                  "Unsupported type for ProtoField");

    typename TypeMap::type arg;
    arg.header.type = TypeMap::enum_value;
    arg_ = std::move(arg);
  }

  ~ProtoField() {
    free_string_value();
  }

  void set_value(uint32_t id, T value) {
    if constexpr (std::is_same_v<T, int64_t>) {
      arg_.header.id = id;
      arg_.value = value;
    } else if constexpr (std::is_same_v<T, double>) {
      arg_.header.id = id;
      arg_.value = value;
    } else if constexpr (std::is_same_v<T, const char*>) {
      free_string_value();
      arg_.header.id = id;
      arg_.str = value;
    }
  }

  static void delete_field(ProtoField* field) {
    delete field;
  }

  const TypeMap::type* get() const {
    return &arg_;
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(ProtoField);
  TypeMap::type arg_;

  constexpr void free_string_value() {
    if constexpr (std::is_same_v<typename TypeMap::type,
                                 PerfettoTeHlProtoFieldCstr>) {
      if (arg_.str) {
        free((void*)arg_.str);
        arg_.str = nullptr;
      }
    }
  }
};

class ProtoFieldNested {
 public:
  ProtoFieldNested();

  void add_field(PerfettoTeHlProtoField* field);
  void set_id(uint32_t id);
  static void delete_field(ProtoFieldNested* field);

  const PerfettoTeHlProtoFieldNested* get() const;

 private:
  DISALLOW_COPY_AND_ASSIGN(ProtoFieldNested);
  PerfettoTeHlProtoFieldNested field_;
  // These PerfettoTeHlProtoField pointers are really pointers to all the other
  // types of protos: PerfettoTeHlProtoFieldVarInt, PerfettoTeHlProtoFieldVarInt,
  // PerfettoTeHlProtoFieldVarInt, PerfettoTeHlProtoFieldNested. Those objects are
  // individually managed by Java.
  std::vector<PerfettoTeHlProtoField*> fields_;
};

class Proto {
 public:
  Proto();

  void add_field(PerfettoTeHlProtoField* field);
  void clear_fields();
  static void delete_proto(Proto* proto);

  const PerfettoTeHlExtraProtoFields* get() const;

 private:
  DISALLOW_COPY_AND_ASSIGN(Proto);
  PerfettoTeHlExtraProtoFields proto_;
  // These PerfettoTeHlProtoField pointers are really pointers to all the other
  // types of protos: PerfettoTeHlProtoFieldVarInt, PerfettoTeHlProtoFieldVarInt,
  // PerfettoTeHlProtoFieldVarInt, PerfettoTeHlProtoFieldNested. Those objects are
  // individually managed by Java.
  std::vector<PerfettoTeHlProtoField*> fields_;
};

/**
 * @brief Activates a trigger.
 * @param name The name of the trigger.
 * @param ttl_ms The time-to-live of the trigger in milliseconds.
 */
void activate_trigger(const char* name, uint32_t ttl_ms);
}  // namespace tracing_perfetto
+32 −1
Original line number Diff line number Diff line
@@ -21,12 +21,44 @@ package {
    default_applicable_licenses: ["frameworks_native_license"],
}

cc_library_static {
    name: "libtracing_perfetto_test_utils",
    export_include_dirs: [
        "include",
    ],
    static_libs: [
        "libflagtest",
        "libgmock",
        "perfetto_trace_protos",
    ],
    cflags: [
        "-Wall",
        "-Werror",
        "-Wno-enum-compare",
        "-Wno-unused-function",
    ],

    srcs: [
        "utils.cpp",
    ],

    shared_libs: [
        "libperfetto_c",
        "liblog",
        "libprotobuf-cpp-lite",
    ],
    export_shared_lib_headers: [
        "libperfetto_c",
    ],
}

cc_test {
    name: "libtracing_perfetto_tests",
    static_libs: [
        "libflagtest",
        "libgmock",
        "perfetto_trace_protos",
        "libtracing_perfetto_test_utils",
    ],
    cflags: [
        "-Wall",
@@ -42,7 +74,6 @@ cc_test {
    ],
    srcs: [
        "tracing_perfetto_test.cpp",
        "utils.cpp",
    ],
    test_suites: ["device-tests"],
}
+124 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 The Android Open Source Project
 *
 * 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.
 */

// Copied from //external/perfetto/src/shared_lib/test/utils.h

#ifndef UTILS_H
#define UTILS_H

#include <cassert>
#include <condition_variable>
#include <cstdint>
#include <functional>
#include <iterator>
#include <memory>
#include <mutex>
#include <ostream>
#include <string>
#include <vector>

#include "perfetto/public/abi/pb_decoder_abi.h"
#include "perfetto/public/pb_utils.h"
#include "perfetto/public/tracing_session.h"

// Pretty printer for gtest
void PrintTo(const PerfettoPbDecoderField& field, std::ostream*);

namespace perfetto {
namespace shlib {
namespace test_utils {

class WaitableEvent {
 public:
  WaitableEvent() = default;
  void Notify() {
    std::unique_lock<std::mutex> lock(m_);
    notified_ = true;
    cv_.notify_one();
  }
  bool WaitForNotification() {
    std::unique_lock<std::mutex> lock(m_);
    cv_.wait(lock, [this] { return notified_; });
    return notified_;
  }
  bool IsNotified() {
    std::unique_lock<std::mutex> lock(m_);
    return notified_;
  }

 private:
  std::mutex m_;
  std::condition_variable cv_;
  bool notified_ = false;
};

class TracingSession {
 public:
  class Builder {
   public:
    Builder() = default;
    Builder& add_enabled_category(std::string category) {
      enabled_categories_.push_back(std::move(category));
      return *this;
    }
    Builder& add_disabled_category(std::string category) {
      disabled_categories_.push_back(std::move(category));
      return *this;
    }
    Builder& add_atrace_category(std::string category) {
      atrace_categories_.push_back(std::move(category));
      return *this;
    }
    Builder& add_atrace_category_prefer_sdk(std::string category) {
      atrace_categories_prefer_sdk_.push_back(std::move(category));
      return *this;
    }
    TracingSession Build();

   private:
    std::vector<std::string> enabled_categories_;
    std::vector<std::string> disabled_categories_;
    std::vector<std::string> atrace_categories_;
    std::vector<std::string> atrace_categories_prefer_sdk_;
  };

  static TracingSession Adopt(struct PerfettoTracingSessionImpl*);
  static TracingSession FromBytes(void *buf, size_t len);

  TracingSession(TracingSession&&) noexcept;

  ~TracingSession();

  struct PerfettoTracingSessionImpl* session() const {
    return session_;
  }

  bool FlushBlocking(uint32_t timeout_ms);
  void WaitForStopped();
  void StopBlocking();
  std::vector<uint8_t> ReadBlocking();

 private:
  TracingSession() = default;
  struct PerfettoTracingSessionImpl* session_;
  std::unique_ptr<WaitableEvent> stopped_;
};

}  // namespace test_utils
}  // namespace shlib
}  // namespace perfetto

#endif  // UTILS_H
+1 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@
#include <unistd.h>

#include "gtest/gtest.h"

#include "perfetto/public/abi/data_source_abi.h"
#include "perfetto/public/abi/heap_buffer.h"
#include "perfetto/public/abi/pb_decoder_abi.h"
Loading