Unverified Commit 8f46a9db authored by Amir Ayupov's avatar Amir Ayupov Committed by GitHub
Browse files

[llvm-profgen] Support [buildid:]0xaddr format in perfscript input (#190863)

Add support for optional build ID prefix in perfscript addresses,
following the format buildid:0xhexaddr. This enables multi-DSO profiling
with a single input file: each address optionally carries a build ID,
and profgen filters addresses by matching the binary's build ID.

--filter-build-id=<hex> CLI option overrides auto-detected build ID.
parent ff91bf82
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
2
	          aabb1122:4005dc
	          aabb1122:400634
	          aabb1122:400684
	    7f68c5788793
 aabb1122:0x4005c8/aabb1122:0x4005dc aabb1122:0x40062f/aabb1122:0x4005b0 aabb1122:0x400645/aabb1122:0x4005ff aabb1122:0x400637/aabb1122:0x400645 aabb1122:0x4005e9/aabb1122:0x400634 aabb1122:0x4005d7/aabb1122:0x4005e5 aabb1122:0x40062f/aabb1122:0x4005b0 aabb1122:0x400645/aabb1122:0x4005ff aabb1122:0x400637/aabb1122:0x400645 aabb1122:0x4005e9/aabb1122:0x400634 aabb1122:0x4005d7/aabb1122:0x4005e5 aabb1122:0x40062f/aabb1122:0x4005b0 aabb1122:0x400645/aabb1122:0x4005ff aabb1122:0x400637/aabb1122:0x400645 aabb1122:0x4005e9/aabb1122:0x400634 aabb1122:0x4005c8/aabb1122:0x4005dc
2
	          aabb1122:4005b0
	          aabb1122:400684
	    7f68c5788793
 aabb1122:0x40062f/aabb1122:0x4005b0 aabb1122:0x400645/aabb1122:0x4005ff aabb1122:0x400637/aabb1122:0x400645 aabb1122:0x4005e9/aabb1122:0x400634 aabb1122:0x4005c8/aabb1122:0x4005dc aabb1122:0x40062f/aabb1122:0x4005b0 aabb1122:0x400645/aabb1122:0x4005ff aabb1122:0x400637/aabb1122:0x400645 aabb1122:0x4005e9/aabb1122:0x400634 aabb1122:0x4005d7/aabb1122:0x4005e5 aabb1122:0x40062f/aabb1122:0x4005b0 aabb1122:0x400645/aabb1122:0x4005ff aabb1122:0x400637/aabb1122:0x400645 aabb1122:0x4005e9/aabb1122:0x400634 aabb1122:0x4005d7/aabb1122:0x4005e5 aabb1122:0x40062f/aabb1122:0x4005b0
+9 −0
Original line number Diff line number Diff line
	          aabb1122:4005dc
	          aabb1122:400634
	          aabb1122:400684
	    7f68c5788793
 aabb1122:0x4005c8/aabb1122:0x4005dc aabb1122:0x40062f/aabb1122:0x4005b0 aabb1122:0x400645/aabb1122:0x4005ff aabb1122:0x400637/aabb1122:0x400645 aabb1122:0x4005e9/aabb1122:0x400634 aabb1122:0x4005d7/aabb1122:0x4005e5 aabb1122:0x40062f/aabb1122:0x4005b0 aabb1122:0x400645/aabb1122:0x4005ff aabb1122:0x400637/aabb1122:0x400645 aabb1122:0x4005e9/aabb1122:0x400634 aabb1122:0x4005d7/aabb1122:0x4005e5 aabb1122:0x40062f/aabb1122:0x4005b0 aabb1122:0x400645/aabb1122:0x4005ff aabb1122:0x400637/aabb1122:0x400645 aabb1122:0x4005e9/aabb1122:0x400634 aabb1122:0x4005c8/aabb1122:0x4005dc
	          aabb1122:4005b0
	          aabb1122:400684
	    7f68c5788793
 aabb1122:0x40062f/aabb1122:0x4005b0 aabb1122:0x400645/aabb1122:0x4005ff aabb1122:0x400637/aabb1122:0x400645 aabb1122:0x4005e9/aabb1122:0x400634 aabb1122:0x4005c8/aabb1122:0x4005dc aabb1122:0x40062f/aabb1122:0x4005b0 aabb1122:0x400645/aabb1122:0x4005ff aabb1122:0x400637/aabb1122:0x400645 aabb1122:0x4005e9/aabb1122:0x400634 aabb1122:0x4005d7/aabb1122:0x4005e5 aabb1122:0x40062f/aabb1122:0x4005b0 aabb1122:0x400645/aabb1122:0x4005ff aabb1122:0x400637/aabb1122:0x400645 aabb1122:0x4005e9/aabb1122:0x400634 aabb1122:0x4005d7/aabb1122:0x4005e5 aabb1122:0x40062f/aabb1122:0x4005b0
+57 −0
Original line number Diff line number Diff line
; REQUIRES: x86_64-linux
; Test that [buildid:]0xaddr format is correctly parsed in hybrid perfscript
; input. Both callstack frames and LBR entries may carry buildid prefixes.

;; Test 1: Hybrid perfscript with buildid prefix on callstack frames and
;; LBR entries, using --filter-build-id to match "aabb1122".
; RUN: llvm-profgen --format=text --perfscript=%S/Inputs/buildid-cs-noprobe.aggperfscript --binary=%S/Inputs/noinline-cs-noprobe.perfbin --output=%t --skip-symbolization --profile-summary-cold-count=0 --filter-build-id=aabb1122
; RUN: FileCheck %s --input-file %t --check-prefix=CHECK-HYBRID

; CHECK-HYBRID:      [foo]
; CHECK-HYBRID-NEXT:   3
; CHECK-HYBRID-NEXT:   5ff-62f:6
; CHECK-HYBRID-NEXT:   634-637:6
; CHECK-HYBRID-NEXT:   645-645:6
; CHECK-HYBRID-NEXT:   3
; CHECK-HYBRID-NEXT:   62f->5b0:6
; CHECK-HYBRID-NEXT:   637->645:6
; CHECK-HYBRID-NEXT:   645->5ff:6
; CHECK-HYBRID-NEXT: [foo:3 @ bar]
; CHECK-HYBRID-NEXT:   4
; CHECK-HYBRID-NEXT:   5b0-5c8:2
; CHECK-HYBRID-NEXT:   5b0-5d7:4
; CHECK-HYBRID-NEXT:   5dc-5e9:2
; CHECK-HYBRID-NEXT:   5e5-5e9:4
; CHECK-HYBRID-NEXT:   3
; CHECK-HYBRID-NEXT:   5c8->5dc:4
; CHECK-HYBRID-NEXT:   5d7->5e5:4
; CHECK-HYBRID-NEXT:   5e9->634:6

;; Test 2: Non-pre-aggregated perfscript with buildid prefix (no leading count).
; RUN: llvm-profgen --format=text --perfscript=%S/Inputs/buildid-cs-noprobe.perfscript --binary=%S/Inputs/noinline-cs-noprobe.perfbin --output=%t2 --skip-symbolization --profile-summary-cold-count=0 --filter-build-id=aabb1122
; RUN: FileCheck %s --input-file %t2 --check-prefix=CHECK-NOAGG

; CHECK-NOAGG:      [foo]
; CHECK-NOAGG-NEXT:   3
; CHECK-NOAGG-NEXT:   5ff-62f:3
; CHECK-NOAGG-NEXT:   634-637:3
; CHECK-NOAGG-NEXT:   645-645:3
; CHECK-NOAGG-NEXT:   3
; CHECK-NOAGG-NEXT:   62f->5b0:3
; CHECK-NOAGG-NEXT:   637->645:3
; CHECK-NOAGG-NEXT:   645->5ff:3
; CHECK-NOAGG-NEXT: [foo:3 @ bar]
; CHECK-NOAGG-NEXT:   4
; CHECK-NOAGG-NEXT:   5b0-5c8:1
; CHECK-NOAGG-NEXT:   5b0-5d7:2
; CHECK-NOAGG-NEXT:   5dc-5e9:1
; CHECK-NOAGG-NEXT:   5e5-5e9:2
; CHECK-NOAGG-NEXT:   3
; CHECK-NOAGG-NEXT:   5c8->5dc:2
; CHECK-NOAGG-NEXT:   5d7->5e5:2
; CHECK-NOAGG-NEXT:   5e9->634:3

;; Test 3: With non-matching filter, callstack frames are filtered out,
;; resulting in no samples.
; RUN: llvm-profgen --format=text --perfscript=%S/Inputs/buildid-cs-noprobe.aggperfscript --binary=%S/Inputs/noinline-cs-noprobe.perfbin --output=%t3 --skip-symbolization --filter-build-id=ccdd3344 2>&1 | FileCheck %s --check-prefix=CHECK-NOMATCH
; CHECK-NOMATCH: warning: No samples in perf script!
+58 −13
Original line number Diff line number Diff line
@@ -69,6 +69,13 @@ cl::opt<bool> TimeProfGen("time-profgen", cl::desc("Time llvm-profgen phases"),
static const char *TimerGroupName = "profgen";
static const char *TimerGroupDesc = "llvm-profgen";

static cl::opt<std::string> FilterBuildID(
    "filter-build-id",
    cl::desc("Override auto-detected build ID for filtering perfscript "
             "addresses in [buildid:]addr format. When set, only addresses "
             "with a matching build ID prefix are kept."),
    cl::cat(ProfGenCategory));

namespace sampleprof {

void VirtualUnwinder::unwindCall(UnwindState &State) {
@@ -665,12 +672,37 @@ void HybridPerfReader::unwindSamples() {
}

/// Parse a hex address from \p Str.
static bool parseAddress(StringRef Str, uint64_t &Addr, bool HasPrefix) {
/// Parse an optional [buildid:] prefix into \p BuildID.
static bool parseAddress(StringRef Str, uint64_t &Addr, bool HasPrefix,
                         StringRef &BuildID) {
  size_t ColonPos = Str.find(':');
  if (ColonPos != StringRef::npos) {
    BuildID = Str.substr(0, ColonPos);
    Str = Str.substr(ColonPos + 1);
  }
  if (Str.consume_front("0x") != HasPrefix)
    return true;
  return Str.getAsInteger(16, Addr);
}

/// Return the build ID to use for filtering perfscript addresses.
/// If --filter-build-id is specified, use it as an override (with a warning
/// if it doesn't match the binary's auto-detected build ID).
/// Otherwise, use the auto-detected value from the binary.
static StringRef getFilterBuildID(const ProfiledBinary *Binary) {
  StringRef BinaryBuildID = Binary->getFilterBuildID();
  if (FilterBuildID.getNumOccurrences() == 0)
    return BinaryBuildID;
  static bool Warned = false;
  if (!Warned && !BinaryBuildID.empty() && FilterBuildID != BinaryBuildID) {
    WithColor::warning() << "--filter-build-id=" << FilterBuildID
                         << " does not match binary build ID " << BinaryBuildID
                         << "\n";
    Warned = true;
  }
  return FilterBuildID;
}

bool PerfScriptReader::extractLBRStack(TraceStream &TraceIt,
                                       SmallVectorImpl<LBREntry> &LBRStack) {
  // The raw format of LBR stack is like:
@@ -688,8 +720,9 @@ bool PerfScriptReader::extractLBRStack(TraceStream &TraceIt,
  // Skip the leading instruction pointer.
  size_t Index = 0;
  uint64_t LeadingAddr;
  StringRef LeadingBuildID;
  if (!Records.empty() && !Records[0].contains('/')) {
    if (parseAddress(Records[0], LeadingAddr, false)) {
    if (parseAddress(Records[0], LeadingAddr, false, LeadingBuildID)) {
      WarnInvalidLBR(TraceIt);
      TraceIt.advance();
      return false;
@@ -709,10 +742,12 @@ bool PerfScriptReader::extractLBRStack(TraceStream &TraceIt,
    Token.split(Addresses, "/");
    uint64_t Src;
    uint64_t Dst;
    StringRef SrcBuildID, DstBuildID;

    // Stop at broken LBR records.
    if (Addresses.size() < 2 || parseAddress(Addresses[0], Src, true) ||
        parseAddress(Addresses[1], Dst, true)) {
    if (Addresses.size() < 2 ||
        parseAddress(Addresses[0], Src, true, SrcBuildID) ||
        parseAddress(Addresses[1], Dst, true, DstBuildID)) {
      WarnInvalidLBR(TraceIt);
      break;
    }
@@ -720,8 +755,12 @@ bool PerfScriptReader::extractLBRStack(TraceStream &TraceIt,
    // Canonicalize to use preferred load address as base address.
    Src = Binary->canonicalizeVirtualAddress(Src);
    Dst = Binary->canonicalizeVirtualAddress(Dst);
    bool SrcIsInternal = Binary->addressIsCode(Src);
    bool DstIsInternal = Binary->addressIsCode(Dst);
    // Filter by build ID.
    StringRef BinaryBuildID = getFilterBuildID(Binary);
    bool SrcIsInternal =
        SrcBuildID == BinaryBuildID && Binary->addressIsCode(Src);
    bool DstIsInternal =
        DstBuildID == BinaryBuildID && Binary->addressIsCode(Dst);
    if (!SrcIsInternal)
      Src = ExternalAddr;
    if (!DstIsInternal)
@@ -739,16 +778,17 @@ bool PerfScriptReader::extractLBRStack(TraceStream &TraceIt,
bool PerfScriptReader::extractCallstack(TraceStream &TraceIt,
                                        SmallVectorImpl<uint64_t> &CallStack) {
  // The raw format of call stack is like:
  //            4005dc      # leaf frame
  //            4005dc               # leaf frame (no buildid)
  //	          400634
  //	          400684      # root frame
  //	          deadbeef:400684      # root frame (with buildid prefix)
  // It's in bottom-up order with each frame in one line.

  // Extract stack frames from sample
  while (!TraceIt.isAtEoF() && !isLBRSample(TraceIt.getCurrentLine(), true)) {
    StringRef FrameStr = TraceIt.getCurrentLine().ltrim();
    uint64_t FrameAddr = 0;
    if (parseAddress(FrameStr, FrameAddr, false)) {
    StringRef FrameBuildID;
    if (parseAddress(FrameStr, FrameAddr, false, FrameBuildID)) {
      // We might parse a non-perf sample line like empty line and comments,
      // skip it
      TraceIt.advance();
@@ -758,7 +798,9 @@ bool PerfScriptReader::extractCallstack(TraceStream &TraceIt,

    FrameAddr = Binary->canonicalizeVirtualAddress(FrameAddr);
    // Currently intermixed frame from different binaries is not supported.
    if (!Binary->addressIsCode(FrameAddr)) {
    bool IsExternal = FrameBuildID != getFilterBuildID(Binary) ||
                      !Binary->addressIsCode(FrameAddr);
    if (IsExternal) {
      if (CallStack.empty())
        NumLeafExternalFrame++;
      // Push a special value(ExternalAddr) for the external frames so that
@@ -1171,7 +1213,7 @@ void PerfScriptReader::parseAndAggregateTrace() {
// A LBR sample is like:
// 40062f 0x5c6313f/0x5c63170/P/-/-/0  0x5c630e7/0x5c63130/P/-/-/0 ...
// A heuristic for fast detection by checking whether a
// leading "  0x" and the '/' exist.
// leading "  0x" or " buildid:0x" and the '/' exist.
bool PerfScriptReader::isLBRSample(StringRef Line, bool CheckLineStart) {
  // Skip the leading instruction pointer
  SmallVector<StringRef, 32> Records;
@@ -1180,7 +1222,8 @@ bool PerfScriptReader::isLBRSample(StringRef Line, bool CheckLineStart) {
  Line.split(Records, " ", 2, CheckLineStart);
  if (Records.size() < 2)
    return false;
  if (Records[1].starts_with("0x") && Records[1].contains('/'))
  if ((Records[1].starts_with("0x") || Records[1].contains(":0x")) &&
      Records[1].contains('/'))
    return true;
  return false;
}
@@ -1218,8 +1261,10 @@ PerfContent PerfScriptReader::checkPerfScriptType(StringRef FileName) {

    // Detect sample with call stack
    int32_t Count = 0;
    StringRef FrameBuildId;
    while (!TraceIt.isAtEoF() &&
           !parseAddress(TraceIt.getCurrentLine().ltrim(), FrameAddr, false)) {
           !parseAddress(TraceIt.getCurrentLine().ltrim(), FrameAddr, false,
                         FrameBuildId)) {
      Count++;
      TraceIt.advance();
    }