From 6f4e8180b5b4857eaf6caf410fd3a4a41ed85930 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Wed, 30 Nov 2016 10:50:01 +0100 Subject: buildstats: add system state sampling /proc/[diskstats|meminfo|stat] get sampled and written to the same proc_.log files as during normal bootchat logging. This will allow rendering the CPU, disk and memory usage charts. Right now sampling happens once a second, triggered by the heartbeat event.That produces quite a bit of data for long builds, which will be addressed in a separate commit by storing the data in a more compact form. Signed-off-by: Patrick Ohly Signed-off-by: Ross Burton --- meta/classes/buildstats.bbclass | 24 +++++++++++++++++++++ meta/lib/buildstats.py | 47 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 meta/lib/buildstats.py diff --git a/meta/classes/buildstats.bbclass b/meta/classes/buildstats.bbclass index 57ecc8fee8..9c0c37dcdd 100644 --- a/meta/classes/buildstats.bbclass +++ b/meta/classes/buildstats.bbclass @@ -188,3 +188,27 @@ python run_buildstats () { addhandler run_buildstats run_buildstats[eventmask] = "bb.event.BuildStarted bb.event.BuildCompleted bb.build.TaskStarted bb.build.TaskSucceeded bb.build.TaskFailed" +python runqueue_stats () { + import buildstats + from bb import event, runqueue + # We should not record any samples before the first task has started, + # because that's the first activity shown in the process chart. + # Besides, at that point we are sure that the build variables + # are available that we need to find the output directory. + # The persistent SystemStats is stored in the datastore and + # closed when the build is done. + system_stats = d.getVar('_buildstats_system_stats', True) + if not system_stats and isinstance(e, (bb.runqueue.sceneQueueTaskStarted, bb.runqueue.runQueueTaskStarted)): + system_stats = buildstats.SystemStats(d) + d.setVar('_buildstats_system_stats', system_stats) + if system_stats: + # Ensure that we sample at important events. + done = isinstance(e, bb.event.BuildCompleted) + system_stats.sample(force=done) + if done: + system_stats.close() + d.delVar('_buildstats_system_stats') +} + +addhandler runqueue_stats +runqueue_stats[eventmask] = "bb.runqueue.sceneQueueTaskStarted bb.runqueue.runQueueTaskStarted bb.event.HeartbeatEvent bb.event.BuildCompleted" diff --git a/meta/lib/buildstats.py b/meta/lib/buildstats.py new file mode 100644 index 0000000000..8ce4112c2d --- /dev/null +++ b/meta/lib/buildstats.py @@ -0,0 +1,47 @@ +# Implements system state sampling. Called by buildstats.bbclass. +# Because it is a real Python module, it can hold persistent state, +# like open log files and the time of the last sampling. + +import time + +class SystemStats: + def __init__(self, d): + bn = d.getVar('BUILDNAME', True) + bsdir = os.path.join(d.getVar('BUILDSTATS_BASE', True), bn) + bb.utils.mkdirhier(bsdir) + + self.proc_files = [] + for filename in ('diskstats', 'meminfo', 'stat'): + # In practice, this class gets instantiated only once in + # the bitbake cooker process. Therefore 'append' mode is + # not strictly necessary, but using it makes the class + # more robust should two processes ever write + # concurrently. + self.proc_files.append((filename, + open(os.path.join(bsdir, 'proc_%s.log' % filename), 'ab'))) + # Last time that we sampled data. + self.last = 0 + # Minimum number of seconds between recording a sample. This + # becames relevant when we get called very often while many + # short tasks get started. Sampling during quiet periods + # depends on the heartbeat event, which fires less often. + self.min_seconds = 1 + + def close(self): + self.monitor_disk.close() + for _, output, _ in self.proc_files: + output.close() + + def sample(self, force): + now = time.time() + if (now - self.last > self.min_seconds) or force: + for filename, output in self.proc_files: + with open(os.path.join('/proc', filename), 'rb') as input: + data = input.read() + # Unbuffered raw write, less overhead and useful + # in case that we end up with concurrent writes. + os.write(output.fileno(), + ('%.0f\n' % now).encode('ascii') + + data + + b'\n') + self.last = now -- cgit 1.2.3-korg