Skip to content

Commit d9ddf43

Browse files
committed
Add API for tracing regions.
1 parent 1d48f20 commit d9ddf43

3 files changed

Lines changed: 159 additions & 1 deletion

File tree

lib/CppInterOp/Tracing.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "llvm/Support/FileSystem.h"
1414
#include "llvm/Support/ManagedStatic.h"
1515
#include "llvm/Support/Path.h"
16+
#include "llvm/Support/Process.h"
1617
#include "llvm/Support/raw_ostream.h"
1718

1819
#include <cassert>
@@ -68,5 +69,45 @@ std::string TraceInfo::writeToFile(const std::string& Version) {
6869
return std::string(Path);
6970
}
7071

72+
std::string TraceInfo::StartRegion() {
73+
m_RegionStart = m_Log.size();
74+
m_InRegion = true;
75+
76+
llvm::SmallString<128> TmpDir;
77+
llvm::sys::path::system_temp_directory(/*ErasedOnReboot=*/true, TmpDir);
78+
llvm::SmallString<128> Path;
79+
llvm::sys::path::append(Path, TmpDir, "cppinterop-reproducer-%%%%%%.cpp");
80+
int FD;
81+
std::error_code EC = llvm::sys::fs::createUniqueFile(Path, FD, Path);
82+
if (EC)
83+
return "";
84+
llvm::sys::Process::SafelyCloseFileDescriptor(FD);
85+
m_RegionPath = std::string(Path);
86+
return m_RegionPath;
87+
}
88+
89+
void TraceInfo::StopRegion(const std::string& Version) {
90+
if (!m_InRegion)
91+
return;
92+
m_InRegion = false;
93+
94+
std::error_code EC;
95+
llvm::raw_fd_ostream OS(m_RegionPath, EC);
96+
if (EC)
97+
return;
98+
99+
OS << "// CppInterOp trace region\n";
100+
std::string Ver = Version.empty() ? CppImpl::GetVersion() : Version;
101+
WriteVersionComment(OS, Ver);
102+
OS << "// Generated automatically.\n";
103+
OS << "#include <CppInterOp/CppInterOp.h>\n\n";
104+
OS << "void reproducer() {\n";
105+
for (size_t i = m_RegionStart; i < m_Log.size(); ++i)
106+
OS << m_Log[i] << "\n";
107+
OS << "}\n\n";
108+
OS << "int main() { reproducer(); return 0; }\n";
109+
OS.flush();
110+
}
111+
71112
} // namespace Tracing
72113
} // namespace CppInterOp

