From af3e2e08eeb0d878e32b680abf3de2ad021c7a55 Mon Sep 17 00:00:00 2001 From: Vlad Zolotarov Date: Thu, 19 Feb 2015 13:35:40 +0200 Subject: [PATCH 01/39] DPDK: Always enable HW CRC stripping Signed-off-by: Vlad Zolotarov --- net/dpdk.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/dpdk.cc b/net/dpdk.cc index 17535e8245..8b3a1ecd90 100644 --- a/net/dpdk.cc +++ b/net/dpdk.cc @@ -1133,6 +1133,9 @@ int dpdk_device::init_port_start() port_conf.rxmode.hw_vlan_strip = 1; } + // Enable HW CRC stripping + port_conf.rxmode.hw_strip_crc = 1; + // Check that all CSUM features are either all set all together or not set // all together. If this assumption breaks we need to rework the below logic // by splitting the csum offload feature bit into separate bits for IPv4, From 7cdc99ca50cfa23773ab37fbce09af38942cd8e1 Mon Sep 17 00:00:00 2001 From: Vlad Zolotarov Date: Thu, 26 Feb 2015 20:57:34 +0200 Subject: [PATCH 02/39] configure.py: Generate DPDK CFLAGS from DPDK's environment Creates a temp makefile that includes the mk/rte.vars.mk from DPDK SDK and prints MACHINE_CFLAGS. The value of MACHINE_CFLAGS is then added to args.user_cflags. Effectively the MACHINE_CFLAGS is a bunch of -DRTE_XXX macros and -march=native. Without these flags the compilation fails inside rte_memcpy.h in the upstream DPDK tree. This patch reverts the 04e0c5293b41bafdfc9d33ba45318d602aa9458b patch. Signed-off-by: Vlad Zolotarov --- configure.py | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/configure.py b/configure.py index 16687e05e1..c5025220dd 100755 --- a/configure.py +++ b/configure.py @@ -27,12 +27,6 @@ def get_flags(): if line.rstrip('\n').startswith('flags'): return re.sub(r'^flags\s+: ', '', line).split() -def has_avx(): - return 'avx' in get_flags() - -def has_avx2(): - return 'avx2' in get_flags() - def add_tristate(arg_parser, name, dest, help): arg_parser.add_argument('--enable-' + name, dest = dest, action = 'store_true', default = None, help = 'Enable ' + help) @@ -51,6 +45,35 @@ def apply_tristate(var, test, note, missing): return False return False +# +# dpdk_cflags - fetch the DPDK specific CFLAGS +# +# Run a simple makefile that "includes" the DPDK main makefile and prints the +# MACHINE_CFLAGS value +# +def dpdk_cflags (dpdk_target): + with tempfile.NamedTemporaryFile() as sfile: + dpdk_target = os.path.abspath(dpdk_target) + dpdk_target = re.sub(r'\/+$', '', dpdk_target) + dpdk_sdk_path = os.path.dirname(dpdk_target) + dpdk_target_name = os.path.basename(dpdk_target) + dpdk_arch = dpdk_target_name.split('-')[0] + + sfile.file.write(bytes('include ' + dpdk_sdk_path + '/mk/rte.vars.mk' + "\n", 'utf-8')) + sfile.file.write(bytes('all:' + "\n\t", 'utf-8')) + sfile.file.write(bytes('@echo $(MACHINE_CFLAGS)' + "\n", 'utf-8')) + sfile.file.flush() + + dpdk_cflags = subprocess.check_output(['make', '-f', sfile.name, + 'RTE_SDK=' + dpdk_sdk_path, + 'RTE_TARGET=' + dpdk_target_name, + 'RTE_ARCH=' + dpdk_arch]) + dpdk_cflags_str = dpdk_cflags.decode('utf-8') + dpdk_cflags_str = re.sub(r'\n+$', '', dpdk_cflags_str) + dpdk_cflags_final = '' + + return dpdk_cflags_str + def try_compile(compiler, source = '', flags = []): with tempfile.NamedTemporaryFile() as sfile: sfile.file.write(bytes(source, 'utf-8')) @@ -292,10 +315,9 @@ if args.with_osv: if args.dpdk_target: args.user_cflags = (args.user_cflags + - ' -DHAVE_DPDK -I' + - args.dpdk_target + '/include -Wno-error=literal-suffix -Wno-literal-suffix -Wno-invalid-offsetof -m64' + - ' -mavx' if has_avx() else '' + - ' -mavx2' if has_avx2() else '') + ' -DHAVE_DPDK -I' + args.dpdk_target + '/include ' + + dpdk_cflags(args.dpdk_target) + + ' -Wno-error=literal-suffix -Wno-literal-suffix -Wno-invalid-offsetof') libs += (' -L' + args.dpdk_target + '/lib ' + '-Wl,--whole-archive -lrte_pmd_bond -lrte_pmd_vmxnet3_uio -lrte_pmd_virtio_uio -lrte_pmd_i40e -lrte_pmd_ixgbe -lrte_pmd_e1000 -lrte_pmd_ring -Wl,--no-whole-archive -lrte_distributor -lrte_kni -lrte_pipeline -lrte_table -lrte_port -lrte_timer -lrte_hash -lrte_lpm -lrte_power -lrte_acl -lrte_meter -lrte_sched -lrte_kvargs -lrte_mbuf -lrte_ip_frag -lethdev -lrte_eal -lrte_malloc -lrte_mempool -lrte_ring -lrte_cmdline -lrte_cfgfile -lrt -lm -ldl') From 8fb948e1b293baca048b4022d268e67e76450027 Mon Sep 17 00:00:00 2001 From: Vlad Zolotarov Date: Tue, 3 Mar 2015 20:44:54 +0200 Subject: [PATCH 03/39] DPDK: Use for() loop in dpdk_qp::refill_one_cluster() Signed-off-by: Vlad Zolotarov --- net/dpdk.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/net/dpdk.cc b/net/dpdk.cc index 8b3a1ecd90..763854a229 100644 --- a/net/dpdk.cc +++ b/net/dpdk.cc @@ -1455,8 +1455,7 @@ inline packet dpdk_qp::from_mbuf(rte_mbuf* m) template inline bool dpdk_qp::refill_one_cluster(rte_mbuf* head) { - while (head != NULL) { - struct rte_mbuf *m_next = head->next; + for (; head != nullptr; head = head->next) { if (!refill_rx_mbuf(head)) { // // If we failed to allocate a new buffer - push the rest of the @@ -1466,7 +1465,6 @@ inline bool dpdk_qp::refill_one_cluster(rte_mbuf* head) return false; } _rx_free_bufs.push_back(head); - head = m_next; } return true; From 553e1a5e757e3ed5a89f162907c1127e84275d50 Mon Sep 17 00:00:00 2001 From: Vlad Zolotarov Date: Thu, 19 Feb 2015 18:44:56 +0200 Subject: [PATCH 04/39] DPDK: Add LRO support - Added LRO ON/OFF native stack command line parameter. - Implemented handling the reception of a clustered packet: - Without hugetlbfs: allocate a single buffer to contain the whole packet's data and copy its contents into it. If the allocation failed - build the "packet" directly from the cluster. - With hugetlbfs: create a packet from cluster mbuf's data buffers. Signed-off-by: Vlad Zolotarov New in v3: - Use RTE_ETHDEV_HAS_LRO_SUPPORT defined in rte_ethdev.h instead of RTE_ETHDEV_LRO_SUPPORT defined in config/common_linuxapp. New in v2: - dpdk_qp::from_mbuf_lro(): Free the cluster after copying to the allocated buffer. - Some style cleanups. --- net/dpdk.cc | 141 +++++++++++++++++++++++++++++++++++--------- net/dpdk.hh | 3 +- net/native-stack.cc | 6 +- net/net.hh | 2 + 4 files changed, 122 insertions(+), 30 deletions(-) diff --git a/net/dpdk.cc b/net/dpdk.cc index 763854a229..f423891a94 100644 --- a/net/dpdk.cc +++ b/net/dpdk.cc @@ -168,6 +168,7 @@ class dpdk_device : public device { net::hw_features _hw_features; uint8_t _queues_ready = 0; unsigned _home_cpu; + bool _use_lro; std::vector _redir_table; #ifdef RTE_VERSION_1_7 struct rte_eth_rxconf _rx_conf_default = {}; @@ -214,10 +215,11 @@ private: void check_port_link_status(); public: - dpdk_device(uint8_t port_idx, uint16_t num_queues) + dpdk_device(uint8_t port_idx, uint16_t num_queues, bool use_lro) : _port_idx(port_idx) , _num_queues(num_queues) - , _home_cpu(engine().cpu_id()) { + , _home_cpu(engine().cpu_id()) + , _use_lro(use_lro) { /* now initialise the port we will use */ int ret = init_port_start(); @@ -235,6 +237,8 @@ public: return _hw_features; } + net::hw_features& hw_features_ref() { return _hw_features; } + const rte_eth_rxconf* def_rx_conf() const { #ifdef RTE_VERSION_1_7 return &_rx_conf_default; @@ -1043,12 +1047,22 @@ private: */ packet from_mbuf(rte_mbuf* m); + /** + * Transform an LRO rte_mbuf cluster into the "packet" object. + * @param m HEAD of the mbufs' cluster to transform + * + * @return a "packet" object representing the newly received LRO packet. + */ + packet from_mbuf_lro(rte_mbuf* m); + private: dpdk_device* _dev; uint8_t _qid; rte_mempool *_pktmbuf_pool_rx; std::vector _rx_free_pkts; std::vector _rx_free_bufs; + std::vector _frags; + std::vector _bufs; size_t _num_rx_free_segs = 0; reactor::poller _rx_gc_poller; std::unique_ptr _rx_xmem; @@ -1136,6 +1150,16 @@ int dpdk_device::init_port_start() // Enable HW CRC stripping port_conf.rxmode.hw_strip_crc = 1; +#ifdef RTE_ETHDEV_HAS_LRO_SUPPORT + // Enable LRO + if (_use_lro && (_dev_info.rx_offload_capa & DEV_RX_OFFLOAD_TCP_LRO)) { + printf("LRO is on\n"); + port_conf.rxmode.enable_lro = 1; + _hw_features.rx_lro = true; + } else +#endif + printf("LRO is off\n"); + // Check that all CSUM features are either all set all together or not set // all together. If this assumption breaks we need to rework the below logic // by splitting the csum offload feature bit into separate bits for IPv4, @@ -1416,40 +1440,106 @@ void dpdk_qp::rx_start() { } template<> -inline packet dpdk_qp::from_mbuf(rte_mbuf* m) +inline packet dpdk_qp::from_mbuf_lro(rte_mbuf* m) { // - // Try to allocate a buffer for packet's data. If we fail - give the - // application an mbuf itself. If we succeed - copy the data into this - // buffer, create a packet based on this buffer and return the mbuf to its - // pool. + // Try to allocate a buffer for the whole packet's data. + // If we fail - construct the packet from mbufs. + // If we succeed - copy the data into this buffer, create a packet based on + // this buffer and return the mbuf to its pool. // - auto len = rte_pktmbuf_data_len(m); - char* buf = (char*)malloc(len); + auto pkt_len = rte_pktmbuf_pkt_len(m); + char* buf = (char*)malloc(pkt_len); if (!buf) { - fragment f{rte_pktmbuf_mtod(m, char*), len}; - return packet(f, make_deleter(deleter(), [m] { rte_pktmbuf_free(m); })); + _frags.clear(); + + for (rte_mbuf* m1 = m; m1 != nullptr; m1 = m1->next) { + char* data = rte_pktmbuf_mtod(m1, char*); + _frags.emplace_back(fragment{data, rte_pktmbuf_data_len(m1)}); + } + + return packet(_frags.begin(), _frags.end(), + make_deleter(deleter(), [m] { rte_pktmbuf_free(m); })); } else { - rte_memcpy(buf, rte_pktmbuf_mtod(m, char*), len); + // Copy the contents of the packet into the buffer we've just allocated + size_t offset = 0; + for (rte_mbuf* m1 = m; m1 != nullptr; m1 = m1->next) { + char* data = rte_pktmbuf_mtod(m1, char*); + auto len = rte_pktmbuf_data_len(m1); + + rte_memcpy(buf + offset, data, len); + offset += len; + } + rte_pktmbuf_free(m); - fragment f{buf, len}; - return packet(f, make_free_deleter(buf)); + return packet(fragment{buf, pkt_len}, make_free_deleter(buf)); } } +template<> +inline packet dpdk_qp::from_mbuf(rte_mbuf* m) +{ + if (!_dev->hw_features_ref().rx_lro || rte_pktmbuf_is_contiguous(m)) { + // + // Try to allocate a buffer for packet's data. If we fail - give the + // application an mbuf itself. If we succeed - copy the data into this + // buffer, create a packet based on this buffer and return the mbuf to + // its pool. + // + auto len = rte_pktmbuf_data_len(m); + char* buf = (char*)malloc(len); + if (!buf) { + return packet(fragment{rte_pktmbuf_mtod(m, char*), len}, + make_deleter(deleter(), + [m] { rte_pktmbuf_free(m); })); + } else { + rte_memcpy(buf, rte_pktmbuf_mtod(m, char*), len); + rte_pktmbuf_free(m); + + return packet(fragment{buf, len}, make_free_deleter(buf)); + } + } else { + return from_mbuf_lro(m); + } +} + +template<> +inline packet dpdk_qp::from_mbuf_lro(rte_mbuf* m) +{ + _frags.clear(); + _bufs.clear(); + + for (; m != nullptr; m = m->next) { + char* data = rte_pktmbuf_mtod(m, char*); + + _frags.emplace_back(fragment{data, rte_pktmbuf_data_len(m)}); + _bufs.push_back(data); + } + + return packet(_frags.begin(), _frags.end(), + make_deleter(deleter(), + [bufs_vec = std::move(_bufs)] { + for (auto&& b : bufs_vec) { + free(b); + } + })); +} + template<> inline packet dpdk_qp::from_mbuf(rte_mbuf* m) { - char* data = rte_pktmbuf_mtod(m, char*); - - fragment f{data, rte_pktmbuf_data_len(m)}; - packet p(f, make_free_deleter(data)); - _rx_free_pkts.push_back(m); _num_rx_free_segs += rte_mbuf_nb_segs(m); - return p; + if (!_dev->hw_features_ref().rx_lro || rte_pktmbuf_is_contiguous(m)) { + char* data = rte_pktmbuf_mtod(m, char*); + + return packet(fragment{data, rte_pktmbuf_data_len(m)}, + make_free_deleter(data)); + } else { + return from_mbuf_lro(m); + } } template @@ -1518,12 +1608,6 @@ void dpdk_qp::process_packets( struct rte_mbuf *m = bufs[i]; offload_info oi; - // TODO: Remove this when implement LRO support - if (!rte_pktmbuf_is_contiguous(m)) { - rte_exit(EXIT_FAILURE, - "DPDK-Rx: Have got a fragmented buffer - not supported\n"); - } - packet p = from_mbuf(m); // Set stipped VLAN value if available @@ -1632,7 +1716,8 @@ std::unique_ptr dpdk_device::init_local_queue(boost::program_options::variab std::unique_ptr create_dpdk_net_device( uint8_t port_idx, - uint8_t num_queues) + uint8_t num_queues, + bool use_lro) { static bool called = false; @@ -1648,7 +1733,7 @@ std::unique_ptr create_dpdk_net_device( printf("ports number: %d\n", rte_eth_dev_count()); } - return std::make_unique(port_idx, num_queues); + return std::make_unique(port_idx, num_queues, use_lro); } boost::program_options::options_description diff --git a/net/dpdk.hh b/net/dpdk.hh index 69cf0dda6d..86a13defbf 100644 --- a/net/dpdk.hh +++ b/net/dpdk.hh @@ -30,7 +30,8 @@ std::unique_ptr create_dpdk_net_device( uint8_t port_idx = 0, - uint8_t num_queues = 1); + uint8_t num_queues = 1, + bool use_lro = true); boost::program_options::options_description get_dpdk_net_options_description(); diff --git a/net/native-stack.cc b/net/native-stack.cc index d36e872b7e..88859cd16a 100644 --- a/net/native-stack.cc +++ b/net/native-stack.cc @@ -85,7 +85,8 @@ void create_native_net_device(boost::program_options::variables_map opts) { if (opts.count("dpdk-pmd")) { // Hardcoded port index 0. // TODO: Inherit it from the opts - dev = create_dpdk_net_device(0, smp::count); + dev = create_dpdk_net_device(0, smp::count, + !(opts.count("lro") && opts["lro"].as() == "off")); } else #endif dev = create_virtio_net_device(opts); @@ -325,6 +326,9 @@ boost::program_options::options_description nns_options() { #ifdef HAVE_DPDK ("dpdk-pmd", "Use DPDK PMD drivers") #endif + ("lro", + boost::program_options::value()->default_value("on"), + "Enable LRO") ; add_native_net_options_description(opts); diff --git a/net/net.hh b/net/net.hh index 96cf20ff7b..bccc73033d 100644 --- a/net/net.hh +++ b/net/net.hh @@ -71,6 +71,8 @@ struct hw_features { bool tx_csum_l4_offload = false; // Enable rx checksum offload bool rx_csum_offload = false; + // LRO is enabled + bool rx_lro = false; // Enable tx TCP segment offload bool tx_tso = false; // Enable tx UDP fragmentation offload From 1ceaa135432ab46406a22f00257d95ac9ea38707 Mon Sep 17 00:00:00 2001 From: Vlad Zolotarov Date: Tue, 3 Mar 2015 21:13:58 +0200 Subject: [PATCH 05/39] virtio: Disable/enable LRO according to "--lro" parameter Signed-off-by: Vlad Zolotarov --- net/virtio.cc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/net/virtio.cc b/net/virtio.cc index 72185fddd9..3243914759 100644 --- a/net/virtio.cc +++ b/net/virtio.cc @@ -87,11 +87,18 @@ private: } if (!(_opts.count("tso") && _opts["tso"].as() == "off")) { seastar_supported_features |= VIRTIO_NET_F_HOST_TSO4; - seastar_supported_features |= VIRTIO_NET_F_GUEST_TSO4; _hw_features.tx_tso = true; } else { _hw_features.tx_tso = false; } + + if (!(_opts.count("lro") && _opts["lro"].as() == "off")) { + seastar_supported_features |= VIRTIO_NET_F_GUEST_TSO4; + _hw_features.rx_lro = true; + } else { + _hw_features.rx_lro = false; + } + if (!(_opts.count("ufo") && _opts["ufo"].as() == "off")) { seastar_supported_features |= VIRTIO_NET_F_HOST_UFO; seastar_supported_features |= VIRTIO_NET_F_GUEST_UFO; From 0028959968e4e518e6033bdf2d17f2d65c8768b6 Mon Sep 17 00:00:00 2001 From: Vlad Zolotarov Date: Mon, 6 Apr 2015 17:55:55 +0300 Subject: [PATCH 06/39] net: separated qp stats into a separate struct - Moved all qp stats into a separate class: qp_stats. - Moved the stats update function into the new stats class. - Add the _stats_plugin_name property to net::qp class: default value is "network". Signed-off-by: Vlad Zolotarov --- net/dpdk.cc | 4 +- net/net.cc | 133 ++++++++++++++++++++++++++++++++++++++++-------- net/net.hh | 88 ++++++++++++++++++++++++++++---- net/virtio.cc | 2 +- net/xenfront.cc | 4 +- 5 files changed, 194 insertions(+), 37 deletions(-) diff --git a/net/dpdk.cc b/net/dpdk.cc index f423891a94..736450bb8f 100644 --- a/net/dpdk.cc +++ b/net/dpdk.cc @@ -1405,7 +1405,7 @@ void dpdk_device::check_port_link_status() template dpdk_qp::dpdk_qp(dpdk_device* dev, uint8_t qid) - : _dev(dev), _qid(qid), + : qp(true, "network", qid), _dev(dev), _qid(qid), _rx_gc_poller([&] { return rx_gc(); }), _tx_buf_factory(qid), _tx_gc_poller([&] { return _tx_buf_factory.gc(); }) @@ -1603,7 +1603,7 @@ template void dpdk_qp::process_packets( struct rte_mbuf **bufs, uint16_t count) { - update_rx_count(count); + _stats.rx.good.update_pkts_bunch(count); for (uint16_t i = 0; i < count; i++) { struct rte_mbuf *m = bufs[i]; offload_info oi; diff --git a/net/net.cc b/net/net.cc index 4a732446fc..c1e01dbc01 100644 --- a/net/net.cc +++ b/net/net.cc @@ -62,47 +62,136 @@ bool qp::poll_tx() { } } } while (work && _tx_packetq.size() < 128); - } if (!_tx_packetq.empty()) { - _last_tx_bunch = send(_tx_packetq); - _packets_snt += _last_tx_bunch; + _stats.tx.good.update_pkts_bunch(send(_tx_packetq)); return true; } return false; } -qp::qp() +qp::qp(bool register_copy_stats, + const std::string stats_plugin_name, uint8_t qid) : _tx_poller([this] { return poll_tx(); }) + , _stats_plugin_name(stats_plugin_name) + , _queue_name(std::string("queue") + std::to_string(qid)) , _collectd_regs({ - // queue_length value:GAUGE:0:U - // Absolute value of num packets in last tx bunch. - scollectd::add_polled_metric(scollectd::type_instance_id("network" + + // + // Packets rate: DERIVE:0:u + // + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name + , scollectd::per_cpu_plugin_instance + , "if_packets", _queue_name) + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.rx.good.packets) + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.tx.good.packets) + ), + // + // Bytes rate: DERIVE:0:U + // + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name + , scollectd::per_cpu_plugin_instance + , "if_octets", _queue_name) + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.rx.good.bytes) + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.tx.good.bytes) + ), + + // + // Queue length: GAUGE:0:U + // + // Tx + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name , scollectd::per_cpu_plugin_instance , "queue_length", "tx-packet-queue") - , scollectd::make_typed(scollectd::data_type::GAUGE, _last_tx_bunch) + , scollectd::make_typed(scollectd::data_type::GAUGE + , std::bind(&decltype(_tx_packetq)::size, &_tx_packetq)) ), - // total_operations value:DERIVE:0:U - scollectd::add_polled_metric(scollectd::type_instance_id("network" + + // + // Number of packets in last bunch: GAUGE:0:U + // + // Tx + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name , scollectd::per_cpu_plugin_instance - , "total_operations", "tx-packets") - , scollectd::make_typed(scollectd::data_type::DERIVE, _packets_snt) + , "requests", "tx-packet-queue-last-bunch") + , scollectd::make_typed(scollectd::data_type::GAUGE + , _stats.tx.good.last_bunch) ), - // queue_length value:GAUGE:0:U - // Absolute value of num packets in last rx bunch. - scollectd::add_polled_metric(scollectd::type_instance_id("network" + // Rx + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name , scollectd::per_cpu_plugin_instance - , "queue_length", "rx-packet-queue") - , scollectd::make_typed(scollectd::data_type::GAUGE, _last_rx_bunch) + , "requests", "rx-packet-queue-last-bunch") + , scollectd::make_typed(scollectd::data_type::GAUGE + , _stats.rx.good.last_bunch) ), - // total_operations value:DERIVE:0:U - scollectd::add_polled_metric(scollectd::type_instance_id("network" + + // + // Fragments rate: DERIVE:0:U + // + // Tx + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name , scollectd::per_cpu_plugin_instance - , "total_operations", "rx-packets") - , scollectd::make_typed(scollectd::data_type::DERIVE, _packets_rcv) + , "total_operations", "tx-frags") + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.tx.good.nr_frags) ), - }) { + // Rx + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name + , scollectd::per_cpu_plugin_instance + , "total_operations", "rx-frags") + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.rx.good.nr_frags) + ), + }) +{ + if (register_copy_stats) { + // + // Non-zero-copy data bytes rate: DERIVE:0:u + // + _collectd_regs.push_back( + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name + , scollectd::per_cpu_plugin_instance + , "if_octets", _queue_name + " Copy Bytes") + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.rx.good.copy_bytes) + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.tx.good.copy_bytes) + )); + // + // Non-zero-copy data fragments rate: DERIVE:0:u + // + // Tx + _collectd_regs.push_back( + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name + , scollectd::per_cpu_plugin_instance + , "total_operations", "tx-frags-copy") + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.tx.good.copy_frags) + )); + // Rx + _collectd_regs.push_back( + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name + , scollectd::per_cpu_plugin_instance + , "total_operations", "rx-frags-copy") + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.rx.good.copy_frags) + )); + } } qp::~qp() { diff --git a/net/net.hh b/net/net.hh index bccc73033d..a7c20f0274 100644 --- a/net/net.hh +++ b/net/net.hh @@ -133,6 +133,75 @@ public: friend class l3_protocol; }; +struct qp_stats_good { + /** + * Update the packets bunch related statistics. + * + * Update the last packets bunch size and the total packets counter. + * + * @param count Number of packets in the last packets bunch. + */ + void update_pkts_bunch(uint64_t count) { + last_bunch = count; + packets += count; + } + + /** + * Increment the appropriate counters when a few fragments have been + * processed in a copy-way. + * + * @param nr_frags Number of copied fragments + * @param bytes Number of copied bytes + */ + void update_copy_stats(uint64_t nr_frags, uint64_t bytes) { + copy_frags += nr_frags; + copy_bytes += bytes; + } + + /** + * Increment total fragments and bytes statistics + * + * @param nfrags Number of processed fragments + * @param nbytes Number of bytes in the processed fragments + */ + void update_frags_stats(uint64_t nfrags, uint64_t nbytes) { + nr_frags += nfrags; + bytes += nbytes; + } + + uint64_t bytes; // total number of bytes + uint64_t nr_frags; // total number of fragments + uint64_t copy_frags; // fragments that were copied on L2 level + uint64_t copy_bytes; // bytes that were copied on L2 level + uint64_t packets; // total number of packets + uint64_t last_bunch; // number of packets in the last sent/received bunch +}; + +struct qp_stats { + qp_stats() { + std::memset(&rx, 0, sizeof(rx)); + std::memset(&tx, 0, sizeof(tx)); + } + + struct { + struct qp_stats_good good; + + struct { + void inc_csum_err() { + ++csum; + ++total; + } + + uint64_t total; // total number of erroneous packets + uint64_t csum; // packets with bad checksum + } bad; + } rx; + + struct { + struct qp_stats_good good; + } tx; +}; + class qp { using packet_provider_type = std::function ()>; std::vector _pkt_providers; @@ -141,18 +210,17 @@ class qp { stream _rx_stream; reactor::poller _tx_poller; circular_buffer _tx_packetq; - uint64_t _packets_snt = 0; - uint64_t _packets_rcv = 0; - uint64_t _last_tx_bunch = 0; - uint64_t _last_rx_bunch = 0; - std::vector _collectd_regs; + protected: - void update_rx_count(uint64_t count) { - _last_rx_bunch = count; - _packets_rcv += count; - } + const std::string _stats_plugin_name; + const std::string _queue_name; + std::vector _collectd_regs; + qp_stats _stats; + public: - qp(); + qp(bool register_copy_stats = false, + const std::string stats_plugin_name = std::string("network"), + uint8_t qid = 0); virtual ~qp(); virtual future<> send(packet p) = 0; virtual uint32_t send(circular_buffer& p) { diff --git a/net/virtio.cc b/net/virtio.cc index 3243914759..6b05c8d573 100644 --- a/net/virtio.cc +++ b/net/virtio.cc @@ -556,7 +556,7 @@ protected: _ring.wake_notifier_wait(); } void update_rx_count(uint64_t c) { - _dev.update_rx_count(c); + _dev._stats.rx.good.update_pkts_bunch(c); } private: future<> prepare_buffers(); diff --git a/net/xenfront.cc b/net/xenfront.cc index c850ec60f7..39349529c5 100644 --- a/net/xenfront.cc +++ b/net/xenfront.cc @@ -253,7 +253,7 @@ future<> xenfront_qp::queue_rx_packet() bunch++; return true; }, _rx_refs); - update_rx_count(bunch); + _stats.rx.good.update_pkts_bunch(bunch); } void xenfront_qp::alloc_one_rx_reference(unsigned index) { @@ -310,7 +310,7 @@ port xenfront_qp::bind_rx_evtchn(bool split) { } xenfront_qp::xenfront_qp(xenfront_device* dev, boost::program_options::variables_map opts) - : _dev(dev) + : qp(true), _dev(dev) , _otherend(_dev->_xenstore->read(path("backend-id"))) , _backend(_dev->_xenstore->read(path("backend"))) , _gntalloc(gntalloc::instance(_dev->_userspace, _otherend)) From de9bad074b81eabe43d6114f727954094bbe9421 Mon Sep 17 00:00:00 2001 From: Vlad Zolotarov Date: Sun, 5 Apr 2015 11:56:14 +0300 Subject: [PATCH 07/39] DPDK: rework the tx_buf methods interface Rework the tx_buf class methods to accept dpdp_qp instead of tx_factory and dpdk_device. Signed-off-by: Vlad Zolotarov --- net/dpdk.cc | 71 +++++++++++++++++++++++++---------------------------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/net/dpdk.cc b/net/dpdk.cc index 736450bb8f..7b37023738 100644 --- a/net/dpdk.cc +++ b/net/dpdk.cc @@ -288,38 +288,36 @@ class dpdk_qp : public net::qp { * way. * * @param p packet to translate - * @param dev Parent dpdk_device - * @param fc Buffers' factory to use + * @param qp dpdk_qp handle * * @return the HEAD tx_buf of the cluster or nullptr in case of a * failure */ static tx_buf* from_packet_zc( - packet&& p, dpdk_device& dev, tx_buf_factory& fc) { + packet&& p, dpdk_qp& qp) { return from_packet(check_frag0, translate_one_frag, copy_one_frag, [](packet&& _p, tx_buf& _last_seg) { _last_seg.set_packet(std::move(_p)); - }, std::move(p), dev, fc); + }, std::move(p), qp); } /** * Creates a tx_buf cluster representing a given packet in a "copy" way. * * @param p packet to translate - * @param dev Parent dpdk_device - * @param fc Buffers' factory to use + * @param qp dpdk_qp handle * * @return the HEAD tx_buf of the cluster or nullptr in case of a * failure */ static tx_buf* from_packet_copy( - packet&& p, dpdk_device& dev, tx_buf_factory& fc) { + packet&& p, dpdk_qp& qp) { return from_packet([](packet& _p) { return true; }, copy_one_frag, copy_one_frag, [](packet&& _p, tx_buf& _last_seg) {}, - std::move(p), dev, fc); + std::move(p), qp); } private: /** @@ -330,8 +328,7 @@ class dpdk_qp : public net::qp { * @param do_one_frag Functor that handles a single frag translation * @param fin Functor that performs a cluster finalization * @param p packet to translate - * @param dev Parent dpdk_device object - * @param fc Buffers' factory to use + * @param qp dpdk_qp handle * * @return the HEAD tx_buf of the cluster or nullptr in case of a * failure @@ -341,7 +338,7 @@ class dpdk_qp : public net::qp { static tx_buf* from_packet( FirstFragCheck frag0_check, TrOneFunc do_one_frag, CopyOneFunc copy_one_frag, FinalizeFunc fin, - packet&& p, dpdk_device& dev, tx_buf_factory& fc) { + packet&& p, dpdk_qp& qp) { // Too fragmented - linearize if (p.nr_frags() > max_frags) { @@ -356,10 +353,10 @@ class dpdk_qp : public net::qp { // copied and if yes - send it in a copy way // if (!frag0_check(p)) { - if (!copy_one_frag(fc, p.frag(0), head, last_seg, nsegs)) { + if (!copy_one_frag(qp, p.frag(0), head, last_seg, nsegs)) { return nullptr; } - } else if (!do_one_frag(fc, p.frag(0), head, last_seg, nsegs)) { + } else if (!do_one_frag(qp, p.frag(0), head, last_seg, nsegs)) { return nullptr; } @@ -367,7 +364,7 @@ class dpdk_qp : public net::qp { for (unsigned i = 1; i < p.nr_frags(); i++) { rte_mbuf *h = nullptr, *new_last_seg = nullptr; - if (!do_one_frag(fc, p.frag(i), h, new_last_seg, nsegs)) { + if (!do_one_frag(qp, p.frag(i), h, new_last_seg, nsegs)) { me(head)->recycle(); return nullptr; } @@ -391,7 +388,7 @@ class dpdk_qp : public net::qp { rte_mbuf_l2_len(head) = sizeof(struct ether_hdr); rte_mbuf_l3_len(head) = oi.ip_hdr_len; } - if (dev.hw_features().tx_csum_l4_offload) { + if (qp.port().hw_features().tx_csum_l4_offload) { if (oi.protocol == ip_protocol_num::tcp) { head->ol_flags |= PKT_TX_TCP_CKSUM; // TODO: Take a VLAN header into an account here @@ -424,7 +421,7 @@ class dpdk_qp : public net::qp { * * @param do_one_buf Functor responsible for a single rte_mbuf * handling - * @param fc Buffers' factory to allocate the tx_buf from (in) + * @param qp dpdk_qp handle (in) * @param frag Fragment to copy (in) * @param head Head of the cluster (out) * @param last_seg Last segment of the cluster (out) @@ -433,7 +430,7 @@ class dpdk_qp : public net::qp { * @return TRUE in case of success */ template - static bool do_one_frag(DoOneBufFunc do_one_buf, tx_buf_factory& fc, + static bool do_one_frag(DoOneBufFunc do_one_buf, dpdk_qp& qp, fragment& frag, rte_mbuf*& head, rte_mbuf*& last_seg, unsigned& nsegs) { // @@ -450,7 +447,7 @@ class dpdk_qp : public net::qp { assert(frag.size); // Create a HEAD of mbufs' cluster and set the first bytes into it - len = do_one_buf(fc, head, base, left_to_set); + len = do_one_buf(qp, head, base, left_to_set); if (!len) { return false; } @@ -465,7 +462,7 @@ class dpdk_qp : public net::qp { // rte_mbuf* prev_seg = head; while (left_to_set) { - len = do_one_buf(fc, m, base, left_to_set); + len = do_one_buf(qp, m, base, left_to_set); if (!len) { me(head)->recycle(); return false; @@ -488,7 +485,7 @@ class dpdk_qp : public net::qp { /** * Zero-copy handling of a single net::fragment. * - * @param fc Buffers' factory to allocate the tx_buf from (in) + * @param qp dpdk_qp handle (in) * @param frag Fragment to copy (in) * @param head Head of the cluster (out) * @param last_seg Last segment of the cluster (out) @@ -496,17 +493,17 @@ class dpdk_qp : public net::qp { * * @return TRUE in case of success */ - static bool translate_one_frag(tx_buf_factory& fc, fragment& frag, + static bool translate_one_frag(dpdk_qp& qp, fragment& frag, rte_mbuf*& head, rte_mbuf*& last_seg, unsigned& nsegs) { - return do_one_frag(set_one_data_buf, fc, frag, head, + return do_one_frag(set_one_data_buf, qp, frag, head, last_seg, nsegs); } /** * Copies one net::fragment into the cluster of rte_mbuf's. * - * @param fc Buffers' factory to allocate the tx_buf from (in) + * @param qp dpdk_qp handle (in) * @param frag Fragment to copy (in) * @param head Head of the cluster (out) * @param last_seg Last segment of the cluster (out) @@ -517,10 +514,10 @@ class dpdk_qp : public net::qp { * * @return TRUE in case of success */ - static bool copy_one_frag(tx_buf_factory& fc, fragment& frag, + static bool copy_one_frag(dpdk_qp& qp, fragment& frag, rte_mbuf*& head, rte_mbuf*& last_seg, unsigned& nsegs) { - return do_one_frag(copy_one_data_buf, fc, frag, head, + return do_one_frag(copy_one_data_buf, qp, frag, head, last_seg, nsegs); } @@ -528,7 +525,7 @@ class dpdk_qp : public net::qp { * Allocates a single rte_mbuf and sets it to point to a given data * buffer. * - * @param fc Buffers' factory to allocate the tx_buf from (in) + * @param qp dpdk_qp handle (in) * @param m New allocated rte_mbuf (out) * @param va virtual address of a data buffer (in) * @param buf_len length of the data to copy (in) @@ -536,7 +533,7 @@ class dpdk_qp : public net::qp { * @return The actual number of bytes that has been set in the mbuf */ static size_t set_one_data_buf( - tx_buf_factory& fc, rte_mbuf*& m, char* va, size_t buf_len) { + dpdk_qp& qp, rte_mbuf*& m, char* va, size_t buf_len) { using namespace memory; translation tr = translate(va, buf_len); @@ -551,10 +548,10 @@ class dpdk_qp : public net::qp { phys_addr_t pa = tr.addr; if (!tr.size) { - return copy_one_data_buf(fc, m, va, buf_len); + return copy_one_data_buf(qp, m, va, buf_len); } - tx_buf* buf = fc.get(); + tx_buf* buf = qp.get_tx_buf(); if (!buf) { return 0; } @@ -571,7 +568,7 @@ class dpdk_qp : public net::qp { /** * Allocates a single rte_mbuf and copies a given data into it. * - * @param fc Buffers' factory to allocate the tx_buf from (in) + * @param qp dpdk_qp handle (in) * @param m New allocated rte_mbuf (out) * @param data Data to copy from (in) * @param buf_len length of the data to copy (in) @@ -579,9 +576,9 @@ class dpdk_qp : public net::qp { * @return The actual number of bytes that has been copied */ static size_t copy_one_data_buf( - tx_buf_factory& fc, rte_mbuf*& m, char* data, size_t buf_len) + dpdk_qp& qp, rte_mbuf*& m, char* data, size_t buf_len) { - tx_buf* buf = fc.get(); + tx_buf* buf = qp.get_tx_buf(); if (!buf) { return 0; } @@ -594,7 +591,6 @@ class dpdk_qp : public net::qp { rte_mbuf_data_len(m) = len; rte_mbuf_pkt_len(m) = len; - rte_memcpy(rte_pktmbuf_mtod(m, void*), data, len); return len; @@ -901,17 +897,18 @@ public: if (HugetlbfsMemBackend) { // Zero-copy send return _send(pb, [&] (packet&& p) { - return tx_buf::from_packet_zc( - std::move(p), *_dev, _tx_buf_factory); + return tx_buf::from_packet_zc(std::move(p), *this); }); } else { // "Copy"-send return _send(pb, [&](packet&& p) { - return tx_buf::from_packet_copy( - std::move(p), *_dev, _tx_buf_factory); + return tx_buf::from_packet_copy(std::move(p), *this); }); } } + + dpdk_device& port() const { return *_dev; } + tx_buf* get_tx_buf() { return _tx_buf_factory.get(); } private: template From 10cad8a87a1c1603111200f044c11d2aea174e2d Mon Sep 17 00:00:00 2001 From: Vlad Zolotarov Date: Mon, 6 Apr 2015 15:59:12 +0300 Subject: [PATCH 08/39] DPDK: update network statistics Signed-off-by: Vlad Zolotarov --- net/dpdk.cc | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/net/dpdk.cc b/net/dpdk.cc index 7b37023738..fc312e39bd 100644 --- a/net/dpdk.cc +++ b/net/dpdk.cc @@ -591,6 +591,8 @@ class dpdk_qp : public net::qp { rte_mbuf_data_len(m) = len; rte_mbuf_pkt_len(m) = len; + qp._stats.tx.good.update_copy_stats(1, len); + rte_memcpy(rte_pktmbuf_mtod(m, void*), data, len); return len; @@ -931,10 +933,17 @@ private: _tx_burst.data() + _tx_burst_idx, _tx_burst.size() - _tx_burst_idx); + uint64_t nr_frags = 0, bytes = 0; + for (int i = 0; i < sent; i++) { + rte_mbuf* m = _tx_burst[_tx_burst_idx + i]; + bytes += rte_mbuf_pkt_len(m); + nr_frags += rte_mbuf_nb_segs(m); pb.pop_front(); } + _stats.tx.good.update_frags_stats(nr_frags, bytes); + _tx_burst_idx += sent; if (_tx_burst_idx == _tx_burst.size()) { @@ -1600,11 +1609,15 @@ template void dpdk_qp::process_packets( struct rte_mbuf **bufs, uint16_t count) { - _stats.rx.good.update_pkts_bunch(count); + uint64_t nr_frags = 0, bytes = 0; + for (uint16_t i = 0; i < count; i++) { struct rte_mbuf *m = bufs[i]; offload_info oi; + nr_frags += rte_mbuf_nb_segs(m); + bytes += rte_mbuf_pkt_len(m); + packet p = from_mbuf(m); // Set stipped VLAN value if available @@ -1617,6 +1630,7 @@ void dpdk_qp::process_packets( if (_dev->hw_features().rx_csum_offload) { if (m->ol_flags & (PKT_RX_IP_CKSUM_BAD | PKT_RX_L4_CKSUM_BAD)) { // Packet with bad checksum, just drop it. + _stats.rx.bad.inc_csum_err(); continue; } // Note that when _hw_features.rx_csum_offload is on, the receive @@ -1631,6 +1645,14 @@ void dpdk_qp::process_packets( _dev->l2receive(std::move(p)); } + + _stats.rx.good.update_pkts_bunch(count); + _stats.rx.good.update_frags_stats(nr_frags, bytes); + + if (!HugetlbfsMemBackend) { + _stats.rx.good.copy_frags = _stats.rx.good.nr_frags; + _stats.rx.good.copy_bytes = _stats.rx.good.bytes; + } } template From 80bad5273a5e4e4314eac19a70b4e6ce61e705c3 Mon Sep 17 00:00:00 2001 From: Vlad Zolotarov Date: Sun, 5 Apr 2015 18:38:37 +0300 Subject: [PATCH 09/39] virtio: update network statistics Signed-off-by: Vlad Zolotarov --- net/virtio.cc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/net/virtio.cc b/net/virtio.cc index 6b05c8d573..5214eb17bc 100644 --- a/net/virtio.cc +++ b/net/virtio.cc @@ -591,11 +591,17 @@ qp::txq::txq(qp& dev, ring_config config) uint32_t qp::txq::post(circular_buffer& pb) { + uint64_t bytes = 0, nr_frags = 0; + _packets.clear(); while (!pb.empty() && pb.front().nr_frags() + 1 <= _ring.available_descriptors().current()) { net_hdr_mrg vhdr = {}; auto p = std::move(pb.front()); + + bytes += p.len(); + nr_frags += p.nr_frags(); + pb.pop_front(); // Handle TCP checksum offload auto oi = p.offload_info(); @@ -642,6 +648,9 @@ qp::txq::post(circular_buffer& pb) { _packets.emplace_back(packet_as_buffer_chain{ std::move(q) }); } _ring.post(_packets.begin(), _packets.end()); + + _dev._stats.tx.good.update_frags_stats(nr_frags, bytes); + return _packets.size(); } @@ -723,7 +732,12 @@ qp::rxq::complete_buffer(single_buffer&& bc, size_t len) { del = make_object_deleter(std::move(_buffers)); } packet p(_fragments.begin(), _fragments.end(), std::move(del)); + + _dev._stats.rx.good.update_frags_stats(p.nr_frags(), p.len()); + _dev._dev->l2receive(std::move(p)); + + _ring.available_descriptors().signal(_fragments.size()); } } From a33994924fd87a163aa48f2898019baa533dd528 Mon Sep 17 00:00:00 2001 From: Vlad Zolotarov Date: Mon, 6 Apr 2015 12:04:21 +0300 Subject: [PATCH 10/39] xenfront: make front_ring::process_ring() return void This function was always returning a ready future, so I made it return void and changed the callers to explicitly return ready future. Signed-off-by: Vlad Zolotarov --- net/xenfront.cc | 14 +++++++++----- net/xenfront.hh | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/net/xenfront.cc b/net/xenfront.cc index 39349529c5..43b1886ecb 100644 --- a/net/xenfront.cc +++ b/net/xenfront.cc @@ -212,7 +212,7 @@ unsigned front_ring::entries::get_index() { } template -future<> front_ring::process_ring(std::function func, grant_head *refs) +void front_ring::process_ring(std::function func, grant_head *refs) { auto prod = _sring->rsp_prod; rmb(); @@ -240,20 +240,22 @@ future<> front_ring::process_ring(std::function } rsp_cons = prod; _sring->rsp_event = prod + 1; - - return make_ready_future<>(); } future<> xenfront_qp::queue_rx_packet() { uint64_t bunch; - return _rx_ring.process_ring([this, &bunch] (gntref &entry, rx &rx) mutable { + + _rx_ring.process_ring([this, &bunch] (gntref &entry, rx &rx) mutable { packet p(static_cast(entry.page) + rx.rsp.offset, rx.rsp.status); _dev->l2receive(std::move(p)); bunch++; return true; }, _rx_refs); + _stats.rx.good.update_pkts_bunch(bunch); + + return make_ready_future<>(); } void xenfront_qp::alloc_one_rx_reference(unsigned index) { @@ -283,7 +285,7 @@ future<> xenfront_qp::alloc_rx_references() { future<> xenfront_qp::handle_tx_completions() { - return _tx_ring.process_ring([this] (gntref &entry, tx &tx) { + _tx_ring.process_ring([this] (gntref &entry, tx &tx) { if (tx.rsp.status == 1) { return false; } @@ -295,6 +297,8 @@ future<> xenfront_qp::handle_tx_completions() { return true; }, _tx_refs); + + return make_ready_future<>(); } port xenfront_qp::bind_tx_evtchn(bool split) { diff --git a/net/xenfront.hh b/net/xenfront.hh index 40768e617a..ed2a18833a 100644 --- a/net/xenfront.hh +++ b/net/xenfront.hh @@ -125,7 +125,7 @@ public: entries entries; - future<> process_ring(std::function func, grant_head *refs); + void process_ring(std::function func, grant_head *refs); void dump() { _sring->dump(); From da02b240ef58e30c67cc14978963ea067c140292 Mon Sep 17 00:00:00 2001 From: Vlad Zolotarov Date: Mon, 6 Apr 2015 14:15:42 +0300 Subject: [PATCH 11/39] xenfront: Fix: initialize the "bunch" variable Signed-off-by: Vlad Zolotarov --- net/xenfront.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/xenfront.cc b/net/xenfront.cc index 43b1886ecb..846bd82fb6 100644 --- a/net/xenfront.cc +++ b/net/xenfront.cc @@ -244,7 +244,7 @@ void front_ring::process_ring(std::function func future<> xenfront_qp::queue_rx_packet() { - uint64_t bunch; + uint64_t bunch = 0; _rx_ring.process_ring([this, &bunch] (gntref &entry, rx &rx) mutable { packet p(static_cast(entry.page) + rx.rsp.offset, rx.rsp.status); From 671c57e5b9f71919952565d1acad605d18671b7c Mon Sep 17 00:00:00 2001 From: Vlad Zolotarov Date: Tue, 7 Apr 2015 15:08:51 +0300 Subject: [PATCH 12/39] xenfront: update network statistics - Add xenfront_qp private member to fron_ring class. - Update network statistics. Signed-off-by: Vlad Zolotarov --- net/xenfront.cc | 31 ++++++++++++++++++++++++------- net/xenfront.hh | 7 +++++-- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/net/xenfront.cc b/net/xenfront.cc index 846bd82fb6..6061af4548 100644 --- a/net/xenfront.cc +++ b/net/xenfront.cc @@ -119,6 +119,7 @@ public: ~xenfront_qp(); virtual void rx_start() override; virtual future<> send(packet p) override; + void inc_rx_error_count() { ++_stats.rx.bad.total; } }; std::unordered_map @@ -149,6 +150,10 @@ xenfront_qp::send(packet _p) { // // In-kernel should be fine + if (_p.nr_frags() > 1) { + _stats.tx.good.update_copy_stats(_p.nr_frags(), _p.len()); + } + // FIXME: negotiate and use scatter/gather _p.linearize(); @@ -180,14 +185,15 @@ xenfront_qp::send(packet _p) { _tx_ring.req_prod_pvt = idx; _tx_ring._sring->req_prod = req_prod + 1; - _tx_ring._sring->req_event++; + if ((frag + 1) == p.nr_frags()) { _tx_evtchn.notify(); - return make_ready_future<>(); - } else { - return make_ready_future<>(); } + + _stats.tx.good.update_frags_stats(1, f.size); + + return make_ready_future<>(); }); // FIXME: Don't forget to clear all grant refs when frontend closes. Or is it automatic? @@ -222,6 +228,7 @@ void front_ring::process_ring(std::function func if (el.rsp.status < 0) { dump("Packet error", el.rsp); + _dev.inc_rx_error_count(); continue; } @@ -245,15 +252,25 @@ void front_ring::process_ring(std::function func future<> xenfront_qp::queue_rx_packet() { uint64_t bunch = 0; + uint64_t bytes = 0; - _rx_ring.process_ring([this, &bunch] (gntref &entry, rx &rx) mutable { + _rx_ring.process_ring([this, &bunch, &bytes] (gntref &entry, rx &rx) mutable { packet p(static_cast(entry.page) + rx.rsp.offset, rx.rsp.status); + _dev->l2receive(std::move(p)); + + bytes += rx.rsp.status; bunch++; + return true; }, _rx_refs); _stats.rx.good.update_pkts_bunch(bunch); + // + // Our XEN implementation only supports packets with a single fragment + // at the moment. + // + _stats.rx.good.update_frags_stats(bunch, bytes); return make_ready_future<>(); } @@ -319,8 +336,8 @@ xenfront_qp::xenfront_qp(xenfront_device* dev, boost::program_options::variables , _backend(_dev->_xenstore->read(path("backend"))) , _gntalloc(gntalloc::instance(_dev->_userspace, _otherend)) , _evtchn(evtchn::instance(_dev->_userspace, _otherend)) - , _tx_ring(_gntalloc->alloc_ref()) - , _rx_ring(_gntalloc->alloc_ref()) + , _tx_ring(_gntalloc->alloc_ref(), *this) + , _rx_ring(_gntalloc->alloc_ref(), *this) , _tx_refs(_gntalloc->alloc_ref(front_ring::nr_ents)) , _rx_refs(_gntalloc->alloc_ref(front_ring::nr_ents)) { diff --git a/net/xenfront.hh b/net/xenfront.hh index ed2a18833a..31ae845db4 100644 --- a/net/xenfront.hh +++ b/net/xenfront.hh @@ -90,6 +90,7 @@ public: }; using phys = uint64_t; +class xenfront_qp; template class front_ring { @@ -118,9 +119,9 @@ public: uint32_t rsp_cons = 0; int32_t ref = -1; - front_ring(gntref r) + front_ring(gntref r, xenfront_qp& dev) : ref(r.xen_id), entries(this) - , _sring(new (r.page) sring()) { + , _sring(new (r.page) sring()), _dev(dev) { } entries entries; @@ -145,6 +146,8 @@ public: sring *_sring; T& operator[](std::size_t i) { return _sring->_ring[idx(i)]; } +private: + xenfront_qp& _dev; }; } From 7c692c499c30d527dc3ac81bc64dd064e7dccb88 Mon Sep 17 00:00:00 2001 From: Vlad Zolotarov Date: Wed, 8 Apr 2015 12:10:07 +0300 Subject: [PATCH 13/39] net stats: Register backend specific collectd counters Error counters are backend-specific thus we will register them separately for each specific backend. Signed-off-by: Vlad Zolotarov --- net/dpdk.cc | 17 +++++++++++++++++ net/xenfront.cc | 9 +++++++++ 2 files changed, 26 insertions(+) diff --git a/net/dpdk.cc b/net/dpdk.cc index fc312e39bd..21c15a8297 100644 --- a/net/dpdk.cc +++ b/net/dpdk.cc @@ -1438,6 +1438,23 @@ dpdk_qp::dpdk_qp(dpdk_device* dev, uint8_t qid) rte_eth_dev_socket_id(_dev->port_idx()), _dev->def_tx_conf()) < 0) { rte_exit(EXIT_FAILURE, "Cannot initialize tx queue\n"); } + + // Register error statistics: Rx total and checksum errors + _collectd_regs.push_back( + scollectd::add_polled_metric(scollectd::type_instance_id("network" + , scollectd::per_cpu_plugin_instance + , "requests", "rx-csum-errors") + , scollectd::make_typed(scollectd::data_type::GAUGE + , _stats.rx.bad.csum) + )); + + _collectd_regs.push_back( + scollectd::add_polled_metric(scollectd::type_instance_id("network" + , scollectd::per_cpu_plugin_instance + , "requests", "rx-errors") + , scollectd::make_typed(scollectd::data_type::GAUGE + , _stats.rx.bad.total) + )); } template diff --git a/net/xenfront.cc b/net/xenfront.cc index 6061af4548..eba396d65b 100644 --- a/net/xenfront.cc +++ b/net/xenfront.cc @@ -381,6 +381,15 @@ xenfront_qp::xenfront_qp(xenfront_device* dev, boost::program_options::variables _dev->_xenstore->write(path("state"), 4, t); } + // Register Rx error statistics + _collectd_regs.push_back( + scollectd::add_polled_metric(scollectd::type_instance_id("network" + , scollectd::per_cpu_plugin_instance + , "requests", "rx-errors") + , scollectd::make_typed(scollectd::data_type::GAUGE + , _stats.rx.bad.total) + )); + keep_doing([this] { return alloc_rx_references(); }); From 539efcebc2c99ebdd32c48e388d4660649b863a5 Mon Sep 17 00:00:00 2001 From: Vlad Zolotarov Date: Wed, 8 Apr 2015 11:54:36 +0300 Subject: [PATCH 14/39] DPDK: added port_stats: struct and collection Signed-off-by: Vlad Zolotarov --- net/dpdk.cc | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/net/dpdk.cc b/net/dpdk.cc index 21c15a8297..aaafbe18af 100644 --- a/net/dpdk.cc +++ b/net/dpdk.cc @@ -161,6 +161,38 @@ static constexpr const char* pktmbuf_pool_name = "dpdk_net_pktmbuf_pool"; static constexpr uint8_t packet_read_size = 32; /******************************************************************************/ +struct port_stats { + port_stats() { + std::memset(&rx, 0, sizeof(rx)); + std::memset(&tx, 0, sizeof(tx)); + } + + struct { + struct { + uint64_t mcast; // number of received multicast packets + uint64_t pause_xon; // number of received PAUSE XON frames + uint64_t pause_xoff; // number of received PAUSE XOFF frames + } good; + + struct { + uint64_t dropped; // missed packets (e.g. full FIFO) + uint64_t crc; // packets with CRC error + uint64_t len; // packets with a bad length + uint64_t total; // total number of erroneous received packets + } bad; + } rx; + + struct { + struct { + uint64_t pause_xon; // number of sent PAUSE XON frames + uint64_t pause_xoff; // number of sent PAUSE XOFF frames + } good; + + struct { + uint64_t total; // total number of failed transmitted packets + } bad; + } tx; +}; class dpdk_device : public device { uint8_t _port_idx; @@ -174,6 +206,8 @@ class dpdk_device : public device { struct rte_eth_rxconf _rx_conf_default = {}; struct rte_eth_txconf _tx_conf_default = {}; #endif + port_stats _stats; + timer<> _stats_collector; public: rte_eth_dev_info _dev_info = {}; @@ -226,7 +260,35 @@ public: if (ret != 0) { rte_exit(EXIT_FAILURE, "Cannot initialise port %u\n", _port_idx); } + + _stats_collector.set_callback([&] { + rte_eth_stats rte_stats = {}; + int rc = rte_eth_stats_get(_port_idx, &rte_stats); + + if (rc) { + printf("Failed to get port statistics: %s\n", strerror(rc)); + } + + _stats.rx.good.mcast = rte_stats.imcasts; + _stats.rx.good.pause_xon = rte_stats.rx_pause_xon; + _stats.rx.good.pause_xoff = rte_stats.rx_pause_xoff; + + _stats.rx.bad.crc = rte_stats.ibadcrc; + _stats.rx.bad.dropped = rte_stats.imissed; + _stats.rx.bad.len = rte_stats.ibadlen; + _stats.rx.bad.total = rte_stats.ierrors; + + _stats.tx.good.pause_xon = rte_stats.tx_pause_xon; + _stats.tx.good.pause_xoff = rte_stats.tx_pause_xoff; + + _stats.tx.bad.total = rte_stats.oerrors; + }); } + + ~dpdk_device() { + _stats_collector.cancel(); + } + ethernet_address hw_address() override { struct ether_addr mac; rte_eth_macaddr_get(_port_idx, &mac); @@ -1397,6 +1459,9 @@ void dpdk_device::check_port_link_status() ("full-duplex") : ("half-duplex\n")) << std::endl; _link_ready_promise.set_value(); + + // We may start collecting statistics only after the Link is UP. + _stats_collector.arm_periodic(2s); } else if (count++ < max_check_time) { std::cout << "." << std::flush; return; From 1aad5d48c7d393d18d8a57abfe8d85a36f00784d Mon Sep 17 00:00:00 2001 From: Vlad Zolotarov Date: Tue, 14 Apr 2015 20:02:34 +0300 Subject: [PATCH 15/39] DPDK: Register Port statistics Signed-off-by: Vlad Zolotarov --- net/dpdk.cc | 100 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 92 insertions(+), 8 deletions(-) diff --git a/net/dpdk.cc b/net/dpdk.cc index aaafbe18af..edc60745f3 100644 --- a/net/dpdk.cc +++ b/net/dpdk.cc @@ -208,6 +208,9 @@ class dpdk_device : public device { #endif port_stats _stats; timer<> _stats_collector; + const std::string _stats_plugin_name; + const std::string _stats_plugin_inst; + std::vector _collectd_regs; public: rte_eth_dev_info _dev_info = {}; @@ -253,7 +256,10 @@ public: : _port_idx(port_idx) , _num_queues(num_queues) , _home_cpu(engine().cpu_id()) - , _use_lro(use_lro) { + , _use_lro(use_lro) + , _stats_plugin_name("network") + , _stats_plugin_inst(std::string("port") + std::to_string(_port_idx)) + { /* now initialise the port we will use */ int ret = init_port_start(); @@ -283,6 +289,78 @@ public: _stats.tx.bad.total = rte_stats.oerrors; }); + + // Register port statistics collectd pollers + // Rx Good + _collectd_regs.push_back( + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name + , _stats_plugin_inst + , "if_multicast", _stats_plugin_inst + " Rx Multicast") + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.rx.good.mcast) + )); + + // Rx Errors + _collectd_regs.push_back( + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name + , _stats_plugin_inst + , "if_rx_errors", "Bad CRC") + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.rx.bad.crc) + )); + _collectd_regs.push_back( + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name + , _stats_plugin_inst + , "if_rx_errors", "Dropped") + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.rx.bad.dropped) + )); + _collectd_regs.push_back( + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name + , _stats_plugin_inst + , "if_rx_errors", "Bad Length") + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.rx.bad.len) + )); + + // Coupled counters: + // Good + _collectd_regs.push_back( + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name + , _stats_plugin_inst + , "if_packets", _stats_plugin_inst + " Pause XON") + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.rx.good.pause_xon) + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.tx.good.pause_xon) + )); + _collectd_regs.push_back( + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name + , _stats_plugin_inst + , "if_packets", _stats_plugin_inst + " Pause XOFF") + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.rx.good.pause_xoff) + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.tx.good.pause_xoff) + )); + + // Errors + _collectd_regs.push_back( + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name + , _stats_plugin_inst + , "if_errors", _stats_plugin_inst) + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.rx.bad.total) + , scollectd::make_typed(scollectd::data_type::DERIVE + , _stats.tx.bad.total) + )); } ~dpdk_device() { @@ -949,7 +1027,8 @@ class dpdk_qp : public net::qp { }; public: - explicit dpdk_qp(dpdk_device* dev, uint8_t qid); + explicit dpdk_qp(dpdk_device* dev, uint8_t qid, + const std::string stats_plugin_name); virtual void rx_start() override; virtual future<> send(packet p) override { @@ -1475,8 +1554,9 @@ void dpdk_device::check_port_link_status() } template -dpdk_qp::dpdk_qp(dpdk_device* dev, uint8_t qid) - : qp(true, "network", qid), _dev(dev), _qid(qid), +dpdk_qp::dpdk_qp(dpdk_device* dev, uint8_t qid, + const std::string stats_plugin_name) + : qp(true, stats_plugin_name, qid), _dev(dev), _qid(qid), _rx_gc_poller([&] { return rx_gc(); }), _tx_buf_factory(qid), _tx_gc_poller([&] { return _tx_buf_factory.gc(); }) @@ -1506,7 +1586,8 @@ dpdk_qp::dpdk_qp(dpdk_device* dev, uint8_t qid) // Register error statistics: Rx total and checksum errors _collectd_regs.push_back( - scollectd::add_polled_metric(scollectd::type_instance_id("network" + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name , scollectd::per_cpu_plugin_instance , "requests", "rx-csum-errors") , scollectd::make_typed(scollectd::data_type::GAUGE @@ -1514,7 +1595,8 @@ dpdk_qp::dpdk_qp(dpdk_device* dev, uint8_t qid) )); _collectd_regs.push_back( - scollectd::add_polled_metric(scollectd::type_instance_id("network" + scollectd::add_polled_metric(scollectd::type_instance_id( + _stats_plugin_name , scollectd::per_cpu_plugin_instance , "requests", "rx-errors") , scollectd::make_typed(scollectd::data_type::GAUGE @@ -1799,9 +1881,11 @@ std::unique_ptr dpdk_device::init_local_queue(boost::program_options::variab std::unique_ptr qp; if (opts.count("hugepages")) { - qp = std::make_unique>(this, qid); + qp = std::make_unique>(this, qid, + _stats_plugin_name + "-" + _stats_plugin_inst); } else { - qp = std::make_unique>(this, qid); + qp = std::make_unique>(this, qid, + _stats_plugin_name + "-" + _stats_plugin_inst); } smp::submit_to(_home_cpu, [this] () mutable { From 5afc4c718106ae1033b89291829a40e89961d29f Mon Sep 17 00:00:00 2001 From: Avi Kivity Date: Wed, 15 Apr 2015 16:50:23 +0300 Subject: [PATCH 16/39] Add doxygen configurarion file --- Doxyfile | 2362 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2362 insertions(+) create mode 100644 Doxyfile diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000000..b269f77b62 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,2362 @@ +# Doxyfile 1.8.9.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Seastar" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "High performance C++ framework for concurrent servers" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = build/documentation + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = YES + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = YES + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = YES + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = YES + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = YES + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. + +INPUT = + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = build + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: NO. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /