Merge "tp: allow span outer join on unpartitioned tables"
diff --git a/Android.bp b/Android.bp
index 3b4bbdd..563cfa8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6838,6 +6838,7 @@
filegroup {
name: "perfetto_src_base_base",
srcs: [
+ "src/base/crash_keys.cc",
"src/base/ctrl_c_handler.cc",
"src/base/event_fd.cc",
"src/base/file_utils.cc",
diff --git a/BUILD b/BUILD
index edf1172..9e1fcd0 100644
--- a/BUILD
+++ b/BUILD
@@ -333,6 +333,7 @@
srcs = [
"include/perfetto/ext/base/circular_queue.h",
"include/perfetto/ext/base/container_annotations.h",
+ "include/perfetto/ext/base/crash_keys.h",
"include/perfetto/ext/base/ctrl_c_handler.h",
"include/perfetto/ext/base/endian.h",
"include/perfetto/ext/base/event_fd.h",
@@ -634,6 +635,7 @@
perfetto_cc_library(
name = "src_base_base",
srcs = [
+ "src/base/crash_keys.cc",
"src/base/ctrl_c_handler.cc",
"src/base/event_fd.cc",
"src/base/file_utils.cc",
diff --git a/gn/gen_perfetto_version_header.gni b/gn/gen_perfetto_version_header.gni
index 17c879f..59cd681 100644
--- a/gn/gen_perfetto_version_header.gni
+++ b/gn/gen_perfetto_version_header.gni
@@ -21,15 +21,20 @@
import("perfetto.gni")
+_ver_script = "${perfetto_root_path}tools/write_version_header.py"
+_has_git = false
+if (perfetto_enable_git_rev_version_header) {
+ _has_git = "1" == exec_script(_ver_script, [ "--check_git" ], "trim string")
+}
+
template("gen_perfetto_version_header") {
action(target_name) {
- script = "${perfetto_root_path}tools/write_version_header.py"
+ script = _ver_script
changelog = "${perfetto_root_path}CHANGELOG"
inputs = [ changelog ]
outputs = []
args = []
- if (perfetto_build_standalone && !is_perfetto_build_generator &&
- perfetto_enable_git_rev_version_header) {
+ if (_has_git) {
inputs += [ "${perfetto_root_path}.git/HEAD" ]
}
@@ -49,7 +54,7 @@
]
outputs += [ invoker.ts_out ]
}
- if (!perfetto_enable_git_rev_version_header) {
+ if (!_has_git) {
args += [ "--no_git" ]
}
}
diff --git a/gn/perfetto.gni b/gn/perfetto.gni
index c6817aa..4520087 100644
--- a/gn/perfetto.gni
+++ b/gn/perfetto.gni
@@ -235,7 +235,9 @@
}
declare_args() {
- perfetto_enable_git_rev_version_header = enable_perfetto_version_gen
+ perfetto_enable_git_rev_version_header =
+ enable_perfetto_version_gen && perfetto_build_standalone &&
+ !is_perfetto_build_generator
# The traced_probes daemon is very Linux-specific, as it depends on ftrace and
# various /proc interfaces. There is no point making its code platform-neutral
diff --git a/include/perfetto/ext/base/BUILD.gn b/include/perfetto/ext/base/BUILD.gn
index ec8cca8..e86ca99 100644
--- a/include/perfetto/ext/base/BUILD.gn
+++ b/include/perfetto/ext/base/BUILD.gn
@@ -18,6 +18,7 @@
sources = [
"circular_queue.h",
"container_annotations.h",
+ "crash_keys.h",
"ctrl_c_handler.h",
"endian.h",
"event_fd.h",
diff --git a/include/perfetto/ext/base/crash_keys.h b/include/perfetto/ext/base/crash_keys.h
new file mode 100644
index 0000000..921265f
--- /dev/null
+++ b/include/perfetto/ext/base/crash_keys.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef INCLUDE_PERFETTO_EXT_BASE_CRASH_KEYS_H_
+#define INCLUDE_PERFETTO_EXT_BASE_CRASH_KEYS_H_
+
+#include <algorithm>
+#include <atomic>
+
+#include <stdint.h>
+#include <string.h>
+
+#include "perfetto/base/compiler.h"
+#include "perfetto/ext/base/string_view.h"
+
+// Crash keys are very simple global variables with static-storage that
+// are reported on crash time for managed crashes (CHECK/FATAL/Watchdog).
+// - Translation units can define a CrashKey and register it at some point
+// during initialization.
+// - CrashKey instances must be long-lived. They should really be just global
+// static variable in the anonymous namespace.
+// Example:
+// subsystem_1.cc
+// CrashKey g_client_id("ipc_client_id");
+// ...
+// OnIpcReceived(client_id) {
+// g_client_id.Set(client_id);
+// ... // Process the IPC
+// g_client_id.Clear();
+// }
+// Or equivalently:
+// OnIpcReceived(client_id) {
+// auto scoped_key = g_client_id.SetScoped(client_id);
+// ... // Process the IPC
+// }
+//
+// If a crash happens while processing the IPC, the crash report will
+// have a line "ipc_client_id: 42".
+//
+// Thread safety considerations:
+// CrashKeys can be registered and set/cleared from any thread.
+// There is no compelling use-case to have full acquire/release consistency when
+// setting a key. This means that if a thread crashes immediately after a
+// crash key has been set on another thread, the value printed on the crash
+// report could be incomplete. The code guarantees defined behavior and does
+// not rely on null-terminated string (in the worst case 32 bytes of random
+// garbage will be printed out).
+
+// The tests live in logging_unittest.cc.
+
+namespace perfetto {
+namespace base {
+
+constexpr size_t kCrashKeyMaxStrSize = 32;
+
+// CrashKey instances must be long lived
+class CrashKey {
+ public:
+ class ScopedClear {
+ public:
+ explicit ScopedClear(CrashKey* k) : key_(k) {}
+ ~ScopedClear() {
+ if (key_)
+ key_->Clear();
+ }
+ ScopedClear(const ScopedClear&) = delete;
+ ScopedClear& operator=(const ScopedClear&) = delete;
+ ScopedClear& operator=(ScopedClear&&) = delete;
+ ScopedClear(ScopedClear&& other) : key_(other.key_) {
+ other.key_ = nullptr;
+ }
+
+ private:
+ CrashKey* key_;
+ };
+
+ // constexpr so it can be used in the anon namespace without requiring a
+ // global constructor.
+ // |name| must be a long-lived string.
+ constexpr explicit CrashKey(const char* name)
+ : registered_{}, type_(Type::kUnset), name_(name), str_value_{} {}
+ CrashKey(const CrashKey&) = delete;
+ CrashKey& operator=(const CrashKey&) = delete;
+ CrashKey(CrashKey&&) = delete;
+ CrashKey& operator=(CrashKey&&) = delete;
+
+ enum class Type : uint8_t { kUnset = 0, kInt, kStr };
+
+ void Clear() { type_ = Type::kUnset; }
+
+ void Set(int64_t value) {
+ int_value_ = value;
+ type_ = Type::kInt;
+ if (PERFETTO_UNLIKELY(!registered_.load(std::memory_order_relaxed)))
+ Register();
+ }
+
+ void Set(StringView sv) {
+ size_t len = std::min(sv.size(), sizeof(str_value_) - 1);
+ memcpy(str_value_, sv.data(), len);
+ str_value_[len] = '\0';
+ type_ = Type::kStr;
+ if (PERFETTO_UNLIKELY(!registered_.load(std::memory_order_relaxed)))
+ Register();
+ }
+
+ ScopedClear SetScoped(int64_t value) PERFETTO_WARN_UNUSED_RESULT {
+ Set(value);
+ return ScopedClear(this);
+ }
+
+ ScopedClear SetScoped(StringView sv) PERFETTO_WARN_UNUSED_RESULT {
+ Set(sv);
+ return ScopedClear(this);
+ }
+
+ int64_t int_value() const { return int_value_; }
+ size_t ToString(char* dst, size_t len);
+
+ private:
+ void Register();
+
+ std::atomic<bool> registered_;
+ Type type_;
+ const char* const name_;
+ union {
+ char str_value_[kCrashKeyMaxStrSize];
+ int64_t int_value_;
+ };
+};
+
+// Fills |dst| with a string containing one line for each crash key
+// (excluding the unset ones).
+// Returns number of chars written, without counting the NUL terminator.
+// This is used in logging.cc when emitting the crash report abort message.
+size_t SerializeCrashKeys(char* dst, size_t len);
+
+void UnregisterAllCrashKeysForTesting();
+
+} // namespace base
+} // namespace perfetto
+
+#endif // INCLUDE_PERFETTO_EXT_BASE_CRASH_KEYS_H_
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index dc8eff5..5d5d645 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -31,6 +31,7 @@
"../../include/perfetto/ext/base",
]
sources = [
+ "crash_keys.cc",
"ctrl_c_handler.cc",
"event_fd.cc",
"file_utils.cc",
diff --git a/src/base/crash_keys.cc b/src/base/crash_keys.cc
new file mode 100644
index 0000000..5c975df
--- /dev/null
+++ b/src/base/crash_keys.cc
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 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 "perfetto/ext/base/crash_keys.h"
+
+#include <string.h>
+
+#include <atomic>
+#include <cinttypes>
+
+#include "perfetto/ext/base/string_utils.h"
+
+namespace perfetto {
+namespace base {
+
+namespace {
+
+constexpr size_t kMaxKeys = 32;
+
+std::atomic<CrashKey*> g_keys[kMaxKeys]{};
+std::atomic<uint32_t> g_num_keys{};
+} // namespace
+
+void CrashKey::Register() {
+ // If doesn't matter if we fail below. If there are no slots left, don't
+ // keep trying re-registering on every Set(), the outcome won't change.
+
+ // If two threads raced on the Register(), avoid registering the key twice.
+ if (registered_.exchange(true))
+ return;
+
+ uint32_t slot = g_num_keys.fetch_add(1);
+ if (slot >= kMaxKeys) {
+ PERFETTO_LOG("Too many crash keys registered");
+ return;
+ }
+ g_keys[slot].store(this);
+}
+
+// Returns the number of chars written, without counting the \0.
+size_t CrashKey::ToString(char* dst, size_t len) {
+ if (len > 0)
+ *dst = '\0';
+ switch (type_) {
+ case Type::kUnset:
+ break;
+ case Type::kInt:
+ return SprintfTrunc(dst, len, "%s: %" PRId64 "\n", name_, int_value_);
+ case Type::kStr:
+ // Don't assume |str_value_| is properly null-terminated.
+ return SprintfTrunc(dst, len, "%s: %.*s\n", name_,
+ int(sizeof(str_value_)), str_value_);
+ }
+ return 0;
+}
+
+void UnregisterAllCrashKeysForTesting() {
+ g_num_keys.store(0);
+ for (auto& key : g_keys)
+ key.store(nullptr);
+}
+
+size_t SerializeCrashKeys(char* dst, size_t len) {
+ size_t written = 0;
+ uint32_t num_keys = g_num_keys.load();
+ if (len > 0)
+ *dst = '\0';
+ for (uint32_t i = 0; i < num_keys && written < len; i++) {
+ CrashKey* key = g_keys[i].load();
+ if (!key)
+ continue; // Can happen if we hit this between the add and the store.
+ written += key->ToString(dst + written, len - written);
+ }
+ PERFETTO_DCHECK(written <= len);
+ PERFETTO_DCHECK(len == 0 || dst[written] == '\0');
+ return written;
+}
+
+} // namespace base
+} // namespace perfetto
diff --git a/src/base/logging.cc b/src/base/logging.cc
index aaf6c69..9318ecd 100644
--- a/src/base/logging.cc
+++ b/src/base/logging.cc
@@ -28,6 +28,7 @@
#include "perfetto/base/build_config.h"
#include "perfetto/base/time.h"
+#include "perfetto/ext/base/crash_keys.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/string_view.h"
#include "src/base/log_ring_buffer.h"
@@ -201,7 +202,9 @@
// But if that happens we have bigger problems, not worth designing around it.
// The behaviour is still defined in the race case (the string attached to
// the crash report will contain a mixture of log strings).
- g_log_ring_buffer.Read(g_crash_buf, sizeof(g_crash_buf));
+ size_t wr = 0;
+ wr += SerializeCrashKeys(&g_crash_buf[wr], sizeof(g_crash_buf) - wr);
+ wr += g_log_ring_buffer.Read(&g_crash_buf[wr], sizeof(g_crash_buf) - wr);
// Read() null-terminates the string properly. This is just to avoid UB when
// two threads race on each other (T1 writes a shorter string, T2
diff --git a/src/base/logging_unittest.cc b/src/base/logging_unittest.cc
index ce400f9..ee042bd 100644
--- a/src/base/logging_unittest.cc
+++ b/src/base/logging_unittest.cc
@@ -23,6 +23,7 @@
#include <thread>
#include <vector>
+#include "perfetto/ext/base/crash_keys.h"
#include "perfetto/ext/base/string_utils.h"
#include "src/base/log_ring_buffer.h"
#include "test/gtest_and_gmock.h"
@@ -174,6 +175,83 @@
testing::UnorderedElementsAreArray(expected_events));
}
+TEST(CrashKeysTest, SetClearAndLongKeys) {
+ UnregisterAllCrashKeysForTesting();
+
+ char buf[1024];
+ memset(buf, 'x', sizeof(buf));
+ EXPECT_EQ(0u, SerializeCrashKeys(buf, sizeof(buf)));
+ EXPECT_STREQ(buf, "");
+
+ CrashKey k1("key1");
+ CrashKey k2("key2");
+ CrashKey k3("key3");
+ CrashKey k4("key4");
+
+ k1.Set(0);
+ k1.Clear();
+
+ k2.Set(42);
+
+ k3.Set("xx");
+ k3.Clear();
+
+ k4.Set("value");
+
+ EXPECT_EQ(21u, SerializeCrashKeys(buf, sizeof(buf)));
+ EXPECT_STREQ(buf, "key2: 42\nkey4: value\n");
+
+ EXPECT_EQ(0u, SerializeCrashKeys(buf, 0));
+
+ EXPECT_EQ(0u, SerializeCrashKeys(buf, 1));
+ EXPECT_STREQ(buf, "");
+
+ // Test truncated output.
+ EXPECT_EQ(5u, SerializeCrashKeys(buf, 5 + 1));
+ EXPECT_STREQ(buf, "key2:");
+
+ k2.Clear();
+
+ std::string long_str(1024, 'x');
+ k4.Set(StringView(long_str));
+
+ EXPECT_EQ(6 + kCrashKeyMaxStrSize, SerializeCrashKeys(buf, sizeof(buf)));
+ std::string expected =
+ "key4: " + long_str.substr(0, kCrashKeyMaxStrSize - 1) + "\n";
+ EXPECT_EQ(buf, expected);
+
+ UnregisterAllCrashKeysForTesting();
+}
+
+TEST(CrashKeysTest, ScopedSet) {
+ UnregisterAllCrashKeysForTesting();
+
+ char buf[1024];
+ memset(buf, 'x', sizeof(buf));
+
+ CrashKey k1("key1");
+ CrashKey k2("key2");
+
+ auto scoped_key = k1.SetScoped(42);
+ EXPECT_GT(SerializeCrashKeys(buf, sizeof(buf)), 0u);
+ EXPECT_STREQ(buf, "key1: 42\n");
+
+ {
+ auto scoped_key2 = k2.SetScoped("foo");
+ EXPECT_GT(SerializeCrashKeys(buf, sizeof(buf)), 0u);
+ EXPECT_STREQ(buf, "key1: 42\nkey2: foo\n");
+ }
+
+ EXPECT_GT(SerializeCrashKeys(buf, sizeof(buf)), 0u);
+ EXPECT_STREQ(buf, "key1: 42\n");
+
+ k1.Clear();
+ EXPECT_EQ(0u, SerializeCrashKeys(buf, sizeof(buf)));
+ EXPECT_STREQ(buf, "");
+
+ UnregisterAllCrashKeysForTesting();
+}
+
} // namespace
} // namespace base
} // namespace perfetto
diff --git a/src/ipc/host_impl.cc b/src/ipc/host_impl.cc
index 9467ca9..da1ce4c 100644
--- a/src/ipc/host_impl.cc
+++ b/src/ipc/host_impl.cc
@@ -21,6 +21,7 @@
#include <utility>
#include "perfetto/base/task_runner.h"
+#include "perfetto/ext/base/crash_keys.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/ipc/service.h"
#include "perfetto/ext/ipc/service_descriptor.h"
@@ -37,6 +38,8 @@
constexpr base::SockFamily kHostSockFamily =
kUseTCPSocket ? base::SockFamily::kInet : base::SockFamily::kUnix;
+base::CrashKey g_crash_key_uid("ipc_uid");
+
uid_t GetPosixPeerUid(base::UnixSocket* sock) {
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
base::ignore_result(sock);
@@ -123,6 +126,9 @@
ClientConnection* client = it->second;
BufferedFrameDeserializer& frame_deserializer = client->frame_deserializer;
+ auto peer_uid = GetPosixPeerUid(client->sock.get());
+ auto scoped_key = g_crash_key_uid.SetScoped(static_cast<int64_t>(peer_uid));
+
size_t rsize;
do {
auto buf = frame_deserializer.BeginReceive();
@@ -217,8 +223,9 @@
});
}
- service->client_info_ =
- ClientInfo(client->id, GetPosixPeerUid(client->sock.get()));
+ auto peer_uid = GetPosixPeerUid(client->sock.get());
+ auto scoped_key = g_crash_key_uid.SetScoped(static_cast<int64_t>(peer_uid));
+ service->client_info_ = ClientInfo(client->id, peer_uid);
service->received_fd_ = &client->received_fd;
method.invoker(service, *decoded_req_args, std::move(deferred_reply));
service->received_fd_ = nullptr;
@@ -251,6 +258,9 @@
// static
void HostImpl::SendFrame(ClientConnection* client, const Frame& frame, int fd) {
+ auto peer_uid = GetPosixPeerUid(client->sock.get());
+ auto scoped_key = g_crash_key_uid.SetScoped(static_cast<int64_t>(peer_uid));
+
std::string buf = BufferedFrameDeserializer::Serialize(frame);
// When a new Client connects in OnNewClientConnection we set a timeout on
diff --git a/src/trace_processor/metrics/metrics.cc b/src/trace_processor/metrics/metrics.cc
index c84b90d..f0c706b 100644
--- a/src/trace_processor/metrics/metrics.cc
+++ b/src/trace_processor/metrics/metrics.cc
@@ -781,12 +781,13 @@
return base::ErrStatus("Unknown metric %s", name.c_str());
const auto& sql_metric = *metric_it;
- auto queries = base::SplitString(sql_metric.sql, ";\n");
- for (const auto& query : queries) {
- PERFETTO_DLOG("Executing query: %s", query.c_str());
- auto prep_it = tp->ExecuteQuery(query);
- prep_it.Next();
- RETURN_IF_ERROR(prep_it.Status());
+ for (const auto& outer : base::SplitString(sql_metric.sql, ";\n")) {
+ for (const auto& query : base::SplitString(outer, ";\r\n")) {
+ PERFETTO_DLOG("Executing query: %s", query.c_str());
+ auto prep_it = tp->ExecuteQuery(query);
+ prep_it.Next();
+ RETURN_IF_ERROR(prep_it.Status());
+ }
}
auto output_query =
diff --git a/src/traced/probes/ftrace/cpu_reader.cc b/src/traced/probes/ftrace/cpu_reader.cc
index c5f9168..21139c6 100644
--- a/src/traced/probes/ftrace/cpu_reader.cc
+++ b/src/traced/probes/ftrace/cpu_reader.cc
@@ -25,6 +25,7 @@
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/crash_keys.h"
#include "perfetto/ext/base/metatrace.h"
#include "perfetto/ext/base/optional.h"
#include "perfetto/ext/base/utils.h"
@@ -61,6 +62,8 @@
constexpr uint32_t kTypeTimeExtend = 30;
constexpr uint32_t kTypeTimeStamp = 31;
+base::CrashKey g_crash_key_cpu("ftrace_cpu");
+
struct EventHeader {
uint32_t type_or_length : 5;
uint32_t time_delta : 27;
@@ -160,6 +163,7 @@
size_t max_pages,
const std::set<FtraceDataSource*>& started_data_sources) {
PERFETTO_DCHECK(max_pages > 0 && parsing_buf_size_pages > 0);
+ auto scoped_key = g_crash_key_cpu.SetScoped(static_cast<int>(cpu_));
metatrace::ScopedEvent evt(metatrace::TAG_FTRACE,
metatrace::FTRACE_CPU_READ_CYCLE);
diff --git a/tools/write_version_header.py b/tools/write_version_header.py
index e8ddcb9..7dc8783 100755
--- a/tools/write_version_header.py
+++ b/tools/write_version_header.py
@@ -81,6 +81,7 @@
def main():
parser = argparse.ArgumentParser()
+ parser.add_argument('--check_git', action='store_true')
parser.add_argument(
'--no_git',
action='store_true',
@@ -91,6 +92,11 @@
parser.add_argument('--changelog', help='Path to CHANGELOG.')
args = parser.parse_args()
+ if args.check_git:
+ has_git = os.path.exists(os.path.join(PROJECT_ROOT, '.git', 'HEAD'))
+ print('1' if has_git else '0')
+ return 0
+
release = get_latest_release(args.changelog)
if args.no_git: