rippled
JobQueue.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012, 2013 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/basics/PerfLog.h>
21 #include <ripple/basics/contract.h>
22 #include <ripple/core/JobQueue.h>
23 #include <mutex>
24 
25 namespace ripple {
26 
28  beast::insight::Collector::ptr const& collector,
29  beast::Journal journal,
30  Logs& logs,
31  perf::PerfLog& perfLog)
32  : m_journal(journal)
33  , m_lastJob(0)
34  , m_invalidJobData(JobTypes::instance().getInvalid(), collector, logs)
35  , m_processCount(0)
36  , m_workers(*this, &perfLog, "JobQueue", 0)
37  , m_cancelCallback(std::bind(&JobQueue::isStopping, this))
38  , perfLog_(perfLog)
39  , m_collector(collector)
40 {
41  hook = m_collector->make_hook(std::bind(&JobQueue::collect, this));
42  job_count = m_collector->make_gauge("job_count");
43 
44  {
46 
47  for (auto const& x : JobTypes::instance())
48  {
49  JobTypeInfo const& jt = x.second;
50 
51  // And create dynamic information for all jobs
52  auto const result(m_jobData.emplace(
53  std::piecewise_construct,
56  assert(result.second == true);
57  (void)result.second;
58  }
59  }
60 }
61 
63 {
64  // Must unhook before destroying
66 }
67 
68 void
70 {
72  job_count = m_jobSet.size();
73 }
74 
75 bool
77  JobType type,
78  std::string const& name,
79  JobFunction const& func)
80 {
81  assert(type != jtINVALID);
82 
83  auto iter(m_jobData.find(type));
84  assert(iter != m_jobData.end());
85  if (iter == m_jobData.end())
86  return false;
87 
88  JLOG(m_journal.debug())
89  << __func__ << " : Adding job : " << name << " : " << type;
90  JobTypeData& data(iter->second);
91 
92  // FIXME: Workaround incorrect client shutdown ordering
93  // do not add jobs to a queue with no threads
94  assert(type == jtCLIENT || m_workers.getNumberOfThreads() > 0);
95 
96  {
98  auto result = m_jobSet.emplace(
99  type, name, ++m_lastJob, data.load(), func, m_cancelCallback);
100  queueJob(*result.first, lock);
101  }
102  return true;
103 }
104 
105 int
107 {
108  std::lock_guard lock(m_mutex);
109 
110  JobDataMap::const_iterator c = m_jobData.find(t);
111 
112  return (c == m_jobData.end()) ? 0 : c->second.waiting;
113 }
114 
115 int
117 {
118  std::lock_guard lock(m_mutex);
119 
120  JobDataMap::const_iterator c = m_jobData.find(t);
121 
122  return (c == m_jobData.end()) ? 0 : (c->second.waiting + c->second.running);
123 }
124 
125 int
127 {
128  // return the number of jobs at this priority level or greater
129  int ret = 0;
130 
131  std::lock_guard lock(m_mutex);
132 
133  for (auto const& x : m_jobData)
134  {
135  if (x.first >= t)
136  ret += x.second.waiting;
137  }
138 
139  return ret;
140 }
141 
142 void
143 JobQueue::setThreadCount(int c, bool const standaloneMode)
144 {
145  if (standaloneMode)
146  {
147  c = 1;
148  }
149  else if (c == 0)
150  {
151  c = static_cast<int>(std::thread::hardware_concurrency());
152  c = 2 + std::min(c, 4); // I/O will bottleneck
153  JLOG(m_journal.info()) << "Auto-tuning to " << c
154  << " validation/transaction/proposal threads.";
155  }
156  else
157  {
158  JLOG(m_journal.info()) << "Configured " << c
159  << " validation/transaction/proposal threads.";
160  }
161 
163 }
164 
167 {
168  JobDataMap::iterator iter(m_jobData.find(t));
169  assert(iter != m_jobData.end());
170 
171  if (iter == m_jobData.end())
172  return {};
173 
174  return std::make_unique<LoadEvent>(iter->second.load(), name, true);
175 }
176 
177 void
179 {
180  if (isStopped())
181  LogicError("JobQueue::addLoadEvents() called after JobQueue stopped");
182 
183  JobDataMap::iterator iter(m_jobData.find(t));
184  assert(iter != m_jobData.end());
185  iter->second.load().addSamples(count, elapsed);
186 }
187 
188 bool
190 {
191  int count = 0;
192 
193  for (auto& x : m_jobData)
194  {
195  if (x.second.load().isOver())
196  ++count;
197  }
198 
199  return count > 0;
200 }
201 
204 {
205  using namespace std::chrono_literals;
207 
208  ret["threads"] = m_workers.getNumberOfThreads();
209 
210  Json::Value priorities = Json::arrayValue;
211 
212  std::lock_guard lock(m_mutex);
213 
214  for (auto& x : m_jobData)
215  {
216  assert(x.first != jtINVALID);
217 
218  if (x.first == jtGENERIC)
219  continue;
220 
221  JobTypeData& data(x.second);
222 
223  LoadMonitor::Stats stats(data.stats());
224 
225  int waiting(data.waiting);
226  int running(data.running);
227 
228  if ((stats.count != 0) || (waiting != 0) ||
229  (stats.latencyPeak != 0ms) || (running != 0))
230  {
231  Json::Value& pri = priorities.append(Json::objectValue);
232 
233  pri["job_type"] = data.name();
234 
235  if (stats.isOverloaded)
236  pri["over_target"] = true;
237 
238  if (waiting != 0)
239  pri["waiting"] = waiting;
240 
241  if (stats.count != 0)
242  pri["per_second"] = static_cast<int>(stats.count);
243 
244  if (stats.latencyPeak != 0ms)
245  pri["peak_time"] = static_cast<int>(stats.latencyPeak.count());
246 
247  if (stats.latencyAvg != 0ms)
248  pri["avg_time"] = static_cast<int>(stats.latencyAvg.count());
249 
250  if (running != 0)
251  pri["in_progress"] = running;
252  }
253  }
254 
255  ret["job_types"] = priorities;
256 
257  return ret;
258 }
259 
260 void
262 {
264  cv_.wait(lock, [this] { return m_processCount == 0 && m_jobSet.empty(); });
265 }
266 
269 {
270  JobDataMap::iterator c(m_jobData.find(type));
271  assert(c != m_jobData.end());
272 
273  // NIKB: This is ugly and I hate it. We must remove jtINVALID completely
274  // and use something sane.
275  if (c == m_jobData.end())
276  return m_invalidJobData;
277 
278  return c->second;
279 }
280 
281 void
283 {
284  stopping_ = true;
285  using namespace std::chrono_literals;
286  jobCounter_.join("JobQueue", 1s, m_journal);
287  {
288  // After the JobCounter is joined, all jobs have finished executing
289  // (i.e. returned from `Job::doJob`) and no more are being accepted,
290  // but there may still be some threads between the return of
291  // `Job::doJob` and the return of `JobQueue::processTask`. That is why
292  // we must wait on the condition variable to make these assertions.
294  cv_.wait(
295  lock, [this] { return m_processCount == 0 && m_jobSet.empty(); });
296  assert(m_processCount == 0);
297  assert(m_jobSet.empty());
298  assert(nSuspend_ == 0);
299  stopped_ = true;
300  }
301 }
302 
303 bool
305 {
306  return stopped_;
307 }
308 
309 void
311 {
312  JobType const type(job.getType());
313  assert(type != jtINVALID);
314  assert(m_jobSet.find(job) != m_jobSet.end());
315  perfLog_.jobQueue(type);
316 
317  JobTypeData& data(getJobTypeData(type));
318 
319  if (data.waiting + data.running < getJobLimit(type))
320  {
321  m_workers.addTask();
322  }
323  else
324  {
325  // defer the task until we go below the limit
326  //
327  ++data.deferred;
328  }
329  ++data.waiting;
330 }
331 
332 void
334 {
335  assert(!m_jobSet.empty());
336 
338  for (iter = m_jobSet.begin(); iter != m_jobSet.end(); ++iter)
339  {
340  JobTypeData& data(getJobTypeData(iter->getType()));
341 
342  assert(data.running <= getJobLimit(data.type()));
343 
344  // Run this job if we're running below the limit.
345  if (data.running < getJobLimit(data.type()))
346  {
347  assert(data.waiting > 0);
348  break;
349  }
350  }
351 
352  assert(iter != m_jobSet.end());
353 
354  JobType const type = iter->getType();
355  JobTypeData& data(getJobTypeData(type));
356 
357  assert(type != jtINVALID);
358 
359  job = *iter;
360  m_jobSet.erase(iter);
361 
362  --data.waiting;
363  ++data.running;
364 }
365 
366 void
368 {
369  assert(type != jtINVALID);
370 
371  JobTypeData& data = getJobTypeData(type);
372 
373  // Queue a deferred task if possible
374  if (data.deferred > 0)
375  {
376  assert(data.running + data.waiting >= getJobLimit(type));
377 
378  --data.deferred;
379  m_workers.addTask();
380  }
381 
382  --data.running;
383 }
384 
385 void
387 {
388  JobType type;
389 
390  {
391  using namespace std::chrono;
392  Job::clock_type::time_point const start_time(Job::clock_type::now());
393  {
394  Job job;
395  {
396  std::lock_guard lock(m_mutex);
397  getNextJob(job);
398  ++m_processCount;
399  }
400  type = job.getType();
401  JobTypeData& data(getJobTypeData(type));
402  JLOG(m_journal.trace()) << "Doing " << data.name() << "job";
403 
404  // The amount of time that the job was in the queue
405  auto const q_time =
406  ceil<microseconds>(start_time - job.queue_time());
407  perfLog_.jobStart(type, q_time, start_time, instance);
408 
409  job.doJob();
410 
411  // The amount of time it took to execute the job
412  auto const x_time =
413  ceil<microseconds>(Job::clock_type::now() - start_time);
414 
415  if (x_time >= 10ms || q_time >= 10ms)
416  {
417  getJobTypeData(type).dequeue.notify(q_time);
418  getJobTypeData(type).execute.notify(x_time);
419  }
420  perfLog_.jobFinish(type, x_time, instance);
421  }
422  }
423 
424  {
425  std::lock_guard lock(m_mutex);
426  // Job should be destroyed before stopping
427  // otherwise destructors with side effects can access
428  // parent objects that are already destroyed.
429  finishJob(type);
430  if (--m_processCount == 0 && m_jobSet.empty())
431  cv_.notify_all();
432  }
433 
434  // Note that when Job::~Job is called, the last reference
435  // to the associated LoadEvent object (in the Job) may be destroyed.
436 }
437 
438 int
440 {
441  JobTypeInfo const& j(JobTypes::instance().get(type));
442  assert(j.type() != jtINVALID);
443 
444  return j.limit();
445 }
446 
447 } // namespace ripple
ripple::JobQueue::finishJob
void finishJob(JobType type)
Definition: JobQueue.cpp:367
ripple::JobQueue::m_jobSet
std::set< Job > m_jobSet
Definition: JobQueue.h:252
ripple::JobQueue::nSuspend_
int nSuspend_
Definition: JobQueue.h:263
std::bind
T bind(T... args)
std::string
STL class.
std::shared_ptr< Collector >
ripple::jtCLIENT
@ jtCLIENT
Definition: Job.h:51
ripple::Logs
Manages partitions for logging.
Definition: Log.h:48
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:309
ripple::JobTypes
Definition: JobTypes.h:32
Json::arrayValue
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
ripple::JobTypeData::execute
beast::insight::Event execute
Definition: JobTypeData.h:52
std::map::find
T find(T... args)
ripple::Workers::getNumberOfThreads
int getNumberOfThreads() const noexcept
Retrieve the desired number of threads.
Definition: Workers.cpp:53
ripple::JobQueue::getJobLimit
int getJobLimit(JobType type)
Definition: JobQueue.cpp:439
std::chrono::milliseconds
ripple::JobQueue::getJson
Json::Value getJson(int c=0)
Definition: JobQueue.cpp:203
ripple::LoadMonitor::Stats::count
std::uint64_t count
Definition: LoadMonitor.h:60
ripple::LoadMonitor::Stats::isOverloaded
bool isOverloaded
Definition: LoadMonitor.h:63
std::map::emplace
T emplace(T... args)
ripple::Workers::setNumberOfThreads
void setNumberOfThreads(int numberOfThreads)
Set the desired number of threads.
Definition: Workers.cpp:63
ripple::Job::queue_time
clock_type::time_point const & queue_time() const
Returns the time when the job was queued.
Definition: Job.cpp:65
std::lock_guard
STL class.
ripple::perf::PerfLog
Singleton class that maintains performance counters and optionally writes Json-formatted data to a di...
Definition: PerfLog.h:47
ripple::JobQueue::addRefCountedJob
bool addRefCountedJob(JobType type, std::string const &name, JobFunction const &func)
Definition: JobQueue.cpp:76
std::function
ripple::JobQueue::m_journal
beast::Journal m_journal
Definition: JobQueue.h:249
ripple::JobTypeInfo::limit
int limit() const
Definition: JobTypeInfo.h:77
ripple::JobQueue::JobQueue
JobQueue(beast::insight::Collector::ptr const &collector, beast::Journal journal, Logs &logs, perf::PerfLog &perfLog)
Definition: JobQueue.cpp:27
ripple::JobQueue::getJobCount
int getJobCount(JobType t) const
Jobs waiting at this priority.
Definition: JobQueue.cpp:106
ripple::JobQueue::jobCounter_
JobCounter jobCounter_
Definition: JobQueue.h:253
ripple::JobQueue::m_invalidJobData
JobTypeData m_invalidJobData
Definition: JobQueue.h:257
ripple::JobTypeData::dequeue
beast::insight::Event dequeue
Definition: JobTypeData.h:51
ripple::JobQueue::getJobCountTotal
int getJobCountTotal(JobType t) const
Jobs waiting plus running at this priority.
Definition: JobQueue.cpp:116
Json::Value::append
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:882
ripple::LoadMonitor::Stats
Definition: LoadMonitor.h:56
Json::objectValue
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:43
ripple::JobQueue::~JobQueue
~JobQueue()
Definition: JobQueue.cpp:62
ripple::JobQueue::hook
beast::insight::Hook hook
Definition: JobQueue.h:272
std::thread::hardware_concurrency
T hardware_concurrency(T... args)
ripple::JobQueue::m_mutex
std::mutex m_mutex
Definition: JobQueue.h:250
ripple::JobTypeInfo::type
JobType type() const
Definition: JobTypeInfo.h:65
ripple::JobQueue::m_collector
beast::insight::Collector::ptr m_collector
Definition: JobQueue.h:270
ripple::JobQueue::isOverloaded
bool isOverloaded()
Definition: JobQueue.cpp:189
std::unique_lock
STL class.
ripple::JobTypeInfo
Holds all the 'static' information about a job, which does not change.
Definition: JobTypeInfo.h:28
ripple::jtGENERIC
@ jtGENERIC
Definition: Job.h:77
ripple::jtINVALID
@ jtINVALID
Definition: Job.h:36
ripple::JobTypes::instance
static JobTypes const & instance()
Definition: JobTypes.h:103
ripple::JobQueue::processTask
void processTask(int instance) override
Perform a task.
Definition: JobQueue.cpp:386
ripple::JobQueue::m_jobData
JobDataMap m_jobData
Definition: JobQueue.h:256
beast::Journal::info
Stream info() const
Definition: Journal.h:321
ripple::JobTypeData
Definition: JobTypeData.h:29
ripple::Job
Definition: Job.h:85
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
std::condition_variable::wait
T wait(T... args)
ripple::JobQueue::getJobTypeData
JobTypeData & getJobTypeData(JobType type)
Definition: JobQueue.cpp:268
ripple::JobQueue::m_lastJob
std::uint64_t m_lastJob
Definition: JobQueue.h:251
ripple::perf::PerfLog::jobQueue
virtual void jobQueue(JobType const type)=0
Log queued job.
ripple::perf::PerfLog::jobStart
virtual void jobStart(JobType const type, microseconds dur, steady_time_point startTime, int instance)=0
Log job executing.
ripple::JobQueue::isStopped
bool isStopped() const
Definition: JobQueue.cpp:304
std::forward_as_tuple
T forward_as_tuple(T... args)
ripple::JobQueue::stopping_
std::atomic_bool stopping_
Definition: JobQueue.h:254
ripple::JobQueue::stopped_
std::atomic_bool stopped_
Definition: JobQueue.h:255
ripple::JobQueue::rendezvous
void rendezvous()
Block until no jobs running.
Definition: JobQueue.cpp:261
ripple::JobQueue::m_cancelCallback
Job::CancelCallback m_cancelCallback
Definition: JobQueue.h:266
ripple::JobQueue
A pool of threads to perform work.
Definition: JobQueue.h:55
std::min
T min(T... args)
ripple::JobQueue::stop
void stop()
Definition: JobQueue.cpp:282
ripple::JobQueue::getJobCountGE
int getJobCountGE(JobType t) const
All waiting jobs at or greater than this priority.
Definition: JobQueue.cpp:126
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::JobQueue::queueJob
void queueJob(Job const &job, std::lock_guard< std::mutex > const &lock)
Definition: JobQueue.cpp:310
ripple::JobQueue::perfLog_
perf::PerfLog & perfLog_
Definition: JobQueue.h:269
std
STL namespace.
ripple::LogicError
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
Definition: contract.cpp:48
ripple::JobQueue::setThreadCount
void setThreadCount(int c, bool const standaloneMode)
Set the number of thread serving the job queue to precisely this number.
Definition: JobQueue.cpp:143
std::chrono::milliseconds::count
T count(T... args)
ripple::Workers::addTask
void addTask()
Add a task to be performed.
Definition: Workers.cpp:126
ripple::ClosureCounter::join
void join(char const *name, std::chrono::milliseconds wait, beast::Journal j)
Returns once all counted in-flight closures are destroyed.
Definition: ClosureCounter.h:166
mutex
ripple::LoadMonitor::Stats::latencyAvg
std::chrono::milliseconds latencyAvg
Definition: LoadMonitor.h:61
beast::Journal::debug
Stream debug() const
Definition: Journal.h:315
ripple::JobType
JobType
Definition: Job.h:34
ripple::JobQueue::job_count
beast::insight::Gauge job_count
Definition: JobQueue.h:271
std::map::end
T end(T... args)
ripple::Job::getType
JobType getType() const
Definition: Job.cpp:52
ripple::JobQueue::m_processCount
int m_processCount
Definition: JobQueue.h:260
ripple::JobQueue::cv_
std::condition_variable cv_
Definition: JobQueue.h:274
ripple::JobQueue::makeLoadEvent
std::unique_ptr< LoadEvent > makeLoadEvent(JobType t, std::string const &name)
Return a scoped LoadEvent.
Definition: JobQueue.cpp:166
ripple::JobQueue::m_workers
Workers m_workers
Definition: JobQueue.h:265
ripple::JobQueue::addLoadEvents
void addLoadEvents(JobType t, int count, std::chrono::milliseconds elapsed)
Add multiple load events.
Definition: JobQueue.cpp:178
ripple::Job::doJob
void doJob()
Definition: Job.cpp:79
std::unique_ptr
STL class.
ripple::LoadMonitor::Stats::latencyPeak
std::chrono::milliseconds latencyPeak
Definition: LoadMonitor.h:62
beast::insight::Hook
A reference to a handler for performing polled collection.
Definition: Hook.h:31
std::condition_variable::notify_all
T notify_all(T... args)
std::set
STL class.
ripple::JobQueue::getNextJob
void getNextJob(Job &job)
Definition: JobQueue.cpp:333
ripple::perf::PerfLog::jobFinish
virtual void jobFinish(JobType const type, microseconds dur, int instance)=0
Log job finishing.
Json::Value
Represents a JSON value.
Definition: json_value.h:145
beast::insight::Event::notify
void notify(std::chrono::duration< Rep, Period > const &value) const
Push an event notification.
Definition: Event.h:64
ripple::get
T & get(EitherAmount &amt)
Definition: AmountSpec.h:118
ripple::JobQueue::collect
void collect()
Definition: JobQueue.cpp:69
std::chrono
std::chrono::steady_clock::now
T now(T... args)