Falcon
Loading...
Searching...
No Matches
iprocessor.hpp
1// ---------------------------------------------------------------------
2// This file is part of falcon-core.
3//
4// Copyright (C) 2015, 2016, 2017 Neuro-Electronics Research Flanders
5//
6// Falcon-server is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// Falcon-server is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15//
16// You should have received a copy of the GNU General Public License
17// along with falcon-core. If not, see <http://www.gnu.org/licenses/>.
18// ---------------------------------------------------------------------
19
20#pragma once
21
22#include <functional>
23#include <map>
24#include <memory>
25#include <set>
26#include <string>
27#include <utility>
28#include <vector>
29
30#include "factory/factory.hpp"
31#include "graphexceptions.hpp"
32#include "options/options.hpp"
33#include "portpolicy.hpp"
34#include "runinfo.hpp"
35#include "sharedstate.hpp"
36#include "streamports.hpp"
37#include "threadutilities.hpp"
38#include "yaml-cpp/yaml.h"
39
40// exception class for all processor related errors
41GRAPHERROR(ProcessorInternalError);
42GRAPHERROR(ProcessingError);
43GRAPHERROR(ProcessingConfigureError);
44GRAPHERROR(ProcessingCreatePortsError);
45GRAPHERROR(ProcessingStreamInfoError);
46GRAPHERROR(ProcessingPrepareError);
47GRAPHERROR(ProcessingPreprocessingError);
48
49void convert_name(std::string& s);
51 uint64_t sync_cycles;
52 uint64_t work_cycles;
53};
54
55namespace graph {
56class ProcessorGraph;
57}
58
59class IProcessor {
60 friend class ISlotIn;
61 friend class graph::ProcessorGraph;
62
63 public: // called by anyone
64 IProcessor(ThreadPriority priority = PRIORITY_HIGH) : running_(false), thread_() {
65 // add test option
66 add_option("test", new_test_flag_);
67
68 // add advanced options
69 add_advanced_option("thread_core", thread_core_);
70 add_advanced_option("threadpriority", thread_priority_ = priority);
71 add_advanced_option("buffer_sizes", requested_buffer_sizes_);
72 }
73
74 virtual ~IProcessor() { internal_Stop(); }
75
76 // Thread-local storage to prevent contention between 256 threads
77 // Initialized to nullptr; allocated only on first use
78 static inline thread_local std::vector<TimingEntry>* t_metrics = nullptr;
79 static constexpr size_t MAX_SAMPLES = 100000;
80
81 // Fast, inline recording function
82 inline void record_metrics(uint64_t sync, uint64_t work) noexcept {
83 if (!t_metrics) [[unlikely]] {
84 t_metrics = new std::vector<TimingEntry>();
85 t_metrics->reserve(MAX_SAMPLES);
86 }
87
88 if (t_metrics->size() < MAX_SAMPLES) {
89 t_metrics->push_back({sync, work});
90 }
91 }
92
93 // Dump logic to be called in Postprocess
94 void dump_benchmarks() {
95 if (!t_metrics || t_metrics->empty()) return;
96
97 std::system("mkdir -p bench");
98
99 // Use .bin extension to distinguish from CSV
100 std::string filename = "bench/bench_" + std::string(name()) + "_" +
101 std::to_string(reinterpret_cast<uintptr_t>(this)) + ".bin";
102
103 std::ofstream f{filename, std::ios::binary};
104 if (f.is_open()) {
105 // Write the entire buffer in one system call
106 f.write(reinterpret_cast<const char*>(t_metrics->data()),
107 t_metrics->size() * sizeof(TimingEntry));
108 f.close();
109 } else {
110 LOG(ERROR) << "Failed to write binary to " << filename;
111 }
112
113 delete t_metrics;
114 t_metrics = nullptr;
115 }
116
123 const std::string name() const { return name_; }
124
131 const std::string type() const { return type_; }
132
136 unsigned int n_input_ports() const { return input_ports_.size(); }
137
141 unsigned int n_output_ports() const { return output_ports_.size(); }
142
146 const std::set<std::string> input_port_names() const;
147
151 const std::set<std::string> output_port_names() const;
152
158 bool has_input_port(const std::string& port) { return input_ports_.count(port) == 1; }
159
165 bool has_output_port(const std::string& port) { return output_ports_.count(port) == 1; }
166
167 virtual bool issource() const { return n_input_ports() == 0; }
168 virtual bool issink() const { return n_output_ports() == 0; }
169 virtual bool isfilter() const { return (!issource() && !issink()); }
170 virtual bool isautonomous() const { return (issource() && issink()); }
171
172 ThreadPriority thread_priority() const { return thread_priority_(); }
173 ThreadCore thread_core() const { return thread_core_(); }
174 bool running() const { return running_.load(); }
175
176 YAML::Node ExportYAML();
177
178 void checkNonvoluntaryContextSwitches();
179
180 protected:
181 std::map<std::string, std::shared_ptr<std::ostream>> streams_;
182 std::vector<TimePoint> test_source_timestamps_;
183
184 /* this methods creates a file whose access key is filename and whose
185 fullpath is prefix.filename.extension*/
186 void create_file(const std::string& prefix, const std::string& variable_name,
187 const std::string& extension = "bin");
188
189 void prepare_latency_test(ProcessingContext& context);
190 void save_source_timestamps_to_disk(std::uint64_t n_timestamps);
191
192 protected: // callable by derived processors, but not others
214 template <typename TValue>
215 void add_option(std::string name, TValue& value, std::string description = "",
216 bool required = false) {
217 options_.add(std::move(name), value, std::move(description), required);
218 }
219
226 void remove_option(const std::string& name);
227
238 template <typename DATATYPE>
240 const typename DATATYPE::Parameters& parameters,
241 const PortOutPolicy& policy) {
242 if (name.size() == 0) {
243 name = DATATYPE::dataname();
244 }
245 convert_name(name);
246 if (output_ports_.count(name) == 1) {
247 throw std::runtime_error("Output port name \"" + name +
248 "\" is invalid or already exists.");
249 }
250
251 output_ports_[name] = std::unique_ptr<IPortOut>((IPortOut*) new PortOut<DATATYPE>(
252 this, PortAddress(this->name(), name), parameters, policy));
253
254 return ((PortOut<DATATYPE>*) output_ports_[name].get());
255 }
256
264 template <typename DATATYPE>
265 PortOut<DATATYPE>* create_output_port(const typename DATATYPE::Parameters& parameters,
266 const PortOutPolicy& policy) {
267 return create_output_port<DATATYPE>(DATATYPE::dataname(), parameters, policy);
268 }
269
280 template <typename DATATYPE>
281
283 const typename DATATYPE::Capabilities& capabilities,
284 const PortInPolicy& policy) {
285 if (name.size() == 0) {
286 name = DATATYPE::dataname();
287 }
288 convert_name(name);
289 if (input_ports_.count(name) == 1) {
290 throw std::runtime_error("Input port name \"" + name +
291 "\" is invalid or already exists.");
292 }
293
294 input_ports_[name] = std::unique_ptr<IPortIn>((IPortIn*) new PortIn<DATATYPE>(
295 this, PortAddress(this->name(), name), capabilities, policy));
296
297 return ((PortIn<DATATYPE>*) input_ports_[name].get());
298 }
299
307 template <typename DATATYPE>
308 PortIn<DATATYPE>* create_input_port(const typename DATATYPE::Capabilities& capabilities,
309 const PortInPolicy& policy) {
310 return create_input_port<DATATYPE>(DATATYPE::dataname(), capabilities, policy);
311 }
312
318 IPortIn* input_port(const std::string& port) { return input_ports_.at(port).get(); }
319
325 IPortOut* output_port(const std::string& port) { return output_ports_.at(port).get(); }
326
332 IPortIn* input_port(const PortAddress& address);
333
339 IPortOut* output_port(const PortAddress& address);
340
346 ISlotIn* input_slot(const SlotAddress& address);
347
353 ISlotOut* output_slot(const SlotAddress& address);
354
355 /*self – peers – external
356
357 static variable: value is not changed by self or others
358 R – N – N (static variable) -> not very useful
359 R – N – R (externally observed static variable)
360 R – R – N (static shared observable)
361 R – R – R (externally observed static shared observable)
362 R – N – W (externally controlled static variable)
363 R – R – W (externally controlled shared static variable)
364 isolated producer: value is changed by self only
365 W – N – N (isolated producer) -> not very useful
366 W – N – R (externally observed isolated producer)
367 W – N – W (bi-directional channel)
368 co-operative producer: self and others change the value
369 W – W – N (co-operative producer)
370 W – W – R (externally observed co-operative producer)
371 W – W – W (externally controlled co-operative producer)
372 follower: value is changed by others, self only reads
373 R – W – N (follower)
374 R – W – R (externally observed follower)
375 R – W – W (externally controlled follower)
376 broadcaster: self changes the value, others follow
377 W – R – N (broadcaster)
378 W – R – R (externally observed broadcaster)
379 W – R – W (externally controlled broadcaster)
380
381 */
382
405 template <typename T>
406 StaticState<T>* create_static_state(std::string state, T default_value, bool shared = true,
407 Permission external = Permission::WRITE,
408 std::string description = "") {
409 if (shared) {
410 return ((StaticState<T>*) create_readable_shared_state<T>(
411 state, default_value, Permission::READ, external, description));
412 } else {
413 return ((StaticState<T>*) create_readable_shared_state<T>(
414 state, default_value, Permission::NONE, external, description));
415 }
416 }
417
439 template <typename T>
440 ProducerState<T>* create_producer_state(std::string state, T default_value,
441 bool cooperative = false,
442 Permission external = Permission::READ,
443 std::string description = "") {
444 if (cooperative) {
445 return ((ProducerState<T>*) create_writable_shared_state<T>(
446 state, default_value, Permission::WRITE, external, description));
447 } else {
448 return ((ProducerState<T>*) create_writable_shared_state<T>(
449 state, default_value, Permission::NONE, external, description));
450 }
451 }
452
471 template <typename T>
472 BroadcasterState<T>* create_broadcaster_state(std::string state, T default_value,
473 Permission external = Permission::NONE,
474 std::string description = "") {
475 return ((BroadcasterState<T>*) create_writable_shared_state<T>(
476 state, default_value, Permission::READ, external, description));
477 }
478
498 template <typename T>
499 FollowerState<T>* create_follower_state(std::string state, T default_value,
500 Permission external = Permission::NONE,
501 std::string description = "") {
502 return ((FollowerState<T>*) create_readable_shared_state<T>(
503 state, default_value, Permission::WRITE, external, description));
504 }
505
524 template <typename T>
525 ReadableState<T>* create_readable_shared_state(const std::string& state, T default_value,
526 Permission peers = Permission::WRITE,
527 Permission external = Permission::NONE,
528 std::string description = "") {
529 if (shared_states_.count(state) == 1) {
530 throw ProcessorInternalError(
531 "Shared state \"" + state + "\" is invalid or already exists.", name());
532 }
533
534 shared_states_[state] = std::unique_ptr<IState>(
535 (IState*) new ReadableState<T>(default_value, description, peers, external));
536
537 return ((ReadableState<T>*) shared_states_[state].get());
538 }
539
558 template <typename T>
559 WritableState<T>* create_writable_shared_state(const std::string& state, T default_value,
560 Permission peers = Permission::READ,
561 Permission external = Permission::NONE,
562 std::string description = "") {
563 if (shared_states_.count(state) == 1) {
564 throw ProcessorInternalError(
565 "Shared state \"" + state + "\" is invalid or already exists.", name());
566 }
567
568 shared_states_[state] = std::unique_ptr<IState>(
569 (IState*) new WritableState<T>(default_value, description, peers, external));
570 return ((WritableState<T>*) shared_states_[state].get());
571 }
572
578 std::shared_ptr<IState> shared_state(const std::string& state) {
579 if (this->shared_states_.count(state) == 0) {
580 throw ProcessorInternalError("Shared state \"" + state + "\" does not exist.", name());
581 }
582 return shared_states_[state];
583 }
584
585 template <class T>
586 void expose_method(const std::string& methodname, YAML::Node (T::*method)(const YAML::Node&)) {
587 if (exposed_methods_.count(methodname) == 1) {
588 throw ProcessorInternalError(
589 "Exposed method \"" + methodname + "\" is invalid or already exists.", name());
590 }
591 exposed_methods_[methodname] =
592 std::bind(method, static_cast<T*>(this), std::placeholders::_1);
593 }
594
595 std::function<YAML::Node(const YAML::Node&)>& exposed_method(const std::string& method) {
596 if (this->exposed_methods_.count(method) == 0) {
597 throw ProcessorInternalError("Exposed method \"" + method + "\" does not exist.",
598 name());
599 }
600 return exposed_methods_[method];
601 }
602
603 protected: // to be overridden and callable by derived processors
604 virtual std::string default_input_port() const;
605 virtual std::string default_output_port() const;
606
607 private: // to be overridden by derived processors, callable internally
608 virtual void Configure(const GlobalContext& _) {}
609 virtual void CreatePorts() = 0;
610 virtual void Preprocess(ProcessingContext& _) {}
611 virtual void Process(ProcessingContext& context) = 0;
612 virtual void Postprocess(ProcessingContext& _) {}
613 virtual void CompleteStreamInfo();
614 virtual void Prepare(GlobalContext& _) {}
615 virtual void Unprepare(GlobalContext& _) {}
616 virtual void TestPrepare(ProcessingContext& _) {}
617 virtual void TestFinalize(ProcessingContext& _) {}
618
619 private: // callable internally only
620 void internal_Configure(const YAML::Node& node,
621 const GlobalContext& context); // from engine
622 void internal_CreatePorts();
623 void internal_PrepareConnectionIn(SlotAddress& in);
624 void internal_PrepareConnectionOut(SlotAddress& out);
625 void internal_ConnectIn(const SlotAddress& address, IProcessor* upstream,
626 const SlotAddress& upstream_address);
627 void internal_ConnectOut(const SlotAddress& address, IProcessor* downstream,
628 const SlotAddress& downstream_address);
629
630 void internal_NegotiateConnections();
631
632 void internal_CreateRingBuffers();
633 void internal_PrepareProcessing();
634
635 void internal_ThreadEntry(RunContext& runcontext);
636
637 void internal_Start(RunContext& runcontext);
638 void internal_Stop();
639
640 void internal_Alert();
641
642 YAML::Node internal_ApplyMethod(const std::string& name, const YAML::Node& node);
643
644 void set_name_and_type(std::string name, std::string type) {
645 name_ = std::move(name);
646 type_ = std::move(type);
647 }
648
649 template <typename TValue>
650 void add_advanced_option(std::string name, TValue& value, std::string description = "",
651 bool required = false) {
652 advanced_options_.add(std::move(name), value, std::move(description), required);
653 }
654
655 private:
656 std::string name_;
657 std::string type_;
658
659 std::map<std::string, std::unique_ptr<IPortIn>> input_ports_;
660 std::map<std::string, std::unique_ptr<IPortOut>> output_ports_;
661
662 std::map<std::string, std::function<YAML::Node(const YAML::Node&)>> exposed_methods_;
663 std::map<std::string, std::shared_ptr<IState>> shared_states_;
664
665 bool negotiated_ = false;
666 bool prepared_ = false;
667
668 std::atomic<bool> running_;
669
670 std::thread thread_;
671
672 options::Value<ThreadPriority, false> thread_priority_{
673 PRIORITY_HIGH, options::inrange<ThreadPriority>(PRIORITY_NONE, PRIORITY_HIGH)};
674
675 options::Value<ThreadCore, false> thread_core_{
676 CORE_NOT_PINNED, options::inrange<ThreadCore>(
677 CORE_NOT_PINNED, (ThreadCore) sysconf(_SC_NPROCESSORS_ONLN) - 1)};
678
679 options::NullableBool new_test_flag_;
680 options::Value<std::map<std::string, int>> requested_buffer_sizes_{};
681
685 uint64_t lastNonvoluntaryContextSwitches = 0;
686
687 protected:
688 options::OptionList options_;
689 options::OptionList advanced_options_;
690};
691
692typedef std::map<std::string, std::pair<std::string, std::unique_ptr<IProcessor>>> ProcessorMap;
693
694typedef factory::ObjectFactory<IProcessor, std::string> ProcessorFactory;
695
696template <class PROCESSOR>
697class ProcessorRegistrar {
698 public:
699 ProcessorRegistrar(std::string name);
700};
701
702template <class PROCESSOR>
703ProcessorRegistrar<PROCESSOR>::ProcessorRegistrar(std::string name) {
704 ProcessorFactory::instance().registerClass(name,
705 factory::createInstance<IProcessor, PROCESSOR>);
706}
707
708#define REGISTERPROCESSOR(PROCESSOR) \
709 namespace { \
710 static ProcessorRegistrar<PROCESSOR> _registrar(#PROCESSOR); \
711 };
Definition istreamports.hpp:184
Definition istreamports.hpp:87
PortOut< DATATYPE > * create_output_port(std::string name, const typename DATATYPE::Parameters &parameters, const PortOutPolicy &policy)
Definition iprocessor.hpp:239
PortOut< DATATYPE > * create_output_port(const typename DATATYPE::Parameters &parameters, const PortOutPolicy &policy)
Definition iprocessor.hpp:265
bool has_output_port(const std::string &port)
Definition iprocessor.hpp:165
void add_option(std::string name, TValue &value, std::string description="", bool required=false)
Definition iprocessor.hpp:215
StaticState< T > * create_static_state(std::string state, T default_value, bool shared=true, Permission external=Permission::WRITE, std::string description="")
Definition iprocessor.hpp:406
WritableState< T > * create_writable_shared_state(const std::string &state, T default_value, Permission peers=Permission::READ, Permission external=Permission::NONE, std::string description="")
Definition iprocessor.hpp:559
unsigned int n_output_ports() const
Definition iprocessor.hpp:141
BroadcasterState< T > * create_broadcaster_state(std::string state, T default_value, Permission external=Permission::NONE, std::string description="")
Definition iprocessor.hpp:472
const std::string type() const
Definition iprocessor.hpp:131
IPortOut * output_port(const std::string &port)
Definition iprocessor.hpp:325
const std::set< std::string > input_port_names() const
Definition iprocessor.cpp:40
FollowerState< T > * create_follower_state(std::string state, T default_value, Permission external=Permission::NONE, std::string description="")
Definition iprocessor.hpp:499
ISlotOut * output_slot(const SlotAddress &address)
Definition iprocessor.cpp:99
const std::set< std::string > output_port_names() const
Definition iprocessor.cpp:48
ReadableState< T > * create_readable_shared_state(const std::string &state, T default_value, Permission peers=Permission::WRITE, Permission external=Permission::NONE, std::string description="")
Definition iprocessor.hpp:525
unsigned int n_input_ports() const
Definition iprocessor.hpp:136
bool has_input_port(const std::string &port)
Definition iprocessor.hpp:158
std::shared_ptr< IState > shared_state(const std::string &state)
Definition iprocessor.hpp:578
ISlotIn * input_slot(const SlotAddress &address)
Definition iprocessor.cpp:95
IPortIn * input_port(const std::string &port)
Definition iprocessor.hpp:318
PortIn< DATATYPE > * create_input_port(std::string name, const typename DATATYPE::Capabilities &capabilities, const PortInPolicy &policy)
Definition iprocessor.hpp:282
PortIn< DATATYPE > * create_input_port(const typename DATATYPE::Capabilities &capabilities, const PortInPolicy &policy)
Definition iprocessor.hpp:308
ProducerState< T > * create_producer_state(std::string state, T default_value, bool cooperative=false, Permission external=Permission::READ, std::string description="")
Definition iprocessor.hpp:440
void remove_option(const std::string &name)
Definition iprocessor.cpp:83
const std::string name() const
Definition iprocessor.hpp:123
Definition istreamports.hpp:126
Definition istreamports.hpp:40
Definition sharedstate.hpp:138
Definition connections.hpp:24
Definition portpolicy.hpp:43
Definition streamports.hpp:256
Definition portpolicy.hpp:54
Definition streamports.hpp:98
Definition sharedstate.hpp:192
Definition connections.hpp:54
Definition sharedstate.hpp:332
Definition processorgraph.hpp:62
Definition iprocessor.hpp:50