CCTLib
Calling-context and data-object attribution library for Intel Pin
loadspy_integration_test.cpp
Go to the documentation of this file.
1 // GoogleTest-driven integration tests for loadspy_client.
2 //
3 // Semantics: loadspy reports "redundant loads" -- a load whose value was
4 // already present in memory (i.e., the same value was already loaded from
5 // or written to that address by the same-thread execution earlier, with
6 // no intervening store that could have changed it).
7 //
8 // Output format is text; primary summary line is
9 // " Total redundant bytes = <pct> %"
10 // (one per thread; thread 0 first).
11 
12 #include <cerrno>
13 #include <cstdio>
14 #include <cstdlib>
15 #include <cstring>
16 #include <fcntl.h>
17 #include <fstream>
18 #include <regex>
19 #include <sstream>
20 #include <string>
21 #include <sys/wait.h>
22 #include <unistd.h>
23 #include <vector>
24 
25 #include <gtest/gtest.h>
26 
27 namespace {
28 
29 std::string env(const char* name) { const char* v = getenv(name); return v ? v : ""; }
30 std::string cctlib_root() { std::string r = env("CCTLIB_ROOT"); return r.empty() ? "../.." : r; }
31 std::string pin_root() { return env("PIN_ROOT"); }
32 
33 int run_pin(const std::string& tool, const std::vector<std::string>& args) {
34  std::string pin = pin_root() + "/pin";
35  std::vector<char*> argv;
36  argv.push_back(const_cast<char*>(pin.c_str()));
37  argv.push_back(const_cast<char*>("-t"));
38  argv.push_back(const_cast<char*>(tool.c_str()));
39  argv.push_back(const_cast<char*>("--"));
40  for (auto& a : args) argv.push_back(const_cast<char*>(a.c_str()));
41  argv.push_back(nullptr);
42 
43  pid_t pid = fork();
44  if (pid < 0) return -1;
45  if (pid == 0) {
46  if (!env("CCTLIB_TEST_VERBOSE").size()) {
47  int devnull = open("/dev/null", O_WRONLY);
48  if (devnull >= 0) { dup2(devnull, 1); dup2(devnull, 2); close(devnull); }
49  }
50  execv(pin.c_str(), argv.data());
51  _exit(127);
52  }
53  int status = 0;
54  if (waitpid(pid, &status, 0) < 0) return -1;
55  if (WIFEXITED(status)) return WEXITSTATUS(status);
56  return -2;
57 }
58 
59 std::string find_newest(const std::string& dir, const std::string& prefix) {
60  std::string cmd = "ls -t " + dir + "/" + prefix + "* 2>/dev/null | head -1";
61  FILE* p = popen(cmd.c_str(), "r");
62  if (!p) return {};
63  char buf[4096]; std::string out;
64  if (fgets(buf, sizeof(buf), p)) out = buf;
65  pclose(p);
66  if (!out.empty() && out.back() == '\n') out.pop_back();
67  return out;
68 }
69 std::string read_file(const std::string& path) {
70  std::ifstream in(path); std::stringstream ss; ss << in.rdbuf(); return ss.str();
71 }
72 void cleanup(const std::string& dir, const std::string& prefix) {
73  std::string cmd = "rm -f " + dir + "/" + prefix + "*"; (void)system(cmd.c_str());
74 }
75 
76 double parse_first_redundant_pct(const std::string& content) {
77  std::regex re(R"(Total redundant bytes = ([\d.]+)\s*%)");
78  std::smatch m;
79  if (!std::regex_search(content, m, re)) return -1;
80  return std::stod(m[1]);
81 }
82 
83 class LoadspyIntegration : public ::testing::Test {
84  protected:
85  std::string root_, tool_;
86  void SetUp() override {
87  root_ = cctlib_root();
88  ASSERT_FALSE(pin_root().empty()) << "PIN_ROOT required";
89  tool_ = root_ + "/clients/obj-intel64/loadspy_client.so";
90  ASSERT_EQ(0, access(tool_.c_str(), F_OK)) << tool_ << " missing";
91  ASSERT_EQ(0, chdir(root_.c_str())) << "chdir failed";
92  }
93  // loadspy writes its output file with prefix "redLoad.out." (a
94  // historical artefact from RedSpy's shared codebase).
95  double run_and_parse_pct(const std::string& victim) {
96  cleanup(root_, "redLoad.out.");
97  int rc = run_pin(tool_, {victim});
98  EXPECT_EQ(0, rc) << "loadspy on " << victim << " returned " << rc;
99  std::string out = find_newest(root_, "redLoad.out.");
100  EXPECT_FALSE(out.empty()) << "no redLoad.out.* file";
102  }
103 };
104 
105 TEST_F(LoadspyIntegration, RunsCleanlyOnLs) {
106  double pct = run_and_parse_pct("/bin/ls");
107  ASSERT_GE(pct, 0.0);
108  EXPECT_LT(pct, 100.0);
109 }
110 
111 // TP has 100000 * 8 = 800KB of workload redundant loads; TN has zero
112 // (the intervening store kills the redundancy).
113 //
114 // Design note on the low threshold: loadspy's percentage is over the
115 // TOTAL load traffic, and libc's own load traffic dominates. Every extra
116 // workload load also grows the denominator, so bigger workload does not
117 // linearly raise the percentage. Empirically TP-TN sits around 0.7-1.5
118 // percentage points; +0.3% is a floor that clearly signals workload but
119 // leaves margin for system variance.
120 TEST_F(LoadspyIntegration, TruePositiveHigherThanTrueNegative) {
121  double tp = run_and_parse_pct(root_ + "/tests/gtest/obj/apps/loadspy_tp_simple");
122  double tn = run_and_parse_pct(root_ + "/tests/gtest/obj/apps/loadspy_tn_simple");
123  ASSERT_GE(tp, 0.0);
124  ASSERT_GE(tn, 0.0);
125  EXPECT_GT(tp, tn + 0.3)
126  << "TP=" << tp << "% TN=" << tn << "%";
127 }
128 
129 // Symbol attribution: the top loadspy report contexts should reference the
130 // victim's load8 function for the TP victim.
131 TEST_F(LoadspyIntegration, TPReportAttributesToLoad8) {
132  cleanup(root_, "redLoad.out.");
133  int rc = run_pin(tool_, {root_ + "/tests/gtest/obj/apps/loadspy_tp_simple"});
134  ASSERT_EQ(0, rc);
135  std::string out = find_newest(root_, "redLoad.out.");
136  ASSERT_FALSE(out.empty());
137  std::string cmd = "grep -c 'load8:.*loadspy_tp_simple.c' " + out;
138  FILE* p = popen(cmd.c_str(), "r");
139  ASSERT_NE(p, nullptr);
140  char buf[64]; std::string s;
141  if (fgets(buf, sizeof(buf), p)) s = buf;
142  pclose(p);
143  long hits = s.empty() ? 0 : std::stol(s);
144  EXPECT_GT(hits, 0);
145 }
146 
147 // ------------------------------------------------------------------
148 // ISA breadth: SIMD loads and rep-movsq. Each victim's workload issues
149 // repeated identical loads from the same address; loadspy should classify
150 // the second (and subsequent) as redundant. Attribution test: the report
151 // mentions the victim's source file for the load context.
152 
154  public ::testing::WithParamInterface<const char*> {};
155 
156 // The workload's redundant-load contribution should elevate the report's
157 // percentage above the TN baseline. Threshold intentionally low because
158 // loadspy denominators include libc load traffic, which is large.
159 TEST_P(LoadspyIsa, ExceedsBaseline) {
160  double tn = run_and_parse_pct(root_ + "/tests/gtest/obj/apps/loadspy_tn_simple");
161  double isa = run_and_parse_pct(root_ + "/tests/gtest/obj/apps/isa/" + std::string(GetParam()));
162  ASSERT_GE(tn, 0.0);
163  ASSERT_GE(isa, 0.0);
164  EXPECT_GT(isa, tn + 0.3)
165  << "victim=" << GetParam() << " isa=" << isa << "% tn=" << tn << "%";
166 }
167 
168 // Attribution: report should reference the victim's source file.
169 TEST_P(LoadspyIsa, AttributionInReport) {
170  cleanup(root_, "redLoad.out.");
171  int rc = run_pin(tool_, {root_ + "/tests/gtest/obj/apps/isa/" + std::string(GetParam())});
172  ASSERT_EQ(0, rc);
173  std::string out = find_newest(root_, "redLoad.out.");
174  ASSERT_FALSE(out.empty());
175  std::string cmd = std::string("grep -c '") + GetParam() + ".c' " + out;
176  FILE* p = popen(cmd.c_str(), "r");
177  ASSERT_NE(p, nullptr);
178  char buf[64]; std::string s;
179  if (fgets(buf, sizeof(buf), p)) s = buf;
180  pclose(p);
181  long hits = s.empty() ? 0 : std::stol(s);
182  EXPECT_GT(hits, 0);
183 }
184 
186  IsaBreadth, LoadspyIsa,
187  ::testing::Values(
188  "loadspy_sse16_tp", // 128-bit SIMD load
189  "loadspy_avx32_tp", // 256-bit SIMD load
190  "loadspy_repmovs_tp", // rep movsq -- string load+store
191  "loadspy_cross_page_tp", // qword load straddling a 4KB page boundary
192  "loadspy_mixed_widths_tp" // 8B load then 2x 4B load covering same 8B
193  ),
194  [](const testing::TestParamInfo<const char*>& info) {
195  return std::string(info.param);
196  });
197 
198 } // namespace
static uint64_t buf[ITERS]
volatile uint8_t status
Definition: cctlib.cpp:230
std::string find_newest(const std::string &dir, const std::string &prefix)
double parse_first_redundant_pct(const std::string &content)
void cleanup(const std::string &dir, const std::string &prefix)
INSTANTIATE_TEST_SUITE_P(IsaBreadth, LoadspyIsa, ::testing::Values("loadspy_sse16_tp", "loadspy_avx32_tp", "loadspy_repmovs_tp", "loadspy_cross_page_tp", "loadspy_mixed_widths_tp"), [](const testing::TestParamInfo< const char * > &info) { return std::string(info.param);})
TEST_F(LoadspyIntegration, TPReportAttributesToLoad8)
int run_pin(const std::string &tool, const std::vector< std::string > &args)
int a[1000]
Definition: testArray.c:5