1/*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "tooling/client/domain/heapprofiler_client.h"
17#include "common/log_wrapper.h"
18#include "tooling/utils/utils.h"
19#include "tooling/client/session/session.h"
20
21#include <map>
22#include <functional>
23#include <cstring>
24
25using Result = panda::ecmascript::tooling::Result;
26namespace OHOS::ArkCompiler::Toolchain {
27static constexpr int32_t SAMPLING_INTERVAL = 16384;
28bool HeapProfilerClient::DispatcherCmd(const std::string &cmd, const std::string &arg)
29{
30    path_ = arg;
31
32    std::map<std::string, std::function<int()>> dispatcherTable {
33        { "allocationtrack", std::bind(&HeapProfilerClient::AllocationTrackCommand, this)},
34        { "allocationtrack-stop", std::bind(&HeapProfilerClient::AllocationTrackStopCommand, this)},
35        { "heapdump", std::bind(&HeapProfilerClient::HeapDumpCommand, this)},
36        { "heapprofiler-enable", std::bind(&HeapProfilerClient::Enable, this)},
37        { "heapprofiler-disable", std::bind(&HeapProfilerClient::Disable, this)},
38        { "sampling", std::bind(&HeapProfilerClient::Samping, this)},
39        { "sampling-stop", std::bind(&HeapProfilerClient::SampingStop, this)},
40        { "collectgarbage", std::bind(&HeapProfilerClient::CollectGarbage, this)}
41    };
42
43    auto entry = dispatcherTable.find(cmd);
44    if (entry != dispatcherTable.end() && entry->second != nullptr) {
45        entry->second();
46        LOGI("DispatcherCmd reqStr1: %{public}s", cmd.c_str());
47        return true;
48    }
49
50    LOGI("unknown command: %{public}s", cmd.c_str());
51    return false;
52}
53
54int HeapProfilerClient::HeapDumpCommand()
55{
56    Session *session = SessionManager::getInstance().GetSessionById(sessionId_);
57    uint32_t id = session->GetMessageId();
58
59    idEventMap_.emplace(id, HEAPDUMP);
60    std::unique_ptr<PtJson> request = PtJson::CreateObject();
61    request->Add("id", id);
62    request->Add("method", "HeapProfiler.takeHeapSnapshot");
63
64    std::unique_ptr<PtJson> params = PtJson::CreateObject();
65    params->Add("reportProgress", true);
66    params->Add("captureNumericValue", true);
67    params->Add("exposeInternals", false);
68    request->Add("params", params);
69
70    std::string message = request->Stringify();
71    if (session->ClientSendReq(message)) {
72        session->GetDomainManager().SetDomainById(id, "HeapProfiler");
73    }
74    return 0;
75}
76
77int HeapProfilerClient::AllocationTrackCommand()
78{
79    Session *session = SessionManager::getInstance().GetSessionById(sessionId_);
80    uint32_t id = session->GetMessageId();
81
82    idEventMap_.emplace(id, ALLOCATION);
83    std::unique_ptr<PtJson> request = PtJson::CreateObject();
84    request->Add("id", id);
85    request->Add("method", "HeapProfiler.startTrackingHeapObjects");
86
87    std::unique_ptr<PtJson> params = PtJson::CreateObject();
88    params->Add("trackAllocations", true);
89    request->Add("params", params);
90
91    std::string message = request->Stringify();
92    if (session->ClientSendReq(message)) {
93        session->GetDomainManager().SetDomainById(id, "HeapProfiler");
94    }
95    return 0;
96}
97
98int HeapProfilerClient::AllocationTrackStopCommand()
99{
100    Session *session = SessionManager::getInstance().GetSessionById(sessionId_);
101    uint32_t id = session->GetMessageId();
102
103    idEventMap_.emplace(id, ALLOCATION_STOP);
104    std::unique_ptr<PtJson> request = PtJson::CreateObject();
105    request->Add("id", id);
106    request->Add("method", "HeapProfiler.stopTrackingHeapObjects");
107
108    std::unique_ptr<PtJson> params = PtJson::CreateObject();
109    params->Add("reportProgress", true);
110    request->Add("params", params);
111
112    std::string message = request->Stringify();
113    if (session->ClientSendReq(message)) {
114        session->GetDomainManager().SetDomainById(id, "HeapProfiler");
115    }
116    return 0;
117}
118
119int HeapProfilerClient::Enable()
120{
121    Session *session = SessionManager::getInstance().GetSessionById(sessionId_);
122    uint32_t id = session->GetMessageId();
123
124    idEventMap_.emplace(id, ENABLE);
125    std::unique_ptr<PtJson> request = PtJson::CreateObject();
126    request->Add("id", id);
127    request->Add("method", "HeapProfiler.enable");
128
129    std::unique_ptr<PtJson> params = PtJson::CreateObject();
130    request->Add("params", params);
131
132    std::string message = request->Stringify();
133    if (session->ClientSendReq(message)) {
134        session->GetDomainManager().SetDomainById(id, "HeapProfiler");
135    }
136    return 0;
137}
138
139int HeapProfilerClient::Disable()
140{
141    Session *session = SessionManager::getInstance().GetSessionById(sessionId_);
142    uint32_t id = session->GetMessageId();
143
144    idEventMap_.emplace(id, DISABLE);
145    std::unique_ptr<PtJson> request = PtJson::CreateObject();
146    request->Add("id", id);
147    request->Add("method", "HeapProfiler.disable");
148
149    std::unique_ptr<PtJson> params = PtJson::CreateObject();
150    request->Add("params", params);
151
152    std::string message = request->Stringify();
153    if (session->ClientSendReq(message)) {
154        session->GetDomainManager().SetDomainById(id, "HeapProfiler");
155    }
156    return 0;
157}
158
159int HeapProfilerClient::Samping()
160{
161    Session *session = SessionManager::getInstance().GetSessionById(sessionId_);
162    uint32_t id = session->GetMessageId();
163
164    idEventMap_.emplace(id, SAMPLING);
165    std::unique_ptr<PtJson> request = PtJson::CreateObject();
166    request->Add("id", id);
167    request->Add("method", "HeapProfiler.startSampling");
168
169    std::unique_ptr<PtJson> params = PtJson::CreateObject();
170    params->Add("samplingInterval", SAMPLING_INTERVAL);
171    request->Add("params", params);
172
173    std::string message = request->Stringify();
174    if (session->ClientSendReq(message)) {
175        session->GetDomainManager().SetDomainById(id, "HeapProfiler");
176    }
177    return 0;
178}
179
180int HeapProfilerClient::SampingStop()
181{
182    Session *session = SessionManager::getInstance().GetSessionById(sessionId_);
183    uint32_t id = session->GetMessageId();
184
185    idEventMap_.emplace(id, SAMPLING_STOP);
186    std::unique_ptr<PtJson> request = PtJson::CreateObject();
187    request->Add("id", id);
188    request->Add("method", "HeapProfiler.stopSampling");
189
190    std::unique_ptr<PtJson> params = PtJson::CreateObject();
191    request->Add("params", params);
192
193    std::string message = request->Stringify();
194    if (session->ClientSendReq(message)) {
195        session->GetDomainManager().SetDomainById(id, "HeapProfiler");
196    }
197    return 0;
198}
199
200int HeapProfilerClient::CollectGarbage()
201{
202    Session *session = SessionManager::getInstance().GetSessionById(sessionId_);
203    uint32_t id = session->GetMessageId();
204
205    idEventMap_.emplace(id, COLLECT_GARBAGE);
206    std::unique_ptr<PtJson> request = PtJson::CreateObject();
207    request->Add("id", id);
208    request->Add("method", "HeapProfiler.collectGarbage");
209
210    std::unique_ptr<PtJson> params = PtJson::CreateObject();
211    request->Add("params", params);
212
213    std::string message = request->Stringify();
214    if (session->ClientSendReq(message)) {
215        session->GetDomainManager().SetDomainById(id, "HeapProfiler");
216    }
217    return 0;
218}
219
220void HeapProfilerClient::RecvReply(std::unique_ptr<PtJson> json)
221{
222    if (json == nullptr) {
223        LOGE("json parse error");
224        return;
225    }
226    if (!json->IsObject()) {
227        LOGE("json parse format error");
228        json->ReleaseRoot();
229        return;
230    }
231    std::unique_ptr<PtJson> result;
232    Result ret = json->GetObject("result", &result);
233    if (ret == Result::SUCCESS) {
234        SaveHeapsamplingData(std::move(result));
235        return;
236    }
237    std::string wholeMethod;
238    std::string method;
239    ret = json->GetString("method", &wholeMethod);
240    if (ret != Result::SUCCESS || wholeMethod.empty()) {
241        LOGE("find method error");
242        return;
243    }
244    std::string::size_type length = wholeMethod.length();
245    std::string::size_type indexPoint = 0;
246    indexPoint = wholeMethod.find_first_of('.', 0);
247    if (indexPoint == std::string::npos || indexPoint == 0 || indexPoint == length - 1) {
248        return;
249    }
250    method = wholeMethod.substr(indexPoint + 1, length);
251    if (method == "lastSeenObjectId") {
252        isAllocationMsg_ = true;
253    }
254    std::unique_ptr<PtJson> params;
255    ret = json->GetObject("params", &params);
256    if (ret != Result::SUCCESS) {
257        LOGE("find params error");
258        return;
259    }
260    std::string chunk;
261    ret = params->GetString("chunk", &chunk);
262    if (ret == Result::SUCCESS) {
263        SaveHeapSnapshotAndAllocationTrackData(chunk);
264    }
265    return;
266}
267
268void HeapProfilerClient::SaveHeapSnapshotAndAllocationTrackData(const std::string &chunk)
269{
270    std::string head = "{\"snapshot\":\n";
271    std::string heapFile;
272    if (!strncmp(chunk.c_str(), head.c_str(), head.length())) {
273        char date[16];
274        char time[16];
275        bool res = Utils::GetCurrentTime(date, time, sizeof(date));
276        if (!res) {
277            LOGE("arkdb: get time failed");
278            return;
279        }
280        if (isAllocationMsg_) {
281            heapFile = "Heap-" + std::to_string(sessionId_) + "-" + std::string(date) + "T" + std::string(time) +
282                        ".heaptimeline";
283            fileName_ = path_ + heapFile;
284            std::cout << "heaptimeline file name is " << fileName_ << std::endl;
285        } else {
286            heapFile = "Heap-"+ std::to_string(sessionId_) + "-"  + std::string(date) + "T" + std::string(time) +
287                        ".heapsnapshot";
288            fileName_ = path_ + heapFile;
289            std::cout << "heapsnapshot file name is " << fileName_ << std::endl;
290        }
291        std::cout << ">>> ";
292        fflush(stdout);
293    }
294
295    std::string tail = "]\n}\n";
296    std::string subStr = chunk.substr(chunk.length() - tail.length(), chunk.length());
297    if (!strncmp(subStr.c_str(), tail.c_str(), tail.length())) {
298        isAllocationMsg_ = false;
299    }
300    WriteHeapProfilerForFile(fileName_, chunk);
301    return;
302}
303
304void HeapProfilerClient::SaveHeapsamplingData(std::unique_ptr<PtJson> result)
305{
306    std::unique_ptr<PtJson> profile;
307    std::string heapFile;
308    Result ret = result->GetObject("profile", &profile);
309    if (ret != Result::SUCCESS) {
310        LOGE("arkdb: get profile failed");
311        return;
312    }
313    char date[16];
314    char time[16];
315    bool res = Utils::GetCurrentTime(date, time, sizeof(date));
316    if (!res) {
317        LOGE("arkdb: get time failed");
318        return;
319    }
320    heapFile = "Heap-" + std::to_string(sessionId_) + "-" + std::string(date) + "T" + std::string(time) +
321                ".heapprofile";
322    fileName_ = path_ + heapFile;
323    std::cout << "heapprofile file name is " << fileName_ << std::endl;
324    std::cout << ">>> ";
325    fflush(stdout);
326
327    WriteHeapProfilerForFile(fileName_, profile->Stringify());
328    return;
329}
330
331bool HeapProfilerClient::WriteHeapProfilerForFile(const std::string &fileName, const std::string &data)
332{
333    std::ofstream ofs;
334    std::string realPath;
335    bool res = Utils::RealPath(fileName, realPath, false);
336    if (!res) {
337        LOGE("arkdb: path is not realpath!");
338        return false;
339    }
340    ofs.open(fileName.c_str(), std::ios::app);
341    if (!ofs.is_open()) {
342        LOGE("arkdb: file open error!");
343        return false;
344    }
345    size_t strSize = data.size();
346    ofs.write(data.c_str(), strSize);
347    ofs.close();
348    ofs.clear();
349    return true;
350}
351} // OHOS::ArkCompiler::Toolchain