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