1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/test/chromedriver/chrome/heap_snapshot_taker.h"
6
7#include "base/json/json_reader.h"
8#include "base/logging.h"
9#include "base/values.h"
10#include "chrome/test/chromedriver/chrome/devtools_client.h"
11#include "chrome/test/chromedriver/chrome/status.h"
12
13HeapSnapshotTaker::HeapSnapshotTaker(DevToolsClient* client)
14    : client_(client), snapshot_uid_(-1) {
15  client_->AddListener(this);
16}
17
18HeapSnapshotTaker::~HeapSnapshotTaker() {}
19
20Status HeapSnapshotTaker::TakeSnapshot(scoped_ptr<base::Value>* snapshot) {
21  Status status1 = TakeSnapshotInternal();
22  base::DictionaryValue params;
23  Status status2 = client_->SendCommand("Debugger.disable", params);
24  Status status3(kOk);
25  if (snapshot_uid_ != -1) {  // Clear the snapshot cached in chrome.
26    status3 = client_->SendCommand("HeapProfiler.clearProfiles", params);
27  }
28
29  Status status4(kOk);
30  if (status1.IsOk() && status2.IsOk() && status3.IsOk()) {
31    scoped_ptr<base::Value> value(base::JSONReader::Read(snapshot_));
32    if (!value)
33      status4 = Status(kUnknownError, "heap snapshot not in JSON format");
34    else
35      *snapshot = value.Pass();
36  }
37  snapshot_uid_ = -1;
38  snapshot_.clear();
39  if (status1.IsError())
40    return status1;
41  else if (status2.IsError())
42    return status2;
43  else if (status3.IsError())
44    return status3;
45  else
46    return status4;
47}
48
49Status HeapSnapshotTaker::TakeSnapshotInternal() {
50  if (snapshot_uid_ != -1)
51    return Status(kUnknownError, "unexpected heap snapshot was triggered");
52
53  base::DictionaryValue params;
54  const char* kMethods[] = {
55      "Debugger.enable",
56      "HeapProfiler.collectGarbage",
57      "HeapProfiler.takeHeapSnapshot"
58  };
59  for (size_t i = 0; i < arraysize(kMethods); ++i) {
60    Status status = client_->SendCommand(kMethods[i], params);
61    if (status.IsError())
62      return status;
63  }
64
65  if (snapshot_uid_ == -1)
66    return Status(kUnknownError, "failed to receive snapshot uid");
67
68  base::DictionaryValue uid_params;
69  uid_params.SetInteger("uid", snapshot_uid_);
70  Status status = client_->SendCommand(
71      "HeapProfiler.getHeapSnapshot", uid_params);
72  if (status.IsError())
73    return status;
74
75  return Status(kOk);
76}
77
78Status HeapSnapshotTaker::OnEvent(DevToolsClient* client,
79                                  const std::string& method,
80                                  const base::DictionaryValue& params) {
81  if (method == "HeapProfiler.addProfileHeader") {
82    if (snapshot_uid_ != -1) {
83      LOG(WARNING) << "multiple heap snapshot triggered";
84    } else if (!params.GetInteger("header.uid", &snapshot_uid_)) {
85      return Status(kUnknownError,
86                    "HeapProfiler.addProfileHeader has invalid 'header.uid'");
87    }
88  } else if (method == "HeapProfiler.addHeapSnapshotChunk") {
89    int uid = -1;
90    if (!params.GetInteger("uid", &uid)) {
91      return Status(kUnknownError,
92                    "HeapProfiler.addHeapSnapshotChunk has no 'uid'");
93    } else if (uid == snapshot_uid_) {
94      std::string chunk;
95      if (!params.GetString("chunk", &chunk)) {
96        return Status(kUnknownError,
97                      "HeapProfiler.addHeapSnapshotChunk has no 'chunk'");
98      }
99
100      snapshot_.append(chunk);
101    } else {
102      LOG(WARNING) << "expect chunk event uid " << snapshot_uid_
103                   << ", but got " << uid;
104    }
105  }
106  return Status(kOk);
107}
108