aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--meta/classes/buildstats.bbclass24
-rw-r--r--meta/lib/buildstats.py47
2 files changed, 71 insertions, 0 deletions
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