File indexing completed on 2026-08-25 09:28:47
0001
0002 #include "JExecutionEngine.h"
0003 #include <JANA/Utils/JApplicationInspector.h>
0004 #include <JANA/JVersion.h>
0005
0006 #if JANA2_HAVE_PERFETTO
0007 #include <JANA/Services/JPerfettoService.h>
0008 #endif
0009
0010 #include <chrono>
0011 #include <cstddef>
0012 #include <cstdio>
0013 #include <ctime>
0014 #include <exception>
0015 #include <mutex>
0016 #include <csignal>
0017 #include <ostream>
0018 #include <sstream>
0019 #include <sys/stat.h>
0020 #include <fcntl.h>
0021 #include <unistd.h>
0022
0023 thread_local int jana2_worker_id = -1;
0024 thread_local JBacktrace* jana2_worker_backtrace = nullptr;
0025
0026 void JExecutionEngine::Init() {
0027 auto params = GetApplication()->GetJParameterManager();
0028
0029 params->SetDefaultParameter("jana:timeout", m_timeout_s,
0030 "Max time (in seconds) JANA will wait for a thread to update its heartbeat before hard-exiting. 0 to disable timeout completely.");
0031
0032 params->SetDefaultParameter("jana:warmup_timeout", m_warmup_timeout_s,
0033 "Max time (in seconds) JANA will wait for 'initial' events to complete before hard-exiting.");
0034
0035 params->SetDefaultParameter("jana:backoff_interval", m_backoff_ms,
0036 "Max time (in seconds) JANA will wait for 'initial' events to complete before hard-exiting.");
0037
0038 params->SetDefaultParameter("jana:show_ticker", m_show_ticker, "Controls whether the ticker is visible");
0039
0040 params->SetDefaultParameter("jana:ticker_interval", m_ticker_ms, "Controls the ticker interval (in ms)");
0041
0042 auto p = params->SetDefaultParameter("jana:status_fname", m_path_to_named_pipe,
0043 "Filename of named pipe for retrieving instantaneous status info");
0044
0045 size_t pid = getpid();
0046 mkfifo(m_path_to_named_pipe.c_str(), 0666);
0047
0048 LOG_WARN(GetLogger()) << "To pause processing and inspect, press Ctrl-C." << LOG_END;
0049 LOG_WARN(GetLogger()) << "For a clean shutdown, press Ctrl-C twice." << LOG_END;
0050 LOG_WARN(GetLogger()) << "For a hard shutdown, press Ctrl-C three times." << LOG_END;
0051
0052 if (p->IsDefault()) {
0053 LOG_WARN(GetLogger()) << "For worker status information, press Ctrl-Z, or run `jana-status " << pid << "`" << LOG_END;
0054 }
0055 else {
0056 LOG_WARN(GetLogger()) << "For worker status information, press Ctrl-Z, or run `jana-status " << pid << " " << m_path_to_named_pipe << "`" << LOG_END;
0057 }
0058
0059
0060
0061
0062
0063 for (JArrow* arrow : m_topology->GetArrows()) {
0064
0065 arrow->Initialize();
0066
0067 m_arrow_states.emplace_back();
0068 auto& arrow_state = m_arrow_states.back();
0069 arrow_state.is_source = arrow->IsSource();
0070 arrow_state.is_sink = arrow->IsSink();
0071 arrow_state.is_parallel = arrow->IsParallel();
0072 arrow_state.next_input = arrow->GetNextPortIndex();
0073 }
0074 }
0075
0076 void JExecutionEngine::RequestInspector() {
0077 std::unique_lock<std::mutex> lock(m_mutex);
0078 m_interrupt_status = InterruptStatus::InspectRequested;
0079 }
0080
0081 void JExecutionEngine::RunTopology() {
0082 std::unique_lock<std::mutex> lock(m_mutex);
0083
0084 if (m_runstatus == RunStatus::Failed) {
0085 throw JException("Cannot switch topology runstatus to Running because it is already Failed");
0086 }
0087 if (m_runstatus == RunStatus::Finished) {
0088 throw JException("Cannot switch topology runstatus to Running because it is already Finished");
0089 }
0090 if (m_arrow_states.size() == 0) {
0091 throw JException("Cannot execute an empty topology! Hint: Have you provided an event source?");
0092 }
0093
0094
0095 m_time_at_start = clock_t::now();
0096 m_event_count_at_start = m_event_count_at_finish;
0097
0098
0099 for (auto& arrow: m_arrow_states) {
0100 if (arrow.status == ArrowState::Status::Paused) {
0101 arrow.status = ArrowState::Status::Running;
0102 }
0103 }
0104
0105 m_runstatus = RunStatus::Running;
0106
0107 lock.unlock();
0108 m_condvar.notify_one();
0109 }
0110
0111 void JExecutionEngine::ScaleWorkers(size_t nthreads) {
0112
0113
0114
0115
0116
0117
0118
0119
0120 std::unique_lock<std::mutex> lock(m_mutex);
0121
0122 if (nthreads != 0 && m_arrow_states.size() == 0) {
0123
0124
0125 throw JException("Cannot execute an empty topology! Hint: Have you provided an event source?");
0126 }
0127
0128 auto prev_nthreads = m_worker_states.size();
0129
0130 if (prev_nthreads < nthreads) {
0131
0132 LOG_DEBUG(GetLogger()) << "Scaling up to " << nthreads << " worker threads" << LOG_END;
0133 auto mapping = m_topology->GetProcessorMapping();
0134 for (size_t worker_id=prev_nthreads; worker_id < nthreads; ++worker_id) {
0135 auto worker = std::make_unique<WorkerState>();
0136 worker->worker_id = worker_id;
0137 worker->is_stop_requested = false;
0138 worker->cpu_id = mapping.get_cpu_id(worker_id);
0139 worker->location_id = mapping.get_loc_id(worker_id);
0140 worker->thread = new std::thread(&JExecutionEngine::RunWorker, this, Worker{worker_id, &worker->backtrace});
0141 LOG_DEBUG(GetLogger()) << "Launching worker thread " << worker_id << " on cpu=" << worker->cpu_id << ", location=" << worker->location_id << LOG_END;
0142 m_worker_states.push_back(std::move(worker));
0143
0144 bool pin_to_cpu = (mapping.get_affinity() != JProcessorMapping::AffinityStrategy::None);
0145 if (pin_to_cpu) {
0146 JCpuInfo::PinThreadToCpu(worker->thread, worker->cpu_id);
0147 }
0148 }
0149 }
0150
0151 else if (prev_nthreads > nthreads) {
0152
0153 LOG_DEBUG(GetLogger()) << "Scaling down to " << nthreads << " worker threads" << LOG_END;
0154
0155
0156 for (int worker_id=prev_nthreads-1; worker_id >= (int)nthreads; --worker_id) {
0157 LOG_DEBUG(GetLogger()) << "Stopping worker " << worker_id << LOG_END;
0158 m_worker_states[worker_id]->is_stop_requested = true;
0159 }
0160 m_condvar.notify_all();
0161 lock.unlock();
0162
0163
0164 for (int worker_id=prev_nthreads-1; worker_id >= (int) nthreads; --worker_id) {
0165 if (m_worker_states[worker_id]->thread != nullptr) {
0166 if (m_worker_states[worker_id]->is_timed_out) {
0167
0168
0169
0170 m_worker_states[worker_id]->thread->detach();
0171 LOG_DEBUG(GetLogger()) << "Detached worker " << worker_id << LOG_END;
0172 }
0173 else {
0174 LOG_DEBUG(GetLogger()) << "Joining worker " << worker_id << LOG_END;
0175 m_worker_states[worker_id]->thread->join();
0176 LOG_DEBUG(GetLogger()) << "Joined worker " << worker_id << LOG_END;
0177 }
0178 }
0179 else {
0180 LOG_DEBUG(GetLogger()) << "Skipping worker " << worker_id << LOG_END;
0181 }
0182 }
0183
0184 lock.lock();
0185
0186 for (int worker_id=prev_nthreads-1; worker_id >= (int)nthreads; --worker_id) {
0187 if (m_worker_states.back()->thread != nullptr) {
0188 delete m_worker_states.back()->thread;
0189 }
0190 m_worker_states.pop_back();
0191 }
0192 }
0193 }
0194
0195 void JExecutionEngine::PauseTopology() {
0196 std::unique_lock<std::mutex> lock(m_mutex);
0197 if (m_runstatus != RunStatus::Running) return;
0198 m_runstatus = RunStatus::Pausing;
0199 for (auto& arrow: m_arrow_states) {
0200 if (arrow.status == ArrowState::Status::Running) {
0201 arrow.status = ArrowState::Status::Paused;
0202 }
0203 }
0204 LOG_WARN(GetLogger()) << "Requested pause" << LOG_END;
0205 lock.unlock();
0206 m_condvar.notify_all();
0207 }
0208
0209 void JExecutionEngine::DrainTopology() {
0210 std::unique_lock<std::mutex> lock(m_mutex);
0211 if (m_runstatus != RunStatus::Running) return;
0212 m_runstatus = RunStatus::Draining;
0213 for (auto& arrow: m_arrow_states) {
0214 if (arrow.is_source) {
0215 if (arrow.status == ArrowState::Status::Running) {
0216 arrow.status = ArrowState::Status::Paused;
0217 }
0218 }
0219 }
0220 LOG_WARN(GetLogger()) << "Requested drain" << LOG_END;
0221 lock.unlock();
0222 m_condvar.notify_all();
0223 }
0224
0225 void JExecutionEngine::RunSupervisor() {
0226
0227 if (m_interrupt_status == InterruptStatus::NoInterruptsUnsupervised) {
0228 m_interrupt_status = InterruptStatus::NoInterruptsSupervised;
0229 }
0230 size_t last_event_count = 0;
0231 clock_t::time_point last_measurement_time = clock_t::now();
0232
0233 Perf perf;
0234 while (true) {
0235
0236 if (m_enable_timeout && m_timeout_s > 0) {
0237 CheckTimeout();
0238 }
0239
0240 if (m_print_worker_report_requested) {
0241 PrintWorkerReport(false);
0242 m_print_worker_report_requested = false;
0243 }
0244
0245 if (m_send_worker_report_requested) {
0246 PrintWorkerReport(true);
0247 m_send_worker_report_requested = false;
0248 }
0249
0250 perf = GetPerf();
0251 if ((perf.runstatus == RunStatus::Paused && m_interrupt_status != InterruptStatus::InspectRequested) ||
0252 perf.runstatus == RunStatus::Finished ||
0253 perf.runstatus == RunStatus::Failed) {
0254 break;
0255 }
0256
0257 if (m_interrupt_status == InterruptStatus::InspectRequested) {
0258 if (perf.runstatus == RunStatus::Paused) {
0259 LOG_INFO(GetLogger()) << "Entering inspector" << LOG_END;
0260 m_enable_timeout = false;
0261 m_interrupt_status = InterruptStatus::InspectInProgress;
0262 InspectApplication(GetApplication());
0263 m_interrupt_status = InterruptStatus::NoInterruptsSupervised;
0264
0265
0266 last_measurement_time = clock_t::now();
0267 last_event_count = 0;
0268 continue;
0269 }
0270 else if (perf.runstatus == RunStatus::Running) {
0271 PauseTopology();
0272 }
0273 }
0274 else if (m_interrupt_status == InterruptStatus::PauseAndQuit) {
0275 PauseTopology();
0276 }
0277
0278 if (m_show_ticker) {
0279 auto next_measurement_time = clock_t::now();
0280 auto last_measurement_duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(next_measurement_time - last_measurement_time).count();
0281 float latest_throughput_hz = (last_measurement_duration_ms == 0) ? 0 : (perf.event_count - last_event_count) * 1000.0 / last_measurement_duration_ms;
0282 last_measurement_time = next_measurement_time;
0283 last_event_count = perf.event_count;
0284
0285
0286 LOG_INFO(m_logger) << "Status: " << perf.event_count << " events processed at "
0287 << JTypeInfo::to_string_with_si_prefix(latest_throughput_hz) << "Hz ("
0288 << JTypeInfo::to_string_with_si_prefix(perf.throughput_hz) << "Hz avg)" << LOG_END;
0289 }
0290
0291 std::this_thread::sleep_for(std::chrono::milliseconds(m_ticker_ms));
0292 }
0293 LOG_INFO(GetLogger()) << "Processing paused." << LOG_END;
0294
0295 if (perf.runstatus == RunStatus::Failed) {
0296 HandleFailures();
0297 }
0298
0299 PrintFinalReport();
0300 }
0301
0302 bool JExecutionEngine::CheckTimeout() {
0303 std::unique_lock<std::mutex> lock(m_mutex);
0304 auto now = clock_t::now();
0305 bool timeout_detected = false;
0306 for (auto& worker: m_worker_states) {
0307 auto timeout_s = (worker->is_event_warmed_up) ? m_timeout_s : m_warmup_timeout_s;
0308 auto duration_s = std::chrono::duration_cast<std::chrono::seconds>(now - worker->last_checkout_time).count();
0309 if (duration_s > timeout_s && worker->last_arrow_id != static_cast<uint64_t>(-1)) {
0310 worker->is_timed_out = true;
0311 timeout_detected = true;
0312 m_runstatus = RunStatus::Failed;
0313 }
0314 }
0315 return timeout_detected;
0316 }
0317
0318 void JExecutionEngine::HandleFailures() {
0319
0320 std::unique_lock<std::mutex> lock(m_mutex);
0321
0322
0323 for (auto& worker: m_worker_states) {
0324 if (worker->is_timed_out) {
0325 std::string arrow_name = (worker->last_arrow_id == static_cast<uint64_t>(-1)) ? "(none)" : m_topology->GetArrows()[worker->last_arrow_id]->GetName();
0326 LOG_FATAL(GetLogger()) << "Timeout in worker thread " << worker->worker_id << " while executing " << arrow_name << " on event #" << worker->last_event_nr << LOG_END;
0327 pthread_kill(worker->thread->native_handle(), SIGUSR2);
0328 LOG_INFO(GetLogger()) << "Worker thread signalled; waiting for backtrace capture." << LOG_END;
0329 worker->backtrace.WaitForCapture();
0330 }
0331 if (worker->stored_exception != nullptr) {
0332 std::string arrow_name = (worker->last_arrow_id == static_cast<uint64_t>(-1)) ? "(none)" : m_topology->GetArrows()[worker->last_arrow_id]->GetName();
0333 LOG_FATAL(GetLogger()) << "Exception in worker thread " << worker->worker_id << " while executing " << arrow_name << " on event #" << worker->last_event_nr << LOG_END;
0334 }
0335 }
0336
0337
0338
0339 for (auto& worker: m_worker_states) {
0340 if (worker->stored_exception != nullptr) {
0341 GetApplication()->SetExitCode((int) JApplication::ExitCode::UnhandledException);
0342 std::rethrow_exception(worker->stored_exception);
0343 }
0344 if (worker->is_timed_out) {
0345 GetApplication()->SetExitCode((int) JApplication::ExitCode::Timeout);
0346 auto ex = JException("Timeout in worker thread");
0347 ex.backtrace = worker->backtrace;
0348 throw ex;
0349 }
0350 }
0351 }
0352
0353 void JExecutionEngine::FinishTopology() {
0354 std::unique_lock<std::mutex> lock(m_mutex);
0355 assert(m_runstatus == RunStatus::Paused);
0356
0357 LOG_DEBUG(GetLogger()) << "Finishing processing..." << LOG_END;
0358 for (auto* arrow : m_topology->GetArrows()) {
0359 arrow->Finalize();
0360 }
0361 for (auto* pool: m_topology->GetPools()) {
0362 pool->Finalize();
0363 }
0364 m_runstatus = RunStatus::Finished;
0365 LOG_INFO(GetLogger()) << "Finished processing." << LOG_END;
0366 }
0367
0368 JExecutionEngine::RunStatus JExecutionEngine::GetRunStatus() {
0369 std::unique_lock<std::mutex> lock(m_mutex);
0370 return m_runstatus;
0371 }
0372
0373 JExecutionEngine::Perf JExecutionEngine::GetPerf() {
0374 std::unique_lock<std::mutex> lock(m_mutex);
0375 Perf result;
0376 if (m_runstatus == RunStatus::Paused || m_runstatus == RunStatus::Failed) {
0377 result.event_count = m_event_count_at_finish - m_event_count_at_start;
0378 result.uptime_ms = std::chrono::duration_cast<std::chrono::milliseconds>(m_time_at_finish - m_time_at_start).count();
0379 }
0380 else {
0381
0382 size_t current_event_count = 0;
0383 for (auto& state : m_arrow_states) {
0384 if (state.is_sink) {
0385 current_event_count += state.events_processed;
0386 }
0387 }
0388 result.event_count = current_event_count - m_event_count_at_start;
0389 result.uptime_ms = std::chrono::duration_cast<std::chrono::milliseconds>(clock_t::now() - m_time_at_start).count();
0390 }
0391 result.runstatus = m_runstatus;
0392 result.thread_count = m_worker_states.size();
0393 result.throughput_hz = (result.uptime_ms == 0) ? 0 : (result.event_count * 1000.0) / result.uptime_ms;
0394 result.event_level = JEventLevel::PhysicsEvent;
0395 return result;
0396 }
0397
0398 JExecutionEngine::Worker JExecutionEngine::RegisterWorker() {
0399 std::unique_lock<std::mutex> lock(m_mutex);
0400 auto mapping = m_topology->GetProcessorMapping();
0401 auto worker_id = m_worker_states.size();
0402 auto worker = std::make_unique<WorkerState>();
0403 worker->worker_id = worker_id;
0404 worker->is_stop_requested = false;
0405 worker->cpu_id = mapping.get_cpu_id(worker_id);
0406 worker->location_id = mapping.get_loc_id(worker_id);
0407 worker->thread = nullptr;
0408 m_worker_states.push_back(std::move(worker));
0409
0410 bool pin_to_cpu = (mapping.get_affinity() != JProcessorMapping::AffinityStrategy::None);
0411 if (pin_to_cpu) {
0412 JCpuInfo::PinThreadToCpu(worker->thread, worker->cpu_id);
0413 }
0414 return {worker_id, &worker->backtrace};
0415
0416 }
0417
0418
0419 void JExecutionEngine::RunWorker(Worker worker) {
0420
0421 LOG_DEBUG(GetLogger()) << "Launched worker thread " << worker.worker_id << LOG_END;
0422 jana2_worker_id = worker.worker_id;
0423 jana2_worker_backtrace = worker.backtrace;
0424 #if JANA2_HAVE_PERFETTO
0425 JPerfettoService::RegisterCurrentThread(worker.worker_id);
0426 #endif
0427 try {
0428 Task task;
0429 while (true) {
0430 ExchangeTask(task, worker.worker_id);
0431 if (task.arrow == nullptr) break;
0432 {
0433 #if JANA2_HAVE_PERFETTO
0434 TRACE_EVENT("jana", perfetto::DynamicString{task.arrow->GetName()},
0435 "worker_id", (uint64_t)worker.worker_id);
0436 #endif
0437 task.arrow->Fire(task.input_event, task.outputs, task.output_count, task.status);
0438 }
0439 }
0440 LOG_DEBUG(GetLogger()) << "Stopped worker thread " << worker.worker_id << LOG_END;
0441 }
0442 catch(JException& ex) {
0443 LOG_ERROR(GetLogger()) << "Exception on worker thread " << worker.worker_id << ": " << ex.GetMessage();
0444 std::unique_lock<std::mutex> lock(m_mutex);
0445 m_runstatus = RunStatus::Failed;
0446 m_worker_states.at(worker.worker_id)->stored_exception = std::current_exception();
0447 }
0448 catch (...) {
0449 LOG_ERROR(GetLogger()) << "Exception on worker thread " << worker.worker_id << LOG_END;
0450 std::unique_lock<std::mutex> lock(m_mutex);
0451 m_runstatus = RunStatus::Failed;
0452 m_worker_states.at(worker.worker_id)->stored_exception = std::current_exception();
0453 }
0454 }
0455
0456
0457 void JExecutionEngine::ExchangeTask(Task& task, size_t worker_id, bool nonblocking) {
0458
0459 auto checkin_time = std::chrono::steady_clock::now();
0460
0461
0462
0463 std::unique_lock<std::mutex> lock(m_mutex);
0464
0465 auto& worker = *m_worker_states.at(worker_id);
0466
0467 if (task.arrow != nullptr) {
0468 CheckinCompletedTask_Unsafe(task, worker, checkin_time);
0469 }
0470
0471 if (worker.is_stop_requested) {
0472 return;
0473 }
0474
0475 FindNextReadyTask_Unsafe(task, worker);
0476
0477 if (nonblocking) { return; }
0478 auto idle_time_start = clock_t::now();
0479 m_total_scheduler_duration += (idle_time_start - checkin_time);
0480
0481 while (task.arrow == nullptr && !worker.is_stop_requested) {
0482 m_condvar.wait(lock);
0483 FindNextReadyTask_Unsafe(task, worker);
0484 }
0485 worker.last_checkout_time = clock_t::now();
0486
0487 if (task.input_event != nullptr) {
0488 worker.last_event_nr = task.input_event->GetEventNumber();
0489 }
0490 else {
0491 worker.last_event_nr = 0;
0492 }
0493 m_total_idle_duration += (worker.last_checkout_time - idle_time_start);
0494
0495
0496
0497
0498 if (task.arrow != nullptr && task.arrow->IsParallel()) {
0499 m_condvar.notify_one();
0500 }
0501 }
0502
0503
0504 void JExecutionEngine::CheckinCompletedTask_Unsafe(Task& task, WorkerState& worker, clock_t::time_point checkin_time) {
0505
0506 auto processing_duration = checkin_time - worker.last_checkout_time;
0507
0508 ArrowState& arrow_state = m_arrow_states.at(worker.last_arrow_id);
0509
0510 arrow_state.active_tasks -= 1;
0511 arrow_state.total_processing_duration += processing_duration;
0512
0513 for (size_t output=0; output<task.output_count; ++output) {
0514 if (!task.arrow->GetPort(task.outputs[output].second).GetSkipFinishEvent()) {
0515 arrow_state.events_processed++;
0516 }
0517 }
0518
0519
0520 task.arrow->Push(task.outputs, task.output_count, worker.location_id);
0521
0522 if (task.status == JArrow::FireResult::Finished) {
0523
0524
0525
0526
0527 arrow_state.status = ArrowState::Status::Finished;
0528
0529
0530 if (m_runstatus == RunStatus::Running) {
0531 bool draining = true;
0532 for (auto& arrow: m_arrow_states) {
0533 if (arrow.is_source && arrow.status == ArrowState::Status::Running) {
0534 draining = false;
0535 }
0536 }
0537 if (draining) {
0538 m_runstatus = RunStatus::Draining;
0539 }
0540 }
0541 }
0542 worker.last_arrow_id = -1;
0543 worker.last_event_nr = 0;
0544
0545 task.arrow = nullptr;
0546 task.input_event = nullptr;
0547 task.output_count = 0;
0548 task.status = JArrow::FireResult::NotRunYet;
0549 };
0550
0551
0552 void JExecutionEngine::FindNextReadyTask_Unsafe(Task& task, WorkerState& worker) {
0553
0554 if (m_runstatus == RunStatus::Running || m_runstatus == RunStatus::Draining) {
0555
0556
0557
0558 size_t arrow_count = m_arrow_states.size();
0559 m_next_arrow_id += 1;
0560 m_next_arrow_id %= arrow_count;
0561
0562 for (size_t i=m_next_arrow_id; i<(m_next_arrow_id+arrow_count); ++i) {
0563 size_t arrow_id = i % arrow_count;
0564
0565 auto& state = m_arrow_states[arrow_id];
0566 if (!state.is_parallel && (state.active_tasks != 0)) {
0567
0568 LOG_TRACE(GetLogger()) << "Scheduler: Arrow with id " << arrow_id << " is unready: Sequential and already active." << LOG_END;
0569 continue;
0570 }
0571
0572 if (state.status != ArrowState::Status::Running) {
0573 LOG_TRACE(GetLogger()) << "Scheduler: Arrow with id " << arrow_id << " is unready: Arrow is either paused or finished." << LOG_END;
0574 continue;
0575 }
0576
0577
0578
0579 JArrow* arrow = m_topology->GetArrows()[arrow_id];
0580
0581 auto port = arrow->GetNextPortIndex();
0582 JEvent* event = (port == -1) ? nullptr : arrow->Pull(port, worker.location_id);
0583 if (event != nullptr || port == -1) {
0584 LOG_TRACE(GetLogger()) << "Scheduler: Found next ready arrow with id " << arrow_id << LOG_END;
0585
0586 state.active_tasks += 1;
0587
0588 task.arrow = arrow;
0589 task.input_port = port;
0590 task.input_event = event;
0591 task.output_count = 0;
0592 task.status = JArrow::FireResult::NotRunYet;
0593
0594 worker.last_arrow_id = arrow_id;
0595 if (event != nullptr) {
0596 worker.is_event_warmed_up = event->IsWarmedUp();
0597 worker.last_event_nr = event->GetEventNumber();
0598 }
0599 else {
0600 worker.is_event_warmed_up = true;
0601 worker.last_event_nr = 0;
0602 }
0603 return;
0604 }
0605 else {
0606 LOG_TRACE(GetLogger()) << "Scheduler: Arrow with id " << arrow_id << " is unready: Input event is needed but not on queue yet." << LOG_END;
0607 }
0608 }
0609 }
0610
0611
0612
0613
0614
0615
0616 if (m_runstatus == RunStatus::Pausing || m_runstatus == RunStatus::Draining) {
0617
0618
0619
0620 bool any_active_source_found = false;
0621 bool any_active_task_found = false;
0622
0623 LOG_DEBUG(GetLogger()) << "Scheduler: No tasks ready" << LOG_END;
0624
0625 for (size_t arrow_id = 0; arrow_id < m_arrow_states.size(); ++arrow_id) {
0626 auto& state = m_arrow_states[arrow_id];
0627 any_active_source_found |= (state.status == ArrowState::Status::Running && state.is_source);
0628 any_active_task_found |= (state.active_tasks != 0);
0629
0630 }
0631
0632 if (!any_active_source_found && !any_active_task_found) {
0633
0634 m_time_at_finish = clock_t::now();
0635 m_event_count_at_finish = 0;
0636 for (auto& arrow_state : m_arrow_states) {
0637 if (arrow_state.is_sink) {
0638 m_event_count_at_finish += arrow_state.events_processed;
0639 }
0640 }
0641 LOG_DEBUG(GetLogger()) << "Scheduler: Processing paused" << LOG_END;
0642 m_runstatus = RunStatus::Paused;
0643
0644 }
0645 }
0646
0647 worker.last_arrow_id = -1;
0648
0649 task.arrow = nullptr;
0650 task.input_port = -1;
0651 task.input_event = nullptr;
0652 task.output_count = 0;
0653 task.status = JArrow::FireResult::NotRunYet;
0654 }
0655
0656
0657 void JExecutionEngine::PrintFinalReport() {
0658
0659 std::unique_lock<std::mutex> lock(m_mutex);
0660 auto event_count = m_event_count_at_finish - m_event_count_at_start;
0661 auto uptime_ms = std::chrono::duration_cast<std::chrono::milliseconds>(m_time_at_finish - m_time_at_start).count();
0662 auto thread_count = m_worker_states.size();
0663 auto throughput_hz = (event_count * 1000.0) / uptime_ms;
0664
0665 LOG_INFO(GetLogger()) << "Detailed report:" << LOG_END;
0666 LOG_INFO(GetLogger()) << LOG_END;
0667 LOG_INFO(GetLogger()) << " Avg throughput [Hz]: " << std::setprecision(3) << throughput_hz << LOG_END;
0668 LOG_INFO(GetLogger()) << " Completed events [count]: " << event_count << LOG_END;
0669 LOG_INFO(GetLogger()) << " Total uptime [s]: " << std::setprecision(4) << uptime_ms/1000.0 << LOG_END;
0670 LOG_INFO(GetLogger()) << " Thread team size [count]: " << thread_count << LOG_END;
0671 LOG_INFO(GetLogger()) << LOG_END;
0672 LOG_INFO(GetLogger()) << " Arrow-level metrics:" << LOG_END;
0673 LOG_INFO(GetLogger()) << LOG_END;
0674
0675 size_t total_useful_ms = 0;
0676
0677 for (size_t arrow_id=0; arrow_id < m_arrow_states.size(); ++arrow_id) {
0678 auto* arrow = m_topology->GetArrows()[arrow_id];
0679 auto& arrow_state = m_arrow_states[arrow_id];
0680 auto useful_ms = std::chrono::duration_cast<std::chrono::milliseconds>(arrow_state.total_processing_duration).count();
0681 total_useful_ms += useful_ms;
0682 auto avg_latency = useful_ms*1.0/arrow_state.events_processed;
0683 auto throughput_bottleneck = 1000.0 / avg_latency;
0684 if (arrow->IsParallel()) {
0685 throughput_bottleneck *= thread_count;
0686 }
0687
0688 LOG_INFO(GetLogger()) << " - Arrow name: " << arrow->GetName() << LOG_END;
0689 LOG_INFO(GetLogger()) << " Parallel: " << arrow->IsParallel() << LOG_END;
0690 LOG_INFO(GetLogger()) << " Events completed: " << arrow_state.events_processed << LOG_END;
0691 LOG_INFO(GetLogger()) << " Avg latency [ms/event]: " << avg_latency << LOG_END;
0692 LOG_INFO(GetLogger()) << " Throughput bottleneck [Hz]: " << throughput_bottleneck << LOG_END;
0693 LOG_INFO(GetLogger()) << LOG_END;
0694 }
0695
0696 auto total_scheduler_ms = std::chrono::duration_cast<std::chrono::milliseconds>(m_total_scheduler_duration).count();
0697 auto total_idle_ms = std::chrono::duration_cast<std::chrono::milliseconds>(m_total_idle_duration).count();
0698
0699 LOG_INFO(GetLogger()) << " Total useful time [s]: " << std::setprecision(6) << total_useful_ms/1000.0 << LOG_END;
0700 LOG_INFO(GetLogger()) << " Total scheduler time [s]: " << std::setprecision(6) << total_scheduler_ms/1000.0 << LOG_END;
0701 LOG_INFO(GetLogger()) << " Total idle time [s]: " << std::setprecision(6) << total_idle_ms/1000.0 << LOG_END;
0702
0703 LOG_INFO(GetLogger()) << LOG_END;
0704
0705 LOG_INFO(GetLogger()) << "Final report: " << event_count << " events processed at "
0706 << JTypeInfo::to_string_with_si_prefix(throughput_hz) << "Hz" << LOG_END;
0707
0708 }
0709
0710 void JExecutionEngine::SetTickerEnabled(bool show_ticker) {
0711 m_show_ticker = show_ticker;
0712 }
0713
0714 bool JExecutionEngine::IsTickerEnabled() const {
0715 return m_show_ticker;
0716 }
0717
0718 void JExecutionEngine::SetTimeoutEnabled(bool timeout_enabled) {
0719 m_enable_timeout = timeout_enabled;
0720 }
0721
0722 bool JExecutionEngine::IsTimeoutEnabled() const {
0723 return m_enable_timeout;
0724 }
0725
0726 JArrow::FireResult JExecutionEngine::Fire(size_t arrow_id, size_t location_id) {
0727
0728 std::unique_lock<std::mutex> lock(m_mutex);
0729 if (arrow_id >= m_topology->GetArrows().size()) {
0730 LOG_WARN(GetLogger()) << "Firing unsuccessful: No arrow exists with id=" << arrow_id << LOG_END;
0731 return JArrow::FireResult::NotRunYet;
0732 }
0733 JArrow* arrow = m_topology->GetArrows()[arrow_id];
0734 LOG_WARN(GetLogger()) << "Attempting to fire arrow with name=" << arrow->GetName()
0735 << ", index=" << arrow_id << ", location=" << location_id << LOG_END;
0736
0737 ArrowState& arrow_state = m_arrow_states[arrow_id];
0738 if (arrow_state.status == ArrowState::Status::Finished) {
0739 LOG_WARN(GetLogger()) << "Firing unsuccessful: Arrow status is Finished." << arrow_id << LOG_END;
0740 return JArrow::FireResult::Finished;
0741 }
0742 if (!arrow_state.is_parallel && arrow_state.active_tasks != 0) {
0743 LOG_WARN(GetLogger()) << "Firing unsuccessful: Arrow is sequential and already has an active task." << arrow_id << LOG_END;
0744 return JArrow::FireResult::NotRunYet;
0745 }
0746 arrow_state.active_tasks += 1;
0747
0748 auto port = arrow->GetNextPortIndex();
0749 JEvent* event = nullptr;
0750 if (port != -1) {
0751 event = arrow->Pull(port, location_id);
0752 if (event == nullptr) {
0753 LOG_WARN(GetLogger()) << "Firing unsuccessful: Arrow needs an input event from port " << port << ", but the queue or pool is empty." << LOG_END;
0754 arrow_state.active_tasks -= 1;
0755 return JArrow::FireResult::NotRunYet;
0756 }
0757 else {
0758 LOG_WARN(GetLogger()) << "Input event #" << event->GetEventNumber() << " from port " << port << LOG_END;
0759 }
0760 }
0761 else {
0762 LOG_WARN(GetLogger()) << "No input events" << LOG_END;
0763 }
0764 lock.unlock();
0765
0766 size_t output_count;
0767 JArrow::OutputData outputs;
0768 JArrow::FireResult result = JArrow::FireResult::NotRunYet;
0769
0770 LOG_WARN(GetLogger()) << "Firing arrow" << LOG_END;
0771 arrow->Fire(event, outputs, output_count, result);
0772 LOG_WARN(GetLogger()) << "Fired arrow with result " << ToString(result) << LOG_END;
0773 if (output_count == 0) {
0774 LOG_WARN(GetLogger()) << "No output events" << LOG_END;
0775 }
0776 else {
0777 for (size_t i=0; i<output_count; ++i) {
0778 LOG_WARN(GetLogger()) << "Output event #" << outputs.at(i).first->GetEventNumber() << " on port " << outputs.at(i).second << LOG_END;
0779 }
0780 }
0781
0782 lock.lock();
0783 arrow->Push(outputs, output_count, location_id);
0784 arrow_state.active_tasks -= 1;
0785 lock.unlock();
0786 return result;
0787 }
0788
0789
0790 void JExecutionEngine::HandleSIGINT() {
0791 InterruptStatus status = m_interrupt_status;
0792 std::cout << std::endl;
0793 switch (status) {
0794 case InterruptStatus::NoInterruptsSupervised: m_interrupt_status = InterruptStatus::InspectRequested; break;
0795 case InterruptStatus::InspectRequested: m_interrupt_status = InterruptStatus::PauseAndQuit; break;
0796 case InterruptStatus::NoInterruptsUnsupervised:
0797 case InterruptStatus::PauseAndQuit:
0798 case InterruptStatus::InspectInProgress:
0799 _exit(-2);
0800 }
0801 }
0802
0803 void JExecutionEngine::HandleSIGUSR1() {
0804 m_send_worker_report_requested = true;
0805 }
0806
0807 void JExecutionEngine::HandleSIGUSR2() {
0808 if (jana2_worker_backtrace != nullptr) {
0809 jana2_worker_backtrace->Capture(3);
0810 }
0811 }
0812
0813 void JExecutionEngine::HandleSIGTSTP() {
0814 std::cout << std::endl;
0815 m_print_worker_report_requested = true;
0816 }
0817
0818 void JExecutionEngine::PrintWorkerReport(bool send_to_pipe) {
0819
0820 std::unique_lock<std::mutex> lock(m_mutex);
0821 LOG_INFO(GetLogger()) << "Generating worker report. It may take some time to retrieve each symbol's debug information." << LOG_END;
0822 for (auto& worker: m_worker_states) {
0823 worker->backtrace.Reset();
0824 pthread_kill(worker->thread->native_handle(), SIGUSR2);
0825 }
0826 for (auto& worker: m_worker_states) {
0827 worker->backtrace.WaitForCapture();
0828 }
0829 std::ostringstream oss;
0830 oss << "Worker report" << std::endl;
0831 for (auto& worker: m_worker_states) {
0832 oss << "------------------------------" << std::endl
0833 << " Worker: " << worker->worker_id << std::endl
0834 << " Current arrow: " << worker->last_arrow_id << std::endl
0835 << " Current event: " << worker->last_event_nr << std::endl
0836 << " Backtrace:" << std::endl << std::endl
0837 << worker->backtrace.ToString();
0838 }
0839 auto s = oss.str();
0840 LOG_WARN(GetLogger()) << s << LOG_END;
0841
0842 if (send_to_pipe) {
0843
0844 int fd = open(m_path_to_named_pipe.c_str(), O_WRONLY);
0845 if (fd >= 0) {
0846 write(fd, s.c_str(), s.length()+1);
0847 close(fd);
0848 }
0849 else {
0850 LOG_ERROR(GetLogger()) << "Unable to open named pipe '" << m_path_to_named_pipe << "' for writing. \n"
0851 << " You can use a different named pipe for status info by setting the parameter `jana:status_fname`.\n"
0852 << " The status report will still show up in the log." << LOG_END;
0853 }
0854 }
0855 }
0856
0857
0858 std::string ToString(JExecutionEngine::RunStatus runstatus) {
0859 switch(runstatus) {
0860 case JExecutionEngine::RunStatus::Running: return "Running";
0861 case JExecutionEngine::RunStatus::Paused: return "Paused";
0862 case JExecutionEngine::RunStatus::Failed: return "Failed";
0863 case JExecutionEngine::RunStatus::Pausing: return "Pausing";
0864 case JExecutionEngine::RunStatus::Draining: return "Draining";
0865 case JExecutionEngine::RunStatus::Finished: return "Finished";
0866 default: return "CorruptedRunStatus";
0867 }
0868 }
0869