lin
2025-03-11 6f4f7a76e03a46fefb056a4b18197f1d9e8aa939
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
// Copyright (C) 2018 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.
 
//#undef NDEBUG // get DCHECK etc.
 
 
#include "common/debug.h"
#include "common/expected.h"
#include "perfetto/rx_producer.h"
 
#include <android-base/unique_fd.h>
#include <android-base/parseint.h>
#include <android-base/file.h>
 
#include "rxcpp/rx.hpp"
#include <iostream>
#include <optional>
 
#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syscall.h>
#include <fcntl.h>
#include <unistd.h>
 
using namespace iorap::perfetto;  // NOLINT
 
#if defined(IORAP_PERFETTO_MAIN)
 
void Usage(char** argv) {
  std::cerr << "Usage: " << argv[0] << " [--config-proto=config.pb] [--duration-ms=5000] [--output-proto=output.pb]" << std::endl;
  std::cerr << "" << std::endl;
  std::cerr << "  Request a perfetto trace, blocking until it's complete. The resulting trace proto" << std::endl;
  std::cerr << "  is output to stdout as text, or to --output-proto as a binary." << std::endl;
  std::cerr << "" << std::endl;
  std::cerr << "  Optional flags:" << std::endl;
  std::cerr << "    --help,-h                  Print this Usage." << std::endl;
  std::cerr << "    --output-proto $,-op $     Perfetto tracebuffer output file (default stdout)." << std::endl;
  std::cerr << "    --config-proto $,-cp $     Path to binary protobuf config." << std::endl;
  std::cerr << "    --duration-ms $,-dm $      How long to run trace for in milliseconds." << std::endl;
  std::cerr << "    --simple                   Simplest possible perfetto state transitions (default off)." << std::endl;
  std::cerr << "    --verbose,-v               Set verbosity (default off)." << std::endl;
  std::cerr << "    --wait,-w                  Wait for key stroke before continuing (default off)." << std::endl;
  exit(1);
}
 
PerfettoDependencies::Component CreateCommandLinePerfettoDependenciesComponent(
    uint32_t duration_ms) {
  // TODO: read from command line.
  static const uint32_t kBufferSize = 4096;
 
  // TODO: remove this hack.
  static const uint32_t kTraceDurationMs = duration_ms;
 
  // fruit: using 'bindInstance' causes a segfault every time.
#if 0
 
  // fruit: Can't use a stateful lambda, so use bindInstance instead of registerProvider.
  auto config = PerfettoDependencies::CreateConfig(duration_ms,
                                                   /*deferred_start*/true,
                                                   kBufferSize);
 
  .... bindInstance(config);
#endif
 
  return fruit::createComponent()
    .bind<PerfettoConsumer, PerfettoConsumerImpl>()
    .registerProvider([]() /* -> TraceConfig */ {
        return PerfettoDependencies::CreateConfig(kTraceDurationMs,
                                                  /*deferred_start*/true,
                                                  kBufferSize);
    });
}
 
static void CollectPerfettoTraceBufferViaAbstractions(
    RxProducerFactory& producer_factory,
    const std::string& arg_output_proto,
    const int arg_duration_ms) {
  LOG(VERBOSE) << "CollectPerfettoTraceBufferViaAbstractions";
 
  // Don't create a subscriber to emit the PerfettoStreamCommand.
  // RxCpp is "greedy" and consumes every possible item emitted (it doesn't support 'pull'). We want
  // to operate on a (command,state) iteration every time, just like in a real scenario.
  // Adding the 'interval' turns into a non-greedy version (i.e. push).
 
  // Immediately emit 'kStartTracing', wait and emit kStopTracing, wait and emit kShutdown.
  // In reality, there would be a delay between all these events.
  auto /*observable<PerfettoStreamCommand>*/ commands =
      rxcpp::observable<>::just(PerfettoStreamCommand::kStartTracing)
          // wait 1x
          .concat(
              // Pick a value longer than the perfetto config delay_ms, so that we send
              // 'kShutdown' after tracing has already finished.
              rxcpp::observable<>::interval(std::chrono::milliseconds(arg_duration_ms * 2))
                  .take(2)  // kStopTracing, kShutdown.
                  .map([](int value) {
                         // value is 1,2,3,...
                         return static_cast<PerfettoStreamCommand>(value);  // 1,2, ...
                       })
          );
 
  auto /*observable<PerfettoTraceProto>*/ trace_proto_stream =
      producer_factory.CreateTraceStream(commands);
 
  trace_proto_stream
    .observe_on(ObserveOnNewIoThread())  // Write data on an idle-class-priority thread.
    .as_blocking()  // Wait for observable to terminate with on_completed or on_error.
    .subscribe(/*on_next*/[arg_output_proto]
      (PerfettoTraceProto trace_proto) {
             if (!trace_proto.WriteFullyToFile(arg_output_proto)) {
               LOG(ERROR) << "Failed to save TraceBuffer to " << arg_output_proto;
             } else {
               LOG(INFO) << "TraceBuffer saved to file: " << arg_output_proto;
               LOG(INFO);
               LOG(INFO) << "To print this in a human readable form, execute these commands:";
               LOG(INFO) << "$> adb pull '" << arg_output_proto << "'";
               LOG(INFO) << "$> trace_to_text systrace <filename.pb>";
             }
      },
      /*on_error*/[](rxcpp::util::error_ptr err) {
        LOG(ERROR) << "Perfetto trace proto collection error: " << rxcpp::util::what(err);
      });
}
 
