diff --git a/repos/gems/run/cpu_load_display.run b/repos/gems/run/cpu_load_display.run new file mode 100644 index 0000000000..6c2e1d121f --- /dev/null +++ b/repos/gems/run/cpu_load_display.run @@ -0,0 +1,217 @@ +# +# Build +# + +if {[have_spec hw_odroid_xu]} { + puts "Run script not supported on this platform."; exit 0 } + +set build_components { + core init + drivers/timer + server/nitpicker + server/dynamic_rom + drivers/framebuffer drivers/input + app/trace_subject_reporter + app/cpu_load_display + app/cpu_burner +} + +lappend_if [have_spec usb] build_components drivers/usb +lappend_if [have_spec gpio] build_components drivers/gpio + +source ${genode_dir}/repos/base/run/platform_drv.inc +append_platform_drv_build_components + +build $build_components + +create_boot_directory + +# +# Generate config +# + +append config { + + + + + + + + + + + + + + + + + + } + +append_if [have_spec sdl] config { + + + + + + + } + +append_platform_drv_config + +append_if [have_spec framebuffer] config { + + + + + } + +append_if [have_spec gpio] config { + + + + + } + +append_if [have_spec imx53] config { + + + + + } + +append_if [have_spec ps2] config { + + + + } + +append_if [expr ![have_spec ps2] && [have_spec usb]] config { + + + + + } + +append config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +install_config $config + +# copy backdrop PNG images to bin directory +foreach file { genode_logo.png grid.png } { + file copy -force [genode_dir]/repos/gems/src/app/backdrop/$file bin/ } + + +# +# Boot modules +# + +# generic modules +set boot_modules { + core init + timer + nitpicker report_rom dynamic_rom + cpu_load_display cpu_burner trace_subject_reporter +} + +# platform-specific modules +append_platform_drv_boot_modules + +lappend_if [have_spec linux] boot_modules fb_sdl +lappend_if [have_spec ps2] boot_modules ps2_drv +lappend_if [have_spec framebuffer] boot_modules fb_drv +lappend_if [have_spec usb] boot_modules usb_drv +lappend_if [have_spec gpio] boot_modules gpio_drv +lappend_if [have_spec imx53] boot_modules input_drv + +build_boot_image $boot_modules + +append qemu_args " -m 256 " +append qemu_args " -smp 4,cores=4 " + +run_genode_until forever diff --git a/repos/gems/src/app/cpu_load_display/README b/repos/gems/src/app/cpu_load_display/README new file mode 100644 index 0000000000..623f06b1b5 --- /dev/null +++ b/repos/gems/src/app/cpu_load_display/README @@ -0,0 +1,2 @@ +This nitpicker client application generates a graph based on the data supplied +by the trace-subject reporter. diff --git a/repos/gems/src/app/cpu_load_display/main.cc b/repos/gems/src/app/cpu_load_display/main.cc new file mode 100644 index 0000000000..6b5fce5ac9 --- /dev/null +++ b/repos/gems/src/app/cpu_load_display/main.cc @@ -0,0 +1,472 @@ +/* + * \brief CPU load display + * \author Norman Feske + * \date 2015-06-30 + */ + +/* + * Copyright (C) 2015 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +/* Genode includes */ +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Cpu_load_display { + + class Timeline; + class Cpu; + class Cpu_registry; + template class Scene; + + typedef Genode::Xml_node Xml_node; + typedef Genode::Color Color; + + using Genode::max; +}; + + +class Cpu_load_display::Timeline : public Genode::List::Element +{ + public: + + enum { HISTORY_LEN = 32 }; + + typedef Genode::String<160> Label; + + private: + + unsigned const _subject_id = 0; + + unsigned _activity[HISTORY_LEN]; + + unsigned _sum_activity = 0; + + Label _label; + + /** + * Return hue value based on subject ID + */ + unsigned _hue() const + { + /* + * To get nicely varying hue values, we pass the subject ID + * to a hash function. + */ + unsigned int const a = 1588635695, q = 2, r = 1117695901; + return (a*(_subject_id % q) - r*(_subject_id / q)) & 255; + } + + public: + + Timeline(unsigned subject_id, Label const &label) + : + _subject_id(subject_id), _label(label) + { + Genode::memset(_activity, 0, sizeof(_activity)); + } + + void activity(unsigned long recent_activity, unsigned now) + { + unsigned const i = now % HISTORY_LEN; + + _sum_activity -= _activity[i]; + + _activity[i] = recent_activity; + + _sum_activity += recent_activity; + } + + unsigned long activity(unsigned i) const + { + return _activity[i % HISTORY_LEN]; + } + + bool has_subject_id(unsigned subject_id) const + { + return _subject_id == subject_id; + } + + bool idle() const { return _sum_activity == 0; } + + bool is_kernel() const + { + return _label == Label("kernel"); + } + + enum Color_type { COLOR_TOP, COLOR_BOTTOM }; + + Color color(Color_type type) const + { + unsigned const brightness = 140; + unsigned const saturation = type == COLOR_TOP ? 70 : 140; + unsigned const alpha = 230; + + Color const c = color_from_hsv(_hue(), saturation, brightness); + return Color(c.r, c.g, c.b, alpha); + } +}; + + +class Cpu_load_display::Cpu : public Genode::List::Element +{ + private: + + Genode::Point<> const _pos; + Genode::List _timelines; + + Timeline *_lookup_timeline(Xml_node subject) + { + unsigned long const subject_id = subject.attribute_value("id", 0UL); + + char label[sizeof(Timeline::Label)]; + subject.attribute("label").value(label, sizeof(label)); + + for (Timeline *t = _timelines.first(); t; t = t->next()) { + if (t->has_subject_id(subject_id)) + return t; + } + + /* add new timeline */ + Timeline *t = new (Genode::env()->heap()) Timeline(subject_id, label); + _timelines.insert(t); + return t; + } + + unsigned long _activity(Xml_node subject) + { + try { + Xml_node activity = subject.sub_node("activity"); + return activity.attribute_value("recent", 0UL); + } catch (Xml_node::Nonexistent_sub_node) { } + + return 0; + } + + public: + + Cpu(Genode::Point<> pos) : _pos(pos) { } + + bool has_pos(Genode::Point<> pos) const + { + return pos == _pos; + } + + void import_trace_subject(Xml_node subject, unsigned now) + { + unsigned long const activity = _activity(subject); + + if (activity) + _lookup_timeline(subject)->activity(activity, now); + } + + void advance(unsigned now) + { + Timeline *next = nullptr; + for (Timeline *t = _timelines.first(); t; t = next) { + + next = t->next(); + + t->activity(0, now); + + if (t->idle()) { + + PDBG("discard timeline"); + _timelines.remove(t); + Genode::destroy(Genode::env()->heap(), t); + } + } + } + + unsigned long activity_sum(unsigned i) const + { + unsigned long sum = 0; + + for (Timeline const *t = _timelines.first(); t; t = t->next()) + sum += t->activity(i); + + return sum; + } + + template + void for_each_timeline(FN const &fn) const + { + for (Timeline const *t = _timelines.first(); t; t = t->next()) + fn(*t); + } +}; + + +class Cpu_load_display::Cpu_registry +{ + private: + + Genode::List _cpus; + + static Genode::Point<> _cpu_pos(Xml_node subject) + { + try { + Xml_node affinity = subject.sub_node("affinity"); + return Genode::Point<>(affinity.attribute_value("xpos", 0UL), + affinity.attribute_value("ypos", 0UL)); + } catch (Xml_node::Nonexistent_sub_node) { } + + return Genode::Point<>(0, 0); + } + + Cpu *_lookup_cpu(Xml_node subject) + { + /* find CPU that matches the affinity of the subject */ + Genode::Point<> cpu_pos = _cpu_pos(subject); + for (Cpu *cpu = _cpus.first(); cpu; cpu = cpu->next()) { + if (cpu->has_pos(cpu_pos)) + return cpu; + } + + /* add new CPU */ + Cpu *cpu = new (Genode::env()->heap()) Cpu(cpu_pos); + _cpus.insert(cpu); + return cpu; + } + + void _import_trace_subject(Xml_node subject, unsigned now) + { + Cpu *cpu = _lookup_cpu(subject); + + cpu->import_trace_subject(subject, now); + } + + public: + + void import_trace_subjects(Xml_node node, unsigned now) + { + node.for_each_sub_node("subject", [&] (Xml_node subject) { + _import_trace_subject(subject, now); }); + } + + template + void for_each_cpu(FN const &fn) const + { + for (Cpu const *cpu = _cpus.first(); cpu; cpu = cpu->next()) + fn(*cpu); + } + + void advance(unsigned now) + { + for (Cpu *cpu = _cpus.first(); cpu; cpu = cpu->next()) + cpu->advance(now); + } +}; + + +template +class Cpu_load_display::Scene : public Nano3d::Scene +{ + private: + + Nitpicker::Area const _size; + + void _handle_config(unsigned) + { + Genode::config()->reload(); + } + + Genode::Signal_dispatcher _config_dispatcher; + + Genode::Attached_rom_dataspace _trace_subjects { "trace_subjects" }; + + unsigned _now = 0; + + Cpu_registry _cpu_registry; + + void _handle_trace_subjects(unsigned) + { + _trace_subjects.update(); + + if (!_trace_subjects.is_valid()) + return; + + _cpu_registry.advance(++_now); + + try { + Xml_node subjects(_trace_subjects.local_addr()); + _cpu_registry.import_trace_subjects(subjects, _now); + } catch (...) { PWRN("failed to import trace subjects"); } + } + + Genode::Signal_dispatcher _trace_subjects_dispatcher; + + public: + + Scene(Genode::Signal_receiver &sig_rec, unsigned update_rate_ms, + Nitpicker::Point pos, Nitpicker::Area size) + : + Nano3d::Scene(sig_rec, update_rate_ms, pos, size), _size(size), + _config_dispatcher(sig_rec, *this, &Scene::_handle_config), + _trace_subjects_dispatcher(sig_rec, *this, &Scene::_handle_trace_subjects) + { + Genode::config()->sigh(_config_dispatcher); + _handle_config(0); + + _trace_subjects.sigh(_trace_subjects_dispatcher); + } + + private: + + Polygon::Shaded_painter _shaded_painter { + *Genode::env()->heap(), _size.h() }; + + long _activity_sum[Timeline::HISTORY_LEN]; + long _y_level[Timeline::HISTORY_LEN]; + long _y_curr[Timeline::HISTORY_LEN]; + + void _plot_cpu(Genode::Surface &pixel, + Genode::Surface &alpha, + Cpu const &cpu, Nitpicker::Rect rect) + { + enum { HISTORY_LEN = Timeline::HISTORY_LEN }; + + /* calculate activity sum for each point in history */ + for (unsigned i = 0; i < HISTORY_LEN; i++) + _activity_sum[i] = cpu.activity_sum(i); + + for (unsigned i = 0; i < HISTORY_LEN; i++) + _y_level[i] = 0; + + int const h = rect.h(); + int const w = rect.w(); + + cpu.for_each_timeline([&] (Timeline const &timeline) { + + if (timeline.is_kernel()) + return; + + bool first = true; + + /* reset values of the current timeline */ + for (unsigned i = 0; i < HISTORY_LEN; i++) + _y_curr[i] = 0; + + Color const top_color = timeline.color(Timeline::COLOR_TOP); + Color const bottom_color = timeline.color(Timeline::COLOR_BOTTOM); + + for (unsigned i = 0; i < HISTORY_LEN; i++) { + + unsigned const t = (_now - i - 0) % HISTORY_LEN; + unsigned const prev_t = (_now - i + 1) % HISTORY_LEN; + + unsigned long const activity = timeline.activity(t); + + int const dy = _activity_sum[t] ? (activity*h) / _activity_sum[t] : 0; + + _y_curr[t] = _y_level[t] + dy; + + if (!first) { + + /* draw polygon */ + int const n = HISTORY_LEN - 1; + int const x0 = ((n - i + 0)*w)/n + rect.x1(); + int const x1 = ((n - i + 1)*w)/n + rect.x1(); + + int const y0 = rect.y1() + h - _y_curr[t]; + int const y1 = rect.y1() + h - _y_curr[prev_t]; + int const y2 = rect.y1() + h - _y_level[prev_t]; + int const y3 = rect.y1() + h - _y_level[t]; + + typedef Polygon::Shaded_painter::Point Point; + Point points[4]; + points[0] = Point(x0, y0, top_color); + points[1] = Point(x1, y1, top_color); + points[2] = Point(x1, y2, y1 == y2 ? top_color : bottom_color); + points[3] = Point(x0, y3, y3 == y0 ? top_color : bottom_color); + _shaded_painter.paint(pixel, alpha, points, 4); + + /* drop shadow */ + Color const black (0, 0, 0, 100); + Color const translucent (0, 0, 0, 0); + + points[0] = Point(x0, y3 - 5, translucent); + points[1] = Point(x1, y2 - 5, translucent); + points[2] = Point(x1, y2, black); + points[3] = Point(x0, y3, black); + + _shaded_painter.paint(pixel, alpha, points, 4); + } + first = false; + } + + /* raise level by the values of the current timeline */ + for (unsigned i = 0; i < HISTORY_LEN; i++) + _y_level[i] = _y_curr[i]; + + }); + } + + public: + + /** + * Scene interface + */ + void render(Genode::Surface &pixel, + Genode::Surface &alpha) override + { + /* background */ + Color const top_color = Color(10, 10, 10, 20); + Color const bottom_color = Color(10, 10, 10, 100); + + unsigned const w = pixel.size().w(); + unsigned const h = pixel.size().h(); + + typedef Polygon::Shaded_painter::Point Point; + Point points[4]; + points[0] = Point(0, 0, top_color); + points[1] = Point(w - 1, 0, top_color); + points[2] = Point(w - 1, h - 1, bottom_color); + points[3] = Point(0, h - 1, bottom_color); + _shaded_painter.paint(pixel, alpha, points, 4); + + /* determine number of CPUs */ + unsigned num_cpus = 0; + _cpu_registry.for_each_cpu([&] (Cpu const &cpu) { num_cpus++; }); + + if (num_cpus == 0) + return; + + /* plot graphs for the CPUs below each other */ + enum { GAP = 8 }; + Nitpicker::Point const step(0, _size.h()/num_cpus); + Nitpicker::Area const size(_size.w(), step.y() - GAP); + Nitpicker::Point point(0, GAP/2); + + _cpu_registry.for_each_cpu([&] (Cpu const &cpu) { + _plot_cpu(pixel, alpha, cpu, Nitpicker::Rect(point, size)); + point = point + step; + }); + } +}; + + +int main(int argc, char **argv) +{ + static Genode::Signal_receiver sig_rec; + + enum { UPDATE_RATE_MS = 250 }; + + static Cpu_load_display::Scene + scene(sig_rec, UPDATE_RATE_MS, + Nitpicker::Point(0, 0), Nitpicker::Area(400, 400)); + + scene.dispatch_signals_loop(sig_rec); + + return 0; +} diff --git a/repos/gems/src/app/cpu_load_display/target.mk b/repos/gems/src/app/cpu_load_display/target.mk new file mode 100644 index 0000000000..bec9c28d5d --- /dev/null +++ b/repos/gems/src/app/cpu_load_display/target.mk @@ -0,0 +1,4 @@ +TARGET = cpu_load_display +SRC_CC = main.cc +LIBS = base config +INC_DIR += $(PRG_DIR)