Merge "perfetto-ui: Tidy up scrolling code"
diff --git a/Android.bp b/Android.bp
index 89742c8..2cc1d07 100644
--- a/Android.bp
+++ b/Android.bp
@@ -426,6 +426,7 @@
"liblog",
"libprotobuf-cpp-lite",
],
+ host_supported: true,
export_include_dirs: [
"include",
"include/perfetto/base/build_configs/android_tree",
diff --git a/BUILD b/BUILD
index a349220..8bf03e6 100644
--- a/BUILD
+++ b/BUILD
@@ -1269,6 +1269,14 @@
],
)
+# GN target: //protos/perfetto/config:merged_config
+perfetto_cc_proto_library(
+ name = "protos_perfetto_config_merged_config",
+ deps = [
+ ":protos_perfetto_config_merged_config_protos",
+ ],
+)
+
# GN target: //protos/perfetto/common:lite
perfetto_cc_proto_library(
name = "protos_perfetto_common_lite",
@@ -1277,6 +1285,17 @@
],
)
+# GN target: //protos/perfetto/config:merged_config
+perfetto_proto_library(
+ name = "protos_perfetto_config_merged_config_protos",
+ srcs = [
+ "protos/perfetto/config/perfetto_config.proto",
+ ],
+ visibility = [
+ "//visibility:public",
+ ],
+)
+
# GN target: //protos/perfetto/trace/ftrace:lite
perfetto_cc_proto_library(
name = "protos_perfetto_trace_ftrace_lite",
@@ -1686,6 +1705,9 @@
srcs = [
"protos/perfetto/trace/perfetto_trace.proto",
],
+ visibility = [
+ "//visibility:public",
+ ],
)
# GN target: //protos/perfetto/config/profiling:zero
@@ -1842,6 +1864,9 @@
srcs = [
"protos/perfetto/metrics/metrics.proto",
],
+ visibility = [
+ "//visibility:public",
+ ],
deps = [
":protos_perfetto_metrics_android_protos",
],
diff --git a/BUILD.gn b/BUILD.gn
index 0a10260..0ba5807 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -210,6 +210,7 @@
configs += [ "//build/config/compiler:no_chromium_code" ]
public_deps = [
"include/perfetto/ext/tracing/core",
+ "protos/perfetto/common:zero",
"protos/perfetto/trace:zero",
"protos/perfetto/trace/chrome:zero",
"protos/perfetto/trace/interned_data:zero",
diff --git a/bazel/rules.bzl b/bazel/rules.bzl
index 87c48c7..2b45862 100644
--- a/bazel/rules.bzl
+++ b/bazel/rules.bzl
@@ -21,7 +21,7 @@
def default_cc_args():
return {
- "deps": [PERFETTO_CONFIG.root + ":build_config_hdr"],
+ "deps": PERFETTO_CONFIG.deps.build_config,
"copts": [],
"includes": ["include"],
"linkopts": select({
diff --git a/bazel/standalone/perfetto_cfg.bzl b/bazel/standalone/perfetto_cfg.bzl
index e89c7a0..cd4df1a 100644
--- a/bazel/standalone/perfetto_cfg.bzl
+++ b/bazel/standalone/perfetto_cfg.bzl
@@ -28,6 +28,12 @@
# to allow perfetto embedders (e.g. gapid) and google internal builds to
# override paths and target names to their own third_party.
deps = struct(
+ # Target exposing the build config header. It should be a valid
+ # cc_library dependency as it will become a dependency of every
+ # perfetto_cc_library target. It needs to expose a
+ # "perfetto_build_flags.h" file that can be included via:
+ # #include "perfetto_build_flags.h".
+ build_config = ["//:build_config_hdr"],
zlib = ["@perfetto_dep_zlib//:zlib"],
jsoncpp = ["@perfetto_dep_jsoncpp//:jsoncpp"],
linenoise = ["@perfetto_dep_linenoise//:linenoise"],
diff --git a/include/perfetto/ext/base/scoped_file.h b/include/perfetto/ext/base/scoped_file.h
index 2fee6a1..24c8970 100644
--- a/include/perfetto/ext/base/scoped_file.h
+++ b/include/perfetto/ext/base/scoped_file.h
@@ -88,7 +88,8 @@
mode_t mode = kInvalidMode) {
PERFETTO_DCHECK((flags & O_CREAT) == 0 || mode != kInvalidMode);
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
- ScopedFile fd(open(path.c_str(), flags, mode));
+ // Always use O_BINARY on Windows, to avoid silly EOL translations.
+ ScopedFile fd(open(path.c_str(), flags | O_BINARY, mode));
#else
// Always open a ScopedFile with O_CLOEXEC so we can safely fork and exec.
ScopedFile fd(open(path.c_str(), flags | O_CLOEXEC, mode));
diff --git a/include/perfetto/profiling/BUILD.gn b/include/perfetto/profiling/BUILD.gn
index a897783..23e19c9 100644
--- a/include/perfetto/profiling/BUILD.gn
+++ b/include/perfetto/profiling/BUILD.gn
@@ -21,3 +21,9 @@
"symbolizer.h",
]
}
+
+source_set("normalize") {
+ sources = [
+ "normalize.h",
+ ]
+}
diff --git a/src/profiling/memory/ext.h b/include/perfetto/profiling/normalize.h
similarity index 91%
rename from src/profiling/memory/ext.h
rename to include/perfetto/profiling/normalize.h
index b353e98..7b7da1d 100644
--- a/src/profiling/memory/ext.h
+++ b/include/perfetto/profiling/normalize.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef SRC_PROFILING_MEMORY_EXT_H_
-#define SRC_PROFILING_MEMORY_EXT_H_
+#ifndef INCLUDE_PERFETTO_PROFILING_NORMALIZE_H_
+#define INCLUDE_PERFETTO_PROFILING_NORMALIZE_H_
// Header only code that gets used in other projects.
// This is currently used in
@@ -63,4 +63,4 @@
} // namespace profiling
} // namespace perfetto
-#endif // SRC_PROFILING_MEMORY_EXT_H_
+#endif // INCLUDE_PERFETTO_PROFILING_NORMALIZE_H_
diff --git a/include/perfetto/profiling/pprof_builder.h b/include/perfetto/profiling/pprof_builder.h
index db6f57b..2ec9515 100644
--- a/include/perfetto/profiling/pprof_builder.h
+++ b/include/perfetto/profiling/pprof_builder.h
@@ -21,6 +21,8 @@
#include <string>
#include <vector>
+#include "perfetto/trace_processor/trace_processor.h"
+
// TODO(135923303): do not depend on anything in this file as it will be
// changed heavily as part of fixing b/135923303.
namespace perfetto {
@@ -33,11 +35,22 @@
std::string serialized;
};
+bool TraceToPprof(trace_processor::TraceProcessor*,
+ std::vector<SerializedProfile>* output,
+ Symbolizer* symbolizer,
+ uint64_t pid = 0,
+ const std::vector<uint64_t>& timestamps = {});
+
bool TraceToPprof(std::istream* input,
std::vector<SerializedProfile>* output,
- Symbolizer* symbolizer);
+ Symbolizer* symbolizer,
+ uint64_t pid = 0,
+ const std::vector<uint64_t>& timestamps = {});
-bool TraceToPprof(std::istream* input, std::vector<SerializedProfile>* output);
+bool TraceToPprof(std::istream* input,
+ std::vector<SerializedProfile>* output,
+ uint64_t pid = 0,
+ const std::vector<uint64_t>& timestamps = {});
} // namespace trace_to_text
} // namespace perfetto
diff --git a/src/profiling/memory/BUILD.gn b/src/profiling/memory/BUILD.gn
index a384fe8..10af659 100644
--- a/src/profiling/memory/BUILD.gn
+++ b/src/profiling/memory/BUILD.gn
@@ -93,6 +93,7 @@
source_set("proc_utils") {
deps = [
"../../../gn:default_deps",
+ "../../../include/perfetto/profiling:normalize",
"../../base",
]
sources = [
@@ -212,6 +213,7 @@
"../../../gn:default_deps",
"../../../gn:gtest_and_gmock",
"../../../gn:libunwindstack",
+ "../../../include/perfetto/profiling:normalize",
"../../base",
"../../base:test_support",
"../../tracing",
diff --git a/src/profiling/memory/proc_utils.cc b/src/profiling/memory/proc_utils.cc
index 07f85cd..6d0b6f3 100644
--- a/src/profiling/memory/proc_utils.cc
+++ b/src/profiling/memory/proc_utils.cc
@@ -21,7 +21,7 @@
#include <unistd.h>
#include "perfetto/ext/base/file_utils.h"
-#include "src/profiling/memory/ext.h"
+#include "perfetto/profiling/normalize.h"
namespace perfetto {
namespace profiling {
diff --git a/src/profiling/memory/proc_utils_unittest.cc b/src/profiling/memory/proc_utils_unittest.cc
index b1881bb..6c0065c 100644
--- a/src/profiling/memory/proc_utils_unittest.cc
+++ b/src/profiling/memory/proc_utils_unittest.cc
@@ -15,7 +15,7 @@
*/
#include "src/profiling/memory/proc_utils.h"
-#include "src/profiling/memory/ext.h"
+#include "perfetto/profiling/normalize.h"
#include "perfetto/ext/base/utils.h"
#include "test/gtest_and_gmock.h"
diff --git a/src/trace_processor/db/BUILD.gn b/src/trace_processor/db/BUILD.gn
index b09dc86..7360779 100644
--- a/src/trace_processor/db/BUILD.gn
+++ b/src/trace_processor/db/BUILD.gn
@@ -60,6 +60,8 @@
]
sources = [
"bit_vector_benchmark.cc",
+ "row_map_benchmark.cc",
+ "sparse_vector_benchmark.cc",
]
}
}
diff --git a/src/trace_processor/db/bit_vector.cc b/src/trace_processor/db/bit_vector.cc
index 5e2db89..edf1af6 100644
--- a/src/trace_processor/db/bit_vector.cc
+++ b/src/trace_processor/db/bit_vector.cc
@@ -19,12 +19,19 @@
namespace perfetto {
namespace trace_processor {
-BitVector::BitVector(uint32_t count, bool value) : inner_(count, value) {}
+BitVector::BitVector() = default;
-BitVector::BitVector(std::vector<bool> inner) : inner_(std::move(inner)) {}
+BitVector::BitVector(uint32_t count, bool value) {
+ Resize(count, value);
+}
+
+BitVector::BitVector(std::vector<Block> blocks,
+ std::vector<uint32_t> counts,
+ uint32_t size)
+ : size_(size), counts_(std::move(counts)), blocks_(std::move(blocks)) {}
BitVector BitVector::Copy() const {
- return BitVector(inner_);
+ return BitVector(blocks_, counts_, size_);
}
} // namespace trace_processor
diff --git a/src/trace_processor/db/bit_vector.h b/src/trace_processor/db/bit_vector.h
index 4bddd75..bd85b76 100644
--- a/src/trace_processor/db/bit_vector.h
+++ b/src/trace_processor/db/bit_vector.h
@@ -18,8 +18,10 @@
#define SRC_TRACE_PROCESSOR_DB_BIT_VECTOR_H_
#include <stdint.h>
+#include <stdio.h>
#include <algorithm>
+#include <array>
#include <vector>
#include "perfetto/base/logging.h"
@@ -29,17 +31,15 @@
// A bitvector which compactly stores a vector of bools using a single bit
// for each bool.
-// TODO(lalitm): currently this is just a thin wrapper around std::vector<bool>
-// but in the future, we plan to add quite a few optimizations around ranges
-// of set bits.
class BitVector {
public:
// Creates an empty bitvector.
- BitVector() = default;
+ BitVector();
// Creates a bitvector of |count| size filled with |value|.
BitVector(uint32_t count, bool value = false);
+ // Enable moving bitvectors as they have no unmovable state.
BitVector(BitVector&&) noexcept = default;
BitVector& operator=(BitVector&&) = default;
@@ -47,21 +47,14 @@
BitVector Copy() const;
// Returns the size of the bitvector.
- uint32_t size() const { return static_cast<uint32_t>(inner_.size()); }
+ uint32_t size() const { return static_cast<uint32_t>(size_); }
// Returns whether the bit at |idx| is set.
bool IsSet(uint32_t idx) const {
PERFETTO_DCHECK(idx < size());
- return inner_[idx];
- }
- // Returns the index of the next set bit at or after index |idx|.
- // If there is no other set bits, returns |size()|.
- uint32_t NextSet(uint32_t idx) const {
- PERFETTO_DCHECK(idx <= inner_.size());
- auto it = std::find(inner_.begin() + static_cast<ptrdiff_t>(idx),
- inner_.end(), true);
- return static_cast<uint32_t>(std::distance(inner_.begin(), it));
+ Address a = IndexToAddress(idx);
+ return blocks_[a.block_idx].IsSet(a.block_offset);
}
// Returns the number of set bits in the bitvector.
@@ -70,35 +63,184 @@
// Returns the number of set bits between the start of the bitvector
// (inclusive) and the index |end| (exclusive).
uint32_t GetNumBitsSet(uint32_t end) const {
- return static_cast<uint32_t>(std::count(
- inner_.begin(), inner_.begin() + static_cast<ptrdiff_t>(end), true));
+ if (end == 0)
+ return 0;
+
+ // Although the external interface we present uses an exclusive |end|,
+ // internally it's a lot nicer to work with an inclusive |end| (mainly
+ // because we get block rollovers on exclusive ends which means we need
+ // to have if checks to ensure we don't overflow the number of blocks).
+ Address addr = IndexToAddress(end - 1);
+ uint32_t idx = addr.block_idx;
+
+ // Add the number of set bits until the start of the block to the number
+ // of set bits until the end address inside the block.
+ return counts_[idx] + blocks_[idx].GetNumBitsSet(addr.block_offset);
}
- // Returns the index of the |n|'th set bit.
+ // Returns the index of the |n|th set bit. Should only be called with |n| <
+ // GetNumBitsSet().
uint32_t IndexOfNthSet(uint32_t n) const {
- // TODO(lalitm): improve the performance of this method by investigating
- // AVX instructions.
- uint32_t offset = 0;
- for (uint32_t i = NextSet(0); i < size(); i = NextSet(i + 1), ++offset) {
- if (offset == n)
- return i;
+ PERFETTO_DCHECK(n < GetNumBitsSet());
+
+ // First search for the block which, up until the start of it, has more than
+ // n bits set. Note that this should never return |counts.begin()| as
+ // that should always be 0.
+ // TODO(lalitm): investigate whether we can make this faster with small
+ // binary search followed by a linear search instead of binary searching the
+ // full way.
+ auto it = std::upper_bound(counts_.begin(), counts_.end(), n);
+ PERFETTO_DCHECK(it != counts_.begin());
+
+ // Go back one block to find the block which has the bit we are looking for.
+ uint16_t block_idx =
+ static_cast<uint16_t>(std::distance(counts_.begin(), it) - 1);
+
+ // Figure out how many set bits forward we are looking inside the block
+ // by taking away the number of bits at the start of the block from n.
+ uint32_t set_in_block = n - counts_[block_idx];
+
+ // Compute the address of the bit in the block then convert the full
+ // address back to an index.
+ BlockOffset block_offset = blocks_[block_idx].IndexOfNthSet(set_in_block);
+ return AddressToIndex(Address{block_idx, block_offset});
+ }
+
+ // Sets the bit at index |idx| to true.
+ void Set(uint32_t idx) {
+ // Set the bit to the correct value inside the block but store the old
+ // bit to help fix the counts.
+ auto addr = IndexToAddress(idx);
+ bool old_value = blocks_[addr.block_idx].IsSet(addr.block_offset);
+
+ // If the old value was unset, set the bit and add one to the count.
+ if (PERFETTO_LIKELY(!old_value)) {
+ blocks_[addr.block_idx].Set(addr.block_offset);
+
+ uint32_t size = static_cast<uint32_t>(counts_.size());
+ for (uint32_t i = addr.block_idx + 1; i < size; ++i) {
+ counts_[i]++;
+ }
}
- PERFETTO_FATAL("Index out of bounds");
}
- // Sets the value at index |idx| to |value|.
- void Set(uint32_t idx, bool value) {
- PERFETTO_DCHECK(idx < size());
- inner_[idx] = value;
+ // Sets the bit at index |idx| to false.
+ void Clear(uint32_t idx) {
+ // Set the bit to the correct value inside the block but store the old
+ // bit to help fix the counts.
+ auto addr = IndexToAddress(idx);
+ bool old_value = blocks_[addr.block_idx].IsSet(addr.block_offset);
+
+ // If the old value was set, clear the bit and subtract one from all the
+ // counts.
+ if (PERFETTO_LIKELY(old_value)) {
+ blocks_[addr.block_idx].Clear(addr.block_offset);
+
+ uint32_t size = static_cast<uint32_t>(counts_.size());
+ for (uint32_t i = addr.block_idx + 1; i < size; ++i) {
+ counts_[i]--;
+ }
+ }
}
- // Appends |value| to the bitvector.
- void Append(bool value) { inner_.push_back(value); }
+ // Appends true to the bitvector.
+ void AppendTrue() {
+ Address addr = IndexToAddress(size_);
+ uint32_t old_blocks_size = static_cast<uint32_t>(blocks_.size());
+ uint32_t new_blocks_size = addr.block_idx + 1;
+
+ if (PERFETTO_UNLIKELY(new_blocks_size > old_blocks_size)) {
+ uint32_t t = GetNumBitsSet();
+ blocks_.emplace_back();
+ counts_.emplace_back(t);
+ }
+
+ size_++;
+ blocks_[addr.block_idx].Set(addr.block_offset);
+ }
+
+ // Appends false to the bitvector.
+ void AppendFalse() {
+ Address addr = IndexToAddress(size_);
+ uint32_t old_blocks_size = static_cast<uint32_t>(blocks_.size());
+ uint32_t new_blocks_size = addr.block_idx + 1;
+
+ if (PERFETTO_UNLIKELY(new_blocks_size > old_blocks_size)) {
+ uint32_t t = GetNumBitsSet();
+ blocks_.emplace_back();
+ counts_.emplace_back(t);
+ }
+
+ size_++;
+ // We don't need to clear the bit as we ensure that anything after
+ // size_ is always set to false.
+ }
// Resizes the BitVector to the given |size|.
// Truncates the BitVector if |size| < |size()| or fills the new space with
- // |value| if |size| > |size()|.
- void Resize(uint32_t size, bool value = false) { inner_.resize(size, value); }
+ // |value| if |size| > |size()|. Calling this method is a noop if |size| ==
+ // |size()|.
+ void Resize(uint32_t size, bool value = false) {
+ uint32_t old_size = size_;
+ if (size == old_size)
+ return;
+
+ // Empty bitvectors should be memory efficient so we don't keep any data
+ // around in the bitvector.
+ if (size == 0) {
+ blocks_.clear();
+ counts_.clear();
+ size_ = 0;
+ return;
+ }
+
+ // Compute the address of the new last bit in the bitvector.
+ Address last_addr = IndexToAddress(size - 1);
+ uint32_t old_blocks_size = static_cast<uint32_t>(counts_.size());
+ uint32_t new_blocks_size = last_addr.block_idx + 1;
+
+ // Then, resize the block and count vectors to have the correct
+ // number of entries.
+ blocks_.resize(new_blocks_size);
+ counts_.resize(new_blocks_size);
+
+ if (new_blocks_size > old_blocks_size) {
+ // If we've increased the number of blocks, we need to fix the
+ // counts vector by setting the newly added counts to the
+ // count of the bitvector. This matches the empty blocks we just
+ // added. Below, we will actually set the bits in the newly added
+ // blocks and as we do that, we will update the counts.
+ //
+ // Note: as we haven't updated |size_| yet GetNumBitsSet() won't take into
+ // account the newly added blocks yet.
+ uint32_t count = GetNumBitsSet();
+ for (uint32_t i = old_blocks_size; i < new_blocks_size; ++i) {
+ counts_[i] = count;
+ }
+ }
+
+ // Actually update the size before we call |Set| below to ensure Set's
+ // invariants make sense.
+ size_ = size;
+ if (size > old_size) {
+ // Just go through all the newly added bits and set them to the true - we
+ // don't need to do this if !value because we always expect the newly
+ // added bits to be zeroed (this is ensured by the else branch).
+ // TODO(lalitm): this is clearly non optimal. Try and have a more
+ // optimized version of this based on setting whole blocks
+ // to 1.
+ if (value) {
+ for (uint32_t i = old_size; i < size; ++i) {
+ Set(i);
+ }
+ }
+ } else {
+ // Throw away all the bits after the new last bit. We do this to make
+ // future lookup, append and resize operations not have to worrying about
+ // trailing garbage bits in the last block.
+ blocks_[last_addr.block_idx].ClearAfter(last_addr.block_offset);
+ }
+ }
// Updates the ith set bit of this bitvector with the value of
// |other.IsSet(i)|.
@@ -116,20 +258,303 @@
void UpdateSetBits(const BitVector& other) {
PERFETTO_DCHECK(other.size() == GetNumBitsSet());
- uint32_t offset = 0;
- for (uint32_t i = NextSet(0); i < size(); i = NextSet(i + 1), ++offset) {
- if (!other.IsSet(offset))
- Set(i, false);
+ // Go through each set bit and if |other| has it unset, then unset the
+ // bit taking care to update the index we consider to take into account
+ // the bits we just unset.
+ // TODO(lalitm): we add an iterator implementation to remove this
+ // inefficient loop.
+ uint32_t removed = 0;
+ for (uint32_t i = 0, size = other.size(); i < size; ++i) {
+ if (!other.IsSet(i)) {
+ Clear(IndexOfNthSet(i - removed++));
+ }
}
}
private:
- BitVector(std::vector<bool>);
+ // Represents the offset of a bit within a block.
+ struct BlockOffset {
+ uint16_t word_idx;
+ uint16_t bit_idx;
+ };
+
+ // Represents the address of a bit within the bitvector.
+ struct Address {
+ uint32_t block_idx;
+ BlockOffset block_offset;
+ };
+
+ // Represents the smallest collection of bits we can refer to as
+ // one unit.
+ //
+ // Currently, this is implemented as a 64 bit integer as this is the
+ // largest type which we can assume to be present on all platforms.
+ class BitWord {
+ public:
+ static constexpr uint32_t kBits = 64;
+
+ // Returns whether the bit at the given index is set.
+ bool IsSet(uint32_t idx) const {
+ PERFETTO_DCHECK(idx < kBits);
+ return (word >> idx) & 1ull;
+ }
+
+ // Sets the bit at the given index to true.
+ void Set(uint32_t idx) {
+ PERFETTO_DCHECK(idx < kBits);
+
+ // Or the value for the true shifted up to |idx| with the word.
+ word |= 1ull << idx;
+ }
+
+ // Sets the bit at the given index to false.
+ void Clear(uint32_t idx) {
+ PERFETTO_DCHECK(idx < kBits);
+
+ // And the integer of all bits set apart from |idx| with the word.
+ word &= ~(1ull << idx);
+ }
+
+ // Clears all the bits (i.e. sets the atom to zero).
+ void ClearAll() { word = 0; }
+
+ // Returns the index of the nth set bit.
+ // Undefined if |n| >= |GetNumBitsSet()|.
+ uint16_t IndexOfNthSet(uint32_t n) const {
+ PERFETTO_DCHECK(n < kBits);
+
+ // The below code is very dense but essentially computes the nth set
+ // bit inside |atom| in the "broadword" style of programming (sometimes
+ // referred to as "SIMD within a register").
+ //
+ // Instead of treating a uint64 as an individual unit, broadword
+ // algorithms treat them as a packed vector of uint8. By doing this, they
+ // allow branchless algorithms when considering bits of a uint64.
+ //
+ // In benchmarks, this algorithm has found to be the fastest, portable
+ // way of computing the nth set bit (if we were only targetting new
+ // versions of x64, we could also use pdep + ctz but unfortunately
+ // this would fail on WASM - this about 2.5-3x faster on x64).
+ //
+ // The code below was taken from the paper
+ // http://vigna.di.unimi.it/ftp/papers/Broadword.pdf
+ uint64_t s = word - ((word & 0xAAAAAAAAAAAAAAAA) >> 1);
+ s = (s & 0x3333333333333333) + ((s >> 2) & 0x3333333333333333);
+ s = ((s + (s >> 4)) & 0x0F0F0F0F0F0F0F0F) * L8;
+
+ uint64_t b = (BwLessThan(s, n * L8) >> 7) * L8 >> 53 & ~7ull;
+ uint64_t l = n - ((s << 8) >> b & 0xFF);
+ s = (BwGtZero(((word >> b & 0xFF) * L8) & 0x8040201008040201) >> 7) * L8;
+
+ uint64_t ret = b + ((BwLessThan(s, l * L8) >> 7) * L8 >> 56);
+
+ return static_cast<uint16_t>(ret);
+ }
+
+ // Returns the number of set bits.
+ uint32_t GetNumBitsSet() const {
+ // We use __builtin_popcountll here as it's available natively for the two
+ // targets we care most about (x64 and WASM).
+ return static_cast<uint32_t>(__builtin_popcountll(word));
+ }
+
+ // Returns the number of set bits up to and including the bit at |idx|.
+ uint32_t GetNumBitsSet(uint32_t idx) const {
+ PERFETTO_DCHECK(idx < kBits);
+
+ // We use __builtin_popcountll here as it's available natively for the two
+ // targets we care most about (x64 and WASM).
+ return static_cast<uint32_t>(__builtin_popcountll(WordUntil(idx)));
+ }
+
+ // Retains all bits up to and including the bit at |idx| and clears
+ // all bits after this point.
+ void ClearAfter(uint32_t idx) {
+ PERFETTO_DCHECK(idx < kBits);
+ word = WordUntil(idx);
+ }
+
+ private:
+ // Constant with all the low bit of every byte set.
+ static constexpr uint64_t L8 = 0x0101010101010101;
+
+ // Constant with all the high bit of every byte set.
+ static constexpr uint64_t H8 = 0x8080808080808080;
+
+ // Returns a packed uint64 encoding whether each byte of x is less
+ // than each corresponding byte of y.
+ // This is computed in the "broadword" style of programming; see
+ // IndexOfNthSet for details on this.
+ static uint64_t BwLessThan(uint64_t x, uint64_t y) {
+ return (((y | H8) - (x & ~H8)) ^ x ^ y) & H8;
+ }
+
+ // Returns a packed uint64 encoding whether each byte of x is greater
+ // than or equal zero.
+ // This is computed in the "broadword" style of programming; see
+ // IndexOfNthSet for details on this.
+ static uint64_t BwGtZero(uint64_t x) { return (((x | H8) - L8) | x) & H8; }
+
+ // Returns the bits up to and including the bit at |idx|.
+ uint64_t WordUntil(uint32_t idx) const {
+ PERFETTO_DCHECK(idx < kBits);
+
+ // To understand what is happeninng here, consider an example.
+ // Suppose we want to all the bits up to the 7th bit in the atom
+ // 7th
+ // |
+ // v
+ // atom: 01010101011111000
+ //
+ // The easiest way to do this would be if we had a mask with only
+ // the bottom 7 bits set:
+ // mask: 00000000001111111
+ //
+ // Start with 1 and shift it up (idx + 1) bits we get:
+ // top : 00000000010000000
+ uint64_t top = 1ull << ((idx + 1ull) % kBits);
+
+ // We need to handle the case where idx == 63. In this case |top| will be
+ // zero because 1 << ((idx + 1) % 64) == 1 << (64 % 64) == 1.
+ // In this case, we actually want top == 0. We can do this by shifting
+ // down by (idx + 1) / kBits - this will be a noop for every index other
+ // than idx == 63. This should also be free on intel because of the mod
+ // instruction above.
+ top = top >> ((idx + 1) / kBits);
+
+ // Then if we take away 1, we get precisely the mask we want.
+ uint64_t mask = top - 1u;
+
+ // Finish up by anding the the atom with the computed msk.
+ return word & mask;
+ }
+
+ uint64_t word = 0;
+ };
+
+ // Represents a group of bits with a bitcount such that it is
+ // efficient to work on these bits.
+ //
+ // On x86 architectures we generally target for trace processor, the
+ // size of a cache line is 64 bytes (or 512 bits). For this reason,
+ // we make the size of the block contain 8 atoms as 8 * 64 == 512.
+ //
+ // TODO(lalitm): investigate whether we should tune this value for
+ // WASM and ARM.
+ class Block {
+ public:
+ // See class documentation for how these constants are chosen.
+ static constexpr uint32_t kWords = 8;
+ static constexpr uint32_t kBits = kWords * BitWord::kBits;
+
+ // Returns whether the bit at the given address is set.
+ bool IsSet(const BlockOffset& addr) const {
+ PERFETTO_DCHECK(addr.word_idx < kWords);
+
+ return words_[addr.word_idx].IsSet(addr.bit_idx);
+ }
+
+ // Sets the bit at the given address to true.
+ void Set(const BlockOffset& addr) {
+ PERFETTO_DCHECK(addr.word_idx < kWords);
+
+ words_[addr.word_idx].Set(addr.bit_idx);
+ }
+
+ // Sets the bit at the given address to false.
+ void Clear(const BlockOffset& addr) {
+ PERFETTO_DCHECK(addr.word_idx < kWords);
+
+ words_[addr.word_idx].Clear(addr.bit_idx);
+ }
+
+ // Gets the offset of the nth set bit in this block.
+ BlockOffset IndexOfNthSet(uint32_t n) const {
+ uint32_t count = 0;
+ for (uint16_t i = 0; i < kWords; ++i) {
+ // Keep a running count of all the set bits in the atom.
+ uint32_t value = count + words_[i].GetNumBitsSet();
+ if (value <= n) {
+ count = value;
+ continue;
+ }
+
+ // The running count of set bits is more than |n|. That means this atom
+ // contains the bit we are looking for.
+
+ // Take away the number of set bits to the start of this atom from |n|.
+ uint32_t set_in_atom = n - count;
+
+ // Figure out the index of the set bit inside the atom and create the
+ // address of this bit from that.
+ uint16_t bit_idx = words_[i].IndexOfNthSet(set_in_atom);
+ PERFETTO_DCHECK(bit_idx < 64);
+ return BlockOffset{i, bit_idx};
+ }
+ PERFETTO_FATAL("Index out of bounds");
+ }
+
+ // Gets the number of set bits within a block up to and including the bit
+ // at the given address.
+ uint32_t GetNumBitsSet(const BlockOffset& addr) const {
+ PERFETTO_DCHECK(addr.word_idx < kWords);
+
+ // Count all the set bits in the atom until we reach the last atom
+ // index.
+ uint32_t count = 0;
+ for (uint32_t i = 0; i < addr.word_idx; ++i) {
+ count += words_[i].GetNumBitsSet();
+ }
+
+ // For the last atom, only count the bits upto and including the bit
+ // index.
+ return count + words_[addr.word_idx].GetNumBitsSet(addr.bit_idx);
+ }
+
+ // Retains all bits up to and including the bit at |addr| and clears
+ // all bits after this point.
+ void ClearAfter(const BlockOffset& offset) {
+ PERFETTO_DCHECK(offset.word_idx < kWords);
+
+ // In the first atom, keep the bits until the address specified.
+ words_[offset.word_idx].ClearAfter(offset.bit_idx);
+
+ // For all subsequent atoms, we just clear the whole atom.
+ for (uint32_t i = offset.word_idx + 1; i < kWords; ++i) {
+ words_[i].ClearAll();
+ }
+ }
+
+ private:
+ std::array<BitWord, kWords> words_{};
+ };
+
+ BitVector(std::vector<Block> blocks,
+ std::vector<uint32_t> counts,
+ uint32_t size);
BitVector(const BitVector&) = delete;
BitVector& operator=(const BitVector&) = delete;
- std::vector<bool> inner_;
+ static Address IndexToAddress(uint32_t idx) {
+ Address a;
+ a.block_idx = idx / Block::kBits;
+
+ uint16_t bit_idx_inside_block = idx % Block::kBits;
+ a.block_offset.word_idx = bit_idx_inside_block / BitWord::kBits;
+ a.block_offset.bit_idx = bit_idx_inside_block % BitWord::kBits;
+ return a;
+ }
+
+ static uint32_t AddressToIndex(Address addr) {
+ return addr.block_idx * Block::kBits +
+ addr.block_offset.word_idx * BitWord::kBits +
+ addr.block_offset.bit_idx;
+ }
+
+ uint32_t size_ = 0;
+ std::vector<uint32_t> counts_;
+ std::vector<Block> blocks_;
};
} // namespace trace_processor
diff --git a/src/trace_processor/db/bit_vector_benchmark.cc b/src/trace_processor/db/bit_vector_benchmark.cc
index d6ed930..50e9baf 100644
--- a/src/trace_processor/db/bit_vector_benchmark.cc
+++ b/src/trace_processor/db/bit_vector_benchmark.cc
@@ -18,68 +18,119 @@
#include "src/trace_processor/db/bit_vector.h"
-static void BM_BitVectorAppend(benchmark::State& state) {
- static constexpr uint32_t kPoolSize = 1024 * 1024;
- std::vector<bool> bit_pool(kPoolSize);
+namespace {
- static constexpr uint32_t kRandomSeed = 42;
- std::minstd_rand0 rnd_engine(kRandomSeed);
- for (uint32_t i = 0; i < kPoolSize; ++i) {
- bit_pool[i] = rnd_engine() % 2;
- }
+using perfetto::trace_processor::BitVector;
- perfetto::trace_processor::BitVector bv;
- uint32_t pool_idx = 0;
+}
+
+static void BM_BitVectorAppendTrue(benchmark::State& state) {
+ BitVector bv;
for (auto _ : state) {
- bv.Append(bit_pool[pool_idx]);
- pool_idx = (pool_idx + 1) % kPoolSize;
+ bv.AppendTrue();
benchmark::ClobberMemory();
}
}
-BENCHMARK(BM_BitVectorAppend);
+BENCHMARK(BM_BitVectorAppendTrue);
+
+static void BM_BitVectorAppendFalse(benchmark::State& state) {
+ BitVector bv;
+ for (auto _ : state) {
+ bv.AppendFalse();
+ benchmark::ClobberMemory();
+ }
+}
+BENCHMARK(BM_BitVectorAppendFalse);
static void BM_BitVectorSet(benchmark::State& state) {
+ static constexpr uint32_t kRandomSeed = 42;
+ std::minstd_rand0 rnd_engine(kRandomSeed);
+
+ uint32_t size = static_cast<uint32_t>(state.range(0));
+
+ BitVector bv;
+ for (uint32_t i = 0; i < size; ++i) {
+ if (rnd_engine() % 2) {
+ bv.AppendTrue();
+ } else {
+ bv.AppendFalse();
+ }
+ }
+
static constexpr uint32_t kPoolSize = 1024 * 1024;
std::vector<bool> bit_pool(kPoolSize);
std::vector<uint32_t> row_pool(kPoolSize);
-
- static constexpr uint32_t kSize = 123456;
- perfetto::trace_processor::BitVector bv;
-
- static constexpr uint32_t kRandomSeed = 42;
- std::minstd_rand0 rnd_engine(kRandomSeed);
for (uint32_t i = 0; i < kPoolSize; ++i) {
bit_pool[i] = rnd_engine() % 2;
- row_pool[i] = rnd_engine() % kSize;
- }
-
- for (uint32_t i = 0; i < kSize; ++i) {
- bv.Append(rnd_engine() % 2);
+ row_pool[i] = rnd_engine() % size;
}
uint32_t pool_idx = 0;
for (auto _ : state) {
- bv.Set(row_pool[pool_idx], bit_pool[pool_idx]);
+ bv.Set(row_pool[pool_idx]);
pool_idx = (pool_idx + 1) % kPoolSize;
benchmark::ClobberMemory();
}
}
-BENCHMARK(BM_BitVectorSet);
+BENCHMARK(BM_BitVectorSet)
+ ->Arg(64)
+ ->Arg(512)
+ ->Arg(8192)
+ ->Arg(123456)
+ ->Arg(1234567);
-static void BM_BitVectorIndexOfNthSet(benchmark::State& state) {
- static constexpr uint32_t kPoolSize = 1024 * 1024;
- std::vector<uint32_t> row_pool(kPoolSize);
-
- static constexpr uint32_t kSize = 123456;
- perfetto::trace_processor::BitVector bv;
-
+static void BM_BitVectorClear(benchmark::State& state) {
static constexpr uint32_t kRandomSeed = 42;
std::minstd_rand0 rnd_engine(kRandomSeed);
- for (uint32_t i = 0; i < kSize; ++i) {
- bool value = rnd_engine() % 2;
- bv.Append(value);
+
+ uint32_t size = static_cast<uint32_t>(state.range(0));
+
+ BitVector bv;
+ for (uint32_t i = 0; i < size; ++i) {
+ if (rnd_engine() % 2) {
+ bv.AppendTrue();
+ } else {
+ bv.AppendFalse();
+ }
}
+ static constexpr uint32_t kPoolSize = 1024 * 1024;
+ std::vector<uint32_t> row_pool(kPoolSize);
+ for (uint32_t i = 0; i < kPoolSize; ++i) {
+ row_pool[i] = rnd_engine() % size;
+ }
+
+ uint32_t pool_idx = 0;
+ for (auto _ : state) {
+ bv.Clear(row_pool[pool_idx]);
+ pool_idx = (pool_idx + 1) % kPoolSize;
+ benchmark::ClobberMemory();
+ }
+}
+BENCHMARK(BM_BitVectorClear)
+ ->Arg(64)
+ ->Arg(512)
+ ->Arg(8192)
+ ->Arg(123456)
+ ->Arg(1234567);
+
+static void BM_BitVectorIndexOfNthSet(benchmark::State& state) {
+ static constexpr uint32_t kRandomSeed = 42;
+ std::minstd_rand0 rnd_engine(kRandomSeed);
+
+ uint32_t size = static_cast<uint32_t>(state.range(0));
+
+ BitVector bv;
+ for (uint32_t i = 0; i < size; ++i) {
+ if (rnd_engine() % 2) {
+ bv.AppendTrue();
+ } else {
+ bv.AppendFalse();
+ }
+ }
+
+ static constexpr uint32_t kPoolSize = 1024 * 1024;
+ std::vector<uint32_t> row_pool(kPoolSize);
uint32_t set_bit_count = bv.GetNumBitsSet();
for (uint32_t i = 0; i < kPoolSize; ++i) {
row_pool[i] = rnd_engine() % set_bit_count;
@@ -91,18 +142,28 @@
pool_idx = (pool_idx + 1) % kPoolSize;
}
}
-BENCHMARK(BM_BitVectorIndexOfNthSet);
+BENCHMARK(BM_BitVectorIndexOfNthSet)
+ ->Arg(64)
+ ->Arg(512)
+ ->Arg(8192)
+ ->Arg(123456)
+ ->Arg(1234567);
static void BM_BitVectorGetNumBitsSet(benchmark::State& state) {
- static constexpr uint32_t kSize = 123456;
- perfetto::trace_processor::BitVector bv;
- uint32_t count = 0;
-
static constexpr uint32_t kRandomSeed = 42;
std::minstd_rand0 rnd_engine(kRandomSeed);
- for (uint32_t i = 0; i < kSize; ++i) {
+
+ uint32_t size = static_cast<uint32_t>(state.range(0));
+
+ uint32_t count = 0;
+ BitVector bv;
+ for (uint32_t i = 0; i < size; ++i) {
bool value = rnd_engine() % 2;
- bv.Append(value);
+ if (value) {
+ bv.AppendTrue();
+ } else {
+ bv.AppendFalse();
+ }
if (value)
count++;
@@ -114,4 +175,9 @@
}
PERFETTO_CHECK(res == count);
}
-BENCHMARK(BM_BitVectorGetNumBitsSet);
+BENCHMARK(BM_BitVectorGetNumBitsSet)
+ ->Arg(64)
+ ->Arg(512)
+ ->Arg(8192)
+ ->Arg(123456)
+ ->Arg(1234567);
diff --git a/src/trace_processor/db/bit_vector_unittest.cc b/src/trace_processor/db/bit_vector_unittest.cc
index 371b4fe..7e7543b 100644
--- a/src/trace_processor/db/bit_vector_unittest.cc
+++ b/src/trace_processor/db/bit_vector_unittest.cc
@@ -16,97 +16,210 @@
#include "src/trace_processor/db/bit_vector.h"
+#include <random>
+
#include "test/gtest_and_gmock.h"
namespace perfetto {
namespace trace_processor {
namespace {
-TEST(BitVectorUnittest, Set) {
- BitVector bv(3, true);
- bv.Set(0, false);
- bv.Set(1, true);
+TEST(BitVectorUnittest, CreateAllTrue) {
+ BitVector bv(2049, true);
- ASSERT_EQ(bv.size(), 3u);
- ASSERT_FALSE(bv.IsSet(0));
+ // Ensure that a selection of interesting bits are set.
+ ASSERT_TRUE(bv.IsSet(0));
ASSERT_TRUE(bv.IsSet(1));
- ASSERT_TRUE(bv.IsSet(2));
+ ASSERT_TRUE(bv.IsSet(511));
+ ASSERT_TRUE(bv.IsSet(512));
+ ASSERT_TRUE(bv.IsSet(2047));
+ ASSERT_TRUE(bv.IsSet(2048));
}
-TEST(BitVectorUnittest, Append) {
+TEST(BitVectorUnittest, CreateAllFalse) {
+ BitVector bv(2049, false);
+
+ // Ensure that a selection of interesting bits are cleared.
+ ASSERT_FALSE(bv.IsSet(0));
+ ASSERT_FALSE(bv.IsSet(1));
+ ASSERT_FALSE(bv.IsSet(511));
+ ASSERT_FALSE(bv.IsSet(512));
+ ASSERT_FALSE(bv.IsSet(2047));
+ ASSERT_FALSE(bv.IsSet(2048));
+}
+
+TEST(BitVectorUnittest, Set) {
+ BitVector bv(2049, false);
+ bv.Set(0);
+ bv.Set(1);
+ bv.Set(511);
+ bv.Set(512);
+ bv.Set(2047);
+
+ // Ensure the bits we touched are set.
+ ASSERT_TRUE(bv.IsSet(0));
+ ASSERT_TRUE(bv.IsSet(1));
+ ASSERT_TRUE(bv.IsSet(511));
+ ASSERT_TRUE(bv.IsSet(512));
+ ASSERT_TRUE(bv.IsSet(2047));
+
+ // Ensure that a selection of other interestinng bits are
+ // still cleared.
+ ASSERT_FALSE(bv.IsSet(2));
+ ASSERT_FALSE(bv.IsSet(63));
+ ASSERT_FALSE(bv.IsSet(64));
+ ASSERT_FALSE(bv.IsSet(510));
+ ASSERT_FALSE(bv.IsSet(513));
+ ASSERT_FALSE(bv.IsSet(1023));
+ ASSERT_FALSE(bv.IsSet(1024));
+ ASSERT_FALSE(bv.IsSet(2046));
+ ASSERT_FALSE(bv.IsSet(2048));
+ ASSERT_FALSE(bv.IsSet(2048));
+}
+
+TEST(BitVectorUnittest, Clear) {
+ BitVector bv(2049, true);
+ bv.Clear(0);
+ bv.Clear(1);
+ bv.Clear(511);
+ bv.Clear(512);
+ bv.Clear(2047);
+
+ // Ensure the bits we touched are cleared.
+ ASSERT_FALSE(bv.IsSet(0));
+ ASSERT_FALSE(bv.IsSet(1));
+ ASSERT_FALSE(bv.IsSet(511));
+ ASSERT_FALSE(bv.IsSet(512));
+ ASSERT_FALSE(bv.IsSet(2047));
+
+ // Ensure that a selection of other interestinng bits are
+ // still set.
+ ASSERT_TRUE(bv.IsSet(2));
+ ASSERT_TRUE(bv.IsSet(63));
+ ASSERT_TRUE(bv.IsSet(64));
+ ASSERT_TRUE(bv.IsSet(510));
+ ASSERT_TRUE(bv.IsSet(513));
+ ASSERT_TRUE(bv.IsSet(1023));
+ ASSERT_TRUE(bv.IsSet(1024));
+ ASSERT_TRUE(bv.IsSet(2046));
+ ASSERT_TRUE(bv.IsSet(2048));
+}
+
+TEST(BitVectorUnittest, AppendToEmpty) {
BitVector bv;
- bv.Append(true);
- bv.Append(false);
+ bv.AppendTrue();
+ bv.AppendFalse();
ASSERT_EQ(bv.size(), 2u);
ASSERT_TRUE(bv.IsSet(0));
ASSERT_FALSE(bv.IsSet(1));
}
-TEST(BitVectorUnittest, NextSet) {
- BitVector bv(6, false);
- bv.Set(1, true);
- bv.Set(2, true);
- bv.Set(4, true);
+TEST(BitVectorUnittest, AppendToExisting) {
+ BitVector bv(2046, false);
+ bv.AppendTrue();
+ bv.AppendFalse();
+ bv.AppendTrue();
+ bv.AppendTrue();
- ASSERT_EQ(bv.NextSet(0), 1u);
- ASSERT_EQ(bv.NextSet(1), 1u);
- ASSERT_EQ(bv.NextSet(2), 2u);
- ASSERT_EQ(bv.NextSet(3), 4u);
- ASSERT_EQ(bv.NextSet(4), 4u);
- ASSERT_EQ(bv.NextSet(5), 6u);
+ ASSERT_EQ(bv.size(), 2050u);
+ ASSERT_TRUE(bv.IsSet(2046));
+ ASSERT_FALSE(bv.IsSet(2047));
+ ASSERT_TRUE(bv.IsSet(2048));
+ ASSERT_TRUE(bv.IsSet(2049));
}
TEST(BitVectorUnittest, GetNumBitsSet) {
- BitVector bv(6, false);
- bv.Set(1, true);
- bv.Set(2, true);
- bv.Set(4, true);
+ BitVector bv(2049, false);
+ bv.Set(0);
+ bv.Set(1);
+ bv.Set(511);
+ bv.Set(512);
+ bv.Set(2047);
+ bv.Set(2048);
- ASSERT_EQ(bv.GetNumBitsSet(), 3u);
+ ASSERT_EQ(bv.GetNumBitsSet(), 6u);
ASSERT_EQ(bv.GetNumBitsSet(0), 0u);
- ASSERT_EQ(bv.GetNumBitsSet(1), 0u);
- ASSERT_EQ(bv.GetNumBitsSet(2), 1u);
+ ASSERT_EQ(bv.GetNumBitsSet(1), 1u);
+ ASSERT_EQ(bv.GetNumBitsSet(2), 2u);
ASSERT_EQ(bv.GetNumBitsSet(3), 2u);
- ASSERT_EQ(bv.GetNumBitsSet(4), 2u);
- ASSERT_EQ(bv.GetNumBitsSet(5), 3u);
- ASSERT_EQ(bv.GetNumBitsSet(6), 3u);
+ ASSERT_EQ(bv.GetNumBitsSet(511), 2u);
+ ASSERT_EQ(bv.GetNumBitsSet(512), 3u);
+ ASSERT_EQ(bv.GetNumBitsSet(1023), 4u);
+ ASSERT_EQ(bv.GetNumBitsSet(1024), 4u);
+ ASSERT_EQ(bv.GetNumBitsSet(2047), 4u);
+ ASSERT_EQ(bv.GetNumBitsSet(2048), 5u);
+ ASSERT_EQ(bv.GetNumBitsSet(2049), 6u);
}
TEST(BitVectorUnittest, IndexOfNthSet) {
- BitVector bv(6, false);
- bv.Set(1, true);
- bv.Set(2, true);
- bv.Set(4, true);
+ BitVector bv(2050, false);
+ bv.Set(0);
+ bv.Set(1);
+ bv.Set(511);
+ bv.Set(512);
+ bv.Set(2047);
+ bv.Set(2048);
- ASSERT_EQ(bv.IndexOfNthSet(0), 1u);
- ASSERT_EQ(bv.IndexOfNthSet(1), 2u);
- ASSERT_EQ(bv.IndexOfNthSet(2), 4u);
+ ASSERT_EQ(bv.IndexOfNthSet(0), 0u);
+ ASSERT_EQ(bv.IndexOfNthSet(1), 1u);
+ ASSERT_EQ(bv.IndexOfNthSet(2), 511u);
+ ASSERT_EQ(bv.IndexOfNthSet(3), 512u);
+ ASSERT_EQ(bv.IndexOfNthSet(4), 2047u);
+ ASSERT_EQ(bv.IndexOfNthSet(5), 2048u);
}
TEST(BitVectorUnittest, Resize) {
BitVector bv(1, false);
+
bv.Resize(2, true);
- bv.Resize(3, false);
-
- ASSERT_EQ(bv.IsSet(1), true);
- ASSERT_EQ(bv.IsSet(2), false);
-
- bv.Resize(2, false);
-
ASSERT_EQ(bv.size(), 2u);
ASSERT_EQ(bv.IsSet(1), true);
+
+ bv.Resize(2049, false);
+ ASSERT_EQ(bv.size(), 2049u);
+ ASSERT_EQ(bv.IsSet(2), false);
+ ASSERT_EQ(bv.IsSet(2047), false);
+ ASSERT_EQ(bv.IsSet(2048), false);
+
+ // Set these two bits; the first should be preserved and the
+ // second should disappear.
+ bv.Set(512);
+ bv.Set(513);
+
+ bv.Resize(513, false);
+ ASSERT_EQ(bv.size(), 513u);
+ ASSERT_EQ(bv.IsSet(1), true);
+ ASSERT_EQ(bv.IsSet(512), true);
+ ASSERT_EQ(bv.GetNumBitsSet(), 2u);
+
+ // When we resize up, we need to be sure that the set bit from
+ // before we resized down is not still present as a garbage bit.
+ bv.Resize(514, false);
+ ASSERT_EQ(bv.size(), 514u);
+ ASSERT_EQ(bv.IsSet(513), false);
+ ASSERT_EQ(bv.GetNumBitsSet(), 2u);
+}
+
+TEST(BitVectorUnittest, AppendAfterResizeDown) {
+ BitVector bv(2049, false);
+ bv.Set(2048);
+
+ bv.Resize(2048);
+ bv.AppendFalse();
+ ASSERT_EQ(bv.IsSet(2048), false);
+ ASSERT_EQ(bv.GetNumBitsSet(), 0u);
}
TEST(BitVectorUnittest, UpdateSetBits) {
BitVector bv(6, false);
- bv.Set(1, true);
- bv.Set(2, true);
- bv.Set(4, true);
+ bv.Set(1);
+ bv.Set(2);
+ bv.Set(4);
BitVector picker(3u, true);
- picker.Set(1, false);
+ picker.Clear(1);
bv.UpdateSetBits(picker);
@@ -115,6 +228,37 @@
ASSERT_TRUE(bv.IsSet(4));
}
+TEST(BitVectorUnittest, QueryStressTest) {
+ BitVector bv;
+ std::vector<bool> bool_vec;
+ std::vector<uint32_t> int_vec;
+
+ static constexpr uint32_t kCount = 4096;
+ std::minstd_rand0 rand;
+ for (uint32_t i = 0; i < kCount; ++i) {
+ bool res = rand() % 2u;
+ if (res) {
+ bv.AppendTrue();
+ } else {
+ bv.AppendFalse();
+ }
+ bool_vec.push_back(res);
+ if (res)
+ int_vec.emplace_back(i);
+ }
+
+ for (uint32_t i = 0; i < kCount; ++i) {
+ uint32_t count = static_cast<uint32_t>(std::count(
+ bool_vec.begin(), bool_vec.begin() + static_cast<int32_t>(i), true));
+ ASSERT_EQ(bv.IsSet(i), bool_vec[i]);
+ ASSERT_EQ(bv.GetNumBitsSet(i), count);
+ }
+
+ for (uint32_t i = 0; i < int_vec.size(); ++i) {
+ ASSERT_EQ(bv.IndexOfNthSet(i), int_vec[i]);
+ }
+}
+
} // namespace
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/db/row_map.h b/src/trace_processor/db/row_map.h
index e6e1f81..7b2b10e 100644
--- a/src/trace_processor/db/row_map.h
+++ b/src/trace_processor/db/row_map.h
@@ -82,7 +82,7 @@
if (compact_) {
if (row >= bit_vector_.size())
bit_vector_.Resize(row + 1, false);
- bit_vector_.Set(row, true);
+ bit_vector_.Set(row);
} else {
index_vector_.emplace_back(row);
}
@@ -109,8 +109,13 @@
void RemoveIf(Predicate p) {
if (compact_) {
const auto& bv = bit_vector_;
- for (uint32_t i = bv.NextSet(0); i < bv.size(); i = bv.NextSet(i + 1)) {
- bit_vector_.Set(i, !p(i));
+ uint32_t removed = 0;
+ for (uint32_t i = 0, size = bv.GetNumBitsSet(); i < size; ++i) {
+ uint32_t idx = bv.IndexOfNthSet(i - removed);
+ if (p(idx)) {
+ removed++;
+ bit_vector_.Clear(idx);
+ }
}
} else {
auto it = std::remove_if(index_vector_.begin(), index_vector_.end(), p);
diff --git a/src/trace_processor/db/row_map_benchmark.cc b/src/trace_processor/db/row_map_benchmark.cc
new file mode 100644
index 0000000..89f41a7
--- /dev/null
+++ b/src/trace_processor/db/row_map_benchmark.cc
@@ -0,0 +1,158 @@
+// Copyright (C) 2019 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.
+
+#include <random>
+
+#include <benchmark/benchmark.h>
+
+#include "src/trace_processor/db/row_map.h"
+
+using perfetto::trace_processor::BitVector;
+using perfetto::trace_processor::RowMap;
+
+namespace {
+
+static constexpr uint32_t kPoolSize = 100000;
+static constexpr uint32_t kSize = 123456;
+
+std::vector<uint32_t> CreateRandomIndexVector(uint32_t size, uint32_t mod) {
+ static constexpr uint32_t kRandomSeed = 476;
+ std::minstd_rand0 rnd_engine(kRandomSeed);
+ std::vector<uint32_t> rows(size);
+ for (uint32_t i = 0; i < size; ++i) {
+ rows[i] = rnd_engine() % mod;
+ }
+ return rows;
+}
+
+BitVector CreateRandomBitVector(uint32_t size) {
+ static constexpr uint32_t kRandomSeed = 42;
+ std::minstd_rand0 rnd_engine(kRandomSeed);
+ BitVector bv;
+ for (uint32_t i = 0; i < size; ++i) {
+ if (rnd_engine() % 2) {
+ bv.AppendTrue();
+ } else {
+ bv.AppendFalse();
+ }
+ }
+ return bv;
+}
+
+} // namespace
+
+static void BM_RowMapBitVectorGet(benchmark::State& state) {
+ RowMap rm(CreateRandomBitVector(kSize));
+ auto pool_vec = CreateRandomIndexVector(kPoolSize, rm.size());
+
+ uint32_t pool_idx = 0;
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(rm.Get(pool_vec[pool_idx]));
+ pool_idx = (pool_idx + 1) % kPoolSize;
+ }
+}
+BENCHMARK(BM_RowMapBitVectorGet);
+
+static void BM_RowMapIndexVectorGet(benchmark::State& state) {
+ RowMap rm(CreateRandomIndexVector(kSize, kSize));
+ auto pool_vec = CreateRandomIndexVector(kPoolSize, kSize);
+
+ uint32_t pool_idx = 0;
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(rm.Get(pool_vec[pool_idx]));
+ pool_idx = (pool_idx + 1) % kPoolSize;
+ }
+}
+BENCHMARK(BM_RowMapIndexVectorGet);
+
+// TODO(lalitm): add benchmarks for IndexOf after BitVector is made faster.
+// We can't add them right now because they are just too slow to run.
+
+static void BM_RowMapBitVectorAdd(benchmark::State& state) {
+ auto pool_vec = CreateRandomIndexVector(kPoolSize, kSize);
+
+ RowMap rm(BitVector{});
+ uint32_t pool_idx = 0;
+ for (auto _ : state) {
+ rm.Add(pool_vec[pool_idx]);
+ pool_idx = (pool_idx + 1) % kPoolSize;
+ benchmark::ClobberMemory();
+ }
+}
+BENCHMARK(BM_RowMapBitVectorAdd);
+
+static void BM_RowMapIndexVectorAdd(benchmark::State& state) {
+ auto pool_vec = CreateRandomIndexVector(kPoolSize, kSize);
+
+ RowMap rm(std::vector<uint32_t>{});
+ uint32_t pool_idx = 0;
+ for (auto _ : state) {
+ rm.Add(pool_vec[pool_idx]);
+ pool_idx = (pool_idx + 1) % kPoolSize;
+ benchmark::ClobberMemory();
+ }
+}
+BENCHMARK(BM_RowMapIndexVectorAdd);
+
+static void BM_RowMapBvSelectBv(benchmark::State& state) {
+ RowMap rm(CreateRandomBitVector(kSize));
+ RowMap selector(CreateRandomBitVector(rm.size()));
+
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(rm.SelectRows(selector));
+ }
+}
+BENCHMARK(BM_RowMapBvSelectBv);
+
+// TODO(lalitm): add benchmarks for BvSelectIv after BitVector is made faster.
+// We can't add them right now because they are just too slow to run.
+
+static void BM_RowMapIvSelectBv(benchmark::State& state) {
+ RowMap rm(CreateRandomIndexVector(kSize, kSize));
+ RowMap selector(CreateRandomBitVector(rm.size()));
+
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(rm.SelectRows(selector));
+ }
+}
+BENCHMARK(BM_RowMapIvSelectBv);
+
+static void BM_RowMapIvSelectIv(benchmark::State& state) {
+ RowMap rm(CreateRandomIndexVector(kSize, kSize));
+ RowMap selector(CreateRandomIndexVector(rm.size(), rm.size()));
+
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(rm.SelectRows(selector));
+ }
+}
+BENCHMARK(BM_RowMapIvSelectIv);
+
+static void BM_RowMapBvSelectSingleRow(benchmark::State& state) {
+ // This benchmark tests the performance of selecting just a single
+ // row of a RowMap. We specially test this case as it occurs on every join
+ // based on id originating from SQLite; nested subqueries will be performed
+ // on the id column and will select just a single row.
+ RowMap rm(CreateRandomBitVector(kSize));
+
+ static constexpr uint32_t kRandomSeed = 123;
+ std::minstd_rand0 rnd_engine(kRandomSeed);
+ BitVector bv(rm.size(), false);
+ bv.Set(rnd_engine() % bv.size());
+ RowMap selector(std::move(bv));
+
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(rm.SelectRows(selector));
+ }
+}
+BENCHMARK(BM_RowMapBvSelectSingleRow);
diff --git a/src/trace_processor/db/row_map_unittest.cc b/src/trace_processor/db/row_map_unittest.cc
index 7e375ac..719b533 100644
--- a/src/trace_processor/db/row_map_unittest.cc
+++ b/src/trace_processor/db/row_map_unittest.cc
@@ -27,12 +27,12 @@
std::shared_ptr<RowMap> BitVectorRowMap() {
BitVector bv;
- bv.Append(true);
- bv.Append(false);
- bv.Append(true);
- bv.Append(true);
- bv.Append(false);
- bv.Append(true);
+ bv.AppendTrue();
+ bv.AppendFalse();
+ bv.AppendTrue();
+ bv.AppendTrue();
+ bv.AppendFalse();
+ bv.AppendTrue();
return std::shared_ptr<RowMap>(new RowMap(std::move(bv)));
}
@@ -70,10 +70,10 @@
RowMap row_map = GetParam()->Copy();
BitVector picker_bv;
- picker_bv.Append(true);
- picker_bv.Append(false);
- picker_bv.Append(false);
- picker_bv.Append(true);
+ picker_bv.AppendTrue();
+ picker_bv.AppendFalse();
+ picker_bv.AppendFalse();
+ picker_bv.AppendTrue();
RowMap picker(std::move(picker_bv));
auto res = row_map.SelectRows(picker);
diff --git a/src/trace_processor/db/sparse_vector.h b/src/trace_processor/db/sparse_vector.h
index 12a2a60..c2472ac 100644
--- a/src/trace_processor/db/sparse_vector.h
+++ b/src/trace_processor/db/sparse_vector.h
@@ -51,11 +51,11 @@
// Adds the given value to the SparseVector.
void Append(T val) {
data_.emplace_back(val);
- valid_.Append(true);
+ valid_.AppendTrue();
}
// Adds a null value to the SparseVector.
- void AppendNull() { valid_.Append(false); }
+ void AppendNull() { valid_.AppendFalse(); }
// Adds the given optional value to the SparseVector.
void Append(base::Optional<T> val) {
@@ -76,7 +76,7 @@
data_[data_idx] = val;
} else {
data_.insert(data_.begin() + static_cast<ptrdiff_t>(data_idx), val);
- valid_.Set(idx, true);
+ valid_.Set(idx);
}
}
diff --git a/src/trace_processor/db/sparse_vector_benchmark.cc b/src/trace_processor/db/sparse_vector_benchmark.cc
new file mode 100644
index 0000000..f9403dc
--- /dev/null
+++ b/src/trace_processor/db/sparse_vector_benchmark.cc
@@ -0,0 +1,39 @@
+// Copyright (C) 2019 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.
+
+#include <random>
+
+#include <benchmark/benchmark.h>
+
+#include "src/trace_processor/db/sparse_vector.h"
+
+static void BM_SparseVectorAppend(benchmark::State& state) {
+ static constexpr uint32_t kPoolSize = 123456;
+ std::vector<uint8_t> data_pool(kPoolSize);
+
+ static constexpr uint32_t kRandomSeed = 42;
+ std::minstd_rand0 rnd_engine(kRandomSeed);
+ for (uint32_t i = 0; i < kPoolSize; ++i) {
+ data_pool[i] = rnd_engine() % std::numeric_limits<uint8_t>::max();
+ }
+
+ perfetto::trace_processor::SparseVector<uint8_t> sv;
+ uint32_t pool_idx = 0;
+ for (auto _ : state) {
+ sv.Append(data_pool[pool_idx]);
+ pool_idx = (pool_idx + 1) % kPoolSize;
+ benchmark::ClobberMemory();
+ }
+}
+BENCHMARK(BM_SparseVectorAppend);
diff --git a/src/trace_processor/export_json.cc b/src/trace_processor/export_json.cc
index fd5ea07..2ff1f6e 100644
--- a/src/trace_processor/export_json.cc
+++ b/src/trace_processor/export_json.cc
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#include "perfetto/base/build_config.h"
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+
#include <inttypes.h>
#include <json/reader.h>
#include <json/value.h>
@@ -848,3 +851,5 @@
} // namespace json
} // namespace trace_processor
} // namespace perfetto
+
+#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
diff --git a/src/trace_processor/fuchsia_trace_parser.cc b/src/trace_processor/fuchsia_trace_parser.cc
index 5313dd3..9afcfe8 100644
--- a/src/trace_processor/fuchsia_trace_parser.cc
+++ b/src/trace_processor/fuchsia_trace_parser.cc
@@ -197,6 +197,9 @@
UniqueTid utid =
procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
static_cast<uint32_t>(tinfo.pid));
+ // TODO(lalitm): make use of this track id.
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ perfetto::base::ignore_result(track_id);
RowId row = context_->event_tracker->PushInstant(ts, name, 0, utid,
RefType::kRefUtid);
for (const Arg& arg : args) {
@@ -266,6 +269,9 @@
UniqueTid utid =
procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
static_cast<uint32_t>(tinfo.pid));
+ // TODO(lalitm): make use of this track id.
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ perfetto::base::ignore_result(track_id);
slices->Begin(ts, utid, RefType::kRefUtid, cat, name);
break;
}
@@ -273,6 +279,9 @@
UniqueTid utid =
procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
static_cast<uint32_t>(tinfo.pid));
+ // TODO(lalitm): make use of this track id.
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ perfetto::base::ignore_result(track_id);
// TODO(b/131181693): |cat| and |name| are not passed here so that
// if two slices end at the same timestep, the slices get closed in
// the correct order regardless of which end event is processed first.
@@ -285,6 +294,9 @@
UniqueTid utid =
procs->UpdateThread(static_cast<uint32_t>(tinfo.tid),
static_cast<uint32_t>(tinfo.pid));
+ // TODO(lalitm): make use of this track id.
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ perfetto::base::ignore_result(track_id);
slices->Scoped(ts, utid, RefType::kRefUtid, cat, name, end_ts - ts);
break;
}
diff --git a/src/trace_processor/json_trace_parser.cc b/src/trace_processor/json_trace_parser.cc
index 5dc2816..1f0dbb4 100644
--- a/src/trace_processor/json_trace_parser.cc
+++ b/src/trace_processor/json_trace_parser.cc
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#include "perfetto/base/build_config.h"
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+
#include "src/trace_processor/json_trace_parser.h"
#include <inttypes.h>
@@ -23,7 +26,6 @@
#include <limits>
#include <string>
-#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/string_view.h"
#include "perfetto/ext/base/utils.h"
@@ -31,10 +33,7 @@
#include "src/trace_processor/process_tracker.h"
#include "src/trace_processor/slice_tracker.h"
#include "src/trace_processor/trace_processor_context.h"
-
-#if !PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
-#error JSON parsing and exporting is not supported in this build configuration
-#endif
+#include "src/trace_processor/track_tracker.h"
namespace perfetto {
namespace trace_processor {
@@ -88,10 +87,16 @@
switch (phase) {
case 'B': { // TRACE_EVENT_BEGIN.
+ // TODO(lalitm): make use of this track id.
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ perfetto::base::ignore_result(track_id);
slice_tracker->Begin(timestamp, utid, RefType::kRefUtid, cat_id, name_id);
break;
}
case 'E': { // TRACE_EVENT_END.
+ // TODO(lalitm): make use of this track id.
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ perfetto::base::ignore_result(track_id);
slice_tracker->End(timestamp, utid, RefType::kRefUtid, cat_id, name_id);
break;
}
@@ -100,6 +105,9 @@
json_trace_utils::CoerceToNs(value["dur"]);
if (!opt_dur.has_value())
return;
+ // TODO(lalitm): make use of this track id.
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ perfetto::base::ignore_result(track_id);
slice_tracker->Scoped(timestamp, utid, RefType::kRefUtid, cat_id, name_id,
opt_dur.value());
break;
@@ -124,3 +132,5 @@
} // namespace trace_processor
} // namespace perfetto
+
+#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
diff --git a/src/trace_processor/json_trace_tokenizer.cc b/src/trace_processor/json_trace_tokenizer.cc
index 274d908..120cc69 100644
--- a/src/trace_processor/json_trace_tokenizer.cc
+++ b/src/trace_processor/json_trace_tokenizer.cc
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#include "perfetto/base/build_config.h"
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+
#include "src/trace_processor/json_trace_tokenizer.h"
#include <json/reader.h>
@@ -147,3 +150,5 @@
} // namespace trace_processor
} // namespace perfetto
+
+#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
diff --git a/src/trace_processor/json_trace_utils.cc b/src/trace_processor/json_trace_utils.cc
index 0d6ba70..c4db237 100644
--- a/src/trace_processor/json_trace_utils.cc
+++ b/src/trace_processor/json_trace_utils.cc
@@ -14,17 +14,14 @@
* limitations under the License.
*/
+#include "perfetto/base/build_config.h"
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+
#include "src/trace_processor/json_trace_utils.h"
#include <json/value.h>
#include <limits>
-#include "perfetto/base/build_config.h"
-
-#if !PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
-#error JSON parsing and exporting is not supported in this build configuration
-#endif
-
namespace perfetto {
namespace trace_processor {
namespace json_trace_utils {
@@ -83,3 +80,5 @@
} // namespace json_trace_utils
} // namespace trace_processor
} // namespace perfetto
+
+#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
diff --git a/src/trace_processor/proto_incremental_state.h b/src/trace_processor/proto_incremental_state.h
index 408b2fd..837325c 100644
--- a/src/trace_processor/proto_incremental_state.h
+++ b/src/trace_processor/proto_incremental_state.h
@@ -82,6 +82,11 @@
} // namespace proto_incremental_state_internal
+struct DefaultFieldName;
+struct BuildIdFieldName;
+struct MappingPathsFieldName;
+struct FunctionNamesFieldName;
+
// Stores per-packet-sequence incremental state during trace parsing, such as
// reference timestamps for delta timestamp calculation and interned messages.
class ProtoIncrementalState {
@@ -161,7 +166,9 @@
int32_t pid() const { return pid_; }
int32_t tid() const { return tid_; }
- template <typename MessageType>
+ // Use DefaultFieldName only if there is a single field in InternedData of
+ // the MessageType.
+ template <typename MessageType, typename FieldName = DefaultFieldName>
InternedDataMap<MessageType>* GetInternedDataMap();
private:
@@ -191,7 +198,9 @@
InternedDataMap<protos::pbzero::DebugAnnotationName>
debug_annotation_names_;
InternedDataMap<protos::pbzero::SourceLocation> source_locations_;
- InternedDataMap<protos::pbzero::InternedString> interned_strings_;
+ InternedDataMap<protos::pbzero::InternedString> build_ids_;
+ InternedDataMap<protos::pbzero::InternedString> mapping_paths_;
+ InternedDataMap<protos::pbzero::InternedString> function_names_;
InternedDataMap<protos::pbzero::LogMessageBody> interned_log_messages_;
InternedDataMap<protos::pbzero::Mapping> mappings_;
InternedDataMap<protos::pbzero::Frame> frames_;
@@ -253,9 +262,25 @@
template <>
inline ProtoIncrementalState::InternedDataMap<protos::pbzero::InternedString>*
+ProtoIncrementalState::PacketSequenceState::
+ GetInternedDataMap<protos::pbzero::InternedString, BuildIdFieldName>() {
+ return &build_ids_;
+}
+
+template <>
+inline ProtoIncrementalState::InternedDataMap<protos::pbzero::InternedString>*
ProtoIncrementalState::PacketSequenceState::GetInternedDataMap<
- protos::pbzero::InternedString>() {
- return &interned_strings_;
+ protos::pbzero::InternedString,
+ MappingPathsFieldName>() {
+ return &mapping_paths_;
+}
+
+template <>
+inline ProtoIncrementalState::InternedDataMap<protos::pbzero::InternedString>*
+ProtoIncrementalState::PacketSequenceState::GetInternedDataMap<
+ protos::pbzero::InternedString,
+ FunctionNamesFieldName>() {
+ return &function_names_;
}
template <>
diff --git a/src/trace_processor/proto_trace_parser.cc b/src/trace_processor/proto_trace_parser.cc
index d8c972a..c2410d5 100644
--- a/src/trace_processor/proto_trace_parser.cc
+++ b/src/trace_processor/proto_trace_parser.cc
@@ -36,6 +36,8 @@
#include "src/trace_processor/heap_profile_tracker.h"
#include "src/trace_processor/metadata.h"
#include "src/trace_processor/process_tracker.h"
+#include "src/trace_processor/proto_incremental_state.h"
+#include "src/trace_processor/stack_profile_tracker.h"
#include "src/trace_processor/syscall_tracker.h"
#include "src/trace_processor/systrace_parser.h"
#include "src/trace_processor/trace_processor_context.h"
@@ -132,9 +134,24 @@
: seq_state_(seq_state) {}
base::Optional<base::StringView> GetString(
- StackProfileTracker::SourceStringId iid) const override {
- auto* map =
- seq_state_->GetInternedDataMap<protos::pbzero::InternedString>();
+ StackProfileTracker::SourceStringId iid,
+ StackProfileTracker::InternedStringType type) const override {
+ ProtoIncrementalState::InternedDataMap<protos::pbzero::InternedString>*
+ map = nullptr;
+ switch (type) {
+ case StackProfileTracker::InternedStringType::kBuildId:
+ map = seq_state_->GetInternedDataMap<protos::pbzero::InternedString,
+ BuildIdFieldName>();
+ break;
+ case StackProfileTracker::InternedStringType::kFunctionName:
+ map = seq_state_->GetInternedDataMap<protos::pbzero::InternedString,
+ FunctionNamesFieldName>();
+ break;
+ case StackProfileTracker::InternedStringType::kMappingPath:
+ map = seq_state_->GetInternedDataMap<protos::pbzero::InternedString,
+ MappingPathsFieldName>();
+ break;
+ }
auto it = map->find(iid);
if (it == map->end()) {
PERFETTO_DLOG("Did not find string %" PRIu64 " in %zu elems", iid,
@@ -1771,6 +1788,9 @@
switch (static_cast<char>(phase)) {
case 'B': { // TRACE_EVENT_PHASE_BEGIN.
+ // TODO(lalitm): make use of this track id.
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ perfetto::base::ignore_result(track_id);
auto opt_slice_id = slice_tracker->Begin(
ts, utid, RefType::kRefUtid, category_id, name_id, args_callback);
if (opt_slice_id.has_value()) {
@@ -1785,6 +1805,9 @@
break;
}
case 'E': { // TRACE_EVENT_PHASE_END.
+ // TODO(lalitm): make use of this track id.
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ perfetto::base::ignore_result(track_id);
auto opt_slice_id = slice_tracker->End(
ts, utid, RefType::kRefUtid, category_id, name_id, args_callback);
if (opt_slice_id.has_value()) {
@@ -1798,6 +1821,9 @@
auto duration_ns = legacy_event.duration_us() * 1000;
if (duration_ns < 0)
return;
+ // TODO(lalitm): make use of this track id.
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ perfetto::base::ignore_result(track_id);
auto opt_slice_id =
slice_tracker->Scoped(ts, utid, RefType::kRefUtid, category_id,
name_id, duration_ns, args_callback);
@@ -1823,6 +1849,9 @@
switch (legacy_event.instant_event_scope()) {
case LegacyEvent::SCOPE_UNSPECIFIED:
case LegacyEvent::SCOPE_THREAD: {
+ // TODO(lalitm): make use of this track id.
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ perfetto::base::ignore_result(track_id);
auto opt_slice_id =
slice_tracker->Scoped(ts, utid, RefType::kRefUtid, category_id,
name_id, duration_ns, args_callback);
@@ -2436,6 +2465,9 @@
sprintf(fallback, "Event %d", eid);
name_id = context_->storage->InternString(fallback);
}
+ // TODO(lalitm): make use of this track id.
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ perfetto::base::ignore_result(track_id);
context_->slice_tracker->Scoped(ts, utid, RefType::kRefUtid, cat_id,
name_id, event.event_duration_ns());
} else if (event.has_counter_id()) {
diff --git a/src/trace_processor/proto_trace_tokenizer.cc b/src/trace_processor/proto_trace_tokenizer.cc
index 2714db9..e093ae8 100644
--- a/src/trace_processor/proto_trace_tokenizer.cc
+++ b/src/trace_processor/proto_trace_tokenizer.cc
@@ -28,6 +28,7 @@
#include "src/trace_processor/clock_tracker.h"
#include "src/trace_processor/event_tracker.h"
#include "src/trace_processor/process_tracker.h"
+#include "src/trace_processor/proto_incremental_state.h"
#include "src/trace_processor/stats.h"
#include "src/trace_processor/trace_blob_view.h"
#include "src/trace_processor/trace_sorter.h"
@@ -58,7 +59,7 @@
constexpr uint8_t kTracePacketTag =
MakeTagLengthDelimited(protos::pbzero::Trace::kPacketFieldNumber);
-template <typename MessageType>
+template <typename MessageType, typename FieldName = DefaultFieldName>
void InternMessage(TraceProcessorContext* context,
ProtoIncrementalState::PacketSequenceState* state,
TraceBlobView message) {
@@ -77,7 +78,7 @@
}
iid = field.as_uint64();
- auto res = state->GetInternedDataMap<MessageType>()->emplace(
+ auto res = state->GetInternedDataMap<MessageType, FieldName>()->emplace(
iid,
ProtoIncrementalState::InternedDataView<MessageType>(std::move(message)));
// If a message with this ID is already interned, its data should not have
@@ -424,17 +425,17 @@
for (auto it = interned_data_decoder.build_ids(); it; ++it) {
size_t offset = interned_data.offset_of(it->data());
- InternMessage<protos::pbzero::InternedString>(
+ InternMessage<protos::pbzero::InternedString, BuildIdFieldName>(
context_, state, interned_data.slice(offset, it->size()));
}
for (auto it = interned_data_decoder.mapping_paths(); it; ++it) {
size_t offset = interned_data.offset_of(it->data());
- InternMessage<protos::pbzero::InternedString>(
+ InternMessage<protos::pbzero::InternedString, MappingPathsFieldName>(
context_, state, interned_data.slice(offset, it->size()));
}
for (auto it = interned_data_decoder.function_names(); it; ++it) {
size_t offset = interned_data.offset_of(it->data());
- InternMessage<protos::pbzero::InternedString>(
+ InternMessage<protos::pbzero::InternedString, FunctionNamesFieldName>(
context_, state, interned_data.slice(offset, it->size()));
}
diff --git a/src/trace_processor/slice_tracker.cc b/src/trace_processor/slice_tracker.cc
index 70083da..500d23c 100644
--- a/src/trace_processor/slice_tracker.cc
+++ b/src/trace_processor/slice_tracker.cc
@@ -23,6 +23,7 @@
#include "src/trace_processor/slice_tracker.h"
#include "src/trace_processor/trace_processor_context.h"
#include "src/trace_processor/trace_storage.h"
+#include "src/trace_processor/track_tracker.h"
namespace perfetto {
namespace trace_processor {
@@ -45,6 +46,9 @@
UniqueTid utid =
context_->process_tracker->UpdateThread(ftrace_tid, atrace_tgid);
ftrace_to_atrace_tgid_[ftrace_tid] = atrace_tgid;
+ // TODO(lalitm): make use of this track id.
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ perfetto::base::ignore_result(track_id);
return Begin(timestamp, utid, RefType::kRefUtid, category, name);
}
@@ -137,6 +141,9 @@
}
UniqueTid utid =
context_->process_tracker->UpdateThread(ftrace_tid, actual_tgid);
+ // TODO(lalitm): make use of this track id.
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ perfetto::base::ignore_result(track_id);
return End(timestamp, utid, RefType::kRefUtid);
}
diff --git a/src/trace_processor/stack_profile_tracker.cc b/src/trace_processor/stack_profile_tracker.cc
index 8d0a157..be518f3 100644
--- a/src/trace_processor/stack_profile_tracker.cc
+++ b/src/trace_processor/stack_profile_tracker.cc
@@ -40,13 +40,15 @@
const InternLookup* intern_lookup) {
std::string path;
for (SourceStringId str_id : mapping.name_ids) {
- auto opt_str = FindString(str_id, intern_lookup);
+ auto opt_str =
+ FindString(str_id, intern_lookup, InternedStringType::kMappingPath);
if (!opt_str)
break;
path += "/" + *opt_str;
}
- auto opt_build_id = FindAndInternString(mapping.build_id, intern_lookup);
+ auto opt_build_id = FindAndInternString(mapping.build_id, intern_lookup,
+ InternedStringType::kBuildId);
if (!opt_build_id) {
context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
PERFETTO_DFATAL("Invalid string.");
@@ -86,7 +88,8 @@
int64_t StackProfileTracker::AddFrame(SourceFrameId id,
const SourceFrame& frame,
const InternLookup* intern_lookup) {
- auto opt_str_id = FindAndInternString(frame.name_id, intern_lookup);
+ auto opt_str_id = FindAndInternString(frame.name_id, intern_lookup,
+ InternedStringType::kFunctionName);
if (!opt_str_id) {
context_->storage->IncrementStats(stats::stackprofile_invalid_string_id);
PERFETTO_DFATAL("Invalid string.");
@@ -170,11 +173,12 @@
base::Optional<StringId> StackProfileTracker::FindAndInternString(
SourceStringId id,
- const InternLookup* intern_lookup) {
+ const InternLookup* intern_lookup,
+ StackProfileTracker::InternedStringType type) {
if (id == 0)
return empty_;
- auto opt_str = FindString(id, intern_lookup);
+ auto opt_str = FindString(id, intern_lookup, type);
if (!opt_str)
return empty_;
@@ -183,18 +187,20 @@
base::Optional<std::string> StackProfileTracker::FindString(
SourceStringId id,
- const InternLookup* intern_lookup) {
+ const InternLookup* intern_lookup,
+ StackProfileTracker::InternedStringType type) {
if (id == 0)
return "";
auto it = string_map_.find(id);
if (it == string_map_.end()) {
if (intern_lookup) {
- auto str = intern_lookup->GetString(id);
+ auto str = intern_lookup->GetString(id, type);
if (!str) {
context_->storage->IncrementStats(
stats::stackprofile_invalid_string_id);
PERFETTO_DFATAL("Invalid string.");
+ return base::nullopt;
}
return str->ToStdString();
}
diff --git a/src/trace_processor/stack_profile_tracker.h b/src/trace_processor/stack_profile_tracker.h
index caf5b91..114865c 100644
--- a/src/trace_processor/stack_profile_tracker.h
+++ b/src/trace_processor/stack_profile_tracker.h
@@ -60,6 +60,12 @@
public:
using SourceStringId = uint64_t;
+ enum class InternedStringType {
+ kMappingPath,
+ kBuildId,
+ kFunctionName,
+ };
+
struct SourceMapping {
SourceStringId build_id = 0;
uint64_t exact_offset = 0;
@@ -98,7 +104,8 @@
virtual ~InternLookup();
virtual base::Optional<base::StringView> GetString(
- SourceStringId) const = 0;
+ SourceStringId,
+ InternedStringType) const = 0;
virtual base::Optional<SourceMapping> GetMapping(SourceMappingId) const = 0;
virtual base::Optional<SourceFrame> GetFrame(SourceFrameId) const = 0;
virtual base::Optional<SourceCallstack> GetCallstack(
@@ -133,9 +140,11 @@
// InternedData (for versions newer than Q).
base::Optional<StringId> FindAndInternString(
SourceStringId,
- const InternLookup* intern_lookup);
+ const InternLookup* intern_lookup,
+ InternedStringType type);
base::Optional<std::string> FindString(SourceStringId,
- const InternLookup* intern_lookup);
+ const InternLookup* intern_lookup,
+ InternedStringType type);
base::Optional<int64_t> FindMapping(SourceMappingId,
const InternLookup* intern_lookup);
base::Optional<int64_t> FindFrame(SourceFrameId,
diff --git a/src/trace_processor/syscall_tracker.h b/src/trace_processor/syscall_tracker.h
index 76523dd..4ad84a1 100644
--- a/src/trace_processor/syscall_tracker.h
+++ b/src/trace_processor/syscall_tracker.h
@@ -24,6 +24,7 @@
#include "src/trace_processor/slice_tracker.h"
#include "src/trace_processor/trace_processor_context.h"
#include "src/trace_processor/trace_storage.h"
+#include "src/trace_processor/track_tracker.h"
namespace perfetto {
namespace trace_processor {
@@ -50,6 +51,9 @@
void Enter(int64_t ts, UniqueTid utid, uint32_t syscall_num) {
StringId name = SyscallNumberToStringId(syscall_num);
if (!name.is_null()) {
+ // TODO(lalitm): make use of this track id.
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ perfetto::base::ignore_result(track_id);
context_->slice_tracker->Begin(ts, utid, RefType::kRefUtid, 0 /* cat */,
name);
}
@@ -58,6 +62,9 @@
void Exit(int64_t ts, UniqueTid utid, uint32_t syscall_num) {
StringId name = SyscallNumberToStringId(syscall_num);
if (!name.is_null()) {
+ // TODO(lalitm): make use of this track id.
+ TrackId track_id = context_->track_tracker->InternThreadTrack(utid);
+ perfetto::base::ignore_result(track_id);
context_->slice_tracker->End(ts, utid, RefType::kRefUtid, 0 /* cat */,
name);
}
diff --git a/src/trace_processor/syscall_tracker_unittest.cc b/src/trace_processor/syscall_tracker_unittest.cc
index fa5fce5..21861ec 100644
--- a/src/trace_processor/syscall_tracker_unittest.cc
+++ b/src/trace_processor/syscall_tracker_unittest.cc
@@ -52,6 +52,8 @@
public:
SyscallTrackerTest() {
context.storage.reset(new TraceStorage());
+ track_tracker = new TrackTracker(&context);
+ context.track_tracker.reset(track_tracker);
slice_tracker = new MockSliceTracker(&context);
context.slice_tracker.reset(slice_tracker);
context.syscall_tracker.reset(new SyscallTracker(&context));
@@ -60,6 +62,7 @@
protected:
TraceProcessorContext context;
MockSliceTracker* slice_tracker;
+ TrackTracker* track_tracker;
};
TEST_F(SyscallTrackerTest, ReportUnknownSyscalls) {
diff --git a/src/trace_processor/systrace_parser.cc b/src/trace_processor/systrace_parser.cc
index ec5d4c5..082dec5 100644
--- a/src/trace_processor/systrace_parser.cc
+++ b/src/trace_processor/systrace_parser.cc
@@ -20,6 +20,7 @@
#include "src/trace_processor/event_tracker.h"
#include "src/trace_processor/process_tracker.h"
#include "src/trace_processor/slice_tracker.h"
+#include "src/trace_processor/track_tracker.h"
namespace perfetto {
namespace trace_processor {
@@ -90,13 +91,21 @@
break;
}
- case 'S': {
- // Currently unsupported.
- break;
- }
-
+ case 'S':
case 'F': {
- // Currently unsupported.
+ StringId name_id = context_->storage->InternString(point.name);
+ int64_t cookie = static_cast<int64_t>(point.value);
+ UniquePid upid =
+ context_->process_tracker->GetOrCreateProcess(point.tgid);
+
+ TrackId track_id = context_->track_tracker->InternAndroidAsyncTrack(
+ name_id, upid, cookie);
+ if (point.phase == 'S') {
+ context_->slice_tracker->Begin(ts, track_id, RefType::kRefTrack, 0,
+ name_id);
+ } else {
+ context_->slice_tracker->End(ts, track_id, RefType::kRefTrack);
+ }
break;
}
diff --git a/src/trace_processor/systrace_parser.h b/src/trace_processor/systrace_parser.h
index f85e1dd..c8c3452 100644
--- a/src/trace_processor/systrace_parser.h
+++ b/src/trace_processor/systrace_parser.h
@@ -99,6 +99,11 @@
size_t len = str.size();
*out = {};
+ constexpr const char* kClockSyncPrefix = "trace_event_clock_sync:";
+ if (len >= strlen(kClockSyncPrefix) &&
+ strncmp(kClockSyncPrefix, s, strlen(kClockSyncPrefix)) == 0)
+ return SystraceParseResult::kUnsupported;
+
if (len < 2)
return SystraceParseResult::kFailure;
diff --git a/src/trace_processor/systrace_parser_unittest.cc b/src/trace_processor/systrace_parser_unittest.cc
index 5d6f32e..d1a1158 100644
--- a/src/trace_processor/systrace_parser_unittest.cc
+++ b/src/trace_processor/systrace_parser_unittest.cc
@@ -69,6 +69,13 @@
ASSERT_EQ(ParseSystraceTracePoint("F|123|foo|456", &result),
Result::kSuccess);
EXPECT_EQ(result, SystraceTracePoint::F(123, "foo", 456));
+
+ ASSERT_EQ(ParseSystraceTracePoint("trace_event_clock_sync: parent_ts=0.123\n",
+ &result),
+ Result::kUnsupported);
+ ASSERT_EQ(ParseSystraceTracePoint("trace_event_clock_sync: realtime_ts=123\n",
+ &result),
+ Result::kUnsupported);
}
} // namespace
diff --git a/src/trace_processor/tables/track_tables.h b/src/trace_processor/tables/track_tables.h
index fa142ae..76a4417 100644
--- a/src/trace_processor/tables/track_tables.h
+++ b/src/trace_processor/tables/track_tables.h
@@ -39,6 +39,13 @@
PERFETTO_TP_TABLE(PERFETTO_TP_PROCESS_TRACK_TABLE_DEF);
+#define PERFETTO_TP_THREAD_TRACK_TABLE_DEF(NAME, PARENT, C) \
+ NAME(ThreadTrackTable, "thread_track") \
+ PARENT(PERFETTO_TP_TRACK_TABLE_DEF, C) \
+ C(uint32_t, utid)
+
+PERFETTO_TP_TABLE(PERFETTO_TP_THREAD_TRACK_TABLE_DEF);
+
#define PERFETTO_TP_GPU_TRACK_DEF(NAME, PARENT, C) \
NAME(GpuTrackTable, "gpu_track") \
PARENT(PERFETTO_TP_TRACK_TABLE_DEF, C) \
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index 342613d..d8eb471 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -771,31 +771,34 @@
Usage: %s [OPTIONS] trace_file.pb
Options:
- -h, --help Prints this guide.
- -v, --version Prints the version of trace processor.
- -d, --debug Enable virtual table debugging.
- -W, --wide Prints interactive output with double column
- width.
- -p, --perf-file FILE Writes the time taken to ingest the trace and
- execute the queries to the given file. Only
- valid with -q or --run-metrics and the file
- will only be written if the execution
- is successful.
- -q, --query-file FILE Read and execute an SQL query from a file.
- -i, --interactive Starts interactive mode even after a query file
- is specified with -q or --run-metrics.
- -e, --export FILE Export the trace into a SQLite database.
- --run-metrics x,y,z Runs a comma separated list of metrics and
- prints the result as a TraceMetrics proto to
- stdout. The specified can either be in-built
- metrics or SQL/proto files of extension
- metrics.
- --metrics-output=[binary|text] Allows the output of --run-metrics to be
- specified in either proto binary or proto
- text format (default: text).
- --extra-metrics PATH Registers all SQL files at the given path to
- the trace processor and extends the builtin
- metrics proto with $PATH/metrics-ext.proto.)",
+ -h, --help Prints this guide.
+ -v, --version Prints the version of trace processor.
+ -d, --debug Enable virtual table debugging.
+ -W, --wide Prints interactive output with double
+ column width.
+ -p, --perf-file FILE Writes the time taken to ingest the trace
+ and execute the queries to the given file.
+ Only valid with -q or --run-metrics and
+ the file will only be written if the
+ execution is successful.
+ -q, --query-file FILE Read and execute an SQL query from a file.
+ -i, --interactive Starts interactive mode even after a query
+ file is specified with -q or
+ --run-metrics.
+ -e, --export FILE Export the trace into a SQLite database.
+ --run-metrics x,y,z Runs a comma separated list of metrics and
+ prints the result as a TraceMetrics proto
+ to stdout. The specified can either be
+ in-built metrics or SQL/proto files of
+ extension metrics.
+ --metrics-output=[binary|text|json] Allows the output of --run-metrics to be
+ specified in either proto binary, proto
+ text format or JSON format (default: proto
+ text).
+ --extra-metrics PATH Registers all SQL files at the given path
+ to the trace processor and extends the
+ builtin metrics proto with
+ $PATH/metrics-ext.proto.)",
argv[0]);
}
diff --git a/src/trace_processor/trace_storage.h b/src/trace_processor/trace_storage.h
index df9092b..0799eae 100644
--- a/src/trace_processor/trace_storage.h
+++ b/src/trace_processor/trace_storage.h
@@ -1174,6 +1174,13 @@
return &process_track_table_;
}
+ const tables::ThreadTrackTable& thread_track_table() const {
+ return thread_track_table_;
+ }
+ tables::ThreadTrackTable* mutable_thread_track_table() {
+ return &thread_track_table_;
+ }
+
const Slices& slices() const { return slices_; }
Slices* mutable_slices() { return &slices_; }
@@ -1311,6 +1318,7 @@
tables::TrackTable track_table_{&string_pool_, nullptr};
tables::GpuTrackTable gpu_track_table_{&string_pool_, &track_table_};
tables::ProcessTrackTable process_track_table_{&string_pool_, &track_table_};
+ tables::ThreadTrackTable thread_track_table_{&string_pool_, &track_table_};
// Metadata for gpu tracks.
GpuContexts gpu_contexts_;
diff --git a/src/trace_processor/track_tracker.cc b/src/trace_processor/track_tracker.cc
index 0a22369..cfbbf76 100644
--- a/src/trace_processor/track_tracker.cc
+++ b/src/trace_processor/track_tracker.cc
@@ -27,8 +27,22 @@
source_scope_key_(context->storage->InternString("source_scope")),
fuchsia_source_(context->storage->InternString("fuchsia")),
chrome_source_(context->storage->InternString("chrome")),
+ android_source_(context->storage->InternString("android")),
context_(context) {}
+TrackId TrackTracker::InternThreadTrack(UniqueTid utid) {
+ ThreadTrackTuple tuple{utid};
+ auto it = thread_tracks_.find(tuple);
+ if (it != thread_tracks_.end())
+ return it->second;
+
+ tables::ThreadTrackTable::Row row;
+ row.utid = utid;
+ auto id = context_->storage->mutable_thread_track_table()->Insert(row);
+ thread_tracks_[tuple] = id;
+ return id;
+}
+
TrackId TrackTracker::InternFuchsiaAsyncTrack(StringId name,
int64_t correlation_id) {
FuchsiaAsyncTrackTuple tuple{correlation_id};
@@ -95,5 +109,27 @@
return id;
}
+TrackId TrackTracker::InternAndroidAsyncTrack(StringId name,
+ UniquePid upid,
+ int64_t cookie) {
+ AndroidAsyncTrackTuple tuple{upid, cookie, name};
+
+ auto it = android_async_tracks_.find(tuple);
+ if (it != android_async_tracks_.end())
+ return it->second;
+
+ tables::ProcessTrackTable::Row row(name);
+ row.upid = upid;
+ auto id = context_->storage->mutable_process_track_table()->Insert(row);
+ android_async_tracks_[tuple] = id;
+
+ RowId row_id = TraceStorage::CreateRowId(TableId::kTrack, id);
+ context_->args_tracker->AddArg(row_id, source_key_, source_key_,
+ Variadic::String(android_source_));
+ context_->args_tracker->AddArg(row_id, source_id_key_, source_id_key_,
+ Variadic::Integer(cookie));
+ return id;
+}
+
} // namespace trace_processor
} // namespace perfetto
diff --git a/src/trace_processor/track_tracker.h b/src/trace_processor/track_tracker.h
index fc5f3de..8aa7ee8 100644
--- a/src/trace_processor/track_tracker.h
+++ b/src/trace_processor/track_tracker.h
@@ -28,6 +28,9 @@
public:
explicit TrackTracker(TraceProcessorContext*);
+ // Interns a thread track into the storage.
+ TrackId InternThreadTrack(UniqueTid utid);
+
// Interns a Fuchsia async track into the storage.
TrackId InternFuchsiaAsyncTrack(StringId name, int64_t correlation_id);
@@ -40,7 +43,18 @@
int64_t source_id,
StringId source_scope);
+ // Interns a Android async track into the storage.
+ TrackId InternAndroidAsyncTrack(StringId name, uint32_t upid, int64_t cookie);
+
private:
+ struct ThreadTrackTuple {
+ UniqueTid utid;
+
+ friend bool operator<(const ThreadTrackTuple& l,
+ const ThreadTrackTuple& r) {
+ return l.utid < r.utid;
+ }
+ };
struct FuchsiaAsyncTrackTuple {
int64_t correlation_id;
@@ -70,10 +84,23 @@
std::tie(r.source_id, r.upid, r.source_scope);
}
};
+ struct AndroidAsyncTrackTuple {
+ UniquePid upid;
+ int64_t cookie;
+ StringId name;
+ friend bool operator<(const AndroidAsyncTrackTuple& l,
+ const AndroidAsyncTrackTuple& r) {
+ return std::tie(l.upid, l.cookie, l.name) <
+ std::tie(r.upid, r.cookie, r.name);
+ }
+ };
+
+ std::map<ThreadTrackTuple, TrackId> thread_tracks_;
std::map<FuchsiaAsyncTrackTuple, TrackId> fuchsia_async_tracks_;
std::map<GpuTrackTuple, TrackId> gpu_tracks_;
std::map<ChromeTrackTuple, TrackId> chrome_tracks_;
+ std::map<AndroidAsyncTrackTuple, TrackId> android_async_tracks_;
StringId source_key_ = 0;
StringId source_id_key_ = 0;
@@ -81,6 +108,7 @@
StringId fuchsia_source_ = 0;
StringId chrome_source_ = 0;
+ StringId android_source_ = 0;
TraceProcessorContext* const context_;
};
diff --git a/src/traced/service/builtin_producer.cc b/src/traced/service/builtin_producer.cc
index b11da94..16df2e1 100644
--- a/src/traced/service/builtin_producer.cc
+++ b/src/traced/service/builtin_producer.cc
@@ -66,13 +66,17 @@
metatrace_dsd.set_will_notify_on_stop(true);
endpoint_->RegisterDataSource(metatrace_dsd);
- DataSourceDescriptor lazy_heapprofd_dsd;
- lazy_heapprofd_dsd.set_name(kHeapprofdDataSourceName);
- endpoint_->RegisterDataSource(lazy_heapprofd_dsd);
+ {
+ DataSourceDescriptor lazy_heapprofd_dsd;
+ lazy_heapprofd_dsd.set_name(kHeapprofdDataSourceName);
+ endpoint_->RegisterDataSource(lazy_heapprofd_dsd);
+ }
- DataSourceDescriptor lazy_java_hprof_dsd;
- lazy_heapprofd_dsd.set_name(kJavaHprofDataSourceName);
- endpoint_->RegisterDataSource(lazy_java_hprof_dsd);
+ {
+ DataSourceDescriptor lazy_java_hprof_dsd;
+ lazy_java_hprof_dsd.set_name(kJavaHprofDataSourceName);
+ endpoint_->RegisterDataSource(lazy_java_hprof_dsd);
+ }
}
void BuiltinProducer::SetupDataSource(DataSourceInstanceID ds_id,
diff --git a/test/trace_processor/android_async_slice.textproto b/test/trace_processor/android_async_slice.textproto
new file mode 100644
index 0000000..787d929
--- /dev/null
+++ b/test/trace_processor/android_async_slice.textproto
@@ -0,0 +1,26 @@
+packet {
+ ftrace_events {
+ cpu: 3
+ event {
+ timestamp: 74289018336
+ pid: 4064
+ print {
+ ip: 18446743562018522420
+ buf: "S|1204|launching: com.android.chrome|0\n"
+ }
+ }
+ }
+}
+packet {
+ ftrace_events {
+ cpu: 2
+ event {
+ timestamp: 74662603008
+ pid: 1257
+ print {
+ ip: 18446743562018522420
+ buf: "F|1204|launching: com.android.chrome|0\n"
+ }
+ }
+ }
+}
diff --git a/test/trace_processor/index b/test/trace_processor/index
index 83d0fe0..7bb14f4 100644
--- a/test/trace_processor/index
+++ b/test/trace_processor/index
@@ -69,6 +69,9 @@
# Power rails
../data/power_rails.pb power_rails.sql power_rails_power_rails.out
+# Android userspace async slices
+android_async_slice.textproto process_track_slices.sql process_track_slices_android_async_slice.out
+
# The below tests check the autogenerated tables.
# Span join
diff --git a/test/trace_processor/process_track_slices.sql b/test/trace_processor/process_track_slices.sql
new file mode 100644
index 0000000..b84557f
--- /dev/null
+++ b/test/trace_processor/process_track_slices.sql
@@ -0,0 +1,9 @@
+SELECT
+ ts,
+ dur,
+ pid,
+ slice.name as slice_name,
+ process_track.name as track_name
+FROM slice
+INNER JOIN process_track ON slice.ref = process_track.id
+INNER JOIN process USING (upid)
diff --git a/test/trace_processor/process_track_slices_android_async_slice.out b/test/trace_processor/process_track_slices_android_async_slice.out
new file mode 100644
index 0000000..a30a8f0
--- /dev/null
+++ b/test/trace_processor/process_track_slices_android_async_slice.out
@@ -0,0 +1,2 @@
+"ts","dur","pid","slice_name","track_name"
+74289018336,373584672,1204,"launching: com.android.chrome","launching: com.android.chrome"
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 8bfe686..28360fa 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -82,6 +82,7 @@
target_host_supported = [
'//protos/perfetto/trace:perfetto_trace_protos',
+ '//:libperfetto',
]
target_host_only = [
diff --git a/tools/gen_bazel b/tools/gen_bazel
index a8643ec..47fc90e 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -66,8 +66,10 @@
] + public_targets
# Root proto targets (to force discovery of intermediate proto targets).
+# These targets are marked public.
proto_targets = [
'//protos/perfetto/trace:merged_trace',
+ '//protos/perfetto/config:merged_config',
'//protos/perfetto/metrics:lite',
'//protos/perfetto/trace:lite',
'//protos/perfetto/config:lite',
@@ -230,6 +232,9 @@
deps = [':' + get_sources_label(x) for x in target.proto_deps]
sources_label.deps = sorted(deps)
+ if target.name in proto_targets:
+ sources_label.visibility = ['//visibility:public']
+
return [plugin_label, sources_label]
diff --git a/tools/heap_profile b/tools/heap_profile
index 3938b64..5680a68 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -22,6 +22,7 @@
import atexit
import hashlib
import os
+import shutil
import signal
import subprocess
import sys
@@ -342,6 +343,7 @@
if os.path.lexists(symlink_path):
os.unlink(symlink_path)
os.symlink(profile_path, symlink_path)
+ shutil.copyfile('/tmp/profile', os.path.join(profile_path, 'raw-trace'))
print("Wrote profiles to {} (symlink {})".format(profile_path, symlink_path))
print("These can be viewed using pprof. Googlers: head to pprof/ and "
diff --git a/tools/trace_to_text/main.cc b/tools/trace_to_text/main.cc
index 225bf70..9377bb6 100644
--- a/tools/trace_to_text/main.cc
+++ b/tools/trace_to_text/main.cc
@@ -20,10 +20,11 @@
#include <vector>
#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "tools/trace_to_text/symbolize_profile.h"
#include "tools/trace_to_text/trace_to_profile.h"
#include "tools/trace_to_text/trace_to_systrace.h"
#include "tools/trace_to_text/trace_to_text.h"
-#include "tools/trace_to_text/symbolize_profile.h"
#if PERFETTO_BUILDFLAG(PERFETTO_VERSION_GEN)
#include "perfetto_version.gen.h"
@@ -35,18 +36,29 @@
int Usage(const char* argv0) {
printf(
- "Usage: %s systrace|json|text|profile [--truncate start|end] [trace.pb] "
+ "Usage: %s systrace|json|text|profile [--pid PID] "
+ "[--timestamps TIMESTAMP1,TIMESTAMP2,...] "
+ "[--truncate start|end] [trace.pb] "
"[trace.txt]\n",
argv0);
return 1;
}
+uint64_t StringToUint64OrDie(const char* str) {
+ char* end;
+ uint64_t number = static_cast<uint64_t>(strtoll(str, &end, 10));
+ if (*end != '\0')
+ PERFETTO_FATAL("Invalid %s. Expected decimal integer.", str);
+ return number;
+}
} // namespace
int main(int argc, char** argv) {
std::vector<const char*> positional_args;
perfetto::trace_to_text::Keep truncate_keep =
perfetto::trace_to_text::Keep::kAll;
+ uint64_t pid = 0;
+ std::vector<uint64_t> timestamps;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0) {
printf("%s\n", PERFETTO_GET_GIT_REVISION());
@@ -64,6 +76,16 @@
"start of the trace.");
return Usage(argv[0]);
}
+ } else if (i <= argc && strcmp(argv[i], "--pid") == 0) {
+ i++;
+ pid = StringToUint64OrDie(argv[i]);
+ } else if (i <= argc && strcmp(argv[i], "--timestamps") == 0) {
+ i++;
+ std::vector<std::string> ts_strings =
+ perfetto::base::SplitString(argv[i], ",");
+ for (const auto& ts : ts_strings) {
+ timestamps.emplace_back(StringToUint64OrDie(ts.c_str()));
+ }
} else {
positional_args.push_back(argv[i]);
}
@@ -121,8 +143,10 @@
if (format == "text")
return perfetto::trace_to_text::TraceToText(input_stream, output_stream);
- if (format == "profile")
- return perfetto::trace_to_text::TraceToProfile(input_stream, output_stream);
+ if (format == "profile") {
+ return perfetto::trace_to_text::TraceToProfile(input_stream, output_stream,
+ pid, timestamps);
+ }
if (format == "symbolize")
return perfetto::trace_to_text::SymbolizeProfile(input_stream,
diff --git a/tools/trace_to_text/pprof_builder.cc b/tools/trace_to_text/pprof_builder.cc
index 5d56cf9..1cea63b 100644
--- a/tools/trace_to_text/pprof_builder.cc
+++ b/tools/trace_to_text/pprof_builder.cc
@@ -85,30 +85,14 @@
using Iterator = trace_processor::TraceProcessor::Iterator;
constexpr const char* kQueryProfiles =
- "select distinct hpa.upid, hpa.ts from heap_profile_allocation hpa;";
+ "select distinct hpa.upid, hpa.ts, p.pid from heap_profile_allocation hpa, "
+ "process p where p.upid = hpa.upid;";
struct Callsite {
int64_t id;
int64_t frame_id;
};
-// Walk tree bottom up and assign the inverse of the frame_ids of the path
-// that was used to reach each node into result.
-void Walk(const std::vector<std::vector<Callsite>> children_map,
- std::vector<std::vector<int64_t>>* result,
- std::vector<int64_t> parents,
- const Callsite& root) {
- PERFETTO_DCHECK((*result)[static_cast<size_t>(root.id)].empty());
- parents.push_back(root.frame_id);
- // pprof stores the frames the other way round that we do, reverse here.
- (*result)[static_cast<size_t>(root.id)].assign(parents.rbegin(),
- parents.rend());
- const std::vector<Callsite>& children =
- children_map[static_cast<size_t>(root.id)];
- for (const Callsite& child : children)
- Walk(children_map, result, parents, child);
-}
-
// Return map from callsite_id to list of frame_ids that make up the callstack.
std::vector<std::vector<int64_t>> GetCallsiteToFrames(
trace_processor::TraceProcessor* tp) {
@@ -120,20 +104,22 @@
return {};
}
int64_t count = count_it.Get(0).long_value;
- std::vector<std::vector<Callsite>> children(static_cast<size_t>(count));
Iterator it = tp->ExecuteQuery(
- "select id, parent_id, frame_id from stack_profile_callsite;");
- std::vector<Callsite> roots;
+ "select id, parent_id, frame_id from stack_profile_callsite order by "
+ "depth;");
+ std::vector<std::vector<int64_t>> result(static_cast<size_t>(count));
while (it.Next()) {
int64_t id = it.Get(0).long_value;
int64_t parent_id = it.Get(1).long_value;
int64_t frame_id = it.Get(2).long_value;
- Callsite callsite{id, frame_id};
- if (parent_id == -1)
- roots.emplace_back(callsite);
- else
- children[static_cast<size_t>(parent_id)].emplace_back(callsite);
+ std::vector<int64_t>& path = result[static_cast<size_t>(id)];
+ path.push_back(frame_id);
+ if (parent_id != -1) {
+ const std::vector<int64_t>& parent_path =
+ result[static_cast<size_t>(parent_id)];
+ path.insert(path.end(), parent_path.begin(), parent_path.end());
+ }
}
if (!it.Status().ok()) {
@@ -141,13 +127,6 @@
it.Status().message().c_str());
return {};
}
-
- std::vector<std::vector<int64_t>> result(static_cast<size_t>(count));
- auto start = base::GetWallTimeMs();
- for (const Callsite& root : roots)
- Walk(children, &result, {}, root);
- PERFETTO_DLOG("Walked %zu in %llu", children.size(),
- (base::GetWallTimeMs() - start).count());
return result;
}
@@ -443,18 +422,28 @@
bool TraceToPprof(std::istream* input,
std::vector<SerializedProfile>* output,
- Symbolizer* symbolizer) {
+ Symbolizer* symbolizer,
+ uint64_t pid,
+ const std::vector<uint64_t>& timestamps) {
trace_processor::Config config;
std::unique_ptr<trace_processor::TraceProcessor> tp =
trace_processor::TraceProcessor::CreateInstance(config);
if (!ReadTrace(tp.get(), input))
- return 1;
+ return false;
tp->NotifyEndOfFile();
+ return TraceToPprof(tp.get(), output, symbolizer, pid, timestamps);
+}
+
+bool TraceToPprof(trace_processor::TraceProcessor* tp,
+ std::vector<SerializedProfile>* output,
+ Symbolizer* symbolizer,
+ uint64_t pid,
+ const std::vector<uint64_t>& timestamps) {
if (symbolizer) {
SymbolizeDatabase(
- tp.get(), symbolizer, [&tp](perfetto::protos::TracePacket packet) {
+ tp, symbolizer, [&tp](perfetto::protos::TracePacket packet) {
size_t size = static_cast<size_t>(packet.ByteSize());
std::unique_ptr<uint8_t[]> buf(new uint8_t[size]);
packet.SerializeToArray(buf.get(), packet.ByteSize());
@@ -489,8 +478,8 @@
}
int64_t max_symbol_id = max_symbol_id_it.Get(0).long_value;
- auto callsite_to_frames = GetCallsiteToFrames(tp.get());
- auto symbol_set_id_to_lines = GetSymbolSetIdToLines(tp.get());
+ auto callsite_to_frames = GetCallsiteToFrames(tp);
+ auto symbol_set_id_to_lines = GetSymbolSetIdToLines(tp);
Iterator it = tp->ExecuteQuery(kQueryProfiles);
while (it.Next()) {
@@ -498,12 +487,19 @@
max_symbol_id);
uint64_t upid = static_cast<uint64_t>(it.Get(0).long_value);
uint64_t ts = static_cast<uint64_t>(it.Get(1).long_value);
+ uint64_t profile_pid = static_cast<uint64_t>(it.Get(2).long_value);
+ if ((pid > 0 && profile_pid != pid) ||
+ (!timestamps.empty() && std::find(timestamps.begin(), timestamps.end(),
+ ts) == timestamps.end())) {
+ continue;
+ }
+
std::string pid_query = "select pid from process where upid = ";
pid_query += std::to_string(upid) + ";";
Iterator pid_it = tp->ExecuteQuery(pid_query);
PERFETTO_CHECK(pid_it.Next());
- GProfile profile = builder.GenerateGProfile(tp.get(), upid, ts);
+ GProfile profile = builder.GenerateGProfile(tp, upid, ts);
output->emplace_back(
SerializedProfile{static_cast<uint64_t>(pid_it.Get(0).long_value),
profile.SerializeAsString()});
@@ -516,8 +512,11 @@
return true;
}
-bool TraceToPprof(std::istream* input, std::vector<SerializedProfile>* output) {
- return TraceToPprof(input, output, nullptr);
+bool TraceToPprof(std::istream* input,
+ std::vector<SerializedProfile>* output,
+ uint64_t pid,
+ const std::vector<uint64_t>& timestamps) {
+ return TraceToPprof(input, output, nullptr, pid, timestamps);
}
} // namespace trace_to_text
diff --git a/tools/trace_to_text/trace_to_profile.cc b/tools/trace_to_text/trace_to_profile.cc
index e70931d..cc00194 100644
--- a/tools/trace_to_text/trace_to_profile.cc
+++ b/tools/trace_to_text/trace_to_profile.cc
@@ -47,7 +47,10 @@
namespace perfetto {
namespace trace_to_text {
-int TraceToProfile(std::istream* input, std::ostream* output) {
+int TraceToProfile(std::istream* input,
+ std::ostream* output,
+ uint64_t pid,
+ std::vector<uint64_t> timestamps) {
std::unique_ptr<Symbolizer> symbolizer;
auto binary_path = GetPerfettoBinaryPath();
if (!binary_path.empty()) {
@@ -61,7 +64,7 @@
}
std::vector<SerializedProfile> profiles;
- TraceToPprof(input, &profiles, symbolizer.get());
+ TraceToPprof(input, &profiles, symbolizer.get(), pid, timestamps);
if (profiles.empty()) {
return 0;
}
diff --git a/tools/trace_to_text/trace_to_profile.h b/tools/trace_to_text/trace_to_profile.h
index 4e84f8e..629d3ef 100644
--- a/tools/trace_to_text/trace_to_profile.h
+++ b/tools/trace_to_text/trace_to_profile.h
@@ -18,11 +18,15 @@
#define TOOLS_TRACE_TO_TEXT_TRACE_TO_PROFILE_H_
#include <iostream>
+#include <vector>
namespace perfetto {
namespace trace_to_text {
-int TraceToProfile(std::istream* input, std::ostream* output);
+int TraceToProfile(std::istream* input,
+ std::ostream* output,
+ uint64_t pid = 0,
+ std::vector<uint64_t> timestamps = {});
} // namespace trace_to_text
} // namespace perfetto
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index 1acf957..b5ac993 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -459,6 +459,28 @@
}
}
+ button {
+ background-color: #262f3c;
+ color: #fff;
+ font-size: 0.875rem;
+ padding-left: 1rem;
+ padding-right: 1rem;
+ padding-top: .5rem;
+ padding-bottom: .5rem;
+ border-radius: .25rem;
+ margin-top: 12px;
+ }
+
+ .explanation {
+ font-size: 14px;
+ width: 35%;
+ margin-top: 10px;
+ }
+
+ .material-icons {
+ vertical-align: middle;
+ margin-right: 10px;
+ }
}
.tickbar {
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index b3c37c6..13f9f6b 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -15,7 +15,7 @@
import {Draft} from 'immer';
import {assertExists} from '../base/logging';
-import {ConvertTrace} from '../controller/trace_converter';
+import {ConvertTrace, ConvertTraceToPprof} from '../controller/trace_converter';
import {
AdbRecordingTarget,
@@ -95,11 +95,21 @@
state.videoEnabled = true;
},
+ // TODO(b/141359485): Actions should only modify state.
convertTraceToJson(
_: StateDraft, args: {file: Blob, truncate?: 'start'|'end'}): void {
ConvertTrace(args.file, args.truncate);
},
+ convertTraceToPprof(_: StateDraft, args: {
+ pid: number,
+ src: string|File|ArrayBuffer,
+ ts1: number,
+ ts2?: number
+ }): void {
+ ConvertTraceToPprof(args.pid, args.src, args.ts1, args.ts2);
+ },
+
openTraceFromUrl(state: StateDraft, args: {url: string}): void {
clearTraceState(state);
const id = `${state.nextId++}`;
diff --git a/ui/src/controller/globals.ts b/ui/src/controller/globals.ts
index 062770b..179c0ee 100644
--- a/ui/src/controller/globals.ts
+++ b/ui/src/controller/globals.ts
@@ -27,8 +27,9 @@
import {ControllerAny} from './controller';
-type PublishKinds = 'OverviewData'|'TrackData'|'Threads'|'QueryResult'|
- 'LegacyTrace'|'SliceDetails'|'CounterDetails'|'HeapDumpDetails'|'Loading'|
+type PublishKinds =
+ 'OverviewData'|'TrackData'|'Threads'|'QueryResult'|'LegacyTrace'|
+ 'SliceDetails'|'CounterDetails'|'HeapDumpDetails'|'FileDownload'|'Loading'|
'Search'|'BufferUsage'|'RecordingLog'|'SearchResult';
export interface App {
diff --git a/ui/src/controller/permalink_controller.ts b/ui/src/controller/permalink_controller.ts
index ddf4276..7bafa23 100644
--- a/ui/src/controller/permalink_controller.ts
+++ b/ui/src/controller/permalink_controller.ts
@@ -24,6 +24,10 @@
export const BUCKET_NAME = 'perfetto-ui-data';
+function needsToBeUploaded(obj: {}): obj is ArrayBuffer|File {
+ return obj instanceof ArrayBuffer || obj instanceof File;
+}
+
export class PermalinkController extends Controller<'main'> {
private lastRequestId?: string;
constructor() {
@@ -57,10 +61,12 @@
const state = globals.state;
// Upload each loaded trace.
- const fileToUrl = new Map<File, string>();
+ const fileToUrl = new Map<File|ArrayBuffer, string>();
for (const engine of Object.values<EngineConfig>(state.engines)) {
- if (!(engine.source instanceof File)) continue;
- PermalinkController.updateStatus(`Uploading ${engine.source.name}`);
+ if (!needsToBeUploaded(engine.source)) continue;
+ const name = engine.source instanceof File ? engine.source.name :
+ `trace ${engine.id}`;
+ PermalinkController.updateStatus(`Uploading ${name}`);
const url = await this.saveTrace(engine.source);
fileToUrl.set(engine.source, url);
}
@@ -69,8 +75,9 @@
const uploadState = produce(state, draft => {
for (const engine of Object.values<Draft<EngineConfig>>(
draft.engines)) {
- if (!(engine.source instanceof File)) continue;
- engine.source = fileToUrl.get(engine.source)!;
+ if (!needsToBeUploaded(engine.source)) continue;
+ const newSource = fileToUrl.get(engine.source);
+ if (newSource) engine.source = newSource;
}
draft.permalink = {};
});
@@ -100,7 +107,7 @@
return hash;
}
- private static async saveTrace(trace: File): Promise<string> {
+ private static async saveTrace(trace: File|ArrayBuffer): Promise<string> {
// TODO(hjd): This should probably also be a hash but that requires
// trace processor support.
const name = uuidv4();
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index 9e881b7..99cbfcf 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -124,6 +124,9 @@
}
async heapDumpDetails(ts: number, upid: number) {
+ const pidValue = await this.args.engine.query(
+ `select pid from process where upid = ${upid}`);
+ const pid = pidValue.columns[0].longValues![0];
const allocatedMemory = await this.args.engine.query(
`select sum(size) from heap_profile_allocation where ts <= ${
ts} and size > 0 and upid = ${upid}`);
@@ -133,7 +136,7 @@
ts} and upid = ${upid}`);
const allocatedNotFreed = allocatedNotFreedMemory.columns[0].longValues![0];
const startTime = fromNs(ts) - globals.state.traceTime.startSec;
- return {ts: startTime, allocated, allocatedNotFreed};
+ return {ts: startTime, allocated, allocatedNotFreed, tsNs: ts, pid};
}
async counterDetails(ts: number, rightTs: number, id: number) {
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 34a79a4..cfa03f2 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -307,6 +307,40 @@
}
}
+
+ const upidToProcessTracks = new Map();
+ const rawProcessTracks = await engine.query(`
+ select id, upid, name, maxDepth
+ from process_track
+ join (
+ select ref as id, max(depth) as maxDepth
+ from slice
+ where ref_type = 'track' group by ref
+ ) using(id)
+ `);
+ for (let i = 0; i < rawProcessTracks.numRecords; i++) {
+ const trackId = rawProcessTracks.columns[0].longValues![i];
+ const upid = rawProcessTracks.columns[1].longValues![i];
+ const name = rawProcessTracks.columns[2].stringValues![i];
+ const maxDepth = rawProcessTracks.columns[3].longValues![i];
+ const track = {
+ engineId: this.engineId,
+ kind: 'AsyncSliceTrack',
+ name,
+ config: {
+ trackId,
+ maxDepth,
+ },
+ };
+
+ const tracks = upidToProcessTracks.get(upid);
+ if (tracks) {
+ tracks.push(track);
+ } else {
+ upidToProcessTracks.set(upid, [track]);
+ }
+ }
+
const heapProfiles = await engine.query(`
select distinct(upid) from heap_profile_allocation`);
@@ -513,6 +547,12 @@
config: {upid}
});
}
+
+ if (upidToProcessTracks.has(upid)) {
+ for (const track of upidToProcessTracks.get(upid)) {
+ tracksToAdd.push(Object.assign(track, {trackGroup: pUuid}));
+ }
+ }
}
}
const counterThreadNames = counterUtids[utid];
diff --git a/ui/src/controller/trace_converter.ts b/ui/src/controller/trace_converter.ts
index 5941cb4..e4648a0 100644
--- a/ui/src/controller/trace_converter.ts
+++ b/ui/src/controller/trace_converter.ts
@@ -53,6 +53,74 @@
(self as {} as {mod: {}}).mod = mod;
}
+export async function ConvertTraceToPprof(
+ pid: number, src: string|File|ArrayBuffer, ts1: number, ts2?: number) {
+ generateBlob(src).then(result => {
+ const mod = trace_to_text({
+ noInitialRun: true,
+ locateFile: (s: string) => s,
+ print: updateStatus,
+ printErr: updateStatus,
+ onRuntimeInitialized: () => {
+ updateStatus('Converting trace');
+ const timestamps = `${ts1}${ts2 === undefined ? '' : `,${ts2}`}`;
+ mod.callMain([
+ 'profile',
+ `--pid`,
+ `${pid}`,
+ `--timestamps`,
+ timestamps,
+ '/fs/trace.proto'
+ ]);
+ updateStatus('Trace conversion completed');
+ const heapDirName =
+ Object.keys(mod.FS.lookupPath('/tmp/').node.contents)[0];
+ const heapDirContents =
+ mod.FS.lookupPath(`/tmp/${heapDirName}`).node.contents;
+ const heapDumpFiles = Object.keys(heapDirContents);
+ let fileNum = 0;
+ heapDumpFiles.forEach(heapDump => {
+ const fileContents =
+ mod.FS.lookupPath(`/tmp/${heapDirName}/${heapDump}`)
+ .node.contents;
+ fileNum++;
+ const fileName = `/heap_dump.${fileNum}.${pid}.pb`;
+ downloadFile(new Blob([fileContents]), fileName);
+ });
+ updateStatus('Profile(s) downloaded');
+ },
+ onAbort: () => {
+ console.log('ABORT');
+ },
+ });
+ mod.FS.mkdir('/fs');
+ mod.FS.mount(
+ mod.FS.filesystems.WORKERFS,
+ {blobs: [{name: 'trace.proto', data: result}]},
+ '/fs');
+ });
+}
+
+async function generateBlob(src: string|ArrayBuffer|File) {
+ let blob: Blob = new Blob();
+ if (typeof src === 'string') {
+ const resp = await fetch(src);
+ if (resp.status !== 200) {
+ throw new Error(`fetch() failed with HTTP error ${resp.status}`);
+ }
+ blob = await resp.blob();
+ } else if (src instanceof ArrayBuffer) {
+ blob = new Blob([new Uint8Array(src, 0, src.byteLength)]);
+ } else {
+ blob = src;
+ }
+ return blob;
+}
+
+function downloadFile(file: Blob, name: string) {
+ globals.publish('FileDownload', {file, name});
+}
+
function updateStatus(msg: {}) {
console.log(msg);
globals.dispatch(Actions.updateStatus({
diff --git a/ui/src/frontend/chrome_slice_panel.ts b/ui/src/frontend/chrome_slice_panel.ts
index 9a6f142..97ce65c 100644
--- a/ui/src/frontend/chrome_slice_panel.ts
+++ b/ui/src/frontend/chrome_slice_panel.ts
@@ -31,9 +31,11 @@
[m('table',
[
m('tr', m('th', `Name`), m('td', `${sliceInfo.name}`)),
- m('tr',
- m('th', `Category`),
- m('td', `${sliceInfo.category}`)),
+ (sliceInfo.category === '[NULL]') ?
+ null :
+ m('tr',
+ m('th', `Category`),
+ m('td', `${sliceInfo.category}`)),
m('tr',
m('th', `Start time`),
m('td', `${timeToCode(sliceInfo.ts)}`)),
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index f9aca97..e62a889 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -44,8 +44,10 @@
export interface HeapDumpDetails {
ts?: number;
+ tsNs?: number;
allocated?: number;
allocatedNotFreed?: number;
+ pid?: number;
}
export interface QuantizedLoad {
diff --git a/ui/src/frontend/heap_dump_panel.ts b/ui/src/frontend/heap_dump_panel.ts
index d6c14e1..7c31636 100644
--- a/ui/src/frontend/heap_dump_panel.ts
+++ b/ui/src/frontend/heap_dump_panel.ts
@@ -14,6 +14,7 @@
import * as m from 'mithril';
+import {Actions} from '../common/actions';
import {timeToCode} from '../common/time';
import {globals} from './globals';
@@ -22,13 +23,19 @@
interface HeapDumpDetailsPanelAttrs {}
export class HeapDumpDetailsPanel extends Panel<HeapDumpDetailsPanelAttrs> {
+ private ts = 0;
+ private pid = 0;
+
view() {
const heapDumpInfo = globals.heapDumpDetails;
if (heapDumpInfo && heapDumpInfo.ts && heapDumpInfo.allocated &&
- heapDumpInfo.allocatedNotFreed) {
+ heapDumpInfo.allocatedNotFreed && heapDumpInfo.tsNs &&
+ heapDumpInfo.pid) {
+ this.ts = heapDumpInfo.tsNs;
+ this.pid = heapDumpInfo.pid;
return m(
'.details-panel',
- m('.details-panel-heading', `Heap Snapshot Details:`),
+ m('.details-panel-heading', `Heap Profile Details:`),
m(
'.details-table',
[m('table',
@@ -47,7 +54,23 @@
heapDumpInfo.allocatedNotFreed
.toLocaleString()} bytes`)),
])],
- ));
+ ),
+ m('.explanation',
+ 'Heap profile support is in beta. To explore a heap profile,',
+ ' download and open it in ',
+ m(`a[href='https://pprof.corp.google.com']`, 'pprof'),
+ ' (Googlers only) or ',
+ m(`a[href='https://www.speedscope.app']`, 'Speedscope'),
+ '.'),
+ m('button',
+ {
+ onclick: () => {
+ this.downloadPprof();
+ }
+ },
+ m('i.material-icons', 'file_download'),
+ 'Download profile'),
+ );
} else {
return m(
'.details-panel',
@@ -55,5 +78,14 @@
}
}
+ downloadPprof() {
+ const engine = Object.values(globals.state.engines)[0];
+ if (!engine) return;
+ const src = engine.source;
+ // TODO(tneda): add second timestamp
+ globals.dispatch(
+ Actions.convertTraceToPprof({pid: this.pid, ts1: this.ts, src}));
+ }
+
renderCanvas() {}
}
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index 3790f61..2b1effe 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -128,6 +128,17 @@
this.redraw();
}
+ publishFileDownload(args: {file: File, name?: string}) {
+ const url = URL.createObjectURL(args.file);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = args.name !== undefined ? args.name : args.file.name;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+ }
+
publishLoading(loading: boolean) {
globals.loading = loading;
globals.rafScheduler.scheduleRedraw();
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 620981f..63d7a48 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -309,9 +309,7 @@
}));
break;
case 'HEAP_DUMP':
- detailsPanels.push(m(HeapDumpDetailsPanel, {
- key: 'heap_dump',
- }));
+ detailsPanels.push(m(HeapDumpDetailsPanel, {key: 'heap_dump'}));
break;
case 'CHROME_SLICE':
detailsPanels.push(m(ChromeSliceDetailsPanel));
diff --git a/ui/src/tracks/all_controller.ts b/ui/src/tracks/all_controller.ts
index 79d3070..ca4f7c8 100644
--- a/ui/src/tracks/all_controller.ts
+++ b/ui/src/tracks/all_controller.ts
@@ -25,3 +25,4 @@
import './process_summary/controller';
import './thread_state/controller';
import './vsync/controller';
+import './async_slices/controller';
diff --git a/ui/src/tracks/all_frontend.ts b/ui/src/tracks/all_frontend.ts
index dfa57fc..3ce3de0 100644
--- a/ui/src/tracks/all_frontend.ts
+++ b/ui/src/tracks/all_frontend.ts
@@ -25,3 +25,4 @@
import './process_summary/frontend';
import './thread_state/frontend';
import './vsync/frontend';
+import './async_slices/frontend';
diff --git a/ui/src/tracks/async_slices/common.ts b/ui/src/tracks/async_slices/common.ts
new file mode 100644
index 0000000..3b7c885
--- /dev/null
+++ b/ui/src/tracks/async_slices/common.ts
@@ -0,0 +1,22 @@
+// Copyright (C) 2019 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.
+
+export {Data} from '../chrome_slices/common';
+
+export const SLICE_TRACK_KIND = 'AsyncSliceTrack';
+
+export interface Config {
+ maxDepth: number;
+ trackId: number;
+}
diff --git a/ui/src/tracks/async_slices/controller.ts b/ui/src/tracks/async_slices/controller.ts
new file mode 100644
index 0000000..e56999b
--- /dev/null
+++ b/ui/src/tracks/async_slices/controller.ts
@@ -0,0 +1,143 @@
+// Copyright (C) 2019 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.
+
+import {fromNs, toNs} from '../../common/time';
+import {LIMIT} from '../../common/track_data';
+import {
+ TrackController,
+ trackControllerRegistry,
+} from '../../controller/track_controller';
+
+import {Config, Data, SLICE_TRACK_KIND} from './common';
+
+class AsyncSliceTrackController extends TrackController<Config, Data> {
+ static readonly kind = SLICE_TRACK_KIND;
+ private setup = false;
+
+ async onBoundsChange(start: number, end: number, resolution: number):
+ Promise<Data> {
+ const startNs = toNs(start);
+ const endNs = toNs(end);
+ // Ns in 1px width. We want all slices smaller than 1px to be grouped.
+ const minNs = toNs(resolution);
+
+ if (!this.setup) {
+ await this.query(
+ `create virtual table ${this.tableName('window')} using window;`);
+
+ await this.query(
+ `create view ${this.tableName('small')} as ` +
+ `select ts,dur,depth,name,slice_id from slice ` +
+ `where ref_type = 'track' ` +
+ `and ref = ${this.config.trackId} ` +
+ `and dur < ${minNs} ` +
+ `order by ts;`);
+
+ await this.query(`create virtual table ${this.tableName('span')} using
+ span_join(${this.tableName('small')} PARTITIONED depth,
+ ${this.tableName('window')});`);
+
+ this.setup = true;
+ }
+
+ const windowDurNs = Math.max(1, endNs - startNs);
+
+ this.query(`update ${this.tableName('window')} set
+ window_start=${startNs},
+ window_dur=${windowDurNs},
+ quantum=${minNs}`);
+
+ await this.query(`drop view if exists ${this.tableName('small')}`);
+ await this.query(`drop view if exists ${this.tableName('big')}`);
+ await this.query(`drop view if exists ${this.tableName('summary')}`);
+
+ await this.query(
+ `create view ${this.tableName('small')} as ` +
+ `select ts,dur,depth,name, slice_id from slice ` +
+ `where ref_type = 'track' ` +
+ `and ref = ${this.config.trackId} ` +
+ `and dur < ${minNs} ` +
+ `order by ts `);
+
+ await this.query(
+ `create view ${this.tableName('big')} as ` +
+ `select ts,dur,depth,name, slice_id from slice ` +
+ `where ref_type = 'track' ` +
+ `and ref = ${this.config.trackId} ` +
+ `and ts >= ${startNs} - dur ` +
+ `and ts <= ${endNs} ` +
+ `and dur >= ${minNs} ` +
+ `order by ts `);
+
+ // So that busy slices never overlap, we use the start of the bucket
+ // as the ts, even though min(ts) would technically be more accurate.
+ await this.query(`create view ${this.tableName('summary')} as select
+ (quantum_ts * ${minNs} + ${startNs}) as ts,
+ ${minNs} as dur,
+ depth,
+ 'Busy' as name,
+ -1 as slice_id
+ from ${this.tableName('span')}
+ group by depth, quantum_ts
+ order by ts;`);
+
+ const query = `select * from ${this.tableName('summary')} UNION ` +
+ `select * from ${this.tableName('big')} order by ts limit ${LIMIT}`;
+
+ const rawResult = await this.query(query);
+
+ if (rawResult.error) {
+ throw new Error(`Query error "${query}": ${rawResult.error}`);
+ }
+
+ const numRows = +rawResult.numRecords;
+
+ const slices: Data = {
+ start,
+ end,
+ resolution,
+ length: numRows,
+ strings: [],
+ sliceIds: new Float64Array(numRows),
+ starts: new Float64Array(numRows),
+ ends: new Float64Array(numRows),
+ depths: new Uint16Array(numRows),
+ titles: new Uint16Array(numRows),
+ };
+
+ const stringIndexes = new Map<string, number>();
+ function internString(str: string) {
+ let idx = stringIndexes.get(str);
+ if (idx !== undefined) return idx;
+ idx = slices.strings.length;
+ slices.strings.push(str);
+ stringIndexes.set(str, idx);
+ return idx;
+ }
+
+ for (let row = 0; row < numRows; row++) {
+ const cols = rawResult.columns;
+ const startSec = fromNs(+cols[0].longValues![row]);
+ slices.starts[row] = startSec;
+ slices.ends[row] = startSec + fromNs(+cols[1].longValues![row]);
+ slices.depths[row] = +cols[2].longValues![row];
+ slices.titles[row] = internString(cols[3].stringValues![row]);
+ slices.sliceIds[row] = +cols[4].longValues![row];
+ }
+ return slices;
+ }
+}
+
+
+trackControllerRegistry.register(AsyncSliceTrackController);
diff --git a/ui/src/tracks/async_slices/frontend.ts b/ui/src/tracks/async_slices/frontend.ts
new file mode 100644
index 0000000..918f831
--- /dev/null
+++ b/ui/src/tracks/async_slices/frontend.ts
@@ -0,0 +1,29 @@
+// Copyright (C) 2019 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.
+
+import {TrackState} from '../../common/state';
+import {Track} from '../../frontend/track';
+import {trackRegistry} from '../../frontend/track_registry';
+import {ChromeSliceTrack} from '../chrome_slices/frontend';
+
+import {SLICE_TRACK_KIND} from './common';
+
+export class AsyncSliceTrack extends ChromeSliceTrack {
+ static readonly kind = SLICE_TRACK_KIND;
+ static create(trackState: TrackState): Track {
+ return new AsyncSliceTrack(trackState);
+ }
+}
+
+trackRegistry.register(AsyncSliceTrack);
diff --git a/ui/src/tracks/chrome_slices/common.ts b/ui/src/tracks/chrome_slices/common.ts
index ad240c3..68653da 100644
--- a/ui/src/tracks/chrome_slices/common.ts
+++ b/ui/src/tracks/chrome_slices/common.ts
@@ -30,5 +30,4 @@
ends: Float64Array;
depths: Uint16Array;
titles: Uint16Array; // Index in |strings|.
- categories: Uint16Array; // Index in |strings|.
}
diff --git a/ui/src/tracks/chrome_slices/controller.ts b/ui/src/tracks/chrome_slices/controller.ts
index 1d44a7c..cbe6486 100644
--- a/ui/src/tracks/chrome_slices/controller.ts
+++ b/ui/src/tracks/chrome_slices/controller.ts
@@ -38,10 +38,8 @@
await this.query(
`create view ${this.tableName('small')} as ` +
- `select ts,dur,depth,cat,name,slice_id from slices ` +
+ `select ts,dur,depth,name,slice_id from slice ` +
`where utid = ${this.config.utid} ` +
- `and ts >= ${startNs} - dur ` +
- `and ts <= ${endNs} ` +
`and dur < ${minNs} ` +
`order by ts;`);
@@ -65,16 +63,14 @@
await this.query(
`create view ${this.tableName('small')} as ` +
- `select ts,dur,depth,cat,name, slice_id from slices ` +
+ `select ts,dur,depth,name,slice_id from slice ` +
`where utid = ${this.config.utid} ` +
- `and ts >= ${startNs} - dur ` +
- `and ts <= ${endNs} ` +
`and dur < ${minNs} ` +
`order by ts `);
await this.query(
`create view ${this.tableName('big')} as ` +
- `select ts,dur,depth,cat,name, slice_id from slices ` +
+ `select ts,dur,depth,name,slice_id from slice ` +
`where utid = ${this.config.utid} ` +
`and ts >= ${startNs} - dur ` +
`and ts <= ${endNs} ` +
@@ -87,11 +83,10 @@
(quantum_ts * ${minNs} + ${startNs}) as ts,
${minNs} as dur,
depth,
- cat,
'Busy' as name,
-1 as slice_id
from ${this.tableName('span')}
- group by cat, depth, quantum_ts
+ group by depth, quantum_ts
order by ts;`);
const query = `select * from ${this.tableName('summary')} UNION ` +
@@ -116,7 +111,6 @@
ends: new Float64Array(numRows),
depths: new Uint16Array(numRows),
titles: new Uint16Array(numRows),
- categories: new Uint16Array(numRows),
};
const stringIndexes = new Map<string, number>();
@@ -135,9 +129,8 @@
slices.starts[row] = startSec;
slices.ends[row] = startSec + fromNs(+cols[1].longValues![row]);
slices.depths[row] = +cols[2].longValues![row];
- slices.categories[row] = internString(cols[3].stringValues![row]);
- slices.titles[row] = internString(cols[4].stringValues![row]);
- slices.sliceIds[row] = +cols[5].longValues![row];
+ slices.titles[row] = internString(cols[3].stringValues![row]);
+ slices.sliceIds[row] = +cols[4].longValues![row];
}
return slices;
}
diff --git a/ui/src/tracks/chrome_slices/frontend.ts b/ui/src/tracks/chrome_slices/frontend.ts
index 5826f67..544d84f 100644
--- a/ui/src/tracks/chrome_slices/frontend.ts
+++ b/ui/src/tracks/chrome_slices/frontend.ts
@@ -34,9 +34,9 @@
return hash & 0xff;
}
-class ChromeSliceTrack extends Track<Config, Data> {
- static readonly kind = SLICE_TRACK_KIND;
- static create(trackState: TrackState): ChromeSliceTrack {
+export class ChromeSliceTrack extends Track<Config, Data> {
+ static readonly kind: string = SLICE_TRACK_KIND;
+ static create(trackState: TrackState): Track {
return new ChromeSliceTrack(trackState);
}