namespace iorap::perfetto {
// Reach inside rx_producer.cc
// Not part of any headers because it's internal.
void CollectPerfettoTraceBufferImmediately(
    RxProducerFactory& producer_factory,
    const std::string& arg_output_proto);
}
 
int main(int argc, char** argv) {
  android::base::InitLogging(argv);
  android::base::SetLogger(android::base::StderrLogger);
 
  bool wait_for_keystroke = false;
  bool enable_verbose = false;
 
  std::string arg_output_proto;
  std::string arg_config_proto;
  uint32_t arg_duration_ms = 1000;
  bool arg_simple = false;
 
  if (argc == 1) {
    Usage(argv);
  }
 
  for (int arg = 1; arg < argc; ++arg) {
    std::string argstr = argv[arg];
    bool has_arg_next = (arg+1)<argc;
    std::string arg_next = has_arg_next ? argv[arg+1] : "";
 
    if (argstr == "--help" || argstr == "-h") {
      Usage(argv);
    } else if (argstr == "--output-proto" || argstr == "-op") {
      if (!has_arg_next) {
        std::cerr << "Missing --output-proto <value>" << std::endl;
        return 1;
      }
      arg_output_proto = arg_next;
      ++arg;
    } else if (argstr == "--config-proto" || argstr == "-cp") {
      if (!has_arg_next) {
        std::cerr << "Missing --config-proto <value>" << std::endl;
        return 1;
      }
      arg_config_proto = arg_next;
      LOG(WARNING) << "TODO: parse configs from a file, not implemented yet.";
      ++arg;
    } else if (argstr == "--duration-ms" || argstr == "-dm") {
      if (!has_arg_next) {
        std::cerr << "Missing --duration-ms <value>" << std::endl;
        return 1;
      }
      if (!android::base::ParseUint(arg_next.c_str(), /*out*/&arg_duration_ms)) {
        std::cerr << "Invalid --duration-ms " << arg_next << ", reason: " << strerror(errno);
        return 1;
      }
      ++arg;
    } else if (argstr == "--simple") {
      arg_simple = true;
    } else if (argstr == "--verbose" || argstr == "-v") {
      enable_verbose = true;
    } else if (argstr == "--wait" || argstr == "-w") {
      wait_for_keystroke = true;
    }
  }
 
  if (enable_verbose) {
    android::base::SetMinimumLogSeverity(android::base::VERBOSE);
 
    LOG(VERBOSE) << "Verbose check";
    LOG(VERBOSE) << "Debug check: " << ::iorap::kIsDebugBuild;
  }
 
  // Useful to attach a debugger...
  // 1) $> iorap-cmd-perfetto -w <args>
  // 2) $> gdbclient <pid>
  if (wait_for_keystroke) {
    LOG(INFO) << "Self pid: " << getpid();
    LOG(INFO) << "Press any key to continue...";
    std::cin >> wait_for_keystroke;
  }
 
  int return_code = 0;
  // TODO: convert #on-error into a non-0 return code.
 
  PerfettoDependencies::Injector injector{
      CreateCommandLinePerfettoDependenciesComponent,
      arg_duration_ms
  };
  RxProducerFactory rx_producer_factory{/*borrow*/injector};
 
  if (arg_simple) {
    // To debug any kind of low-level perfetto issues.
    CollectPerfettoTraceBufferImmediately(/*inout*/rx_producer_factory, arg_output_proto);
  } else {
    // To debug our own iorap internal abstractions.
    CollectPerfettoTraceBufferViaAbstractions(/*inout*/rx_producer_factory,
                                              arg_output_proto,
                                              arg_duration_ms);
  }
 
  // Uncomment this if we want to leave the process around to inspect it from adb shell.
  // sleep(100000);
 
  // 0 -> successfully wrote the TraceProto out to file.
  // 1 -> failed along the way (#on_error and also see the error logs).
  return return_code;
}
 
#endif