lib/CppInterOp/Tracing.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ class TraceInfo {
5050
unsigned m_VarCount = 0;
5151

5252
std::vector<std::string> m_Log;
53+
size_t m_RegionStart = 0; ///< Log index where current region began.
54+
bool m_InRegion = false; ///< True between StartTracing/StopTracing.
5355

5456
public:
5557
TraceInfo() : m_TG("CppInterOp", "CppInterOp Timing Report") {}
@@ -110,6 +112,16 @@ class TraceInfo {
110112
/// \param Version optional version string embedded as comments.
111113
CPPINTEROP_TRACE_API std::string writeToFile(const std::string& Version = "");
112114

115+
/// Begin a traced region. Returns the path where StopTracing will write.
116+
CPPINTEROP_TRACE_API std::string StartRegion();
117+
118+
/// End the traced region and write only the region's entries to the file.
119+
CPPINTEROP_TRACE_API void StopRegion(const std::string& Version = "");
120+
121+
private:
122+
std::string m_RegionPath;
123+
124+
public:
113125
void clear() {
114126
// Stop any running timers before clearing to avoid triggering
115127
// TimerGroup's destructor report.

unittests/CppInterOp/TracingTests.cpp

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,7 @@ TEST_F(TracingTest, LogEntriesIncludeTimingAnnotation) {
699699
}
700700

701701
// ---------------------------------------------------------------------------
702-
// Tests: all CPPINTEROP_API functions must have INTEROP_TRACE
702+
// Tests: StartTracing / StopTracing region-based tracing
703703
// ---------------------------------------------------------------------------
704704

705705
#ifndef EMSCRIPTEN
@@ -709,6 +709,111 @@ static std::string ReadFileToString(const std::string& path) {
709709
std::istreambuf_iterator<char>()};
710710
}
711711
// This test reads source files from the build tree via CPPINTEROP_DIR.
712+
TEST_F(TracingTest, StartStopTracingWritesToFile) {
713+
// StartTracing begins recording; StopTracing writes the file.
714+
std::string Path = CppInterOp::Tracing::StartTracing();
715+
ASSERT_FALSE(Path.empty());
716+
717+
// Calls within the region are recorded.
718+
VoidFunc();
719+
AnnotatedFunction(5);
720+
721+
CppInterOp::Tracing::StopTracing();
722+
723+
// The file should exist and contain the traced calls.
724+
std::string content = ReadFileToString(Path);
725+
ASSERT_FALSE(content.empty());
726+
EXPECT_THAT(content, HasSubstr("Cpp::VoidFunc()"));
727+
EXPECT_THAT(content, HasSubstr("Cpp::AnnotatedFunction("));
728+
EXPECT_THAT(content, HasSubstr("#include <CppInterOp/CppInterOp.h>"));
729+
730+
llvm::sys::fs::remove(Path);
731+
}
732+
733+
TEST_F(TracingTest, OnlyRegionCallsAreRecorded) {
734+
// Calls before StartTracing should not appear.
735+
VoidFunc();
736+
737+
std::string Path = CppInterOp::Tracing::StartTracing();
738+
ASSERT_FALSE(Path.empty());
739+
740+
AnnotatedFunction(42);
741+
742+
CppInterOp::Tracing::StopTracing();
743+
744+
// Calls after StopTracing should not appear either.
745+
VoidFunc();
746+
747+
std::string content = ReadFileToString(Path);
748+
// Should contain only the call within the region.
749+
EXPECT_THAT(content, HasSubstr("Cpp::AnnotatedFunction(42)"));
750+
// VoidFunc was called before and after — should NOT be in the file.
751+
// Count occurrences of VoidFunc in the reproducer body.
752+
auto bodyStart = content.find("void reproducer()");
753+
ASSERT_NE(bodyStart, std::string::npos);
754+
std::string body = content.substr(bodyStart);
755+
EXPECT_EQ(body.find("VoidFunc"), std::string::npos)
756+
<< "VoidFunc should not appear in the reproducer body:\n"
757+
<< body;
758+
759+
llvm::sys::fs::remove(Path);
760+
}
761+
762+
TEST_F(TracingTest, StartTracingWithEnvVarNarrowsToRegion) {
763+
// When CPPINTEROP_LOG=1 is set (tracing already active from SetUp),
764+
// StartTracing/StopTracing should still narrow to just the region.
765+
ASSERT_NE(TraceInfo::TheTraceInfo, nullptr);
766+
767+
// This call is before the region.
768+
VoidFunc();
769+
770+
std::string Path = CppInterOp::Tracing::StartTracing();
771+
ASSERT_FALSE(Path.empty());
772+
773+
AnnotatedFunction(7);
774+
775+
CppInterOp::Tracing::StopTracing();
776+
777+
std::string content = ReadFileToString(Path);
778+
auto bodyStart = content.find("void reproducer()");
779+
ASSERT_NE(bodyStart, std::string::npos);
780+
std::string body = content.substr(bodyStart);
781+
EXPECT_THAT(body, HasSubstr("Cpp::AnnotatedFunction(7)"));
782+
EXPECT_EQ(body.find("VoidFunc"), std::string::npos);
783+
784+
llvm::sys::fs::remove(Path);
785+
}
786+
787+
TEST_F(TracingTest, MultipleStartStopRegions) {
788+
// Multiple regions should each produce their own file.
789+
std::string Path1 = CppInterOp::Tracing::StartTracing();
790+
AnnotatedFunction(1);
791+
CppInterOp::Tracing::StopTracing();
792+
793+
std::string Path2 = CppInterOp::Tracing::StartTracing();
794+
AnnotatedFunction(2);
795+
CppInterOp::Tracing::StopTracing();
796+
797+
ASSERT_NE(Path1, Path2);
798+
799+
std::string content1 = ReadFileToString(Path1);
800+
std::string content2 = ReadFileToString(Path2);
801+
802+
EXPECT_THAT(content1, HasSubstr("Cpp::AnnotatedFunction(1)"));
803+
EXPECT_THAT(content2, HasSubstr("Cpp::AnnotatedFunction(2)"));
804+
805+
// Each file should only have its own call.
806+
EXPECT_EQ(content1.find("AnnotatedFunction(2)"), std::string::npos);
807+
EXPECT_EQ(content2.find("AnnotatedFunction(1)"), std::string::npos);
808+
809+
llvm::sys::fs::remove(Path1);
810+
llvm::sys::fs::remove(Path2);
811+
}
812+
813+
// ---------------------------------------------------------------------------
814+
// Tests: all CPPINTEROP_API functions must have INTEROP_TRACE
815+
// ---------------------------------------------------------------------------
816+
712817
TEST(TracingCoverageTest, AllPublicAPIsAreTraced) {
713818
// 1. Parse the header to extract all CPPINTEROP_API function names.
714819
std::string Header =

0 commit comments

Comments
 (0)