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