1 /*
2 * Copyright (c) Huawei Technologies Co., Ltd. 2021. All rights reserved.
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 * Description: FtraceParser class implements
16 */
17 #include "ftrace_parser.h"
18
19 #include <algorithm>
20 #include <cerrno>
21 #include <cstring>
22 #include <fcntl.h>
23 #include <fstream>
24 #include <regex>
25 #include <sstream>
26 #include <unistd.h>
27
28 #include "common.h"
29 #include "file_utils.h"
30 #include "securec.h"
31 #include "string_utils.h"
32
33 #ifdef HILOG_DEBUG
34 #undef HILOG_DEBUG
35 #endif
36
37 #define HILOG_DEBUG(LOG_CORE, fmt, ...) \
38 if (debugOn_) { \
39 PROFILER_LOG_INFO(LOG_CORE, ":DEBUG: " fmt, ##__VA_ARGS__); \
40 }
41
42 namespace {
43 using namespace OHOS::Developtools::Profiler;
44 constexpr unsigned RB_MISSED_EVENTS = (1uL << 31); // Flag when events were overwritten
45 constexpr unsigned RB_MISSED_STORED = (1 << 30); // Missed count stored at end
46 constexpr unsigned RB_MISSED_FLAGS = (RB_MISSED_EVENTS | RB_MISSED_STORED);
47
48 constexpr unsigned COL_IDX_NAME = 0;
49 constexpr unsigned COL_IDX_VALUE = 1;
50
51 constexpr unsigned TS_EXT_SHIFT = 27;
52 constexpr uint32_t INT_MAX_LEN = 10;
53
GetTimestampIncrements(uint64_t ext)54 inline uint64_t GetTimestampIncrements(uint64_t ext)
55 {
56 return ext << TS_EXT_SHIFT;
57 }
58 } // namespace
59
60 FTRACE_NS_BEGIN
FtraceParser()61 FtraceParser::FtraceParser()
62 {
63 PROFILER_LOG_INFO(LOG_CORE, "FtraceParser create!");
64 }
65
Init()66 bool FtraceParser::Init()
67 {
68 fixedCharArrayRegex_ = std::regex(R"(char \w+\[\d+\])");
69 flexDataLocArrayRegex_ = std::regex(R"(__data_loc [a-zA-Z_0-9 ]+\[\] \w+)");
70 if (FtraceFsOps::GetInstance().IsHmKernel()) {
71 return true;
72 }
73 std::string printkFormats = FtraceFsOps::GetInstance().GetPrintkFormats();
74 CHECK_TRUE(printkFormats.size() > 0, false, "read printk_formats failed!");
75 CHECK_TRUE(PrintkFormatsParser::GetInstance().Parse(printkFormats), false, "parse printk_formats failed");
76
77 std::string formatDesc = FtraceFsOps::GetInstance().GetPageHeaderFormat();
78 CHECK_TRUE(formatDesc.size() > 0, false, "read header_page failed!");
79 osVersion_ = FtraceFsOps::GetInstance().GetKernelVersion();
80 return ParseHeaderPageFormat(formatDesc);
81 }
82
~FtraceParser()83 FtraceParser::~FtraceParser()
84 {
85 PROFILER_LOG_INFO(LOG_CORE, "FtraceParser destroy!");
86 }
87
SetupEvent(const std::string & type,const std::string & name)88 bool FtraceParser::SetupEvent(const std::string& type, const std::string& name)
89 {
90 if (!SubEventParser<FtraceEvent>::GetInstance().IsSupport(name)) {
91 // no sub event parser found for event, so no need to parse format file
92 return false;
93 }
94
95 EventFormat format;
96 format.eventType = type;
97 format.eventName = name;
98 std::string desc = FtraceFsOps::GetInstance().GetEventDataFormat(type, name);
99 if (desc != "") {
100 CHECK_TRUE(ParseEventFormat(desc.data(), format), false, "parse %s/%s/format failed!",
101 type.c_str(), name.c_str());
102 CHECK_TRUE(SubEventParser<FtraceEvent>::GetInstance().SetupEvent(format),
103 false, "setup %s/%s failed!", type.c_str(), name.c_str());
104 CHECK_TRUE(SubEventParser<ProtoEncoder::FtraceEvent>::GetInstance().SetupEvent(format),
105 false, "setup pbzero %s/%s failed!", type.c_str(), name.c_str());
106 }
107 return true;
108 }
109
ParseHeaderPageFormat(const std::string & formatDesc)110 bool FtraceParser::ParseHeaderPageFormat(const std::string& formatDesc)
111 {
112 EventFormat format = {};
113 CHECK_TRUE(ParseEventFormat(formatDesc, format), false, "parse events/header_page failed!");
114
115 bool commitFound = false;
116 for (auto& field : format.fields) {
117 if (field.name == "timestamp") {
118 pageHeaderFormat_.timestamp = field;
119 } else if (field.name == "commit") {
120 pageHeaderFormat_.commit = field;
121 commitFound = true;
122 } else if (field.name == "overwrite") {
123 pageHeaderFormat_.overwrite = field;
124 }
125 }
126
127 CHECK_TRUE(commitFound, false, "commit field not found!");
128 return true;
129 }
130
GetHeaderPageCommitSize(void)131 int FtraceParser::GetHeaderPageCommitSize(void)
132 {
133 // return the size value of commit field read from events/header_page
134 return pageHeaderFormat_.commit.size;
135 }
136
ParseEventFormat(const std::string & formatDesc,EventFormat & format)137 bool FtraceParser::ParseEventFormat(const std::string& formatDesc, EventFormat& format)
138 {
139 std::string idLinePrefix = "ID:";
140 std::string fieldLinePrefix = "field:";
141 std::string printFmtLinePrefix = "print fmt:";
142
143 std::string line;
144 std::stringstream sin(formatDesc);
145 while (getline(sin, line)) {
146 line = StringUtils::Strip(line);
147 if (line.empty()) {
148 continue;
149 } else if (StringUtils::StartsWith(line, fieldLinePrefix)) {
150 ParseFieldFormat(line, format);
151 } else if (StringUtils::StartsWith(line, idLinePrefix)) {
152 auto idStr = line.substr(idLinePrefix.size() + 1);
153 if (COMMON::IsNumeric(idStr)) {
154 format.eventId = static_cast<uint32_t>(atoi(idStr.c_str()));
155 }
156 }
157 }
158 CHECK_TRUE(format.fields.size() > 0, false, "ParseEventFormat failed!");
159 size_t lastFiledIndex = format.fields.size() > 1 ? format.fields.size() - 1 : 0;
160 format.eventSize = format.fields[lastFiledIndex].offset + format.fields[lastFiledIndex].size;
161 return true;
162 }
163
SplitNameFromTypeName(const std::string & typeName)164 static std::string SplitNameFromTypeName(const std::string& typeName)
165 {
166 std::string name;
167 if (typeName.size() > 0) { // split type and name
168 auto posT0 = typeName.rfind(" ");
169 std::string rightHalf = typeName.substr(posT0 + 1);
170 size_t dataIndex = rightHalf.size() > 1 ? rightHalf.size() - 1 : 0;
171 if (rightHalf[dataIndex] != ']') {
172 name = rightHalf;
173 } else {
174 std::string::size_type postT1 = rightHalf.rfind('[');
175 if (postT1 == std::string::npos) {
176 return "";
177 }
178 name = rightHalf.substr(0, postT1);
179 }
180 }
181 return name;
182 }
183
EraseNameFromTypeName(const std::string & typeName,const std::string & name)184 static std::string EraseNameFromTypeName(const std::string& typeName, const std::string& name)
185 {
186 std::string type;
187 if (name.size() > 0) { // erase name part from typeName
188 type = typeName;
189 auto pos = type.find(name);
190 type.replace(pos, name.size(), "");
191 type = StringUtils::Strip(type);
192 }
193 return type;
194 }
195
ParseCommonFiledIndex(CommonFiledIndex & commonIndex,const std::string & name,int index)196 static void ParseCommonFiledIndex(CommonFiledIndex& commonIndex, const std::string& name, int index)
197 {
198 if (name == "common_type") {
199 commonIndex.type = index;
200 } else if (name == "common_flags") {
201 commonIndex.flags = index;
202 } else if (name == "common_preempt_count") {
203 commonIndex.preemt = index;
204 } else if (name == "common_pid") {
205 commonIndex.pid = index;
206 }
207 }
208
ParseFieldFormat(const std::string & fieldLine,EventFormat & format)209 bool FtraceParser::ParseFieldFormat(const std::string& fieldLine, EventFormat& format)
210 {
211 FieldFormat fieldInfo;
212 std::string typeName;
213 std::string offsetStr;
214 std::string sizeStr;
215 std::string signedStr;
216
217 for (auto& part : StringUtils::Split(fieldLine, ";")) {
218 auto cols = StringUtils::Split(StringUtils::Strip(part), ":");
219 if (cols.size() < COL_IDX_VALUE) {
220 continue;
221 }
222 const auto& key = cols[COL_IDX_NAME];
223 if (key == "field") {
224 typeName = cols[COL_IDX_VALUE];
225 } else if (key == "offset") {
226 offsetStr = cols[COL_IDX_VALUE];
227 } else if (key == "size") {
228 sizeStr = cols[COL_IDX_VALUE];
229 } else if (key == "signed") {
230 signedStr = cols[COL_IDX_VALUE];
231 }
232 }
233
234 std::string name = SplitNameFromTypeName(typeName);
235 std::string type = EraseNameFromTypeName(typeName, name); // for field type
236 fieldInfo.name = name;
237 fieldInfo.typeName = typeName;
238 if (COMMON::IsNumeric(offsetStr)) {
239 fieldInfo.offset = atoi(offsetStr.c_str());
240 }
241 if (COMMON::IsNumeric(sizeStr)) {
242 fieldInfo.size = atoi(sizeStr.c_str());
243 }
244 if (COMMON::IsNumeric(signedStr)) {
245 fieldInfo.isSigned = atoi(signedStr.c_str());
246 }
247
248 ParseFieldType(type, fieldInfo);
249 ParseProtoType(fieldInfo);
250
251 if (StringUtils::StartsWith(name, "common_")) {
252 ParseCommonFiledIndex(format.commonIndex, name, static_cast<int>(format.commonFields.size()));
253 format.commonFields.push_back(fieldInfo);
254 } else {
255 format.fields.push_back(fieldInfo);
256 }
257 return true;
258 }
259
ParseSepcialIntType(FieldFormat & field,const std::string & type,const std::string & typeName)260 static bool ParseSepcialIntType(FieldFormat& field, const std::string& type, const std::string& typeName)
261 {
262 if (type == "bool") {
263 field.filedType = FIELD_TYPE_BOOL;
264 return true;
265 }
266
267 if (type == "ino_t" || type == "i_ino") {
268 if (field.size == sizeof(uint32_t)) {
269 field.filedType = FIELD_TYPE_INODE32;
270 return true;
271 } else if (field.size == sizeof(uint64_t)) {
272 field.filedType = FIELD_TYPE_INODE64;
273 return true;
274 }
275 }
276
277 if (type == "dev_t") {
278 if (field.size == sizeof(uint32_t)) {
279 field.filedType = FIELD_TYPE_DEVID32;
280 return true;
281 } else if (field.size == sizeof(uint64_t)) {
282 field.filedType = FIELD_TYPE_DEVID64;
283 return true;
284 }
285 }
286
287 // Pids (as in 'sched_switch').
288 if (type == "pid_t") {
289 field.filedType = FIELD_TYPE_PID32;
290 return true;
291 }
292
293 if ((typeName.find("common_pid") != std::string::npos)) {
294 field.filedType = FIELD_TYPE_COMMONPID32;
295 return true;
296 }
297 return false;
298 }
299
ParseCommonIntType(FieldFormat & field,bool sign)300 static bool ParseCommonIntType(FieldFormat& field, bool sign)
301 {
302 switch (field.size) {
303 case sizeof(int8_t):
304 field.filedType = sign ? FIELD_TYPE_INT8 : FIELD_TYPE_UINT8;
305 return true;
306 case sizeof(int16_t):
307 field.filedType = sign ? FIELD_TYPE_INT16 : FIELD_TYPE_UINT16;
308 return true;
309 case sizeof(int32_t):
310 field.filedType = sign ? FIELD_TYPE_INT32 : FIELD_TYPE_UINT32;
311 return true;
312 case sizeof(int64_t):
313 field.filedType = sign ? FIELD_TYPE_INT64 : FIELD_TYPE_UINT64;
314 return true;
315 default:
316 break;
317 }
318 return false;
319 }
320
ParseKernelAddrField(FieldFormat & field,const std::string & type)321 static bool ParseKernelAddrField(FieldFormat& field, const std::string& type)
322 {
323 if (type == "void*" || type == "void *") {
324 if (field.size == sizeof(uint64_t)) { // 64-bit kernel addresses
325 field.filedType = FIELD_TYPE_SYMADDR64;
326 return true;
327 } else if (field.size == sizeof(uint32_t)) { // 32-bit kernel addresses
328 field.filedType = FIELD_TYPE_SYMADDR32;
329 return true;
330 }
331 }
332 return false;
333 }
334
ParseFieldType(const std::string & type,FieldFormat & field)335 bool FtraceParser::ParseFieldType(const std::string& type, FieldFormat& field)
336 {
337 const std::string& typeName = field.typeName;
338 // Fixed size C char arrary, likes "char a[LEN]"
339 if (std::regex_match(typeName, fixedCharArrayRegex_)) {
340 field.filedType = FIELD_TYPE_FIXEDCSTRING;
341 return true;
342 }
343
344 // for flex array with __data_loc mark, likes: __data_loc char[] name; __data_loc __u8[] buf;
345 if (std::regex_match(typeName, flexDataLocArrayRegex_)) {
346 CHECK_TRUE(field.size == sizeof(uint32_t), false, "__data_loc %s, size: %hu", typeName.c_str(), field.size);
347 field.filedType = FIELD_TYPE_DATALOC;
348 return true;
349 }
350
351 if ((typeName.find("char[]") != std::string::npos) || (typeName.find("char *") != std::string::npos)) {
352 field.filedType = FIELD_TYPE_STRINGPTR;
353 return true;
354 }
355
356 // Variable length strings: "char foo" + size: 0 (as in 'print').
357 if ((type == "char" || type == "char []") && field.size == 0) {
358 field.filedType = FIELD_TYPE_CSTRING;
359 return true;
360 }
361
362 // 64-bit kernel addresses
363 if (ParseKernelAddrField(field, type)) {
364 return true;
365 }
366
367 if (ParseSepcialIntType(field, type, typeName)) {
368 return true;
369 }
370
371 // int uint:
372 if (ParseCommonIntType(field, field.isSigned)) {
373 return true;
374 }
375 return false;
376 }
377
ParseProtoType(FieldFormat & field)378 void FtraceParser::ParseProtoType(FieldFormat& field)
379 {
380 switch (field.filedType) {
381 case FIELD_TYPE_CSTRING:
382 case FIELD_TYPE_FIXEDCSTRING:
383 case FIELD_TYPE_STRINGPTR:
384 case FIELD_TYPE_DATALOC:
385 field.protoType = PROTO_TYPE_STRING;
386 break;
387 case FIELD_TYPE_INT8:
388 case FIELD_TYPE_INT16:
389 case FIELD_TYPE_INT32:
390 case FIELD_TYPE_PID32:
391 case FIELD_TYPE_COMMONPID32:
392 field.protoType = PROTO_TYPE_INT32;
393 break;
394 case FIELD_TYPE_INT64:
395 field.protoType = PROTO_TYPE_INT64;
396 break;
397 case FIELD_TYPE_UINT8:
398 case FIELD_TYPE_UINT16:
399 case FIELD_TYPE_UINT32:
400 case FIELD_TYPE_BOOL:
401 case FIELD_TYPE_DEVID32:
402 case FIELD_TYPE_SYMADDR32:
403 field.protoType = PROTO_TYPE_UINT32;
404 break;
405 case FIELD_TYPE_DEVID64:
406 case FIELD_TYPE_UINT64:
407 case FIELD_TYPE_INODE32:
408 case FIELD_TYPE_INODE64:
409 case FIELD_TYPE_SYMADDR64:
410 field.protoType = PROTO_TYPE_UINT64;
411 break;
412 case FIELD_TYPE_INVALID:
413 field.protoType = PROTO_TYPE_UNKNOWN;
414 break;
415 default:
416 break;
417 }
418 }
419
ParsePerCpuStatus(PerCpuStats & stats,const std::string & perCpuStats)420 bool FtraceParser::ParsePerCpuStatus(PerCpuStats& stats, const std::string& perCpuStats)
421 {
422 std::string line;
423 std::stringstream input(perCpuStats);
424
425 int count = 0;
426 while (getline(input, line, '\n')) {
427 std::string sep = ": ";
428 size_t pos = line.rfind(sep);
429 if (pos == std::string::npos) {
430 continue;
431 }
432 std::stringstream ss(line.substr(pos + sep.size()));
433 std::string name = line.substr(0, pos);
434 if (name == "entries") {
435 ss >> stats.entries;
436 count++;
437 } else if (name == "overrun") {
438 ss >> stats.overrun;
439 count++;
440 } else if (name == "commit overrun") {
441 ss >> stats.commitOverrun;
442 count++;
443 } else if (name == "bytes") {
444 ss >> stats.bytes;
445 count++;
446 } else if (name == "oldest event ts") {
447 ss >> stats.oldestEventTs;
448 count++;
449 } else if (name == "now ts") {
450 ss >> stats.nowTs;
451 count++;
452 } else if (name == "dropped events") {
453 ss >> stats.droppedEvents;
454 count++;
455 } else if (name == "read events") {
456 ss >> stats.readEvents;
457 count++;
458 }
459 }
460 return count > 0;
461 }
462
463 // parse kernel ring buffer page header data
ParsePageHeader()464 bool FtraceParser::ParsePageHeader()
465 {
466 // read time stamp
467 uint64_t timestamp = 0;
468 CHECK_TRUE(ReadInc(&cur_, endOfPage_, ×tamp, sizeof(timestamp)), false, "read timestamp from page failed!");
469 pageHeader_.timestamp = timestamp;
470
471 // read data size and overwriten flags
472 uint64_t commit = 0;
473 const int commitSize = GetHeaderPageCommitSize(); // 8B on 64bit device, 4B on 32bit device
474 CHECK_TRUE(ReadInc(&cur_, endOfPage_, &commit, commitSize), false, "read commit to page header failed!");
475
476 // refers kernel function ring_buffer_page_len:
477 pageHeader_.size = (commit & ~RB_MISSED_FLAGS);
478 pageHeader_.overwrite = (commit & RB_MISSED_EVENTS);
479
480 pageHeader_.startpos = cur_;
481 pageHeader_.endpos = cur_ + pageHeader_.size;
482 return true;
483 }
484
485 // parse /sys/kernel/debug/tracing/saved_tgids
486 // refers kernel function saved_tgids_show
ParseSavedTgid(const std::string & savedTgid)487 bool FtraceParser::ParseSavedTgid(const std::string& savedTgid)
488 {
489 int32_t pid = 0;
490 int32_t tgid = 0;
491 std::stringstream sin(savedTgid);
492 // kernel format code with: "%d %d\n"
493 while (sin >> pid >> tgid) {
494 std::string filePath = "/proc/" + std::to_string(pid) + "/status";
495 if (access(filePath.c_str(), F_OK) == 0) {
496 tgidDict_[pid] = tgid;
497 }
498 }
499
500 if (tgidDict_.size() == 0) {
501 PROFILER_LOG_WARN(LOG_CORE, "ParseSavedTgid: parsed tigds: %zu", tgidDict_.size());
502 }
503 return true;
504 }
505
506 // parse /sys/kernel/debug/tracing/saved_cmdlines
507 // refers kernel function saved_cmdlines_show
ParseSavedCmdlines(const std::string & savedCmdlines)508 bool FtraceParser::ParseSavedCmdlines(const std::string& savedCmdlines)
509 {
510 bool retval = false;
511 int32_t pid;
512 std::string comm;
513 std::string line;
514 std::stringstream sin(savedCmdlines);
515 while (std::getline(sin, line)) {
516 // kernel format with: "%d %s\n"
517 auto pos = line.find(' ');
518 if (pos != std::string::npos && pos < INT_MAX_LEN) {
519 auto pidStr = line.substr(0, pos);
520 pid = COMMON::IsNumeric(pidStr) ? std::stoi(pidStr) : 0;
521 comm = line.substr(pos + 1);
522 commDict_[pid] = comm;
523 retval = true;
524 }
525 }
526
527 if (commDict_.size() == 0) {
528 PROFILER_LOG_WARN(LOG_CORE, "ParseSavedCmdlines: parsed cmdlines: %zu", commDict_.size());
529 }
530 return retval;
531 }
532
533 // parse /sys/kernel/debug/tracing/saved_tgids
534 // refers kernel function saved_tgids_show
ParseTargetedSavedTgid(const std::string & savedTgidsPath,int targetPid)535 void FtraceParser::ParseTargetedSavedTgid(const std::string& savedTgidsPath, int targetPid)
536 {
537 int32_t pid = 0;
538 int32_t tgid = 0;
539 std::stringstream sin(savedTgidsPath);
540 // kernel format code with: "%d %d\n"
541 while (sin >> pid >> tgid) {
542 if (pid == targetPid) {
543 tgidDict_[pid] = tgid;
544 break;
545 }
546 }
547
548 if (tgidDict_.size() == 0) {
549 PROFILER_LOG_WARN(LOG_CORE, "ParseSavedTgid: parsed tigds: %zu", tgidDict_.size());
550 }
551 }
552
ParsePaddingData(const FtraceEventHeader & eventHeader)553 bool FtraceParser::ParsePaddingData(const FtraceEventHeader& eventHeader)
554 {
555 if (eventHeader.timeDelta == 0) {
556 return false;
557 }
558 uint32_t paddingLength;
559 CHECK_TRUE(ReadInc(&cur_, endOfData_, &paddingLength, sizeof(paddingLength)), false, "read padding len failed!");
560
561 // skip padding data
562 cur_ += paddingLength;
563 return true;
564 }
565
ParseTimeExtend(const FtraceEventHeader & eventHeader)566 bool FtraceParser::ParseTimeExtend(const FtraceEventHeader& eventHeader)
567 {
568 uint32_t deltaExt = 0;
569 CHECK_TRUE(ReadInc(&cur_, endOfData_, &deltaExt, sizeof(deltaExt)), false, "read time delta failed!");
570
571 timestamp_ += GetTimestampIncrements(deltaExt);
572 PROFILER_LOG_INFO(LOG_CORE, "ParseTimeExtend: update ts with %u to %" PRIu64, deltaExt, timestamp_);
573 return true;
574 }
575
ParseTimeStamp(const FtraceEventHeader & eventHeader)576 bool FtraceParser::ParseTimeStamp(const FtraceEventHeader& eventHeader)
577 {
578 uint32_t deltaExt = 0;
579 CHECK_TRUE(ReadInc(&cur_, endOfData_, &deltaExt, sizeof(deltaExt)), false, "read time delta failed!");
580
581 // refers kernel function rb_update_write_stamp in ring_buffer.c
582 timestamp_ = eventHeader.timeDelta + GetTimestampIncrements(deltaExt);
583 PROFILER_LOG_INFO(LOG_CORE, "ParseTimeStamp: update ts with %u to %" PRIu64, deltaExt, timestamp_);
584 return true;
585 }
586
ReadInc(uint8_t * start[],uint8_t end[],void * outData,size_t outSize)587 bool FtraceParser::ReadInc(uint8_t* start[], uint8_t end[], void* outData, size_t outSize)
588 {
589 if ((end - *start) < static_cast<ptrdiff_t>(outSize)) {
590 return false;
591 }
592 CHECK_TRUE(memcpy_s(outData, outSize, *start, outSize) == EOK, false,
593 "read %zu bytes from memory region FAILED", outSize);
594 *start += outSize;
595 return true;
596 }
597
IsValidIndex(int idx)598 bool FtraceParser::IsValidIndex(int idx)
599 {
600 return idx != CommonFiledIndex::INVALID_IDX;
601 }
602
SetDebugOn(bool value)603 void FtraceParser::SetDebugOn(bool value)
604 {
605 debugOn_ = value;
606 PROFILER_LOG_INFO(LOG_CORE, "debugOption: %s", debugOn_ ? "true" : "false");
607 }
608 FTRACE_NS_END
609