summaryrefslogtreecommitdiffstats
path: root/src/server
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2016-10-01 05:46:29 -0400
committerDavid Robillard <d@drobilla.net>2016-10-02 12:24:57 -0400
commit0f50e9239bbda77ce38f297870f8cf4158025acc (patch)
tree89e255ade0257ca3f1adf9d1e9d8a81195b45d15 /src/server
parent9b8bce71893ef450992f82a28a6a0287c479baaf (diff)
downloadingen-0f50e9239bbda77ce38f297870f8cf4158025acc.tar.gz
ingen-0f50e9239bbda77ce38f297870f8cf4158025acc.tar.bz2
ingen-0f50e9239bbda77ce38f297870f8cf4158025acc.zip
Show audio and load information in status line
Diffstat (limited to 'src/server')
-rw-r--r--src/server/Engine.cpp63
-rw-r--r--src/server/Engine.hpp55
-rw-r--r--src/server/GraphImpl.cpp3
-rw-r--r--src/server/RunContext.hpp15
-rw-r--r--src/server/events/Get.cpp12
5 files changed, 137 insertions, 11 deletions
diff --git a/src/server/Engine.cpp b/src/server/Engine.cpp
index 1a0ab0a0..893f6f92 100644
--- a/src/server/Engine.cpp
+++ b/src/server/Engine.cpp
@@ -83,9 +83,11 @@ Engine::Engine(Ingen::World* world)
, _worker(new Worker(world->log(), event_queue_size()))
, _sync_worker(new Worker(world->log(), event_queue_size(), true))
, _listener(NULL)
+ , _cycle_start_time(0)
, _rand_engine(0)
, _uniform_dist(0.0f, 1.0f)
, _quit_flag(false)
+ , _reset_load_flag(false)
, _direct_driver(true)
, _atomic_bundles(world->conf().option("atomic-bundles").get<int32_t>())
{
@@ -264,8 +266,29 @@ Engine::quit()
bool
Engine::main_iteration()
{
+ const Ingen::URIs& uris = world()->uris();
+
_post_processor->process();
_maid->cleanup();
+
+ if (_event_load.changed) {
+ _broadcaster->set_property(Raul::URI("ingen:/engine"),
+ uris.ingen_maxEventLoad,
+ uris.forge.make(_event_load.max / 100.0f));
+ _event_load.changed = false;
+ }
+
+ if (_run_load.changed) {
+ _broadcaster->put(Raul::URI("ingen:/engine"),
+ { { uris.ingen_meanRunLoad,
+ uris.forge.make(floorf(_run_load.mean) / 100.0f) },
+ { uris.ingen_minRunLoad,
+ uris.forge.make(_run_load.min / 100.0f) },
+ { uris.ingen_maxRunLoad,
+ uris.forge.make(_run_load.max / 100.0f) } });
+ _run_load.changed = false;
+ }
+
return !_quit_flag;
}
@@ -275,6 +298,7 @@ Engine::set_driver(SPtr<Driver> driver)
_driver = driver;
for (RunContext* ctx : _run_contexts) {
ctx->set_priority(driver->real_time_priority());
+ ctx->set_rate(driver->sample_rate());
}
}
@@ -285,14 +309,31 @@ Engine::event_time()
return 0;
}
- const SampleCount start = _direct_driver
+ // FIXME: Jitter with direct driver
+ const SampleCount now = _direct_driver
? run_context().start()
: _driver->frame_time();
- /* Exactly one cycle latency (some could run ASAP if we get lucky, but not
- always, and a slight constant latency is far better than jittery lower
- (average) latency */
- return start + _driver->block_length();
+ return now + _driver->block_length();
+}
+
+uint64_t
+Engine::current_time(const RunContext& context) const
+{
+ struct timespec time;
+#ifdef CLOCK_MONOTONIC_RAW
+ clock_gettime(CLOCK_MONOTONIC_RAW, &time);
+#else
+ clock_gettime(CLOCK_MONOTONIC, &time);
+#endif
+
+ return (uint64_t)time.tv_sec * 1e6 + (uint64_t)time.tv_nsec / 1e3;
+}
+
+void
+Engine::reset_load()
+{
+ _reset_load_flag = true;
}
void
@@ -367,6 +408,7 @@ unsigned
Engine::run(uint32_t sample_count)
{
RunContext& ctx = run_context();
+ _cycle_start_time = current_time(ctx);
// Apply control bindings to input
control_bindings()->pre_process(
@@ -377,6 +419,7 @@ Engine::run(uint32_t sample_count)
// Process events that came in during the last cycle
// (Aiming for jitter-free 1 block event latency, ideally)
const unsigned n_processed_events = process_events();
+ const uint64_t t_events = current_time(ctx);
// Run root graph
if (_root_graph) {
@@ -387,6 +430,16 @@ Engine::run(uint32_t sample_count)
ctx, _root_graph->port_impl(1)->buffer(0).get());
}
+ // Update load for this cycle
+ if (ctx.duration() > 0) {
+ _event_load.update(t_events - _cycle_start_time, ctx.duration());
+ _run_load.update(current_time(ctx) - t_events, ctx.duration());
+ if (_reset_load_flag) {
+ _run_load = Load();
+ _reset_load_flag = false;
+ }
+ }
+
return n_processed_events;
}
diff --git a/src/server/Engine.hpp b/src/server/Engine.hpp
index 2d5e053e..fc80d64b 100644
--- a/src/server/Engine.hpp
+++ b/src/server/Engine.hpp
@@ -89,8 +89,27 @@ public:
void set_driver(SPtr<Driver> driver);
+ /** Return the frame time to execute an event that arrived now.
+ *
+ * This aims to return a time one cycle from "now", so that events ideally
+ * have 1 cycle of latency with no jitter.
+ */
SampleCount event_time();
+ /** Return the time this cycle began processing in microseconds.
+ *
+ * This value is comparable to the value returned by current_time().
+ */
+ inline uint64_t cycle_start_time(const RunContext& context) const {
+ return _cycle_start_time;
+ }
+
+ /** Return the current time in microseconds. */
+ uint64_t current_time(const RunContext& context) const;
+
+ /** Reset the load statistics (when the expected DSP load changes). */
+ void reset_load();
+
/** Enqueue an event to be processed (non-realtime threads only). */
void enqueue_event(Event* ev, Event::Mode mode=Event::Mode::NORMAL);
@@ -128,11 +147,41 @@ public:
SPtr<Store> store() const;
size_t event_queue_size() const;
- bool atomic_bundles() const { return _atomic_bundles; }
+ size_t n_threads() const { return _run_contexts.size(); }
+ bool atomic_bundles() const { return _atomic_bundles; }
private:
Ingen::World* _world;
+ struct Load {
+ void update(uint64_t time, uint64_t available) {
+ const uint64_t load = time * 100 / available;
+ if (load < min) {
+ min = load;
+ changed = true;
+ }
+ if (load > max) {
+ max = load;
+ changed = true;
+ }
+ if (++n == 1) {
+ mean = load;
+ } else {
+ const float a = mean + ((float)load - mean) / (float)++n;
+ if (a != mean) {
+ changed = floorf(a) != floorf(mean);
+ mean = a;
+ }
+ }
+ }
+
+ uint64_t min = std::numeric_limits<uint64_t>::max();
+ uint64_t max = 0;
+ float mean = 0.0f;
+ uint64_t n = 0;
+ bool changed = false;
+ };
+
BlockFactory* _block_factory;
Broadcaster* _broadcaster;
BufferFactory* _buffer_factory;
@@ -153,6 +202,9 @@ private:
SocketListener* _listener;
std::vector<RunContext*> _run_contexts;
+ uint64_t _cycle_start_time;
+ Load _event_load;
+ Load _run_load;
std::mt19937 _rand_engine;
std::uniform_real_distribution<float> _uniform_dist;
@@ -161,6 +213,7 @@ private:
std::mutex _tasks_mutex;
bool _quit_flag;
+ bool _reset_load_flag;
bool _direct_driver;
bool _atomic_bundles;
};
diff --git a/src/server/GraphImpl.cpp b/src/server/GraphImpl.cpp
index 22276a34..bdd0a69a 100644
--- a/src/server/GraphImpl.cpp
+++ b/src/server/GraphImpl.cpp
@@ -1,6 +1,6 @@
/*
This file is part of Ingen.
- Copyright 2007-2015 David Robillard <http://drobilla.net/>
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
Ingen is free software: you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free
@@ -302,6 +302,7 @@ GraphImpl::set_compiled_graph(CompiledGraph* cg)
{
if (_compiled_graph && _compiled_graph != cg) {
_engine.maid()->dispose(_compiled_graph);
+ _engine.reset_load();
}
_compiled_graph = cg;
}
diff --git a/src/server/RunContext.hpp b/src/server/RunContext.hpp
index 803e0c3b..eba30545 100644
--- a/src/server/RunContext.hpp
+++ b/src/server/RunContext.hpp
@@ -87,6 +87,18 @@ public:
/** Return true iff any notifications are pending. */
bool pending_notifications() const { return _event_sink->read_space(); }
+ /** Return the duration of this cycle in microseconds.
+ *
+ * This is the cycle length in frames (nframes) converted to microseconds,
+ * that is, the amount of real time that this cycle's audio represents.
+ * Note that this is unrelated to the amount of time available to execute a
+ * cycle (other than the fact that it must be processed in significantly
+ * less time to avoid a dropout when running in real time).
+ */
+ inline uint64_t duration() const {
+ return (uint64_t)_nframes * 1e6 / _rate;
+ }
+
inline void locate(FrameTime s, SampleCount nframes) {
_start = s;
_end = s + nframes;
@@ -103,6 +115,7 @@ public:
}
void set_priority(int priority);
+ void set_rate(SampleCount rate) { _rate = rate; }
inline Engine& engine() const { return _engine; }
inline Task* task() const { return _task; }
@@ -112,6 +125,7 @@ public:
inline FrameTime end() const { return _end; }
inline SampleCount offset() const { return _offset; }
inline SampleCount nframes() const { return _nframes; }
+ inline SampleCount rate() const { return _rate; }
inline bool realtime() const { return _realtime; }
protected:
@@ -129,6 +143,7 @@ protected:
FrameTime _end; ///< End frame of this cycle, timeline relative
SampleCount _offset; ///< Offset into data buffers
SampleCount _nframes; ///< Number of frames past offset to process
+ SampleCount _rate; ///< Sample rate in Hz
bool _realtime; ///< True iff context is hard realtime
bool _copy; ///< True iff this is a copy (shared event_sink)
};
diff --git a/src/server/events/Get.cpp b/src/server/events/Get.cpp
index fa56f23a..bec57104 100644
--- a/src/server/events/Get.cpp
+++ b/src/server/events/Get.cpp
@@ -1,6 +1,6 @@
/*
This file is part of Ingen.
- Copyright 2007-2015 David Robillard <http://drobilla.net/>
+ Copyright 2007-2016 David Robillard <http://drobilla.net/>
Ingen is free software: you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free
@@ -90,10 +90,14 @@ Get::post_process()
} else if (_uri == "ingen:/engine") {
// TODO: Keep a proper RDF model of the engine
URIs& uris = _engine.world()->uris();
- _request_client->set_property(
+ _request_client->put(
Raul::URI("ingen:/engine"),
- uris.param_sampleRate,
- uris.forge.make(int32_t(_engine.driver()->sample_rate())));
+ { { uris.param_sampleRate,
+ uris.forge.make(int32_t(_engine.driver()->sample_rate())) },
+ { uris.bufsz_maxBlockLength,
+ uris.forge.make(int32_t(_engine.driver()->block_length())) },
+ { uris.ingen_numThreads,
+ uris.forge.make(int32_t(_engine.n_threads())) } });
} else {
_response.send(_request_client.get());
}