Merge "Fix flags checks in V4L2Tracker"
diff --git a/BUILD b/BUILD
index 7f3dc0f..bf4ded4 100644
--- a/BUILD
+++ b/BUILD
@@ -1838,6 +1838,7 @@
"src/trace_processor/trace_processor_storage_impl.h",
"src/trace_processor/trace_sorter.cc",
"src/trace_processor/trace_sorter.h",
+ "src/trace_processor/trace_sorter_internal.h",
"src/trace_processor/trace_sorter_queue.h",
"src/trace_processor/virtual_destructors.cc",
],
diff --git a/CHANGELOG b/CHANGELOG
index bdfce0f..b735102 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,15 +2,34 @@
Tracing service and probes:
*
Trace Processor:
- * Scheduling: fixed parsing of "R+" (preempted) and "I" (idle kernel
- thread) end states in |sched.end_state| for traces collected on Linux
- kernels v4.14 and above. Previously, preemption was not recognised, and
- idle was reported as "x" (task dead). See commit c60a630cfe0.
+ *
UI:
*
SDK:
*
+
+v30.0 - 2022-10-06:
+ Trace Processor:
+ * Fixed parsing of "R+" (preempted) and "I" (idle kernel thread) end states
+ of sched_switch events, collected on Linux kernels v4.14 and above.
+ Previously, preemption was not recognised, and idle was reported as
+ "x" (task dead). See commit c60a630cfe0.
+ * Add support for parsing sys_write syscalls.
+ * Remove the thread_slice table: all columns have moved to the slice table
+ and thread_slice exists as a view for backwards compatibility. This view
+ will also be removed in the future
+ * Add Base64 encode SQL function.
+ * Add support for importing function graph ftrace events.
+ * Add support for importing V4L2 ftrace events.
+ * Add support for importing virtio-video ftrace events.
+ UI:
+ * Fix downloading profiles from flamegraphs.
+ * Enable Pivot table support by default.
+ SDK:
+ * Add support for disallowing concurrent tracing sessions.
+
+
v29.0 - 2022-09-06:
Tracing service and probes:
* Add support for only tracing selected syscalls. By selecting only syscalls
diff --git a/meson.build b/meson.build
index 0601514..5295c52 100644
--- a/meson.build
+++ b/meson.build
@@ -18,8 +18,8 @@
project(
'perfetto',
- ['c','cpp'],
- default_options: ['c_std=c99', 'cpp_std=c++11']
+ ['cpp'],
+ default_options: ['cpp_std=c++11']
)
fs = import('fs')
@@ -28,18 +28,32 @@
error('sdk dir not found, please checkout a release tag, e.g. v14.0')
endif
-dep_threads = dependency('threads')
+cpp = meson.get_compiler('cpp')
+
+deps_perfetto = [dependency('threads')]
+
+if host_machine.system() == 'android'
+ deps_perfetto += cpp.find_library('log')
+endif
lib_perfetto = static_library(
'perfetto',
sources: 'sdk/perfetto.cc',
- dependencies: dep_threads,
+ dependencies: deps_perfetto,
install: true,
)
inc_perfetto = include_directories('sdk')
+dir_perfetto_trace = join_paths(meson.current_source_dir(),
+ 'protos/perfetto/trace')
+
+install_data(dir_perfetto_trace / 'perfetto_trace.proto')
+
dep_perfetto = declare_dependency(
link_with: lib_perfetto,
include_directories: inc_perfetto,
+ variables: {
+ 'pkgdatadir': dir_perfetto_trace,
+ }
)
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 3a9e151..6975781 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -164,6 +164,7 @@
"trace_processor_storage_impl.h",
"trace_sorter.cc",
"trace_sorter.h",
+ "trace_sorter_internal.h",
"trace_sorter_queue.h",
"virtual_destructors.cc",
]
diff --git a/src/trace_processor/metrics/sql/android/android_startup.sql b/src/trace_processor/metrics/sql/android/android_startup.sql
index d534274..6204ced 100644
--- a/src/trace_processor/metrics/sql/android/android_startup.sql
+++ b/src/trace_processor/metrics/sql/android/android_startup.sql
@@ -399,6 +399,10 @@
'broadcastReceiveReg*'
) > 10
+ UNION ALL
+ SELECT 'No baseline or cloud profiles'
+ Where MISSING_BASELINE_PROFILE_FOR_LAUNCH(launches.id, launches.package)
+
)
)
) as startup
diff --git a/src/trace_processor/metrics/sql/android/startup/slice_functions.sql b/src/trace_processor/metrics/sql/android/startup/slice_functions.sql
index 509e3bb..bf8048c 100644
--- a/src/trace_processor/metrics/sql/android/startup/slice_functions.sql
+++ b/src/trace_processor/metrics/sql/android/startup/slice_functions.sql
@@ -151,3 +151,28 @@
)
'
);
+
+-- Given a launch id and package name, returns if baseline or cloud profile is missing.
+SELECT CREATE_FUNCTION(
+ 'MISSING_BASELINE_PROFILE_FOR_LAUNCH(launch_id LONG, pkg_name STRING)',
+ 'BOOL',
+ '
+ SELECT (COUNT(slice_name) > 0)
+ FROM (
+ SELECT *
+ FROM SLICES_FOR_LAUNCH_AND_SLICE_NAME(
+ $launch_id,
+ "location=* status=* filter=* reason=*"
+ )
+ ORDER BY slice_name
+ )
+ WHERE
+ -- when location is the package odex file and the reason is "install" or "install-dm",
+ -- if the compilation filter is not "speed-profile", baseline/cloud profile is missing.
+ SUBSTR(STR_SPLIT(slice_name, " status=", 0), LENGTH("location=") + 1)
+ LIKE ("%" || $pkg_name || "%odex")
+ AND (STR_SPLIT(slice_name, " reason=", 1) = "install"
+ OR STR_SPLIT(slice_name, " reason=", 1) = "install-dm")
+ AND STR_SPLIT(STR_SPLIT(slice_name, " filter=", 1), " reason=", 0) != "speed-profile"
+ '
+);
diff --git a/src/trace_processor/parser_types.h b/src/trace_processor/parser_types.h
index b47b3af..848e024 100644
--- a/src/trace_processor/parser_types.h
+++ b/src/trace_processor/parser_types.h
@@ -56,6 +56,9 @@
RefPtr<PacketSequenceStateGeneration> generation)
: TracePacketData{std::move(pv), std::move(generation)} {}
+ explicit TrackEventData(TracePacketData tpd)
+ : TracePacketData(std::move(tpd)) {}
+
static constexpr size_t kMaxNumExtraCounters = 8;
base::Optional<int64_t> thread_timestamp;
diff --git a/src/trace_processor/trace_sorter.h b/src/trace_processor/trace_sorter.h
index 13bff30..4f00832 100644
--- a/src/trace_processor/trace_sorter.h
+++ b/src/trace_processor/trace_sorter.h
@@ -211,7 +211,7 @@
int64_t max_timestamp() const { return global_max_ts_; }
private:
- // Stores offset and type of metadat.
+ // Stores offset and type of metadata.
struct Descriptor {
public:
static constexpr uint8_t kTypeBits = 4;
diff --git a/src/trace_processor/trace_sorter_internal.h b/src/trace_processor/trace_sorter_internal.h
new file mode 100644
index 0000000..9412fca
--- /dev/null
+++ b/src/trace_processor/trace_sorter_internal.h
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2022 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 SRC_TRACE_PROCESSOR_TRACE_SORTER_INTERNAL_H_
+#define SRC_TRACE_PROCESSOR_TRACE_SORTER_INTERNAL_H_
+
+#include <deque>
+#include "perfetto/base/logging.h"
+#include "src/trace_processor/parser_types.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace trace_sorter_internal {
+
+// Moves object to the specified pointer and returns the pointer to the space
+// behind it.
+template <typename T>
+char* AppendUnchecked(char* ptr, T value) {
+ PERFETTO_DCHECK(reinterpret_cast<uintptr_t>(ptr) % alignof(T) == 0);
+ new (ptr) T(std::move(value));
+ return ptr + sizeof(T);
+}
+
+// Evicts object the the specified pointer, which now points to the space behind
+// it.
+template <typename T>
+T EvictUnchecked(char** ptr) {
+ PERFETTO_DCHECK(reinterpret_cast<uintptr_t>(*ptr) % alignof(T) == 0);
+ T* type_ptr = reinterpret_cast<T*>(*ptr);
+ T out(std::move(*type_ptr));
+ type_ptr->~T();
+ *ptr += sizeof(T);
+ return out;
+}
+
+// Stores details of TrackEventData: presence of attributes and the
+// lenght of the array.
+struct TrackEventDataDescriptor {
+ public:
+ static constexpr uint64_t kBitsForCounterValues = 4;
+ static constexpr uint64_t kThreadTimestampMask =
+ 1 << (kBitsForCounterValues + 1);
+ static constexpr uint64_t kThreadInstructionCountMask =
+ 1 << kBitsForCounterValues;
+
+ TrackEventDataDescriptor(bool has_thread_timestamp,
+ bool has_thread_instruction_count,
+ uint64_t number_of_counter_values)
+ : packed_value_(GetPacketValue(has_thread_timestamp,
+ has_thread_instruction_count,
+ number_of_counter_values)) {
+ PERFETTO_DCHECK(number_of_counter_values <=
+ TrackEventData::kMaxNumExtraCounters);
+ }
+
+ explicit TrackEventDataDescriptor(const TrackEventData& ted)
+ : TrackEventDataDescriptor(ted.thread_timestamp.has_value(),
+ ted.thread_instruction_count.has_value(),
+ CountNumberOfCounterValues(ted)) {
+ static_assert(
+ TrackEventData::kMaxNumExtraCounters < (1 << kBitsForCounterValues),
+ "kMaxNumExtraCounters can't be compressed properly");
+ }
+
+ static uint64_t CountNumberOfCounterValues(const TrackEventData& ted) {
+ uint32_t num = 0;
+ for (; num < TrackEventData::kMaxNumExtraCounters; ++num) {
+ if (std::equal_to<double>()(ted.extra_counter_values[num], 0)) {
+ break;
+ }
+ }
+ return num;
+ }
+
+ static uint64_t GetPacketValue(bool has_thread_timestamp,
+ bool has_thread_instruction_count,
+ uint64_t number_of_counter_values) {
+ return (static_cast<uint64_t>(has_thread_timestamp)
+ << (kBitsForCounterValues + 1)) |
+ (static_cast<uint64_t>(has_thread_instruction_count)
+ << kBitsForCounterValues) |
+ number_of_counter_values;
+ }
+
+ bool HasThreadTimestamp() const {
+ return static_cast<bool>(packed_value_ & kThreadTimestampMask);
+ }
+
+ bool HasThreadInstructionCount() const {
+ return static_cast<bool>(packed_value_ & kThreadInstructionCountMask);
+ }
+
+ uint64_t NumberOfCounterValues() const {
+ return static_cast<uint64_t>(
+ packed_value_ & static_cast<uint64_t>(~(3 << kBitsForCounterValues)));
+ }
+
+ uint64_t AppendedSize() const {
+ return sizeof(TracePacketData) +
+ 8l * (/*counter_value*/ 1 + HasThreadTimestamp() +
+ HasThreadInstructionCount() + NumberOfCounterValues());
+ }
+
+ private:
+ // uint8_t would be enough to hold all of the required data, but we need 8
+ // bytes type for alignment.
+ uint64_t packed_value_ = 0;
+};
+
+// Adds and removes object of the type from queue memory. Can be overriden
+// for more specific functionality related to a type. All child classes
+// should implement the same interface.
+template <typename T>
+class TypedMemoryAccessor {
+ public:
+ static char* Append(char* ptr, T value) {
+ return AppendUnchecked(ptr, std::move(value));
+ }
+ static T Evict(char* ptr) { return EvictUnchecked<T>(&ptr); }
+ static uint64_t AppendSize(const T&) {
+ return static_cast<uint64_t>(sizeof(T));
+ }
+};
+
+// Responsibe for accessing memory in the queue related to TrackEventData.
+// Appends the struct more efficiently by compressing and decompressing some
+// of TrackEventData attributes.
+template <>
+class TypedMemoryAccessor<TrackEventData> {
+ public:
+ static char* Append(char* ptr, TrackEventData ted) {
+ auto ted_desc = TrackEventDataDescriptor(ted);
+ ptr = AppendUnchecked(ptr, ted_desc);
+ ptr = AppendUnchecked(ptr, TracePacketData{std::move(ted.packet),
+ std::move(ted.sequence_state)});
+ ptr = AppendUnchecked(ptr, ted.counter_value);
+ if (ted_desc.HasThreadTimestamp()) {
+ ptr = AppendUnchecked(ptr, ted.thread_timestamp.value());
+ }
+ if (ted_desc.HasThreadInstructionCount()) {
+ ptr = AppendUnchecked(ptr, ted.thread_instruction_count.value());
+ }
+ for (uint32_t i = 0; i < ted_desc.NumberOfCounterValues(); i++) {
+ ptr = AppendUnchecked(ptr, ted.extra_counter_values[i]);
+ }
+ return ptr;
+ }
+
+ static TrackEventData Evict(char* ptr) {
+ auto ted_desc = EvictUnchecked<TrackEventDataDescriptor>(&ptr);
+ TrackEventData ted(EvictUnchecked<TracePacketData>(&ptr));
+ ted.counter_value = EvictUnchecked<double>(&ptr);
+ if (ted_desc.HasThreadTimestamp()) {
+ ted.thread_timestamp = EvictUnchecked<int64_t>(&ptr);
+ }
+ if (ted_desc.HasThreadInstructionCount()) {
+ ted.thread_instruction_count = EvictUnchecked<int64_t>(&ptr);
+ }
+ for (uint32_t i = 0; i < ted_desc.NumberOfCounterValues(); i++) {
+ ted.extra_counter_values[i] = EvictUnchecked<double>(&ptr);
+ }
+ return ted;
+ }
+
+ static uint64_t AppendSize(const TrackEventData& value) {
+ return static_cast<uint64_t>(sizeof(TrackEventDataDescriptor)) +
+ TrackEventDataDescriptor(value).AppendedSize();
+ }
+};
+
+} // namespace trace_sorter_internal
+} // namespace trace_processor
+} // namespace perfetto
+#endif // SRC_TRACE_PROCESSOR_TRACE_SORTER_INTERNAL_H_
diff --git a/src/trace_processor/trace_sorter_queue.h b/src/trace_processor/trace_sorter_queue.h
index a5646d1..a1edf28 100644
--- a/src/trace_processor/trace_sorter_queue.h
+++ b/src/trace_processor/trace_sorter_queue.h
@@ -17,9 +17,11 @@
#ifndef SRC_TRACE_PROCESSOR_TRACE_SORTER_QUEUE_H_
#define SRC_TRACE_PROCESSOR_TRACE_SORTER_QUEUE_H_
+#include <cstddef>
#include <deque>
+#include "perfetto/base/logging.h"
#include "perfetto/ext/base/utils.h"
-#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/trace_sorter_internal.h"
namespace perfetto {
namespace trace_processor {
@@ -54,12 +56,12 @@
uint32_t Append(T value) {
PERFETTO_DCHECK(!mem_blocks_.empty());
- if (PERFETTO_UNLIKELY(!mem_blocks_.back().HasSpace<T>())) {
+ uint64_t size = Block::AppendSize<T>(value);
+ if (PERFETTO_UNLIKELY(!mem_blocks_.back().HasSpace(size))) {
mem_blocks_.emplace_back(Block(block_size_));
}
-
auto& back_block = mem_blocks_.back();
- PERFETTO_DCHECK(back_block.HasSpace<T>());
+ PERFETTO_DCHECK(back_block.HasSpace(size));
return GlobalMemOffsetFromLastBlockOffset(
back_block.Append(std::move(value)));
}
@@ -100,36 +102,27 @@
storage_(
base::AlignedAllocTyped<uint64_t>(size_ / sizeof(uint64_t))) {}
- template <typename T>
- bool HasSpace() const {
-#if PERFETTO_DCHECK_IS_ON()
- return sizeof(T) + sizeof(uint64_t) <= size_ - offset_;
-#else
- return sizeof(T) <= size_ - offset_;
-#endif
- }
+ bool HasSpace(uint64_t size) const { return size <= size_ - offset_; }
template <typename T>
uint32_t Append(T value) {
static_assert(alignof(T) <= 8,
"Class must have at most 8 byte alignment");
-
PERFETTO_DCHECK(offset_ % 8 == 0);
- PERFETTO_DCHECK(HasSpace<T>());
+ PERFETTO_DCHECK(HasSpace(AppendSize(value)));
- uint32_t cur_offset = offset_;
char* storage_begin_ptr = reinterpret_cast<char*>(storage_.get());
- char* ptr = storage_begin_ptr + cur_offset;
+ char* ptr = storage_begin_ptr + offset_;
+
#if PERFETTO_DCHECK_IS_ON()
- uint64_t* size_ptr = reinterpret_cast<uint64_t*>(ptr);
- *size_ptr = sizeof(T);
- ptr += sizeof(uint64_t);
+ ptr = AppendUnchecked(ptr, TypedMemoryAccessor<T>::AppendSize(value));
#endif
- new (ptr) T(std::move(value));
+ ptr = TypedMemoryAccessor<T>::Append(ptr, std::move(value));
num_elements_++;
- ptr += sizeof(T);
- offset_ = static_cast<uint32_t>(
- base::AlignUp<8>(static_cast<uint32_t>(ptr - storage_begin_ptr)));
+
+ auto cur_offset = offset_;
+ offset_ = static_cast<uint32_t>(base::AlignUp<8>(static_cast<uint32_t>(
+ ptr - reinterpret_cast<char*>(storage_.get()))));
return cur_offset;
}
@@ -139,16 +132,26 @@
PERFETTO_DCHECK(offset % 8 == 0);
char* ptr = reinterpret_cast<char*>(storage_.get()) + offset;
+ uint64_t size = 0;
#if PERFETTO_DCHECK_IS_ON()
- uint64_t size = *reinterpret_cast<uint64_t*>(ptr);
- PERFETTO_DCHECK(size == sizeof(T));
- ptr += sizeof(uint64_t);
+ size = EvictUnchecked<uint64_t>(&ptr);
#endif
- T* type_ptr = reinterpret_cast<T*>(ptr);
- T out(std::move(*type_ptr));
- type_ptr->~T();
+ T value = TypedMemoryAccessor<T>::Evict(ptr);
+ PERFETTO_DCHECK(size == TypedMemoryAccessor<T>::AppendSize(value));
num_elements_evicted_++;
- return out;
+ return value;
+ }
+
+ template <typename T>
+ static uint64_t AppendSize(const T& value) {
+#if PERFETTO_DCHECK_IS_ON()
+ // On debug runs for each append of T we also append the sizeof(T) to the
+ // queue for sanity check, which we later evict and compare with object
+ // size. This value needs to be added to general size of an object.
+ return sizeof(uint64_t) + TypedMemoryAccessor<T>::AppendSize(value);
+#else
+ return TypedMemoryAccessor<T>::AppendSize(value);
+#endif
}
uint32_t offset() const { return offset_; }
diff --git a/test/trace_processor/startup/android_startup_breakdown.out b/test/trace_processor/startup/android_startup_breakdown.out
index bff5a1f..1ebdfb5 100644
--- a/test/trace_processor/startup/android_startup_breakdown.out
+++ b/test/trace_processor/startup/android_startup_breakdown.out
@@ -82,8 +82,8 @@
optimization_status {
odex_status: "up-to-date"
compilation_filter: "speed"
- compilation_reason: "prebuilt"
- location: "/system/framework/oat/arm/com.android.location.provider.odex"
+ compilation_reason: "install-dm"
+ location: "/system/framework/oat/arm/com.google.android.calendar.odex"
}
optimization_status {
odex_status: "io-error-no-oat"
@@ -107,6 +107,7 @@
slow_start_reason: "Main Thread - Time spent in Runnable state"
slow_start_reason: "Time spent in bindApplication"
slow_start_reason: "Time spent in ResourcesManager#getResources"
+ slow_start_reason: "No baseline or cloud profiles"
startup_type: "cold"
}
}
diff --git a/test/trace_processor/startup/android_startup_breakdown.py b/test/trace_processor/startup/android_startup_breakdown.py
index 852ba95..5a5b4a4 100644
--- a/test/trace_processor/startup/android_startup_breakdown.py
+++ b/test/trace_processor/startup/android_startup_breakdown.py
@@ -91,8 +91,8 @@
ts=to_s(204),
tid=3,
pid=3,
- buf='location=/system/framework/oat/arm/com.android.location.provider' \
- '.odex status=up-to-date filter=speed reason=prebuilt')
+ buf='location=/system/framework/oat/arm/com.google.android.calendar' \
+ '.odex status=up-to-date filter=speed reason=install-dm')
trace.add_atrace_end(ts=to_s(205), tid=3, pid=3)
trace.add_atrace_async_end(
diff --git a/test/trace_processor/startup/android_startup_breakdown_slow.out b/test/trace_processor/startup/android_startup_breakdown_slow.out
index a9d0dac..8be42b3 100644
--- a/test/trace_processor/startup/android_startup_breakdown_slow.out
+++ b/test/trace_processor/startup/android_startup_breakdown_slow.out
@@ -81,8 +81,8 @@
}
optimization_status {
odex_status: "up-to-date"
- compilation_filter: "speed"
- compilation_reason: "prebuilt"
+ compilation_filter: "speed-profile"
+ compilation_reason: "install"
location: "/system/framework/oat/arm/com.android.location.provider.odex"
}
optimization_status {
diff --git a/test/trace_processor/startup/android_startup_breakdown_slow.py b/test/trace_processor/startup/android_startup_breakdown_slow.py
index 7bf9191..06422a2 100644
--- a/test/trace_processor/startup/android_startup_breakdown_slow.py
+++ b/test/trace_processor/startup/android_startup_breakdown_slow.py
@@ -92,7 +92,7 @@
tid=3,
pid=3,
buf='location=/system/framework/oat/arm/com.android.location.provider' \
- '.odex status=up-to-date filter=speed reason=prebuilt')
+ '.odex status=up-to-date filter=speed-profile reason=install')
trace.add_atrace_end(ts=to_s(205), tid=3, pid=3)
trace.add_atrace_async_end(
diff --git a/tools/diff_test_trace_processor.py b/tools/diff_test_trace_processor.py
index aa1454f..1552b2e 100755
--- a/tools/diff_test_trace_processor.py
+++ b/tools/diff_test_trace_processor.py
@@ -291,7 +291,7 @@
else:
result_str += write_cmdlines()
- result_str += f"{red_str}[ FAIL ]{end_color_str} {test_name} "
+ result_str += f"{red_str}[ FAILED ]{end_color_str} {test_name} "
result_str += f"{os.path.basename(trace_path)}\n"
if args.rebase:
@@ -467,66 +467,67 @@
sys.stderr.write(
f"[==========] {len(tests)} tests ran. ({test_time_ms} ms total)\n")
- if test_failures:
+ sys.stderr.write(
+ f"{green(args.no_colors)}[ PASSED ]{end_color(args.no_colors)} "
+ f"{len(tests) - len(test_failures)} tests.\n")
+ if len(test_failures) > 0:
sys.stderr.write(
- f"{red(args.no_colors)}[ PASSED ]{end_color(args.no_colors)} "
- f"{len(tests) - len(test_failures)} tests.\n")
- else:
- sys.stderr.write(
- f"{green(args.no_colors)}[ PASSED ]{end_color(args.no_colors)} "
- f"{len(tests)} tests.\n")
+ f"{red(args.no_colors)}[ FAILED ]{end_color(args.no_colors)} "
+ f"{len(test_failures)} tests.\n")
+ for failure in test_failures:
+ sys.stderr.write(
+ f"{red(args.no_colors)}[ FAILED ]{end_color(args.no_colors)} "
+ f"{failure}\n")
if args.rebase:
sys.stderr.write('\n')
sys.stderr.write(f"{rebased} tests rebased.\n")
- if len(test_failures) == 0:
- if args.perf_file:
- test_dir = os.path.join(ROOT_DIR, 'test')
- trace_processor_dir = os.path.join(test_dir, 'trace_processor')
-
- metrics = []
- sorted_data = sorted(
- perf_data,
- key=lambda x: (x.test_type, x.trace_path, x.query_path_or_metric))
- for perf_args in sorted_data:
- trace_short_path = os.path.relpath(perf_args.trace_path, test_dir)
-
- query_short_path_or_metric = perf_args.query_path_or_metric
- if perf_args.test_type == 'queries':
- query_short_path_or_metric = os.path.relpath(
- perf_args.query_path_or_metric, trace_processor_dir)
-
- metrics.append({
- 'metric': 'tp_perf_test_ingest_time',
- 'value': float(perf_args.ingest_time_ns) / 1.0e9,
- 'unit': 's',
- 'tags': {
- 'test_name': f"{trace_short_path}-{query_short_path_or_metric}",
- 'test_type': perf_args.test_type,
- },
- 'labels': {},
- })
- metrics.append({
- 'metric': 'perf_test_real_time',
- 'value': float(perf_args.real_time_ns) / 1.0e9,
- 'unit': 's',
- 'tags': {
- 'test_name': f"{trace_short_path}-{query_short_path_or_metric}",
- 'test_type': perf_args.test_type,
- },
- 'labels': {},
- })
-
- output_data = {'metrics': metrics}
- with open(args.perf_file, 'w+') as perf_file:
- perf_file.write(json.dumps(output_data, indent=2))
- return 0
- else:
- for failure in test_failures:
- sys.stderr.write(f"[ FAILED ] {failure}\n")
+ if len(test_failures) > 0:
return 1
+ if args.perf_file:
+ test_dir = os.path.join(ROOT_DIR, 'test')
+ trace_processor_dir = os.path.join(test_dir, 'trace_processor')
+
+ metrics = []
+ sorted_data = sorted(
+ perf_data,
+ key=lambda x: (x.test_type, x.trace_path, x.query_path_or_metric))
+ for perf_args in sorted_data:
+ trace_short_path = os.path.relpath(perf_args.trace_path, test_dir)
+
+ query_short_path_or_metric = perf_args.query_path_or_metric
+ if perf_args.test_type == 'queries':
+ query_short_path_or_metric = os.path.relpath(
+ perf_args.query_path_or_metric, trace_processor_dir)
+
+ metrics.append({
+ 'metric': 'tp_perf_test_ingest_time',
+ 'value': float(perf_args.ingest_time_ns) / 1.0e9,
+ 'unit': 's',
+ 'tags': {
+ 'test_name': f"{trace_short_path}-{query_short_path_or_metric}",
+ 'test_type': perf_args.test_type,
+ },
+ 'labels': {},
+ })
+ metrics.append({
+ 'metric': 'perf_test_real_time',
+ 'value': float(perf_args.real_time_ns) / 1.0e9,
+ 'unit': 's',
+ 'tags': {
+ 'test_name': f"{trace_short_path}-{query_short_path_or_metric}",
+ 'test_type': perf_args.test_type,
+ },
+ 'labels': {},
+ })
+
+ output_data = {'metrics': metrics}
+ with open(args.perf_file, 'w+') as perf_file:
+ perf_file.write(json.dumps(output_data, indent=2))
+ return 0
+
if __name__ == '__main__':
sys.exit(main())
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index 059dcb7..31b19db 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -311,6 +311,8 @@
width: 100%;
text-align: right;
line-height: 1;
+ display: block; // Required in order for inherited white-space property not
+ // to screw up vertical rendering of the popup menu items.
&:hover {
background: #c7d0db;
@@ -797,6 +799,13 @@
td.menu {
text-align: left;
}
+
+ td {
+ // In context menu icon to go on a separate line.
+ // In regular pivot table cells, avoids wrapping the icon spacer to go on
+ // a separate line.
+ white-space: pre;
+ }
}
.name-completion {
diff --git a/ui/src/common/recordingV2/adb_connection_impl.ts b/ui/src/common/recordingV2/adb_connection_impl.ts
index 98a10b1..9fae926 100644
--- a/ui/src/common/recordingV2/adb_connection_impl.ts
+++ b/ui/src/common/recordingV2/adb_connection_impl.ts
@@ -42,7 +42,7 @@
// We wait for the stream to be closed by the device, which happens
// after the shell command is successfully received.
- adbStream.addOnStreamClose(() => {
+ adbStream.addOnStreamCloseCallback(() => {
onStreamingEnded.resolve();
});
return onStreamingEnded;
@@ -55,10 +55,10 @@
const commandOutput = new ArrayBufferBuilder();
const onStreamingEnded = defer<string>();
- adbStream.addOnStreamData((data: Uint8Array) => {
+ adbStream.addOnStreamDataCallback((data: Uint8Array) => {
commandOutput.append(data);
});
- adbStream.addOnStreamClose(() => {
+ adbStream.addOnStreamCloseCallback(() => {
onStreamingEnded.resolve(
textDecoder.decode(commandOutput.toArrayBuffer()));
});
diff --git a/ui/src/common/recordingV2/adb_connection_over_websocket.ts b/ui/src/common/recordingV2/adb_connection_over_websocket.ts
index 529103b..7287b65 100644
--- a/ui/src/common/recordingV2/adb_connection_over_websocket.ts
+++ b/ui/src/common/recordingV2/adb_connection_over_websocket.ts
@@ -114,11 +114,11 @@
this.websocket.onclose = this.onClose.bind(this);
}
- addOnStreamData(onStreamData: OnStreamDataCallback) {
+ addOnStreamDataCallback(onStreamData: OnStreamDataCallback) {
this.onStreamDataCallbacks.push(onStreamData);
}
- addOnStreamClose(onStreamClose: OnStreamCloseCallback) {
+ addOnStreamCloseCallback(onStreamClose: OnStreamCloseCallback) {
this.onStreamCloseCallbacks.push(onStreamClose);
}
diff --git a/ui/src/common/recordingV2/adb_connection_over_webusb.ts b/ui/src/common/recordingV2/adb_connection_over_webusb.ts
index 2a121ae..42686bb 100644
--- a/ui/src/common/recordingV2/adb_connection_over_webusb.ts
+++ b/ui/src/common/recordingV2/adb_connection_over_webusb.ts
@@ -465,11 +465,11 @@
this._isConnected = true;
}
- addOnStreamData(onStreamData: OnStreamDataCallback): void {
+ addOnStreamDataCallback(onStreamData: OnStreamDataCallback): void {
this.onStreamDataCallbacks.push(onStreamData);
}
- addOnStreamClose(onStreamClose: OnStreamCloseCallback): void {
+ addOnStreamCloseCallback(onStreamClose: OnStreamCloseCallback): void {
this.onStreamCloseCallbacks.push(onStreamClose);
}
diff --git a/ui/src/common/recordingV2/adb_file_handler.ts b/ui/src/common/recordingV2/adb_file_handler.ts
index 17d4c2d..d659adb 100644
--- a/ui/src/common/recordingV2/adb_file_handler.ts
+++ b/ui/src/common/recordingV2/adb_file_handler.ts
@@ -51,9 +51,9 @@
this.isPushOngoing = true;
const transferFinished = defer<void>();
- this.byteStream.addOnStreamData(
+ this.byteStream.addOnStreamDataCallback(
(data) => this.onStreamData(data, transferFinished));
- this.byteStream.addOnStreamClose(() => this.isPushOngoing = false);
+ this.byteStream.addOnStreamCloseCallback(() => this.isPushOngoing = false);
const sendMessage = new ArrayBufferBuilder();
// 'SEND' is the API method used to send a file to device.
diff --git a/ui/src/common/recordingV2/host_os_byte_stream.ts b/ui/src/common/recordingV2/host_os_byte_stream.ts
new file mode 100644
index 0000000..dd46ba8
--- /dev/null
+++ b/ui/src/common/recordingV2/host_os_byte_stream.ts
@@ -0,0 +1,84 @@
+// Copyright (C) 2022 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 {defer} from '../../base/deferred';
+
+import {
+ ByteStream,
+ OnStreamCloseCallback,
+ OnStreamDataCallback,
+} from './recording_interfaces_v2';
+
+// A HostOsByteStream instantiates a websocket connection to the host OS.
+// It exposes an API to write commands to this websocket and read its output.
+export class HostOsByteStream implements ByteStream {
+ // handshakeSignal will be resolved with the stream when the websocket
+ // connection becomes open.
+ private handshakeSignal = defer<HostOsByteStream>();
+ private _isConnected: boolean = false;
+ private websocket: WebSocket;
+ private onStreamDataCallbacks: OnStreamDataCallback[] = [];
+ private onStreamCloseCallbacks: OnStreamCloseCallback[] = [];
+
+ private constructor(websocketUrl: string) {
+ this.websocket = new WebSocket(websocketUrl);
+ this.websocket.onmessage = this.onMessage.bind(this);
+ this.websocket.onopen = this.onOpen.bind(this);
+ }
+
+ addOnStreamDataCallback(onStreamData: OnStreamDataCallback): void {
+ this.onStreamDataCallbacks.push(onStreamData);
+ }
+
+ addOnStreamCloseCallback(onStreamClose: OnStreamCloseCallback): void {
+ this.onStreamCloseCallbacks.push(onStreamClose);
+ }
+
+ close(): void {
+ this.websocket.close();
+ for (const onStreamClose of this.onStreamCloseCallbacks) {
+ onStreamClose();
+ }
+ this.onStreamDataCallbacks = [];
+ this.onStreamCloseCallbacks = [];
+ }
+
+ async closeAndWaitForTeardown(): Promise<void> {
+ this.close();
+ }
+
+ isConnected(): boolean {
+ return this._isConnected;
+ }
+
+ write(msg: string|Uint8Array): void {
+ this.websocket.send(msg);
+ }
+
+ private async onMessage(evt: MessageEvent) {
+ for (const onStreamData of this.onStreamDataCallbacks) {
+ const arrayBufferResponse = await evt.data.arrayBuffer();
+ onStreamData(new Uint8Array(arrayBufferResponse));
+ }
+ }
+
+ private onOpen() {
+ this._isConnected = true;
+ this.handshakeSignal.resolve(this);
+ }
+
+ static create(websocketUrl: string): Promise<HostOsByteStream> {
+ return (new HostOsByteStream(websocketUrl)).handshakeSignal;
+ }
+}
diff --git a/ui/src/common/recordingV2/recording_interfaces_v2.ts b/ui/src/common/recordingV2/recording_interfaces_v2.ts
index 6929463..974d277 100644
--- a/ui/src/common/recordingV2/recording_interfaces_v2.ts
+++ b/ui/src/common/recordingV2/recording_interfaces_v2.ts
@@ -78,13 +78,13 @@
targetType: 'CHROME'|'CHROME_OS';
}
-export interface LinuxTargetInfo extends TargetInfoBase {
- targetType: 'LINUX';
+export interface HostOsTargetInfo extends TargetInfoBase {
+ targetType: 'LINUX'|'MACOS';
}
// Holds information about a target. It's used by the UI and the logic which
// generates a config.
-export type TargetInfo = AndroidTargetInfo|ChromeTargetInfo|LinuxTargetInfo;
+export type TargetInfo = AndroidTargetInfo|ChromeTargetInfo|HostOsTargetInfo;
// RecordingTargetV2 is subclassed by Android devices and the Chrome browser/OS.
// It creates tracing sessions which are used by the UI. For Android, it manages
@@ -166,8 +166,8 @@
export interface ByteStream {
// The caller can add callbacks, to be executed when the stream receives new
// data or when it finished closing itself.
- addOnStreamData(onStreamData: OnStreamDataCallback): void;
- addOnStreamClose(onStreamClose: OnStreamCloseCallback): void;
+ addOnStreamDataCallback(onStreamData: OnStreamDataCallback): void;
+ addOnStreamCloseCallback(onStreamClose: OnStreamCloseCallback): void;
isConnected(): boolean;
write(data: string|Uint8Array): void;
diff --git a/ui/src/common/recordingV2/recording_page_controller.ts b/ui/src/common/recordingV2/recording_page_controller.ts
index c2e0367..8c7d856 100644
--- a/ui/src/common/recordingV2/recording_page_controller.ts
+++ b/ui/src/common/recordingV2/recording_page_controller.ts
@@ -17,6 +17,7 @@
import {autosaveConfigStore} from '../../frontend/record_config';
import {
DEFAULT_ADB_WEBSOCKET_URL,
+ DEFAULT_TRACED_WEBSOCKET_URL,
} from '../../frontend/recording/recording_ui_utils';
import {
couldNotClaimInterface,
@@ -45,6 +46,10 @@
import {
ANDROID_WEBUSB_TARGET_FACTORY,
} from './target_factories/android_webusb_target_factory';
+import {
+ HOST_OS_TARGET_FACTORY,
+ HostOsTargetFactory,
+} from './target_factories/host_os_target_factory';
import {targetFactoryRegistry} from './target_factory_registry';
// The recording page can be in any of these states. It can transition between
@@ -453,6 +458,13 @@
AndroidWebsocketTargetFactory;
websocketTargetFactory.tryEstablishWebsocket(DEFAULT_ADB_WEBSOCKET_URL);
}
+ if (targetFactoryRegistry.has(HOST_OS_TARGET_FACTORY)) {
+ const websocketTargetFactory =
+ targetFactoryRegistry.get(HOST_OS_TARGET_FACTORY) as
+ HostOsTargetFactory;
+ websocketTargetFactory.tryEstablishWebsocket(
+ DEFAULT_TRACED_WEBSOCKET_URL);
+ }
}
shouldShowTargetSelection(): boolean {
diff --git a/ui/src/common/recordingV2/recording_utils.ts b/ui/src/common/recordingV2/recording_utils.ts
index eed891e..72d2761 100644
--- a/ui/src/common/recordingV2/recording_utils.ts
+++ b/ui/src/common/recordingV2/recording_utils.ts
@@ -29,6 +29,28 @@
return hdr + cmd;
}
+// Sample user agent for Chrome on Mac OS:
+// 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
+// (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'
+export function isMacOs(userAgent: string) {
+ return userAgent.toLowerCase().includes(' mac os ');
+}
+
+// Sample user agent for Chrome on Linux:
+// Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)
+// Chrome/105.0.0.0 Safari/537.36
+export function isLinux(userAgent: string) {
+ return userAgent.toLowerCase().includes(' linux ');
+}
+
+// Sample user agent for Chrome on Chrome OS:
+// "Mozilla/5.0 (X11; CrOS x86_64 14816.99.0) AppleWebKit/537.36
+// (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36"
+// This condition is wider, in the unlikely possibility of different casing,
+export function isCrOS(userAgent: string) {
+ return userAgent.toLowerCase().includes(' cros ');
+}
+
// End Websocket //////////////////////////////////////////////////////////
// Begin Adb //////////////////////////////////////////////////////////////
diff --git a/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts b/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts
index 8ac448d..1650934 100644
--- a/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts
+++ b/ui/src/common/recordingV2/target_factories/chrome_target_factory.ts
@@ -18,20 +18,16 @@
RecordingTargetV2,
TargetFactory,
} from '../recording_interfaces_v2';
-import {EXTENSION_ID, EXTENSION_NOT_INSTALLED} from '../recording_utils';
+import {
+ EXTENSION_ID,
+ EXTENSION_NOT_INSTALLED,
+ isCrOS,
+} from '../recording_utils';
import {targetFactoryRegistry} from '../target_factory_registry';
import {ChromeTarget} from '../targets/chrome_target';
export const CHROME_TARGET_FACTORY = 'ChromeTargetFactory';
-// Sample user agent for Chrome on Chrome OS:
-// "Mozilla/5.0 (X11; CrOS x86_64 14816.99.0) AppleWebKit/537.36
-// (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36"
-// This condition is wider, in the unlikely possibility of different casing,
-export function isCrOS(userAgent: string) {
- return userAgent.toLowerCase().includes(' cros ');
-}
-
export class ChromeTargetFactory implements TargetFactory {
readonly kind = CHROME_TARGET_FACTORY;
// We only check the connection once at the beginning to:
diff --git a/ui/src/common/recordingV2/target_factories/chrome_target_factory_unittest.ts b/ui/src/common/recordingV2/target_factories/chrome_target_factory_unittest.ts
index 478a3f4..c12365d 100644
--- a/ui/src/common/recordingV2/target_factories/chrome_target_factory_unittest.ts
+++ b/ui/src/common/recordingV2/target_factories/chrome_target_factory_unittest.ts
@@ -12,17 +12,29 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {isCrOS} from './chrome_target_factory';
+import {isCrOS, isLinux, isMacOs} from '../recording_utils';
test('parse Chrome on Chrome OS user agent', () => {
const userAgent = 'Mozilla/5.0 (X11; CrOS x86_64 14816.99.0) ' +
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 ' +
'Safari/537.36';
expect(isCrOS(userAgent)).toBe(true);
+ expect(isMacOs(userAgent)).toBe(false);
+ expect(isLinux(userAgent)).toBe(false);
});
test('parse Chrome on Mac user agent', () => {
const userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' +
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36';
expect(isCrOS(userAgent)).toBe(false);
+ expect(isMacOs(userAgent)).toBe(true);
+ expect(isLinux(userAgent)).toBe(false);
+});
+
+test('parse Chrome on Linux user agent', () => {
+ const userAgent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 ' +
+ '(KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36';
+ expect(isCrOS(userAgent)).toBe(false);
+ expect(isMacOs(userAgent)).toBe(false);
+ expect(isLinux(userAgent)).toBe(true);
});
diff --git a/ui/src/common/recordingV2/target_factories/host_os_target_factory.ts b/ui/src/common/recordingV2/target_factories/host_os_target_factory.ts
new file mode 100644
index 0000000..d7342c0
--- /dev/null
+++ b/ui/src/common/recordingV2/target_factories/host_os_target_factory.ts
@@ -0,0 +1,81 @@
+// Copyright (C) 2022 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 {RecordingError} from '../recording_error_handling';
+import {
+ OnTargetChangeCallback,
+ RecordingTargetV2,
+ TargetFactory,
+} from '../recording_interfaces_v2';
+import {isLinux, isMacOs} from '../recording_utils';
+import {targetFactoryRegistry} from '../target_factory_registry';
+import {HostOsTarget} from '../targets/host_os_target';
+
+export const HOST_OS_TARGET_FACTORY = 'HostOsTargetFactory';
+
+export class HostOsTargetFactory implements TargetFactory {
+ readonly kind = HOST_OS_TARGET_FACTORY;
+ private target?: HostOsTarget;
+ private onTargetChange: OnTargetChangeCallback = () => {};
+
+ connectNewTarget(): Promise<RecordingTargetV2> {
+ throw new RecordingError(
+ 'Can not create a new Host OS target.' +
+ 'The Host OS target is created at factory initialisation.');
+ }
+
+ getName(): string {
+ return 'HostOs';
+ }
+
+ listRecordingProblems(): string[] {
+ return [];
+ }
+
+ listTargets(): RecordingTargetV2[] {
+ if (this.target) {
+ return [this.target];
+ }
+ return [];
+ }
+
+ tryEstablishWebsocket(websocketUrl: string) {
+ if (this.target) {
+ if (this.target.getUrl() === websocketUrl) {
+ return;
+ } else {
+ this.target.disconnect();
+ }
+ }
+ this.target = new HostOsTarget(
+ websocketUrl, this.maybeClearTarget.bind(this), this.onTargetChange);
+ this.onTargetChange();
+ }
+
+ maybeClearTarget(target: HostOsTarget): void {
+ if (this.target === target) {
+ this.target = undefined;
+ this.onTargetChange();
+ }
+ }
+
+ setOnTargetChange(onTargetChange: OnTargetChangeCallback): void {
+ this.onTargetChange = onTargetChange;
+ }
+}
+
+// We instantiate the host target factory only on Mac and Linux.
+if (isMacOs(navigator.userAgent) || isLinux(navigator.userAgent)) {
+ targetFactoryRegistry.register(new HostOsTargetFactory());
+}
diff --git a/ui/src/common/recordingV2/target_factories/index.ts b/ui/src/common/recordingV2/target_factories/index.ts
index 3386d5b..3c1e3af 100644
--- a/ui/src/common/recordingV2/target_factories/index.ts
+++ b/ui/src/common/recordingV2/target_factories/index.ts
@@ -15,4 +15,5 @@
import './android_webusb_target_factory';
import './android_websocket_target_factory';
import './chrome_target_factory';
+import './host_os_target_factory';
import './virtual_target_factory';
diff --git a/ui/src/common/recordingV2/targets/android_websocket_target.ts b/ui/src/common/recordingV2/targets/android_websocket_target.ts
index ed085d9..ab4f130 100644
--- a/ui/src/common/recordingV2/targets/android_websocket_target.ts
+++ b/ui/src/common/recordingV2/targets/android_websocket_target.ts
@@ -33,7 +33,7 @@
targetType: 'ANDROID',
// 'androidApiLevel' will be populated after ADB authorization.
androidApiLevel: this.androidApiLevel,
- dataSources: [],
+ dataSources: this.dataSources || [],
name: this.serialNumber + ' WebSocket',
};
}
diff --git a/ui/src/common/recordingV2/targets/host_os_target.ts b/ui/src/common/recordingV2/targets/host_os_target.ts
new file mode 100644
index 0000000..112c984
--- /dev/null
+++ b/ui/src/common/recordingV2/targets/host_os_target.ts
@@ -0,0 +1,133 @@
+// Copyright (C) 2022 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 {HostOsByteStream} from '../host_os_byte_stream';
+import {RecordingError} from '../recording_error_handling';
+import {
+ DataSource,
+ HostOsTargetInfo,
+ OnDisconnectCallback,
+ OnTargetChangeCallback,
+ RecordingTargetV2,
+ TracingSession,
+ TracingSessionListener,
+} from '../recording_interfaces_v2';
+import {
+ isLinux,
+ isMacOs,
+ WEBSOCKET_CLOSED_ABNORMALLY_CODE,
+} from '../recording_utils';
+import {TracedTracingSession} from '../traced_tracing_session';
+
+export class HostOsTarget implements RecordingTargetV2 {
+ private readonly targetType: 'LINUX'|'MACOS';
+ private readonly name: string;
+ private websocket: WebSocket;
+ private streams = new Set<HostOsByteStream>();
+ private dataSources?: DataSource[];
+ private onDisconnect: OnDisconnectCallback = (_) => {};
+
+ constructor(
+ websocketUrl: string,
+ private maybeClearTarget: (target: HostOsTarget) => void,
+ private onTargetChange: OnTargetChangeCallback) {
+ if (isMacOs(navigator.userAgent)) {
+ this.name = 'MacOS';
+ this.targetType = 'MACOS';
+ } else if (isLinux(navigator.userAgent)) {
+ this.name = 'Linux';
+ this.targetType = 'LINUX';
+ } else {
+ throw new RecordingError(
+ 'Host OS target created on an unsupported operating system.');
+ }
+
+ this.websocket = new WebSocket(websocketUrl);
+ this.websocket.onclose = this.onClose.bind(this);
+ }
+
+ getInfo(): HostOsTargetInfo {
+ return {
+ targetType: this.targetType,
+ name: this.name,
+ dataSources: this.dataSources || [],
+ };
+ }
+
+ canCreateTracingSession(): boolean {
+ return true;
+ }
+
+ async createTracingSession(tracingSessionListener: TracingSessionListener):
+ Promise<TracingSession> {
+ this.onDisconnect = tracingSessionListener.onDisconnect;
+
+ const osStream = await HostOsByteStream.create(this.getUrl());
+ this.streams.add(osStream);
+ const tracingSession =
+ new TracedTracingSession(osStream, tracingSessionListener);
+ await tracingSession.initConnection();
+
+ if (!this.dataSources) {
+ this.dataSources = await tracingSession.queryServiceState();
+ this.onTargetChange();
+ }
+ return tracingSession;
+ }
+
+ // Starts a tracing session in order to fetch data sources from the
+ // device. Then, it cancels the session.
+ async fetchTargetInfo(tracingSessionListener: TracingSessionListener):
+ Promise<void> {
+ const tracingSession =
+ await this.createTracingSession(tracingSessionListener);
+ tracingSession.cancel();
+ }
+
+ async disconnect(): Promise<void> {
+ if (this.websocket.readyState === this.websocket.OPEN) {
+ this.websocket.close();
+ // We remove the 'onclose' callback so the 'disconnect' method doesn't get
+ // executed twice.
+ this.websocket.onclose = null;
+ }
+ for (const stream of this.streams) {
+ stream.close();
+ }
+ // We remove the existing target from the factory if present.
+ this.maybeClearTarget(this);
+ // We run the onDisconnect callback in case this target is used for tracing.
+ this.onDisconnect();
+ }
+
+ // We can connect to the Host OS without taking the connection away from
+ // another process.
+ async canConnectWithoutContention(): Promise<boolean> {
+ return true;
+ }
+
+ getUrl() {
+ return this.websocket.url;
+ }
+
+ private onClose(ev: CloseEvent): void {
+ if (ev.code === WEBSOCKET_CLOSED_ABNORMALLY_CODE) {
+ console.info(
+ `It's safe to ignore the 'WebSocket connection to ${
+ this.getUrl()} error above, if present. It occurs when ` +
+ 'checking the connection to the local Websocket server.');
+ }
+ this.disconnect();
+ }
+}
diff --git a/ui/src/common/recordingV2/traced_tracing_session.ts b/ui/src/common/recordingV2/traced_tracing_session.ts
index f898c8f..c8a1d10 100644
--- a/ui/src/common/recordingV2/traced_tracing_session.ts
+++ b/ui/src/common/recordingV2/traced_tracing_session.ts
@@ -110,8 +110,9 @@
constructor(
private byteStream: ByteStream,
private tracingSessionListener: TracingSessionListener) {
- this.byteStream.addOnStreamData((data) => this.handleReceivedData(data));
- this.byteStream.addOnStreamClose(() => this.clearState());
+ this.byteStream.addOnStreamDataCallback(
+ (data) => this.handleReceivedData(data));
+ this.byteStream.addOnStreamCloseCallback(() => this.clearState());
}
queryServiceState(): Promise<DataSource[]> {
diff --git a/ui/src/frontend/recording/recording_ui_utils.ts b/ui/src/frontend/recording/recording_ui_utils.ts
index 7c91a4a..97a5e24 100644
--- a/ui/src/frontend/recording/recording_ui_utils.ts
+++ b/ui/src/frontend/recording/recording_ui_utils.ts
@@ -25,6 +25,7 @@
export const FORCE_RESET_MESSAGE = 'Force reset the USB interface';
export const DEFAULT_ADB_WEBSOCKET_URL = 'ws://127.0.0.1:8037/adb';
+export const DEFAULT_TRACED_WEBSOCKET_URL = 'ws://127.0.0.1:8037/traced';
export function getWebsocketTargetFactory(): AndroidWebsocketTargetFactory {
return targetFactoryRegistry.get(ANDROID_WEBSOCKET_TARGET_FACTORY) as
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index ff64c2f..e1be6a7 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -628,7 +628,11 @@
fileName = url.split('/').slice(-1)[0];
} else if (src.type === 'ARRAY_BUFFER') {
const blob = new Blob([src.buffer], {type: 'application/octet-stream'});
- if (src.fileName) {
+ const inputFileName =
+ window.prompt('Please enter a name for your file or leave blank');
+ if (inputFileName) {
+ fileName = `${inputFileName}.perfetto_trace.gz`;
+ } else if (src.fileName) {
fileName = src.fileName;
}
url = URL.createObjectURL(blob);