Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
FpeMonitor.cpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file FpeMonitor.cpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2022-2023 CERN for the benefit of the Acts project
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 
10 
12 
13 #include <algorithm>
14 #include <bitset>
15 #include <cfenv>
16 #include <csignal>
17 #include <cstddef>
18 #include <cstdint>
19 #include <iostream>
20 #include <iterator>
21 #include <memory>
22 #include <mutex>
23 #include <stdexcept>
24 #include <string_view>
25 #include <vector>
26 
27 #include <boost/stacktrace/frame.hpp>
28 #include <boost/stacktrace/safe_dump_to.hpp>
29 #include <boost/stacktrace/stacktrace.hpp>
30 #include <boost/stacktrace/stacktrace_fwd.hpp>
31 
32 #define FPU_EXCEPTION_MASK 0x3f
33 #define FPU_STATUS_FLAGS 0xff
34 #define SSE_STATUS_FLAGS FPU_EXCEPTION_MASK
35 #define SSE_EXCEPTION_MASK (FPU_EXCEPTION_MASK << 7)
36 
37 namespace Acts {
38 
39 namespace {
40 bool areFpesEquivalent(
41  std::pair<FpeType, const boost::stacktrace::stacktrace &> lhs,
42  std::pair<FpeType, const boost::stacktrace::stacktrace &> rhs) {
43  const auto &fl = *lhs.second.begin();
44  const auto &fr = *rhs.second.begin();
45  return lhs.first == rhs.first && (boost::stacktrace::hash_value(fl) ==
47 }
48 } // namespace
49 
51 
53  std::size_t countIn, FpeType typeIn,
54  std::shared_ptr<const boost::stacktrace::stacktrace> stIn)
55  : count{countIn}, type{typeIn}, st{std::move(stIn)} {}
56 
57 FpeMonitor::Result FpeMonitor::Result::merged(const Result &with) const {
58  Result result{};
59 
60  for (unsigned int i = 0; i < m_counts.size(); i++) {
61  result.m_counts[i] = m_counts[i] + with.m_counts[i];
62  }
63 
64  std::copy(with.m_stracktraces.begin(), with.m_stracktraces.end(),
65  std::back_inserter(result.m_stracktraces));
66  std::copy(m_stracktraces.begin(), m_stracktraces.end(),
67  std::back_inserter(result.m_stracktraces));
68 
70 
71  return result;
72 }
73 
74 void FpeMonitor::Result::merge(const Result &with) {
75  for (unsigned int i = 0; i < m_counts.size(); i++) {
76  m_counts[i] = m_counts[i] + with.m_counts[i];
77  }
78 
79  std::copy(with.m_stracktraces.begin(), with.m_stracktraces.end(),
80  std::back_inserter(m_stracktraces));
81 
82  deduplicate();
83 }
84 
85 void FpeMonitor::Result::add(FpeType type, void *stackPtr,
86  std::size_t bufferSize) {
87  auto st = std::make_unique<boost::stacktrace::stacktrace>(
88  boost::stacktrace::stacktrace::from_dump(stackPtr, bufferSize));
89 
90  auto it = std::find_if(
91  m_stracktraces.begin(), m_stracktraces.end(), [&](const FpeInfo &el) {
92  return areFpesEquivalent({el.type, *el.st}, {type, *st});
93  });
94 
95  if (it != m_stracktraces.end()) {
96  it->count += 1;
97  } else {
98  m_stracktraces.push_back({1, type, std::move(st)});
99  }
100 }
101 
103  FpeType type, const boost::stacktrace::stacktrace &st) const {
104  return std::find_if(m_stracktraces.begin(), m_stracktraces.end(),
105  [&](const FpeInfo &el) {
106  return areFpesEquivalent({el.type, *el.st}, {type, st});
107  }) != m_stracktraces.end();
108 }
109 
110 FpeMonitor::Result &FpeMonitor::result() {
111  consumeRecorded();
112  return m_result;
113 }
114 
116  if (m_recorded.empty()) {
117  return;
118  }
119 
120  for (auto [type, stackPtr, remaining] : m_recorded) {
121  m_result.add(type, stackPtr, remaining);
122  }
123 
124  m_buffer.reset();
125  m_recorded.clear();
126 }
127 
128 unsigned int FpeMonitor::Result::count(FpeType type) const {
129  return m_counts.at(static_cast<uint32_t>(type));
130 }
131 
132 unsigned int FpeMonitor::Result::numStackTraces() const {
133  return m_stracktraces.size();
134 }
135 
136 const std::vector<FpeMonitor::Result::FpeInfo>
138  return m_stracktraces;
139 }
140 
141 bool FpeMonitor::Result::encountered(FpeType type) const {
142  return count(type) > 0;
143 }
144 
145 void FpeMonitor::Result::summary(std::ostream &os, std::size_t depth) const {
146  os << "FPE result summary:\n";
147  static const std::vector<FpeType> types = {
148  FpeType::INTDIV, FpeType::INTOVF, FpeType::FLTDIV, FpeType::FLTOVF,
149  FpeType::FLTUND, FpeType::FLTRES, FpeType::FLTINV, FpeType::FLTSUB};
150 
151  for (auto type : types) {
152  os << "- " << type << ": " << count(type) << "\n";
153  }
154 
155  os << "\nStack traces:\n";
156  for (const auto &[count, type, st] : stackTraces()) {
157  os << "- " << type << ": (" << count << " times)\n";
158 
159  os << stackTraceToString(*st, depth);
160  }
161  os << std::endl;
162 }
163 
165  std::vector<FpeInfo> copy{};
166  copy = std::move(m_stracktraces);
167  m_stracktraces.clear();
168 
169  for (auto &info : copy) {
170  auto it = std::find_if(
171  m_stracktraces.begin(), m_stracktraces.end(),
172  [&info](const FpeInfo &el) {
173  return areFpesEquivalent({el.type, *el.st}, {info.type, *info.st});
174  });
175  if (it != m_stracktraces.end()) {
176  it->count += info.count;
177  continue;
178  }
179  m_stracktraces.push_back({info.count, info.type, std::move(info.st)});
180  }
181 }
182 
184  : m_excepts{FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW} {
185  enable();
186 }
187 
188 FpeMonitor::FpeMonitor(int excepts) : m_excepts(excepts) {
189  enable();
190 }
191 
192 FpeMonitor::~FpeMonitor() {
193  disable();
194 }
195 
196 void FpeMonitor::signalHandler(int /*signal*/, siginfo_t *si, void *ctx) {
197  if (stack().empty()) {
198  return;
199  }
200 
201  FpeMonitor &fpe = *stack().top();
202  fpe.m_result.m_counts.at(si->si_code)++;
203 
204  try {
205  // collect stack trace skipping 2 frames, which should be the signal handler
206  // and the calling facility. This might be platform specific, not sure
207  auto [buffer, remaining] = fpe.m_buffer.next();
208  std::size_t depth = boost::stacktrace::safe_dump_to(2, buffer, remaining);
209  std::size_t stored =
210  depth * sizeof(boost::stacktrace::frame::native_frame_ptr_t);
211  fpe.m_buffer.pushOffset(stored); // record how much storage was consumed
212  fpe.m_recorded.emplace_back(
213  static_cast<FpeType>(si->si_code), buffer,
214  remaining); // record buffer offset and fpe type
215 
216  } catch (const std::bad_alloc &e) {
217  std::cout << "Unable to collect stack trace due to memory limit"
218  << std::endl;
219  }
220 
221 #if defined(__linux__) && defined(__x86_64__)
222  __uint16_t *cw = &((ucontext_t *)ctx)->uc_mcontext.fpregs->cwd;
223  *cw |= FPU_EXCEPTION_MASK;
224 
225  __uint16_t *sw = &((ucontext_t *)ctx)->uc_mcontext.fpregs->swd;
226  *sw &= ~FPU_STATUS_FLAGS;
227 
228  __uint32_t *mxcsr = &((ucontext_t *)ctx)->uc_mcontext.fpregs->mxcsr;
229  // *mxcsr |= SSE_EXCEPTION_MASK; // disable all SSE exceptions
230  *mxcsr |= ((*mxcsr & SSE_STATUS_FLAGS) << 7);
231  *mxcsr &= ~SSE_STATUS_FLAGS; // clear all pending SSE exceptions
232 #else
233  (void)ctx;
234 #endif
235 }
236 
237 void FpeMonitor::enable() {
238 #if defined(__linux__) && defined(__x86_64__)
239  ensureSignalHandlerInstalled();
240 
241  // clear pending exceptions so they don't immediately fire
242  std::feclearexcept(m_excepts);
243 
244  if (!stack().empty()) {
245  // unset previous except state
246  fedisableexcept(stack().top()->m_excepts);
247  }
248  // apply this stack
249  feenableexcept(m_excepts);
250 
251  stack().push(this);
252 #else
253  (void)m_excepts;
254 #endif
255 }
256 
257 void FpeMonitor::rearm() {
258  consumeRecorded();
259 #if defined(__linux__) && defined(__x86_64__)
260  std::feclearexcept(m_excepts);
261  feenableexcept(m_excepts);
262 #endif
263 }
264 
265 void FpeMonitor::ensureSignalHandlerInstalled() {
266  auto &state = globalState();
267  if (state.isSignalHandlerInstalled) {
268  return;
269  }
270 
271  std::lock_guard lock{state.mutex};
272 
273  struct sigaction action {};
274  action.sa_sigaction = &signalHandler;
275  action.sa_flags = SA_SIGINFO;
276  sigaction(SIGFPE, &action, nullptr);
277 
278  state.isSignalHandlerInstalled = true;
279 }
280 
281 void FpeMonitor::disable() {
282 #if defined(__linux__) && defined(__x86_64__)
283  std::feclearexcept(m_excepts);
284  assert(!stack().empty() && "FPE stack shouldn't be empty at this point");
285  stack().pop();
286  // disable excepts we enabled here
287  fedisableexcept(m_excepts);
288  if (!stack().empty()) {
289  // restore excepts from next stack element
290  std::feclearexcept(stack().top()->m_excepts);
291  feenableexcept(stack().top()->m_excepts);
292  }
293 #endif
294 }
295 
296 std::stack<FpeMonitor *> &FpeMonitor::stack() {
297  static thread_local std::stack<FpeMonitor *> monitors;
298  return monitors;
299 }
300 
301 FpeMonitor::GlobalState &FpeMonitor::globalState() {
302  static GlobalState state{};
303  return state;
304 }
305 
306 std::ostream &operator<<(std::ostream &os, FpeType type) {
307 #define CASE(x) \
308  case FpeType::x: \
309  os << #x; \
310  break;
311 
312  switch (type) {
313  CASE(INTDIV)
314  CASE(INTOVF)
315  CASE(FLTDIV)
316  CASE(FLTOVF)
317  CASE(FLTUND)
318  CASE(FLTRES)
319  CASE(FLTINV)
320  CASE(FLTSUB)
321  }
322 #undef CASE
323 
324  return os;
325 }
326 
327 std::string FpeMonitor::stackTraceToString(
328  const boost::stacktrace::stacktrace &st, std::size_t depth) {
329  return boost::stacktrace::detail::to_string(st.as_vector().data(),
330  std::min(depth, st.size()));
331 }
332 
333 std::string FpeMonitor::getSourceLocation(
334  const boost::stacktrace::frame &frame) {
335  return frame.source_file() + ":" + std::to_string(frame.source_line());
336 }
337 
338 } // namespace Acts