1 /*
2 * Copyright (c) 2021 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 "ecmascript/dfx/hprof/heap_snapshot_json_serializer.h"
17
18 #include "ecmascript/dfx/hprof/heap_snapshot.h"
19 #include "securec.h"
20
21 namespace panda::ecmascript {
22
Serialize(HeapSnapshot * snapshot,Stream * stream)23 bool HeapSnapshotJSONSerializer::Serialize(HeapSnapshot *snapshot, Stream *stream)
24 {
25 // Serialize Node/Edge/String-Table
26 LOG_ECMA(INFO) << "HeapSnapshotJSONSerializer::Serialize begin";
27 ECMA_BYTRACE_NAME(HITRACE_TAG_ARK, "HeapSnapshotJSONSerializer::Serialize");
28 ASSERT(snapshot->GetNodes() != nullptr && snapshot->GetEdges() != nullptr &&
29 snapshot->GetEcmaStringTable() != nullptr);
30 auto writer = new StreamWriter(stream);
31
32 SerializeSnapshotHeader(snapshot, writer); // 1.
33 SerializeNodes(snapshot, writer); // 2.
34 SerializeEdges(snapshot, writer); // 3.
35 SerializeTraceFunctionInfo(snapshot, writer); // 4.
36 SerializeTraceTree(snapshot, writer); // 5.
37 SerializeSamples(snapshot, writer); // 6.
38 SerializeLocations(writer); // 7.
39 SerializeStringTable(snapshot, writer); // 8.
40 SerializerSnapshotClosure(writer); // 9.
41 writer->End();
42
43 delete writer;
44
45 LOG_ECMA(INFO) << "HeapSnapshotJSONSerializer::Serialize exit";
46 return true;
47 }
48
49 /*
50 4 byte: str_num
51 4 byte: unuse
52 {
53 4 byte: string size
54 4 byte: obj_num
55 [8 byte: obj adrr] * obj_num
56 string contents
57 } * str_num
58 */
DumpStringTable(StringHashMap * stringTable,Stream * stream,CUnorderedMap<uint64_t,CVector<uint64_t>> & strIdMapObjVec)59 uint32_t HeapSnapshotJSONSerializer::DumpStringTable(StringHashMap *stringTable, Stream *stream,
60 CUnorderedMap<uint64_t, CVector<uint64_t>> &strIdMapObjVec)
61 {
62 ASSERT(stringTable != nullptr);
63 size_t bufSize = 8 * 1024 * 1024; // 8MB buf use for string dump
64 char *buf = new char[bufSize];
65 uint32_t secHead[] = {stringTable->GetCapcity(), 0};
66 *reinterpret_cast<uint64_t *>(buf) = *reinterpret_cast<uint64_t *>(secHead);
67 uint32_t secTotalSize = sizeof(secHead);
68 size_t offset = sizeof(secHead);
69 for (auto key : stringTable->GetOrderedKeyStorage()) {
70 auto [strId, str] = stringTable->GetStringAndIdPair(key);
71 auto objVec = strIdMapObjVec[strId];
72 uint32_t objVecSize = objVec.size() * sizeof(uint64_t);
73 uint32_t strHead[] = {str->size(), objVec.size()};
74 auto currLen = sizeof(strHead) + objVecSize + str->size() + 1;
75 if (offset + currLen > bufSize) {
76 stream->WriteBinBlock(buf, offset);
77 offset = 0;
78 if (currLen > bufSize) {
79 delete[] buf;
80 bufSize = currLen;
81 buf = new char[bufSize];
82 }
83 }
84 *reinterpret_cast<uint64_t *>(buf + offset) = *reinterpret_cast<uint64_t *>(strHead);
85 offset += sizeof(strHead);
86 if (memcpy_s(buf + offset, bufSize - offset, reinterpret_cast<void *>(objVec.data()), objVecSize) != EOK) {
87 LOG_ECMA(ERROR) << "DumpStringTable: memcpy_s failed";
88 break;
89 }
90 offset += objVecSize;
91 if (memcpy_s(buf + offset, bufSize - offset, str->data(), str->size() + 1) != EOK) {
92 LOG_ECMA(ERROR) << "DumpStringTable: memcpy_s failed";
93 break;
94 }
95 offset += str->size() + 1;
96 secTotalSize += currLen;
97 }
98 auto padding = (8 - secTotalSize % 8) % 8;
99 if (offset + padding > 0) {
100 stream->WriteBinBlock(buf, offset + padding);
101 }
102 delete[] buf;
103 return secTotalSize + padding;
104 }
105
SerializeSnapshotHeader(HeapSnapshot * snapshot,StreamWriter * writer)106 void HeapSnapshotJSONSerializer::SerializeSnapshotHeader(HeapSnapshot *snapshot, StreamWriter *writer)
107 {
108 writer->WriteString("{\"snapshot\":\n"); // 1.
109 writer->WriteString("{\"meta\":\n"); // 2.
110 // NOLINTNEXTLINE(modernize-raw-string-literal)
111 writer->WriteString("{\"node_fields\":[\"type\",\"name\",\"id\",\"self_size\",\"edge_count\",\"trace_node_id\",");
112 writer->WriteString("\"detachedness\",\"native_size\"],\n"); // 3.
113 // NOLINTNEXTLINE(modernize-raw-string-literal)
114 writer->WriteString("\"node_types\":[[\"hidden\",\"array\",\"string\",\"object\",\"code\",\"closure\",\"regexp\",");
115 // NOLINTNEXTLINE(modernize-raw-string-literal)
116 writer->WriteString("\"number\",\"native\",\"synthetic\",\"concatenated string\",\"slicedstring\",\"symbol\",");
117 // NOLINTNEXTLINE(modernize-raw-string-literal)
118 writer->WriteString("\"bigint\"],\"string\",\"number\",\"number\",\"number\",\"number\",\"number\"],\n"); // 4.
119 // NOLINTNEXTLINE(modernize-raw-string-literal)
120 writer->WriteString("\"edge_fields\":[\"type\",\"name_or_index\",\"to_node\"],\n"); // 5.
121 // NOLINTNEXTLINE(modernize-raw-string-literal)
122 writer->WriteString("\"edge_types\":[[\"context\",\"element\",\"property\",\"internal\",\"hidden\",\"shortcut\",");
123 // NOLINTNEXTLINE(modernize-raw-string-literal)
124 writer->WriteString("\"weak\"],\"string_or_number\",\"node\"],\n"); // 6.
125 // NOLINTNEXTLINE(modernize-raw-string-literal)
126 writer->WriteString("\"trace_function_info_fields\":[\"function_id\",\"name\",\"script_name\",\"script_id\",");
127 // NOLINTNEXTLINE(modernize-raw-string-literal)
128 writer->WriteString("\"line\",\"column\"],\n"); // 7.
129 // NOLINTNEXTLINE(modernize-raw-string-literal)
130 writer->WriteString("\"trace_node_fields\":[\"id\",\"function_info_index\",\"count\",\"size\",\"children\"],\n");
131 // NOLINTNEXTLINE(modernize-raw-string-literal)
132 writer->WriteString("\"sample_fields\":[\"timestamp_us\",\"last_assigned_id\"],\n"); // 9.
133 // NOLINTNEXTLINE(modernize-raw-string-literal)
134 // 10.
135 writer->WriteString("\"location_fields\":[\"object_index\",\"script_id\",\"line\",\"column\"]},\n\"node_count\":");
136 writer->WriteNumber(snapshot->GetNodeCount()); // 11.
137 writer->WriteString(",\n\"edge_count\":");
138 writer->WriteNumber(snapshot->GetEdgeCount()); // 12.
139 writer->WriteString(",\n\"trace_function_count\":");
140 writer->WriteNumber(snapshot->GetTrackAllocationsStack().size()); // 13.
141 writer->WriteString("\n},\n"); // 14.
142 }
143
SerializeNodes(HeapSnapshot * snapshot,StreamWriter * writer)144 void HeapSnapshotJSONSerializer::SerializeNodes(HeapSnapshot *snapshot, StreamWriter *writer)
145 {
146 LOG_ECMA(INFO) << "HeapSnapshotJSONSerializer::SerializeNodes";
147 ECMA_BYTRACE_NAME(HITRACE_TAG_ARK, "HeapSnapshotJSONSerializer::SerializeNodes");
148 const CList<Node *> *nodes = snapshot->GetNodes();
149 const StringHashMap *stringTable = snapshot->GetEcmaStringTable();
150 ASSERT(nodes != nullptr);
151 writer->WriteString("\"nodes\":["); // Section Header
152 size_t i = 0;
153 for (auto *node : *nodes) {
154 if (i > 0) {
155 writer->WriteChar(','); // add comma except first line
156 }
157 writer->WriteNumber(static_cast<int>(node->GetType())); // 1.
158 writer->WriteChar(',');
159 writer->WriteNumber(stringTable->GetStringId(node->GetName())); // 2.
160 writer->WriteChar(',');
161 writer->WriteNumber(node->GetId()); // 3.
162 writer->WriteChar(',');
163 writer->WriteNumber(node->GetSelfSize()); // 4.
164 writer->WriteChar(',');
165 writer->WriteNumber(node->GetEdgeCount()); // 5.
166 writer->WriteChar(',');
167 writer->WriteNumber(node->GetStackTraceId()); // 6.
168 writer->WriteChar(',');
169 writer->WriteChar('0'); // 7.detachedness default 0
170 writer->WriteChar(',');
171 writer->WriteNumber(node->GetNativeSize());
172 if (i == nodes->size() - 1) { // add comma at last the line
173 writer->WriteString("],\n"); // 7. detachedness default
174 } else {
175 writer->WriteString("\n"); // 7.
176 }
177 i++;
178 }
179 }
180
SerializeEdges(HeapSnapshot * snapshot,StreamWriter * writer)181 void HeapSnapshotJSONSerializer::SerializeEdges(HeapSnapshot *snapshot, StreamWriter *writer)
182 {
183 LOG_ECMA(INFO) << "HeapSnapshotJSONSerializer::SerializeEdges";
184 ECMA_BYTRACE_NAME(HITRACE_TAG_ARK, "HeapSnapshotJSONSerializer::SerializeEdges");
185 const CList<Edge *> *edges = snapshot->GetEdges();
186 const StringHashMap *stringTable = snapshot->GetEcmaStringTable();
187 ASSERT(edges != nullptr);
188 writer->WriteString("\"edges\":[");
189 size_t i = 0;
190 for (auto *edge : *edges) {
191 StringId nameOrIndex = edge->GetType() == EdgeType::ELEMENT ?
192 edge->GetIndex() : stringTable->GetStringId(edge->GetName());
193 if (i > 0) { // add comma except the first line
194 writer->WriteChar(',');
195 }
196 writer->WriteNumber(static_cast<int>(edge->GetType())); // 1.
197 writer->WriteChar(',');
198 writer->WriteNumber(nameOrIndex); // 2. Use StringId
199 writer->WriteChar(',');
200
201 if (i == edges->size() - 1) { // add comma at last the line
202 writer->WriteNumber(edge->GetTo()->GetIndex() * Node::NODE_FIELD_COUNT); // 3.
203 writer->WriteString("],\n");
204 } else {
205 writer->WriteNumber(edge->GetTo()->GetIndex() * Node::NODE_FIELD_COUNT); // 3.
206 writer->WriteChar('\n');
207 }
208 i++;
209 }
210 }
211
SerializeTraceFunctionInfo(HeapSnapshot * snapshot,StreamWriter * writer)212 void HeapSnapshotJSONSerializer::SerializeTraceFunctionInfo(HeapSnapshot *snapshot, StreamWriter *writer)
213 {
214 const CVector<FunctionInfo> trackAllocationsStack = snapshot->GetTrackAllocationsStack();
215 const StringHashMap *stringTable = snapshot->GetEcmaStringTable();
216
217 writer->WriteString("\"trace_function_infos\":["); // Empty
218 size_t i = 0;
219
220 for (const auto &info : trackAllocationsStack) {
221 if (i > 0) { // add comma except the first line
222 writer->WriteChar(',');
223 }
224 writer->WriteNumber(info.functionId);
225 writer->WriteChar(',');
226 CString functionName(info.functionName.c_str());
227 writer->WriteNumber(stringTable->GetStringId(&functionName));
228 writer->WriteChar(',');
229 CString scriptName(info.scriptName.c_str());
230 writer->WriteNumber(stringTable->GetStringId(&scriptName));
231 writer->WriteChar(',');
232 writer->WriteNumber(info.scriptId);
233 writer->WriteChar(',');
234 writer->WriteNumber(info.lineNumber);
235 writer->WriteChar(',');
236 writer->WriteNumber(info.columnNumber);
237 writer->WriteChar('\n');
238 i++;
239 }
240 writer->WriteString("],\n");
241 }
242
SerializeTraceTree(HeapSnapshot * snapshot,StreamWriter * writer)243 void HeapSnapshotJSONSerializer::SerializeTraceTree(HeapSnapshot *snapshot, StreamWriter *writer)
244 {
245 writer->WriteString("\"trace_tree\":[");
246 TraceTree* tree = snapshot->GetTraceTree();
247 if ((tree != nullptr) && (snapshot->trackAllocations())) {
248 SerializeTraceNode(tree->GetRoot(), writer);
249 }
250 writer->WriteString("],\n");
251 }
252
SerializeTraceNode(TraceNode * node,StreamWriter * writer)253 void HeapSnapshotJSONSerializer::SerializeTraceNode(TraceNode* node, StreamWriter *writer)
254 {
255 if (node == nullptr) {
256 return;
257 }
258
259 writer->WriteNumber(node->GetId());
260 writer->WriteChar(',');
261 writer->WriteNumber(node->GetNodeIndex());
262 writer->WriteChar(',');
263 writer->WriteNumber(node->GetTotalCount());
264 writer->WriteChar(',');
265 writer->WriteNumber(node->GetTotalSize());
266 writer->WriteString(",[");
267
268 int i = 0;
269 for (TraceNode* child : node->GetChildren()) {
270 if (i > 0) {
271 writer->WriteChar(',');
272 }
273 SerializeTraceNode(child, writer);
274 i++;
275 }
276 writer->WriteChar(']');
277 }
278
SerializeSamples(HeapSnapshot * snapshot,StreamWriter * writer)279 void HeapSnapshotJSONSerializer::SerializeSamples(HeapSnapshot *snapshot, StreamWriter *writer)
280 {
281 writer->WriteString("\"samples\":[");
282 const CVector<TimeStamp> &timeStamps = snapshot->GetTimeStamps();
283 if (!timeStamps.empty()) {
284 auto firstTimeStamp = timeStamps[0];
285 bool isFirst = true;
286 for (auto timeStamp : timeStamps) {
287 if (!isFirst) {
288 writer->WriteString("\n, ");
289 } else {
290 isFirst = false;
291 }
292 writer->WriteNumber(timeStamp.GetTimeStamp() - firstTimeStamp.GetTimeStamp());
293 writer->WriteString(", ");
294 writer->WriteNumber(timeStamp.GetLastSequenceId());
295 }
296 }
297 writer->WriteString("],\n");
298 }
299
SerializeLocations(StreamWriter * writer)300 void HeapSnapshotJSONSerializer::SerializeLocations(StreamWriter *writer)
301 {
302 writer->WriteString("\"locations\":[],\n");
303 }
304
SerializeStringTable(HeapSnapshot * snapshot,StreamWriter * writer)305 void HeapSnapshotJSONSerializer::SerializeStringTable(HeapSnapshot *snapshot, StreamWriter *writer)
306 {
307 LOG_ECMA(INFO) << "HeapSnapshotJSONSerializer::SerializeStringTable";
308 ECMA_BYTRACE_NAME(HITRACE_TAG_ARK, "HeapSnapshotJSONSerializer::SerializeStringTable");
309 const StringHashMap *stringTable = snapshot->GetEcmaStringTable();
310 ASSERT(stringTable != nullptr);
311 writer->WriteString("\"strings\":[\"<dummy>\",\n");
312 writer->WriteString("\"\",\n");
313 writer->WriteString("\"GC roots\",\n");
314 // StringId Range from 3
315 size_t capcity = stringTable->GetCapcity();
316 ASSERT(capcity > 0);
317 size_t i = 0;
318 for (auto key : stringTable->GetOrderedKeyStorage()) {
319 if (i == capcity - 1) {
320 writer->WriteChar('\"');
321 SerializeString(stringTable->GetStringByKey(key), writer); // No Comma for the last line
322 writer->WriteString("\"\n");
323 } else {
324 writer->WriteChar('\"');
325 SerializeString(stringTable->GetStringByKey(key), writer);
326 writer->WriteString("\",\n");
327 }
328 i++;
329 }
330 writer->WriteString("]\n");
331 }
332
SerializeString(CString * str,StreamWriter * writer)333 void HeapSnapshotJSONSerializer::SerializeString(CString *str, StreamWriter *writer)
334 {
335 if (str == nullptr || writer == nullptr) {
336 return;
337 }
338 const char *s = str->c_str();
339 while (*s != '\0') {
340 if (*s == '\"' || *s == '\\') {
341 writer->WriteChar('\\');
342 writer->WriteChar(*s);
343 s++;
344 } else if (*s == '\n') {
345 writer->WriteString("\\n");
346 s++;
347 } else if (*s == '\b') {
348 writer->WriteString("\\b");
349 s++;
350 } else if (*s == '\f') {
351 writer->WriteString("\\f");
352 s++;
353 } else if (*s == '\r') {
354 writer->WriteString("\\r");
355 s++;
356 } else if (*s == '\t') {
357 writer->WriteString("\\t");
358 s++;
359 } else if (*s > ASCII_US && *s < ASCII_DEL) {
360 writer->WriteChar(*s);
361 s++;
362 } else if (*s <= ASCII_US || *s == ASCII_DEL) {
363 // special char convert to \u unicode
364 SerializeUnicodeChar(static_cast<uint32_t>(*s), writer);
365 s++;
366 } else {
367 // convert utf-8 to \u unicode
368 size_t len = 1;
369 while (len <= UTF8_MAX_BYTES && *(s + len) != '\0') {
370 len++;
371 }
372 auto [unicode, bytes] =
373 base::utf_helper::ConvertUtf8ToUnicodeChar(reinterpret_cast<const uint8_t *>(s), len);
374 if (unicode == base::utf_helper::INVALID_UTF8) {
375 LOG_ECMA(WARN) << "HeapSnapshotJSONSerializer::SerializeString, str is not utf-8";
376 writer->WriteChar('?');
377 s++;
378 } else {
379 SerializeUnicodeChar(unicode, writer);
380 s += bytes;
381 }
382 }
383 }
384 }
385
SerializeUnicodeChar(uint32_t unicodeChar,StreamWriter * writer)386 void HeapSnapshotJSONSerializer::SerializeUnicodeChar(uint32_t unicodeChar, StreamWriter *writer)
387 {
388 static const char hexChars[] = "0123456789ABCDEF";
389 writer->WriteString("\\u");
390 writer->WriteChar(hexChars[(unicodeChar >> 0xC) & 0xF]);
391 writer->WriteChar(hexChars[(unicodeChar >> 0x8) & 0xF]);
392 writer->WriteChar(hexChars[(unicodeChar >> 0x4) & 0xF]);
393 writer->WriteChar(hexChars[unicodeChar & 0xF]);
394 }
395
SerializerSnapshotClosure(StreamWriter * writer)396 void HeapSnapshotJSONSerializer::SerializerSnapshotClosure(StreamWriter *writer)
397 {
398 writer->WriteString("}\n");
399 }
400 } // namespace panda::ecmascript
401