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 "ecmascript/dfx/hprof/string_hashmap.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 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
SerializeSnapshotHeader(HeapSnapshot * snapshot,StreamWriter * writer)48 void HeapSnapshotJSONSerializer::SerializeSnapshotHeader(HeapSnapshot *snapshot, StreamWriter *writer)
49 {
50 writer->WriteString("{\"snapshot\":\n"); // 1.
51 writer->WriteString("{\"meta\":\n"); // 2.
52 // NOLINTNEXTLINE(modernize-raw-string-literal)
53 writer->WriteString("{\"node_fields\":[\"type\",\"name\",\"id\",\"self_size\",\"edge_count\",\"trace_node_id\",");
54 writer->WriteString("\"detachedness\",\"native_size\"],\n"); // 3.
55 // NOLINTNEXTLINE(modernize-raw-string-literal)
56 writer->WriteString("\"node_types\":[[\"hidden\",\"array\",\"string\",\"object\",\"code\",\"closure\",\"regexp\",");
57 // NOLINTNEXTLINE(modernize-raw-string-literal)
58 writer->WriteString("\"number\",\"native\",\"synthetic\",\"concatenated string\",\"slicedstring\",\"symbol\",");
59 // NOLINTNEXTLINE(modernize-raw-string-literal)
60 writer->WriteString("\"bigint\"],\"string\",\"number\",\"number\",\"number\",\"number\",\"number\"],\n"); // 4.
61 // NOLINTNEXTLINE(modernize-raw-string-literal)
62 writer->WriteString("\"edge_fields\":[\"type\",\"name_or_index\",\"to_node\"],\n"); // 5.
63 // NOLINTNEXTLINE(modernize-raw-string-literal)
64 writer->WriteString("\"edge_types\":[[\"context\",\"element\",\"property\",\"internal\",\"hidden\",\"shortcut\",");
65 // NOLINTNEXTLINE(modernize-raw-string-literal)
66 writer->WriteString("\"weak\"],\"string_or_number\",\"node\"],\n"); // 6.
67 // NOLINTNEXTLINE(modernize-raw-string-literal)
68 writer->WriteString("\"trace_function_info_fields\":[\"function_id\",\"name\",\"script_name\",\"script_id\",");
69 // NOLINTNEXTLINE(modernize-raw-string-literal)
70 writer->WriteString("\"line\",\"column\"],\n"); // 7.
71 // NOLINTNEXTLINE(modernize-raw-string-literal)
72 writer->WriteString("\"trace_node_fields\":[\"id\",\"function_info_index\",\"count\",\"size\",\"children\"],\n");
73 // NOLINTNEXTLINE(modernize-raw-string-literal)
74 writer->WriteString("\"sample_fields\":[\"timestamp_us\",\"last_assigned_id\"],\n"); // 9.
75 // NOLINTNEXTLINE(modernize-raw-string-literal)
76 // 10.
77 writer->WriteString("\"location_fields\":[\"object_index\",\"script_id\",\"line\",\"column\"]},\n\"node_count\":");
78 writer->WriteNumber(snapshot->GetNodeCount()); // 11.
79 writer->WriteString(",\n\"edge_count\":");
80 writer->WriteNumber(snapshot->GetEdgeCount()); // 12.
81 writer->WriteString(",\n\"trace_function_count\":");
82 writer->WriteNumber(snapshot->GetTrackAllocationsStack().size()); // 13.
83 writer->WriteString("\n},\n"); // 14.
84 }
85
SerializeNodes(HeapSnapshot * snapshot,StreamWriter * writer)86 void HeapSnapshotJSONSerializer::SerializeNodes(HeapSnapshot *snapshot, StreamWriter *writer)
87 {
88 const CList<Node *> *nodes = snapshot->GetNodes();
89 const StringHashMap *stringTable = snapshot->GetEcmaStringTable();
90 ASSERT(nodes != nullptr);
91 writer->WriteString("\"nodes\":["); // Section Header
92 size_t i = 0;
93 for (auto *node : *nodes) {
94 if (i > 0) {
95 writer->WriteChar(','); // add comma except first line
96 }
97 writer->WriteNumber(static_cast<int>(node->GetType())); // 1.
98 writer->WriteChar(',');
99 writer->WriteNumber(stringTable->GetStringId(node->GetName())); // 2.
100 writer->WriteChar(',');
101 writer->WriteNumber(node->GetId()); // 3.
102 writer->WriteChar(',');
103 writer->WriteNumber(node->GetSelfSize()); // 4.
104 writer->WriteChar(',');
105 writer->WriteNumber(node->GetEdgeCount()); // 5.
106 writer->WriteChar(',');
107 writer->WriteNumber(node->GetStackTraceId()); // 6.
108 writer->WriteChar(',');
109 writer->WriteChar('0'); // 7.detachedness default 0
110 writer->WriteChar(',');
111 writer->WriteNumber(node->GetNativeSize());
112 if (i == nodes->size() - 1) { // add comma at last the line
113 writer->WriteString("],\n"); // 7. detachedness default
114 } else {
115 writer->WriteString("\n"); // 7.
116 }
117 i++;
118 }
119 }
120
SerializeEdges(HeapSnapshot * snapshot,StreamWriter * writer)121 void HeapSnapshotJSONSerializer::SerializeEdges(HeapSnapshot *snapshot, StreamWriter *writer)
122 {
123 const CList<Edge *> *edges = snapshot->GetEdges();
124 const StringHashMap *stringTable = snapshot->GetEcmaStringTable();
125 ASSERT(edges != nullptr);
126 writer->WriteString("\"edges\":[");
127 size_t i = 0;
128 for (auto *edge : *edges) {
129 StringId nameOrIndex = edge->GetType() == EdgeType::ELEMENT ?
130 edge->GetIndex() : stringTable->GetStringId(edge->GetName());
131 if (i > 0) { // add comma except the first line
132 writer->WriteChar(',');
133 }
134 writer->WriteNumber(static_cast<int>(edge->GetType())); // 1.
135 writer->WriteChar(',');
136 writer->WriteNumber(nameOrIndex); // 2. Use StringId
137 writer->WriteChar(',');
138
139 if (i == edges->size() - 1) { // add comma at last the line
140 writer->WriteNumber(edge->GetTo()->GetIndex() * Node::NODE_FIELD_COUNT); // 3.
141 writer->WriteString("],\n");
142 } else {
143 writer->WriteNumber(edge->GetTo()->GetIndex() * Node::NODE_FIELD_COUNT); // 3.
144 writer->WriteChar('\n');
145 }
146 i++;
147 }
148 }
149
SerializeTraceFunctionInfo(HeapSnapshot * snapshot,StreamWriter * writer)150 void HeapSnapshotJSONSerializer::SerializeTraceFunctionInfo(HeapSnapshot *snapshot, StreamWriter *writer)
151 {
152 const CVector<FunctionInfo> trackAllocationsStack = snapshot->GetTrackAllocationsStack();
153 const StringHashMap *stringTable = snapshot->GetEcmaStringTable();
154
155 writer->WriteString("\"trace_function_infos\":["); // Empty
156 size_t i = 0;
157
158 for (const auto &info : trackAllocationsStack) {
159 if (i > 0) { // add comma except the first line
160 writer->WriteChar(',');
161 }
162 writer->WriteNumber(info.functionId);
163 writer->WriteChar(',');
164 CString functionName(info.functionName.c_str());
165 writer->WriteNumber(stringTable->GetStringId(&functionName));
166 writer->WriteChar(',');
167 CString scriptName(info.scriptName.c_str());
168 writer->WriteNumber(stringTable->GetStringId(&scriptName));
169 writer->WriteChar(',');
170 writer->WriteNumber(info.scriptId);
171 writer->WriteChar(',');
172 writer->WriteNumber(info.lineNumber);
173 writer->WriteChar(',');
174 writer->WriteNumber(info.columnNumber);
175 writer->WriteChar('\n');
176 i++;
177 }
178 writer->WriteString("],\n");
179 }
180
SerializeTraceTree(HeapSnapshot * snapshot,StreamWriter * writer)181 void HeapSnapshotJSONSerializer::SerializeTraceTree(HeapSnapshot *snapshot, StreamWriter *writer)
182 {
183 writer->WriteString("\"trace_tree\":[");
184 TraceTree* tree = snapshot->GetTraceTree();
185 if ((tree != nullptr) && (snapshot->trackAllocations())) {
186 SerializeTraceNode(tree->GetRoot(), writer);
187 }
188 writer->WriteString("],\n");
189 }
190
SerializeTraceNode(TraceNode * node,StreamWriter * writer)191 void HeapSnapshotJSONSerializer::SerializeTraceNode(TraceNode* node, StreamWriter *writer)
192 {
193 if (node == nullptr) {
194 return;
195 }
196
197 writer->WriteNumber(node->GetId());
198 writer->WriteChar(',');
199 writer->WriteNumber(node->GetNodeIndex());
200 writer->WriteChar(',');
201 writer->WriteNumber(node->GetTotalCount());
202 writer->WriteChar(',');
203 writer->WriteNumber(node->GetTotalSize());
204 writer->WriteString(",[");
205
206 int i = 0;
207 for (TraceNode* child : node->GetChildren()) {
208 if (i > 0) {
209 writer->WriteChar(',');
210 }
211 SerializeTraceNode(child, writer);
212 i++;
213 }
214 writer->WriteChar(']');
215 }
216
SerializeSamples(HeapSnapshot * snapshot,StreamWriter * writer)217 void HeapSnapshotJSONSerializer::SerializeSamples(HeapSnapshot *snapshot, StreamWriter *writer)
218 {
219 writer->WriteString("\"samples\":[");
220 const CVector<TimeStamp> &timeStamps = snapshot->GetTimeStamps();
221 if (!timeStamps.empty()) {
222 auto firstTimeStamp = timeStamps[0];
223 bool isFirst = true;
224 for (auto timeStamp : timeStamps) {
225 if (!isFirst) {
226 writer->WriteString("\n, ");
227 } else {
228 isFirst = false;
229 }
230 writer->WriteNumber(timeStamp.GetTimeStamp() - firstTimeStamp.GetTimeStamp());
231 writer->WriteString(", ");
232 writer->WriteNumber(timeStamp.GetLastSequenceId());
233 }
234 }
235 writer->WriteString("],\n");
236 }
237
SerializeLocations(StreamWriter * writer)238 void HeapSnapshotJSONSerializer::SerializeLocations(StreamWriter *writer)
239 {
240 writer->WriteString("\"locations\":[],\n");
241 }
242
SerializeStringTable(HeapSnapshot * snapshot,StreamWriter * writer)243 void HeapSnapshotJSONSerializer::SerializeStringTable(HeapSnapshot *snapshot, StreamWriter *writer)
244 {
245 const StringHashMap *stringTable = snapshot->GetEcmaStringTable();
246 ASSERT(stringTable != nullptr);
247 writer->WriteString("\"strings\":[\"<dummy>\",\n");
248 writer->WriteString("\"\",\n");
249 writer->WriteString("\"GC roots\",\n");
250 // StringId Range from 3
251 size_t capcity = stringTable->GetCapcity();
252 size_t i = 0;
253 for (auto key : stringTable->GetOrderedKeyStorage()) {
254 if (i == capcity - 1) {
255 writer->WriteChar('\"');
256 SerializeString(stringTable->GetStringByKey(key), writer); // No Comma for the last line
257 writer->WriteString("\"\n");
258 } else {
259 writer->WriteChar('\"');
260 SerializeString(stringTable->GetStringByKey(key), writer);
261 writer->WriteString("\",\n");
262 }
263 i++;
264 }
265 writer->WriteString("]\n");
266 }
267
SerializeString(CString * str,StreamWriter * writer)268 void HeapSnapshotJSONSerializer::SerializeString(CString *str, StreamWriter *writer)
269 {
270 const char *s = str->c_str();
271 while (*s != '\0') {
272 if (*s == '\"' || *s == '\\') {
273 writer->WriteChar('\\');
274 writer->WriteChar(*s);
275 s++;
276 } else if (*s == '\n') {
277 writer->WriteString("\\n");
278 s++;
279 } else if (*s == '\b') {
280 writer->WriteString("\\b");
281 s++;
282 } else if (*s == '\f') {
283 writer->WriteString("\\f");
284 s++;
285 } else if (*s == '\r') {
286 writer->WriteString("\\r");
287 s++;
288 } else if (*s == '\t') {
289 writer->WriteString("\\t");
290 s++;
291 } else if (*s > ASCII_US && *s < ASCII_DEL) {
292 writer->WriteChar(*s);
293 s++;
294 } else if (*s <= ASCII_US || *s == ASCII_DEL) {
295 // special char convert to \u unicode
296 SerializeUnicodeChar(static_cast<uint32_t>(*s), writer);
297 s++;
298 } else {
299 // convert utf-8 to \u unicode
300 size_t len = 1;
301 while (len <= UTF8_MAX_BYTES && *(s + len) != '\0') {
302 len++;
303 }
304 auto [unicode, bytes] =
305 base::utf_helper::ConvertUtf8ToUnicodeChar(reinterpret_cast<const uint8_t *>(s), len);
306 if (unicode == base::utf_helper::INVALID_UTF8) {
307 LOG_ECMA(WARN) << "HeapSnapshotJSONSerializer::SerializeString, str is not utf-8";
308 writer->WriteChar('?');
309 s++;
310 } else {
311 SerializeUnicodeChar(unicode, writer);
312 s += bytes;
313 }
314 }
315 }
316 }
317
SerializeUnicodeChar(uint32_t unicodeChar,StreamWriter * writer)318 void HeapSnapshotJSONSerializer::SerializeUnicodeChar(uint32_t unicodeChar, StreamWriter *writer)
319 {
320 static const char hexChars[] = "0123456789ABCDEF";
321 writer->WriteString("\\u");
322 writer->WriteChar(hexChars[(unicodeChar >> 0xC) & 0xF]);
323 writer->WriteChar(hexChars[(unicodeChar >> 0x8) & 0xF]);
324 writer->WriteChar(hexChars[(unicodeChar >> 0x4) & 0xF]);
325 writer->WriteChar(hexChars[unicodeChar & 0xF]);
326 }
327
SerializerSnapshotClosure(StreamWriter * writer)328 void HeapSnapshotJSONSerializer::SerializerSnapshotClosure(StreamWriter *writer)
329 {
330 writer->WriteString("}\n");
331 }
332 } // namespace panda::ecmascript
333