File: perf_output_unittest.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (203 lines) | stat: -rw-r--r-- 7,112 bytes parent folder | download | duplicates (6)
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
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/metrics/perf/perf_output.h"

#include <stdio.h>
#include <unistd.h>

#include <utility>

#include "base/containers/span.h"
#include "base/files/file.h"
#include "base/posix/eintr_wrapper.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/thread_restrictions.h"
#include "chromeos/ash/components/dbus/debug_daemon/fake_debug_daemon_client.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/metrics_proto/sampled_profile.pb.h"

namespace metrics {

namespace {
// Returns an example PerfDataProto. The contents don't have to make sense. They
// just need to constitute a semantically valid protobuf.
// |proto| is an output parameter that will contain the created protobuf.
PerfDataProto GetExamplePerfDataProto() {
  PerfDataProto proto;
  proto.set_timestamp_sec(1435604013);  // Time since epoch in seconds.

  PerfDataProto_PerfFileAttr* file_attr = proto.add_file_attrs();
  file_attr->add_ids(61);
  file_attr->add_ids(62);
  file_attr->add_ids(63);

  PerfDataProto_PerfEventAttr* attr = file_attr->mutable_attr();
  attr->set_type(1);
  attr->set_size(2);
  attr->set_config(3);
  attr->set_sample_period(4);
  attr->set_sample_freq(5);

  PerfDataProto_PerfEventStats* stats = proto.mutable_stats();
  stats->set_num_events_read(100);
  stats->set_num_sample_events(200);
  stats->set_num_mmap_events(300);
  stats->set_num_fork_events(400);
  stats->set_num_exit_events(500);

  return proto;
}

// Perf session ID returned by the GetPerfOutputV2 DBus method call.
const uint64_t kFakePerfSssionId = 101;
// Quipper command line arguments for running perf.
const std::vector<std::string> kQuipperArgs{
    "--duration", "4",      "--", "perf", "record", "-a",
    "-e",         "cycles", "-g", "-c",   "4000037"};

// This fakes DebugDaemonClient by serving example perf data when the profiling
// duration elapses.
class FakeDebugDaemonClient : public ash::FakeDebugDaemonClient {
 public:
  FakeDebugDaemonClient()
      : task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {}

  FakeDebugDaemonClient(const FakeDebugDaemonClient&) = delete;
  FakeDebugDaemonClient& operator=(const FakeDebugDaemonClient&) = delete;

  ~FakeDebugDaemonClient() override {
    EXPECT_FALSE(perf_output_file_.IsValid());
  }

  void GetPerfOutput(const std::vector<std::string>& quipper_args,
                     bool disable_cpu_idle,
                     int file_descriptor,
                     chromeos::DBusMethodCallback<uint64_t> callback) override {
    // We will write perf output to this pipe FD. dup() |file_descriptor|
    // because it is closed after this method returns.
    base::ScopedPlatformFile perf_output_fd(HANDLE_EINTR(dup(file_descriptor)));
    ASSERT_NE(perf_output_fd, -1);

    perf_output_file_ = base::File(std::move(perf_output_fd));
    ASSERT_TRUE(perf_output_file_.IsValid());

    // Returns a fake perf session ID after calling GetPerfOutputFd.
    task_runner_->PostTask(
        FROM_HERE, base::BindOnce(
                       [](chromeos::DBusMethodCallback<uint64_t> callback) {
                         std::move(callback).Run(kFakePerfSssionId);
                       },
                       std::move(callback)));
  }

  bool stop_called() const { return stop_called_; }

  void StopPerf(uint64_t session_id,
                chromeos::VoidDBusMethodCallback callback) override {
    // Simulates stopping the perf session by writing perf data right away.
    DCHECK(task_runner_->RunsTasksInCurrentSequence());
    EXPECT_EQ(session_id, kFakePerfSssionId);
    stop_called_ = true;
    OnFakePerfOutputComplete();
  }

  // This simulates that profile collection is done and quipper writes perf data
  // over the pipe.
  void OnFakePerfOutputComplete() {
    base::ScopedAllowBlockingForTesting allow_block;

    auto perf_data = GetExamplePerfDataProto().SerializeAsString();
    EXPECT_TRUE(perf_output_file_.WriteAtCurrentPosAndCheck(
        base::as_byte_span(perf_data)));

    // Need to close the pipe to unblock the pipe reader.
    perf_output_file_.Close();
  }

 private:
  scoped_refptr<base::SequencedTaskRunner> task_runner_;
  base::File perf_output_file_;
  chromeos::DBusMethodCallback<uint64_t> get_perf_outjput_callback_;
  bool stop_called_ = false;
};

}  // namespace

class PerfOutputCallTest : public testing::Test {
 public:
  PerfOutputCallTest() = default;

  PerfOutputCallTest(const PerfOutputCallTest&) = delete;
  PerfOutputCallTest& operator=(const PerfOutputCallTest&) = delete;

  void SetUp() override {
    debug_daemon_client_ = std::make_unique<FakeDebugDaemonClient>();
  }

  void TearDown() override { perf_output_call_.reset(); }

  void OnPerfOutputComplete(std::string perf_output) {
    perf_output_ = std::move(perf_output);
  }

 protected:
  // |task_environment_| must be the first member (or at least before
  // any member that cares about tasks) to be initialized first and
  // destroyed last.
  content::BrowserTaskEnvironment task_environment_;

  std::unique_ptr<FakeDebugDaemonClient> debug_daemon_client_;
  std::unique_ptr<PerfOutputCall> perf_output_call_;

  std::string perf_output_;
};

// Test getting perf output after profile duration elapses.
TEST_F(PerfOutputCallTest, GetPerfOutput) {
  perf_output_call_ = std::make_unique<PerfOutputCall>(
      debug_daemon_client_.get(), kQuipperArgs, false,
      base::BindOnce(&PerfOutputCallTest::OnPerfOutputComplete,
                     base::Unretained(this)));
  // Not yet collected.
  EXPECT_EQ(perf_output_, "");

  // Perf data is available.
  debug_daemon_client_->OnFakePerfOutputComplete();

  // Note that we can call RunUntilIdle() only after fake perf data is written
  // over the pipe, or RunUntilIdle() will block forever on the reading end.
  task_environment_.RunUntilIdle();

  EXPECT_FALSE(debug_daemon_client_->stop_called());
  EXPECT_EQ(perf_output_, GetExamplePerfDataProto().SerializeAsString());
}

// Test stopping the perf session and get perf output right away.
TEST_F(PerfOutputCallTest, Stop) {
  perf_output_call_ = std::make_unique<PerfOutputCall>(
      debug_daemon_client_.get(), kQuipperArgs, false,
      base::BindOnce(&PerfOutputCallTest::OnPerfOutputComplete,
                     base::Unretained(this)));
  // Not yet collected.
  EXPECT_EQ(perf_output_, "");

  // Perf data is available after calling Stop().
  perf_output_call_->Stop();

  // Note that we can call RunUntilIdle() only after fake perf data is written
  // over the pipe, or RunUntilIdle() will block forever on the reading end.
  task_environment_.RunUntilIdle();

  EXPECT_TRUE(debug_daemon_client_->stop_called());
  EXPECT_EQ(perf_output_, GetExamplePerfDataProto().SerializeAsString());
}

}  // namespace metrics