/// The thread ID for the currently running thread.
uint32_t TId;
+ /// The process ID for the currently running process.
+ uint32_t PId;
+
/// The function call arguments.
std::vector<uint64_t> CallArgs;
};
std::string Function;
uint64_t TSC;
uint32_t TId;
+ uint32_t PId;
std::vector<uint64_t> CallArgs;
};
IO.mapOptional("args", Record.CallArgs);
IO.mapRequired("cpu", Record.CPU);
IO.mapRequired("thread", Record.TId);
+ IO.mapOptional("process", Record.PId, 0U);
IO.mapRequired("kind", Record.Type);
IO.mapRequired("tsc", Record.TSC);
}
FileHeader.NonstopTSC = Bitfield & 1uL << 1;
FileHeader.CycleFrequency = HeaderExtractor.getU64(&OffsetPtr);
std::memcpy(&FileHeader.FreeFormData, Data.bytes_begin() + OffsetPtr, 16);
- if (FileHeader.Version != 1 && FileHeader.Version != 2)
+ if (FileHeader.Version != 1 && FileHeader.Version != 2 &&
+ FileHeader.Version != 3)
return make_error<StringError>(
Twine("Unsupported XRay file version: ") + Twine(FileHeader.Version),
std::make_error_code(std::errc::invalid_argument));
// (4) sint32 : function id
// (8) uint64 : tsc
// (4) uint32 : thread id
- // (12) - : padding
+ // (4) uint32 : process id
+ // (8) - : padding
for (auto S = Data.drop_front(32); !S.empty(); S = S.drop_front(32)) {
DataExtractor RecordExtractor(S, true, 8);
uint32_t OffsetPtr = 0;
Record.FuncId = RecordExtractor.getSigned(&OffsetPtr, sizeof(int32_t));
Record.TSC = RecordExtractor.getU64(&OffsetPtr);
Record.TId = RecordExtractor.getU32(&OffsetPtr);
+ Record.PId = RecordExtractor.getU32(&OffsetPtr);
break;
}
case 1: { // Arg payload record.
OffsetPtr += 2;
int32_t FuncId = RecordExtractor.getSigned(&OffsetPtr, sizeof(int32_t));
auto TId = RecordExtractor.getU32(&OffsetPtr);
- if (Record.FuncId != FuncId || Record.TId != TId)
+ auto PId = RecordExtractor.getU32(&OffsetPtr);
+
+ // Make a check for versions above 3 for the Pid field
+ if (Record.FuncId != FuncId || Record.TId != TId ||
+ (FileHeader.Version >= 3 ? Record.PId != PId : false))
return make_error<StringError>(
Twine("Corrupted log, found arg payload following non-matching "
"function + thread record. Record for function ") +
Twine(Record.FuncId) + " != " + Twine(FuncId) + "; offset: " +
Twine(S.data() - Data.data()),
std::make_error_code(std::errc::executable_format_error));
- // Advance another four bytes to avoid padding.
- OffsetPtr += 4;
+
auto Arg = RecordExtractor.getU64(&OffsetPtr);
Record.CallArgs.push_back(Arg);
break;
struct FDRState {
uint16_t CPUId;
uint16_t ThreadId;
+ int32_t ProcessId;
uint64_t BaseTSC;
/// Encode some of the state transitions for the FDR log reader as explicit
CUSTOM_EVENT_DATA,
CALL_ARGUMENT,
BUFFER_EXTENTS,
+ PID_RECORD,
};
Token Expects;
return "CALL_ARGUMENT";
case FDRState::Token::BUFFER_EXTENTS:
return "BUFFER_EXTENTS";
+ case FDRState::Token::PID_RECORD:
+ return "PID_RECORD";
}
return "UNKNOWN";
}
return Error::success();
}
+/// State transition when a PidRecord is encountered.
+Error processFDRPidRecord(FDRState &State, uint8_t RecordFirstByte,
+ DataExtractor &RecordExtractor) {
+
+ if (State.Expects != FDRState::Token::PID_RECORD)
+ return make_error<StringError>(
+ Twine("Malformed log. Read Pid record kind out of sequence; "
+ "expected: ") +
+ fdrStateToTwine(State.Expects),
+ std::make_error_code(std::errc::executable_format_error));
+
+ uint32_t OffsetPtr = 1; // Read starting after the first byte.
+ State.ProcessId = RecordExtractor.getU32(&OffsetPtr);
+ State.Expects = FDRState::Token::NEW_CPU_ID_RECORD;
+ return Error::success();
+}
+
/// State transition when a CustomEventMarker is encountered.
Error processCustomEventMarker(FDRState &State, uint8_t RecordFirstByte,
DataExtractor &RecordExtractor,
/// Beginning with Version 2 of the FDR log, we do not depend on the size of the
/// buffer, but rather use the extents to determine how far to read in the log
/// for this particular buffer.
+///
+/// In Version 3, FDR log now includes a pid metadata record after
+/// WallTimeMarker
Error processFDRMetadataRecord(FDRState &State, uint8_t RecordFirstByte,
DataExtractor &RecordExtractor,
size_t &RecordSize,
if (auto E =
processFDRWallTimeRecord(State, RecordFirstByte, RecordExtractor))
return E;
+ // In Version 3 and and above, a PidRecord is expected after WallTimeRecord
+ if (Version >= 3)
+ State.Expects = FDRState::Token::PID_RECORD;
break;
case 5: // CustomEventMarker
if (auto E = processCustomEventMarker(State, RecordFirstByte,
if (auto E = processBufferExtents(State, RecordFirstByte, RecordExtractor))
return E;
break;
+ case 9: // Pid
+ if (auto E = processFDRPidRecord(State, RecordFirstByte, RecordExtractor))
+ return E;
+ break;
default:
// Widen the record type to uint16_t to prevent conversion to char.
return make_error<StringError>(
return make_error<StringError>(
"Malformed log. Received Function Record when expecting wallclock.",
std::make_error_code(std::errc::executable_format_error));
+ case FDRState::Token::PID_RECORD:
+ return make_error<StringError>(
+ "Malformed log. Received Function Record when expecting pid.",
+ std::make_error_code(std::errc::executable_format_error));
case FDRState::Token::NEW_CPU_ID_RECORD:
return make_error<StringError>(
"Malformed log. Received Function Record before first CPU record.",
}
Record.CPU = State.CPUId;
Record.TId = State.ThreadId;
+ Record.PId = State.ProcessId;
// Back up to read first 32 bits, including the 4 we pulled RecordType
// and RecordKind out of. The remaining 28 are FunctionId.
uint32_t OffsetPtr = 0;
/// BufSize: 8 byte unsigned integer indicating how large the buffer is.
/// NewBuffer: 16 byte metadata record with Thread Id.
/// WallClockTime: 16 byte metadata record with human readable time.
+/// Pid: 16 byte metadata record with Pid
/// NewCPUId: 16 byte metadata record with CPUId and a 64 bit TSC reading.
/// EOB: 16 byte record in a thread buffer plus mem garbage to fill BufSize.
/// FunctionSequence: NewCPUId | TSCWrap | FunctionRecord
/// BufferExtents: 16 byte metdata record describing how many usable bytes are
/// in the buffer. This is measured from the start of the buffer
/// and must always be at least 48 (bytes).
+///
+/// In Version 3, we make the following changes:
+///
+/// ThreadBuffer: BufferExtents NewBuffer WallClockTime Pid NewCPUId
+/// FunctionSequence
/// EOB: *deprecated*
Error loadFDRLog(StringRef Data, XRayFileHeader &FileHeader,
std::vector<XRayRecord> &Records) {
InitialExpectation = FDRState::Token::NEW_BUFFER_RECORD_OR_EOF;
break;
case 2:
+ case 3:
InitialExpectation = FDRState::Token::BUFFER_EXTENTS;
break;
default:
Twine("Unsupported version '") + Twine(FileHeader.Version) + "'",
std::make_error_code(std::errc::executable_format_error));
}
- FDRState State{0, 0, 0, InitialExpectation, BufferSize, 0};
+ FDRState State{0, 0, 0, 0, InitialExpectation, BufferSize, 0};
// RecordSize will tell the loop how far to seek ahead based on the record
// type that we have just read.
if (!isBufferExtents)
State.CurrentBufferConsumed += RecordSize;
assert(State.CurrentBufferConsumed <= State.CurrentBufferSize);
- if (FileHeader.Version == 2 &&
+ if ((FileHeader.Version == 2 || FileHeader.Version == 3) &&
State.CurrentBufferSize == State.CurrentBufferConsumed) {
// In Version 2 of the log, we don't need to scan to the end of the thread
// buffer if we've already consumed all the bytes we need to.
Records.clear();
std::transform(Trace.Records.begin(), Trace.Records.end(),
std::back_inserter(Records), [&](const YAMLXRayRecord &R) {
- return XRayRecord{R.RecordType, R.CPU, R.Type, R.FuncId,
- R.TSC, R.TId, R.CallArgs};
+ return XRayRecord{R.RecordType, R.CPU, R.Type, R.FuncId,
+ R.TSC, R.TId, R.PId, R.CallArgs};
});
return Error::success();
}
Trace T;
switch (Type) {
case NAIVE_FORMAT:
- if (Version == 1 || Version == 2) {
+ if (Version == 1 || Version == 2 || Version == 3) {
if (auto E = loadNaiveFormatLog(Data, T.FileHeader, T.Records))
return std::move(E);
} else {
}
break;
case FLIGHT_DATA_RECORDER_FORMAT:
- if (Version == 1 || Version == 2) {
+ if (Version == 1 || Version == 2 || Version == 3) {
if (auto E = loadFDRLog(Data, T.FileHeader, T.Records))
return std::move(E);
} else {
--- /dev/null
+; RUN: llvm-xray convert %S/Inputs/basic-log-arg1-version-3.xray -f=yaml -o - | FileCheck %s
+
+; CHECK: ---
+; CHECK-NEXT: header:
+; CHECK-NEXT: version: 3
+; CHECK-NEXT: type: 0
+; CHECK-NEXT: constant-tsc: true
+; CHECK-NEXT: nonstop-tsc: true
+; CHECK-NEXT: cycle-frequency: 3900000000
+; CHECK-NEXT: records:
+; CHECK-NEXT: - { type: 0, func-id: 3, function: '3', cpu: 0, thread: 2590, process: 2590, kind: function-enter, tsc: 2033303630902004 }
+; CHECK-NEXT: - { type: 0, func-id: 3, function: '3', cpu: 0, thread: 2590, process: 2590, kind: function-exit, tsc: 2033403115246844 }
+; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 0, thread: 2590, process: 2590, kind: function-enter, tsc: 2033490200702516 }
+; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 0, thread: 2590, process: 2590, kind: function-exit, tsc: 2033504122687120 }
+; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', args: [ 67 ], cpu: 0, thread: 2590, process: 2590, kind: function-enter-arg, tsc: 2033505343905936 }
+; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 0, thread: 2590, process: 2590, kind: function-exit, tsc: 2033505343936752 }
+; CHECK-NEXT: ...
--- /dev/null
+; RUN: llvm-xray convert %S/Inputs/basic-log-version-3.xray -f=yaml -o - | FileCheck %s
+
+; CHECK: ---
+; CHECK-NEXT: header:
+; CHECK-NEXT: version: 3
+; CHECK-NEXT: type: 0
+; CHECK-NEXT: constant-tsc: true
+; CHECK-NEXT: nonstop-tsc: true
+; CHECK-NEXT: cycle-frequency: 3900000000
+; CHECK-NEXT: records:
+; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 7, thread: 25518, process: 25518, kind: function-enter, tsc: 2070767347414784 }
+; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 7, thread: 25518, process: 25518, kind: function-exit, tsc: 2070767347496472 }
+; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 7, thread: 25518, process: 25518, kind: function-enter, tsc: 2070768324320264 }
+; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 7, thread: 25518, process: 25518, kind: function-exit, tsc: 2070768324344100 }
+; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 7, thread: 25518, process: 25518, kind: function-enter, tsc: 2070768921602152 }
+; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 7, thread: 25518, process: 25518, kind: function-exit, tsc: 2070768921625968 }
+; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 7, thread: 25518, process: 25518, kind: function-enter, tsc: 2070769627174140 }
+; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 7, thread: 25518, process: 25518, kind: function-exit, tsc: 2070769627197624 }
+; CHECK-NEXT: ...
--- /dev/null
+; RUN: llvm-xray convert %S/Inputs/fdr-log-arg1-version-3.xray -f=yaml -o - | FileCheck %s
+
+; CHECK: ---
+; CHECK-NEXT: header:
+; CHECK-NEXT: version: 3
+; CHECK-NEXT: type: 1
+; CHECK-NEXT: constant-tsc: true
+; CHECK-NEXT: nonstop-tsc: true
+; CHECK-NEXT: cycle-frequency: 3900000000
+; CHECK-NEXT: records:
+; CHECK-NEXT: - { type: 0, func-id: 3, function: '3', cpu: 6, thread: 2631, process: 2631, kind: function-enter, tsc: 2034042117104344 }
+; CHECK-NEXT: - { type: 0, func-id: 3, function: '3', cpu: 6, thread: 2631, process: 2631, kind: function-exit, tsc: 2034042117199088 }
+; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 6, thread: 2631, process: 2631, kind: function-enter, tsc: 2034043145686378 }
+; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 6, thread: 2631, process: 2631, kind: function-exit, tsc: 2034043145762200 }
+; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', args: [ 67 ], cpu: 6, thread: 2631, process: 2631, kind: function-enter-arg, tsc: 2034049739853430 }
+; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 6, thread: 2631, process: 2631, kind: function-exit, tsc: 2034049739878154 }
+; CHECK-NEXT: ...
--- /dev/null
+; RUN: llvm-xray convert %S/Inputs/fdr-log-version-3.xray -f=yaml -o - | FileCheck %s
+
+; CHECK: ---
+; CHECK-NEXT: header:
+; CHECK-NEXT: version: 3
+; CHECK-NEXT: type: 1
+; CHECK-NEXT: constant-tsc: true
+; CHECK-NEXT: nonstop-tsc: true
+; CHECK-NEXT: cycle-frequency: 3900000000
+; CHECK-NEXT: records:
+; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 4, thread: 25190, process: 25190, kind: function-enter, tsc: 2069294857657498 }
+; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 4, thread: 25190, process: 25190, kind: function-exit, tsc: 2069294857707502 }
+; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 4, thread: 25190, process: 25190, kind: function-enter, tsc: 2069295590705912 }
+; CHECK-NEXT: - { type: 0, func-id: 2, function: '2', cpu: 4, thread: 25190, process: 25190, kind: function-exit, tsc: 2069295590734308 }
+; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 4, thread: 25190, process: 25190, kind: function-enter, tsc: 2069296377598128 }
+; CHECK-NEXT: - { type: 0, func-id: 1, function: '1', cpu: 4, thread: 25190, process: 25190, kind: function-exit, tsc: 2069296377627032 }
+; CHECK-NEXT: ...
errs()
<< "Error processing record: "
<< llvm::formatv(
- R"({{type: {0}; cpu: {1}; record-type: {2}; function-id: {3}; tsc: {4}; thread-id: {5}}})",
+ R"({{type: {0}; cpu: {1}; record-type: {2}; function-id: {3}; tsc: {4}; thread-id: {5}; process-id: {6}}})",
Record.RecordType, Record.CPU, Record.Type, Record.FuncId,
- Record.TId)
+ Record.TSC, Record.TId, Record.PId)
<< '\n';
for (const auto &ThreadStack : FCA.getPerThreadFunctionStack()) {
errs() << "Thread ID: " << ThreadStack.first << "\n";
Trace.Records.push_back({R.RecordType, R.CPU, R.Type, R.FuncId,
Symbolize ? FuncIdHelper.SymbolOrNumber(R.FuncId)
: llvm::to_string(R.FuncId),
- R.TSC, R.TId, R.CallArgs});
+ R.TSC, R.TId, R.PId, R.CallArgs});
}
Output Out(OS, nullptr, 0);
Out << Trace;
Writer.write(R.FuncId);
Writer.write(R.TSC);
Writer.write(R.TId);
- Writer.write(Padding4B);
+
+ if (FH.Version >= 3)
+ Writer.write(R.PId);
+ else
+ Writer.write(Padding4B);
+
Writer.write(Padding4B);
Writer.write(Padding4B);
}
return CurrentStack;
}
-void writeTraceViewerRecord(raw_ostream &OS, int32_t FuncId, uint32_t TId,
- bool Symbolize,
+void writeTraceViewerRecord(uint16_t Version, raw_ostream &OS, int32_t FuncId,
+ uint32_t TId, uint32_t PId, bool Symbolize,
const FuncIdConversionHelper &FuncIdHelper,
double EventTimestampUs,
const StackTrieNode &StackCursor,
StringRef FunctionPhenotype) {
OS << " ";
- OS << llvm::formatv(
- R"({ "name" : "{0}", "ph" : "{1}", "tid" : "{2}", "pid" : "1", )"
- R"("ts" : "{3:f3}", "sf" : "{4}" })",
- (Symbolize ? FuncIdHelper.SymbolOrNumber(FuncId)
- : llvm::to_string(FuncId)),
- FunctionPhenotype, TId, EventTimestampUs, StackCursor.ExtraData.id);
+ if (Version >= 3) {
+ OS << llvm::formatv(
+ R"({ "name" : "{0}", "ph" : "{1}", "tid" : "{2}", "pid" : "{3}", )"
+ R"("ts" : "{4:f4}", "sf" : "{5}" })",
+ (Symbolize ? FuncIdHelper.SymbolOrNumber(FuncId)
+ : llvm::to_string(FuncId)),
+ FunctionPhenotype, TId, PId, EventTimestampUs,
+ StackCursor.ExtraData.id);
+ } else {
+ OS << llvm::formatv(
+ R"({ "name" : "{0}", "ph" : "{1}", "tid" : "{2}", "pid" : "1", )"
+ R"("ts" : "{3:f3}", "sf" : "{4}" })",
+ (Symbolize ? FuncIdHelper.SymbolOrNumber(FuncId)
+ : llvm::to_string(FuncId)),
+ FunctionPhenotype, TId, EventTimestampUs, StackCursor.ExtraData.id);
+ }
}
} // namespace
void TraceConverter::exportAsChromeTraceEventFormat(const Trace &Records,
raw_ostream &OS) {
const auto &FH = Records.getFileHeader();
+ auto Version = FH.Version;
auto CycleFreq = FH.CycleFrequency;
unsigned id_counter = 0;
StackRootsByThreadId, StacksByStackId,
&id_counter, NodeStore);
// Each record is represented as a json dictionary with function name,
- // type of B for begin or E for end, thread id, process id (faked),
+ // type of B for begin or E for end, thread id, process id,
// timestamp in microseconds, and a stack frame id. The ids are logged
// in an id dictionary after the events.
- writeTraceViewerRecord(OS, R.FuncId, R.TId, Symbolize, FuncIdHelper,
- EventTimestampUs, *StackCursor, "B");
+ writeTraceViewerRecord(Version, OS, R.FuncId, R.TId, R.PId, Symbolize,
+ FuncIdHelper, EventTimestampUs, *StackCursor, "B");
break;
case RecordTypes::EXIT:
case RecordTypes::TAIL_EXIT:
// (And/Or in loop termination below)
StackTrieNode *PreviousCursor = nullptr;
do {
- writeTraceViewerRecord(OS, StackCursor->FuncId, R.TId, Symbolize,
- FuncIdHelper, EventTimestampUs, *StackCursor,
- "E");
+ writeTraceViewerRecord(Version, OS, StackCursor->FuncId, R.TId, R.PId,
+ Symbolize, FuncIdHelper, EventTimestampUs,
+ *StackCursor, "E");
PreviousCursor = StackCursor;
StackCursor = StackCursor->Parent;
} while (PreviousCursor->FuncId != R.FuncId && StackCursor != nullptr);