diff options
-rw-r--r-- | ingen/World.hpp | 14 | ||||
-rw-r--r-- | src/World.cpp | 55 | ||||
-rw-r--r-- | src/gui/ingen_gui_lv2.cpp | 5 | ||||
-rw-r--r-- | src/ingen/ingen.cpp | 3 | ||||
-rw-r--r-- | src/server/CompiledGraph.cpp | 84 | ||||
-rw-r--r-- | src/server/CompiledGraph.hpp | 18 | ||||
-rw-r--r-- | src/server/Engine.cpp | 1 | ||||
-rw-r--r-- | src/server/Task.cpp | 83 | ||||
-rw-r--r-- | src/server/Task.hpp | 49 | ||||
-rw-r--r-- | src/server/ingen_lv2.cpp | 6 | ||||
-rw-r--r-- | tests/TestClient.hpp | 90 | ||||
-rw-r--r-- | tests/ingen_bench.cpp | 189 | ||||
-rw-r--r-- | tests/ingen_test.cpp | 74 | ||||
-rw-r--r-- | wscript | 19 |
14 files changed, 458 insertions, 232 deletions
diff --git a/ingen/World.hpp b/ingen/World.hpp index fc77593a..b0e09b34 100644 --- a/ingen/World.hpp +++ b/ingen/World.hpp @@ -64,20 +64,20 @@ class URIs; class INGEN_API World : public Raul::Noncopyable { public: /** Construct a new Ingen world. - * @param argc Argument count (as in C main()) - * @param argv Argument vector (as in C main()) * @param map LV2 URID map implementation, or NULL to use internal. * @param unmap LV2 URID unmap implementation, or NULL to use internal. * @param log LV2 log implementation, or NULL to use internal. */ - World(int& argc, - char**& argv, - LV2_URID_Map* map, - LV2_URID_Unmap* unmap, - LV2_Log_Log* log); + World(LV2_URID_Map* map, LV2_URID_Unmap* unmap, LV2_Log_Log* log); virtual ~World(); + /** Load configuration from files and command line. + * @param argc Argument count (as in C main()) + * @param argv Argument vector (as in C main()) + */ + virtual void load_configuration(int& argc, char**& argv); + /** Load an Ingen module by name (e.g. "server", "gui", etc.) * @return True on success. */ diff --git a/src/World.cpp b/src/World.cpp index 38b27a9a..d6a1fc1c 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -98,14 +98,12 @@ ingen_load_module(Log& log, const string& name) class World::Impl { public: - Impl(int& a_argc, - char**& a_argv, - LV2_URID_Map* map, + Impl(LV2_URID_Map* map, LV2_URID_Unmap* unmap, LV2_Log_Log* lv2_log) - : argc(a_argc) - , argv(a_argv) - , lv2_features(NULL) + : argc(nullptr) + , argv(nullptr) + , lv2_features(nullptr) , rdf_world(new Sord::World()) , lilv_world(lilv_world_new()) , uri_map(new URIMap(log, map, unmap)) @@ -114,17 +112,6 @@ public: , conf(*forge) , log(lv2_log, *uris) { - // Parse default configuration files - std::list<std::string> files = conf.load_default("ingen", "options.ttl"); - for (const auto& f : files) { - log.info(fmt("Loaded configuration %1%\n") % f); - } - - // Parse command line options, overriding configuration file values - conf.parse(argc, argv); - log.set_flush(conf.option("flush-log").get<int32_t>()); - log.set_trace(conf.option("trace").get<int32_t>()); - lv2_features = new LV2Features(); lv2_features->add_feature(uri_map->urid_map_feature()); lv2_features->add_feature(uri_map->urid_unmap_feature()); @@ -208,8 +195,8 @@ public: typedef std::map<const std::string, ScriptRunner> ScriptRunners; ScriptRunners script_runners; - int& argc; - char**& argv; + int* argc; + char*** argv; LV2Features* lv2_features; Sord::World* rdf_world; LilvWorld* lilv_world; @@ -228,12 +215,8 @@ public: std::string jack_uuid; }; -World::World(int& argc, - char**& argv, - LV2_URID_Map* map, - LV2_URID_Unmap* unmap, - LV2_Log_Log* log) - : _impl(new Impl(argc, argv, map, unmap, log)) +World::World(LV2_URID_Map* map, LV2_URID_Unmap* unmap, LV2_Log_Log* log) + : _impl(new Impl(map, unmap, log)) { _impl->serialiser = SPtr<Serialiser>(new Serialiser(*this)); _impl->parser = SPtr<Parser>(new Parser()); @@ -244,6 +227,24 @@ World::~World() delete _impl; } +void +World::load_configuration(int& argc, char**& argv) +{ + _impl->argc = &argc; + _impl->argv = &argv; + + // Parse default configuration files + const auto files = _impl->conf.load_default("ingen", "options.ttl"); + for (const auto& f : files) { + _impl->log.info(fmt("Loaded configuration %1%\n") % f); + } + + // Parse command line options, overriding configuration file values + _impl->conf.parse(argc, argv); + _impl->log.set_flush(_impl->conf.option("flush-log").get<int32_t>()); + _impl->log.set_trace(_impl->conf.option("trace").get<int32_t>()); +} + void World::set_engine(SPtr<EngineBase> e) { _impl->engine = e; } void World::set_interface(SPtr<Interface> i) { _impl->interface = i; } void World::set_store(SPtr<Store> s) { _impl->store = s; } @@ -254,8 +255,8 @@ SPtr<Parser> World::parser() { return _impl->parser; } SPtr<Serialiser> World::serialiser() { return _impl->serialiser; } SPtr<Store> World::store() { return _impl->store; } -int& World::argc() { return _impl->argc; } -char**& World::argv() { return _impl->argv; } +int& World::argc() { return *_impl->argc; } +char**& World::argv() { return *_impl->argv; } Configuration& World::conf() { return _impl->conf; } Log& World::log() { return _impl->log; } diff --git a/src/gui/ingen_gui_lv2.cpp b/src/gui/ingen_gui_lv2.cpp index 1f950089..005ed270 100644 --- a/src/gui/ingen_gui_lv2.cpp +++ b/src/gui/ingen_gui_lv2.cpp @@ -110,10 +110,11 @@ instantiate(const LV2UI_Descriptor* descriptor, } } - ui->world = new Ingen::World(ui->argc, ui->argv, map, unmap, log); - + ui->world = new Ingen::World(map, unmap, log); ui->forge = new Ingen::Forge(ui->world->uri_map()); + ui->world->load_configuration(ui->argc, ui->argv); + if (!ui->world->load_module("client")) { delete ui; return NULL; diff --git a/src/ingen/ingen.cpp b/src/ingen/ingen.cpp index 9f939621..e4f0a946 100644 --- a/src/ingen/ingen.cpp +++ b/src/ingen/ingen.cpp @@ -90,7 +90,8 @@ main(int argc, char** argv) // Create world try { - world = new Ingen::World(argc, argv, NULL, NULL, NULL); + world = new Ingen::World(NULL, NULL, NULL); + world->load_configuration(argc, argv); if (argc <= 1) { world->conf().print_usage("ingen", cout); return EXIT_FAILURE; diff --git a/src/server/CompiledGraph.cpp b/src/server/CompiledGraph.cpp index b808cb90..5553140c 100644 --- a/src/server/CompiledGraph.cpp +++ b/src/server/CompiledGraph.cpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2015-2016 David Robillard <http://drobilla.net/> + Copyright 2015-2017 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 @@ -43,7 +43,7 @@ public: CompiledGraph::CompiledGraph(GraphImpl* graph) : _log(graph->engine().log()) , _path(graph->path()) - , _master(Task::Mode::SEQUENTIAL) + , _master(new Task(Task::Mode::PARALLEL)) { compile_graph(graph); } @@ -76,7 +76,7 @@ CompiledGraph::compile_set(const std::set<BlockImpl*>& blocks, // Each block is the start of a new sequential task Task seq(Task::Mode::SEQUENTIAL); compile_block(block, seq, k); - task.push_back(seq); + task.push_front(std::move(seq)); } } @@ -91,41 +91,29 @@ CompiledGraph::compile_graph(GraphImpl* graph) // Mark all blocks as unvisited initially b.set_mark(BlockImpl::Mark::UNVISITED); - if (b.providers().empty()) { - // Block has no dependencies, add to initial working set + if (b.dependants().empty()) { + // Block has no dependants, add to initial working set blocks.insert(&b); } } - // Compile initial working set into master task - Task start(Task::Mode::PARALLEL); - std::set<BlockImpl*> next; - compile_set(blocks, start, next); - _master.push_back(start); - // Keep compiling working set until all connected nodes are visited - while (!next.empty()) { - blocks.clear(); + while (!blocks.empty()) { + std::set<BlockImpl*> next; + // The working set is a parallel task... Task par(Task::Mode::PARALLEL); - for (BlockImpl* block : next) { - // ... where each block is the start of a new sequential task + for (BlockImpl* block : blocks) { + // ... where each block is the tail of a new sequential task Task seq(Task::Mode::SEQUENTIAL); - compile_block(block, seq, blocks); - par.push_back(seq); - } - _master.push_back(par); - next = blocks; - } - - // Compile any nodes that weren't reached (disconnected cycles) - for (auto& b : graph->blocks()) { - if (b.get_mark() == BlockImpl::Mark::UNVISITED) { - compile_block(&b, _master, next); + compile_block(block, seq, next); + par.push_front(std::move(seq)); } + _master->push_front(std::move(par)); + blocks = next; } - _master.simplify(); + _master = Task::simplify(std::move(_master)); if (graph->engine().world()->conf().option("trace").get<int32_t>()) { dump([this](const std::string& msg) { @@ -160,23 +148,23 @@ check_feedback(const BlockImpl* root, BlockImpl* dependant) } void -CompiledGraph::compile_dependant(const BlockImpl* root, - BlockImpl* block, - Task& task, - std::set<BlockImpl*>& k) +CompiledGraph::compile_provider(const BlockImpl* root, + BlockImpl* block, + Task& task, + std::set<BlockImpl*>& k) { - if (block->providers().size() > 1) { - /* Dependant has other providers, so this is the start of a sequential task. - Add dependant to future working set and stop traversal. */ + if (block->dependants().size() > 1) { + /* Provider has other dependants, so this is the start of a sequential task. + Add provider to future working set and stop traversal. */ check_feedback(root, block); k.insert(block); } else { - // Dependant has only this provider, add here + // Provider has only this dependant, add here if (task.mode() == Task::Mode::PARALLEL) { // Inside a parallel task, compile into a new sequential child Task seq(Task::Mode::SEQUENTIAL); compile_block(block, seq, k); - task.push_back(seq); + task.push_front(std::move(seq)); } else { // Append to given sequential task compile_block(block, task, k); @@ -191,21 +179,21 @@ CompiledGraph::compile_block(BlockImpl* n, Task& task, std::set<BlockImpl*>& k) case BlockImpl::Mark::UNVISITED: n->set_mark(BlockImpl::Mark::VISITING); - // Execute this task before the dependants to follow - task.push_back(Task(Task::Mode::SINGLE, n)); + // Execute this task after the providers to follow + task.push_front(Task(Task::Mode::SINGLE, n)); - if (n->dependants().size() < 2) { - // Single dependant, append to this sequential task - for (auto& d : n->dependants()) { - compile_dependant(n, d, task, k); + if (n->providers().size() < 2) { + // Single provider, append to this sequential task + for (auto& d : n->providers()) { + compile_provider(n, d, task, k); } } else { - // Multiple dependants, create a new parallel task + // Multiple providers, create a new parallel task Task par(Task::Mode::PARALLEL); - for (auto& d : n->dependants()) { - compile_dependant(n, d, par, k); + for (auto& d : n->providers()) { + compile_provider(n, d, par, k); } - task.push_back(par); + task.push_front(std::move(par)); } n->set_mark(BlockImpl::Mark::VISITED); break; @@ -221,7 +209,7 @@ CompiledGraph::compile_block(BlockImpl* n, Task& task, std::set<BlockImpl*>& k) void CompiledGraph::run(RunContext& context) { - _master.run(context); + _master->run(context); } void @@ -229,7 +217,7 @@ CompiledGraph::dump(std::function<void (const std::string&)> sink) const { sink("(compiled-graph "); sink(_path); - _master.dump(sink, 2, false); + _master->dump(sink, 2, false); sink(")\n"); } diff --git a/src/server/CompiledGraph.hpp b/src/server/CompiledGraph.hpp index eeeb6111..db9aa288 100644 --- a/src/server/CompiledGraph.hpp +++ b/src/server/CompiledGraph.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2016 David Robillard <http://drobilla.net/> + Copyright 2007-2017 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 @@ -63,14 +63,14 @@ private: void compile_graph(GraphImpl* graph); void compile_set(const BlockSet& blocks, Task& task, BlockSet& k); void compile_block(BlockImpl* block, Task& task, BlockSet& k); - void compile_dependant(const BlockImpl* root, - BlockImpl* block, - Task& task, - BlockSet& k); - - Log& _log; - const Raul::Path _path; - Task _master; + void compile_provider(const BlockImpl* root, + BlockImpl* block, + Task& task, + BlockSet& k); + + Log& _log; + const Raul::Path _path; + std::unique_ptr<Task> _master; }; } // namespace Server diff --git a/src/server/Engine.cpp b/src/server/Engine.cpp index 794088bc..5561c36e 100644 --- a/src/server/Engine.cpp +++ b/src/server/Engine.cpp @@ -102,6 +102,7 @@ Engine::Engine(Ingen::World* world) Raul::RingBuffer* ring = new Raul::RingBuffer(24 * event_queue_size()); _notifications.push_back(ring); _run_contexts.push_back(new RunContext(*this, ring, i, i > 0)); + _run_contexts.back()->set_priority(90); // FIXME } _world->lv2_features().add_feature(_worker->schedule_feature()); diff --git a/src/server/Task.cpp b/src/server/Task.cpp index e19855d3..bf1f5028 100644 --- a/src/server/Task.cpp +++ b/src/server/Task.cpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2015-2016 David Robillard <http://drobilla.net/> + Copyright 2015-2017 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 @@ -30,14 +30,14 @@ Task::run(RunContext& context) _block->process(context); break; case Mode::SEQUENTIAL: - for (Task& task : *this) { - task.run(context); + for (const auto& task : _children) { + task->run(context); } break; case Mode::PARALLEL: // Initialize (not) done state of sub-tasks - for (Task& task : *this) { - task.set_done(false); + for (const auto& task : _children) { + task->set_done(false); } // Grab the first sub-task @@ -65,12 +65,12 @@ Task::steal(RunContext& context) { if (_mode == Mode::PARALLEL) { const unsigned i = _next++; - if (i < size()) { - return &(*this)[i]; + if (i < _children.size()) { + return _children[i].get(); } } - return NULL; + return nullptr; } Task* @@ -82,52 +82,65 @@ Task::get_task(RunContext& context) return t; } + int num_spins = 0; while (true) { // Push done end index as forward as possible - for (; _done_end < size() && (*this)[_done_end].done(); ++_done_end) {} + while (_done_end < _children.size() && _children[_done_end]->done()) { + ++_done_end; + } - if (_done_end >= size()) { - return NULL; // All child tasks are finished + if (_done_end >= _children.size()) { + return nullptr; // All child tasks are finished } // All child tasks claimed, but some are unfinished, steal a task - t = context.engine().steal_task(context.id() + 1); - if (t) { + if ((t = context.engine().steal_task(context.id() + 1))) { return t; } + ++num_spins; /* All child tasks are claimed, and we failed to steal any tasks. Spin to prevent blocking, though it would probably be wiser to wait for a signal in non-main threads, and maybe even in the main thread depending on your real-time safe philosophy... more experimentation here is needed. */ } + + if (num_spins > 0) { + fprintf(stderr, "Num spins: %u\n", num_spins); + } } -void -Task::simplify() +std::unique_ptr<Task> +Task::simplify(std::unique_ptr<Task>&& task) { - if (_mode != Mode::SINGLE) { - for (std::vector<Task>::iterator t = begin(); t != end();) { - t->simplify(); - if (t->mode() != Mode::SINGLE && t->empty()) { - // Empty task, erase - t = erase(t); - } else if (t->mode() == _mode) { - // Subtask with the same type, fold child into parent - const Task child(*t); - t = erase(t); - t = insert(t, child.begin(), child.end()); - } else { - ++t; - } - } + if (task->mode() == Mode::SINGLE) { + return std::move(task); + } - if (size() == 1) { - const Task t(front()); - *this = t; + for (auto t = task->_children.begin(); t != task->_children.end();) { + *t = simplify(std::move(*t)); + if ((*t)->empty()) { + // Empty task, erase + t = task->_children.erase(t); + } else if ((*t)->mode() == task->_mode) { + // Subtask with the same type, fold child into parent + std::unique_ptr<Task> child(std::move(*t)); + t = task->_children.erase(t); + t = task->_children.insert( + t, + std::make_move_iterator(child->_children.begin()), + std::make_move_iterator(child->_children.end())); + } else { + ++t; } } + + if (task->_children.size() == 1) { + return std::move(task->_children.front()); + } + + return std::move(task); } void @@ -144,8 +157,8 @@ Task::dump(std::function<void (const std::string&)> sink, unsigned indent, bool sink(_block->path()); } else { sink(((_mode == Mode::SEQUENTIAL) ? "(seq " : "(par ")); - for (size_t i = 0; i < size(); ++i) { - (*this)[i].dump(sink, indent + 5, i == 0); + for (size_t i = 0; i < _children.size(); ++i) { + _children[i]->dump(sink, indent + 5, i == 0); } sink(")"); } diff --git a/src/server/Task.hpp b/src/server/Task.hpp index 2c1a0cc2..982a6206 100644 --- a/src/server/Task.hpp +++ b/src/server/Task.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2016 David Robillard <http://drobilla.net/> + Copyright 2007-2017 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 @@ -18,8 +18,9 @@ #define INGEN_ENGINE_TASK_HPP #include <cassert> +#include <deque> +#include <memory> #include <ostream> -#include <vector> namespace Ingen { namespace Server { @@ -27,7 +28,7 @@ namespace Server { class BlockImpl; class RunContext; -class Task : public std::vector<Task> { +class Task { public: enum class Mode { SINGLE, ///< Single block to run @@ -35,7 +36,7 @@ public: PARALLEL ///< Elements may be run in any order in parallel }; - Task(Mode mode, BlockImpl* block=NULL) + Task(Mode mode, BlockImpl* block = nullptr) : _block(block) , _mode(mode) , _done_end(0) @@ -45,23 +46,13 @@ public: assert(!(mode == Mode::SINGLE && !block)); } - Task& operator=(const Task& copy) { - *static_cast<std::vector<Task>*>(this) = copy; - _block = copy._block; - _mode = copy._mode; - _done_end = copy._done_end; - _next = copy._next.load(); - _done = copy._done.load(); - return *this; - } - - Task(const Task& copy) - : std::vector<Task>(copy) - , _block(copy._block) - , _mode(copy._mode) - , _done_end(copy._done_end) - , _next(copy._next.load()) - , _done(copy._done.load()) + Task(Task&& task) + : _children(std::move(task._children)) + , _block(task._block) + , _mode(task._mode) + , _done_end(task._done_end) + , _next(task._next.load()) + , _done(task._done.load()) {} /** Run task in the given context. */ @@ -70,12 +61,20 @@ public: /** Pretty print task to the given stream (recursively). */ void dump(std::function<void (const std::string&)> sink, unsigned indent, bool first) const; + /** Return true iff this is an empty task. */ + bool empty() const { return _mode != Mode::SINGLE && _children.empty(); } + /** Simplify task expression. */ - void simplify(); + static std::unique_ptr<Task> simplify(std::unique_ptr<Task>&& task); /** Steal a child task from this task (succeeds for PARALLEL only). */ Task* steal(RunContext& context); + /** Prepend a child to this task. */ + void push_front(Task&& task) { + _children.push_front(std::unique_ptr<Task>(new Task(std::move(task)))); + } + Mode mode() const { return _mode; } BlockImpl* block() const { return _block; } bool done() const { return _done; } @@ -83,8 +82,14 @@ public: void set_done(bool done) { _done = done; } private: + typedef std::deque<std::unique_ptr<Task>> Children; + + Task(const Task&) = delete; + Task& operator=(const Task&) = delete; + Task* get_task(RunContext& context); + Children _children; ///< Vector of child tasks BlockImpl* _block; ///< Used for SINGLE only Mode _mode; ///< Execution mode unsigned _done_end; ///< Index of rightmost done sub-task diff --git a/src/server/ingen_lv2.cpp b/src/server/ingen_lv2.cpp index 9119ae14..00b59ca7 100644 --- a/src/server/ingen_lv2.cpp +++ b/src/server/ingen_lv2.cpp @@ -507,9 +507,9 @@ ingen_instantiate(const LV2_Descriptor* descriptor, } IngenPlugin* plugin = new IngenPlugin(); - plugin->map = map; - plugin->world = new Ingen::World( - plugin->argc, plugin->argv, map, unmap, log); + plugin->map = map; + plugin->world = new Ingen::World(map, unmap, log); + plugin->world->load_configuration(plugin->argc, plugin->argv); LV2_URID bufsz_max = map->map(map->handle, LV2_BUF_SIZE__maxBlockLength); LV2_URID bufsz_seq = map->map(map->handle, LV2_BUF_SIZE__sequenceSize); diff --git a/tests/TestClient.hpp b/tests/TestClient.hpp new file mode 100644 index 00000000..f1e0c5a7 --- /dev/null +++ b/tests/TestClient.hpp @@ -0,0 +1,90 @@ +/* + This file is part of Ingen. + Copyright 2007-2017 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 + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef INGEN_TESTCLIENT_HPP +#define INGEN_TESTCLIENT_HPP + +#include "ingen/Interface.hpp" + +using namespace Ingen; + +class TestClient : public Ingen::Interface +{ +public: + explicit TestClient(Log& log) : _log(log) {} + ~TestClient() {} + + Raul::URI uri() const { return Raul::URI("ingen:testClient"); } + + void bundle_begin() {} + + void bundle_end() {} + + void put(const Raul::URI& uri, + const Properties& properties, + Resource::Graph ctx = Resource::Graph::DEFAULT) {} + + void delta(const Raul::URI& uri, + const Properties& remove, + const Properties& add) {} + + void copy(const Raul::URI& old_uri, + const Raul::URI& new_uri) {} + + void move(const Raul::Path& old_path, + const Raul::Path& new_path) {} + + void del(const Raul::URI& uri) {} + + void connect(const Raul::Path& tail, + const Raul::Path& head) {} + + void disconnect(const Raul::Path& tail, + const Raul::Path& head) {} + + void disconnect_all(const Raul::Path& parent_patch_path, + const Raul::Path& path) {} + + void set_property(const Raul::URI& subject, + const Raul::URI& predicate, + const Atom& value) {} + + void set_response_id(int32_t id) {} + + void get(const Raul::URI& uri) {} + + void undo() {} + + void redo() {} + + void response(int32_t id, Status status, const std::string& subject) { + if (status != Status::SUCCESS) { + _log.error(fmt("error on message %1%: %2% (%3%)\n") + % id % ingen_status_string(status) % subject); + exit(EXIT_FAILURE); + } + } + + void error(const std::string& msg) { + _log.error(fmt("error: %1%\n") % msg); + exit(EXIT_FAILURE); + } + +private: + Log& _log; +}; + +#endif // INGEN_TESTCLIENT_HPP diff --git a/tests/ingen_bench.cpp b/tests/ingen_bench.cpp new file mode 100644 index 00000000..c8203331 --- /dev/null +++ b/tests/ingen_bench.cpp @@ -0,0 +1,189 @@ +/* + This file is part of Ingen. + Copyright 2007-2017 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 + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <signal.h> +#include <stdlib.h> + +#include <iostream> +#include <string> +#include <thread> + +#include <boost/optional.hpp> + +#include <glibmm/convert.h> +#include <glibmm/miscutils.h> +#include <glibmm/thread.h> +#include <glibmm/timer.h> + +#include "raul/Path.hpp" + +#include "serd/serd.h" +#include "sord/sordmm.hpp" +#include "sratom/sratom.h" + +#include "ingen_config.h" + +#include "ingen/AtomReader.hpp" +#include "ingen/AtomWriter.hpp" +#include "ingen/Configuration.hpp" +#include "ingen/Configuration.hpp" +#include "ingen/EngineBase.hpp" +#include "ingen/Interface.hpp" +#include "ingen/Parser.hpp" +#include "ingen/Serialiser.hpp" +#include "ingen/Store.hpp" +#include "ingen/URIMap.hpp" +#include "ingen/World.hpp" +#include "ingen/client/ThreadedSigClientInterface.hpp" +#include "ingen/runtime_paths.hpp" +#include "ingen/types.hpp" +#include "../src/server/Clock.hpp" // FIXME + +#include "TestClient.hpp" + +using namespace std; +using namespace Ingen; + +World* world = NULL; + +static void +ingen_try(bool cond, const char* msg) +{ + if (!cond) { + cerr << "ingen: Error: " << msg << endl; + delete world; + exit(EXIT_FAILURE); + } +} + +static uint32_t +flush_events(Ingen::World* world, const uint32_t start) +{ + static const uint32_t block_length = 4096; + int count = 0; + uint32_t offset = start; + while (world->engine()->pending_events()) { + world->engine()->locate(offset, block_length); + world->engine()->run(block_length); + world->engine()->main_iteration(); + g_usleep(1000); + ++count; + offset += block_length; + } + return offset; +} + +static std::string +real_path(const char* path) +{ + char* const c_real_path = realpath(path, NULL); + const std::string result(c_real_path ? c_real_path : ""); + free(c_real_path); + return result; +} + +int +main(int argc, char** argv) +{ + Glib::thread_init(); + set_bundle_path_from_code((void*)&flush_events); + + // Create world + try { + world = new World(NULL, NULL, NULL); + world->conf().add( + "output", "output", 'O', "File to write benchmark output", + Ingen::Configuration::SESSION, world->forge().String, Atom()); + world->load_configuration(argc, argv); + } catch (std::exception& e) { + cout << "ingen: " << e.what() << endl; + return EXIT_FAILURE; + } + + // Get mandatory command line arguments + const Atom& load = world->conf().option("load"); + const Atom& out = world->conf().option("output"); + if (!load.is_valid() || !out.is_valid()) { + cerr << "Usage: ingen_bench --load START_GRAPH --output OUT_FILE" << endl; + return EXIT_FAILURE; + } + + // Get start graph and output file options + const std::string start_graph = real_path((const char*)load.get_body()); + const std::string out_file = (const char*)out.get_body(); + if (start_graph.empty()) { + cerr << "error: initial graph '" + << ((const char*)load.get_body()) + << "' does not exist" << endl; + return EXIT_FAILURE; + } + + // Load modules + ingen_try(world->load_module("server_profiled"), + "Unable to load server module"); + + // Initialise engine + ingen_try(bool(world->engine()), + "Unable to create engine"); + world->engine()->init(48000.0, 4096, 4096); + world->engine()->activate(); + + // Load graph + if (!world->parser()->parse_file(world, world->interface().get(), start_graph)) { + cerr << "error: failed to load initial graph " << start_graph << endl; + return EXIT_FAILURE; + } + uint32_t time = flush_events(world, 0); + + Ingen::Server::Clock clock; + static const uint32_t n_test_frames = 1 << 20; + static const uint32_t block_length = 4096; + + // Set up real-time scheduling for this (main run) thread + pthread_t pthread = pthread_self(); + const int policy = SCHED_FIFO; + sched_param sp; + sp.sched_priority = 90; + if (pthread_setschedparam(pthread, policy, &sp)) { + world->log().error( + fmt("Failed to set real-time priority of run thread (%s)\n") + % strerror(errno)); + } + + const uint64_t t_start = clock.now_microseconds(); + for (uint32_t i = time; i < time + n_test_frames; i += block_length) { + world->engine()->locate(i, block_length); + world->engine()->run(block_length); + //world->engine()->main_iteration(); + } + const uint64_t t_end = clock.now_microseconds(); + + FILE* log = fopen(out_file.c_str(), "a"); + if (ftell(log) == 0) { + fprintf(log, "n_threads\trun_time\tn_frames\n"); + } + fprintf(log, "%u\t%llu\t%u\n", + world->conf().option("threads").get<int32_t>(), + t_end - t_start, + n_test_frames); + fclose(log); + + // Shut down + world->engine()->deactivate(); + + delete world; + return EXIT_SUCCESS; +} diff --git a/tests/ingen_test.cpp b/tests/ingen_test.cpp index 1ac2e383..d55789db 100644 --- a/tests/ingen_test.cpp +++ b/tests/ingen_test.cpp @@ -51,78 +51,13 @@ #include "ingen/runtime_paths.hpp" #include "ingen/types.hpp" +#include "TestClient.hpp" + using namespace std; using namespace Ingen; World* world = NULL; -class TestClient : public Interface -{ -public: - explicit TestClient(Log& log) : _log(log) {} - ~TestClient() {} - - Raul::URI uri() const { return Raul::URI("ingen:testClient"); } - - void bundle_begin() {} - - void bundle_end() {} - - void put(const Raul::URI& uri, - const Properties& properties, - Resource::Graph ctx = Resource::Graph::DEFAULT) {} - - void delta(const Raul::URI& uri, - const Properties& remove, - const Properties& add) {} - - void copy(const Raul::URI& old_uri, - const Raul::URI& new_uri) {} - - void move(const Raul::Path& old_path, - const Raul::Path& new_path) {} - - void del(const Raul::URI& uri) {} - - void connect(const Raul::Path& tail, - const Raul::Path& head) {} - - void disconnect(const Raul::Path& tail, - const Raul::Path& head) {} - - void disconnect_all(const Raul::Path& parent_patch_path, - const Raul::Path& path) {} - - void set_property(const Raul::URI& subject, - const Raul::URI& predicate, - const Atom& value) {} - - void set_response_id(int32_t id) {} - - void get(const Raul::URI& uri) {} - - void undo() {} - - void redo() {} - - void response(int32_t id, Status status, const std::string& subject) { - if (status != Status::SUCCESS) { - _log.error(fmt("error on message %1%: %2% (%3%)\n") - % id % ingen_status_string(status) % subject); - exit(EXIT_FAILURE); - } - } - - void error(const std::string& msg) { - _log.error(fmt("error: %1%\n") % msg); - exit(EXIT_FAILURE); - } - -private: - Log& _log; -}; - - static void ingen_try(bool cond, const char* msg) { @@ -158,7 +93,8 @@ main(int argc, char** argv) // Create world try { - world = new World(argc, argv, NULL, NULL, NULL); + world = new World(NULL, NULL, NULL); + world->load_configuration(argc, argv); } catch (std::exception& e) { cout << "ingen: " << e.what() << endl; return EXIT_FAILURE; @@ -194,7 +130,7 @@ main(int argc, char** argv) world->engine()->init(48000.0, 4096, 4096); world->engine()->activate(); - // Load patch + // Load graph if (!world->parser()->parse_file(world, world->interface().get(), start_graph)) { cerr << "error: failed to load initial graph " << start_graph << endl; return EXIT_FAILURE; @@ -234,15 +234,16 @@ def build(bld): # Test program if bld.env.BUILD_TESTS: - obj = bld(features = 'cxx cxxprogram', - source = 'tests/ingen_test.cpp', - target = 'tests/ingen_test', - includes = ['.'], - use = 'libingen_profiled', - install_path = '', - cxxflags = bld.env.INGEN_TEST_CXXFLAGS, - linkflags = bld.env.INGEN_TEST_LINKFLAGS) - autowaf.use_lib(bld, obj, 'GTHREAD GLIBMM SORD RAUL LILV INGEN LV2 SRATOM') + for i in ['ingen_test', 'ingen_bench']: + obj = bld(features = 'cxx cxxprogram', + source = 'tests/%s.cpp' % i, + target = 'tests/%s' % i, + includes = ['.'], + use = 'libingen_profiled', + install_path = '', + cxxflags = bld.env.INGEN_TEST_CXXFLAGS, + linkflags = bld.env.INGEN_TEST_LINKFLAGS) + autowaf.use_lib(bld, obj, 'GTHREAD GLIBMM SORD RAUL LILV INGEN LV2 SRATOM') bld.install_files('${DATADIR}/applications', 'src/ingen/ingen.desktop') bld.install_files('${BINDIR}', 'scripts/ingenish', chmod=Utils.O755) |