tp: add terminal-aware progress and color controls Share throttled stderr progress reporting across trace loading, bundling, and conversions. Pass --no-progress explicitly through callers, preserve ordinary diagnostics, and support FORCE_COLOR and NO_COLOR independently of progress.
diff --git a/Android.bp b/Android.bp index bc9d489..e7e6672 100644 --- a/Android.bp +++ b/Android.bp
@@ -15952,6 +15952,7 @@ "src/base/paged_memory.cc", "src/base/periodic_task.cc", "src/base/pipe.cc", + "src/base/progress_reporter.cc", "src/base/rt_mutex.cc", "src/base/scoped_mmap.cc", "src/base/scoped_sched_boost.cc", @@ -16069,6 +16070,7 @@ "src/base/no_destructor_unittest.cc", "src/base/paged_memory_unittest.cc", "src/base/periodic_task_unittest.cc", + "src/base/progress_reporter_unittest.cc", "src/base/regex/regex_unittest.cc", "src/base/rt_mutex_unittest.cc", "src/base/scoped_file_unittest.cc",
diff --git a/BUILD b/BUILD index 43be48b..6e664d7 100644 --- a/BUILD +++ b/BUILD
@@ -1436,6 +1436,7 @@ "include/perfetto/ext/base/periodic_task.h", "include/perfetto/ext/base/pipe.h", "include/perfetto/ext/base/platform.h", + "include/perfetto/ext/base/progress_reporter.h", "include/perfetto/ext/base/rt_mutex.h", "include/perfetto/ext/base/scoped_file.h", "include/perfetto/ext/base/scoped_mmap.h", @@ -1977,6 +1978,7 @@ "src/base/paged_memory.cc", "src/base/periodic_task.cc", "src/base/pipe.cc", + "src/base/progress_reporter.cc", "src/base/rt_mutex.cc", "src/base/scoped_mmap.cc", "src/base/scoped_sched_boost.cc",
diff --git a/docs/learning-more/symbolization.md b/docs/learning-more/symbolization.md index 25a388e..f0eeaf1 100644 --- a/docs/learning-more/symbolization.md +++ b/docs/learning-more/symbolization.md
@@ -112,8 +112,17 @@ `PERFETTO_BINARY_PATH` still apply; unset that variable to restrict lookup to `--symbol-paths`. +For scripts that capture diagnostics, redirect stderr to a log: + +```bash +trace_processor bundle input.perfetto-trace enriched-trace 2>bundle.log +``` + +Live progress is suppressed when stderr is redirected. Use `--no-progress` to +suppress it when running in a terminal too. + See the [bundle command reference](/docs/reference/trace-processor-cli.md#subcommand-bundle) -for option semantics, symbol search paths, and exit status. +for option semantics, color controls, and exit status. ### {#option-2-legacy-traceconv-symbolize-deobfuscate} Option 2: Legacy `trace_processor util symbolize` / `util deobfuscate`
diff --git a/docs/reference/trace-processor-cli.md b/docs/reference/trace-processor-cli.md index 8f6055c..5c90ab6 100644 --- a/docs/reference/trace-processor-cli.md +++ b/docs/reference/trace-processor-cli.md
@@ -30,6 +30,8 @@ and behave the same across all subcommands: - **Help and version:** `-h, --help`, `-v, --version`. +- **Progress:** `--no-progress` disables live progress, preserving summaries, + warnings, and errors. - **Trace ingestion:** `--full-sort`, `--no-ftrace-raw`, `--analyze-trace-proto-content`, `--crop-track-events`. - **PerfettoSQL packages:** `--add-sql-package PATH[@PKG]`, @@ -44,6 +46,25 @@ trace processor itself, which you can load back into the UI for performance debugging. +## Progress and diagnostics + +Diagnostics go to stderr. Live progress is displayed only when stderr is a +terminal and `TERM` is not `dumb`. Redirected stderr contains ordinary messages +without progress redraws. `--no-progress` suppresses live progress independently +of verbosity and color; summaries, warnings, and errors remain enabled. + +## Color environment variables + +| Environment | Behavior | +| --- | --- | +| Nonempty `FORCE_COLOR` | Force ANSI color, including when stderr is redirected. Takes precedence over `NO_COLOR`. | +| Nonempty `NO_COLOR` | Disable automatic color. | +| Neither | On POSIX, use terminal detection and disable automatic color for `TERM=dumb`. On Windows, automatic ANSI color is disabled. | + +Empty values are ignored. Any nonempty value counts, including `0`, following +[FORCE_COLOR](https://force-color.org/) and [NO_COLOR](https://no-color.org/). +Forcing color does not enable progress redraws. + ## {#subcommands} Commands | Command | Purpose | @@ -230,7 +251,8 @@ Formats are `systrace`, `json`, `ctrace`, `text`, `profile`, and `firefox`. Omitted input and output paths use stdin and stdout. For `profile`, use -`--output-dir` instead of an output-file argument. Run `help convert` for +`--output-dir` instead of an output-file argument. `--no-progress` applies +to conversion progress as well as trace loading. Run `help convert` for format-specific options. ### {#subcommand-util} `util`: low-level trace utilities
diff --git a/include/perfetto/ext/base/BUILD.gn b/include/perfetto/ext/base/BUILD.gn index 6539089..9fc0876 100644 --- a/include/perfetto/ext/base/BUILD.gn +++ b/include/perfetto/ext/base/BUILD.gn
@@ -48,6 +48,7 @@ "periodic_task.h", "pipe.h", "platform.h", + "progress_reporter.h", "rt_mutex.h", "scoped_file.h", "scoped_mmap.h",
diff --git a/include/perfetto/ext/base/file_utils.h b/include/perfetto/ext/base/file_utils.h index a69c2ec..a5a8353 100644 --- a/include/perfetto/ext/base/file_utils.h +++ b/include/perfetto/ext/base/file_utils.h
@@ -101,6 +101,9 @@ bool FlushFile(int fd); +// Duplicates an open descriptor. The duplicate is not inherited across exec. +ScopedFile DupFile(int fd); + // Moves the file offset to |offset| bytes from the beginning of the file. // Returns false if |offset| cannot be represented by the platform or the seek // fails.
diff --git a/include/perfetto/ext/base/progress_reporter.h b/include/perfetto/ext/base/progress_reporter.h new file mode 100644 index 0000000..146e918 --- /dev/null +++ b/include/perfetto/ext/base/progress_reporter.h
@@ -0,0 +1,51 @@ +/* + * Copyright (C) 2026 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_PROGRESS_REPORTER_H_ +#define INCLUDE_PERFETTO_EXT_BASE_PROGRESS_REPORTER_H_ + +#include <cstdint> +#include <string> + +namespace perfetto::base { + +bool StderrSupportsProgress(); +bool StderrSupportsColor(); + +// A single-line, throttled stderr display. Updates must be plain ASCII without +// terminal escapes. No output is produced for redirected stderr or TERM=dumb. +// Use Clear() before ordinary stdio diagnostics; Perfetto logs clear it +// automatically. This class does not print a final summary. +class ProgressReporter { + public: + explicit ProgressReporter(bool enabled = true); + ~ProgressReporter(); + ProgressReporter(const ProgressReporter&) = delete; + ProgressReporter& operator=(const ProgressReporter&) = delete; + + void Update(const std::string& message); + void Clear(); + static void ClearBeforeLog(); + + private: + void ClearLocked(); + bool enabled_; + int64_t last_update_ms_ = 0; + size_t visible_width_ = 0; +}; + +} // namespace perfetto::base +#endif // INCLUDE_PERFETTO_EXT_BASE_PROGRESS_REPORTER_H_
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn index bc99f55..8866d32 100644 --- a/src/base/BUILD.gn +++ b/src/base/BUILD.gn
@@ -51,6 +51,7 @@ "paged_memory.cc", "periodic_task.cc", "pipe.cc", + "progress_reporter.cc", "rt_mutex.cc", "scoped_mmap.cc", "scoped_sched_boost.cc", @@ -244,6 +245,7 @@ "no_destructor_unittest.cc", "paged_memory_unittest.cc", "periodic_task_unittest.cc", + "progress_reporter_unittest.cc", "regex/regex_unittest.cc", "rt_mutex_unittest.cc", "scoped_file_unittest.cc",
diff --git a/src/base/file_utils.cc b/src/base/file_utils.cc index fc4b53a..a0d8521 100644 --- a/src/base/file_utils.cc +++ b/src/base/file_utils.cc
@@ -266,6 +266,19 @@ #endif } +ScopedFile DupFile(int fd) { +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + ScopedFile duplicate(_dup(fd)); + if (duplicate && !SetHandleInformation( + reinterpret_cast<HANDLE>(_get_osfhandle(*duplicate)), + HANDLE_FLAG_INHERIT, 0)) + return ScopedFile(); + return duplicate; +#else + return ScopedFile(fcntl(fd, F_DUPFD_CLOEXEC, 0)); +#endif +} + bool SeekFile(int fd, uint64_t offset) { #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) if (fd < 0) {
diff --git a/src/base/logging.cc b/src/base/logging.cc index e5a1db2..daa2371 100644 --- a/src/base/logging.cc +++ b/src/base/logging.cc
@@ -15,6 +15,7 @@ */ #include "perfetto/base/logging.h" +#include "perfetto/ext/base/progress_reporter.h" #include <stdarg.h> #include <stdio.h> @@ -126,6 +127,7 @@ log_msg = &large_buf[0]; } + ProgressReporter::ClearBeforeLog(); LogMessageCallback cb = g_log_callback.load(std::memory_order_relaxed); if (cb) { cb({level, line, fname, log_msg}); @@ -148,13 +150,7 @@ break; } -#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \ - !PERFETTO_BUILDFLAG(PERFETTO_OS_WASM) && \ - !PERFETTO_BUILDFLAG(PERFETTO_CHROMIUM_BUILD) - static const bool use_colors = isatty(STDERR_FILENO); -#else - static const bool use_colors = false; -#endif + const bool use_colors = StderrSupportsColor(); // Formats file.cc:line as a space-padded fixed width string. If the file name // |fname| is too long, truncate it on the left-hand side.
diff --git a/src/base/progress_reporter.cc b/src/base/progress_reporter.cc new file mode 100644 index 0000000..c683927 --- /dev/null +++ b/src/base/progress_reporter.cc
@@ -0,0 +1,133 @@ +/* + * Copyright (C) 2026 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/progress_reporter.h" + +#include <algorithm> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <mutex> + +#include "perfetto/base/build_config.h" +#include "perfetto/base/time.h" +#include "perfetto/ext/base/no_destructor.h" +#include "perfetto/ext/base/utils.h" + +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +#include <windows.h> +#elif !PERFETTO_BUILDFLAG(PERFETTO_OS_WASM) +#include <sys/ioctl.h> +#include <unistd.h> +#endif + +namespace perfetto::base { +namespace { +std::mutex& Mutex() { + static NoDestructor<std::mutex> mutex; + return mutex.ref(); +} +ProgressReporter* g_visible_reporter = nullptr; + +size_t TerminalWidth() { +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + CONSOLE_SCREEN_BUFFER_INFO info{}; + if (GetConsoleScreenBufferInfo(GetStdHandle(STD_ERROR_HANDLE), &info)) + return static_cast<size_t>(info.srWindow.Right - info.srWindow.Left + 1); +#elif !PERFETTO_BUILDFLAG(PERFETTO_OS_WASM) + struct winsize size{}; + if (ioctl(STDERR_FILENO, TIOCGWINSZ, &size) == 0 && size.ws_col) + return size.ws_col; +#endif + return 80; +} +} // namespace + +bool StderrSupportsProgress() { +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WASM) + return false; +#else + const char* term = getenv("TERM"); + return IsTty(stderr) && (!term || strcmp(term, "dumb") != 0); +#endif +} + +bool StderrSupportsColor() { + const char* force_color = getenv("FORCE_COLOR"); + if (force_color && *force_color) + return true; + const char* no_color = getenv("NO_COLOR"); + if (no_color && *no_color) + return false; +#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) + // Do not emit ANSI colors without first enabling virtual terminal output. + return false; +#elif PERFETTO_BUILDFLAG(PERFETTO_CHROMIUM_BUILD) + return false; +#else + return StderrSupportsProgress(); +#endif +} + +ProgressReporter::ProgressReporter(bool enabled) : enabled_(enabled) {} + +ProgressReporter::~ProgressReporter() { + Clear(); +} + +void ProgressReporter::Update(const std::string& message) { + if (!enabled_ || !StderrSupportsProgress()) + return; + std::lock_guard<std::mutex> lock(Mutex()); + int64_t now = GetWallTimeMs().count(); + if (last_update_ms_ && now - last_update_ms_ < 100) + return; + last_update_ms_ = now; + if (g_visible_reporter) + g_visible_reporter->ClearLocked(); + // Leave one column unused to avoid wrapping, including on narrow terminals. + size_t width = TerminalWidth(); + visible_width_ = std::min(message.size(), width > 1 ? width - 1 : 0); + if (!visible_width_) + return; + fprintf(stderr, "\r%.*s", static_cast<int>(visible_width_), message.c_str()); + fflush(stderr); + g_visible_reporter = this; +} + +void ProgressReporter::ClearLocked() { + if (!visible_width_) + return; + // Bound clearing by the current width in case the terminal was resized. + size_t width = TerminalWidth(); + size_t clear = std::min(visible_width_, width > 1 ? width - 1 : 0); + fprintf(stderr, "\r%*s\r", static_cast<int>(clear), ""); + fflush(stderr); + visible_width_ = 0; + g_visible_reporter = nullptr; +} + +void ProgressReporter::Clear() { + std::lock_guard<std::mutex> lock(Mutex()); + ClearLocked(); +} + +void ProgressReporter::ClearBeforeLog() { + std::lock_guard<std::mutex> lock(Mutex()); + if (g_visible_reporter) + g_visible_reporter->ClearLocked(); +} +} // namespace perfetto::base
diff --git a/src/base/progress_reporter_unittest.cc b/src/base/progress_reporter_unittest.cc new file mode 100644 index 0000000..3f00735 --- /dev/null +++ b/src/base/progress_reporter_unittest.cc
@@ -0,0 +1,163 @@ +/* + * Copyright (C) 2026 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/progress_reporter.h" + +#include <cstdlib> +#include <functional> +#include <map> +#include <optional> +#include <string> + +#include "perfetto/base/build_config.h" +#include "perfetto/base/logging.h" +#include "perfetto/ext/base/file_utils.h" +#include "perfetto/ext/base/utils.h" +#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) +#include <sys/ioctl.h> +#include <unistd.h> +#endif +#include "test/gtest_and_gmock.h" + +namespace perfetto::base { +namespace { +class ProgressReporterTest : public testing::Test { + protected: + void SetUp() override { + for (const char* name : {"TERM", "NO_COLOR", "FORCE_COLOR"}) { + const char* value = getenv(name); + saved_[name] = value ? std::optional<std::string>(value) : std::nullopt; + UnsetEnv(name); + } + testing::internal::CaptureStderr(); + } + void TearDown() override { + if (!captured_) + testing::internal::GetCapturedStderr(); + for (const auto& item : saved_) { + if (item.second) + SetEnv(item.first, *item.second); + else + UnsetEnv(item.first); + } + } + std::string Output() { + captured_ = true; + return testing::internal::GetCapturedStderr(); + } + +#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) + std::string OnTerminal(const std::function<void()>& action) { + auto master = OpenFile("/dev/ptmx", O_RDWR | O_NOCTTY | O_NONBLOCK); + PERFETTO_CHECK(master); + PERFETTO_CHECK(grantpt(*master) == 0); + PERFETTO_CHECK(unlockpt(*master) == 0); + auto slave = OpenFile(ptsname(*master), O_RDWR | O_NOCTTY); + PERFETTO_CHECK(slave); + struct winsize size{}; + size.ws_col = 20; + PERFETTO_CHECK(ioctl(*slave, TIOCSWINSZ, &size) == 0); + auto saved_stderr = DupFile(STDERR_FILENO); + PERFETTO_CHECK(saved_stderr); + fflush(stderr); + PERFETTO_CHECK(dup2(*slave, STDERR_FILENO) == STDERR_FILENO); + action(); + fflush(stderr); + PERFETTO_CHECK(dup2(*saved_stderr, STDERR_FILENO) == STDERR_FILENO); + std::string output; + char buffer[1024]; + for (;;) { + ssize_t size_read = read(*master, buffer, sizeof(buffer)); + if (size_read <= 0) + break; + output.append(buffer, static_cast<size_t>(size_read)); + } + return output; + } +#endif + + private: + bool captured_ = false; + std::map<std::string, std::optional<std::string>> saved_; +}; + +TEST_F(ProgressReporterTest, RedirectedOutputHasNoProgressOrColor) { + EXPECT_FALSE(StderrSupportsProgress()); + EXPECT_FALSE(StderrSupportsColor()); + { + ProgressReporter progress; + progress.Update("Loading trace: 1 MB"); + } + EXPECT_TRUE(Output().empty()); +} + +TEST_F(ProgressReporterTest, ForceColorDoesNotForceProgress) { + // Any nonempty value, including "0", forces ANSI color. + SetEnv("FORCE_COLOR", "0"); + SetEnv("TERM", "dumb"); + EXPECT_TRUE(StderrSupportsColor()); + EXPECT_FALSE(StderrSupportsProgress()); + { + ProgressReporter progress; + progress.Update("Loading trace: 1 MB"); + } + EXPECT_TRUE(Output().empty()); +} + +#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) +TEST_F(ProgressReporterTest, TerminalClipsAndClearsBeforeLogging) { + SetEnv("TERM", "xterm"); + SetEnv("NO_COLOR", "1"); + auto output = OnTerminal([] { + EXPECT_TRUE(StderrSupportsProgress()); + EXPECT_FALSE(StderrSupportsColor()); + ProgressReporter progress; + progress.Update("1234567890123456789overflow"); + PERFETTO_LOG("diagnostic"); + }); + EXPECT_THAT(output, testing::HasSubstr("\r1234567890123456789")); + EXPECT_THAT(output, testing::Not(testing::HasSubstr("overflow"))); + EXPECT_THAT(output, testing::HasSubstr("\r \r")); + EXPECT_THAT(output, testing::HasSubstr("diagnostic")); +} + +TEST_F(ProgressReporterTest, TerminalSuppressionDoesNotSuppressDiagnostics) { + SetEnv("TERM", "xterm"); + auto output = OnTerminal([] { + ProgressReporter progress(false); + progress.Update("hidden progress"); + fprintf(stderr, "visible diagnostic"); + }); + EXPECT_EQ(output, "visible diagnostic"); + SetEnv("TERM", "dumb"); + EXPECT_TRUE(OnTerminal([] { + EXPECT_FALSE(StderrSupportsProgress()); + ProgressReporter progress; + progress.Update("hidden progress"); + }).empty()); +} +#endif + +TEST_F(ProgressReporterTest, ForceColorOverridesNoColor) { + SetEnv("NO_COLOR", "1"); + EXPECT_FALSE(StderrSupportsColor()); + SetEnv("FORCE_COLOR", "1"); + EXPECT_TRUE(StderrSupportsColor()); + SetEnv("FORCE_COLOR", ""); + EXPECT_FALSE(StderrSupportsColor()); +} +} // namespace +} // namespace perfetto::base
diff --git a/src/trace_processor/shell/bundle_integrationtest.cc b/src/trace_processor/shell/bundle_integrationtest.cc index b2e89f9..0756714 100644 --- a/src/trace_processor/shell/bundle_integrationtest.cc +++ b/src/trace_processor/shell/bundle_integrationtest.cc
@@ -638,5 +638,27 @@ EXPECT_NE(invoker.Run(), 0); } +#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) +TEST_F(TraceconvShellBundleTest, RedirectedProgressIsPlainAndWarningsRemain) { + base::TempFile trace = WriteTempFile(BuildFuncgraphTrace(false)); + for (bool no_progress : {false, true}) { + ArgvInvoker invoker; + invoker.Add("trace_processor_shell"); + invoker.Add("bundle"); + invoker.Add("--no-auto-symbol-paths"); + if (no_progress) + invoker.Add("--no-progress"); + invoker.Add(trace.path()); + invoker.Add(output_path_); + ScopedStderrCapture capture; + ASSERT_EQ(invoker.Run(), 0); + auto output = capture.Get(); + EXPECT_THAT(output, Not(HasSubstr("\r"))); + EXPECT_THAT(output, HasSubstr("Read trace:")); + EXPECT_THAT(output, HasSubstr("symbolize_ksyms")); + } +} +#endif + } // namespace } // namespace perfetto::trace_processor
diff --git a/src/trace_processor/shell/bundle_subcommand.cc b/src/trace_processor/shell/bundle_subcommand.cc index 88fc42c..2ea702b 100644 --- a/src/trace_processor/shell/bundle_subcommand.cc +++ b/src/trace_processor/shell/bundle_subcommand.cc
@@ -26,6 +26,7 @@ #include "perfetto/ext/base/file_utils.h" #include "perfetto/ext/base/scoped_file.h" #include "perfetto/ext/base/string_utils.h" +#include "src/trace_processor/shell/common_flags.h" #include "src/trace_processor/shell/subcommand.h" #include "src/traceconv/trace_to_bundle.h" @@ -86,7 +87,11 @@ Outputs a TAR containing the trace plus the symbols and deobfuscation mappings needed to make it self-contained. Both <input> and <output> must be -real file paths (stdin/stdout are not supported).)"; +real file paths (stdin/stdout are not supported). + +Live progress uses stderr only when it is a terminal (except TERM=dumb). +Nonempty FORCE_COLOR forces ANSI color, overriding NO_COLOR. Otherwise, +nonempty NO_COLOR disables automatic color. Color does not enable progress.)"; } std::vector<FlagSpec> BundleSubcommand::GetFlags() { @@ -199,6 +204,7 @@ context.no_auto_symbol_paths = no_auto_symbol_paths_; context.no_auto_proguard_maps = no_auto_proguard_maps_; context.verbose = verbose_; + context.no_progress = ctx.global && ctx.global->no_progress; if (const char* val = getenv("ANDROID_PRODUCT_OUT")) context.android_product_out = val; if (const char* val = getenv("HOME"))
diff --git a/src/trace_processor/shell/common_flags.cc b/src/trace_processor/shell/common_flags.cc index fb554a2..f589a7a 100644 --- a/src/trace_processor/shell/common_flags.cc +++ b/src/trace_processor/shell/common_flags.cc
@@ -15,7 +15,6 @@ */ #include "src/trace_processor/shell/common_flags.h" -#include "src/traceconv/utils.h" #include <cstdio> #include <cstdlib> @@ -33,6 +32,7 @@ #include "perfetto/base/time.h" #include "perfetto/ext/base/file_utils.h" #include "perfetto/ext/base/getopt.h" +#include "perfetto/ext/base/progress_reporter.h" #include "perfetto/ext/base/status_macros.h" #include "perfetto/ext/base/status_or.h" #include "perfetto/ext/base/string_splitter.h" @@ -123,6 +123,10 @@ flags.push_back(BoolFlag("help", 'h', "Prints this guide.", &opts->help)); flags.push_back( BoolFlag("version", 'v', "Prints the version.", &opts->version)); + flags.push_back(BoolFlag( + "no-progress", '\0', + "Disable live progress (summaries and warnings are still printed).", + &opts->no_progress)); flags.push_back(BoolFlag("full-sort", '\0', "Forces full sort ignoring windowing.", &opts->force_full_sort)); @@ -432,18 +436,22 @@ base::StatusOr<base::TimeNanos> LoadTraceFile( TraceProcessor* tp, TraceProcessorShell_PlatformInterface* platform, - const std::string& trace_file) { + const std::string& trace_file, + bool no_progress) { base::TimeNanos t_load_start = base::GetWallTimeNs(); double size_mb = 0; + base::ProgressReporter progress(!no_progress); - base::Status load_status = - platform->LoadTrace(tp, trace_file, [&size_mb](size_t parsed_size) { + base::Status load_status = platform->LoadTrace( + tp, trace_file, [&size_mb, &progress](size_t parsed_size) { size_mb = static_cast<double>(parsed_size) / 1E6; - fprintf(stderr, "\rLoading trace: %.2f MB\r", size_mb); + progress.Update( + base::StackString<128>("Loading trace: %.2f MB", size_mb) + .ToStdString()); }); // Terminate the in-place progress line so errors/logs below start on a // fresh line. - trace_to_text::EndProgressLine(); + progress.Clear(); if (!load_status.ok()) { return base::ErrStatus("failed to read trace file (path: %s): %s", trace_file.c_str(), load_status.c_message()); @@ -615,8 +623,9 @@ } ASSIGN_OR_RETURN(Config config, BuildConfig(opts, platform)); ASSIGN_OR_RETURN(auto tp, SetupTraceProcessor(opts, config, platform)); - ASSIGN_OR_RETURN(base::TimeNanos t_load, - LoadTraceFile(tp.get(), platform, trace_file)); + ASSIGN_OR_RETURN( + base::TimeNanos t_load, + LoadTraceFile(tp.get(), platform, trace_file, opts.no_progress)); if (t_load_out) *t_load_out = t_load; return std::move(tp);
diff --git a/src/trace_processor/shell/common_flags.h b/src/trace_processor/shell/common_flags.h index 50a7076..51f8e4d 100644 --- a/src/trace_processor/shell/common_flags.h +++ b/src/trace_processor/shell/common_flags.h
@@ -49,6 +49,7 @@ // (unsupported yet) host:port. std::string remote_addr; + bool no_progress = false; bool force_full_sort = false; bool no_ftrace_raw = false; bool analyze_trace_proto_content = false; @@ -113,7 +114,8 @@ base::StatusOr<base::TimeNanos> LoadTraceFile( TraceProcessor* tp, TraceProcessorShell_PlatformInterface* platform, - const std::string& trace_file); + const std::string& trace_file, + bool no_progress = false); // Resolves the trace-file positional argument for a trace-consuming subcommand, // accounting for --remote. In --remote mode the trace is already loaded
diff --git a/src/trace_processor/shell/convert_subcommand.cc b/src/trace_processor/shell/convert_subcommand.cc index 0247802..d7178c1 100644 --- a/src/trace_processor/shell/convert_subcommand.cc +++ b/src/trace_processor/shell/convert_subcommand.cc
@@ -27,6 +27,7 @@ #include "perfetto/base/status.h" #include "perfetto/ext/base/status_macros.h" #include "perfetto/ext/base/string_utils.h" +#include "src/trace_processor/shell/common_flags.h" #include "src/trace_processor/shell/convert_helpers.h" #include "src/trace_processor/shell/subcommand.h" #include "src/traceconv/trace_to_firefox.h" @@ -162,15 +163,19 @@ RETURN_IF_ERROR( OpenConversionOutput(output_path, binary_output, &output_file, &output)); + const bool no_progress = ctx.global && ctx.global->no_progress; if (format == "json") { - RETURN_IF_ERROR(trace_to_text::TraceToJson( - input, output, /*compress=*/false, truncate_keep, full_sort_)); + RETURN_IF_ERROR( + trace_to_text::TraceToJson(input, output, /*compress=*/false, + truncate_keep, full_sort_, no_progress)); } else if (format == "systrace") { - RETURN_IF_ERROR(trace_to_text::TraceToSystrace( - input, output, /*ctrace=*/false, truncate_keep, full_sort_)); + RETURN_IF_ERROR( + trace_to_text::TraceToSystrace(input, output, /*ctrace=*/false, + truncate_keep, full_sort_, no_progress)); } else if (format == "ctrace") { - RETURN_IF_ERROR(trace_to_text::TraceToSystrace( - input, output, /*ctrace=*/true, truncate_keep, full_sort_)); + RETURN_IF_ERROR( + trace_to_text::TraceToSystrace(input, output, /*ctrace=*/true, + truncate_keep, full_sort_, no_progress)); } else if (format == "text" || format == "profile" || format == "firefox") { if (truncate_keep != trace_to_text::Keep::kAll) { return base::ErrStatus("--truncate is unsupported for the '%s' format.", @@ -183,6 +188,7 @@ if (format == "text") { trace_to_text::TraceToTextOptions options; options.skip_unknown_fields = skip_unknown_; + options.no_progress = no_progress; RETURN_IF_ERROR(trace_to_text::TraceToText(input, output, options)); } else if (format == "profile") { if (!output_path.empty()) { @@ -192,9 +198,10 @@ } RETURN_IF_ERROR(trace_to_text::TraceToProfile( input, pid, timestamps, !no_annotations_, output_dir_, profile_type, - verbose_)); + verbose_, no_progress)); } else { // firefox - RETURN_IF_ERROR(trace_to_text::TraceToFirefoxProfile(input, output)); + RETURN_IF_ERROR( + trace_to_text::TraceToFirefoxProfile(input, output, no_progress)); } } else { return base::ErrStatus("convert: unknown format '%s'.", format.c_str());
diff --git a/src/trace_processor/shell/export_subcommand.cc b/src/trace_processor/shell/export_subcommand.cc index 036c761..9bbe791 100644 --- a/src/trace_processor/shell/export_subcommand.cc +++ b/src/trace_processor/shell/export_subcommand.cc
@@ -92,7 +92,9 @@ ASSIGN_OR_RETURN(Config config, BuildConfig(*ctx.global, ctx.platform)); ASSIGN_OR_RETURN(auto tp, SetupTraceProcessor(*ctx.global, config, ctx.platform)); - RETURN_IF_ERROR(LoadTraceFile(tp.get(), ctx.platform, trace_file).status()); + RETURN_IF_ERROR( + LoadTraceFile(tp.get(), ctx.platform, trace_file, ctx.global->no_progress) + .status()); TraceProcessor::ExportFormat export_format; if (format == "sqlite") {
diff --git a/src/trace_processor/shell/server_subcommand.cc b/src/trace_processor/shell/server_subcommand.cc index 9a2697b..caad2aa 100644 --- a/src/trace_processor/shell/server_subcommand.cc +++ b/src/trace_processor/shell/server_subcommand.cc
@@ -255,7 +255,8 @@ if (!trace_file.empty()) { ASSIGN_OR_RETURN(auto t_load, - LoadTraceFile(tp.get(), ctx.platform, trace_file)); + LoadTraceFile(tp.get(), ctx.platform, trace_file, + ctx.global->no_progress)); base::ignore_result(t_load); }
diff --git a/src/trace_processor/shell/shell_utils.cc b/src/trace_processor/shell/shell_utils.cc index d0b958e..d012b79 100644 --- a/src/trace_processor/shell/shell_utils.cc +++ b/src/trace_processor/shell/shell_utils.cc
@@ -26,6 +26,7 @@ #include "perfetto/base/logging.h" #include "perfetto/base/status.h" #include "perfetto/ext/base/file_utils.h" +#include "perfetto/ext/base/progress_reporter.h" #include "perfetto/ext/base/scoped_file.h" #include "perfetto/ext/base/status_macros.h" #include "perfetto/ext/base/string_utils.h" @@ -66,14 +67,7 @@ } // namespace bool StderrSupportsColors() { -#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \ - !PERFETTO_BUILDFLAG(PERFETTO_OS_WASM) && \ - !PERFETTO_BUILDFLAG(PERFETTO_CHROMIUM_BUILD) - static const bool use_colors = isatty(STDERR_FILENO); - return use_colors; -#else - return false; -#endif + return base::StderrSupportsColor(); } namespace {
diff --git a/src/trace_processor/shell/util_subcommand.cc b/src/trace_processor/shell/util_subcommand.cc index c7aabf1..3ffb28e 100644 --- a/src/trace_processor/shell/util_subcommand.cc +++ b/src/trace_processor/shell/util_subcommand.cc
@@ -35,6 +35,7 @@ #include "perfetto/trace_processor/iterator.h" #include "perfetto/trace_processor/read_trace.h" #include "perfetto/trace_processor/trace_processor.h" +#include "src/trace_processor/shell/common_flags.h" #include "src/trace_processor/shell/convert_helpers.h" #include "src/trace_processor/shell/subcommand.h" #include "src/trace_processor/util/json_value.h" @@ -226,7 +227,8 @@ &output_file, &output)); if (util == "symbolize") { - RETURN_IF_ERROR(trace_to_text::SymbolizeProfile(input, output, verbose_)); + RETURN_IF_ERROR(trace_to_text::SymbolizeProfile( + input, output, verbose_, ctx.global && ctx.global->no_progress)); } else if (util == "deobfuscate") { RETURN_IF_ERROR(trace_to_text::DeobfuscateProfile(input, output)); } else if (util == "decompress_packets") {
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc index fcc8537..a11fc79 100644 --- a/src/trace_processor/trace_processor_shell.cc +++ b/src/trace_processor/trace_processor_shell.cc
@@ -171,6 +171,7 @@ Common flags (apply to all commands): -h, --help Show help (per-command if after a command). -v, --version Print version. + --no-progress Disable live progress; keep summaries and errors. --full-sort Force full sort ignoring windowing. --no-ftrace-raw Prevent ingestion of typed ftrace into raw table. --add-sql-package PATH Register SQL files from a directory as a package.
diff --git a/src/traceconv/symbolize_profile.cc b/src/traceconv/symbolize_profile.cc index b8503e9..3252263 100644 --- a/src/traceconv/symbolize_profile.cc +++ b/src/traceconv/symbolize_profile.cc
@@ -33,7 +33,8 @@ // be prepended to the profile to attach the symbol information. base::Status SymbolizeProfile(std::istream* input, std::ostream* output, - bool verbose) { + bool verbose, + bool no_progress) { profiling::SymbolizerConfig sym_config; const char* breakpad_dir = getenv("BREAKPAD_SYMBOL_DIR"); @@ -63,7 +64,7 @@ std::unique_ptr<trace_processor::TraceProcessor> tp = trace_processor::TraceProcessor::CreateInstance(config); - if (!ReadTraceUnfinalized(tp.get(), input)) { + if (!ReadTraceUnfinalized(tp.get(), input, no_progress)) { return base::ErrStatus("failed to read trace"); }
diff --git a/src/traceconv/symbolize_profile.h b/src/traceconv/symbolize_profile.h index 466a6e3..69785de 100644 --- a/src/traceconv/symbolize_profile.h +++ b/src/traceconv/symbolize_profile.h
@@ -26,7 +26,8 @@ base::Status SymbolizeProfile(std::istream* input, std::ostream* output, - bool verbose); + bool verbose, + bool no_progress = false); } // namespace trace_to_text } // namespace perfetto
diff --git a/src/traceconv/trace_to_bundle.cc b/src/traceconv/trace_to_bundle.cc index 6543e3c..54634d5 100644 --- a/src/traceconv/trace_to_bundle.cc +++ b/src/traceconv/trace_to_bundle.cc
@@ -22,37 +22,34 @@ #include "perfetto/base/build_config.h" #include "perfetto/base/logging.h" #include "perfetto/base/status.h" +#include "perfetto/ext/base/progress_reporter.h" +#include "perfetto/ext/base/string_utils.h" #include "perfetto/trace_processor/read_trace.h" #include "perfetto/trace_processor/trace_processor.h" #include "src/trace_processor/util/tar_writer.h" #include "src/trace_processor/util/trace_enrichment/trace_enrichment.h" -#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \ - !PERFETTO_BUILDFLAG(PERFETTO_OS_WASM) && \ - !PERFETTO_BUILDFLAG(PERFETTO_CHROMIUM_BUILD) -#include <unistd.h> // For isatty() -#endif - namespace perfetto::trace_to_text { base::Status TraceToBundle(const std::string& input_file_path, const std::string& output_file_path, const BundleContext& context) { + base::ProgressReporter progress(!context.no_progress); auto tp = trace_processor::TraceProcessor::CreateInstance({}); - // Report reading progress to stderr, like the interactive shell does, so - // long-running bundles don't look frozen. double loaded_mb = 0; auto status = trace_processor::ReadTrace( - tp.get(), input_file_path.c_str(), [&loaded_mb](uint64_t parsed_size) { + tp.get(), input_file_path.c_str(), + [&loaded_mb, &progress](uint64_t parsed_size) { loaded_mb = static_cast<double>(parsed_size) / 1E6; - fprintf(stderr, "\rReading trace: %.2f MB", loaded_mb); + progress.Update( + base::StackString<128>("Reading trace: %.2f MB", loaded_mb) + .ToStdString()); }); - if (!status.ok()) { - fprintf(stderr, "\n"); + progress.Clear(); + if (!status.ok()) return base::ErrStatus("failed to read trace: %s", status.c_message()); - } - fprintf(stderr, "\rRead trace: %.2f MB.\n", loaded_mb); + fprintf(stderr, "Read trace: %.2f MB.\n", loaded_mb); // Add original trace file directly (memory efficient). If the output path // cannot be opened, TarWriter fails gracefully and this propagates a @@ -76,11 +73,7 @@ enrich_config.home_dir = context.home_dir; enrich_config.working_dir = context.working_dir; enrich_config.root_dir = context.root_dir; -#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) && \ - !PERFETTO_BUILDFLAG(PERFETTO_OS_WASM) && \ - !PERFETTO_BUILDFLAG(PERFETTO_CHROMIUM_BUILD) - enrich_config.colorize = isatty(STDERR_FILENO); -#endif + enrich_config.colorize = base::StderrSupportsColor(); // Add explicit ProGuard maps from context. for (const auto& map_spec : context.proguard_maps) {
diff --git a/src/traceconv/trace_to_bundle.h b/src/traceconv/trace_to_bundle.h index f525341..979535a 100644 --- a/src/traceconv/trace_to_bundle.h +++ b/src/traceconv/trace_to_bundle.h
@@ -47,6 +47,9 @@ // If true, output verbose details (all paths tried, etc.) bool verbose = false; + // Suppress live progress; summaries and diagnostics are unaffected. + bool no_progress = false; + // Value of ANDROID_PRODUCT_OUT for AOSP builds symbol discovery std::string android_product_out;
diff --git a/src/traceconv/trace_to_firefox.cc b/src/traceconv/trace_to_firefox.cc index bc20b09..25c44c9 100644 --- a/src/traceconv/trace_to_firefox.cc +++ b/src/traceconv/trace_to_firefox.cc
@@ -46,13 +46,13 @@ PERFETTO_CHECK(it.Status().ok()); } -std::unique_ptr<trace_processor::TraceProcessor> LoadTrace( - std::istream* input) { +std::unique_ptr<trace_processor::TraceProcessor> LoadTrace(std::istream* input, + bool no_progress) { trace_processor::Config config; std::unique_ptr<trace_processor::TraceProcessor> tp = trace_processor::TraceProcessor::CreateInstance(config); - if (!ReadTraceUnfinalized(tp.get(), input)) { + if (!ReadTraceUnfinalized(tp.get(), input, no_progress)) { return nullptr; } if (auto status = tp->NotifyEndOfFile(); !status.ok()) { @@ -63,8 +63,10 @@ } // namespace -base::Status TraceToFirefoxProfile(std::istream* input, std::ostream* output) { - auto tp = LoadTrace(input); +base::Status TraceToFirefoxProfile(std::istream* input, + std::ostream* output, + bool no_progress) { + auto tp = LoadTrace(input, no_progress); if (!tp) { return base::ErrStatus("failed to read trace"); }
diff --git a/src/traceconv/trace_to_firefox.h b/src/traceconv/trace_to_firefox.h index ac3d64d..9193df4 100644 --- a/src/traceconv/trace_to_firefox.h +++ b/src/traceconv/trace_to_firefox.h
@@ -27,7 +27,9 @@ // Exports trace as as Firefox Profile. More details here: // https://firefox-source-docs.mozilla.org/tools/profiler/code-overview.html // https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js -base::Status TraceToFirefoxProfile(std::istream* input, std::ostream* output); +base::Status TraceToFirefoxProfile(std::istream* input, + std::ostream* output, + bool no_progress = false); } // namespace trace_to_text } // namespace perfetto
diff --git a/src/traceconv/trace_to_json.cc b/src/traceconv/trace_to_json.cc index a195b26..79c57e3 100644 --- a/src/traceconv/trace_to_json.cc +++ b/src/traceconv/trace_to_json.cc
@@ -19,6 +19,8 @@ #include <stdio.h> #include "perfetto/base/logging.h" +#include "perfetto/ext/base/progress_reporter.h" +#include "perfetto/ext/base/string_utils.h" #include "perfetto/ext/trace_processor/export_json.h" #include "perfetto/trace_processor/trace_processor.h" #include "src/traceconv/utils.h" @@ -61,12 +63,14 @@ }; bool ExportUserspaceEvents(trace_processor::TraceProcessor* tp, - TraceWriter* writer) { - ProgressLine("Converting userspace events"); + TraceWriter* writer, + bool no_progress) { + base::ProgressReporter progress(!no_progress); + progress.Update("Converting userspace events"); TraceWriterOutputWriter output(writer); base::Status status = trace_processor::json::ExportJson(tp, &output); - EndProgressLine(); + progress.Clear(); if (!status.ok()) { PERFETTO_ELOG("Could not convert userspace events: %s", status.c_message()); return false; @@ -84,7 +88,8 @@ std::ostream* output, bool compress, Keep truncate_keep, - bool full_sort) { + bool full_sort, + bool no_progress) { std::unique_ptr<TraceWriter> trace_writer( compress ? new DeflateTraceWriter(output) : new TraceWriter(output)); @@ -95,14 +100,14 @@ std::unique_ptr<trace_processor::TraceProcessor> tp = trace_processor::TraceProcessor::CreateInstance(config); - if (!ReadTraceUnfinalized(tp.get(), input)) + if (!ReadTraceUnfinalized(tp.get(), input, no_progress)) return base::ErrStatus("failed to read trace"); if (auto status = tp->NotifyEndOfFile(); !status.ok()) { return base::ErrStatus("failed to finalize trace: %s", status.c_message()); } // TODO(eseckler): Support truncation of userspace event data. - if (!ExportUserspaceEvents(tp.get(), trace_writer.get())) { + if (!ExportUserspaceEvents(tp.get(), trace_writer.get(), no_progress)) { // ExportJson streams directly to |trace_writer|, so emitting an empty // trace header here would corrupt any output already written. Report the // conversion failure instead of silently dropping userspace events. @@ -111,15 +116,14 @@ } trace_writer->Write(",\n"); - int ret = ExtractSystrace(tp.get(), trace_writer.get(), - /*wrapped_in_json=*/true, truncate_keep); + int ret = + ExtractSystrace(tp.get(), trace_writer.get(), + /*wrapped_in_json=*/true, truncate_keep, no_progress); if (ret) { - EndProgressLine(); return base::ErrStatus("failed to convert ftrace events"); } trace_writer->Write(kTraceFooter); - EndProgressLine(); return base::OkStatus(); }
diff --git a/src/traceconv/trace_to_json.h b/src/traceconv/trace_to_json.h index 1dc2d7a..f1cc219 100644 --- a/src/traceconv/trace_to_json.h +++ b/src/traceconv/trace_to_json.h
@@ -29,7 +29,8 @@ std::ostream* output, bool compress, Keep truncate_keep, - bool full_sort); + bool full_sort, + bool no_progress = false); } // namespace trace_to_text } // namespace perfetto
diff --git a/src/traceconv/trace_to_profile.cc b/src/traceconv/trace_to_profile.cc index 9dcaac9..019e169 100644 --- a/src/traceconv/trace_to_profile.cc +++ b/src/traceconv/trace_to_profile.cc
@@ -170,12 +170,13 @@ bool annotate_frames, const std::string& output_dir, std::optional<ConversionMode> explicit_mode, - bool verbose) { + bool verbose, + bool no_progress) { // Pre-parse trace. trace_processor::Config config; std::unique_ptr<trace_processor::TraceProcessor> tp = trace_processor::TraceProcessor::CreateInstance(config); - if (!ReadTraceUnfinalized(tp.get(), input)) + if (!ReadTraceUnfinalized(tp.get(), input, no_progress)) return base::ErrStatus("failed to read trace"); tp->Flush();
diff --git a/src/traceconv/trace_to_profile.h b/src/traceconv/trace_to_profile.h index 4242386..e782085 100644 --- a/src/traceconv/trace_to_profile.h +++ b/src/traceconv/trace_to_profile.h
@@ -35,7 +35,8 @@ bool annotate_frames, const std::string& output_dir, std::optional<ConversionMode> conversion_mode, - bool verbose); + bool verbose, + bool no_progress = false); } // namespace trace_to_text } // namespace perfetto
diff --git a/src/traceconv/trace_to_systrace.cc b/src/traceconv/trace_to_systrace.cc index 131dccc..fa3c8d8 100644 --- a/src/traceconv/trace_to_systrace.cc +++ b/src/traceconv/trace_to_systrace.cc
@@ -28,6 +28,7 @@ #include "perfetto/base/build_config.h" #include "perfetto/base/logging.h" #include "perfetto/ext/base/dynamic_string_writer.h" +#include "perfetto/ext/base/progress_reporter.h" #include "perfetto/ext/base/string_utils.h" #include "perfetto/trace_processor/trace_processor.h" #include "src/traceconv/utils.h" @@ -112,8 +113,10 @@ class QueryWriter { public: - QueryWriter(trace_processor::TraceProcessor* tp, TraceWriter* trace_writer) - : tp_(tp), trace_writer_(trace_writer) {} + QueryWriter(trace_processor::TraceProcessor* tp, + TraceWriter* trace_writer, + bool no_progress) + : tp_(tp), trace_writer_(trace_writer), progress_(!no_progress) {} template <typename Callback> bool RunQuery(const std::string& sql, Callback callback) { @@ -124,14 +127,15 @@ callback(&iterator, &line_writer); if (global_writer_.pos() + line_writer.pos() >= kFlushThreshold) { - ProgressLine("Writing row %" PRIu32, rows); + progress_.Update( + base::StackString<128>("Writing row %" PRIu32, rows).ToStdString()); auto str = global_writer_.GetStringView(); trace_writer_->Write(str.data(), str.size()); global_writer_.Clear(); } global_writer_.AppendStringView(line_writer.GetStringView()); } - EndProgressLine(); + progress_.Clear(); // Check if we have an error in the iterator and print if so. auto status = iterator.Status(); @@ -153,12 +157,14 @@ trace_processor::TraceProcessor* tp_ = nullptr; base::DynamicStringWriter global_writer_; TraceWriter* trace_writer_; + base::ProgressReporter progress_; }; int ExtractRawEvents(TraceWriter* trace_writer, QueryWriter& q_writer, bool wrapped_in_json, - Keep truncate_keep) { + Keep truncate_keep, + bool no_progress) { using trace_processor::Iterator; static const char kRawEventsCountSql[] = "select count(1) from ftrace_event"; @@ -178,7 +184,8 @@ return 0; } - ProgressLine("Converting ftrace events"); + base::ProgressReporter progress(!no_progress); + progress.Update("Converting ftrace events"); auto raw_callback = [wrapped_in_json](Iterator* it, base::DynamicStringWriter* writer) { @@ -267,7 +274,8 @@ std::ostream* output, bool ctrace, Keep truncate_keep, - bool full_sort) { + bool full_sort, + bool no_progress) { std::unique_ptr<TraceWriter> trace_writer( ctrace ? new DeflateTraceWriter(output) : new TraceWriter(output)); @@ -278,7 +286,7 @@ std::unique_ptr<trace_processor::TraceProcessor> tp = trace_processor::TraceProcessor::CreateInstance(config); - if (!ReadTraceUnfinalized(tp.get(), input)) + if (!ReadTraceUnfinalized(tp.get(), input, no_progress)) return base::ErrStatus("failed to read trace"); if (auto status = tp->NotifyEndOfFile(); !status.ok()) { return base::ErrStatus("failed to finalize trace: %s", status.c_message()); @@ -287,23 +295,23 @@ if (ctrace) *output << "TRACE:\n"; - int ret = ExtractSystrace(tp.get(), trace_writer.get(), - /*wrapped_in_json=*/false, truncate_keep); + int ret = + ExtractSystrace(tp.get(), trace_writer.get(), + /*wrapped_in_json=*/false, truncate_keep, no_progress); if (ret) { - EndProgressLine(); return base::ErrStatus("failed to convert ftrace events"); } - EndProgressLine(); return base::OkStatus(); } int ExtractSystrace(trace_processor::TraceProcessor* tp, TraceWriter* trace_writer, bool wrapped_in_json, - Keep truncate_keep) { + Keep truncate_keep, + bool no_progress) { using trace_processor::Iterator; - QueryWriter q_writer(tp, trace_writer); + QueryWriter q_writer(tp, trace_writer, no_progress); if (wrapped_in_json) { trace_writer->Write(kProcessDumpHeader); @@ -344,7 +352,7 @@ trace_writer->Write(kProcessDumpFooter); } return ExtractRawEvents(trace_writer, q_writer, wrapped_in_json, - truncate_keep); + truncate_keep, no_progress); } } // namespace trace_to_text
diff --git a/src/traceconv/trace_to_systrace.h b/src/traceconv/trace_to_systrace.h index c25a10b..3f934a3 100644 --- a/src/traceconv/trace_to_systrace.h +++ b/src/traceconv/trace_to_systrace.h
@@ -37,12 +37,14 @@ std::ostream* output, bool ctrace, Keep truncate_keep, - bool full_sort); + bool full_sort, + bool no_progress = false); int ExtractSystrace(trace_processor::TraceProcessor*, TraceWriter*, bool wrapped_in_json, - Keep truncate_keep); + Keep truncate_keep, + bool no_progress = false); } // namespace trace_to_text } // namespace perfetto
diff --git a/src/traceconv/trace_to_text.cc b/src/traceconv/trace_to_text.cc index eb22228..30ec97a 100644 --- a/src/traceconv/trace_to_text.cc +++ b/src/traceconv/trace_to_text.cc
@@ -18,7 +18,9 @@ #include "perfetto/base/logging.h" #include "perfetto/ext/base/file_utils.h" +#include "perfetto/ext/base/progress_reporter.h" #include "perfetto/ext/base/scoped_file.h" +#include "perfetto/ext/base/string_utils.h" #include "perfetto/ext/protozero/proto_ring_buffer.h" #include "src/traceconv/android_extension.descriptor.h" #include "src/traceconv/trace.descriptor.h" @@ -50,8 +52,12 @@ // write the output in given std::ostream*. class OnlineTraceToText { public: - OnlineTraceToText(std::ostream* output, const TraceToTextOptions& options) - : output_(output), skip_unknown_fields_(options.skip_unknown_fields) { + OnlineTraceToText(std::ostream* output, + const TraceToTextOptions& options, + base::ProgressReporter* progress) + : output_(output), + skip_unknown_fields_(options.skip_unknown_fields), + progress_(progress) { pool_.AddFromFileDescriptorSet(kTraceDescriptor.data(), kTraceDescriptor.size()); pool_.AddFromFileDescriptorSet(kAndroidExtensionDescriptor.data(), @@ -84,6 +90,7 @@ size_t bytes_processed_ = 0; size_t packet_ = 0; bool skip_unknown_fields_ = false; + base::ProgressReporter* progress_; }; std::string OnlineTraceToText::TracePacketToText(protozero::ConstBytes packet, @@ -153,7 +160,9 @@ protos::pbzero::TracePacket::Decoder decoder(token.start, token.len); bytes_processed_ += token.len; if ((packet_++ & 0x3f) == 0) { - ProgressLine("Processing trace: %8zu KB", bytes_processed_ / 1024); + progress_->Update(base::StackString<128>("Processing trace: %8zu KB", + bytes_processed_ / 1024) + .ToStdString()); } if (decoder.has_compressed_packets()) { PrintCompressedPackets(decoder.compressed_packets(), @@ -209,7 +218,8 @@ uint32_t buffer_len = 0; InputReader input_reader(input); - OnlineTraceToText online_trace_to_text(output, options); + base::ProgressReporter progress(!options.no_progress); + OnlineTraceToText online_trace_to_text(output, options, &progress); // Sniff the first chunk inside the tokenizer's own buffer, so a proto trace // is never copied. Only a compressed one moves to the decompressor's input. @@ -252,13 +262,13 @@ online_trace_to_text.BeginWrite(kExtractSize), kExtractSize); if (res.ret == ResultCode::kError) { online_trace_to_text.AbortWrite(); - EndProgressLine(); + progress.Clear(); return base::ErrStatus( "failed to decompress, trace is likely corrupt"); } online_trace_to_text.EndWrite(res.bytes_written); if (!online_trace_to_text.ok()) { - EndProgressLine(); + progress.Clear(); return base::ErrStatus("failed to convert trace to text: %s", online_trace_to_text.error().c_str()); } @@ -281,33 +291,33 @@ buffer_len > 0); if (code != ResultCode::kEof) { - EndProgressLine(); + progress.Clear(); return base::ErrStatus( "compressed stream incomplete, trace is likely corrupt"); } if (!input_reader.ok()) { - EndProgressLine(); + progress.Clear(); return base::ErrStatus("failed to read trace: %s", input_reader.error().c_str()); } - EndProgressLine(); + progress.Clear(); return base::OkStatus(); } else if (type == trace_processor::CompressedTraceType::kProto) { do { online_trace_to_text.EndWrite(buffer_len); if (!online_trace_to_text.ok()) { - EndProgressLine(); + progress.Clear(); return base::ErrStatus("failed to convert trace to text: %s", online_trace_to_text.error().c_str()); } } while (input_reader.Read(online_trace_to_text.BeginWrite(kReadSize), &buffer_len, kReadSize)); if (!input_reader.ok()) { - EndProgressLine(); + progress.Clear(); return base::ErrStatus("failed to read trace: %s", input_reader.error().c_str()); } - EndProgressLine(); + progress.Clear(); return base::OkStatus(); } else { return base::ErrStatus(
diff --git a/src/traceconv/trace_to_text.h b/src/traceconv/trace_to_text.h index ef8f7fe..56c792d 100644 --- a/src/traceconv/trace_to_text.h +++ b/src/traceconv/trace_to_text.h
@@ -27,6 +27,7 @@ struct TraceToTextOptions { // If true, unknown proto fields are skipped when converting to text. bool skip_unknown_fields = false; + bool no_progress = false; }; // Returns OkStatus() on success.
diff --git a/src/traceconv/traceconv.cc b/src/traceconv/traceconv.cc index e256883..8d502bc 100644 --- a/src/traceconv/traceconv.cc +++ b/src/traceconv/traceconv.cc
@@ -120,6 +120,7 @@ pkg= prefix scopes the map to a package. --no-auto-proguard-maps Disable automatic ProGuard/R8 mapping discovery (e.g. Gradle project layout) + --no-progress Disable live progress --verbose Print more detailed output binary Converts text proto to binary format @@ -181,6 +182,7 @@ bool no_auto_symbol_paths = false; bool no_auto_proguard_maps = false; bool verbose = false; + bool no_progress = false; bool skip_unknown_fields = false; std::string output_dir; for (int i = 1; i < argc; i++) { @@ -222,6 +224,8 @@ } else if (i < argc && strcmp(argv[i], "--symbol-paths") == 0) { i++; symbol_paths = base::SplitString(argv[i], ","); + } else if (strcmp(argv[i], "--no-progress") == 0) { + no_progress = true; } else if (strcmp(argv[i], "--no-auto-symbol-paths") == 0) { no_auto_symbol_paths = true; } else if (strcmp(argv[i], "--no-auto-proguard-maps") == 0) { @@ -330,19 +334,19 @@ } if (format == "json") - return ToExitCode(trace_to_text::TraceToJson(input_stream, output_stream, - /*compress=*/false, - truncate_keep, full_sort)); + return ToExitCode(trace_to_text::TraceToJson( + input_stream, output_stream, + /*compress=*/false, truncate_keep, full_sort, no_progress)); if (format == "systrace") return ToExitCode(trace_to_text::TraceToSystrace( - input_stream, output_stream, /*ctrace=*/false, truncate_keep, - full_sort)); + input_stream, output_stream, /*ctrace=*/false, truncate_keep, full_sort, + no_progress)); if (format == "ctrace") return ToExitCode(trace_to_text::TraceToSystrace( - input_stream, output_stream, /*ctrace=*/true, truncate_keep, - full_sort)); + input_stream, output_stream, /*ctrace=*/true, truncate_keep, full_sort, + no_progress)); if (truncate_keep != trace_to_text::Keep::kAll) { PERFETTO_ELOG( @@ -361,6 +365,7 @@ if (format == "text") { trace_to_text::TraceToTextOptions options; options.skip_unknown_fields = skip_unknown_fields; + options.no_progress = no_progress; return ToExitCode( trace_to_text::TraceToText(input_stream, output_stream, options)); } @@ -374,27 +379,27 @@ } return ToExitCode(trace_to_text::TraceToProfile( input_stream, pid, timestamps, !profile_no_annotations, output_dir, - profile_type, verbose)); + profile_type, verbose, no_progress)); } if (format == "java_heap_profile") { // legacy alias for "profile --java-heap" return ToExitCode(trace_to_text::TraceToProfile( input_stream, pid, timestamps, !profile_no_annotations, output_dir, - trace_to_text::ConversionMode::kJavaHeapProfile, verbose)); + trace_to_text::ConversionMode::kJavaHeapProfile, verbose, no_progress)); } if (format == "symbolize") - return ToExitCode( - trace_to_text::SymbolizeProfile(input_stream, output_stream, verbose)); + return ToExitCode(trace_to_text::SymbolizeProfile( + input_stream, output_stream, verbose, no_progress)); if (format == "deobfuscate") return ToExitCode( trace_to_text::DeobfuscateProfile(input_stream, output_stream)); if (format == "firefox") - return ToExitCode( - trace_to_text::TraceToFirefoxProfile(input_stream, output_stream)); + return ToExitCode(trace_to_text::TraceToFirefoxProfile( + input_stream, output_stream, no_progress)); if (format == "decompress_packets") return ToExitCode( @@ -434,6 +439,7 @@ context.no_auto_symbol_paths = no_auto_symbol_paths; context.no_auto_proguard_maps = no_auto_proguard_maps; context.verbose = verbose; + context.no_progress = no_progress; if (const char* val = getenv("ANDROID_PRODUCT_OUT")) { context.android_product_out = val; }
diff --git a/src/traceconv/utils.cc b/src/traceconv/utils.cc index 5be46c3..9b3d610 100644 --- a/src/traceconv/utils.cc +++ b/src/traceconv/utils.cc
@@ -27,8 +27,10 @@ #include "perfetto/base/logging.h" #include "perfetto/ext/base/file_utils.h" +#include "perfetto/ext/base/progress_reporter.h" #include "perfetto/ext/base/scoped_file.h" #include "perfetto/ext/base/string_splitter.h" +#include "perfetto/ext/base/string_utils.h" #include "perfetto/protozero/scattered_heap_buffer.h" #include "perfetto/trace_processor/trace_processor.h" @@ -40,7 +42,6 @@ namespace perfetto { namespace trace_to_text { namespace { - #if PERFETTO_BUILDFLAG(PERFETTO_ZLIB) constexpr size_t kCompressionBufferSize = 500 * 1024; #endif @@ -48,7 +49,9 @@ } // namespace bool ReadTraceUnfinalized(trace_processor::TraceProcessor* tp, - std::istream* input) { + std::istream* input, + bool no_progress) { + base::ProgressReporter progress(!no_progress); // 1MB chunk size seems the best tradeoff on a MacBook Pro 2013 - i7 2.8 GHz. constexpr size_t kChunkSize = 1024 * 1024; @@ -63,8 +66,10 @@ for (int i = 0;; i++) { if (i % kStderrRate == 0) { - ProgressLine("Loading trace %.2f MB", - static_cast<double>(file_size) / 1.0e6); + progress.Update( + base::StackString<128>("Loading trace %.2f MB", + static_cast<double>(file_size) / 1.0e6) + .ToStdString()); } std::unique_ptr<uint8_t[]> buf(new uint8_t[kChunkSize]); @@ -81,8 +86,7 @@ tp->Parse(std::move(buf), static_cast<size_t>(rsize)); } - ProgressLine("Loaded trace"); - EndProgressLine(); + progress.Clear(); return true; }
diff --git a/src/traceconv/utils.h b/src/traceconv/utils.h index 64640e9..dd349ef 100644 --- a/src/traceconv/utils.h +++ b/src/traceconv/utils.h
@@ -46,63 +46,9 @@ namespace trace_to_text { -// When running in Web Assembly, fflush() is a no-op and the stdio buffering -// sends progress updates to JS only when a write ends with \n. -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WASM) -constexpr char kProgressChar = '\n'; -#else -constexpr char kProgressChar = '\r'; -#endif - -// True while an in-place progress line has been printed but not yet -// terminated with a newline. TU-local: all progress printing and the -// matching EndProgressLine() live in the same translation unit. -#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WASM) -namespace { -inline bool& IsProgressLineActive() { - static bool active = false; - return active; -} -} // namespace -#endif - -// Prints an in-place progress update to stderr: the message followed by -// kProgressChar (a '\r' so the next update overwrites it). Remembers that a -// progress line is active so the matching EndProgressLine() terminates it -// with a newline. No-op on WASM where updates already end with '\n'. -inline void ProgressLine(const char* fmt, ...) PERFETTO_PRINTF_FORMAT(1, 2); -inline void ProgressLine(const char* fmt, ...) { -#if PERFETTO_BUILDFLAG(PERFETTO_OS_WASM) - ::perfetto::base::ignore_result(fmt); -#else - va_list args; - va_start(args, fmt); - vfprintf(stderr, fmt, args); - va_end(args); - fputc(kProgressChar, stderr); - fflush(stderr); - IsProgressLineActive() = true; -#endif -} - -// Terminates the current in-place progress line so that subsequent output -// (errors, logs, results) starts on a fresh line instead of overwriting or -// merging with the progress text. No-op when no progress line is active, so -// callers can invoke it unconditionally without producing stray blank lines. -// On WASM, progress updates already end with a newline (see kProgressChar), -// so this is a no-op there too. -inline void EndProgressLine() { -#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WASM) - if (IsProgressLineActive()) { - fprintf(stderr, "\n"); - fflush(stderr); - IsProgressLineActive() = false; - } -#endif -} - bool ReadTraceUnfinalized(trace_processor::TraceProcessor* tp, - std::istream* input); + std::istream* input, + bool no_progress = false); void IngestTraceOrDie(trace_processor::TraceProcessor* tp, const std::string& trace_proto);