Win port: add base::getopt_compat
Windows doesn't have <getopt.h> but all our executable these
days depend on it. We have two roads ahead:
1. Create an abstraction layer, like base::ArgParsers to deal
with cmdline parsing.
2. Create a getopt emulation for Windows.
1 is too much risk and too much effort. On Android our cmdline
is de-facto an API, we can't risk subtle breakages.
Instead it's easier and lower risk to create a getopt emulation
for Windows.
This CL also introduces a test that checks that the behavior of
the two is consistent.
Bug: 174454879
Test: perfetto_unittests --gtest_filter=GetOpt*
Change-Id: I68752e615ad0aaf24226ddbce0d7ba77140bdca9
diff --git a/Android.bp b/Android.bp
index 046da6e..a8f0d8c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6405,6 +6405,7 @@
"src/base/ctrl_c_handler.cc",
"src/base/event_fd.cc",
"src/base/file_utils.cc",
+ "src/base/getopt_compat.cc",
"src/base/logging.cc",
"src/base/metatrace.cc",
"src/base/paged_memory.cc",
@@ -6446,6 +6447,7 @@
srcs: [
"src/base/circular_queue_unittest.cc",
"src/base/flat_set_unittest.cc",
+ "src/base/getopt_compat_unittest.cc",
"src/base/metatrace_unittest.cc",
"src/base/no_destructor_unittest.cc",
"src/base/optional_unittest.cc",
diff --git a/BUILD b/BUILD
index d91e53f..71a5ef7 100644
--- a/BUILD
+++ b/BUILD
@@ -295,6 +295,8 @@
"include/perfetto/ext/base/endian.h",
"include/perfetto/ext/base/event_fd.h",
"include/perfetto/ext/base/file_utils.h",
+ "include/perfetto/ext/base/getopt.h",
+ "include/perfetto/ext/base/getopt_compat.h",
"include/perfetto/ext/base/hash.h",
"include/perfetto/ext/base/metatrace.h",
"include/perfetto/ext/base/metatrace_events.h",
@@ -578,6 +580,7 @@
"src/base/ctrl_c_handler.cc",
"src/base/event_fd.cc",
"src/base/file_utils.cc",
+ "src/base/getopt_compat.cc",
"src/base/logging.cc",
"src/base/metatrace.cc",
"src/base/paged_memory.cc",
diff --git a/include/perfetto/ext/base/BUILD.gn b/include/perfetto/ext/base/BUILD.gn
index 15e1799..8cfb32e 100644
--- a/include/perfetto/ext/base/BUILD.gn
+++ b/include/perfetto/ext/base/BUILD.gn
@@ -22,6 +22,8 @@
"endian.h",
"event_fd.h",
"file_utils.h",
+ "getopt.h",
+ "getopt_compat.h",
"hash.h",
"metatrace.h",
"metatrace_events.h",
diff --git a/include/perfetto/ext/base/getopt.h b/include/perfetto/ext/base/getopt.h
new file mode 100644
index 0000000..77c1490
--- /dev/null
+++ b/include/perfetto/ext/base/getopt.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDE_PERFETTO_EXT_BASE_GETOPT_H_
+#define INCLUDE_PERFETTO_EXT_BASE_GETOPT_H_
+
+// This is the header that should be included in all places that need getopt.h.
+// This either routes on the sysroot getopt.h, for OSes that have one (all but
+// Windows) or routes on the home-brewed getopt_compat.h.
+
+#include "perfetto/base/build_config.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include "perfetto/ext/base/getopt_compat.h"
+
+// getopt_compat.h puts everything in a nested namespace, to allow
+// getopt_compat_unittest.cc to use both <getopt.h> and "getopt_compat.h"
+// without collisions.
+
+// Here we expose them into the root namespace, because we want offer a drop-in
+// replacement to the various main.cc, which can't know about the nested
+// namespace.
+using ::perfetto::base::getopt_compat::optarg;
+using ::perfetto::base::getopt_compat::optind;
+using ::perfetto::base::getopt_compat::option;
+constexpr auto getopt = ::perfetto::base::getopt_compat::getopt;
+constexpr auto getopt_long = ::perfetto::base::getopt_compat::getopt_long;
+constexpr auto no_argument = ::perfetto::base::getopt_compat::no_argument;
+constexpr auto required_argument =
+ ::perfetto::base::getopt_compat::required_argument;
+
+#else
+#include <getopt.h>
+#endif
+
+#endif // INCLUDE_PERFETTO_EXT_BASE_GETOPT_H_
diff --git a/include/perfetto/ext/base/getopt_compat.h b/include/perfetto/ext/base/getopt_compat.h
new file mode 100644
index 0000000..d1f5436
--- /dev/null
+++ b/include/perfetto/ext/base/getopt_compat.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDE_PERFETTO_EXT_BASE_GETOPT_COMPAT_H_
+#define INCLUDE_PERFETTO_EXT_BASE_GETOPT_COMPAT_H_
+
+#include <cstddef> // For std::nullptr_t
+
+// No translation units other than base/getopt.h and getopt_compat_unittest.cc
+// should directly include this file. Use base/getopt.h instead.
+
+namespace perfetto {
+namespace base {
+namespace getopt_compat {
+
+// A tiny getopt() replacement for Windows, which doesn't have <getopt.h>.
+// This implementation is based on the subset of features that we use in the
+// Perfetto codebase. It doesn't even try to deal with the full surface of GNU's
+// getopt().
+// Limitations:
+// - getopt_long_only() is not supported.
+// - optional_argument is not supported. That is extremely subtle and caused us
+// problems in the past with GNU's getopt.
+// - It does not reorder non-option arguments. It behaves like MacOS getopt, or
+// GNU's when POSIXLY_CORRECT=1.
+// - Doesn't expose optopt or opterr.
+// - option.flag and longindex are not supported and must be nullptr.
+
+enum {
+ no_argument = 0,
+ required_argument = 1,
+};
+
+struct option {
+ const char* name;
+ int has_arg;
+ std::nullptr_t flag; // Only nullptr is supported.
+ int val;
+};
+
+extern char* optarg;
+extern int optind;
+
+int getopt_long(int argc,
+ char** argv,
+ const char* shortopts,
+ const option* longopts,
+ std::nullptr_t /*longindex is not supported*/);
+
+int getopt(int argc, char** argv, const char* shortopts);
+
+} // namespace getopt_compat
+} // namespace base
+} // namespace perfetto
+
+#endif // INCLUDE_PERFETTO_EXT_BASE_GETOPT_COMPAT_H_
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index 76921cf..8c05e2e 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -33,6 +33,7 @@
"ctrl_c_handler.cc",
"event_fd.cc",
"file_utils.cc",
+ "getopt_compat.cc",
"logging.cc",
"metatrace.cc",
"paged_memory.cc",
@@ -152,6 +153,7 @@
sources = [
"circular_queue_unittest.cc",
"flat_set_unittest.cc",
+ "getopt_compat_unittest.cc",
"no_destructor_unittest.cc",
"optional_unittest.cc",
"paged_memory_unittest.cc",
diff --git a/src/base/getopt_compat.cc b/src/base/getopt_compat.cc
new file mode 100644
index 0000000..19c8adc
--- /dev/null
+++ b/src/base/getopt_compat.cc
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/ext/base/getopt_compat.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <vector>
+
+#include "perfetto/base/logging.h"
+
+namespace perfetto {
+namespace base {
+namespace getopt_compat {
+
+char* optarg = nullptr;
+int optind = 0;
+
+namespace {
+
+char* nextchar = nullptr;
+
+const option* LookupLongOpt(const std::vector<option>& opts,
+ const char* name,
+ size_t len) {
+ for (const option& opt : opts) {
+ if (strncmp(opt.name, name, len) == 0 && strlen(opt.name) == len)
+ return &opt;
+ }
+ return nullptr;
+}
+
+const option* LookupShortOpt(const std::vector<option>& opts, char c) {
+ for (const option& opt : opts) {
+ if (opt.name == nullptr && opt.val == c)
+ return &opt;
+ }
+ return nullptr;
+}
+
+bool ParseOpts(const char* shortopts,
+ const option* longopts,
+ std::vector<option>* res) {
+ // Parse long options first.
+ for (const option* lopt = longopts; lopt && lopt->name; lopt++) {
+ PERFETTO_CHECK(lopt->flag == nullptr);
+ PERFETTO_CHECK(lopt->has_arg == no_argument ||
+ lopt->has_arg == required_argument);
+ res->emplace_back(*lopt);
+ }
+
+ // Merge short options.
+ for (const char* sopt = shortopts; sopt && *sopt;) {
+ const size_t idx = static_cast<size_t>(sopt - shortopts);
+ char c = *sopt++;
+ bool valid = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9');
+ if (!valid) {
+ fprintf(stderr,
+ "Error parsing shortopts. Unexpected char '%c' at offset %zu\n",
+ c, idx);
+ return false;
+ }
+ res->emplace_back();
+ option& opt = res->back();
+ opt.val = c;
+ opt.has_arg = no_argument;
+ if (*sopt == ':') {
+ opt.has_arg = required_argument;
+ ++sopt;
+ }
+ }
+ return true;
+}
+
+} // namespace
+
+int getopt_long(int argc,
+ char** argv,
+ const char* shortopts,
+ const option* longopts,
+ std::nullptr_t /*longind*/) {
+ std::vector<option> opts;
+ optarg = nullptr;
+
+ if (optind == 0)
+ optind = 1;
+
+ if (optind >= argc)
+ return -1;
+
+ if (!ParseOpts(shortopts, longopts, &opts))
+ return '?';
+
+ char* arg = argv[optind];
+
+ if (!nextchar) {
+ // If |nextchar| is null we are NOT in the middle of a short option and we
+ // should parse the next argv.
+ if (strncmp(arg, "--", 2) == 0 && strlen(arg) > 2) {
+ // A --long option.
+ arg += 2;
+ char* sep = strchr(arg, '=');
+ optind++;
+
+ size_t len = sep ? static_cast<size_t>(sep - arg) : strlen(arg);
+ const option* opt = LookupLongOpt(opts, arg, len);
+ if (!opt) {
+ fprintf(stderr, "unrecognized option '--%s'\n", arg);
+ return '?';
+ }
+
+ if (opt->has_arg == no_argument) {
+ if (sep) {
+ fprintf(stderr, "option '--%s' doesn't allow an argument\n", arg);
+ return '?';
+ } else {
+ return opt->val;
+ }
+ } else if (opt->has_arg == required_argument) {
+ if (sep) {
+ optarg = sep + 1;
+ return opt->val;
+ } else if (optind >= argc) {
+ fprintf(stderr, "option '--%s' requires an argument\n", arg);
+ return '?';
+ } else {
+ optarg = argv[optind++];
+ return opt->val;
+ }
+ }
+ // has_arg must be either |no_argument| or |required_argument|. We
+ // shoulnd't get here unless the check in ParseOpts() has a bug.
+ PERFETTO_CHECK(false);
+ } // if (arg ~= "--*").
+
+ if (strlen(arg) > 1 && arg[0] == '-' && arg[1] != '-') {
+ // A sequence of short options. Parsing logic continues below.
+ nextchar = &arg[1];
+ }
+ } // if(!nextchar)
+
+ if (nextchar) {
+ // At this point either:
+ // 1. This is the first char of a sequence of short options, and we fell
+ // through here from the lines above.
+ // 2. This is the N (>1) char of a sequence of short options, and we got
+ // here from a new getopt() call to getopt().
+ const char cur_char = *nextchar;
+ PERFETTO_CHECK(cur_char != '\0');
+
+ // Advance the option char in any case, before we start reasoning on them.
+ // if we got to the end of the "-abc" sequence, increment optind so the next
+ // getopt() call resumes from the next argv argument.
+ if (*(++nextchar) == '\0') {
+ nextchar = nullptr;
+ ++optind;
+ }
+
+ const option* opt = LookupShortOpt(opts, cur_char);
+ if (!opt) {
+ fprintf(stderr, "invalid option -- '%c'\n", cur_char);
+ return '?';
+ }
+ if (opt->has_arg == no_argument) {
+ return cur_char;
+ } else if (opt->has_arg == required_argument) {
+ // This is a subtle getopt behavior. Say you call `tar -fx`, there are
+ // two cases:
+ // 1. If 'f' is no_argument then 'x' (and anything else after) is
+ // interpreted as an independent argument (like `tar -f -x`).
+ // 2. If 'f' is required_argument, than everything else after the 'f'
+ // is interpreted as the option argument (like `tar -f x`)
+ if (!nextchar) {
+ // Case 1.
+ if (optind >= argc) {
+ fprintf(stderr, "option requires an argument -- '%c'\n", cur_char);
+ return '?';
+ } else {
+ optarg = argv[optind++];
+ return cur_char;
+ }
+ } else {
+ // Case 2.
+ optarg = nextchar;
+ nextchar = nullptr;
+ optind++;
+ return cur_char;
+ }
+ }
+ PERFETTO_CHECK(false);
+ } // if (nextchar)
+
+ // If we get here, we found the first non-option argument. Stop here.
+
+ if (strcmp(arg, "--") == 0)
+ optind++;
+
+ return -1;
+}
+
+int getopt(int argc, char** argv, const char* shortopts) {
+ return getopt_long(argc, argv, shortopts, nullptr, nullptr);
+}
+
+} // namespace getopt_compat
+} // namespace base
+} // namespace perfetto
diff --git a/src/base/getopt_compat_unittest.cc b/src/base/getopt_compat_unittest.cc
new file mode 100644
index 0000000..a7d4db3
--- /dev/null
+++ b/src/base/getopt_compat_unittest.cc
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/ext/base/getopt_compat.h"
+
+// This test has two roles:
+// 1. In Windows builds it's a plain unittest for our getopt_compat.cc
+// 2. On other builds it also checks that the behavior of our getopt_compat.cc
+// is the same of <getopt.h> (for the options we support).
+// It does so creating a gtest typed test, and defining two structs that inject
+// getopt functions and global variables like optind.
+
+#include "perfetto/base/build_config.h"
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <getopt.h>
+#endif
+
+#include <initializer_list>
+
+#include "test/gtest_and_gmock.h"
+
+using testing::ElementsAre;
+using testing::ElementsAreArray;
+
+namespace perfetto {
+namespace base {
+namespace {
+
+struct OurGetopt {
+ using LongOptionType = getopt_compat::option;
+ using GetoptFn = decltype(&getopt_compat::getopt);
+ using GetoptLongFn = decltype(&getopt_compat::getopt_long);
+ GetoptFn getopt = &getopt_compat::getopt;
+ GetoptLongFn getopt_long = &getopt_compat::getopt_long;
+ int& optind = getopt_compat::optind;
+ char*& optarg = getopt_compat::optarg;
+};
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+struct SystemGetopt {
+ using LongOptionType = ::option;
+ using GetoptFn = decltype(&::getopt);
+ using GetoptLongFn = decltype(&::getopt_long);
+ GetoptFn getopt = &::getopt;
+ GetoptLongFn getopt_long = &::getopt_long;
+ int& optind = ::optind;
+ char*& optarg = ::optarg;
+};
+#endif
+
+template <typename T>
+class GetoptCompatTest : public testing::Test {
+ public:
+ inline void SetCmdline(std::initializer_list<const char*> arg_list) {
+ // Reset the getopt() state.
+ // When calling getopt() several times, MacOS requires that optind is reset
+ // to 1, while Linux requires optind to be reset to 0. Also MacOS requires
+ // optreset to be set as well.
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+ impl.optind = 1;
+ optreset = 1; // It has no corresponding variable in other OSes.
+#else
+ impl.optind = 0;
+#endif
+ argc = static_cast<int>(arg_list.size());
+ for (char*& arg : argv)
+ arg = nullptr;
+ size_t i = 0;
+ for (const char* arg : arg_list)
+ argv[i++] = const_cast<char*>(arg);
+ }
+ int argc;
+ char* argv[32]; // We don't use more than 32 entries on our tests.
+ T impl;
+};
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+using GetoptTestTypes = ::testing::Types<OurGetopt>;
+#else
+using GetoptTestTypes = ::testing::Types<OurGetopt, SystemGetopt>;
+#endif
+TYPED_TEST_SUITE(GetoptCompatTest, GetoptTestTypes, /* trailing ',' for GCC*/);
+
+TYPED_TEST(GetoptCompatTest, ShortOptions) {
+ auto& t = this->impl;
+
+ const char* sops = "";
+ this->SetCmdline({"argv0"});
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+
+ sops = "h";
+ this->SetCmdline({"argv0"});
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+
+ sops = "h";
+ this->SetCmdline({"argv0", "-h"});
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'h');
+ EXPECT_EQ(t.optarg, nullptr);
+ EXPECT_EQ(t.optind, 2);
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+ EXPECT_EQ(t.optind, 2);
+
+ sops = "h";
+ this->SetCmdline({"argv0", "positional1", "positional2"});
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+
+ sops = "h";
+ this->SetCmdline({"argv0", "--", "positional1", "positional2"});
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+ EXPECT_EQ(t.optind, 2);
+
+ sops = "h";
+ this->SetCmdline({"argv0", "-h"});
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'h');
+ EXPECT_EQ(t.optind, 2);
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+ EXPECT_EQ(t.optind, 2);
+
+ sops = "abc";
+ this->SetCmdline({"argv0", "-c", "-a", "-b"});
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
+ EXPECT_EQ(t.optarg, nullptr);
+ EXPECT_EQ(t.optind, 2);
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
+ EXPECT_EQ(t.optind, 3);
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'b');
+ EXPECT_EQ(t.optind, 4);
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+ EXPECT_EQ(t.optind, 4);
+
+ sops = "abc";
+ this->SetCmdline({"argv0", "-c", "-a", "--", "nonopt"});
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
+ EXPECT_EQ(t.optarg, nullptr);
+ EXPECT_EQ(t.optind, 2);
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
+ EXPECT_EQ(t.optarg, nullptr);
+ EXPECT_EQ(t.optind, 3);
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+ EXPECT_EQ(t.optarg, nullptr);
+ EXPECT_EQ(t.optind, 4);
+
+ sops = "abc";
+ this->SetCmdline({"argv0", "-cb"});
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
+ EXPECT_EQ(t.optarg, nullptr);
+ EXPECT_EQ(t.optind, 1);
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'b');
+ EXPECT_EQ(t.optarg, nullptr);
+ EXPECT_EQ(t.optind, 2);
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+ EXPECT_EQ(t.optarg, nullptr);
+ EXPECT_EQ(t.optind, 2);
+
+ sops = "abc";
+ this->SetCmdline({"argv0", "-aa", "-c"});
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
+ EXPECT_EQ(t.optind, 1);
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
+ EXPECT_EQ(t.optind, 2);
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
+ EXPECT_EQ(t.optind, 3);
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+ EXPECT_EQ(t.optind, 3);
+
+ sops = "a:bc";
+ // The semantic here is `-a b -c`
+ this->SetCmdline({"argv0", "-ab", "-c"});
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
+ EXPECT_EQ(t.optind, 2);
+ EXPECT_STREQ(t.optarg, "b");
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
+ EXPECT_EQ(t.optind, 3);
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+ EXPECT_EQ(t.optind, 3);
+
+ sops = "a:bc";
+ this->SetCmdline({"argv0", "-ab", "--", "-c"});
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
+ EXPECT_EQ(t.optind, 2);
+ EXPECT_STREQ(t.optarg, "b");
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+ EXPECT_EQ(t.optind, 3);
+
+ sops = "a:b:c:";
+ this->SetCmdline({"argv0", "-a", "arg1", "-b", "--", "-c", "-carg"});
+ // This is sbutle, the "--" is an arg value for "-b", not a separator.
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'a');
+ EXPECT_STREQ(t.optarg, "arg1");
+ EXPECT_EQ(t.optind, 3);
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'b');
+ EXPECT_STREQ(t.optarg, "--");
+ EXPECT_EQ(t.optind, 5);
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), 'c');
+ EXPECT_STREQ(t.optarg, "-carg");
+ EXPECT_EQ(t.optind, 7);
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), -1);
+ EXPECT_EQ(t.optind, 7);
+
+ sops = "a";
+ this->SetCmdline({"argv0", "-q"});
+ EXPECT_EQ(t.getopt(this->argc, this->argv, sops), '?');
+ EXPECT_EQ(t.optind, 2);
+}
+
+TYPED_TEST(GetoptCompatTest, LongOptions) {
+ auto& t = this->impl;
+ using LongOptionType = typename decltype(this->impl)::LongOptionType;
+
+ {
+ LongOptionType lopts[]{
+ {nullptr, 0, nullptr, 0},
+ };
+ const char* sops = "";
+ this->SetCmdline({"argv0"});
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
+ EXPECT_EQ(t.optarg, nullptr);
+ EXPECT_EQ(t.optind, 1);
+ }
+
+ {
+ LongOptionType lopts[]{
+ {nullptr, 0, nullptr, 0},
+ };
+ const char* sops = "";
+ this->SetCmdline({"argv0", "--unknown"});
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '?');
+ EXPECT_EQ(t.optarg, nullptr);
+ EXPECT_EQ(t.optind, 2);
+ }
+
+ {
+ LongOptionType lopts[]{
+ {"one", 0 /*no_argument*/, nullptr, 1},
+ {"two", 0 /*no_argument*/, nullptr, 2},
+ {nullptr, 0, nullptr, 0},
+ };
+ const char* sops = "";
+ this->SetCmdline({"argv0", "--two", "--one"});
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
+ EXPECT_EQ(t.optarg, nullptr);
+ EXPECT_EQ(t.optind, 2);
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1);
+ EXPECT_EQ(t.optarg, nullptr);
+ EXPECT_EQ(t.optind, 3);
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
+ EXPECT_EQ(t.optarg, nullptr);
+ EXPECT_EQ(t.optind, 3);
+ }
+
+ {
+ LongOptionType lopts[]{
+ {"one", 0 /*no_argument*/, nullptr, 1},
+ {"two", 0 /*no_argument*/, nullptr, 2},
+ {nullptr, 0, nullptr, 0},
+ };
+ const char* sops = "";
+ this->SetCmdline({"argv0", "--two", "--one", "--not-an-opt"});
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
+ EXPECT_EQ(t.optarg, nullptr);
+ EXPECT_EQ(t.optind, 2);
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1);
+ EXPECT_EQ(t.optarg, nullptr);
+ EXPECT_EQ(t.optind, 3);
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '?');
+ EXPECT_EQ(t.optarg, nullptr);
+ EXPECT_EQ(t.optind, 4);
+ }
+
+ {
+ LongOptionType lopts[]{
+ {"one", 0 /*no_argument*/, nullptr, 1},
+ {"two", 0 /*no_argument*/, nullptr, 2},
+ {nullptr, 0, nullptr, 0},
+ };
+ const char* sops = "";
+ this->SetCmdline({"argv0", "--two", "--one", "--", "--not-an-opt"});
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
+ EXPECT_EQ(t.optind, 2);
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1);
+ EXPECT_EQ(t.optind, 3);
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
+ EXPECT_EQ(t.optind, 4);
+ }
+
+ {
+ LongOptionType lopts[]{
+ {"no1", 0 /*no_argument*/, nullptr, 1},
+ {"req2", 1 /*required_argument*/, nullptr, 2},
+ {"req3", 1 /*required_argument*/, nullptr, 3},
+ {nullptr, 0, nullptr, 0},
+ };
+ const char* sops = "";
+ // This is subtle: the "--" really is an argument for req2, not an argument
+ // separator. The first positional arg is "!!!".
+ this->SetCmdline({"argv0", "--req3", "-", "--no1", "--req2", "--", "!!!"});
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 3);
+ EXPECT_EQ(t.optind, 3);
+ EXPECT_STREQ(t.optarg, "-");
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 1);
+ EXPECT_EQ(t.optind, 4);
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
+ EXPECT_STREQ(t.optarg, "--");
+ EXPECT_EQ(t.optind, 6);
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
+ EXPECT_EQ(t.optind, 6);
+ }
+
+ {
+ LongOptionType lopts[]{
+ {"no1", 0 /*no_argument*/, nullptr, 1},
+ {"req2", 1 /*required_argument*/, nullptr, 2},
+ {nullptr, 0, nullptr, 0},
+ };
+ const char* sops = "";
+ this->SetCmdline({"argv0", "--req2", "foo", "--", "--no1"});
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
+ EXPECT_EQ(t.optind, 3);
+ EXPECT_STREQ(t.optarg, "foo");
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
+ EXPECT_EQ(t.optind, 4);
+ }
+}
+
+TYPED_TEST(GetoptCompatTest, ShortAndLongOptions) {
+ auto& t = this->impl;
+ using LongOptionType = typename decltype(this->impl)::LongOptionType;
+
+ {
+ LongOptionType lopts[]{
+ {"one", 0 /*no_argument*/, nullptr, 1},
+ {"two", 0 /*no_argument*/, nullptr, 2},
+ {"three", 0 /*no_argument*/, nullptr, 3},
+ {nullptr, 0, nullptr, 0},
+ };
+ const char* sops = "123";
+
+ this->SetCmdline({"argv0"});
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
+ EXPECT_EQ(t.optarg, nullptr);
+ EXPECT_EQ(t.optind, 1);
+
+ this->SetCmdline({"argv0", "-13", "--two", "--three", "--", "--one"});
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '1');
+ EXPECT_EQ(t.optind, 1);
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '3');
+ EXPECT_EQ(t.optind, 2);
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
+ EXPECT_EQ(t.optind, 3);
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 3);
+ EXPECT_EQ(t.optind, 4);
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
+ EXPECT_EQ(t.optind, 5);
+
+ this->SetCmdline({"argv0", "--two", "-1", "--two", "-13"});
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
+ EXPECT_EQ(t.optind, 2);
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '1');
+ EXPECT_EQ(t.optind, 3);
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), 2);
+ EXPECT_EQ(t.optind, 4);
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '1');
+ EXPECT_EQ(t.optind, 4);
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), '3');
+ EXPECT_EQ(t.optind, 5);
+ EXPECT_EQ(t.getopt_long(this->argc, this->argv, sops, lopts, nullptr), -1);
+ EXPECT_EQ(t.optind, 5);
+ }
+}
+
+} // namespace
+} // namespace base
+} // namespace perfetto
diff --git a/src/perfetto_cmd/packet_writer.cc b/src/perfetto_cmd/packet_writer.cc
index d4e9835..2bee2f0 100644
--- a/src/perfetto_cmd/packet_writer.cc
+++ b/src/perfetto_cmd/packet_writer.cc
@@ -19,12 +19,12 @@
#include <array>
#include <fcntl.h>
-#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <sys/stat.h>
#include "perfetto/base/build_config.h"
+#include "perfetto/ext/base/getopt.h"
#include "perfetto/ext/base/paged_memory.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/tracing/core/trace_packet.h"
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index 2610154..5890abe 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -19,7 +19,6 @@
#include "perfetto/base/build_config.h"
#include <fcntl.h>
-#include <getopt.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -46,6 +45,7 @@
#include "perfetto/base/time.h"
#include "perfetto/ext/base/ctrl_c_handler.h"
#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/getopt.h"
#include "perfetto/ext/base/string_view.h"
#include "perfetto/ext/base/thread_utils.h"
#include "perfetto/ext/base/utils.h"
@@ -265,7 +265,6 @@
{"save-for-bugreport", no_argument, nullptr, OPT_BUGREPORT},
{nullptr, 0, nullptr, 0}};
- int option_index = 0;
std::string config_file_name;
std::string trace_config_raw;
bool background = false;
@@ -278,8 +277,7 @@
bool has_config_options = false;
for (;;) {
- int option =
- getopt_long(argc, argv, "hc:o:dt:b:s:", long_options, &option_index);
+ int option = getopt_long(argc, argv, "hc:o:dt:b:s:", long_options, nullptr);
if (option == -1)
break; // EOF.
diff --git a/src/perfetto_cmd/trigger_perfetto.cc b/src/perfetto_cmd/trigger_perfetto.cc
index 2e88412..ae44018 100644
--- a/src/perfetto_cmd/trigger_perfetto.cc
+++ b/src/perfetto_cmd/trigger_perfetto.cc
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-#include <getopt.h>
-
#include <string>
#include <vector>
#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/getopt.h"
#include "perfetto/ext/base/unix_task_runner.h"
#include "perfetto/ext/traced/traced.h"
#include "src/android_stats/statsd_logging_helper.h"
@@ -43,12 +42,10 @@
static const option long_options[] = {{"help", no_argument, nullptr, 'h'},
{nullptr, 0, nullptr, 0}};
- int option_index = 0;
-
std::vector<std::string> triggers_to_activate;
for (;;) {
- int option = getopt_long(argc, argv, "h", long_options, &option_index);
+ int option = getopt_long(argc, argv, "h", long_options, nullptr);
if (option == 'h')
return PrintUsage(argv[0]);
diff --git a/src/profiling/memory/main.cc b/src/profiling/memory/main.cc
index cfc4943..146e98d 100644
--- a/src/profiling/memory/main.cc
+++ b/src/profiling/memory/main.cc
@@ -20,10 +20,10 @@
#include <memory>
#include <vector>
-#include <getopt.h>
#include <signal.h>
#include "perfetto/ext/base/event_fd.h"
+#include "perfetto/ext/base/getopt.h"
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/unix_socket.h"
#include "perfetto/ext/base/watchdog.h"
@@ -73,9 +73,8 @@
{"exclusive-for-cmdline", required_argument, nullptr, kTargetCmd},
{"inherit-socket-fd", required_argument, nullptr, kInheritFd},
{nullptr, 0, nullptr, 0}};
- int option_index;
int c;
- while ((c = getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
+ while ((c = getopt_long(argc, argv, "", long_options, nullptr)) != -1) {
switch (c) {
case kCleanupCrash:
cleanup_crash = true;
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index cb6a17e..e02225d 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -32,6 +32,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/base/time.h"
#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/getopt.h"
#include "perfetto/ext/base/scoped_file.h"
#include "perfetto/ext/base/string_splitter.h"
#include "perfetto/ext/base/string_utils.h"
@@ -75,7 +76,6 @@
#define ftruncate _chsize
#else
#include <dirent.h>
-#include <getopt.h>
#endif
#if PERFETTO_BUILDFLAG(PERFETTO_TP_LINENOISE) && \
@@ -668,63 +668,6 @@
std::string metatrace_path;
};
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-void PrintUsage(char** argv) {
- PERFETTO_ELOG(R"(
-Interactive trace processor shell.
-Usage: %s [OPTIONS] trace_file.pb
-
-Options:
- -q, --query-file FILE Read and execute an SQL query from a file.
- If used with --run-metrics, the query is
- executed after the selected metrics and
- the metrics output is suppressed.
- --pre-metrics FILE Read and execute an SQL query from a file.
- This query is executed before the selected
- metrics and can't output any results.
- --run-metrics x,y,z Runs a comma separated list of metrics and
- prints the result as a TraceMetrics proto
- to stdout. The specified can either be
- in-built metrics or SQL/proto files of
- extension metrics.
- --metrics-output [binary|text|json] Allows the output of --run-metrics to be
- specified in either proto binary, proto
- text format or JSON format (default: proto
- text).)",
- argv[0]);
-}
-
-CommandLineOptions ParseCommandLineOptions(int argc, char** argv) {
- CommandLineOptions command_line_options;
-
- if (argc < 2 || argc % 2 == 1) {
- PrintUsage(argv);
- exit(1);
- }
-
- for (int i = 1; i < argc - 1; i += 2) {
- if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--query-file") == 0) {
- command_line_options.query_file_path = argv[i + 1];
- } else if (strcmp(argv[i], "--pre-metrics") == 0) {
- command_line_options.pre_metrics_path = argv[i + 1];
- } else if (strcmp(argv[i], "--run-metrics") == 0) {
- command_line_options.metric_names = argv[i + 1];
- } else if (strcmp(argv[i], "--metrics-output") == 0) {
- command_line_options.metric_output = argv[i + 1];
- } else {
- PrintUsage(argv);
- exit(1);
- }
- }
- command_line_options.trace_file_path = argv[argc - 1];
- command_line_options.launch_shell =
- command_line_options.metric_names.empty() &&
- command_line_options.query_file_path.empty();
- return command_line_options;
-}
-
-#else // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-
void PrintUsage(char** argv) {
PERFETTO_ELOG(R"(
Interactive trace processor shell.
@@ -802,10 +745,9 @@
{nullptr, 0, nullptr, 0}};
bool explicit_interactive = false;
- int option_index = 0;
for (;;) {
int option =
- getopt_long(argc, argv, "hvWiDdm:p:q:e:", long_options, &option_index);
+ getopt_long(argc, argv, "hvWiDdm:p:q:e:", long_options, nullptr);
if (option == -1)
break; // EOF.
@@ -913,8 +855,6 @@
return command_line_options;
}
-#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-
void ExtendPoolWithBinaryDescriptor(google::protobuf::DescriptorPool& pool,
const void* data,
int size) {
diff --git a/src/traced/probes/probes.cc b/src/traced/probes/probes.cc
index 5e8a187..9d83f45 100644
--- a/src/traced/probes/probes.cc
+++ b/src/traced/probes/probes.cc
@@ -15,12 +15,12 @@
*/
#include <fcntl.h>
-#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/getopt.h"
#include "perfetto/ext/base/unix_task_runner.h"
#include "perfetto/ext/base/version.h"
#include "perfetto/ext/traced/traced.h"
@@ -42,9 +42,8 @@
{"version", no_argument, nullptr, OPT_VERSION},
{nullptr, 0, nullptr, 0}};
- int option_index;
for (;;) {
- int option = getopt_long(argc, argv, "", long_options, &option_index);
+ int option = getopt_long(argc, argv, "", long_options, nullptr);
if (option == -1)
break;
switch (option) {
diff --git a/src/traced/service/service.cc b/src/traced/service/service.cc
index 69d5a1c..769ce05 100644
--- a/src/traced/service/service.cc
+++ b/src/traced/service/service.cc
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-#include <getopt.h>
#include <stdio.h>
#include <algorithm>
+#include "perfetto/ext/base/getopt.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/unix_task_runner.h"
#include "perfetto/ext/base/version.h"
@@ -109,9 +109,8 @@
std::string producer_socket_group, consumer_socket_group,
producer_socket_mode, consumer_socket_mode;
- int option_index;
for (;;) {
- int option = getopt_long(argc, argv, "", long_options, &option_index);
+ int option = getopt_long(argc, argv, "", long_options, nullptr);
if (option == -1)
break;
switch (option) {
diff --git a/tools/busy_threads/busy_threads.cc b/tools/busy_threads/busy_threads.cc
index d7976ed..56a80e2 100644
--- a/tools/busy_threads/busy_threads.cc
+++ b/tools/busy_threads/busy_threads.cc
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-#include <getopt.h>
#include <inttypes.h>
#include <stdint.h>
#include <unistd.h>
@@ -24,6 +23,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/base/time.h"
#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/getopt.h"
#include "perfetto/ext/base/scoped_file.h"
#define PERFETTO_HAVE_PTHREADS \
@@ -111,9 +111,8 @@
#endif
{nullptr, 0, nullptr, 0}
};
- int option_index;
int c;
- while ((c = getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
+ while ((c = getopt_long(argc, argv, "", long_options, nullptr)) != -1) {
switch (c) {
case 'd':
background = true;
diff --git a/tools/cpu_utilization/cpu_utilization.cc b/tools/cpu_utilization/cpu_utilization.cc
index 9dc1ddd..2aea7ba 100644
--- a/tools/cpu_utilization/cpu_utilization.cc
+++ b/tools/cpu_utilization/cpu_utilization.cc
@@ -15,7 +15,6 @@
*/
#include <fcntl.h>
-#include <getopt.h>
#include <inttypes.h>
#include <stdint.h>
#include <sys/stat.h>
@@ -28,6 +27,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/base/time.h"
#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/getopt.h"
#include "perfetto/ext/base/scoped_file.h"
// Periodically prints an un-normalized cpu usage ratio (full use of a single
@@ -72,9 +72,8 @@
{"sleep-duration-us", required_argument, nullptr, 't'},
{"sleep-intervals", required_argument, nullptr, 'n'},
{nullptr, 0, nullptr, 0}};
- int option_index;
int c;
- while ((c = getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
+ while ((c = getopt_long(argc, argv, "", long_options, nullptr)) != -1) {
switch (c) {
case 'p':
target_pid = atoi(optarg);
diff --git a/tools/ftrace_proto_gen/main.cc b/tools/ftrace_proto_gen/main.cc
index a92505c..6fca8ce 100644
--- a/tools/ftrace_proto_gen/main.cc
+++ b/tools/ftrace_proto_gen/main.cc
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-#include <getopt.h>
#include <sys/stat.h>
#include <fstream>
#include <map>
@@ -29,6 +28,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/getopt.h"
#include "src/traced/probes/ftrace/format_parser/format_parser.h"
#include "tools/ftrace_proto_gen/ftrace_descriptor_gen.h"
#include "tools/ftrace_proto_gen/ftrace_proto_gen.h"
@@ -60,7 +60,6 @@
{"check_only", no_argument, nullptr, 'c'},
{nullptr, 0, nullptr, 0}};
- int option_index;
int c;
std::string event_list_path;
@@ -71,7 +70,7 @@
std::unique_ptr<std::ostream> (*ostream_factory)(const std::string&) =
&MakeOFStream;
- while ((c = getopt_long(argc, argv, "", long_options, &option_index)) != -1) {
+ while ((c = getopt_long(argc, argv, "", long_options, nullptr)) != -1) {
switch (c) {
case 'w':
event_list_path = optarg;
diff --git a/tools/multithreaded_alloc.cc b/tools/multithreaded_alloc.cc
index 174fd25..ae01aaf 100644
--- a/tools/multithreaded_alloc.cc
+++ b/tools/multithreaded_alloc.cc
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-#include <getopt.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
@@ -29,6 +28,7 @@
#include "perfetto/base/logging.h"
#include "perfetto/base/time.h"
+#include "perfetto/ext/base/getopt.h"
#include "perfetto/ext/base/optional.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/profiling/memory/heap_profile.h"