├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── Makefile.am ├── README.md ├── ahutils ├── Makefile ├── README.md ├── rfile.cc ├── rfile.hh └── testrunner.cc ├── bootstrap ├── build-deb ├── build-rpm ├── builder-support ├── debian │ ├── compat │ ├── control │ ├── copyright │ ├── metronome.install │ ├── metronome.postinst │ ├── metronome.service │ └── rules ├── dockerfiles │ ├── Dockerfile.debbuild │ ├── Dockerfile.rpmbuild │ ├── Dockerfile.target.centos-6 │ ├── Dockerfile.target.centos-7 │ ├── Dockerfile.target.debian-bullseye │ ├── Dockerfile.target.debian-buster │ ├── Dockerfile.target.debian-stretch │ └── Dockerfile.target.sdist ├── post-build └── specfiles │ └── metronome.spec ├── configure.ac ├── dolog.hh ├── examples └── linux-system-feeder ├── html ├── detail.css ├── engine.js ├── favicon.ico ├── graph.css ├── graphs.js ├── index.html ├── js │ ├── common.js │ ├── d3.v3.js │ ├── jquery-1.8.3.min.js │ ├── jsrender.js │ ├── purl.js │ ├── rickshaw.min.js │ └── underscore-min.js ├── legend.css ├── lines.css └── local.js ├── interpolate.cc ├── interpolate.hh ├── iputils.cc ├── iputils.hh ├── m4 ├── ax_arg_default_enable_disable.m4 ├── ax_cxx_compile_stdcxx_11.m4 ├── boost.m4 ├── metronome_with_eigen.m4 ├── pdns_check_os.m4 ├── pdns_d_fortify_source.m4 ├── pdns_enable_malloc_trace.m4 ├── pdns_enable_sanitizers.m4 ├── pdns_param_ssp_buffer_size.m4 ├── pdns_pie.m4 ├── pdns_relro.m4 ├── pdns_stack_protector.m4 ├── systemd.m4 └── warnings.m4 ├── mdump.cc ├── metromisc.hh ├── metronome-upstart.conf ├── metronome.cc ├── metronome.service.in ├── mmanage.cc ├── msubmit.cc ├── statstorage.cc ├── statstorage.hh ├── test-statstorage.cc ├── testrunner.cc └── yahttp ├── .gitignore ├── LICENSE ├── Makefile.am ├── README.md └── yahttp ├── Makefile.am ├── cookie.hpp ├── exception.hpp ├── reqresp.cpp ├── reqresp.hpp ├── router.cpp ├── router.hpp ├── url.hpp ├── utility.hpp ├── yahttp-config.h └── yahttp.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Compiled Dynamic libraries 8 | *.so 9 | *.dylib 10 | *.dll 11 | 12 | # Compiled Static libraries 13 | *.lai 14 | *.la 15 | *.a 16 | *.lib 17 | 18 | # Executables 19 | *.exe 20 | *.out 21 | *.app 22 | 23 | # joe backups 24 | *~ 25 | 26 | # autotools 27 | /Makefile 28 | /Makefile.in 29 | /aclocal.m4 30 | /autom4te.cache 31 | /build-aux/* 32 | !/build-aux/gen-version 33 | /config.cache 34 | /config.guess 35 | /config.h 36 | /config.h.in 37 | /config.sub 38 | /configure 39 | /configure.in 40 | /config.log 41 | /config.status 42 | /depcomp 43 | /install-sh 44 | /ltmain.sh 45 | /libtool 46 | /missing 47 | /mkinstalldirs 48 | /stamp-h 49 | /stamp-h1 50 | /test-driver 51 | /ylwrap 52 | *.la 53 | *.lo 54 | *.a 55 | *.o 56 | .deps 57 | .libs 58 | .dirstamp 59 | .version 60 | libtool.m4 61 | ltoptions.m4 62 | ltsugar.m4 63 | ltversion.m4 64 | lt~obsolete.m4 65 | metronome-*.tar.bz2 66 | 67 | # compiled programs 68 | msubmit 69 | mmanage 70 | metronome 71 | mdump 72 | 73 | metronome.service 74 | 75 | debian/changelog 76 | 77 | *.deb 78 | *.rpm 79 | built_pkgs 80 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "builder"] 2 | path = builder 3 | url = https://github.com/PowerDNS/pdns-builder.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | dist: trusty 3 | sudo: yes 4 | compiler: 5 | - gcc 6 | before_script: 7 | - sudo apt-get update 8 | - sudo apt-get install -y libboost-test-dev libboost-program-options-dev libeigen3-dev 9 | script: 10 | - ./bootstrap 11 | - ./configure 12 | - make dist 13 | - tar xf metronome-*.tar.bz2 -C /tmp 14 | - cd /tmp/metronome-* 15 | - ./configure 16 | - make -j 4 17 | - make -j 4 check 18 | - cd - 19 | - ./builder/build.sh centos-7 20 | - ./builder/build.sh debian-buster 21 | - ./builder/build.sh debian-bullseye 22 | notifications: 23 | email: 24 | - bert.hubert@netherlabs.nl 25 | 26 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | metronomehtmldir = @datarootdir@/metronome 2 | AM_CPPFLAGS = $(EIGEN3_CFLAGS) $(YAHTTP_CFLAGS) $(BOOST_CPPFLAGS) $(SANITIZER_FLAGS) -O3 -Wall -pthread 3 | 4 | AM_LDFLAGS = \ 5 | $(PROGRAM_LDFLAGS) \ 6 | $(THREADFLAGS) \ 7 | $(BOOST_LDFLAGS) 8 | 9 | ACLOCAL_AMFLAGS = -I m4 10 | 11 | SUBDIRS=yahttp 12 | 13 | bin_PROGRAMS = \ 14 | metronome \ 15 | msubmit \ 16 | mmanage \ 17 | mdump 18 | 19 | metronome_SOURCES = \ 20 | metronome.cc \ 21 | iputils.cc iputils.hh \ 22 | statstorage.cc statstorage.hh \ 23 | interpolate.cc interpolate.hh \ 24 | metromisc.hh \ 25 | dolog.hh 26 | 27 | metronome_LDFLAGS = \ 28 | $(AM_LDFLAGS) \ 29 | $(BOOST_PROGRAM_OPTIONS_LIBS) \ 30 | $(YAHTTP_LIBS) 31 | 32 | metronome_LDADD = \ 33 | $(BOOST_PROGRAM_OPTIONS_LIBS) \ 34 | $(SYSTEMD_LIBS) 35 | 36 | msubmit_SOURCES = \ 37 | msubmit.cc \ 38 | iputils.cc iputils.hh \ 39 | metromisc.hh \ 40 | dolog.hh 41 | 42 | mmanage_SOURCES= \ 43 | mmanage.cc \ 44 | statstorage.cc statstorage.hh \ 45 | metromisc.hh \ 46 | dolog.hh 47 | 48 | mdump_SOURCES= \ 49 | mdump.cc \ 50 | statstorage.cc statstorage.hh \ 51 | metromisc.hh \ 52 | dolog.hh 53 | 54 | 55 | nobase_dist_metronomehtml_DATA = \ 56 | html/*.* \ 57 | html/js/* 58 | 59 | dist_doc_DATA = \ 60 | README.md 61 | 62 | EXTRA_DIST = \ 63 | LICENSE \ 64 | metronome.service.in \ 65 | metronome-upstart.conf 66 | 67 | if HAVE_SYSTEMD 68 | metronome.service: metronome.service.in 69 | $(AM_V_GEN)sed -e 's![@]bindir[@]!$(bindir)!' < $< > $@ 70 | 71 | systemdsystemunitdir = $(SYSTEMD_DIR) 72 | 73 | systemdsystemunit_DATA = \ 74 | metronome.service 75 | endif 76 | 77 | 78 | #if DOXYGEN 79 | #codedocs: codedocs/html/index.html 80 | 81 | #codedocs/html/index.html: 82 | # doxygen 83 | #endif 84 | 85 | #check: testrunner 86 | # ./testrunner 87 | 88 | #testrunner: testrunner.o test-statstorage.o statstorage.o 89 | # $(CXX) $^ -lboost_unit_test_framework -o $@ 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | metronome 2 | ========= 3 | Mini-graphite that uses client-side java script to render graphs w/o depending on graphite. 4 | 5 | 'metronome' implements the carbon protocol, so anything that can feed Graphite can feed metronome. 6 | We also accept submissions via POST: 7 | 8 | For example, to submit RX bytes for eth0: 9 | 10 | while true; 11 | do 12 | VAL=$(ip -s link ls eth0 | grep RX -A 1 | tail -1 | awk '{print $1}') 13 | wget -q --post-data="" "http://127.0.0.1:8000/?do=store&name=rxbytes×tamp=$(date +%s)&value=$VAL" -O /dev/null 14 | sleep 1 15 | done 16 | 17 | To retrieve data: 18 | 19 | $ wget http://127.0.0.1:8000/?do=retrieve&name=rxbytes&begin=0&end=$(date +%s)&callback=jsonp 20 | 21 | This delivers a JSONP callback with your values in there. 22 | 23 | To view, try html/index.html 24 | 25 | Installing 26 | ========== 27 | Dependencies include a recent g++ (4.7+), Boost and libeigen. 28 | 29 | Installing Eigen 30 | ---------------- 31 | Either install `libeigen3-dev` (Debian, Ubuntu), `eigen3-devel` (Fedora, EPEL). 32 | 33 | To install it manually: 34 | 35 | $ wget http://bitbucket.org/eigen/eigen/get/3.2.1.tar.bz2 36 | $ tar xf 3.2.1.tar.bz2 37 | 38 | Compiling 39 | ========= 40 | 41 | $ ./bootstrap 42 | $ ./configure 43 | $ make 44 | 45 | If you installed libeigen manually, use `./configure --with-eigen=/path/to/eigen` 46 | 47 | Running 48 | ======= 49 | 50 | $ mkdir stats 51 | $ ./metronome --stats-directory=./stats 52 | 53 | Next, host the 'html/' directory somewhere on a webserver, and edit 'html/local.js' so 54 | it knows the Webserver IP address of metronome (port 8000 on :: by default). 55 | 56 | Thanks to 57 | ========= 58 | Aki Tuomi for the excellent [yahttp](https://github.com/cmouse/yahttp), 59 | [Rickshaw](http://code.shutterstock.com/rickshaw/) for the graphs. [Eigen](http://eigen.tuxfamily.org) for the math. And a cast of thousands for C++2011. 60 | 61 | status 62 | ====== 63 | "Toy" for now - stores all data in flat files, which we read in their entirety to 64 | replay data. 65 | 66 | But, starting to look nice. 67 | -------------------------------------------------------------------------------- /ahutils/Makefile: -------------------------------------------------------------------------------- 1 | -include sysdeps/$(shell uname).inc 2 | 3 | VERSION=0.1 4 | CXXFLAGS?=-Wall -O3 -MMD -MP $(CXX2011FLAGS) # -Wno-unused-local-typedefs 5 | LDFLAGS=$(CXX2011FLAGS) 6 | 7 | PROGRAMS=testrunner 8 | 9 | all: $(PROGRAMS) 10 | 11 | clean: 12 | -rm -f *.d *.o *~ testrunner 13 | 14 | testrunner: rfile.o testrunner.o 15 | $(CXX) $(CXXFLAGS) $^ -o $@ 16 | 17 | -include *.d -------------------------------------------------------------------------------- /ahutils/README.md: -------------------------------------------------------------------------------- 1 | ahutils 2 | ======= 3 | 4 | attempt to create a library of code snippets I use a lot 5 | -------------------------------------------------------------------------------- /ahutils/rfile.cc: -------------------------------------------------------------------------------- 1 | #include "rfile.hh" 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | RFile::RFile(const char* name, const char* mode) 8 | { 9 | d_fp = fopen(name, mode); 10 | if(!d_fp) 11 | throw runtime_error("Error openinging file '"+string(name)+"': "+strerror(errno)); 12 | d_fd = fileno(d_fp); 13 | } -------------------------------------------------------------------------------- /ahutils/rfile.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class RFile 5 | { 6 | public: 7 | RFile(const char* name, const char* mode); 8 | 9 | FILE* d_fp; 10 | int d_fd; 11 | }; -------------------------------------------------------------------------------- /ahutils/testrunner.cc: -------------------------------------------------------------------------------- 1 | #include "rfile.hh" 2 | 3 | int main() 4 | { 5 | RFile rf("./testrunner.cc", "r"); 6 | } -------------------------------------------------------------------------------- /bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | autoreconf -vi 3 | -------------------------------------------------------------------------------- /build-deb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export DEBFULLNAME=${DEBFULLNAME:="PowerDNS.COM BV AutoBuilder"} 4 | export DEBEMAIL=${DEBEMAIL:="noreply@powerdns.com"} 5 | 6 | if [ -z "$VERSION" ]; then 7 | if [ -x build-aux/gen-version ]; then 8 | VERSION="$(./build-aux/gen-version)" 9 | elif [ -r .version ]; then 10 | VERSION="$(cat .version)" 11 | else 12 | echo 'Please set $VERSION' >&2 13 | exit 1 14 | fi 15 | fi 16 | 17 | if [ -z "$RELEASE" ];then 18 | echo 'Please set $RELEASE' >&2 19 | exit 1 20 | fi 21 | 22 | DISTDIR=${DISTDIR:=.} 23 | 24 | set -e 25 | set -x 26 | 27 | if [ "$DISTDIR" != "." ]; then 28 | rm -rf ${DISTDIR}/debian 29 | cp -r debian/ ${DISTDIR} 30 | fi 31 | 32 | cat > ${DISTDIR}/debian/changelog << EOF 33 | metronome (${VERSION}-${RELEASE}) unstable; urgency=medium 34 | 35 | * Automatic build 36 | 37 | -- ${DEBFULLNAME} <${DEBEMAIL}> $(date -R) 38 | 39 | EOF 40 | 41 | cd $DISTDIR 42 | 43 | DH_OPTIONS=--parallel fakeroot debian/rules binary 44 | -------------------------------------------------------------------------------- /build-rpm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$SRC_VERSION" ]; then 4 | SRC_VERSION="$(./build-aux/gen-version)" 5 | fi 6 | 7 | if [ -z "$VERSION" ]; then 8 | if [ -x build-aux/gen-version ]; then 9 | VERSION="$(./build-aux/gen-version)" 10 | elif [ -r .version ]; then 11 | VERSION="$(cat .version)" 12 | else 13 | echo 'Please set $VERSION' >&2 14 | exit 1 15 | fi 16 | fi 17 | 18 | if [ -z "$RELEASE" ];then 19 | echo 'Please set $RELEASE' >&2 20 | exit 1 21 | fi 22 | 23 | rpmdev-setuptree 24 | 25 | if [ -f metronome-${SRC_VERSION}.tar.bz2 ]; then 26 | mv metronome-${SRC_VERSION}.tar.bz2 $HOME/rpmbuild/SOURCES 27 | else 28 | echo "metronome-${SRC_VERSION}.tar.bz2 not found" >&2 29 | exit 1 30 | fi 31 | 32 | set -e 33 | set -x 34 | 35 | rm -rf /tmp/metronome.spec 36 | cp -r metronome.spec /tmp 37 | 38 | sed -i "s!@VERSION@!${VERSION}!" /tmp/metronome.spec 39 | sed -i "s!@SRC_VERSION@!${SRC_VERSION}!" /tmp/metronome.spec 40 | sed -i "s!@RELEASE@!${RELEASE}!" /tmp/metronome.spec 41 | 42 | rpmbuild -bb /tmp/metronome.spec 43 | 44 | cp $HOME/rpmbuild/RPMS/x86_64/metronome*-${VERSION}* . 45 | -------------------------------------------------------------------------------- /builder-support/debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /builder-support/debian/control: -------------------------------------------------------------------------------- 1 | Source: metronome 2 | Section: net 3 | Priority: extra 4 | Standards-Version: 3.9.6 5 | Maintainer: PowerDNS Autobuilder 6 | Origin: PowerDNS 7 | Build-Depends: debhelper (>= 9.20160709) | dh-systemd, dh-autoreconf, libtool, dpkg-dev (>= 1.17.0~), libboost-dev, libboost-program-options-dev, autotools-dev, automake, autoconf, pkg-config, libeigen3-dev, libsystemd-dev 8 | Homepage: https://www.github.com/ahupowerdns/metronome 9 | 10 | Package: metronome 11 | Architecture: any 12 | Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, lsb-base (>= 3.2-14) 13 | Description: mini-graphite with builtin webserver and client-side plotting 14 | Mini-graphite that uses client-side java script to render graphs w/o depending 15 | on graphite. 'metronome' implements the carbon protocol, so anything that can 16 | feed Graphite can feed metronome. We also accept submissions via POST: 17 | 18 | Package: metronome-dbg 19 | Section: debug 20 | Architecture: any 21 | Depends: metronome (= ${binary:Version}), ${misc:Depends} 22 | Description: debugging symbols for metronome 23 | Mini-graphite that uses client-side java script to render graphs w/o depending 24 | on graphite. 'metronome' implements the carbon protocol, so anything that can 25 | feed Graphite can feed metronome. We also accept submissions via POST: 26 | -------------------------------------------------------------------------------- /builder-support/debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: Metronome 3 | Source: https://github.com/ahupowerdns/metronome 4 | 5 | Files: * 6 | Copyright: 2002 - 2014 PowerDNS.COM BV and contributors 7 | License: GPL-2 8 | This program is free software; you can redistribute it and/or modify 9 | it under the terms of version 2 of the GNU General Public License as 10 | published by the Free Software Foundation. 11 | . 12 | In addition, for the avoidance of any doubt, permission is granted to 13 | link this program with OpenSSL and to (re)distribute the binaries 14 | produced as the result of such linking. 15 | . 16 | This program is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU General Public License for more details. 20 | . 21 | You should have received a copy of the GNU General Public License 22 | along with this program; if not, write to the Free Software 23 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 24 | . 25 | On Debian systems, the full text of the GNU General Public 26 | License version 2 can be found in the file 27 | `/usr/share/common-licenses/GPL-2'. 28 | 29 | Files: debian/* 30 | Copyright: 2016 PowerDNS.COM BV 31 | 32 | Files: install-sh 33 | Copyright: 1994 X Consortium 34 | License: 35 | Permission is hereby granted, free of charge, to any person obtaining a copy 36 | of this software and associated documentation files (the "Software"), to deal 37 | in the Software without restriction, including without limitation the rights 38 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 39 | copies of the Software, and to permit persons to whom the Software is 40 | furnished to do so, subject to the following conditions: 41 | . 42 | The above copyright notice and this permission notice shall be included in 43 | all copies or substantial portions of the Software. 44 | . 45 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 46 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 47 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 48 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 49 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 50 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 51 | THE SOFTWARE. 52 | . 53 | Except as contained in this notice, the name of the X Consortium shall not 54 | be used in advertising or otherwise to promote the sale, use or other deal- 55 | ings in this Software without prior written authorization from the X Consor- 56 | tium. 57 | . 58 | FSF changes to this file are in the public domain. 59 | 60 | Files: yahttp/* 61 | Copyright: 2014 Aki Tuomi 62 | License: Expat 63 | Permission is hereby granted, free of charge, to any person obtaining a copy 64 | of this software and associated documentation files (the "Software"), to deal 65 | in the Software without restriction, including without limitation the rights 66 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 67 | copies of the Software, and to permit persons to whom the Software is 68 | furnished to do so, subject to the following conditions: 69 | . 70 | The above copyright notice and this permission notice shall be included in 71 | all copies or substantial portions of the Software. 72 | . 73 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 74 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 75 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 76 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 77 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 78 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 79 | THE SOFTWARE. 80 | -------------------------------------------------------------------------------- /builder-support/debian/metronome.install: -------------------------------------------------------------------------------- 1 | usr/bin/* 2 | usr/share/metronome/* 3 | usr/share/doc/metronome/README.md 4 | -------------------------------------------------------------------------------- /builder-support/debian/metronome.postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # postinst script for metronome 4 | 5 | set -e 6 | 7 | case "$1" in 8 | configure) 9 | if [ -z "`getent group metronome`" ]; then 10 | addgroup --quiet --system metronome 11 | fi 12 | if [ -z "`getent passwd metronome`" ]; then 13 | echo -n "Creating user and group metronome..." 14 | adduser --quiet --system --home /var/lib/metronome --shell /bin/false --ingroup metronome --disabled-password --disabled-login --gecos "Metronome" metronome 15 | echo "done" 16 | fi 17 | ;; 18 | 19 | abort-upgrade|abort-remove|abort-deconfigure|triggered) 20 | ;; 21 | 22 | *) 23 | echo "postinst called with unknown argument \`$1'" >&2 24 | exit 1 25 | ;; 26 | esac 27 | 28 | # dh_installdeb will replace this with shell code automatically 29 | # generated by other debhelper scripts. 30 | 31 | #DEBHELPER# 32 | 33 | exit 0 34 | 35 | -------------------------------------------------------------------------------- /builder-support/debian/metronome.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Metronome 3 | After=network-online.target 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=/usr/bin/metronome --daemon 0 --stats /var/lib/metronome 8 | User=metronome 9 | Group=metronome 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /builder-support/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) 4 | 5 | # Use new build system 6 | %: 7 | dh $@ --with autoreconf --parallel 8 | 9 | override_dh_auto_configure: 10 | ./configure \ 11 | --host=$(DEB_HOST_GNU_TYPE) \ 12 | --build=$(DEB_BUILD_GNU_TYPE) \ 13 | --prefix=/usr \ 14 | --mandir=\$${prefix}/share/man \ 15 | --infodir=\$${prefix}/share/info \ 16 | --libdir='$${prefix}/lib/$(DEB_HOST_MULTIARCH)' \ 17 | --libexecdir='$${prefix}/lib' \ 18 | --enable-systemd --with-systemd=/lib/systemd/system 19 | 20 | override_dh_strip: 21 | dh_strip --dbg-package=metronome-dbg 22 | 23 | # Verbose build (shows used compiler/linker and their flags) 24 | override_dh_auto_build-arch: 25 | dh_auto_build -- V=1 26 | 27 | override_dh_auto_test: 28 | true 29 | -------------------------------------------------------------------------------- /builder-support/dockerfiles/Dockerfile.debbuild: -------------------------------------------------------------------------------- 1 | ARG BUILDER_CACHE_BUSTER= 2 | ARG APT_URL 3 | RUN apt-get update && apt-get -y dist-upgrade && \ 4 | apt-get -y install devscripts dpkg-dev build-essential python3 equivs 5 | 6 | RUN mkdir /dist /metronome 7 | ADD builder/helpers/ /metronome/builder/helpers/ 8 | WORKDIR /metronome 9 | 10 | # Used for -p option to only build specific packages 11 | ARG BUILDER_PACKAGE_MATCH 12 | 13 | ARG BUILDER_VERSION 14 | ARG BUILDER_RELEASE 15 | COPY --from=sdist /sdist /sdist 16 | RUN tar xvf /sdist/metronome-${BUILDER_VERSION}.tar.bz2 17 | ADD builder-support/debian/ metronome-${BUILDER_VERSION}/debian/ 18 | RUN builder/helpers/build-debs.sh metronome-${BUILDER_VERSION} 19 | RUN mv metronome*.deb /dist; mv metronome*.ddeb /dist || true 20 | -------------------------------------------------------------------------------- /builder-support/dockerfiles/Dockerfile.rpmbuild: -------------------------------------------------------------------------------- 1 | FROM dist-base as package-builder 2 | RUN yum install -y rpm-build rpmdevtools /usr/bin/python3 && \ 3 | yum groupinstall -y "Development Tools" && \ 4 | rpmdev-setuptree 5 | 6 | RUN mkdir /dist /metronome 7 | WORKDIR /metronome 8 | ADD builder/helpers/ /metronome/builder/helpers/ 9 | # Used for -p option to only build specific spec files 10 | ARG BUILDER_PACKAGE_MATCH 11 | 12 | ARG BUILDER_VERSION 13 | ARG BUILDER_RELEASE 14 | 15 | COPY --from=sdist /sdist /sdist 16 | RUN for file in /sdist/* ; do ln -s $file /root/rpmbuild/SOURCES/ ; done && ls /root/rpmbuild/SOURCES/ 17 | ADD builder-support/specfiles/ /metronome/builder-support/specfiles 18 | RUN find /metronome/builder-support/specfiles/ -not -name '*.spec' -exec ln -s {} /root/rpmbuild/SOURCES/ \; 19 | 20 | RUN builder/helpers/build-specs.sh builder-support/specfiles/metronome.spec 21 | 22 | RUN cp -R /root/rpmbuild/RPMS/* /dist/ 23 | RUN cp -R /root/rpmbuild/SRPMS/* /dist/ 24 | -------------------------------------------------------------------------------- /builder-support/dockerfiles/Dockerfile.target.centos-6: -------------------------------------------------------------------------------- 1 | # First do the source builds 2 | @INCLUDE Dockerfile.target.sdist 3 | 4 | # This defines the dstribution base layer 5 | # Put only the bare minimum of common commands here, without dev tools 6 | FROM centos:6 as dist-base 7 | ARG BUILDER_CACHE_BUSTER= 8 | RUN yum install -y --verbose epel-release centos-release-scl-rh && \ 9 | yum install -y --nogpgcheck devtoolset-7-gcc-c++ 10 | 11 | # Do the actual rpm build 12 | @INCLUDE Dockerfile.rpmbuild 13 | -------------------------------------------------------------------------------- /builder-support/dockerfiles/Dockerfile.target.centos-7: -------------------------------------------------------------------------------- 1 | # First do the source builds 2 | @INCLUDE Dockerfile.target.sdist 3 | 4 | # This defines the dstribution base layer 5 | # Put only the bare minimum of common commands here, without dev tools 6 | FROM centos:7 as dist-base 7 | ARG BUILDER_CACHE_BUSTER= 8 | RUN yum install -y epel-release 9 | 10 | # Do the actual rpm build 11 | @INCLUDE Dockerfile.rpmbuild 12 | -------------------------------------------------------------------------------- /builder-support/dockerfiles/Dockerfile.target.debian-bullseye: -------------------------------------------------------------------------------- 1 | # First do the source builds 2 | @INCLUDE Dockerfile.target.sdist 3 | FROM debian:bullseye as dist-base 4 | @INCLUDE Dockerfile.debbuild 5 | -------------------------------------------------------------------------------- /builder-support/dockerfiles/Dockerfile.target.debian-buster: -------------------------------------------------------------------------------- 1 | # First do the source builds 2 | @INCLUDE Dockerfile.target.sdist 3 | FROM debian:buster as dist-base 4 | @INCLUDE Dockerfile.debbuild 5 | -------------------------------------------------------------------------------- /builder-support/dockerfiles/Dockerfile.target.debian-stretch: -------------------------------------------------------------------------------- 1 | # First do the source builds 2 | @INCLUDE Dockerfile.target.sdist 3 | FROM debian:stretch as dist-base 4 | @INCLUDE Dockerfile.debbuild 5 | -------------------------------------------------------------------------------- /builder-support/dockerfiles/Dockerfile.target.sdist: -------------------------------------------------------------------------------- 1 | FROM alpine:3.10 as metronome 2 | 3 | ARG BUILDER_CACHE_BUSTER= 4 | RUN apk add gcc g++ make tar autoconf automake eigen-dev file bash libtool pkgconfig boost-dev 5 | 6 | ADD README.md LICENSE metronome.service.in metronome-upstart.conf configure.ac Makefile.am interpolate.cc iputils.cc mdump.cc metronome.cc mmanage.cc msubmit.cc statstorage.cc testrunner.cc test-statstorage.cc dolog.hh interpolate.hh iputils.hh metromisc.hh statstorage.hh /metronome/ 7 | @EXEC sdist_dirs=(m4 ahutils examples html yahttp) 8 | @EXEC for d in ${sdist_dirs[@]} ; do echo "COPY $d/ /metronome/$d/" ; done 9 | ADD builder/helpers/set-configure-ac-version.sh /metronome/builder/helpers/ 10 | 11 | WORKDIR /metronome 12 | RUN mkdir /sdist 13 | 14 | ARG BUILDER_VERSION 15 | RUN /metronome/builder/helpers/set-configure-ac-version.sh && \ 16 | autoreconf -v -i --force && \ 17 | ./configure --with-eigen=/usr/include/eigen3 --disable-dependency-tracking && \ 18 | make dist 19 | RUN cp metronome-${BUILDER_VERSION}.tar.bz2 /sdist/ 20 | 21 | FROM alpine:3.10 as sdist 22 | ARG BUILDER_CACHE_BUSTER= 23 | COPY --from=metronome /sdist/ /sdist/ 24 | -------------------------------------------------------------------------------- /builder-support/post-build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SRCDIR="${BUILDER_TMP}/${BUILDER_VERSION}/${BUILDER_TARGET}" 4 | DESTDIR="built_pkgs/" 5 | 6 | mkdir -p ${DESTDIR} 7 | 8 | # We need the GNU version of tar for --transform 9 | [ -z "$tar" ] && tar=`which gtar tar | grep '^/' | head -1` 10 | if ! $tar --version | grep -q GNU; then 11 | echo "ERROR: could not find GNU tar (as gtar or tar)" 12 | echo "On macOS: brew install gnu-tar" 13 | exit 1 14 | fi 15 | 16 | if [ $(find ${SRCDIR}/dist -name "metronome*" 2>/dev/null | wc -l) -ne 0 ]; then 17 | dst=${DESTDIR}/metronome/${BUILDER_VERSION} 18 | mkdir -p ${dst} 19 | cp ${BUILDER_TMP}/${BUILDER_VERSION}/sdist/metronome-${BUILDER_VERSION}.tar.bz2 ${dst} 20 | tardirname=metronome-${BUILDER_VERSION}-${BUILDER_TARGET} 21 | "$tar" -cjf ${dst}/${tardirname}.tar.bz2 --transform="s,.*/,${tardirname}/,g" $(find ${SRCDIR} -type f) 22 | fi 23 | -------------------------------------------------------------------------------- /builder-support/specfiles/metronome.spec: -------------------------------------------------------------------------------- 1 | %if 0%{?rhel} < 6 2 | exit 1 3 | %endif 4 | 5 | %define upstart_post() \ 6 | if [ -x /sbin/initctl ]; then \ 7 | /sbin/initctl start %1 \ 8 | fi\ 9 | %{nil} 10 | 11 | %define upstart_postun() \ 12 | if [ -x /sbin/initctl ] && /sbin/initctl status %1 2>/dev/null | grep -q 'running' ; then \ 13 | /sbin/initctl stop %1 >/dev/null 2>&1 \ 14 | [ -f /etc/init/%1.conf ] && { echo -n "Re-"; /sbin/initctl start %1; }; \ 15 | fi \ 16 | %{nil} 17 | 18 | Name: metronome 19 | Version: %{getenv:BUILDER_RPM_VERSION} 20 | Release: %{getenv:BUILDER_RPM_RELEASE}%{dist} 21 | Summary: A tiny graphite receiver with flat-file storage 22 | Group: System Environment/Daemons 23 | License: GPLv2 24 | URL: https://github.com/ahupowerdns/metronome 25 | Source0: %{name}-%{getenv:BUILDER_VERSION}.tar.bz2 26 | 27 | %if %{?rhel} >= 7 28 | BuildRequires: boost-devel 29 | BuildRequires: systemd-devel 30 | Requires(post): systemd-units 31 | Requires(preun): systemd-units 32 | Requires(postun): systemd-units 33 | %else 34 | BuildRequires: boost148-devel 35 | BuildRequires: boost148-program-options 36 | BuildRequires: devtoolset-7-gcc-c++ 37 | %endif 38 | 39 | BuildRequires: eigen3-devel 40 | Requires(pre): shadow-utils 41 | 42 | %description 43 | Metronome is a small receiver for Carbon (graphite) data with a small http API to retreive this data. 44 | 45 | %prep 46 | %if %{?rhel} < 7 47 | source /opt/rh/devtoolset-7/enable 48 | %endif 49 | %setup -n %{name}-%{getenv:BUILDER_VERSION} 50 | 51 | %build 52 | %if %{?rhel} < 7 53 | source /opt/rh/devtoolset-7/enable 54 | %endif 55 | 56 | %configure \ 57 | --disable-silent-rules \ 58 | %if %{?rhel} >= 7 59 | --enable-systemd --with-systemd=/lib/systemd/system \ 60 | %else 61 | --disable-systemd \ 62 | --with-boost=/usr/include/boost148 \ 63 | LIBRARY_PATH=/usr/lib64/boost148 LDFLAGS=-L/usr/lib64/boost148 64 | %endif 65 | 66 | %if %{?rhel} >= 7 67 | make %{?_smp_mflags} 68 | %else 69 | LIBRARY_PATH=/usr/lib64/boost148 make %{?_smp_mflags} 70 | %endif 71 | 72 | %install 73 | rm -rf $RPM_BUILD_ROOT 74 | %make_install 75 | install -d -m 755 %{buildroot}/var/lib/%{name} 76 | %if %{?rhel} < 7 77 | install -d -m 755 %{buildroot}%{_sysconfdir}/init 78 | install -m 644 %{name}-upstart.conf %{buildroot}%{_sysconfdir}/init/%{name}.conf 79 | %endif 80 | 81 | %pre 82 | getent group %{name} > /dev/null || groupadd -r %{name} 83 | getent passwd %{name} > /dev/null || \ 84 | useradd -d /var/lib/%{name} -r -g %{name} -d / -s /sbin/nologin \ 85 | -c "Metronome user" %{name} 86 | exit 0 87 | 88 | %post 89 | chown %{name}:%{name} /var/lib/%{name} 90 | %if %{?rhel} >= 7 91 | %systemd_post %{name}.service 92 | %else 93 | %upstart_post %{name} 94 | %endif 95 | 96 | %preun 97 | %if %{?rhel} >= 7 98 | %systemd_preun %{name}.service 99 | %endif 100 | 101 | %postun 102 | %if %{?rhel} >= 7 103 | %systemd_postun %{name}.service 104 | %else 105 | %upstart_postun %{name} 106 | %endif 107 | 108 | %files 109 | /usr/bin/* 110 | /usr/share/%{name} 111 | %if %{?rhel} >= 7 112 | /lib/systemd/system/%{name}.service 113 | %else 114 | %{_sysconfdir}/init/%{name}.conf 115 | %endif 116 | %dir /var/lib/%{name} 117 | %doc %{_defaultdocdir}/%{name}/README.md 118 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT([metronome], m4_esyscmd(builder/gen-version)) 2 | AC_CONFIG_AUX_DIR([build-aux]) 3 | AM_INIT_AUTOMAKE([foreign dist-bzip2 no-dist-gzip tar-ustar -Wno-portability subdir-objects parallel-tests 1.11]) 4 | AM_SILENT_RULES([yes]) 5 | AC_CONFIG_SRCDIR([metronome.cc]) 6 | AC_CONFIG_MACRO_DIR([m4]) 7 | 8 | AC_CONFIG_HEADERS([config.h]) 9 | 10 | AC_CANONICAL_HOST 11 | : ${CFLAGS="-Wall -g -O2"} 12 | : ${CXXFLAGS="-Wall -g -O2"} 13 | 14 | AC_PROG_CXX 15 | AS_IF([test "x$CXX" = "xno" || test "x$CXX:x$GXX" = "xg++:x"], 16 | AC_MSG_ERROR([no C++ compiler found]) 17 | ) 18 | AX_CXX_COMPILE_STDCXX_11(ext,mandatory) 19 | AC_PROG_LIBTOOL 20 | 21 | #AC_LANG([C++]) 22 | 23 | AC_PROG_INSTALL 24 | AC_PROG_MAKE_SET 25 | 26 | AC_DEFINE([_GNU_SOURCE], [1], 27 | [Define _GNU_SOURCE so that we get all necessary prototypes] 28 | ) 29 | 30 | # Warn when pkg.m4 is missing 31 | m4_pattern_forbid([^_?PKG_[A-Z_]+$],[*** pkg.m4 missing, please install pkg-config]) 32 | 33 | PDNS_CHECK_OS 34 | 35 | METRONOME_WITH_EIGEN 36 | 37 | BOOST_REQUIRE([1.35]) 38 | BOOST_PROGRAM_OPTIONS([mt]) 39 | 40 | AX_AVAILABLE_SYSTEMD 41 | AM_CONDITIONAL([HAVE_SYSTEMD], [ test x"$systemd" = "xy" ]) 42 | 43 | AC_MSG_CHECKING([whether we should enable hardening]) 44 | AC_ARG_ENABLE([hardening], 45 | [AS_HELP_STRING([--disable-hardening],[disable compiler security checks @<:@default=no@:>@])], 46 | [enable_hardening=$enableval], 47 | [enable_hardening=yes] 48 | ) 49 | AC_MSG_RESULT([$enable_hardening]) 50 | 51 | AS_IF([test "x$enable_hardening" != "xno"], [ 52 | AC_CC_PIE 53 | AC_CC_STACK_PROTECTOR 54 | AC_CC_PARAM_SSP_BUFFER_SIZE([4]) 55 | AC_CC_D_FORTIFY_SOURCE 56 | AC_LD_RELRO 57 | ]) 58 | 59 | PDNS_ENABLE_SANITIZERS 60 | PDNS_ENABLE_MALLOC_TRACE 61 | 62 | AC_SUBST(LIBS) 63 | 64 | AC_SUBST([AM_CPPFLAGS], 65 | ["AS_ESCAPE([-I$(top_builddir) -I$(top_srcdir)]) $THREADFLAGS $BOOST_CPPFLAGS"] 66 | ) 67 | 68 | AC_SUBST([YAHTTP_CFLAGS], ['-I$(top_srcdir)/yahttp/yahttp']) 69 | AC_SUBST([YAHTTP_LIBS], ['-L$(top_builddir)/yahttp/yahttp -lyahttp']) 70 | 71 | CXXFLAGS="$SANITIZER_FLAGS $CXXFLAGS" 72 | LDFLAGS="$RELRO_LDFLAGS $LDFLAGS" 73 | 74 | AC_SUBST([PROGRAM_LDFLAGS]) 75 | 76 | AC_CONFIG_FILES([Makefile 77 | yahttp/Makefile 78 | yahttp/yahttp/Makefile]) 79 | 80 | AC_OUTPUT 81 | -------------------------------------------------------------------------------- /dolog.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | /* This file is intended not to be metronome specific, and is simple example of C++2011 7 | variadic templates in action. 8 | 9 | The goal is rapid easy to use logging to console & syslog. 10 | 11 | Usage: 12 | string address="localhost"; 13 | infolog("Bound to %s port %d", address, port); 14 | warnlog("Query took %d milliseconds", 1232.4); // yes, %d 15 | errlog("Unable to bind to %s: %s", ca.toStringWithPort(), strerr(errno)); 16 | 17 | If bool g_console is true, will log to stdout. Will syslog in any case with LOG_INFO, 18 | LOG_WARNING, LOG_ERR respectively. If g_verbose=false, infolog is a noop. 19 | More generically, dolog(someiostream, "Hello %s", stream) will log to someiostream 20 | 21 | This will happily print a string to %d! Doesn't do further format processing. 22 | */ 23 | 24 | inline void dolog(std::ostream& os, const char*s) 25 | { 26 | os< 30 | void dolog(std::ostream& os, const char* s, T value, Args... args) 31 | { 32 | while (*s) { 33 | if (*s == '%') { 34 | if (*(s + 1) == '%') { 35 | ++s; 36 | } 37 | else { 38 | os << value; 39 | s += 2; 40 | dolog(os, s, args...); 41 | return; 42 | } 43 | } 44 | os << *s++; 45 | } 46 | } 47 | 48 | extern bool g_console; 49 | extern bool g_disableSyslog; 50 | extern bool g_verbose; 51 | 52 | template 53 | void genlog(int level, const char* s, Args... args) 54 | { 55 | std::ostringstream str; 56 | dolog(str, s, args...); 57 | if (!g_disableSyslog) 58 | syslog(level, "%s", str.str().c_str()); 59 | if(g_console) 60 | std::cout< 64 | void infolog(const char* s, Args... args) 65 | { 66 | if(g_verbose) 67 | genlog(LOG_INFO, s, args...); 68 | } 69 | 70 | template 71 | void warnlog(const char* s, Args... args) 72 | { 73 | genlog(LOG_WARNING, s, args...); 74 | } 75 | 76 | template 77 | void errlog(const char* s, Args... args) 78 | { 79 | genlog(LOG_ERR, s, args...); 80 | } 81 | -------------------------------------------------------------------------------- /examples/linux-system-feeder: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$1" == "" ] 4 | then 5 | HOST=$(hostname -s) 6 | else 7 | HOST=$1 8 | fi 9 | 10 | 11 | # Udp: 5956140687 951311 23693 5957118759 3751 356 0 12 | # UdpLite: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InCsumErrors 13 | # 1 2 3 4 5 6 7 8 14 | LINE=$(cat /proc/net/snmp | grep Udp: | grep -v InData) 15 | 16 | echo -n "system.$HOST.network.udp.rcvbuf-errors " ; echo $LINE | cut -f6 -d" " 17 | echo -n "system.$HOST.network.udp.sndbuf-errors " ; echo $LINE | cut -f7 -d" " 18 | echo -n "system.$HOST.network.udp.noport-errors " ; echo $LINE | cut -f3 -d" " 19 | echo -n "system.$HOST.network.udp.in-errors " ; echo $LINE | cut -f4 -d" " 20 | 21 | pushd /sys/class/net/ > /dev/null 22 | for a in $(find . -maxdepth 1 -follow -type d | cut -f2 -d/ | grep -v \\. ) 23 | do 24 | pushd $a/statistics > /dev/null 25 | for b in * 26 | do 27 | echo -n "system.$HOST.network.interfaces.$a.$b " 28 | cat $b 29 | done 30 | popd > /dev/null 31 | done 32 | popd > /dev/null 33 | 34 | -------------------------------------------------------------------------------- /html/detail.css: -------------------------------------------------------------------------------- 1 | .rickshaw_graph .detail { 2 | pointer-events: none; 3 | position: absolute; 4 | top: 0; 5 | z-index: 2; 6 | background: rgba(0, 0, 0, 0.1); 7 | bottom: 0; 8 | width: 1px; 9 | transition: opacity 0.25s linear; 10 | -moz-transition: opacity 0.25s linear; 11 | -o-transition: opacity 0.25s linear; 12 | -webkit-transition: opacity 0.25s linear; 13 | } 14 | .rickshaw_graph .detail.inactive { 15 | opacity: 0; 16 | } 17 | .rickshaw_graph .detail .item.active { 18 | opacity: 1; 19 | } 20 | .rickshaw_graph .detail .x_label { 21 | font-family: Arial, sans-serif; 22 | border-radius: 3px; 23 | padding: 6px; 24 | opacity: 0.5; 25 | border: 1px solid #e0e0e0; 26 | font-size: 12px; 27 | position: absolute; 28 | background: white; 29 | white-space: nowrap; 30 | top: -20px; /* add this */ 31 | } 32 | .rickshaw_graph .detail .x_label.left { 33 | left: 0; 34 | } 35 | .rickshaw_graph .detail .x_label.right { 36 | right: 0; 37 | } 38 | .rickshaw_graph .detail .item { 39 | position: absolute; 40 | z-index: 2; 41 | border-radius: 3px; 42 | padding: 0.25em; 43 | font-size: 12px; 44 | font-family: Arial, sans-serif; 45 | opacity: 0; 46 | background: rgba(0, 0, 0, 0.4); 47 | color: white; 48 | border: 1px solid rgba(0, 0, 0, 0.4); 49 | margin-left: 1em; 50 | margin-right: 1em; 51 | margin-top: -1em; 52 | white-space: nowrap; 53 | } 54 | .rickshaw_graph .detail .item.left { 55 | left: 0; 56 | } 57 | .rickshaw_graph .detail .item.right { 58 | right: 0; 59 | } 60 | .rickshaw_graph .detail .item.active { 61 | opacity: 1; 62 | background: rgba(0, 0, 0, 0.8); 63 | } 64 | .rickshaw_graph .detail .item:after { 65 | position: absolute; 66 | display: block; 67 | width: 0; 68 | height: 0; 69 | 70 | content: ""; 71 | 72 | border: 5px solid transparent; 73 | } 74 | .rickshaw_graph .detail .item.left:after { 75 | top: 1em; 76 | left: -5px; 77 | margin-top: -5px; 78 | border-right-color: rgba(0, 0, 0, 0.8); 79 | border-left-width: 0; 80 | } 81 | .rickshaw_graph .detail .item.right:after { 82 | top: 1em; 83 | right: -5px; 84 | margin-top: -5px; 85 | border-left-color: rgba(0, 0, 0, 0.8); 86 | border-right-width: 0; 87 | } 88 | .rickshaw_graph .detail .dot { 89 | width: 4px; 90 | height: 4px; 91 | margin-left: -2px; 92 | margin-top: -2px; 93 | border-radius: 5px; 94 | position: absolute; 95 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.6); 96 | background: white; 97 | border-width: 2px; 98 | border-style: solid; 99 | display: none; 100 | background-clip: padding-box; 101 | } 102 | .rickshaw_graph .detail .dot.active { 103 | display: block; 104 | } 105 | -------------------------------------------------------------------------------- /html/engine.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function Metronome(comconfig) 4 | { 5 | if(!(this instanceof Metronome)) 6 | return new Metronome(); 7 | this.config=[]; 8 | this.hierarchy={}; 9 | this.servers=[]; 10 | this.comconfig=comconfig; 11 | } 12 | 13 | /* The ritual: 14 | 0) Have a 'div' where you want your graphs 15 | 1) make a Metronome object -> new Metronome({url: "http://xs.powerdns.com:8080", datapoints: 150}) 16 | 2) call its getAllMetrics method with a startup function 17 | 3) Your startup function gets called with metronome object which is ready & includes a metric hierarchy & server list 18 | 4) In startup function, create a graphing config based on hierarchy & server list, and submit it setupHTML("#yourdiv", configs), 19 | which populates your div with empty graphs of the right size 20 | 5) Call the updateGraphs method to actually retrieve data & draw graphs 21 | 6) Call updateGraphs periodically (every m.getNaturalInterval() milliseconds if you want smooth transitions) 22 | 7) Recall setupGraphs if you change what graphs you want plotted, will clear your div 23 | 24 | The graph config, as passed to setupGraphs: 25 | an array of graphs, each with an 'items' field containing an array of metrics to be graphed. 26 | Each metric has a 'name', which says which metric is to be graphed, plus a legend which is the human friendly name 27 | Example: 28 | configs.push({items: [ 29 | { name: "system.xs.interfaces.eth0.tx_packets", legend: "eth0 TX packets/s"}, 30 | { name: "system.xs.interfaces.eth0.rx_packets", legend: "eth0 RX packets/s"}, 31 | ]}); 32 | By default, we plot the nonNegativeDerivative of the metric. 33 | To turn bits into bytes, add 'bitsToBytes: true'. 34 | 35 | To stack a graph, set 'renderer: stack'. 36 | 37 | To do fancy things, instead of a name, add a 'metrics' field with an array of metrics. 38 | Also, set a function as the formula field. This function gets passed all the metrics you specified (in order), 39 | plus their nonNegativeDerivative. (so, we call formula(raw, derived)). 40 | 41 | We deliver one stock formula, 'Metronome.percentalizer', which can be used to calculate one rate as a percentage 42 | of two rates. So, cache hitrates for example: 43 | 44 | configs.push({ items: [ 45 | { 46 | metrics: [servername+".cache-hits",servername+".cache-misses"], 47 | legend: "% cache hitrate", 48 | formula: m.percentalizer 49 | }); 50 | */ 51 | 52 | // Pass this to 'formula' to get two rates as a percentage of their sum rate 53 | Metronome.prototype.percentalizer=function(r, d) 54 | { 55 | if(d[0] > 0 && d[1] > 0) 56 | return d[0]*100.0/(d[0] +d[1]); 57 | else 58 | return 0; 59 | }; 60 | 61 | // Call this to get array of all metrics at a certain level (listMetricsAt("system", "server1", "network", "interfaces")) 62 | // Note: will not find leaves! Only finds things that have children themselves. Yeah. 63 | Metronome.prototype.listMetricsAt=function() 64 | { 65 | var ref=this.hierarchy; 66 | for(var i = 0; i < arguments.length; ++i) { 67 | ref = ref[arguments[i]]; 68 | } 69 | var ret=[]; 70 | $.each(ref, function(key, val) { 71 | ret.push(key); 72 | }); 73 | ret.sort(); 74 | return ret; 75 | }; 76 | 77 | // the startup function, 78 | Metronome.prototype.getAllMetrics=function(destination) 79 | { 80 | var qstring = this.comconfig.url+"?do=get-metrics&callback=?&name"; 81 | var that=this; 82 | var alerter = window.setTimeout(function(){ alert("Could not contact Metronome statistics server at "+that.comconfig.url+". This is either due to a connectivity problem or a intervening firewall, or otherwise a timeout."); }, 2500); 83 | $.getJSON(qstring, 84 | function(data) { 85 | window.clearTimeout(alerter); 86 | var theservers={}; 87 | that.hierarchy={}; 88 | $.each(data.metrics, function(a, b) { 89 | var parts = b.split('.'); 90 | var name = parts.slice(0,3).join('.'); 91 | theservers[name]=1; 92 | 93 | for(var i = 0; i < parts.length ; ++i) { 94 | var ref = that.hierarchy; 95 | for(var j = 0 ; j < i; ++j) { 96 | if(ref[parts[j]]===undefined) 97 | ref[parts[j]]={}; 98 | ref = ref[parts[j]]; 99 | } 100 | } 101 | }); 102 | that.servers=[]; 103 | $.each(theservers, function(a,b) { 104 | that.servers.push(a); 105 | }); 106 | that.servers.sort(); 107 | destination(that); 108 | }); 109 | }; 110 | 111 | Metronome.prototype.updateGraphs=function() 112 | { 113 | var that=this; 114 | $.each(this.configs, function(key, val) { 115 | that._showGraph(val); 116 | }); 117 | }; 118 | 119 | Metronome.prototype._showGraph=function(config) { 120 | var items = config.items; 121 | 122 | var qstring = this.comconfig.url+"?do=retrieve&callback=?&name="; 123 | var metrics=[]; 124 | for(var item in items) { 125 | if(items[item].name !== undefined) 126 | metrics.push(items[item].name); 127 | if(items[item].metrics !== undefined) { 128 | $.each(items[item].metrics, function(key, value) { 129 | metrics.push(value); 130 | }); 131 | } 132 | } 133 | 134 | qstring+= metrics.join(','); 135 | 136 | var epoch = (new Date).getTime()/1000; 137 | qstring+="&begin="+(epoch+this.comconfig.beginTime)+"&end="+(epoch)+"&datapoints="+this.comconfig.datapoints; 138 | 139 | var that=this; 140 | $.getJSON(qstring, 141 | function(fullseries) { 142 | var toplot=[]; 143 | var grouped={}; 144 | 145 | $.each(metrics, function(num, metric) { 146 | $.each(fullseries.raw[metric], function(key, value) { 147 | if(grouped[value[0]] === undefined) { 148 | grouped[value[0]] = {}; 149 | grouped[value[0]].raw = {}; 150 | grouped[value[0]].derivative = {}; 151 | } 152 | grouped[value[0]].raw[num]=value[1]; 153 | }); 154 | 155 | $.each(fullseries.derivative[metric], function(key, value) { 156 | grouped[value[0]].derivative[num]=value[1]; 157 | }); 158 | }); 159 | // console.log("Grouped", grouped); 160 | for(var num in items) { 161 | var series; 162 | if(items[num].kind==="gauge") 163 | series = fullseries.raw; 164 | else 165 | series = fullseries.derivative; 166 | 167 | var factor = 1; 168 | if(items[num].bytesToBits !== undefined) 169 | factor = 8; 170 | if(items[num].formula === undefined) 171 | toplot[num] = that._coordinateTransform(series[items[num].name], factor); 172 | 173 | } 174 | 175 | for(num in items) { 176 | if(items[num].formula !== undefined) { 177 | toplot[num]=[]; 178 | $.each(grouped, function(key, value) { 179 | toplot[num].push({x: 1.0*key, y: items[num].formula(value.raw, value.derivative) }); 180 | }); 181 | } 182 | } 183 | 184 | var plotseries=[]; 185 | var colors=['red', 'steelblue', 'green', 'orange', 'purple', 'black', 'yellow']; 186 | 187 | for(num in items) { 188 | plotseries.push( { color: colors[num], data: toplot[num], name: items[num].legend, renderer: 'line'}); 189 | } 190 | var n_items = items.length; 191 | 192 | var graph, args; 193 | if (!config.graph) { 194 | config.div.html('
'); 195 | 196 | args = { 197 | element: config.div.find(".chart")[0], 198 | width: 550, 199 | height: 250, 200 | renderer: config.renderer || 'multi', 201 | padding: { top: 0.05 }, 202 | series: plotseries 203 | 204 | }; 205 | graph = new Rickshaw.Graph(args); 206 | config.graph = graph; 207 | config.graph_args = args; 208 | config.graph_n_items = n_items; 209 | 210 | var axes = new Rickshaw.Graph.Axis.Time( { 211 | graph: graph, 212 | orientation: 'bottom', 213 | timeFixture: new Rickshaw.Fixtures.Time.Local() 214 | } ); 215 | 216 | var y_ticks = new Rickshaw.Graph.Axis.Y( { 217 | graph: graph, 218 | orientation: 'left', 219 | tickFormat: 220 | Rickshaw.Fixtures.Number.formatKMBT, 221 | element: config.div.find(".y_axis")[0] 222 | } ); 223 | 224 | var legend = new Rickshaw.Graph.Legend( { 225 | graph: graph, 226 | element: config.div.find(".legend")[0] 227 | } ); 228 | 229 | graph.render(); 230 | 231 | var hoverDetail = new Rickshaw.Graph.HoverDetail( { 232 | graph: graph, 233 | 234 | formatter: function(series, x, y) { 235 | var swatch = ''; 236 | var content = swatch + series.name + ": " + y.toFixed(2); 237 | return content; 238 | }, 239 | xFormatter: function(x) { 240 | return new Date( x * 1000 ).toString(); 241 | } 242 | } ); 243 | 244 | } else { 245 | graph = config.graph; 246 | args = config.graph_args; 247 | if (n_items !== config.graph_n_items) { 248 | args.series = plotseries; 249 | } else { 250 | for(num in items) { 251 | args.series[num].data = toplot[num]; 252 | } 253 | } 254 | graph.render(); 255 | } 256 | 257 | }); 258 | }; 259 | 260 | // This interval represents a millisecond timestep that will keep the shape of your graph identical if you call updateGraphs 261 | Metronome.prototype.getNaturalInterval=function() 262 | { 263 | return -this.comconfig.beginTime*1000/this.comconfig.datapoints; 264 | }; 265 | 266 | 267 | Metronome.prototype.setupGraphs=function(where, configs) 268 | { 269 | this.configs=[]; 270 | $(where).html(""); 271 | for(var a in configs) { 272 | configs[a].div = $('
'); 273 | $(where).append(configs[a].div); 274 | this.configs.push(configs[a]); 275 | } 276 | }; 277 | 278 | Metronome.prototype._coordinateTransform=function(series, factor) 279 | { 280 | var ret=[]; 281 | $.each(series, function(a, b) { 282 | ret.push({x: b[0], y: factor * b[1]}); 283 | }); 284 | return ret; 285 | }; 286 | -------------------------------------------------------------------------------- /html/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahupowerdns/metronome/84334cce83d95abbac55bfb82a8c8150acbad0a1/html/favicon.ico -------------------------------------------------------------------------------- /html/graph.css: -------------------------------------------------------------------------------- 1 | /* graph */ 2 | 3 | .rickshaw_graph { 4 | position: relative; 5 | } 6 | .rickshaw_graph svg { 7 | display: block; 8 | overflow: hidden; 9 | } 10 | 11 | /* ticks */ 12 | 13 | .rickshaw_graph .x_tick { 14 | position: absolute; 15 | top: 0; 16 | bottom: 0; 17 | width: 0px; 18 | border-left: 1px dotted rgba(0, 0, 0, 0.2); 19 | pointer-events: none; 20 | } 21 | .rickshaw_graph .x_tick .title { 22 | position: absolute; 23 | font-size: 12px; 24 | font-family: Arial, sans-serif; 25 | opacity: 0.5; 26 | white-space: nowrap; 27 | margin-left: 3px; 28 | bottom: 1px; 29 | } 30 | 31 | /* annotations */ 32 | 33 | .rickshaw_annotation_timeline { 34 | height: 1px; 35 | border-top: 1px solid #e0e0e0; 36 | margin-top: 10px; 37 | position: relative; 38 | } 39 | .rickshaw_annotation_timeline .annotation { 40 | position: absolute; 41 | height: 6px; 42 | width: 6px; 43 | margin-left: -2px; 44 | top: -3px; 45 | border-radius: 5px; 46 | background-color: rgba(0, 0, 0, 0.25); 47 | } 48 | .rickshaw_graph .annotation_line { 49 | position: absolute; 50 | top: 0; 51 | bottom: -6px; 52 | width: 0px; 53 | border-left: 2px solid rgba(0, 0, 0, 0.3); 54 | display: none; 55 | } 56 | .rickshaw_graph .annotation_line.active { 57 | display: block; 58 | } 59 | 60 | .rickshaw_graph .annotation_range { 61 | background: rgba(0, 0, 0, 0.1); 62 | display: none; 63 | position: absolute; 64 | top: 0; 65 | bottom: -6px; 66 | } 67 | .rickshaw_graph .annotation_range.active { 68 | display: block; 69 | } 70 | .rickshaw_graph .annotation_range.active.offscreen { 71 | display: none; 72 | } 73 | 74 | .rickshaw_annotation_timeline .annotation .content { 75 | background: white; 76 | color: black; 77 | opacity: 0.9; 78 | padding: 5px 5px; 79 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.8); 80 | border-radius: 3px; 81 | position: relative; 82 | z-index: 20; 83 | font-size: 12px; 84 | padding: 6px 8px 8px; 85 | top: 18px; 86 | left: -11px; 87 | width: 160px; 88 | display: none; 89 | cursor: pointer; 90 | } 91 | .rickshaw_annotation_timeline .annotation .content:before { 92 | content: "\25b2"; 93 | position: absolute; 94 | top: -11px; 95 | color: white; 96 | text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.8); 97 | } 98 | .rickshaw_annotation_timeline .annotation.active, 99 | .rickshaw_annotation_timeline .annotation:hover { 100 | background-color: rgba(0, 0, 0, 0.8); 101 | cursor: none; 102 | } 103 | .rickshaw_annotation_timeline .annotation .content:hover { 104 | z-index: 50; 105 | } 106 | .rickshaw_annotation_timeline .annotation.active .content { 107 | display: block; 108 | } 109 | .rickshaw_annotation_timeline .annotation:hover .content { 110 | display: block; 111 | z-index: 50; 112 | } 113 | .rickshaw_graph .y_axis, 114 | .rickshaw_graph .x_axis_d3 { 115 | fill: none; 116 | } 117 | .rickshaw_graph .y_ticks .tick, 118 | .rickshaw_graph .x_ticks_d3 .tick { 119 | stroke: rgba(0, 0, 0, 0.16); 120 | stroke-width: 2px; 121 | shape-rendering: crisp-edges; 122 | pointer-events: none; 123 | } 124 | .rickshaw_graph .y_grid .tick, 125 | .rickshaw_graph .x_grid_d3 .tick { 126 | z-index: -1; 127 | stroke: rgba(0, 0, 0, 0.20); 128 | stroke-width: 1px; 129 | stroke-dasharray: 1 1; 130 | } 131 | .rickshaw_graph .y_grid .tick[data-y-value="0"] { 132 | stroke-dasharray: 1 0; 133 | } 134 | .rickshaw_graph .y_grid path, 135 | .rickshaw_graph .x_grid_d3 path { 136 | fill: none; 137 | stroke: none; 138 | } 139 | .rickshaw_graph .y_ticks path, 140 | .rickshaw_graph .x_ticks_d3 path { 141 | fill: none; 142 | stroke: #808080; 143 | } 144 | .rickshaw_graph .y_ticks text, 145 | .rickshaw_graph .x_ticks_d3 text { 146 | opacity: 0.5; 147 | font-size: 12px; 148 | pointer-events: none; 149 | } 150 | .rickshaw_graph .x_tick.glow .title, 151 | .rickshaw_graph .y_ticks.glow text { 152 | fill: black; 153 | color: black; 154 | text-shadow: 155 | -1px 1px 0 rgba(255, 255, 255, 0.1), 156 | 1px -1px 0 rgba(255, 255, 255, 0.1), 157 | 1px 1px 0 rgba(255, 255, 255, 0.1), 158 | 0px 1px 0 rgba(255, 255, 255, 0.1), 159 | 0px -1px 0 rgba(255, 255, 255, 0.1), 160 | 1px 0px 0 rgba(255, 255, 255, 0.1), 161 | -1px 0px 0 rgba(255, 255, 255, 0.1), 162 | -1px -1px 0 rgba(255, 255, 255, 0.1); 163 | } 164 | .rickshaw_graph .x_tick.inverse .title, 165 | .rickshaw_graph .y_ticks.inverse text { 166 | fill: white; 167 | color: white; 168 | text-shadow: 169 | -1px 1px 0 rgba(0, 0, 0, 0.8), 170 | 1px -1px 0 rgba(0, 0, 0, 0.8), 171 | 1px 1px 0 rgba(0, 0, 0, 0.8), 172 | 0px 1px 0 rgba(0, 0, 0, 0.8), 173 | 0px -1px 0 rgba(0, 0, 0, 0.8), 174 | 1px 0px 0 rgba(0, 0, 0, 0.8), 175 | -1px 0px 0 rgba(0, 0, 0, 0.8), 176 | -1px -1px 0 rgba(0, 0, 0, 0.8); 177 | } 178 | -------------------------------------------------------------------------------- /html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Metronome 19 | 20 | 21 | Fork me on GitHub 22 | 50 |
51 | 52 | 53 | 56 | 59 | 72 | 73 |
54 | 55 | 57 | 58 | 60 | 71 |
74 |
75 |
76 |
77 |

78 |

79 |
80 | Built with Metronome. You are most cordially invited to add your own PowerDNS instances to this "graph as a service". It can be as easy as setting: carbon-server=82.94.213.34, or at runtime: rec_control set-carbon-server 82.94.213.34 81 |
82 | 83 | 84 | -------------------------------------------------------------------------------- /html/js/common.js: -------------------------------------------------------------------------------- 1 | 2 | function getReadableSizeString(fileSizeInBytes) { 3 | var i = -1; 4 | var byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB']; 5 | do { 6 | fileSizeInBytes = fileSizeInBytes / 1024; 7 | i++; 8 | } while (fileSizeInBytes > 1024); 9 | 10 | return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i]; 11 | }; 12 | -------------------------------------------------------------------------------- /html/js/purl.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Purl (A JavaScript URL parser) v2.3.1 3 | * Developed and maintanined by Mark Perkins, mark@allmarkedup.com 4 | * Source repository: https://github.com/allmarkedup/jQuery-URL-Parser 5 | * Licensed under an MIT-style license. See https://github.com/allmarkedup/jQuery-URL-Parser/blob/master/LICENSE for details. 6 | */ 7 | 8 | ;(function(factory) { 9 | if (typeof define === 'function' && define.amd) { 10 | define(factory); 11 | } else { 12 | window.purl = factory(); 13 | } 14 | })(function() { 15 | 16 | var tag2attr = { 17 | a : 'href', 18 | img : 'src', 19 | form : 'action', 20 | base : 'href', 21 | script : 'src', 22 | iframe : 'src', 23 | link : 'href', 24 | embed : 'src', 25 | object : 'data' 26 | }, 27 | 28 | key = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'fragment'], // keys available to query 29 | 30 | aliases = { 'anchor' : 'fragment' }, // aliases for backwards compatability 31 | 32 | parser = { 33 | strict : /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, //less intuitive, more accurate to the specs 34 | loose : /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ // more intuitive, fails on relative paths and deviates from specs 35 | }, 36 | 37 | isint = /^[0-9]+$/; 38 | 39 | function parseUri( url, strictMode ) { 40 | var str = decodeURI( url ), 41 | res = parser[ strictMode || false ? 'strict' : 'loose' ].exec( str ), 42 | uri = { attr : {}, param : {}, seg : {} }, 43 | i = 14; 44 | 45 | while ( i-- ) { 46 | uri.attr[ key[i] ] = res[i] || ''; 47 | } 48 | 49 | // build query and fragment parameters 50 | uri.param['query'] = parseString(uri.attr['query']); 51 | uri.param['fragment'] = parseString(uri.attr['fragment']); 52 | 53 | // split path and fragement into segments 54 | uri.seg['path'] = uri.attr.path.replace(/^\/+|\/+$/g,'').split('/'); 55 | uri.seg['fragment'] = uri.attr.fragment.replace(/^\/+|\/+$/g,'').split('/'); 56 | 57 | // compile a 'base' domain attribute 58 | uri.attr['base'] = uri.attr.host ? (uri.attr.protocol ? uri.attr.protocol+'://'+uri.attr.host : uri.attr.host) + (uri.attr.port ? ':'+uri.attr.port : '') : ''; 59 | 60 | return uri; 61 | } 62 | 63 | function getAttrName( elm ) { 64 | var tn = elm.tagName; 65 | if ( typeof tn !== 'undefined' ) return tag2attr[tn.toLowerCase()]; 66 | return tn; 67 | } 68 | 69 | function promote(parent, key) { 70 | if (parent[key].length === 0) return parent[key] = {}; 71 | var t = {}; 72 | for (var i in parent[key]) t[i] = parent[key][i]; 73 | parent[key] = t; 74 | return t; 75 | } 76 | 77 | function parse(parts, parent, key, val) { 78 | var part = parts.shift(); 79 | if (!part) { 80 | if (isArray(parent[key])) { 81 | parent[key].push(val); 82 | } else if ('object' == typeof parent[key]) { 83 | parent[key] = val; 84 | } else if ('undefined' == typeof parent[key]) { 85 | parent[key] = val; 86 | } else { 87 | parent[key] = [parent[key], val]; 88 | } 89 | } else { 90 | var obj = parent[key] = parent[key] || []; 91 | if (']' == part) { 92 | if (isArray(obj)) { 93 | if ('' !== val) obj.push(val); 94 | } else if ('object' == typeof obj) { 95 | obj[keys(obj).length] = val; 96 | } else { 97 | obj = parent[key] = [parent[key], val]; 98 | } 99 | } else if (~part.indexOf(']')) { 100 | part = part.substr(0, part.length - 1); 101 | if (!isint.test(part) && isArray(obj)) obj = promote(parent, key); 102 | parse(parts, obj, part, val); 103 | // key 104 | } else { 105 | if (!isint.test(part) && isArray(obj)) obj = promote(parent, key); 106 | parse(parts, obj, part, val); 107 | } 108 | } 109 | } 110 | 111 | function merge(parent, key, val) { 112 | if (~key.indexOf(']')) { 113 | var parts = key.split('['); 114 | parse(parts, parent, 'base', val); 115 | } else { 116 | if (!isint.test(key) && isArray(parent.base)) { 117 | var t = {}; 118 | for (var k in parent.base) t[k] = parent.base[k]; 119 | parent.base = t; 120 | } 121 | if (key !== '') { 122 | set(parent.base, key, val); 123 | } 124 | } 125 | return parent; 126 | } 127 | 128 | function parseString(str) { 129 | return reduce(String(str).split(/&|;/), function(ret, pair) { 130 | try { 131 | pair = decodeURIComponent(pair.replace(/\+/g, ' ')); 132 | } catch(e) { 133 | // ignore 134 | } 135 | var eql = pair.indexOf('='), 136 | brace = lastBraceInKey(pair), 137 | key = pair.substr(0, brace || eql), 138 | val = pair.substr(brace || eql, pair.length); 139 | 140 | val = val.substr(val.indexOf('=') + 1, val.length); 141 | 142 | if (key === '') { 143 | key = pair; 144 | val = ''; 145 | } 146 | 147 | return merge(ret, key, val); 148 | }, { base: {} }).base; 149 | } 150 | 151 | function set(obj, key, val) { 152 | var v = obj[key]; 153 | if (typeof v === 'undefined') { 154 | obj[key] = val; 155 | } else if (isArray(v)) { 156 | v.push(val); 157 | } else { 158 | obj[key] = [v, val]; 159 | } 160 | } 161 | 162 | function lastBraceInKey(str) { 163 | var len = str.length, 164 | brace, 165 | c; 166 | for (var i = 0; i < len; ++i) { 167 | c = str[i]; 168 | if (']' == c) brace = false; 169 | if ('[' == c) brace = true; 170 | if ('=' == c && !brace) return i; 171 | } 172 | } 173 | 174 | function reduce(obj, accumulator){ 175 | var i = 0, 176 | l = obj.length >> 0, 177 | curr = arguments[2]; 178 | while (i < l) { 179 | if (i in obj) curr = accumulator.call(undefined, curr, obj[i], i, obj); 180 | ++i; 181 | } 182 | return curr; 183 | } 184 | 185 | function isArray(vArg) { 186 | return Object.prototype.toString.call(vArg) === "[object Array]"; 187 | } 188 | 189 | function keys(obj) { 190 | var key_array = []; 191 | for ( var prop in obj ) { 192 | if ( obj.hasOwnProperty(prop) ) key_array.push(prop); 193 | } 194 | return key_array; 195 | } 196 | 197 | function purl( url, strictMode ) { 198 | if ( arguments.length === 1 && url === true ) { 199 | strictMode = true; 200 | url = undefined; 201 | } 202 | strictMode = strictMode || false; 203 | url = url || window.location.toString(); 204 | 205 | return { 206 | 207 | data : parseUri(url, strictMode), 208 | 209 | // get various attributes from the URI 210 | attr : function( attr ) { 211 | attr = aliases[attr] || attr; 212 | return typeof attr !== 'undefined' ? this.data.attr[attr] : this.data.attr; 213 | }, 214 | 215 | // return query string parameters 216 | param : function( param ) { 217 | return typeof param !== 'undefined' ? this.data.param.query[param] : this.data.param.query; 218 | }, 219 | 220 | // return fragment parameters 221 | fparam : function( param ) { 222 | return typeof param !== 'undefined' ? this.data.param.fragment[param] : this.data.param.fragment; 223 | }, 224 | 225 | // return path segments 226 | segment : function( seg ) { 227 | if ( typeof seg === 'undefined' ) { 228 | return this.data.seg.path; 229 | } else { 230 | seg = seg < 0 ? this.data.seg.path.length + seg : seg - 1; // negative segments count from the end 231 | return this.data.seg.path[seg]; 232 | } 233 | }, 234 | 235 | // return fragment segments 236 | fsegment : function( seg ) { 237 | if ( typeof seg === 'undefined' ) { 238 | return this.data.seg.fragment; 239 | } else { 240 | seg = seg < 0 ? this.data.seg.fragment.length + seg : seg - 1; // negative segments count from the end 241 | return this.data.seg.fragment[seg]; 242 | } 243 | } 244 | 245 | }; 246 | 247 | } 248 | 249 | purl.jQuery = function($){ 250 | if ($ != null) { 251 | $.fn.url = function( strictMode ) { 252 | var url = ''; 253 | if ( this.length ) { 254 | url = $(this).attr( getAttrName(this[0]) ) || ''; 255 | } 256 | return purl( url, strictMode ); 257 | }; 258 | 259 | $.url = purl; 260 | } 261 | }; 262 | 263 | purl.jQuery(window.jQuery); 264 | 265 | return purl; 266 | 267 | }); 268 | -------------------------------------------------------------------------------- /html/js/underscore-min.js: -------------------------------------------------------------------------------- 1 | (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,v=e.reduce,h=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,_=Object.keys,j=i.bind,w=function(n){return n instanceof w?n:this instanceof w?(this._wrapped=n,void 0):new w(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=w),exports._=w):n._=w,w.VERSION="1.4.3";var A=w.each=w.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a in n)if(w.has(n,a)&&t.call(e,n[a],a,n)===r)return};w.map=w.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e[e.length]=t.call(r,n,u,i)}),e)};var O="Reduce of empty array with no initial value";w.reduce=w.foldl=w.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduce===v)return e&&(t=w.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},w.reduceRight=w.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduceRight===h)return e&&(t=w.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=w.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},w.find=w.detect=function(n,t,r){var e;return E(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},w.filter=w.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&(e[e.length]=n)}),e)},w.reject=function(n,t,r){return w.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},w.every=w.all=function(n,t,e){t||(t=w.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var E=w.some=w.any=function(n,t,e){t||(t=w.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};w.contains=w.include=function(n,t){return null==n?!1:y&&n.indexOf===y?-1!=n.indexOf(t):E(n,function(n){return n===t})},w.invoke=function(n,t){var r=o.call(arguments,2);return w.map(n,function(n){return(w.isFunction(t)?t:n[t]).apply(n,r)})},w.pluck=function(n,t){return w.map(n,function(n){return n[t]})},w.where=function(n,t){return w.isEmpty(t)?[]:w.filter(n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},w.max=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.max.apply(Math,n);if(!t&&w.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>=e.computed&&(e={value:n,computed:a})}),e.value},w.min=function(n,t,r){if(!t&&w.isArray(n)&&n[0]===+n[0]&&65535>n.length)return Math.min.apply(Math,n);if(!t&&w.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;e.computed>a&&(e={value:n,computed:a})}),e.value},w.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=w.random(r++),e[r-1]=e[t],e[t]=n}),e};var F=function(n){return w.isFunction(n)?n:function(t){return t[n]}};w.sortBy=function(n,t,r){var e=F(t);return w.pluck(w.map(n,function(n,t,u){return{value:n,index:t,criteria:e.call(r,n,t,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||void 0===r)return 1;if(e>r||void 0===e)return-1}return n.indexi;){var o=i+a>>>1;u>r.call(e,n[o])?i=o+1:a=o}return i},w.toArray=function(n){return n?w.isArray(n)?o.call(n):n.length===+n.length?w.map(n,w.identity):w.values(n):[]},w.size=function(n){return null==n?0:n.length===+n.length?n.length:w.keys(n).length},w.first=w.head=w.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:o.call(n,0,t)},w.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},w.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},w.rest=w.tail=w.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},w.compact=function(n){return w.filter(n,w.identity)};var R=function(n,t,r){return A(n,function(n){w.isArray(n)?t?a.apply(r,n):R(n,t,r):r.push(n)}),r};w.flatten=function(n,t){return R(n,t,[])},w.without=function(n){return w.difference(n,o.call(arguments,1))},w.uniq=w.unique=function(n,t,r,e){w.isFunction(t)&&(e=r,r=t,t=!1);var u=r?w.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:w.contains(a,r))||(a.push(r),i.push(n[e]))}),i},w.union=function(){return w.uniq(c.apply(e,arguments))},w.intersection=function(n){var t=o.call(arguments,1);return w.filter(w.uniq(n),function(n){return w.every(t,function(t){return w.indexOf(t,n)>=0})})},w.difference=function(n){var t=c.apply(e,o.call(arguments,1));return w.filter(n,function(n){return!w.contains(t,n)})},w.zip=function(){for(var n=o.call(arguments),t=w.max(w.pluck(n,"length")),r=Array(t),e=0;t>e;e++)r[e]=w.pluck(n,""+e);return r},w.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},w.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=w.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},w.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},w.range=function(n,t,r){1>=arguments.length&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=Array(e);e>u;)i[u++]=n,n+=r;return i};var I=function(){};w.bind=function(n,t){var r,e;if(n.bind===j&&j)return j.apply(n,o.call(arguments,1));if(!w.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));I.prototype=n.prototype;var u=new I;I.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},w.bindAll=function(n){var t=o.call(arguments,1);return 0==t.length&&(t=w.functions(n)),A(t,function(t){n[t]=w.bind(n[t],n)}),n},w.memoize=function(n,t){var r={};return t||(t=w.identity),function(){var e=t.apply(this,arguments);return w.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},w.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},w.defer=function(n){return w.delay.apply(w,[n,1].concat(o.call(arguments,1)))},w.throttle=function(n,t){var r,e,u,i,a=0,o=function(){a=new Date,u=null,i=n.apply(r,e)};return function(){var c=new Date,l=t-(c-a);return r=this,e=arguments,0>=l?(clearTimeout(u),u=null,a=c,i=n.apply(r,e)):u||(u=setTimeout(o,l)),i}},w.debounce=function(n,t,r){var e,u;return function(){var i=this,a=arguments,o=function(){e=null,r||(u=n.apply(i,a))},c=r&&!e;return clearTimeout(e),e=setTimeout(o,t),c&&(u=n.apply(i,a)),u}},w.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},w.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},w.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},w.after=function(n,t){return 0>=n?t():function(){return 1>--n?t.apply(this,arguments):void 0}},w.keys=_||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)w.has(n,r)&&(t[t.length]=r);return t},w.values=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push(n[r]);return t},w.pairs=function(n){var t=[];for(var r in n)w.has(n,r)&&t.push([r,n[r]]);return t},w.invert=function(n){var t={};for(var r in n)w.has(n,r)&&(t[n[r]]=r);return t},w.functions=w.methods=function(n){var t=[];for(var r in n)w.isFunction(n[r])&&t.push(r);return t.sort()},w.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},w.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},w.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)w.contains(r,u)||(t[u]=n[u]);return t},w.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)null==n[r]&&(n[r]=t[r])}),n},w.clone=function(n){return w.isObject(n)?w.isArray(n)?n.slice():w.extend({},n):n},w.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof w&&(n=n._wrapped),t instanceof w&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==t+"";case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;r.push(n),e.push(t);var a=0,o=!0;if("[object Array]"==u){if(a=n.length,o=a==t.length)for(;a--&&(o=S(n[a],t[a],r,e)););}else{var c=n.constructor,f=t.constructor;if(c!==f&&!(w.isFunction(c)&&c instanceof c&&w.isFunction(f)&&f instanceof f))return!1;for(var s in n)if(w.has(n,s)&&(a++,!(o=w.has(t,s)&&S(n[s],t[s],r,e))))break;if(o){for(s in t)if(w.has(t,s)&&!a--)break;o=!a}}return r.pop(),e.pop(),o};w.isEqual=function(n,t){return S(n,t,[],[])},w.isEmpty=function(n){if(null==n)return!0;if(w.isArray(n)||w.isString(n))return 0===n.length;for(var t in n)if(w.has(n,t))return!1;return!0},w.isElement=function(n){return!(!n||1!==n.nodeType)},w.isArray=x||function(n){return"[object Array]"==l.call(n)},w.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){w["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),w.isArguments(arguments)||(w.isArguments=function(n){return!(!n||!w.has(n,"callee"))}),w.isFunction=function(n){return"function"==typeof n},w.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},w.isNaN=function(n){return w.isNumber(n)&&n!=+n},w.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},w.isNull=function(n){return null===n},w.isUndefined=function(n){return void 0===n},w.has=function(n,t){return f.call(n,t)},w.noConflict=function(){return n._=t,this},w.identity=function(n){return n},w.times=function(n,t,r){for(var e=Array(n),u=0;n>u;u++)e[u]=t.call(r,u);return e},w.random=function(n,t){return null==t&&(t=n,n=0),n+(0|Math.random()*(t-n+1))};var T={escape:{"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"}};T.unescape=w.invert(T.escape);var M={escape:RegExp("["+w.keys(T.escape).join("")+"]","g"),unescape:RegExp("("+w.keys(T.unescape).join("|")+")","g")};w.each(["escape","unescape"],function(n){w[n]=function(t){return null==t?"":(""+t).replace(M[n],function(t){return T[n][t]})}}),w.result=function(n,t){if(null==n)return null;var r=n[t];return w.isFunction(r)?r.call(n):r},w.mixin=function(n){A(w.functions(n),function(t){var r=w[t]=n[t];w.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(w,n))}})};var N=0;w.uniqueId=function(n){var t=""+ ++N;return n?n+t:t},w.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;w.template=function(n,t,r){r=w.defaults({},r,w.templateSettings);var e=RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,a,o){return i+=n.slice(u,o).replace(D,function(n){return"\\"+B[n]}),r&&(i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(i+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),a&&(i+="';\n"+a+"\n__p+='"),u=o+t.length,t}),i+="';\n",r.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var a=Function(r.variable||"obj","_",i)}catch(o){throw o.source=i,o}if(t)return a(t,w);var c=function(n){return a.call(this,n,w)};return c.source="function("+(r.variable||"obj")+"){\n"+i+"}",c},w.chain=function(n){return w(n).chain()};var z=function(n){return this._chain?w(n).chain():n};w.mixin(w),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];w.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];w.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),w.extend(w.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this); -------------------------------------------------------------------------------- /html/legend.css: -------------------------------------------------------------------------------- 1 | .rickshaw_legend { 2 | font-family: Arial; 3 | font-size: 12px; 4 | color: white; 5 | background: #404040; 6 | display: inline-block; 7 | padding: 12px 5px; 8 | border-radius: 2px; 9 | position: relative; 10 | } 11 | .rickshaw_legend:hover { 12 | z-index: 10; 13 | } 14 | .rickshaw_legend .swatch { 15 | width: 10px; 16 | height: 10px; 17 | border: 1px solid rgba(0, 0, 0, 0.2); 18 | } 19 | .rickshaw_legend .line { 20 | clear: both; 21 | line-height: 140%; 22 | padding-right: 15px; 23 | } 24 | .rickshaw_legend .line .swatch { 25 | display: inline-block; 26 | margin-right: 3px; 27 | border-radius: 2px; 28 | } 29 | .rickshaw_legend .label { 30 | margin: 0; 31 | white-space: nowrap; 32 | display: inline; 33 | font-size: inherit; 34 | background-color: transparent; 35 | color: inherit; 36 | font-weight: normal; 37 | line-height: normal; 38 | padding: 0px; 39 | text-shadow: none; 40 | } 41 | .rickshaw_legend .action:hover { 42 | opacity: 0.6; 43 | } 44 | .rickshaw_legend .action { 45 | margin-right: 0.2em; 46 | font-size: 10px; 47 | opacity: 0.2; 48 | cursor: pointer; 49 | font-size: 14px; 50 | } 51 | .rickshaw_legend .line.disabled { 52 | opacity: 0.4; 53 | } 54 | .rickshaw_legend ul { 55 | list-style-type: none; 56 | margin: 0; 57 | padding: 0; 58 | margin: 2px; 59 | cursor: pointer; 60 | } 61 | .rickshaw_legend li { 62 | padding: 0 0 0 2px; 63 | min-width: 80px; 64 | white-space: nowrap; 65 | } 66 | .rickshaw_legend li:hover { 67 | background: rgba(255, 255, 255, 0.08); 68 | border-radius: 3px; 69 | } 70 | .rickshaw_legend li:active { 71 | background: rgba(255, 255, 255, 0.2); 72 | border-radius: 3px; 73 | } 74 | -------------------------------------------------------------------------------- /html/lines.css: -------------------------------------------------------------------------------- 1 | div, span, p, td { 2 | font-family: Arial, sans-serif; 3 | } 4 | #chart { 5 | display: inline-block; 6 | } 7 | #legend { 8 | display: inline-block; 9 | position: relative; 10 | left: 8px; 11 | } 12 | #legend_container { 13 | position: absolute; 14 | right: 0; 15 | bottom: 26px; 16 | width: 0; 17 | } 18 | #chart_container { 19 | float: left; 20 | position: relative; 21 | } 22 | -------------------------------------------------------------------------------- /html/local.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var metronomeServer="http://127.0.0.1:8000/"; 3 | -------------------------------------------------------------------------------- /interpolate.cc: -------------------------------------------------------------------------------- 1 | #include "interpolate.hh" 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | using namespace Eigen; 7 | 8 | namespace { 9 | // returns (1, x, x*x, x*x*x) 10 | VectorXd func(double x, int order) 11 | { 12 | VectorXd ret(order); 13 | if(!order) 14 | return ret; 15 | 16 | ret(0)=1; 17 | for(int i = 1 ; i < order; ++i) 18 | ret(i)=x*ret(i-1); 19 | return ret; 20 | } 21 | 22 | // returns (0, 1, 2*x, 3*x*x, 4*x*x*x) 23 | VectorXd deriv(double x, int order) 24 | { 25 | VectorXd ret(order); 26 | if(!order) 27 | return ret; 28 | 29 | ret(0)=0; 30 | if(order <2) 31 | return ret; 32 | 33 | ret(1)=1; 34 | for(int i = 2 ; i < order; ++i) { 35 | ret(i)=i*x; 36 | x *= x; 37 | } 38 | return ret; 39 | } 40 | 41 | #if 0 42 | void plotSolution(const vector& input, const VectorXd& res, int order) 43 | { 44 | ofstream plot("plot"); 45 | for(double x=-1; x < 1; x+=0.02) { 46 | VectorXd f = func(x, order); 47 | plot< normalize(const vector& input, double* x, double *factor) 57 | { 58 | auto ret = input; 59 | sort(ret.begin(), ret.end()); 60 | double low = ret.begin()->x, high = ret.rbegin()->x; 61 | if(low == high) { 62 | for(auto& d : ret) { 63 | d.x = 0; 64 | } 65 | *x=0; 66 | *factor=0; 67 | return ret; 68 | } 69 | 70 | for(auto& d : ret) { 71 | d.x = -1.0 + 2.0*(d.x-low)/(high-low); 72 | } 73 | *x = (*x - low )/ (high-low); 74 | *factor = 2/(high-low); 75 | return ret; 76 | } 77 | } 78 | 79 | pair interpolate(const vector& input, unsigned int order, double x) 80 | { 81 | /* 82 | 83 | cerr<<"Input: "; 84 | for(const auto& f : input) { 85 | cerr<<"("< 3 | #include 4 | struct InterpolateDatum 5 | { 6 | double x; 7 | double y; 8 | bool operator<(const InterpolateDatum& rhs) const 9 | { 10 | return x < rhs.x; 11 | } 12 | }; 13 | 14 | std::pair interpolate(const std::vector& input, unsigned int order, double x); 15 | -------------------------------------------------------------------------------- /iputils.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "iputils.hh" 4 | /** these functions provide a very lightweight wrapper to the Berkeley sockets API. Errors -> exceptions! */ 5 | 6 | using namespace std; 7 | 8 | static void RuntimeError(const boost::format& fmt) 9 | { 10 | throw runtime_error(fmt.str()); 11 | } 12 | 13 | 14 | int SSocket(int family, int type, int flags) 15 | { 16 | int ret = socket(family, type, flags); 17 | if(ret < 0) 18 | RuntimeError(boost::format("creating socket of type %d: %s") % family % strerror(errno)); 19 | return ret; 20 | } 21 | 22 | int SConnect(int sockfd, const ComboAddress& remote) 23 | { 24 | int ret = connect(sockfd, (struct sockaddr*)&remote, remote.getSocklen()); 25 | if(ret < 0) 26 | RuntimeError(boost::format("connecting socket to %s: %s") % remote.toStringWithPort() % strerror(errno)); 27 | return ret; 28 | } 29 | 30 | int SBind(int sockfd, const ComboAddress& local) 31 | { 32 | int ret = ::bind(sockfd, (struct sockaddr*)&local, local.getSocklen()); 33 | if(ret < 0) 34 | RuntimeError(boost::format("binding socket to %s: %s") % local.toStringWithPort() % strerror(errno)); 35 | return ret; 36 | } 37 | 38 | int SAccept(int sockfd, ComboAddress& remote) 39 | { 40 | socklen_t remlen = remote.getSocklen(); 41 | 42 | int ret = accept(sockfd, (struct sockaddr*)&remote, &remlen); 43 | if(ret < 0) 44 | RuntimeError(boost::format("accepting new connection on socket: %s") % strerror(errno)); 45 | return ret; 46 | } 47 | 48 | int SListen(int sockfd, int limit) 49 | { 50 | int ret = listen(sockfd, limit); 51 | if(ret < 0) 52 | RuntimeError(boost::format("setting socket to listen: %s") % strerror(errno)); 53 | return ret; 54 | } 55 | 56 | int SSetsockopt(int sockfd, int level, int opname, int value) 57 | { 58 | int ret = setsockopt(sockfd, level, opname, &value, sizeof(value)); 59 | if(ret < 0) 60 | RuntimeError(boost::format("setsockopt for level %d and opname %d to %d failed: %s") % level % opname % value % strerror(errno)); 61 | return ret; 62 | } 63 | 64 | int writen(int fd, const void *buf, size_t count) 65 | { 66 | const char *ptr = (char*)buf; 67 | const char *eptr = ptr + count; 68 | 69 | int res; 70 | while(ptr != eptr) { 71 | res = ::write(fd, ptr, eptr - ptr); 72 | if(res < 0) { 73 | if (errno == EAGAIN) 74 | throw std::runtime_error("used writen on non-blocking socket, got EAGAIN"); 75 | else 76 | unixDie("failed in writen"); 77 | } 78 | else if (res == 0) 79 | throw std::runtime_error("could not write all bytes, got eof in writen2"); 80 | 81 | ptr += res; 82 | } 83 | 84 | return count; 85 | } 86 | 87 | 88 | int makeIPv6sockaddr(const std::string& addr, struct sockaddr_in6* ret) 89 | { 90 | if(addr.empty()) 91 | return -1; 92 | string ourAddr(addr); 93 | int port = -1; 94 | if(addr[0]=='[') { // [::]:53 style address 95 | string::size_type pos = addr.find(']'); 96 | if(pos == string::npos || pos + 2 > addr.size() || addr[pos+1]!=':') 97 | return -1; 98 | ourAddr.assign(addr.c_str() + 1, pos-1); 99 | port = atoi(addr.c_str()+pos+2); 100 | } 101 | 102 | struct addrinfo* res; 103 | struct addrinfo hints; 104 | memset(&hints, 0, sizeof(hints)); 105 | 106 | hints.ai_family = AF_INET6; 107 | hints.ai_flags = AI_NUMERICHOST; 108 | 109 | int error; 110 | if((error=getaddrinfo(ourAddr.c_str(), 0, &hints, &res))) { // this is correct 111 | /* 112 | cerr<<"Error translating IPv6 address '"<ai_addr, res->ai_addrlen); 122 | if(port >= 0) 123 | ret->sin6_port = htons(port); 124 | freeaddrinfo(res); 125 | return 0; 126 | } 127 | 128 | int makeIPv4sockaddr(const std::string& str, struct sockaddr_in* ret) 129 | { 130 | if(str.empty()) { 131 | return -1; 132 | } 133 | struct in_addr inp; 134 | 135 | string::size_type pos = str.find(':'); 136 | if(pos == string::npos) { // no port specified, not touching the port 137 | if(inet_aton(str.c_str(), &inp)) { 138 | ret->sin_addr.s_addr=inp.s_addr; 139 | return 0; 140 | } 141 | return -1; 142 | } 143 | if(!*(str.c_str() + pos + 1)) // trailing : 144 | return -1; 145 | 146 | char *eptr = (char*)str.c_str() + str.size(); 147 | int port = strtol(str.c_str() + pos + 1, &eptr, 10); 148 | if(*eptr) 149 | return -1; 150 | 151 | ret->sin_port = htons(port); 152 | if(inet_aton(str.substr(0, pos).c_str(), &inp)) { 153 | ret->sin_addr.s_addr=inp.s_addr; 154 | return 0; 155 | } 156 | return -1; 157 | } 158 | 159 | static int waitForData(int fd, unsigned int seconds) 160 | { 161 | struct pollfd pfd; 162 | memset(&pfd, 0, sizeof(pfd)); 163 | pfd.fd = fd; 164 | pfd.events = POLLIN; 165 | 166 | return poll(&pfd, 1, seconds * 1000); 167 | } 168 | 169 | bool sockGetLine(int sock, string& ret, unsigned int timeout) 170 | { 171 | const time_t startTime = time(nullptr); 172 | ret.clear(); 173 | char c; 174 | int err; 175 | 176 | for (;;) { 177 | const time_t now = time(nullptr); 178 | if (now < startTime) { 179 | return false; 180 | } 181 | 182 | if ((now - startTime) > timeout) { 183 | return false; 184 | } 185 | 186 | int res = waitForData(sock, timeout - (now - startTime)); 187 | if (res < 0) { 188 | throw runtime_error("Error while waiting to read from socket: " + string(strerror(errno))); 189 | } 190 | else if (res == 0) { 191 | /* timeout */ 192 | return false; 193 | } 194 | 195 | err = read(sock, &c, 1); 196 | if (err < 0) { 197 | throw runtime_error("Error reading from socket: "+string(strerror(errno))); 198 | } 199 | 200 | if (err == 0) { 201 | /* disconnected */ 202 | break; 203 | } 204 | 205 | ret.append(1, c); 206 | 207 | if (c == '\n') { 208 | break; 209 | } 210 | } 211 | 212 | return !ret.empty(); 213 | } 214 | -------------------------------------------------------------------------------- /iputils.hh: -------------------------------------------------------------------------------- 1 | /* 2 | PowerDNS Versatile Database Driven Nameserver 3 | Copyright (C) 2002 - 2011 PowerDNS.COM BV 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License version 2 7 | as published by the Free Software Foundation 8 | 9 | Additionally, the license of this program contains a special 10 | exception which allows to distribute the program in binary form when 11 | it is linked against OpenSSL. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 21 | */ 22 | #ifndef PDNS_IPUTILSHH 23 | #define PDNS_IPUTILSHH 24 | #include "metromisc.hh" 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | typedef std::runtime_error PDNSException; 41 | 42 | using std::string; 43 | using std::pair; 44 | using std::vector; 45 | using std::tie; 46 | using std::tuple; 47 | using boost::lexical_cast; 48 | int makeIPv6sockaddr(const std::string& addr, struct sockaddr_in6* ret); 49 | int makeIPv4sockaddr(const std::string& str, struct sockaddr_in* ret); 50 | pair splitField(const string& inp, char sepa); 51 | 52 | union ComboAddress { 53 | struct sockaddr_in sin4; 54 | struct sockaddr_in6 sin6; 55 | 56 | bool operator==(const ComboAddress& rhs) const 57 | { 58 | if(tie(sin4.sin_family, sin4.sin_port) != tie(rhs.sin4.sin_family, rhs.sin4.sin_port)) 59 | return false; 60 | if(sin4.sin_family == AF_INET) 61 | return sin4.sin_addr.s_addr == rhs.sin4.sin_addr.s_addr; 62 | else 63 | return memcmp(&sin6.sin6_addr.s6_addr, &rhs.sin6.sin6_addr.s6_addr, 16)==0; 64 | } 65 | 66 | bool operator<(const ComboAddress& rhs) const 67 | { 68 | if(tie(sin4.sin_family, sin4.sin_port) < tie(rhs.sin4.sin_family, rhs.sin4.sin_port)) 69 | return true; 70 | if(tie(sin4.sin_family, sin4.sin_port) > tie(rhs.sin4.sin_family, rhs.sin4.sin_port)) 71 | return false; 72 | 73 | if(sin4.sin_family == AF_INET) 74 | return sin4.sin_addr.s_addr < rhs.sin4.sin_addr.s_addr; 75 | else 76 | return memcmp(&sin6.sin6_addr.s6_addr, &rhs.sin6.sin6_addr.s6_addr, 16) < 0; 77 | } 78 | 79 | bool operator>(const ComboAddress& rhs) const 80 | { 81 | if(tie(sin4.sin_family, sin4.sin_port) > tie(rhs.sin4.sin_family, rhs.sin4.sin_port)) 82 | return true; 83 | if(tie(sin4.sin_family, sin4.sin_port) < tie(rhs.sin4.sin_family, rhs.sin4.sin_port)) 84 | return false; 85 | 86 | if(sin4.sin_family == AF_INET) 87 | return sin4.sin_addr.s_addr > rhs.sin4.sin_addr.s_addr; 88 | else 89 | return memcmp(&sin6.sin6_addr.s6_addr, &rhs.sin6.sin6_addr.s6_addr, 16) > 0; 90 | } 91 | 92 | struct addressOnlyLessThan: public std::binary_function 93 | { 94 | bool operator()(const ComboAddress& a, const ComboAddress& b) const 95 | { 96 | if(a.sin4.sin_family < b.sin4.sin_family) 97 | return true; 98 | if(a.sin4.sin_family > b.sin4.sin_family) 99 | return false; 100 | if(a.sin4.sin_family == AF_INET) 101 | return a.sin4.sin_addr.s_addr < b.sin4.sin_addr.s_addr; 102 | else 103 | return memcmp(&a.sin6.sin6_addr.s6_addr, &b.sin6.sin6_addr.s6_addr, 16) < 0; 104 | } 105 | }; 106 | 107 | socklen_t getSocklen() const 108 | { 109 | if(sin4.sin_family == AF_INET) 110 | return sizeof(sin4); 111 | else 112 | return sizeof(sin6); 113 | } 114 | 115 | ComboAddress() 116 | { 117 | sin4.sin_family=AF_INET; 118 | sin4.sin_addr.s_addr=0; 119 | sin4.sin_port=0; 120 | } 121 | 122 | // 'port' sets a default value in case 'str' does not set a port 123 | explicit ComboAddress(const string& str, uint16_t port=0) 124 | { 125 | memset(&sin6, 0, sizeof(sin6)); 126 | sin4.sin_family = AF_INET; 127 | sin4.sin_port = 0; 128 | if(makeIPv4sockaddr(str, &sin4)) { 129 | sin6.sin6_family = AF_INET6; 130 | if(makeIPv6sockaddr(str, &sin6) < 0) 131 | throw PDNSException("Unable to convert presentation address '"+ str +"'"); 132 | 133 | } 134 | if(!sin4.sin_port) // 'str' overrides port! 135 | sin4.sin_port=htons(port); 136 | } 137 | 138 | bool isMappedIPv4() const 139 | { 140 | if(sin4.sin_family!=AF_INET6) 141 | return false; 142 | 143 | int n=0; 144 | const unsigned char*ptr = (unsigned char*) &sin6.sin6_addr.s6_addr; 145 | for(n=0; n < 10; ++n) 146 | if(ptr[n]) 147 | return false; 148 | 149 | for(; n < 12; ++n) 150 | if(ptr[n]!=0xff) 151 | return false; 152 | 153 | return true; 154 | } 155 | 156 | ComboAddress mapToIPv4() const 157 | { 158 | if(!isMappedIPv4()) 159 | throw PDNSException("ComboAddress can't map non-mapped IPv6 address back to IPv4"); 160 | ComboAddress ret; 161 | ret.sin4.sin_family=AF_INET; 162 | ret.sin4.sin_port=sin4.sin_port; 163 | 164 | const unsigned char*ptr = (unsigned char*) &sin6.sin6_addr.s6_addr; 165 | ptr+=12; 166 | memcpy(&ret.sin4.sin_addr.s_addr, ptr, 4); 167 | return ret; 168 | } 169 | 170 | string toString() const 171 | { 172 | char host[1024]; 173 | getnameinfo((struct sockaddr*) this, getSocklen(), host, sizeof(host),0, 0, NI_NUMERICHOST); 174 | 175 | return host; 176 | } 177 | 178 | string toStringWithPort() const 179 | { 180 | if(sin4.sin_family==AF_INET) 181 | return toString() + ":" + lexical_cast(ntohs(sin4.sin_port)); 182 | else 183 | return "["+toString() + "]:" + lexical_cast(ntohs(sin4.sin_port)); 184 | } 185 | }; 186 | 187 | /** This exception is thrown by the Netmask class and by extension by the NetmaskGroup class */ 188 | class NetmaskException: public PDNSException 189 | { 190 | public: 191 | NetmaskException(const string &a) : PDNSException(a) {} 192 | }; 193 | 194 | inline ComboAddress makeComboAddress(const string& str) 195 | { 196 | ComboAddress address; 197 | address.sin4.sin_family=AF_INET; 198 | if(inet_pton(AF_INET, str.c_str(), &address.sin4.sin_addr) <= 0) { 199 | address.sin4.sin_family=AF_INET6; 200 | if(makeIPv6sockaddr(str, &address.sin6) < 0) 201 | throw NetmaskException("Unable to convert '"+str+"' to a netmask"); 202 | } 203 | return address; 204 | } 205 | 206 | /** This class represents a netmask and can be queried to see if a certain 207 | IP address is matched by this mask */ 208 | class Netmask 209 | { 210 | public: 211 | Netmask() 212 | { 213 | d_network.sin4.sin_family=0; // disable this doing anything useful 214 | } 215 | 216 | Netmask(const ComboAddress& network, uint8_t bits=0xff) 217 | { 218 | d_network = network; 219 | 220 | if(bits == 0xff) 221 | bits = (network.sin4.sin_family == AF_INET) ? 32 : 128; 222 | 223 | d_bits = bits; 224 | if(d_bits<32) 225 | d_mask=~(0xFFFFFFFF>>d_bits); 226 | else 227 | d_mask=0xFFFFFFFF; // not actually used for IPv6 228 | } 229 | 230 | //! Constructor supplies the mask, which cannot be changed 231 | Netmask(const string &mask) 232 | { 233 | pair split=splitField(mask,'/'); 234 | d_network=makeComboAddress(split.first); 235 | 236 | if(!split.second.empty()) { 237 | d_bits = atoi(split.second.c_str()); 238 | if(d_bits<32) 239 | d_mask=~(0xFFFFFFFF>>d_bits); 240 | else 241 | d_mask=0xFFFFFFFF; 242 | } 243 | else if(d_network.sin4.sin_family==AF_INET) { 244 | d_bits = 32; 245 | d_mask = 0xFFFFFFFF; 246 | } 247 | else { 248 | d_bits=128; 249 | d_mask=0; // silence silly warning - d_mask is unused for IPv6 250 | } 251 | } 252 | 253 | bool match(const ComboAddress& ip) const 254 | { 255 | return match(&ip); 256 | } 257 | 258 | //! If this IP address in socket address matches 259 | bool match(const ComboAddress *ip) const 260 | { 261 | if(d_network.sin4.sin_family != ip->sin4.sin_family) { 262 | return false; 263 | } 264 | if(d_network.sin4.sin_family == AF_INET) { 265 | return match4(htonl((unsigned int)ip->sin4.sin_addr.s_addr)); 266 | } 267 | if(d_network.sin6.sin6_family == AF_INET6) { 268 | uint8_t bytes=d_bits/8, n; 269 | const uint8_t *us=(const uint8_t*) &d_network.sin6.sin6_addr.s6_addr; 270 | const uint8_t *them=(const uint8_t*) &ip->sin6.sin6_addr.s6_addr; 271 | 272 | for(n=0; n < bytes; ++n) { 273 | if(us[n]!=them[n]) { 274 | return false; 275 | } 276 | } 277 | // still here, now match remaining bits 278 | uint8_t bits= d_bits % 8; 279 | uint8_t mask= ~(0xFF>>bits); 280 | 281 | return((us[n] & mask) == (them[n] & mask)); 282 | } 283 | return false; 284 | } 285 | 286 | //! If this ASCII IP address matches 287 | bool match(const string &ip) const 288 | { 289 | ComboAddress address=makeComboAddress(ip); 290 | return match(&address); 291 | } 292 | 293 | //! If this IP address in native format matches 294 | bool match4(uint32_t ip) const 295 | { 296 | return (ip & d_mask) == (ntohl(d_network.sin4.sin_addr.s_addr) & d_mask); 297 | } 298 | 299 | string toString() const 300 | { 301 | return d_network.toString()+"/"+lexical_cast((unsigned int)d_bits); 302 | } 303 | 304 | string toStringNoMask() const 305 | { 306 | return d_network.toString(); 307 | } 308 | const ComboAddress& getNetwork() const 309 | { 310 | return d_network; 311 | } 312 | int getBits() const 313 | { 314 | return d_bits; 315 | } 316 | private: 317 | ComboAddress d_network; 318 | uint32_t d_mask; 319 | uint8_t d_bits; 320 | }; 321 | 322 | /** This class represents a group of supplemental Netmask classes. An IP address matchs 323 | if it is matched by zero or more of the Netmask classes within. 324 | */ 325 | class NetmaskGroup 326 | { 327 | public: 328 | //! If this IP address is matched by any of the classes within 329 | 330 | bool match(const ComboAddress *ip) 331 | { 332 | for(container_t::const_iterator i=d_masks.begin();i!=d_masks.end();++i) 333 | if(i->match(ip) || (ip->isMappedIPv4() && i->match(ip->mapToIPv4()) )) 334 | return true; 335 | 336 | return false; 337 | } 338 | 339 | bool match(const ComboAddress& ip) 340 | { 341 | return match(&ip); 342 | } 343 | 344 | //! Add this Netmask to the list of possible matches 345 | void addMask(const string &ip) 346 | { 347 | d_masks.push_back(Netmask(ip)); 348 | } 349 | 350 | void clear() 351 | { 352 | d_masks.clear(); 353 | } 354 | 355 | bool empty() 356 | { 357 | return d_masks.empty(); 358 | } 359 | 360 | unsigned int size() 361 | { 362 | return (unsigned int)d_masks.size(); 363 | } 364 | 365 | string toString() const 366 | { 367 | std::ostringstream str; 368 | for(container_t::const_iterator iter = d_masks.begin(); iter != d_masks.end(); ++iter) { 369 | if(iter != d_masks.begin()) 370 | str <<", "; 371 | str<toString(); 372 | } 373 | return str.str(); 374 | } 375 | 376 | void toStringVector(vector* vec) const 377 | { 378 | for(container_t::const_iterator iter = d_masks.begin(); iter != d_masks.end(); ++iter) { 379 | vec->push_back(iter->toString()); 380 | } 381 | } 382 | 383 | void toMasks(const string &ips) 384 | { 385 | vector parts; 386 | stringtok(parts, ips, ", \t"); 387 | 388 | for (vector::const_iterator iter = parts.begin(); iter != parts.end(); ++iter) 389 | addMask(*iter); 390 | } 391 | 392 | private: 393 | typedef vector container_t; 394 | container_t d_masks; 395 | }; 396 | 397 | 398 | int SSocket(int family, int type, int flags); 399 | int SConnect(int sockfd, const ComboAddress& remote); 400 | int SBind(int sockfd, const ComboAddress& local); 401 | int SAccept(int sockfd, ComboAddress& remote); 402 | int SListen(int sockfd, int limit); 403 | int SSetsockopt(int sockfd, int level, int opname, int value); 404 | int writen(int fd, const void *buf, size_t count); 405 | inline int writen(int fd, const std::string& str) 406 | { 407 | return writen(fd, str.c_str(), str.size()); 408 | } 409 | bool sockGetLine(int sock, string& ret, unsigned int timeout); 410 | #endif 411 | -------------------------------------------------------------------------------- /m4/ax_arg_default_enable_disable.m4: -------------------------------------------------------------------------------- 1 | AC_DEFUN([AX_ARG_DEFAULT_ENABLE], [ 2 | AC_ARG_ENABLE([$1], AS_HELP_STRING([--disable-$1], [$2 (default is ENABLED)])) 3 | AX_PARSE_VALUE([$1], [y]) 4 | ]) 5 | 6 | AC_DEFUN([AX_ARG_DEFAULT_DISABLE], [ 7 | AC_ARG_ENABLE([$1], AS_HELP_STRING([--enable-$1], [$2 (default is DISABLED)])) 8 | AX_PARSE_VALUE([$1], [n]) 9 | ]) 10 | 11 | dnl This function should not be called outside of this file 12 | AC_DEFUN([AX_PARSE_VALUE], [ 13 | AS_IF([test "x$enable_$1" = "xno"], [ 14 | ax_cv_$1="n" 15 | ], [test "x$enable_$1" = "xyes"], [ 16 | ax_cv_$1="y" 17 | ], [test -z $ax_cv_$1], [ 18 | ax_cv_$1="$2" 19 | ]) 20 | $1=$ax_cv_$1 21 | AC_SUBST($1)]) 22 | -------------------------------------------------------------------------------- /m4/ax_cxx_compile_stdcxx_11.m4: -------------------------------------------------------------------------------- 1 | # ============================================================================ 2 | # http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html 3 | # ============================================================================ 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_CXX_COMPILE_STDCXX_11([ext|noext],[mandatory|optional]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # Check for baseline language coverage in the compiler for the C++11 12 | # standard; if necessary, add switches to CXXFLAGS to enable support. 13 | # 14 | # The first argument, if specified, indicates whether you insist on an 15 | # extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. 16 | # -std=c++11). If neither is specified, you get whatever works, with 17 | # preference for an extended mode. 18 | # 19 | # The second argument, if specified 'mandatory' or if left unspecified, 20 | # indicates that baseline C++11 support is required and that the macro 21 | # should error out if no mode with that support is found. If specified 22 | # 'optional', then configuration proceeds regardless, after defining 23 | # HAVE_CXX11 if and only if a supporting mode is found. 24 | # 25 | # LICENSE 26 | # 27 | # Copyright (c) 2008 Benjamin Kosnik 28 | # Copyright (c) 2012 Zack Weinberg 29 | # Copyright (c) 2013 Roy Stogner 30 | # Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov 31 | # 32 | # Copying and distribution of this file, with or without modification, are 33 | # permitted in any medium without royalty provided the copyright notice 34 | # and this notice are preserved. This file is offered as-is, without any 35 | # warranty. 36 | 37 | #serial 10 38 | 39 | m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody], [[ 40 | template 41 | struct check 42 | { 43 | static_assert(sizeof(int) <= sizeof(T), "not big enough"); 44 | }; 45 | 46 | struct Base { 47 | virtual void f() {} 48 | }; 49 | struct Child : public Base { 50 | virtual void f() override {} 51 | }; 52 | 53 | typedef check> right_angle_brackets; 54 | 55 | int a; 56 | decltype(a) b; 57 | 58 | typedef check check_type; 59 | check_type c; 60 | check_type&& cr = static_cast(c); 61 | 62 | auto d = a; 63 | auto l = [](){}; 64 | // Prevent Clang error: unused variable 'l' [-Werror,-Wunused-variable] 65 | struct use_l { use_l() { l(); } }; 66 | 67 | // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae 68 | // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function because of this 69 | namespace test_template_alias_sfinae { 70 | struct foo {}; 71 | 72 | template 73 | using member = typename T::member_type; 74 | 75 | template 76 | void func(...) {} 77 | 78 | template 79 | void func(member*) {} 80 | 81 | void test(); 82 | 83 | void test() { 84 | func(0); 85 | } 86 | } 87 | ]]) 88 | 89 | AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [dnl 90 | m4_if([$1], [], [], 91 | [$1], [ext], [], 92 | [$1], [noext], [], 93 | [m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_11])])dnl 94 | m4_if([$2], [], [ax_cxx_compile_cxx11_required=true], 95 | [$2], [mandatory], [ax_cxx_compile_cxx11_required=true], 96 | [$2], [optional], [ax_cxx_compile_cxx11_required=false], 97 | [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX_11])]) 98 | AC_LANG_PUSH([C++])dnl 99 | ac_success=no 100 | AC_CACHE_CHECK(whether $CXX supports C++11 features by default, 101 | ax_cv_cxx_compile_cxx11, 102 | [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], 103 | [ax_cv_cxx_compile_cxx11=yes], 104 | [ax_cv_cxx_compile_cxx11=no])]) 105 | if test x$ax_cv_cxx_compile_cxx11 = xyes; then 106 | ac_success=yes 107 | fi 108 | 109 | m4_if([$1], [noext], [], [dnl 110 | if test x$ac_success = xno; then 111 | for switch in -std=gnu++11 -std=gnu++0x; do 112 | cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) 113 | AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, 114 | $cachevar, 115 | [ac_save_CXXFLAGS="$CXXFLAGS" 116 | CXXFLAGS="$CXXFLAGS $switch" 117 | AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], 118 | [eval $cachevar=yes], 119 | [eval $cachevar=no]) 120 | CXXFLAGS="$ac_save_CXXFLAGS"]) 121 | if eval test x\$$cachevar = xyes; then 122 | CXXFLAGS="$CXXFLAGS $switch" 123 | ac_success=yes 124 | break 125 | fi 126 | done 127 | fi]) 128 | 129 | m4_if([$1], [ext], [], [dnl 130 | if test x$ac_success = xno; then 131 | for switch in -std=c++11 -std=c++0x; do 132 | cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) 133 | AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, 134 | $cachevar, 135 | [ac_save_CXXFLAGS="$CXXFLAGS" 136 | CXXFLAGS="$CXXFLAGS $switch" 137 | AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], 138 | [eval $cachevar=yes], 139 | [eval $cachevar=no]) 140 | CXXFLAGS="$ac_save_CXXFLAGS"]) 141 | if eval test x\$$cachevar = xyes; then 142 | CXXFLAGS="$CXXFLAGS $switch" 143 | ac_success=yes 144 | break 145 | fi 146 | done 147 | fi]) 148 | AC_LANG_POP([C++]) 149 | if test x$ax_cxx_compile_cxx11_required = xtrue; then 150 | if test x$ac_success = xno; then 151 | AC_MSG_ERROR([*** A compiler with support for C++11 language features is required.]) 152 | fi 153 | else 154 | if test x$ac_success = xno; then 155 | HAVE_CXX11=0 156 | AC_MSG_NOTICE([No compiler with C++11 support was found]) 157 | else 158 | HAVE_CXX11=1 159 | AC_DEFINE(HAVE_CXX11,1, 160 | [define if the compiler supports basic C++11 syntax]) 161 | fi 162 | 163 | AC_SUBST(HAVE_CXX11) 164 | fi 165 | ]) 166 | -------------------------------------------------------------------------------- /m4/metronome_with_eigen.m4: -------------------------------------------------------------------------------- 1 | AC_DEFUN([METRONOME_WITH_EIGEN],[ 2 | EIGEN3_CFLAGS= 3 | AC_ARG_WITH([eigen], 4 | [AS_HELP_STRING([--with-eigen=DIR], [prefix for Eigen @<:@guess@:>@])]) 5 | 6 | if test x"$with_eigen" != x; then 7 | AC_MSG_CHECKING([for Eigen/SVD in $with_eigen]) 8 | if test -f $with_eigen/Eigen/SVD; then 9 | EIGEN3_CFLAGS='-I$with_eigen' 10 | AC_MSG_RESULT([yes]) 11 | else 12 | AC_MSG_ERROR([please install eigen3 and set --with-eigen correctly]) 13 | fi 14 | else 15 | PKG_CHECK_MODULES([EIGEN3], [eigen3], [], []) 16 | fi 17 | 18 | AC_SUBST([EIGEN3_CFLAGS]) 19 | ]) 20 | -------------------------------------------------------------------------------- /m4/pdns_check_os.m4: -------------------------------------------------------------------------------- 1 | AC_DEFUN([PDNS_CHECK_OS],[ 2 | THREADFLAGS="" 3 | 4 | case "$host_os" in 5 | solaris2.10) 6 | LIBS="-lposix4 -lpthread $LIBS" 7 | CXXFLAGS="-D_REENTRANT $CXXFLAGS" 8 | have_solaris="yes" 9 | ;; 10 | solaris2.8 | solaris2.9 ) 11 | AC_DEFINE(NEED_POSIX_TYPEDEF,,[If POSIX typedefs need to be defined]) 12 | AC_DEFINE(NEED_INET_NTOP_PROTO,,[If your OS is so broken that it needs an additional prototype]) 13 | LIBS="-lposix4 -lpthread $LIBS" 14 | CXXFLAGS="-D_REENTRANT $CXXFLAGS" 15 | have_solaris="yes" 16 | ;; 17 | linux*) 18 | THREADFLAGS="-pthread" 19 | have_linux="yes" 20 | ;; 21 | darwin*) 22 | CXXFLAGS="-D__APPLE_USE_RFC_3542 -D_XOPEN_SOURCE $CXXFLAGS" 23 | ;; 24 | freebsd*) 25 | THREADFLAGS="-pthread" 26 | have_freebsd="yes" 27 | ;; 28 | *) 29 | LDFLAGS="-pthread $LDFLAGS" 30 | CXXFLAGS="-pthread $CXXFLAGS" 31 | ;; 32 | esac 33 | 34 | AM_CONDITIONAL([HAVE_FREEBSD], [test "x$have_freebsd" = "xyes"]) 35 | AM_CONDITIONAL([HAVE_LINUX], [test "x$have_linux" = "xyes"]) 36 | AM_CONDITIONAL([HAVE_SOLARIS], [test "x$have_solaris" = "xyes"]) 37 | 38 | AC_SUBST(THREADFLAGS) 39 | AC_SUBST([DYNLINKFLAGS], [-export-dynamic]) 40 | ]) 41 | -------------------------------------------------------------------------------- /m4/pdns_d_fortify_source.m4: -------------------------------------------------------------------------------- 1 | dnl 2 | dnl Check for support D_FORTIFY_SOURCE 3 | dnl 4 | dnl Copyright (C) 2013 Red Hat, Inc. 5 | dnl 6 | dnl This library is free software; you can redistribute it and/or 7 | dnl modify it under the terms of the GNU Lesser General Public 8 | dnl License as published by the Free Software Foundation; either 9 | dnl version 2.1 of the License, or (at your option) any later version. 10 | dnl 11 | dnl This library is distributed in the hope that it will be useful, 12 | dnl but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | dnl Lesser General Public License for more details. 15 | dnl 16 | dnl You should have received a copy of the GNU Lesser General Public 17 | dnl License along with this library. If not, see 18 | dnl . 19 | dnl 20 | 21 | AC_DEFUN([AC_CC_D_FORTIFY_SOURCE],[ 22 | OLD_CXXFLAGS="$CXXFLAGS" 23 | CXXFLAGS="-Wall -W -Werror $CXXFLAGS" 24 | gl_COMPILER_OPTION_IF([-D_FORTIFY_SOURCE=2], [ 25 | CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 $CFLAGS" 26 | CXXFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 $OLD_CXXFLAGS" 27 | ], [CXXFLAGS="$OLD_CXXFLAGS"], [AC_LANG_PROGRAM([[#include ]],[])]) 28 | ]) 29 | -------------------------------------------------------------------------------- /m4/pdns_enable_malloc_trace.m4: -------------------------------------------------------------------------------- 1 | AC_DEFUN([PDNS_ENABLE_MALLOC_TRACE], [ 2 | AC_MSG_CHECKING([whether to enable code malloc-trace]) 3 | AC_ARG_ENABLE([malloc-trace], 4 | AS_HELP_STRING([--enable-malloc-trace], 5 | [enable malloc-trace @<:@default=no@:>@]), 6 | [enable_malloc_trace=$enableval], 7 | [enable_malloc_trace=no] 8 | ) 9 | AC_MSG_RESULT([$enable_malloc_trace]) 10 | AM_CONDITIONAL([MALLOC_TRACE], [test "x$enable_malloc_trace" = "xyes"]) 11 | AS_IF([test "x$enable_malloc_trace" = "xyes"], 12 | AC_DEFINE([MALLOC_TRACE], [1], [Define to 1 if you want to benefit from malloc trace]) ) 13 | ]) 14 | -------------------------------------------------------------------------------- /m4/pdns_enable_sanitizers.m4: -------------------------------------------------------------------------------- 1 | AC_DEFUN([PDNS_ENABLE_SANITIZERS], [ 2 | PDNS_ENABLE_ASAN 3 | PDNS_ENABLE_MSAN 4 | PDNS_ENABLE_TSAN 5 | PDNS_ENABLE_LSAN 6 | PDNS_ENABLE_UBSAN 7 | 8 | AS_IF([test "x$enable_asan" != "xno" -a "x$enable_tsan" != "xno"],[ 9 | AC_MSG_ERROR([Address Sanitizer is not compatible with Thread Sanitizer]) 10 | ]) 11 | 12 | AS_IF([test "x$enable_msan" != "xno" -a "x$enable_asan" != "xno"],[ 13 | AC_MSG_ERROR([Memory Sanitizer is not compatible with Address Sanitizer]) 14 | ]) 15 | 16 | AS_IF([test "x$enable_msan" != "xno" -a "x$enable_lsan" != "xno"],[ 17 | AC_MSG_ERROR([Memory Sanitizer is not compatible with Leak Sanitizer]) 18 | ]) 19 | 20 | AS_IF([test "x$enable_msan" != "xno" -a "x$enable_tsan" != "xno"],[ 21 | AC_MSG_ERROR([Memory Sanitizer is not compatible with Thread Sanitizer]) 22 | ]) 23 | 24 | AS_IF([test "x$enable_asan" != "xno" -o "x$enable_tsan" != "xno" -o "x$enable_lsan" != "xno" -o "x$enable_ubsan" != "xno" -o "x$enable_msan" != "xno"], [ 25 | gl_WARN_ADD([-fno-omit-frame-pointer]) 26 | ]) 27 | ]) 28 | 29 | AC_DEFUN([PDNS_ENABLE_ASAN], [ 30 | AC_REQUIRE([gl_UNKNOWN_WARNINGS_ARE_ERRORS]) 31 | AC_MSG_CHECKING([whether to enable AddressSanitizer]) 32 | AC_ARG_ENABLE([asan], 33 | AS_HELP_STRING([--enable-asan], 34 | [enable AddressSanitizer @<:@default=no@:>@]), 35 | [enable_asan=$enableval], 36 | [enable_asan=no] 37 | ) 38 | AC_MSG_RESULT([$enable_asan]) 39 | 40 | AS_IF([test "x$enable_asan" != "xno"], [ 41 | gl_COMPILER_OPTION_IF([-fsanitize=address], 42 | [SANITIZER_FLAGS="-fsanitize=address $SANITIZER_FLAGS"], 43 | [AC_MSG_ERROR([Cannot enable AddressSanitizer])] 44 | ) 45 | ]) 46 | AC_SUBST([SANITIZER_FLAGS]) 47 | ]) 48 | 49 | AC_DEFUN([PDNS_ENABLE_TSAN], [ 50 | AC_REQUIRE([gl_UNKNOWN_WARNINGS_ARE_ERRORS]) 51 | AC_MSG_CHECKING([whether to enable ThreadSanitizer]) 52 | AC_ARG_ENABLE([tsan], 53 | AS_HELP_STRING([--enable-tsan], 54 | [enable ThreadSanitizer @<:@default=no@:>@]), 55 | [enable_tsan=$enableval], 56 | [enable_tsan=no] 57 | ) 58 | AC_MSG_RESULT([$enable_tsan]) 59 | 60 | AS_IF([test "x$enable_tsan" != "xno"], [ 61 | gl_COMPILER_OPTION_IF([-fsanitize=thread], 62 | [SANITIZER_FLAGS="-fsanitize=thread $SANITIZER_FLAGS"], 63 | [AC_MSG_ERROR([Cannot enable ThreadSanitizer])] 64 | ) 65 | ]) 66 | AC_SUBST([SANITIZER_FLAGS]) 67 | ]) 68 | 69 | AC_DEFUN([PDNS_ENABLE_LSAN], [ 70 | AC_REQUIRE([gl_UNKNOWN_WARNINGS_ARE_ERRORS]) 71 | AC_MSG_CHECKING([whether to enable LeakSanitizer]) 72 | AC_ARG_ENABLE([lsan], 73 | AS_HELP_STRING([--enable-lsan], 74 | [enable LeakSanitizer @<:@default=no@:>@]), 75 | [enable_lsan=$enableval], 76 | [enable_lsan=no] 77 | ) 78 | AC_MSG_RESULT([$enable_lsan]) 79 | 80 | AS_IF([test "x$enable_lsan" != "xno"], [ 81 | gl_COMPILER_OPTION_IF([-fsanitize=leak], 82 | [SANITIZER_FLAGS="-fsanitize=leak $SANITIZER_FLAGS"], 83 | [AC_MSG_ERROR([Cannot enable LeakSanitizer])] 84 | ) 85 | ]) 86 | AC_SUBST([SANITIZER_FLAGS]) 87 | ]) 88 | 89 | AC_DEFUN([PDNS_ENABLE_UBSAN], [ 90 | AC_REQUIRE([gl_UNKNOWN_WARNINGS_ARE_ERRORS]) 91 | AC_MSG_CHECKING([whether to enable Undefined Behaviour Sanitizer]) 92 | AC_ARG_ENABLE([ubsan], 93 | AS_HELP_STRING([--enable-ubsan], 94 | [enable Undefined Behaviour Sanitizer @<:@default=no@:>@]), 95 | [enable_ubsan=$enableval], 96 | [enable_ubsan=no] 97 | ) 98 | AC_MSG_RESULT([$enable_ubsan]) 99 | 100 | AS_IF([test "x$enable_ubsan" != "xno"], [ 101 | gl_COMPILER_OPTION_IF([-fsanitize=undefined], 102 | [SANITIZER_FLAGS="-fsanitize=undefined $SANITIZER_FLAGS"], 103 | [AC_MSG_ERROR([Cannot enable Undefined Behaviour Sanitizer])] 104 | ) 105 | ]) 106 | AC_SUBST([SANITIZER_FLAGS]) 107 | ]) 108 | 109 | AC_DEFUN([PDNS_ENABLE_MSAN], [ 110 | AC_REQUIRE([gl_UNKNOWN_WARNINGS_ARE_ERRORS]) 111 | AC_MSG_CHECKING([whether to enable MemorySanitizer]) 112 | AC_ARG_ENABLE([msan], 113 | AS_HELP_STRING([--enable-msan], 114 | [enable MemorySanitizer @<:@default=no@:>@]), 115 | [enable_msan=$enableval], 116 | [enable_msan=no] 117 | ) 118 | AC_MSG_RESULT([$enable_msan]) 119 | 120 | AS_IF([test "x$enable_msan" != "xno"], [ 121 | gl_COMPILER_OPTION_IF([-fsanitize=memory], 122 | [SANITIZER_FLAGS="-fsanitize=memory $SANITIZER_FLAGS"], 123 | [AC_MSG_ERROR([Cannot enable MemorySanitizer])] 124 | ) 125 | ]) 126 | AC_SUBST([SANITIZER_FLAGS]) 127 | ]) 128 | 129 | -------------------------------------------------------------------------------- /m4/pdns_param_ssp_buffer_size.m4: -------------------------------------------------------------------------------- 1 | dnl 2 | dnl Check for support for ssp parameter buffer size 3 | dnl 4 | dnl Copyright (C) 2013 Red Hat, Inc. 5 | dnl 6 | dnl This library is free software; you can redistribute it and/or 7 | dnl modify it under the terms of the GNU Lesser General Public 8 | dnl License as published by the Free Software Foundation; either 9 | dnl version 2.1 of the License, or (at your option) any later version. 10 | dnl 11 | dnl This library is distributed in the hope that it will be useful, 12 | dnl but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | dnl Lesser General Public License for more details. 15 | dnl 16 | dnl You should have received a copy of the GNU Lesser General Public 17 | dnl License along with this library. If not, see 18 | dnl . 19 | dnl 20 | 21 | AC_DEFUN([AC_CC_PARAM_SSP_BUFFER_SIZE],[ 22 | gl_COMPILER_OPTION_IF([--param ssp-buffer-size=$1], [ 23 | CFLAGS="--param ssp-buffer-size=$1 $CFLAGS" 24 | CXXFLAGS="--param ssp-buffer-size=$1 $CXXFLAGS" 25 | ]) 26 | ]) 27 | -------------------------------------------------------------------------------- /m4/pdns_pie.m4: -------------------------------------------------------------------------------- 1 | dnl 2 | dnl Check for support for position independent executables 3 | dnl 4 | dnl Copyright (C) 2013 Red Hat, Inc. 5 | dnl 6 | dnl This library is free software; you can redistribute it and/or 7 | dnl modify it under the terms of the GNU Lesser General Public 8 | dnl License as published by the Free Software Foundation; either 9 | dnl version 2.1 of the License, or (at your option) any later version. 10 | dnl 11 | dnl This library is distributed in the hope that it will be useful, 12 | dnl but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | dnl Lesser General Public License for more details. 15 | dnl 16 | dnl You should have received a copy of the GNU Lesser General Public 17 | dnl License along with this library. If not, see 18 | dnl . 19 | dnl 20 | 21 | AC_DEFUN([AC_CC_PIE],[ 22 | AC_REQUIRE([gl_UNKNOWN_WARNINGS_ARE_ERRORS]) 23 | PIE_CFLAGS= 24 | PIE_LDFLAGS= 25 | OLD_CXXFLAGS=$CXXFLAGS 26 | case "$host" in 27 | *-*-mingw* | *-*-msvc* | *-*-cygwin* ) 28 | ;; dnl All code is position independent on Win32 target 29 | *) 30 | CXXFLAGS="-fPIE -DPIE" 31 | gl_COMPILER_OPTION_IF([-pie], [ 32 | PIE_CFLAGS="-fPIE -DPIE" 33 | PIE_LDFLAGS="-pie" 34 | ], [ 35 | dnl some versions of clang require -Wl,-pie instead of -pie 36 | gl_COMPILER_OPTION_IF([[-Wl,-pie]], [ 37 | PIE_CFLAGS="-fPIE -DPIE" 38 | PIE_LDFLAGS="-Wl,-pie" 39 | ], [], 40 | [AC_LANG_PROGRAM([[ 41 | #include 42 | __thread unsigned int t_id; 43 | ]], [[t_id = 1;]])] 44 | ) 45 | ], 46 | [AC_LANG_PROGRAM([[ 47 | #include 48 | __thread unsigned int t_id; 49 | ]], [[t_id = 1;]])] 50 | ) 51 | esac 52 | CXXFLAGS=$OLD_CXXFLAGS 53 | AC_SUBST([PIE_CFLAGS]) 54 | AC_SUBST([PIE_LDFLAGS]) 55 | ]) 56 | -------------------------------------------------------------------------------- /m4/pdns_relro.m4: -------------------------------------------------------------------------------- 1 | dnl 2 | dnl Check for -z now and -z relro linker flags 3 | dnl 4 | dnl Copyright (C) 2013 Red Hat, Inc. 5 | dnl 6 | dnl This library is free software; you can redistribute it and/or 7 | dnl modify it under the terms of the GNU Lesser General Public 8 | dnl License as published by the Free Software Foundation; either 9 | dnl version 2.1 of the License, or (at your option) any later version. 10 | dnl 11 | dnl This library is distributed in the hope that it will be useful, 12 | dnl but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | dnl Lesser General Public License for more details. 15 | dnl 16 | dnl You should have received a copy of the GNU Lesser General Public 17 | dnl License along with this library. If not, see 18 | dnl . 19 | dnl 20 | 21 | AC_DEFUN([AC_LD_RELRO],[ 22 | AC_MSG_CHECKING([for how to force completely read-only GOT table]) 23 | 24 | RELRO_LDFLAGS= 25 | ld_help=`$CXX -Wl,-help 2>&1` 26 | case $ld_help in 27 | *"-z relro"*) RELRO_LDFLAGS="-Wl,-z -Wl,relro" ;; 28 | esac 29 | case $ld_help in 30 | *"-z now"*) RELRO_LDFLAGS="$RELRO_LDFLAGS -Wl,-z -Wl,now" ;; 31 | esac 32 | AC_SUBST([RELRO_LDFLAGS]) 33 | AS_IF([test "x$RELRO_LDFLAGS" != "x"], 34 | [AC_MSG_RESULT([$RELRO_LDFLAGS])], 35 | [AC_MSG_RESULT([unknown])] 36 | ) 37 | ]) 38 | -------------------------------------------------------------------------------- /m4/pdns_stack_protector.m4: -------------------------------------------------------------------------------- 1 | dnl 2 | dnl Check for support for enabling stack protector 3 | dnl 4 | dnl Copyright (C) 2013 Red Hat, Inc. 5 | dnl 6 | dnl This library is free software; you can redistribute it and/or 7 | dnl modify it under the terms of the GNU Lesser General Public 8 | dnl License as published by the Free Software Foundation; either 9 | dnl version 2.1 of the License, or (at your option) any later version. 10 | dnl 11 | dnl This library is distributed in the hope that it will be useful, 12 | dnl but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | dnl Lesser General Public License for more details. 15 | dnl 16 | dnl You should have received a copy of the GNU Lesser General Public 17 | dnl License along with this library. If not, see 18 | dnl . 19 | dnl 20 | 21 | AC_DEFUN([AC_CC_STACK_PROTECTOR],[ 22 | gl_COMPILER_OPTION_IF([-fstack-protector], [ 23 | CFLAGS="-fstack-protector $CFLAGS" 24 | CXXFLAGS="-fstack-protector $CXXFLAGS" 25 | ]) 26 | ]) 27 | -------------------------------------------------------------------------------- /m4/systemd.m4: -------------------------------------------------------------------------------- 1 | # systemd.m4 - Macros to check for and enable systemd -*- Autoconf -*- 2 | # 3 | # Copyright (C) 2014 Luis R. Rodriguez 4 | # Copyright (C) 2016 Pieter Lexis 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, but 12 | # WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | # General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | 20 | #serial 2 21 | 22 | dnl Some optional path options 23 | AC_DEFUN([AX_SYSTEMD_OPTIONS], [ 24 | AC_ARG_WITH(systemd, [ --with-systemd set directory for systemd service files], 25 | SYSTEMD_DIR="$withval", SYSTEMD_DIR="") 26 | AC_SUBST(SYSTEMD_DIR) 27 | 28 | AC_ARG_WITH(systemd, [ --with-systemd-modules-load set directory for systemd modules load files], 29 | SYSTEMD_MODULES_LOAD="$withval", SYSTEMD_MODULES_LOAD="") 30 | AC_SUBST(SYSTEMD_MODULES_LOAD) 31 | ]) 32 | 33 | AC_DEFUN([AX_ENABLE_SYSTEMD_OPTS], [ 34 | AX_ARG_DEFAULT_ENABLE([systemd], [Disable systemd support]) 35 | AX_SYSTEMD_OPTIONS() 36 | ]) 37 | 38 | AC_DEFUN([AX_ALLOW_SYSTEMD_OPTS], [ 39 | AX_ARG_DEFAULT_DISABLE([systemd], [Enable systemd support]) 40 | AX_SYSTEMD_OPTIONS() 41 | ]) 42 | 43 | AC_DEFUN([AX_CHECK_SYSTEMD_LIBS], [ 44 | AC_REQUIRE([AX_CHECK_SYSTEMD_DETECT_AND_ENABLE]) 45 | AS_IF([test "x$libsystemd" = x], [ 46 | AC_MSG_ERROR([Unable to find a suitable libsystemd library]) 47 | ]) 48 | 49 | PKG_CHECK_MODULES([SYSTEMD], [$libsystemd_daemon]) 50 | dnl pkg-config older than 0.24 does not set these for 51 | dnl PKG_CHECK_MODULES() worth also noting is that as of version 208 52 | dnl of systemd pkg-config --cflags currently yields no extra flags yet. 53 | AC_SUBST([SYSTEMD_CFLAGS]) 54 | AC_SUBST([SYSTEMD_LIBS]) 55 | 56 | AS_IF([test "x$SYSTEMD_DIR" = x], [ 57 | dnl In order to use the line below we need to fix upstream systemd 58 | dnl to properly ${prefix} for child variables in 59 | dnl src/core/systemd.pc.in but this is a bit complex at the 60 | dnl moment as they depend on another rootprefix, which can vary 61 | dnl from prefix in practice. We provide our own definition as we 62 | dnl *know* where systemd will dump this to, but this does limit 63 | dnl us to stick to a non custom systemdsystemunitdir, dnl to work 64 | dnl around this we provide the additional configure option 65 | dnl --with-systemd where you can specify the directory for the unit 66 | dnl files. It would also be best to just extend the upstream 67 | dnl pkg-config pkg.m4 with an AC_DEFUN() to do this neatly. 68 | dnl SYSTEMD_DIR="`$PKG_CONFIG --define-variable=prefix=$PREFIX --variable=systemdsystemunitdir systemd`" 69 | SYSTEMD_DIR="\$(prefix)/lib/systemd/system/" 70 | ], []) 71 | 72 | AS_IF([test "x$SYSTEMD_DIR" = x], [ 73 | AC_MSG_ERROR([SYSTEMD_DIR is unset]) 74 | ], []) 75 | 76 | dnl There is no variable for this yet for some reason 77 | AS_IF([test "x$SYSTEMD_MODULES_LOAD" = x], [ 78 | SYSTEMD_MODULES_LOAD="\$(prefix)/lib/modules-load.d/" 79 | ], []) 80 | 81 | AS_IF([test "x$SYSTEMD_MODULES_LOAD" = x], [ 82 | AC_MSG_ERROR([SYSTEMD_MODULES_LOAD is unset]) 83 | ], []) 84 | ]) 85 | 86 | AC_DEFUN([AX_CHECK_SYSTEMD], [ 87 | dnl Respect user override to disable 88 | AS_IF([test "x$enable_systemd" != "xno"], [ 89 | AS_IF([test "x$systemd" = "xy" ], [ 90 | AC_DEFINE([HAVE_SYSTEMD], [1], [Systemd available and enabled]) 91 | systemd=y 92 | AX_CHECK_SYSTEMD_LIBS() 93 | ],[systemd=n]) 94 | ],[systemd=n]) 95 | ]) 96 | 97 | AC_DEFUN([AX_CHECK_SYSTEMD_DETECT_AND_ENABLE], [ 98 | AC_CHECK_HEADER([systemd/sd-daemon.h], [ 99 | for libname in systemd-daemon systemd; do 100 | AC_CHECK_LIB([$libname], [sd_listen_fds], [ 101 | libsystemd_daemon="lib$libname" 102 | systemd=y 103 | libsystemd=y 104 | ]) 105 | done 106 | ]) 107 | ]) 108 | 109 | dnl Enables systemd by default and requires a --disable-systemd option flag 110 | dnl to configure if you want to disable. 111 | AC_DEFUN([AX_ENABLE_SYSTEMD], [ 112 | AX_ENABLE_SYSTEMD_OPTS() 113 | AX_CHECK_SYSTEMD() 114 | ]) 115 | 116 | dnl Systemd will be disabled by default and requires you to run configure with 117 | dnl --enable-systemd to look for and enable systemd. 118 | AC_DEFUN([AX_ALLOW_SYSTEMD], [ 119 | AX_ALLOW_SYSTEMD_OPTS() 120 | AX_CHECK_SYSTEMD() 121 | ]) 122 | 123 | dnl Systemd will be disabled by default but if your build system is detected 124 | dnl to have systemd build libraries it will be enabled. You can always force 125 | dnl disable with --disable-systemd 126 | AC_DEFUN([AX_AVAILABLE_SYSTEMD], [ 127 | AX_ALLOW_SYSTEMD_OPTS() 128 | AX_CHECK_SYSTEMD_DETECT_AND_ENABLE() 129 | AX_CHECK_SYSTEMD() 130 | ]) 131 | -------------------------------------------------------------------------------- /m4/warnings.m4: -------------------------------------------------------------------------------- 1 | # warnings.m4 serial 11 2 | dnl Copyright (C) 2008-2015 Free Software Foundation, Inc. 3 | dnl This file is free software; the Free Software Foundation 4 | dnl gives unlimited permission to copy and/or distribute it, 5 | dnl with or without modifications, as long as this notice is preserved. 6 | 7 | dnl From Simon Josefsson 8 | 9 | # gl_AS_VAR_APPEND(VAR, VALUE) 10 | # ---------------------------- 11 | # Provide the functionality of AS_VAR_APPEND if Autoconf does not have it. 12 | m4_ifdef([AS_VAR_APPEND], 13 | [m4_copy([AS_VAR_APPEND], [gl_AS_VAR_APPEND])], 14 | [m4_define([gl_AS_VAR_APPEND], 15 | [AS_VAR_SET([$1], [AS_VAR_GET([$1])$2])])]) 16 | 17 | 18 | # gl_COMPILER_OPTION_IF(OPTION, [IF-SUPPORTED], [IF-NOT-SUPPORTED], 19 | # [PROGRAM = AC_LANG_PROGRAM()]) 20 | # ----------------------------------------------------------------- 21 | # Check if the compiler supports OPTION when compiling PROGRAM. 22 | # 23 | # FIXME: gl_Warn must be used unquoted until we can assume Autoconf 24 | # 2.64 or newer. 25 | AC_DEFUN([gl_COMPILER_OPTION_IF], 26 | [AS_VAR_PUSHDEF([gl_Warn], [gl_cv_warn_[]_AC_LANG_ABBREV[]_$1])dnl 27 | AS_VAR_PUSHDEF([gl_Flags], [_AC_LANG_PREFIX[]FLAGS])dnl 28 | AS_LITERAL_IF([$1], 29 | [m4_pushdef([gl_Positive], m4_bpatsubst([$1], [^-Wno-], [-W]))], 30 | [gl_positive="$1" 31 | case $gl_positive in 32 | -Wno-*) gl_positive=-W`expr "X$gl_positive" : 'X-Wno-\(.*\)'` ;; 33 | esac 34 | m4_pushdef([gl_Positive], [$gl_positive])])dnl 35 | AC_CACHE_CHECK([whether _AC_LANG compiler handles $1], m4_defn([gl_Warn]), [ 36 | gl_save_compiler_FLAGS="$gl_Flags" 37 | gl_AS_VAR_APPEND(m4_defn([gl_Flags]), 38 | [" $gl_unknown_warnings_are_errors ]m4_defn([gl_Positive])["]) 39 | AC_LINK_IFELSE([m4_default([$4], [AC_LANG_PROGRAM([])])], 40 | [AS_VAR_SET(gl_Warn, [yes])], 41 | [AS_VAR_SET(gl_Warn, [no])]) 42 | gl_Flags="$gl_save_compiler_FLAGS" 43 | ]) 44 | AS_VAR_IF(gl_Warn, [yes], [$2], [$3]) 45 | m4_popdef([gl_Positive])dnl 46 | AS_VAR_POPDEF([gl_Flags])dnl 47 | AS_VAR_POPDEF([gl_Warn])dnl 48 | ]) 49 | 50 | # gl_UNKNOWN_WARNINGS_ARE_ERRORS 51 | # ------------------------------ 52 | # Clang doesn't complain about unknown warning options unless one also 53 | # specifies -Wunknown-warning-option -Werror. Detect this. 54 | AC_DEFUN([gl_UNKNOWN_WARNINGS_ARE_ERRORS], 55 | [gl_COMPILER_OPTION_IF([-Werror -Wunknown-warning-option], 56 | [gl_unknown_warnings_are_errors='-Wunknown-warning-option -Werror'], 57 | [gl_unknown_warnings_are_errors=])]) 58 | 59 | # gl_WARN_ADD(OPTION, [VARIABLE = WARN_CFLAGS], 60 | # [PROGRAM = AC_LANG_PROGRAM()]) 61 | # --------------------------------------------- 62 | # Adds parameter to WARN_CFLAGS if the compiler supports it when 63 | # compiling PROGRAM. For example, gl_WARN_ADD([-Wparentheses]). 64 | # 65 | # If VARIABLE is a variable name, AC_SUBST it. 66 | AC_DEFUN([gl_WARN_ADD], 67 | [AC_REQUIRE([gl_UNKNOWN_WARNINGS_ARE_ERRORS]) 68 | gl_COMPILER_OPTION_IF([$1], 69 | [gl_AS_VAR_APPEND(m4_if([$2], [], [[WARN_CFLAGS]], [[$2]]), [" $1"])], 70 | [], 71 | [$3]) 72 | m4_ifval([$2], 73 | [AS_LITERAL_IF([$2], [AC_SUBST([$2])])], 74 | [AC_SUBST([WARN_CFLAGS])])dnl 75 | ]) 76 | 77 | # Local Variables: 78 | # mode: autoconf 79 | # End: 80 | -------------------------------------------------------------------------------- /mdump.cc: -------------------------------------------------------------------------------- 1 | #include "statstorage.hh" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using std::cin; 10 | using std::cerr; 11 | using std::cout; 12 | using std::endl; 13 | 14 | 15 | // syntax: mmanage 16 | int main(int argc, char** argv) 17 | { 18 | try 19 | { 20 | StatStorage ss("./stats"); 21 | auto metrics=ss.getMetrics(); 22 | cout<<"Have "< 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "dolog.hh" 10 | using std::string; 11 | 12 | inline void unixDie(const string &why) 13 | { 14 | throw std::runtime_error(why+": "+strerror(errno)); 15 | } 16 | 17 | template 18 | void 19 | stringtok (Container &container, string const &in, 20 | const char * const delimiters = " \t\n") 21 | { 22 | const string::size_type len = in.length(); 23 | string::size_type i = 0; 24 | 25 | while (i/dev/null 2>&1 15 | end script 16 | -------------------------------------------------------------------------------- /metronome.cc: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_CONFIG_H 2 | #include "config.h" 3 | #endif 4 | #include "yahttp.hpp" 5 | #include "iputils.hh" 6 | #include "statstorage.hh" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "interpolate.hh" 16 | #include 17 | #ifdef HAVE_SYSTEMD 18 | #include 19 | #endif 20 | 21 | namespace po = boost::program_options; 22 | po::variables_map g_vm; 23 | bool g_verbose; 24 | bool g_console = false; 25 | bool g_disableSyslog; 26 | bool g_http10; 27 | using namespace std; 28 | 29 | static void startCarbonThread(int sd, ComboAddress remote) 30 | { 31 | Socket sock(sd); 32 | sd = -1; 33 | try 34 | { 35 | StatStorage ss(g_vm["stats-directory"].as()); 36 | infolog("Got carbon connection from %s", remote.toStringWithPort()); 37 | string line; 38 | 39 | int numStored=0; 40 | while(sockGetLine(sock.getHandle(), line, g_vm["carbon-timeout"].as())) { 41 | // format: name value timestamp 42 | // cout<<"Got: "< parts; 44 | stringtok(parts, line, " \t\r\n"); 45 | if(parts.size()!=3) { 46 | writen(sock.getHandle(), "ERR Wrong number of parts to line"); 47 | break; 48 | } 49 | ss.store(parts[0], atoi(parts[2].c_str()), atof(parts[1].c_str())); 50 | numStored++; 51 | } 52 | infolog("Closing connection with %s, stored %d data", remote.toStringWithPort(), numStored); 53 | } 54 | catch (const exception& e) 55 | { 56 | errlog("Exception: %s", e.what()); 57 | try { 58 | writen(sock.getHandle(), string("Error: ")+e.what()+"\n"); 59 | } 60 | catch (...) 61 | { 62 | } 63 | } 64 | } 65 | 66 | #if 0 67 | static void dumpRequest(const YaHTTP::Request& req) 68 | { 69 | cout<<"Headers: \n"; 70 | for(auto h : req.headers) { 71 | cout << h.first << " -> "< "< smooth(const vector& vals, double timestamp, int window) 103 | { 104 | auto from = upper_bound(vals.begin(), vals.end(), timestamp-window/2.0); 105 | auto to = upper_bound(vals.begin(), vals.end(), timestamp+window/2.0); 106 | 107 | // cout <<"Initial find for timestamp "<timestamp<timestamp<timestamp<value, 0}; 131 | } 132 | 133 | vector id; 134 | id.reserve(to-from); 135 | bool hadReset=false; 136 | for(auto iter = from ; iter != to; ++iter) { 137 | // cout << '\t'<< iter->timestamp - timestamp<timestamp, iter->value}); 139 | if(iter != from && iter->value < prev(iter)->value) 140 | hadReset=true; 141 | } 142 | if(hadReset) { 143 | auto ret = interpolate(id, 3, timestamp); 144 | return make_pair(ret.first > 0 ? ret.first : 0, 0); 145 | } 146 | return interpolate(id, 3, timestamp); 147 | } 148 | 149 | static void startWebserverThread(int sd, const ComboAddress remote) 150 | { 151 | try 152 | { 153 | Socket sock(sd); 154 | sd = -1; 155 | infolog("Got web connection from %s", remote.toStringWithPort()); 156 | 157 | for(int numrequests=0;;++numrequests) { 158 | string input, line; 159 | while (sockGetLine(sock.getHandle(), line, g_vm["webserver-timeout"].as())) { 160 | 161 | input.append(line); 162 | if(line.empty() || line=="\n" || line=="\r\n") // XXX NO 163 | goto ok; 164 | } 165 | if(input.size()) { 166 | warnlog("Did not receive full request, got %ld bytes", input.size()); 167 | } 168 | else { 169 | infolog("EOF from %s after %d requests", remote.toStringWithPort(), numrequests); 170 | } 171 | return; 172 | ok:; 173 | YaHTTP::Request req; 174 | istringstream str(input); 175 | 176 | str >> req; 177 | YaHTTP::Response resp(req); 178 | ostringstream body; 179 | // dumpRequest(req); 180 | resp.status=200; 181 | if(req.getvars["do"]=="store") { 182 | StatStorage ss(g_vm["stats-directory"].as()); 183 | ss.store(req.getvars["name"], atoi(req.getvars["timestamp"].c_str()), 184 | atof(req.getvars["value"].c_str())); 185 | } 186 | else if(req.getvars["do"]=="get-metrics") { 187 | StatStorage ss(g_vm["stats-directory"].as()); 188 | resp.headers["Content-Type"]= "application/json"; 189 | resp.headers["Access-Control-Allow-Origin"]= "*"; 190 | if(!req.getvars["callback"].empty()) 191 | body<()); 205 | auto vals = ss.retrieve(req.getvars["name"]); 206 | 207 | body.setf(std::ios::fixed); 208 | for(const auto& v: vals) { 209 | auto s = smooth(vals, v.timestamp, 60); 210 | body<()); 216 | vector names; 217 | stringtok(names, req.getvars["name"], ","); 218 | resp.headers["Content-Type"]= "application/json"; 219 | resp.headers["Access-Control-Allow-Origin"]= "*"; 220 | 221 | body.setf(std::ios::fixed); 222 | 223 | double begin = atoi(req.getvars["begin"].c_str()); 224 | double end = atoi(req.getvars["end"].c_str()); 225 | int datapoints = atoi(req.getvars["datapoints"].c_str()); 226 | if(!datapoints) 227 | datapoints=100; 228 | if(!req.getvars["callback"].empty()) 229 | body< > derivative; 233 | for(const auto& name : names) { 234 | // little bit of margin so interpolation has chance to work 235 | auto vals = ss.retrieve(name, begin - (end-begin)/20.0, end + (end-begin)/20.0); 236 | if(!first) 237 | body<<','; 238 | first=false; 239 | 240 | body<< '"' << name << "\": ["; 241 | int count=0; 242 | vector derived; 243 | 244 | double step = (end-begin)/datapoints; 245 | //cout<<"step: "<= vals.rbegin()->timestamp && t - vals.rbegin()->timestamp < 60 && !derived.empty()) { 254 | derived.push_back({(uint32_t)t, derived.rbegin()->value}); 255 | } 256 | else 257 | derived.push_back({(uint32_t)t, inst.second > 0 ? (float)inst.second : 0}); 258 | count++; 259 | } 260 | body<<"]"; 261 | derivative[name]=derived; 262 | } 263 | body<<"}, \"derivative\": { "; 264 | first=true; 265 | for(const auto& deriv: derivative) { 266 | if(!first) 267 | body<<','; 268 | first=false; 269 | body<< '"' << deriv.first << "\": ["; 270 | int count=0; 271 | for(auto iter = deriv.second.begin(); iter !=deriv.second.end(); ++iter) { 272 | if(count) 273 | body<<','; 274 | body<<"["<timestamp<<','<value<<']'; 275 | count++; 276 | } 277 | body<<"]"; 278 | } 279 | body <<"}}"; 280 | if(!req.getvars["callback"].empty()) 281 | body <<");"; 282 | } 283 | else { 284 | resp.status=404; 285 | body<<"404 File not found"<(resp.body.length()); 289 | if(g_http10) 290 | resp.headers["Connection"]="Close"; 291 | else 292 | resp.headers["Connection"]="Keep-Alive"; 293 | ostringstream ostr; 294 | ostr << resp; 295 | 296 | writen(sock.getHandle(), ostr.str()); 297 | if(g_http10) 298 | break; 299 | } 300 | } 301 | catch(exception& e) { 302 | errlog("Web connection thread for %s terminated because of error: %s", remote.toStringWithPort(), e.what()); 303 | } 304 | } 305 | 306 | static void webServerThread(int sock, const ComboAddress& local) 307 | { 308 | try 309 | { 310 | for(;;) { 311 | ComboAddress remote=local; // sets the family flag right 312 | int client=SAccept(sock, remote); 313 | if(client >= 0) { 314 | thread t1(startWebserverThread, client, remote); 315 | t1.detach(); 316 | } 317 | else 318 | errlog("Error from accept: %s", strerror(errno)); 319 | } 320 | } 321 | catch(...) 322 | { 323 | errlog("Webserver thread died because of exception"); 324 | } 325 | } 326 | 327 | static void launchWebserver(int s, const ComboAddress& local) 328 | { 329 | thread t1(webServerThread, s, local); 330 | t1.detach(); 331 | } 332 | 333 | static void processCommandLine(int argc, char **argv) 334 | { 335 | po::options_description desc("Allowed options"); 336 | desc.add_options() 337 | ("help,h", "produce help message") 338 | ("carbon-address", po::value()->default_value("[::]:2003"), "Accept carbon data on this address") 339 | ("carbon-timeout", po::value()->default_value(5), "Maximum time in seconds to send a carbon line") 340 | ("webserver-address", po::value()->default_value("[::]:8000"), "Provide HTTP service on this address") 341 | ("webserver-timeout", po::value()->default_value(5), "Maximum time in seconds to send an HTTP request line") 342 | ("http1.0", "If set, use http 1.0 semantics for lighttpd proxy") 343 | ("quiet", po::value()->default_value(true), "don't be too noisy") 344 | ("daemon", po::value()->default_value(true), "run in background") 345 | ("disable-syslog", "don't log to syslog") 346 | ("stats-directory", po::value()->default_value("./stats"), "Store/access statistics from this directory"); 347 | 348 | try { 349 | po::store(po::command_line_parser(argc, argv).options(desc).run(), g_vm); 350 | po::notify(g_vm); 351 | } 352 | catch(std::exception& e) { 353 | cerr<<"Error parsing options: "<(); 394 | 395 | ComboAddress carbonLocal{g_vm["carbon-address"].as(), 2003}; 396 | int s = makeAndBindSocket(carbonLocal, "carbon"); 397 | ComboAddress wsLocal{g_vm["webserver-address"].as(), 8000}; 398 | int ws = makeAndBindSocket(wsLocal, "webserver"); 399 | 400 | if(g_vm["daemon"].as()) { 401 | daemonize(); 402 | warnlog("daemonizing as %d", getpid()); 403 | g_console=false; 404 | } 405 | else { 406 | infolog("Running in the foreground"); 407 | } 408 | 409 | launchWebserver(ws, wsLocal); 410 | 411 | #ifdef HAVE_SYSTEMD 412 | sd_notify(0, "READY=1"); 413 | #endif 414 | 415 | int client; 416 | ComboAddress remote=carbonLocal; 417 | for(;;) { 418 | client=SAccept(s, remote); 419 | if(client >= 0) { 420 | thread t1(startCarbonThread, client, remote); 421 | t1.detach(); 422 | } 423 | } 424 | } 425 | catch(exception& e) { 426 | errlog("Fatal error: %s", e.what()); 427 | exit(EXIT_FAILURE); 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /metronome.service.in: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Metronome 3 | Documentation=https://github.com/ahupowerdns/metronome/ 4 | After=network-online.target 5 | 6 | [Service] 7 | Type=notify 8 | ExecStart=@bindir@/metronome --daemon 0 \ 9 | --carbon-address '[::]:2003' \ 10 | --webserver-address '[::]:8000' \ 11 | --stats-directory /var/lib/metronome 12 | Restart=on-failure 13 | User=metronome 14 | Group=metronome 15 | PrivateTmp=true 16 | ProtectSystem=full 17 | NoNewPrivileges=true 18 | PrivateDevices=true 19 | CapabilityBoundingSet=CAP_DAC_OVERRIDE CAP_IPC_LOCK CAP_KILL CAP_NET_BIND_SERVICE 20 | 21 | [Install] 22 | WantedBy=multi-user.target 23 | -------------------------------------------------------------------------------- /mmanage.cc: -------------------------------------------------------------------------------- 1 | #include "statstorage.hh" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using std::cin; 10 | using std::cerr; 11 | using std::cout; 12 | using std::endl; 13 | 14 | 15 | // syntax: mmanage 16 | int main(int argc, char** argv) 17 | { 18 | try 19 | { 20 | StatStorage ss("./stats"); 21 | auto metrics=ss.getMetrics(); 22 | cout<<"Have "< 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using std::cin; 9 | using std::cerr; 10 | using std::endl; 11 | 12 | 13 | // syntax: msubmit address 14 | int main(int argc, char** argv) 15 | { 16 | try 17 | { 18 | if(argc!=2) { 19 | cerr<<"Syntax: (echo metric1 value1 ; echo metric2 value2 $(date +%s) ) | msubmit carbon-server-address"< parts; 33 | stringtok(parts, line, " \t\n"); 34 | if(parts.size() < 2) 35 | throw std::runtime_error("Line '"+line+"' contained less than 2 parts"); 36 | 37 | string output = parts[0]; 38 | output+=' '; 39 | output += parts[1]; 40 | output+=' '; 41 | if(parts.size() > 2) 42 | output += parts[2]; 43 | else 44 | output += boost::lexical_cast(now); 45 | output += "\r\n"; 46 | 47 | ret = writen(s, output.c_str(), output.size()); // format: name value timestamp 48 | if(!ret) 49 | throw std::runtime_error("Carbon server '"+remote.toStringWithPort()+"' closed socket while feeding data"); 50 | if(ret < 0) 51 | throw std::runtime_error("Error writing to Carbon server '"+remote.toStringWithPort()+"': "+strerror(errno)); 52 | } 53 | shutdown(s, SHUT_WR); 54 | char c; 55 | ret=read(s, &c, 1); 56 | if(ret < 0) 57 | throw std::runtime_error("Improper shutdown of Carbon session with '"+remote.toStringWithPort()+"': "+strerror(errno)); 58 | if(ret == 1) 59 | throw std::runtime_error("Improper shutdown of Carbon session with '"+remote.toStringWithPort()+"': they sent us data"); 60 | } 61 | catch(const std::exception& e) 62 | { 63 | cerr<<"Error: "< 3 | #include "metromisc.hh" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | using namespace std; 10 | 11 | StatStorage::StatStorage(const string& fname) : d_root(fname) 12 | { 13 | if(regcomp(&d_preg, "^[A-Za-z0-9_.-]+$", REG_NOSUB|REG_EXTENDED)) 14 | throw runtime_error("Regular expression did not compile"); 15 | } 16 | 17 | StatStorage::~StatStorage() 18 | { 19 | regfree(&d_preg); 20 | } 21 | 22 | unsigned int StatStorage::getWeekNum(uint32_t t) 23 | { 24 | return t/(7*86400); 25 | } 26 | 27 | string StatStorage::makeFilename(const string& name, uint32_t timestamp) 28 | { 29 | return d_root+"/"+name+"."+to_string(getWeekNum(timestamp)); 30 | } 31 | 32 | void StatStorage::store(const string& name, uint32_t timestamp, float value) 33 | { 34 | if(name.find("/") != string::npos) 35 | return; 36 | 37 | if (regexec(&d_preg, name.c_str(), 0, NULL, 0) == REG_NOMATCH) 38 | return; 39 | 40 | string fname=makeFilename(name, timestamp); 41 | auto fp = std::unique_ptr(fopen(fname.c_str(), "a"), fclose); 42 | if(!fp) 43 | unixDie("Opening '"+fname+"'"); 44 | StatStorage::Val val({timestamp, value}); 45 | if(fwrite(&val, 1, sizeof(val), fp.get()) != sizeof(val)) { 46 | throw runtime_error("Failed to store datum in "+fname+", may be corrupted now"); 47 | } 48 | } 49 | 50 | void StatStorage::store(const string& name, const vector& data) 51 | { 52 | if(name.find("/") != string::npos) 53 | return; 54 | 55 | unsigned int weekno=0; 56 | string fname; 57 | std::unique_ptr fp{nullptr, fclose}; 58 | for(const auto& d: data) { 59 | if(getWeekNum(d.timestamp) != weekno) { 60 | weekno=getWeekNum(d.timestamp); 61 | fname=makeFilename(name, d.timestamp); 62 | fp = std::unique_ptr(fopen(fname.c_str(), "a"), fclose); 63 | if(!fp) 64 | unixDie("Opening '"+fname+"'"); 65 | } 66 | StatStorage::Val val({d.timestamp, d.value}); 67 | if(fwrite(&val, 1, sizeof(val), fp.get()) != sizeof(val)) { 68 | throw runtime_error("Failed to store datum in "+fname+", may be corrupted now"); 69 | } 70 | } 71 | } 72 | 73 | 74 | static uint64_t filesize(int fd) 75 | { 76 | struct stat buf; 77 | if(!fstat(fd, &buf)) { 78 | return buf.st_size; 79 | } 80 | return 0; 81 | } 82 | 83 | vector StatStorage::getMetrics() 84 | { 85 | auto dir = std::unique_ptr(opendir(d_root.c_str()), closedir); 86 | if (!dir) { 87 | unixDie("Listing metrics from statistics storage"); 88 | } 89 | 90 | vector ret; 91 | for(;;) { 92 | struct dirent* result = readdir(dir.get()); 93 | if (!result) { 94 | break; 95 | } 96 | if (result->d_name[0] != '.') { 97 | char *p; 98 | for(p=result->d_name + strlen(result->d_name) - 1; p !=result->d_name && *p!='.'; --p); 99 | *p='\0'; 100 | 101 | if (*result->d_name) { 102 | ret.push_back(result->d_name); 103 | } 104 | } 105 | } 106 | sort(ret.begin(), ret.end()); 107 | auto newend=unique(ret.begin(), ret.end()); 108 | ret.resize(distance(ret.begin(), newend)); 109 | return ret; 110 | } 111 | 112 | vector StatStorage::retrieveVals(const std::string& name) 113 | { 114 | auto dir = std::unique_ptr(opendir(d_root.c_str()), closedir); 115 | if (!dir) { 116 | unixDie("Listing metrics from statistics storage"); 117 | } 118 | 119 | vector files; 120 | for(;;) { 121 | struct dirent* result = readdir(dir.get()); 122 | if (!result) { 123 | break; 124 | } 125 | if (boost::starts_with(result->d_name, name+".") || result->d_name==name) { 126 | files.push_back(result->d_name); 127 | } 128 | } 129 | 130 | vector ret; 131 | for(const auto& f: files) { 132 | retrieveAllFromFile(d_root+"/"+f, &ret); 133 | } 134 | 135 | return ret; 136 | } 137 | 138 | void StatStorage::retrieveAllFromFile(const std::string& fname, vector* values) 139 | { 140 | auto fp = std::unique_ptr(fopen(fname.c_str(), "r"), fclose); 141 | if(!fp) { 142 | if(errno!=ENOENT) 143 | unixDie("Opening '"+fname+"'"); 144 | return; 145 | } 146 | auto size = filesize(fileno(fp.get())); 147 | auto numEntries = size/sizeof(StatStorage::Val); 148 | auto oldsize=values->size(); 149 | values->resize(oldsize + numEntries); 150 | // cerr<<"Filesize: "< StatStorage::retrieveVals(const std::string& name, uint32_t begin, uint32_t end) 158 | { 159 | vector values; 160 | 161 | if(name.find("/") != string::npos) 162 | return values; 163 | 164 | for(uint32_t t = begin; t < end + 7*86400; t += 7*86400) { 165 | string fname=makeFilename(name, t); 166 | retrieveAllFromFile(fname, &values); 167 | 168 | } 169 | if(!is_sorted(values.begin(), values.end())) 170 | sort(values.begin(), values.end()); 171 | 172 | return values; 173 | } 174 | 175 | vector StatStorage::retrieve(const std::string& name) 176 | { 177 | auto vals = retrieveVals(name); 178 | vector ret; 179 | for(const auto& val : vals) { 180 | ret.push_back({val.timestamp, val.value}); 181 | } 182 | return ret; 183 | } 184 | 185 | vector StatStorage::retrieve(const std::string& name, time_t begin, time_t end, int number) 186 | { 187 | vector values=retrieveVals(name, begin, end); 188 | vector ret; 189 | if(values.empty()) 190 | return ret; 191 | auto beginIter = lower_bound(values.begin(), values.end(), (int64_t)begin); 192 | auto endIter = lower_bound(values.begin(), values.end(), (int64_t)end); 193 | 194 | ret.reserve(endIter- beginIter); 195 | for(auto iter = beginIter; iter != endIter; ++iter) { 196 | ret.push_back({iter->timestamp, iter->value}); 197 | } 198 | return ret; 199 | } 200 | -------------------------------------------------------------------------------- /statstorage.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | //! make your own instance, not thread safe 10 | class StatStorage 11 | { 12 | public: 13 | StatStorage(const std::string& root); 14 | ~StatStorage(); 15 | void store(const std::string& name, uint32_t timestamp, float value); 16 | 17 | struct Datum 18 | { 19 | uint32_t timestamp; 20 | float value; 21 | bool operator<(double t) const 22 | { 23 | return timestamp < t; 24 | } 25 | bool operator==(const Datum &rhs) const 26 | { 27 | return std::tie(rhs.timestamp, rhs.value) == std::tie(timestamp, value); 28 | } 29 | }; 30 | void store(const std::string& name, const std::vector& data); 31 | std::vector retrieve(const std::string& name, time_t begin, time_t end, int number=-1); 32 | std::vector retrieve(const std::string& name); 33 | std::vector getMetrics(); 34 | private: 35 | std::string d_root; 36 | regex_t d_preg; 37 | struct Val { 38 | uint32_t timestamp; 39 | float value; 40 | bool operator<(const Val& rhs) const 41 | { 42 | return timestamp < rhs.timestamp; 43 | } 44 | bool operator<(int64_t rhs) const 45 | { 46 | return timestamp < rhs; 47 | } 48 | } __attribute__((packed)); 49 | 50 | unsigned int getWeekNum(uint32_t t); 51 | std::string makeFilename(const std::string& name, uint32_t timestamp); 52 | std::vector retrieveVals(const std::string& name, uint32_t begin, uint32_t end); 53 | std::vector retrieveVals(const std::string& name); 54 | void retrieveAllFromFile(const std::string& fname, std::vector* values); 55 | }; 56 | 57 | inline bool operator<(double t, const StatStorage::Datum& d) 58 | { 59 | return t < d.timestamp; 60 | } 61 | -------------------------------------------------------------------------------- /test-statstorage.cc: -------------------------------------------------------------------------------- 1 | #include "statstorage.hh" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | using namespace std; 11 | 12 | BOOST_AUTO_TEST_SUITE(statstorage_hh) 13 | BOOST_AUTO_TEST_CASE(test_simple) { 14 | struct timeval tv; 15 | gettimeofday(&tv, 0); 16 | srandom(tv.tv_usec); 17 | string dirname="stat-unit-test."+boost::lexical_cast(random()+getpid()); 18 | mkdir(dirname.c_str(), 0700); 19 | StatStorage ss(dirname); 20 | vector > in, out; 21 | vector names; 22 | for(unsigned int i=0; i < 100; ++i) { 23 | string name = "stat." + boost::lexical_cast(i); 24 | names.push_back(name); 25 | time_t now=time(0)-10000; 26 | vector input; 27 | for(unsigned int n=0; n < 10000; ++n) { 28 | ss.store(name, now+n, n*n); 29 | input.push_back({(uint32_t)(now+n), (float)(n*n)}); 30 | } 31 | in.push_back(input); 32 | } 33 | StatStorage ss2(dirname); 34 | for(unsigned int i=0; i < 100; ++i) { 35 | string name = "stat." + boost::lexical_cast(i); 36 | out.push_back(ss2.retrieve(name)); 37 | } 38 | 39 | BOOST_CHECK_MESSAGE(in==out, "Different data came out than we put in"); 40 | auto newnames=ss2.getMetrics(); 41 | sort(newnames.begin(), newnames.end()); 42 | sort(names.begin(), names.end()); 43 | BOOST_CHECK_MESSAGE(newnames==names, "getMetrics() returned something different"); 44 | } 45 | BOOST_AUTO_TEST_SUITE_END() 46 | -------------------------------------------------------------------------------- /testrunner.cc: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_DYN_LINK 2 | #define BOOST_TEST_MAIN 3 | #define BOOST_TEST_MODULE unit 4 | 5 | #include 6 | -------------------------------------------------------------------------------- /yahttp/.gitignore: -------------------------------------------------------------------------------- 1 | yahttp/libyahttp.a 2 | *.o 3 | Makefile 4 | Makefile.in 5 | -------------------------------------------------------------------------------- /yahttp/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Aki Tuomi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /yahttp/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = yahttp 2 | 3 | EXTRA_DIST = LICENSE README.md 4 | -------------------------------------------------------------------------------- /yahttp/README.md: -------------------------------------------------------------------------------- 1 | Yet Another HTTP Library 2 | ======================== 3 | 4 | YaHTTP aims to be a pure http request/response parser that has no IO ties. It is intended to be used on small-footprint applications and other utilities that want to use HTTP over something else than network IO. 5 | 6 | [![Build Status](https://travis-ci.org/cmouse/yahttp.svg?branch=master)](https://travis-ci.org/cmouse/yahttp) 7 | [![Coverity Scan Build Status](https://scan.coverity.com/projects/2161/badge.svg)](https://scan.coverity.com/projects/2161) 8 | [![Coverage Status](https://coveralls.io/repos/cmouse/yahttp/badge.svg?branch=master%0A)](https://coveralls.io/r/cmouse/yahttp?branch=master%0A) 9 | 10 | WARNINGS 11 | -------- 12 | If you are upgrading from version before May 02, 2014 - *PLEASE BE SURE TO CHECK THAT EVERYTHING WORKS AS EXPECTED*. There has been complete overhaul of the library and many things have changed. 13 | 14 | NOTES 15 | ----- 16 | Do not use resp = req, or resp(req) to build the response object, despite it being supported. This will cause request headers to get duplicated. Also, you *must* set response#version to request#version if you intend to support older than HTTP/1.1 clients. Set response#status to at least 200, it won't be done for you. No Server or Product token is sent either, you can add those if you want. 17 | 18 | If you do not want to send chunked responses, set content-length header. Setting this header will always disable chunked responses. This will also happen if you downgrade your responses to version 10 or 9. 19 | 20 | Integration guide 21 | ----------------- 22 | 23 | Here are some instructions on how to integrate YaHTTP into your project. 24 | 25 | With automake and libtool 26 | ------------------------- 27 | 28 | If you don't need router or any of the C++11 features, you can just create empty yahttp-config.h, or symlink it to your project's config.h for the yahttp.hpp to include. Then just put this stuff into it's own folder and create Makefile.am with following contents (you can change the compilation flags): 29 | 30 | ``` 31 | noinst_LTLIBRARIES=libyahttp.la 32 | libyahttp_la_CXXFLAGS=$(RELRO_CFLAGS) $(PIE_CFLAGS) -D__STRICT_ANSI__ 33 | libyahttp_la_SOURCES=cookie.hpp exception.hpp reqresp.cpp reqresp.hpp router.cpp router.hpp url.hpp utility.hpp yahttp.hpp 34 | ``` 35 | 36 | You can define RELRO and PIE to match your project. 37 | 38 | To compile your project use -Lpath/to/yahttp -lyahttp 39 | 40 | If you need router, additionally check for boost or C++11 and replace yahttp-config.h to config.h in yahttp.hpp or add relevant options to your compiler CXXFLAGS. See below for the flags. 41 | 42 | Without automake 43 | ---------------- 44 | 45 | Create simple Makefile with contents for C++11: 46 | 47 | ``` 48 | OBJECTS=reqresp.o router.o 49 | CXX=gcc 50 | CXXFLAGS=-W -Wall -DHAVE_CXX11 -std=c++11 51 | ``` 52 | 53 | Or create simple Makefile with contents for boost: 54 | 55 | ``` 56 | OBJECTS=reqresp.o router.o 57 | CXX=gcc 58 | CXXFLAGS=-W -Wall -DHAVE_BOOST 59 | ``` 60 | 61 | Or if you don't need either one, just: 62 | 63 | ``` 64 | OBJECTS=reqresp.o 65 | CXX=gcc 66 | CXXFLAGS=-W -Wall 67 | ``` 68 | 69 | YaHTTP include files can be placed where the rest of your includes are. Then just add your own code there and it should work just fine. 70 | -------------------------------------------------------------------------------- /yahttp/yahttp/Makefile.am: -------------------------------------------------------------------------------- 1 | noinst_LTLIBRARIES = libyahttp.la 2 | 3 | libyahttp_la_SOURCES = \ 4 | cookie.hpp \ 5 | exception.hpp \ 6 | reqresp.cpp \ 7 | reqresp.hpp \ 8 | router.cpp \ 9 | router.hpp \ 10 | url.hpp \ 11 | utility.hpp \ 12 | yahttp-config.h \ 13 | yahttp.hpp 14 | -------------------------------------------------------------------------------- /yahttp/yahttp/cookie.hpp: -------------------------------------------------------------------------------- 1 | namespace YaHTTP { 2 | /*! Implements a single cookie */ 3 | class Cookie { 4 | public: 5 | Cookie() { 6 | secure = false; 7 | httponly = false; 8 | name = value = ""; 9 | }; //!< Set the cookie to empty value 10 | 11 | Cookie(const Cookie &rhs) { 12 | domain = rhs.domain; 13 | path = rhs.path; 14 | secure = rhs.secure; 15 | httponly = rhs.httponly; 16 | name = rhs.name; 17 | value = rhs.value; 18 | }; //0) 35 | oss << "; domain=" << domain; 36 | if (path.size()>0) 37 | oss << "; path=" << path; 38 | if (secure) 39 | oss << "; secure"; 40 | if (httponly) 41 | oss << "; httpOnly"; 42 | return oss.str(); 43 | }; //!< Stringify the cookie 44 | }; 45 | 46 | /*! Implements a Cookie jar for storing multiple cookies */ 47 | class CookieJar { 48 | public: 49 | std::map cookies; //cookies = rhs.cookies; 54 | } //cookies.clear(); 58 | } 59 | 60 | void keyValuePair(const std::string &keyvalue, std::string &key, std::string &value) { 61 | size_t pos; 62 | pos = keyvalue.find("="); 63 | if (pos == std::string::npos) throw "Not a Key-Value pair (cookie)"; 64 | key = std::string(keyvalue.begin(), keyvalue.begin()+pos); 65 | value = std::string(keyvalue.begin()+pos+1, keyvalue.end()); 66 | } // cookies; 70 | int cstate = 0; //cookiestate 71 | size_t pos,npos; 72 | pos = 0; 73 | cstate = 0; 74 | while(pos < cookiestr.size()) { 75 | if (cookiestr.compare(pos, 7, "expires") ==0 || 76 | cookiestr.compare(pos, 6, "domain") ==0 || 77 | cookiestr.compare(pos, 4, "path") ==0) { 78 | cstate = 1; 79 | // get the date 80 | std::string key, value, s; 81 | npos = cookiestr.find("; ", pos); 82 | if (npos == std::string::npos) { 83 | // last value 84 | s = std::string(cookiestr.begin() + pos + 1, cookiestr.end()); 85 | pos = cookiestr.size(); 86 | } else { 87 | s = std::string(cookiestr.begin() + pos + 1, cookiestr.begin() + npos - 1); 88 | pos = npos+2; 89 | } 90 | keyValuePair(s, key, value); 91 | if (s == "expires") { 92 | DateTime dt; 93 | dt.parseCookie(value); 94 | for(std::list::iterator i = cookies.begin(); i != cookies.end(); i++) 95 | i->expires = dt; 96 | } else if (s == "domain") { 97 | for(std::list::iterator i = cookies.begin(); i != cookies.end(); i++) 98 | i->domain = value; 99 | } else if (s == "path") { 100 | for(std::list::iterator i = cookies.begin(); i != cookies.end(); i++) 101 | i->path = value; 102 | } 103 | } else if (cookiestr.compare(pos, 8, "httpOnly")==0) { 104 | cstate = 1; 105 | for(std::list::iterator i = cookies.begin(); i != cookies.end(); i++) 106 | i->httponly = true; 107 | } else if (cookiestr.compare(pos, 6, "secure") ==0) { 108 | cstate = 1; 109 | for(std::list::iterator i = cookies.begin(); i != cookies.end(); i++) 110 | i->secure = true; 111 | } else if (cstate == 0) { // expect cookie 112 | Cookie c; 113 | std::string s; 114 | npos = cookiestr.find("; ", pos); 115 | if (npos == std::string::npos) { 116 | // last value 117 | s = std::string(cookiestr.begin() + pos, cookiestr.end()); 118 | pos = cookiestr.size(); 119 | } else { 120 | s = std::string(cookiestr.begin() + pos, cookiestr.begin() + npos); 121 | pos = npos+2; 122 | } 123 | keyValuePair(s, c.name, c.value); 124 | c.name = YaHTTP::Utility::decodeURL(c.name); 125 | c.value = YaHTTP::Utility::decodeURL(c.value); 126 | cookies.push_back(c); 127 | } else if (cstate == 1) { 128 | // ignore crap 129 | break; 130 | } 131 | } 132 | 133 | // store cookies 134 | for(std::list::iterator i = cookies.begin(); i != cookies.end(); i++) { 135 | this->cookies[i->name] = *i; 136 | } 137 | }; // 5 | 6 | namespace YaHTTP { 7 | /*! Generic error class */ 8 | class Error: public std::exception { 9 | public: 10 | Error() {}; 11 | Error(const std::string& reason): reason(reason) {}; 12 | virtual ~Error() throw() {}; 13 | 14 | virtual const char* what() const throw() 15 | { 16 | return reason.c_str(); 17 | } 18 | const std::string reason; // 5 | int AsyncLoader::feed(const std::string& somedata) { 6 | buffer.append(somedata); 7 | while(state < 2) { 8 | int cr=0; 9 | pos = buffer.find_first_of("\n"); 10 | // need to find CRLF in buffer 11 | if (pos == std::string::npos) return false; 12 | if (pos>0 && buffer[pos-1]=='\r') 13 | cr=1; 14 | std::string line(buffer.begin(), buffer.begin()+pos-cr); // exclude CRLF 15 | buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer including CRLF 16 | 17 | if (state == 0) { // startup line 18 | if (target->kind == YAHTTP_TYPE_REQUEST) { 19 | std::string ver; 20 | std::string tmpurl; 21 | std::istringstream iss(line); 22 | iss >> target->method >> tmpurl >> ver; 23 | if (ver.size() == 0) 24 | target->version = 9; 25 | else if (ver.find("HTTP/0.9") == 0) 26 | target->version = 9; 27 | else if (ver.find("HTTP/1.0") == 0) 28 | target->version = 10; 29 | else if (ver.find("HTTP/1.1") == 0) 30 | target->version = 11; 31 | else 32 | throw ParseError("HTTP version not supported"); 33 | // uppercase the target method 34 | std::transform(target->method.begin(), target->method.end(), target->method.begin(), ::toupper); 35 | target->url.parse(tmpurl); 36 | target->getvars = Utility::parseUrlParameters(target->url.parameters); 37 | state = 1; 38 | } else if(target->kind == YAHTTP_TYPE_RESPONSE) { 39 | std::string ver; 40 | std::istringstream iss(line); 41 | std::string::size_type pos1; 42 | iss >> ver >> target->status; 43 | std::getline(iss, target->statusText); 44 | pos1=0; 45 | while(pos1 < target->statusText.size() && ::isspace(target->statusText.at(pos1))) pos1++; 46 | target->statusText = target->statusText.substr(pos1); 47 | if ((pos1 = target->statusText.find("\r")) != std::string::npos) { 48 | target->statusText = target->statusText.substr(0, pos1-1); 49 | } 50 | if (ver.size() == 0) { 51 | target->version = 9; 52 | } else if (ver.find("HTTP/0.9") == 0) 53 | target->version = 9; 54 | else if (ver.find("HTTP/1.0") == 0) 55 | target->version = 10; 56 | else if (ver.find("HTTP/1.1") == 0) 57 | target->version = 11; 58 | else 59 | throw ParseError("HTTP version not supported"); 60 | state = 1; 61 | } 62 | } else if (state == 1) { 63 | std::string key,value; 64 | size_t pos; 65 | if (line.empty()) { 66 | chunked = (target->headers.find("transfer-encoding") != target->headers.end() && target->headers["transfer-encoding"] == "chunked"); 67 | state = 2; 68 | break; 69 | } 70 | // split headers 71 | if ((pos = line.find(": ")) == std::string::npos) 72 | throw ParseError("Malformed header line"); 73 | key = line.substr(0, pos); 74 | value = line.substr(pos+2); 75 | for(std::string::iterator it=key.begin(); it != key.end(); it++) 76 | if (std::isspace(*it)) 77 | throw ParseError("Header key contains whitespace which is not allowed by RFC"); 78 | 79 | Utility::trim(value); 80 | std::transform(key.begin(), key.end(), key.begin(), ::tolower); 81 | // is it already defined 82 | 83 | if ((key == "set-cookie" && target->kind == YAHTTP_TYPE_RESPONSE) || 84 | (key == "cookie" && target->kind == YAHTTP_TYPE_REQUEST)) { 85 | target->jar.parseCookieHeader(value); 86 | } else { 87 | if (key == "host" && target->kind == YAHTTP_TYPE_REQUEST) { 88 | // maybe it contains port? 89 | if ((pos = value.find(":")) == std::string::npos) { 90 | target->url.host = value; 91 | } else { 92 | target->url.host = value.substr(0, pos); 93 | target->url.port = ::atoi(value.substr(pos).c_str()); 94 | } 95 | } 96 | if (target->headers.find(key) != target->headers.end()) { 97 | target->headers[key] = target->headers[key] + ";" + value; 98 | } else { 99 | target->headers[key] = value; 100 | } 101 | } 102 | } 103 | } 104 | 105 | minbody = 0; 106 | // check for expected body size 107 | if (target->kind == YAHTTP_TYPE_REQUEST) maxbody = target->max_request_size; 108 | else if (target->kind == YAHTTP_TYPE_RESPONSE) maxbody = target->max_response_size; 109 | else maxbody = 0; 110 | 111 | if (!chunked) { 112 | if (target->headers.find("content-length") != target->headers.end()) { 113 | std::istringstream maxbodyS(target->headers["content-length"]); 114 | maxbodyS >> minbody; 115 | maxbody = minbody; 116 | } 117 | if (minbody < 1) return true; // guess there isn't anything left. 118 | if (target->kind == YAHTTP_TYPE_REQUEST && static_cast(minbody) > target->max_request_size) throw ParseError("Max request body size exceeded"); 119 | else if (target->kind == YAHTTP_TYPE_RESPONSE && static_cast(minbody) > target->max_response_size) throw ParseError("Max response body size exceeded"); 120 | } 121 | 122 | if (maxbody == 0) hasBody = false; 123 | else hasBody = true; 124 | 125 | if (buffer.size() == 0) return ready(); 126 | 127 | while(buffer.size() > 0) { 128 | if (chunked) { 129 | if (chunk_size == 0) { 130 | char buf[100]; 131 | // read chunk length 132 | if ((pos = buffer.find('\n')) == std::string::npos) return false; 133 | if (pos > 99) 134 | throw ParseError("Impossible chunk_size"); 135 | buffer.copy(buf, pos); 136 | buf[pos]=0; // just in case... 137 | buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer 138 | sscanf(buf, "%x", &chunk_size); 139 | if (!chunk_size) { state = 3; break; } // last chunk 140 | } else { 141 | int crlf=1; 142 | if (buffer.size() < static_cast(chunk_size+1)) return false; // expect newline 143 | if (buffer.at(chunk_size) == '\r') { 144 | if (buffer.size() < static_cast(chunk_size+2) || buffer.at(chunk_size+1) != '\n') return false; // expect newline after carriage return 145 | crlf=2; 146 | } else if (buffer.at(chunk_size) != '\n') return false; 147 | std::string tmp = buffer.substr(0, chunk_size); 148 | buffer.erase(buffer.begin(), buffer.begin()+chunk_size+crlf); 149 | bodybuf << tmp; 150 | chunk_size = 0; 151 | if (buffer.size() == 0) break; // just in case 152 | } 153 | } else { 154 | if (bodybuf.str().length() + buffer.length() > maxbody) 155 | bodybuf << buffer.substr(0, maxbody - bodybuf.str().length()); 156 | else 157 | bodybuf << buffer; 158 | buffer = ""; 159 | } 160 | } 161 | 162 | if (chunk_size!=0) return false; // need more data 163 | 164 | return ready(); 165 | }; 166 | 167 | void HTTPBase::write(std::ostream& os) const { 168 | if (kind == YAHTTP_TYPE_REQUEST) { 169 | std::ostringstream getparmbuf; 170 | std::string getparms; 171 | // prepare URL 172 | for(strstr_map_t::const_iterator i = getvars.begin(); i != getvars.end(); i++) { 173 | getparmbuf << Utility::encodeURL(i->first, false) << "=" << Utility::encodeURL(i->second, false) << "&"; 174 | } 175 | if (getparmbuf.str().length() > 0) { 176 | std::string buf = getparmbuf.str(); 177 | getparms = "?" + std::string(buf.begin(), buf.end() - 1); 178 | } 179 | else 180 | getparms = ""; 181 | os << method << " " << url.path << getparms << " HTTP/" << versionStr(this->version); 182 | } else if (kind == YAHTTP_TYPE_RESPONSE) { 183 | os << "HTTP/" << versionStr(this->version) << " " << status << " "; 184 | if (statusText.empty()) 185 | os << Utility::status2text(status); 186 | else 187 | os << statusText; 188 | } 189 | os << "\r\n"; 190 | 191 | bool cookieSent = false; 192 | bool sendChunked = false; 193 | 194 | if (this->version > 10) { // 1.1 or better 195 | if (headers.find("content-length") == headers.end()) { 196 | // must use chunked on response 197 | sendChunked = (kind == YAHTTP_TYPE_RESPONSE); 198 | if ((headers.find("transfer-encoding") != headers.end() && headers.find("transfer-encoding")->second != "chunked")) { 199 | throw YaHTTP::Error("Transfer-encoding must be chunked, or Content-Length defined"); 200 | } 201 | if ((headers.find("transfer-encoding") == headers.end() && kind == YAHTTP_TYPE_RESPONSE)) { 202 | sendChunked = true; 203 | os << "Transfer-Encoding: chunked\r\n"; 204 | } 205 | } else { 206 | sendChunked = false; 207 | } 208 | } 209 | 210 | // write headers 211 | strstr_map_t::const_iterator iter = headers.begin(); 212 | while(iter != headers.end()) { 213 | if (iter->first == "host" && kind != YAHTTP_TYPE_REQUEST) { iter++; continue; } 214 | if (iter->first == "transfer-encoding" && sendChunked) { iter++; continue; } 215 | std::string header = Utility::camelizeHeader(iter->first); 216 | if (header == "Cookie" || header == "Set-Cookie") cookieSent = true; 217 | os << Utility::camelizeHeader(iter->first) << ": " << iter->second << "\r\n"; 218 | iter++; 219 | } 220 | if (!cookieSent && jar.cookies.size() > 0) { // write cookies 221 | for(strcookie_map_t::const_iterator i = jar.cookies.begin(); i != jar.cookies.end(); i++) { 222 | if (kind == YAHTTP_TYPE_REQUEST) { 223 | os << "Cookie: "; 224 | } else { 225 | os << "Set-Cookie: "; 226 | } 227 | os << i->second.str() << "\r\n"; 228 | } 229 | } 230 | os << "\r\n"; 231 | #ifdef HAVE_CPP_FUNC_PTR 232 | this->renderer(this, os, sendChunked); 233 | #else 234 | SendbodyRenderer r; 235 | r(this, os, chunked) 236 | #endif 237 | }; 238 | 239 | std::ostream& operator<<(std::ostream& os, const Response &resp) { 240 | resp.write(os); 241 | return os; 242 | }; 243 | 244 | std::istream& operator>>(std::istream& is, Response &resp) { 245 | YaHTTP::AsyncResponseLoader arl; 246 | arl.initialize(&resp); 247 | while(is.good()) { 248 | char buf[1024]; 249 | is.read(buf, 1024); 250 | if (is.gcount()>0) { // did we actually read anything 251 | is.clear(); 252 | if (arl.feed(std::string(buf, is.gcount())) == true) break; // completed 253 | } 254 | } 255 | // throw unless ready 256 | if (arl.ready() == false) 257 | throw ParseError("Was not able to extract a valid Response from stream"); 258 | arl.finalize(); 259 | return is; 260 | }; 261 | 262 | std::ostream& operator<<(std::ostream& os, const Request &req) { 263 | req.write(os); 264 | return os; 265 | }; 266 | 267 | std::istream& operator>>(std::istream& is, Request &req) { 268 | YaHTTP::AsyncRequestLoader arl; 269 | arl.initialize(&req); 270 | while(is.good()) { 271 | char buf[1024]; 272 | is.read(buf, 1024); 273 | if (is.gcount()) { // did we actually read anything 274 | is.clear(); 275 | if (arl.feed(std::string(buf, is.gcount())) == true) break; // completed 276 | } 277 | } 278 | if (arl.ready() == false) 279 | throw ParseError("Was not able to extract a valid Request from stream"); 280 | arl.finalize(); 281 | return is; 282 | }; 283 | }; 284 | -------------------------------------------------------------------------------- /yahttp/yahttp/reqresp.hpp: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_CXX11 2 | #include 3 | #define HAVE_CPP_FUNC_PTR 4 | namespace funcptr = std; 5 | #else 6 | #ifdef HAVE_BOOST 7 | #include 8 | namespace funcptr = boost; 9 | #define HAVE_CPP_FUNC_PTR 10 | #endif 11 | #endif 12 | 13 | #include 14 | #include 15 | 16 | #ifndef WIN32 17 | #include 18 | #include 19 | #endif 20 | 21 | #include 22 | 23 | #ifndef YAHTTP_MAX_REQUEST_SIZE 24 | #define YAHTTP_MAX_REQUEST_SIZE 2097152 25 | #endif 26 | 27 | #ifndef YAHTTP_MAX_RESPONSE_SIZE 28 | #define YAHTTP_MAX_RESPONSE_SIZE 2097152 29 | #endif 30 | 31 | #define YAHTTP_TYPE_REQUEST 1 32 | #define YAHTTP_TYPE_RESPONSE 2 33 | 34 | namespace YaHTTP { 35 | typedef std::map strcookie_map_t; //body.length();i+=1024) { 54 | cl = std::min(static_cast(1024), doc->body.length()-i); // for less than 1k blocks 55 | os << std::hex << cl << std::dec << "\r\n"; 56 | os << doc->body.substr(i, cl) << "\r\n"; 57 | } 58 | os << 0 << "\r\n\r\n"; // last chunk 59 | } else { 60 | os << doc->body; 61 | } 62 | return doc->body.length(); 63 | }; //path = path; 70 | }; 71 | 72 | size_t operator()(const HTTPBase *doc __attribute__((unused)), std::ostream& os, bool chunked) const { 73 | char buf[4096]; 74 | size_t n,k; 75 | #ifdef HAVE_CXX11 76 | std::ifstream ifs(path, std::ifstream::binary); 77 | #else 78 | std::ifstream ifs(path.c_str(), std::ifstream::binary); 79 | #endif 80 | n = 0; 81 | 82 | while(ifs && ifs.good()) { 83 | ifs.read(buf, sizeof buf); 84 | n += (k = ifs.gcount()); 85 | if (k) { 86 | if (chunked) os << std::hex << k << std::dec << "\r\n"; 87 | os.write(buf, k); 88 | if (chunked) os << "\r\n"; 89 | } 90 | } 91 | if (chunked) os << 0 << "\r\n\r\n"; 92 | return n; 93 | }; //url = rhs.url; this->kind = rhs.kind; 125 | this->status = rhs.status; this->statusText = rhs.statusText; 126 | this->method = rhs.method; this->headers = rhs.headers; 127 | this->jar = rhs.jar; this->postvars = rhs.postvars; 128 | this->parameters = rhs.parameters; this->getvars = rhs.getvars; 129 | this->body = rhs.body; this->max_request_size = rhs.max_request_size; 130 | this->max_response_size = rhs.max_response_size; this->version = rhs.version; 131 | #ifdef HAVE_CPP_FUNC_PTR 132 | this->renderer = rhs.renderer; 133 | #endif 134 | }; 135 | HTTPBase& operator=(const HTTPBase& rhs) { 136 | this->url = rhs.url; this->kind = rhs.kind; 137 | this->status = rhs.status; this->statusText = rhs.statusText; 138 | this->method = rhs.method; this->headers = rhs.headers; 139 | this->jar = rhs.jar; this->postvars = rhs.postvars; 140 | this->parameters = rhs.parameters; this->getvars = rhs.getvars; 141 | this->body = rhs.body; this->max_request_size = rhs.max_request_size; 142 | this->max_response_size = rhs.max_response_size; this->version = rhs.version; 143 | #ifdef HAVE_CPP_FUNC_PTR 144 | this->renderer = rhs.renderer; 145 | #endif 146 | return *this; 147 | }; 148 | public: 149 | URL url; // renderer; //kind = YAHTTP_TYPE_RESPONSE; 199 | }; 200 | Response& operator=(const HTTPBase& rhs) { 201 | HTTPBase::operator=(rhs); 202 | this->kind = YAHTTP_TYPE_RESPONSE; 203 | return *this; 204 | }; 205 | void initialize() { 206 | HTTPBase::initialize(); 207 | this->kind = YAHTTP_TYPE_RESPONSE; 208 | } 209 | void initialize(const HTTPBase& rhs) { 210 | HTTPBase::initialize(); 211 | this->kind = YAHTTP_TYPE_RESPONSE; 212 | // copy SOME attributes 213 | this->url = rhs.url; 214 | this->method = rhs.method; 215 | this->jar = rhs.jar; 216 | this->version = rhs.version; 217 | } 218 | friend std::ostream& operator<<(std::ostream& os, const Response &resp); 219 | friend std::istream& operator>>(std::istream& is, Response &resp); 220 | }; 221 | 222 | /* Request class, represents a HTTP Request document */ 223 | class Request: public HTTPBase { 224 | public: 225 | Request() { initialize(); }; 226 | Request(const HTTPBase& rhs): HTTPBase(rhs) { 227 | this->kind = YAHTTP_TYPE_REQUEST; 228 | }; 229 | Request& operator=(const HTTPBase& rhs) { 230 | HTTPBase::operator=(rhs); 231 | this->kind = YAHTTP_TYPE_REQUEST; 232 | return *this; 233 | }; 234 | void initialize() { 235 | HTTPBase::initialize(); 236 | this->kind = YAHTTP_TYPE_REQUEST; 237 | } 238 | void initialize(const HTTPBase& rhs) { 239 | HTTPBase::initialize(); 240 | this->kind = YAHTTP_TYPE_REQUEST; 241 | // copy SOME attributes 242 | this->url = rhs.url; 243 | this->method = rhs.method; 244 | this->jar = rhs.jar; 245 | this->version = rhs.version; 246 | } 247 | void setup(const std::string& method, const std::string& url) { 248 | this->url.parse(url); 249 | this->headers["host"] = this->url.host; 250 | this->method = method; 251 | std::transform(this->method.begin(), this->method.end(), this->method.begin(), ::toupper); 252 | this->headers["user-agent"] = "YaHTTP v1.0"; 253 | }; //first, false) << "=" << Utility::encodeURL(i->second, false) << "&"; 260 | } 261 | // remove last bit 262 | if (postbuf.str().length()>0) 263 | body = postbuf.str().substr(0, postbuf.str().length()-1); 264 | else 265 | body = ""; 266 | headers["content-type"] = "application/x-www-form-urlencoded; charset=utf-8"; 267 | } else if (format == multipart) { 268 | headers["content-type"] = "multipart/form-data; boundary=YaHTTP-12ca543"; 269 | for(strstr_map_t::const_iterator i = POST().begin(); i != POST().end(); i++) { 270 | postbuf << "--YaHTTP-12ca543\r\nContent-Disposition: form-data; name=\"" << Utility::encodeURL(i->first, false) << "; charset=UTF-8\r\n\r\n" 271 | << Utility::encodeURL(i->second, false) << "\r\n"; 272 | } 273 | } 274 | 275 | postbuf.str(""); 276 | postbuf << body.length(); 277 | // set method and change headers 278 | method = "POST"; 279 | headers["content-length"] = postbuf.str(); 280 | }; //>(std::istream& is, Request &resp); 284 | }; 285 | 286 | /*! Asynchronous HTTP document loader */ 287 | template 288 | class AsyncLoader { 289 | public: 290 | T* target; //target = target; 308 | hasBody = false; 309 | buffer = ""; 310 | this->target->initialize(); 311 | }; // 1 && 316 | (!hasBody || 317 | (bodybuf.str().size() <= maxbody && 318 | bodybuf.str().size() >= minbody) 319 | ) 320 | ); 321 | }; //headers.find("content-type"); 326 | if (pos != target->headers.end() && Utility::iequals(pos->second, "application/x-www-form-urlencoded", 32)) { 327 | target->postvars = Utility::parseUrlParameters(bodybuf.str()); 328 | } 329 | target->body = bodybuf.str(); 330 | } 331 | bodybuf.str(""); 332 | this->target = NULL; 333 | }; // { 338 | }; 339 | 340 | /*! Asynchronous HTTP request loader */ 341 | class AsyncRequestLoader: public AsyncLoader { 342 | }; 343 | 344 | }; 345 | -------------------------------------------------------------------------------- /yahttp/yahttp/router.cpp: -------------------------------------------------------------------------------- 1 | /* @file 2 | * @brief Concrete implementation of Router 3 | */ 4 | #include "yahttp.hpp" 5 | #include "router.hpp" 6 | 7 | namespace YaHTTP { 8 | typedef funcptr::tuple TDelim; 9 | 10 | // router is defined here. 11 | YaHTTP::Router Router::router; 12 | 13 | void Router::map(const std::string& method, const std::string& url, THandlerFunction handler, const std::string& name) { 14 | std::string method2 = method; 15 | bool isopen=false; 16 | // add into vector 17 | for(std::string::const_iterator i = url.begin(); i != url.end(); i++) { 18 | if (*i == '<' && isopen) throw Error("Invalid URL mask, cannot have < after <"); 19 | if (*i == '<') isopen = true; 20 | if (*i == '>' && !isopen) throw Error("Invalid URL mask, cannot have > without < first"); 21 | if (*i == '>') isopen = false; 22 | } 23 | std::transform(method2.begin(), method2.end(), method2.begin(), ::toupper); 24 | routes.push_back(funcptr::make_tuple(method2, url, handler, name)); 25 | }; 26 | 27 | bool Router::route(Request *req, THandlerFunction& handler) { 28 | std::map params; 29 | int pos1,pos2; 30 | std::string pname; 31 | bool matched = false; 32 | std::string rname; 33 | 34 | // iterate routes 35 | for(TRouteList::iterator i = routes.begin(); !matched && i != routes.end(); i++) { 36 | int k1,k2,k3; 37 | std::string pname; 38 | std::string method, url; 39 | funcptr::tie(method, url, handler, rname) = *i; 40 | 41 | if (method.empty() == false && req->method != method) continue; // no match on method 42 | // see if we can't match the url 43 | params.clear(); 44 | // simple matcher func 45 | for(k1=0, k2=0; k1 < static_cast(url.size()) && k2 < static_cast(req->url.path.size()); ) { 46 | if (url[k1] == '<') { 47 | pos1 = k2; 48 | k3 = k1+1; 49 | // start of parameter 50 | while(k1 < static_cast(url.size()) && url[k1] != '>') k1++; 51 | pname = std::string(url.begin()+k3, url.begin()+k1); 52 | // then we also look it on the url 53 | if (pname[0]=='*') { 54 | pname = pname.substr(1); 55 | // this matches whatever comes after it, basically end of string 56 | pos2 = req->url.path.size(); 57 | matched = true; 58 | if (pname != "") 59 | params[pname] = funcptr::tie(pos1,pos2); 60 | k1 = url.size(); 61 | k2 = req->url.path.size(); 62 | break; 63 | } else { 64 | // match until url[k1] 65 | while(k2 < static_cast(req->url.path.size()) && req->url.path[k2] != url[k1+1]) k2++; 66 | pos2 = k2; 67 | params[pname] = funcptr::tie(pos1,pos2); 68 | } 69 | k2--; 70 | } 71 | else if (url[k1] != req->url.path[k2]) { 72 | break; 73 | } 74 | 75 | k1++; k2++; 76 | } 77 | 78 | // ensure. 79 | if (url[k1] != req->url.path[k2]) 80 | matched = false; 81 | else 82 | matched = true; 83 | } 84 | 85 | if (!matched) { return false; } // no route 86 | req->parameters.clear(); 87 | 88 | for(std::map::iterator i = params.begin(); i != params.end(); i++) { 89 | int p1,p2; 90 | funcptr::tie(p1,p2) = i->second; 91 | std::string value(req->url.path.begin() + p1, req->url.path.begin() + p2); 92 | value = Utility::decodeURL(value); 93 | req->parameters[i->first] = value; 94 | } 95 | 96 | req->routeName = rname; 97 | 98 | return true; 99 | }; 100 | 101 | void Router::printRoutes(std::ostream &os) { 102 | for(TRouteList::iterator i = routes.begin(); i != routes.end(); i++) { 103 | #ifdef HAVE_CXX11 104 | std::streamsize ss = os.width(); 105 | std::ios::fmtflags ff = os.setf(std::ios::left); 106 | os.width(10); 107 | os << std::get<0>(*i); 108 | os.width(50); 109 | os << std::get<1>(*i); 110 | os.width(ss); 111 | os.setf(ff); 112 | os << " " << std::get<3>(*i); 113 | os << std::endl; 114 | #else 115 | os << i->get<0>() << " " << i->get<1>() << " " << i->get<3>() << std::endl; 116 | #endif 117 | } 118 | }; 119 | 120 | std::pair Router::urlFor(const std::string &name, const strstr_map_t& arguments) { 121 | std::ostringstream path; 122 | std::string mask,method,result; 123 | int k1,k2,k3; 124 | 125 | bool found = false; 126 | for(TRouteList::iterator i = routes.begin(); !found && i != routes.end(); i++) { 127 | #ifdef HAVE_CXX11 128 | if (std::get<3>(*i) == name) { mask = std::get<1>(*i); method = std::get<0>(*i); found = true; } 129 | #else 130 | if (i->get<3>() == name) { mask = i->get<1>(); method = i->get<0>(); found = true; } 131 | #endif 132 | } 133 | 134 | if (!found) 135 | throw Error("Route not found"); 136 | 137 | for(k1=0,k3=0;k1(mask.size());k1++) { 138 | if (mask[k1] == '<') { 139 | std::string pname; 140 | strstr_map_t::const_iterator pptr; 141 | k2=k1; 142 | while(k1(mask.size()) && mask[k1]!='>') k1++; 143 | path << mask.substr(k3,k2-k3); 144 | if (mask[k2+1] == '*') 145 | pname = std::string(mask.begin() + k2 + 2, mask.begin() + k1); 146 | else 147 | pname = std::string(mask.begin() + k2 + 1, mask.begin() + k1); 148 | if ((pptr = arguments.find(pname)) != arguments.end()) 149 | path << Utility::encodeURL(pptr->second); 150 | k3 = k1+1; 151 | } 152 | else if (mask[k1] == '*') { 153 | // ready 154 | k3++; 155 | continue; 156 | } 157 | } 158 | path << mask.substr(k3); 159 | result = path.str(); 160 | return std::make_pair(method, result); 161 | } 162 | }; 163 | -------------------------------------------------------------------------------- /yahttp/yahttp/router.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _YAHTTP_ROUTER_HPP 2 | #define _YAHTTP_ROUTER_HPP 1 3 | /* @file 4 | * @brief Defines router class and support structures 5 | */ 6 | #ifdef HAVE_CXX11 7 | #include 8 | #include 9 | #define HAVE_CPP_FUNC_PTR 10 | #define IGNORE std::ignore 11 | namespace funcptr = std; 12 | #else 13 | #ifdef HAVE_BOOST 14 | #include 15 | #include 16 | #define IGNORE boost::tuples::ignore 17 | namespace funcptr = boost; 18 | #define HAVE_CPP_FUNC_PTR 19 | #else 20 | #warning "You need to configure with boost or have C++11 capable compiler for router" 21 | #endif 22 | #endif 23 | 24 | #ifdef HAVE_CPP_FUNC_PTR 25 | #include 26 | #include 27 | 28 | namespace YaHTTP { 29 | typedef funcptr::function THandlerFunction; //!< Handler function pointer 30 | typedef funcptr::tuple TRoute; //!< Route tuple (method, urlmask, handler, name) 31 | typedef std::vector TRouteList; //!< List of routes in order of evaluation 32 | 33 | /*! Implements simple router. 34 | 35 | This class implements a router for masked urls. The URL mask syntax is as of follows 36 | 37 | /<masked>/url<number>/<hi>.<format> 38 | 39 | You can use <*param> to denote that everything will be matched and consumed into the parameter, including slash (/). Use <*> to denote that URL 40 | is consumed but not stored. Note that only path is matched, scheme, host and url parameters are ignored. 41 | */ 42 | class Router { 43 | private: 44 | Router() {}; 45 | static Router router; // urlFor(const std::string &name, const strstr_map_t& arguments); //url.path 64 | static void PrintRoutes(std::ostream &os) { router.printRoutes(os); }; // URLFor(const std::string &name, const strstr_map_t& arguments) { return router.urlFor(name,arguments); }; // 4 | #include 5 | 6 | #include "utility.hpp" 7 | 8 | #ifndef YAHTTP_MAX_URL_LENGTH 9 | #define YAHTTP_MAX_URL_LENGTH 2048 10 | #endif 11 | 12 | namespace YaHTTP { 13 | /*! URL parser and container */ 14 | class URL { 15 | private: 16 | bool parseSchema(const std::string& url, size_t &pos) { 17 | size_t pos1; 18 | if (pos >= url.size()) return false; // no data 19 | if ( (pos1 = url.find_first_of(":",pos)) == std::string::npos ) return false; // schema is mandatory 20 | protocol = url.substr(pos, pos1-pos); 21 | if (protocol == "http") port = 80; 22 | if (protocol == "https") port = 443; 23 | pos = pos1+1; // after : 24 | if (url.compare(pos, 2, "//") == 0) { 25 | pathless = false; // if this is true we put rest into parameters 26 | pos += 2; 27 | } 28 | return true; 29 | }; //= url.size()) return true; // no data 34 | if ( (pos1 = url.find_first_of("/", pos)) == std::string::npos ) { 35 | host = url.substr(pos); 36 | path = "/"; 37 | pos = url.size(); 38 | } else { 39 | host = url.substr(pos, pos1-pos); 40 | pos = pos1; 41 | } 42 | if ( (pos1 = host.find_first_of(":")) != std::string::npos ) { 43 | std::istringstream tmp(host.substr(pos1+1)); 44 | tmp >> port; 45 | host = host.substr(0, pos1); 46 | } 47 | return true; 48 | }; //= url.size()) return true; // no data 53 | 54 | if ( (pos1 = url.find_first_of("@",pos)) == std::string::npos ) return true; // no userinfo 55 | pos2 = url.find_first_of(":",pos); 56 | 57 | if (pos2 != std::string::npos) { // comes with password 58 | username = url.substr(pos, pos2 - pos); 59 | password = url.substr(pos2+1, pos1 - pos2 - 1); 60 | password = Utility::decodeURL(password); 61 | } else { 62 | username = url.substr(pos+1, pos1 - pos); 63 | } 64 | pos = pos1+1; 65 | username = Utility::decodeURL(username); 66 | return true; 67 | }; //= url.size()) return true; // no data 72 | if (url[pos] != '/') return false; // not an url 73 | if ( (pos1 = url.find_first_of("?", pos)) == std::string::npos ) { 74 | path = url.substr(pos); 75 | pos = url.size(); 76 | } else { 77 | path = url.substr(pos, pos1-pos); 78 | pos = pos1; 79 | } 80 | return true; 81 | }; //= url.size()) return true; // no data 86 | if (url[pos] == '#') return true; // anchor starts here 87 | if (url[pos] != '?') return false; // not a parameter 88 | if ( (pos1 = url.find_first_of("#", pos)) == std::string::npos ) { 89 | parameters = url.substr(pos+1);; 90 | pos = url.size(); 91 | } else { 92 | parameters = url.substr(pos+1, pos1-pos-1); 93 | pos = pos1; 94 | } 95 | if (parameters.size()>0 && *(parameters.end()-1) == '&') parameters.resize(parameters.size()-1); 96 | return true; 97 | }; //= url.size()) return true; // no data 101 | if (url[pos] != '#') return false; // not anchor 102 | anchor = url.substr(pos+1); 103 | return true; 104 | }; // 0) 133 | oss << ":" << port; 134 | 135 | oss << path; 136 | if (parameters.empty() == false) { 137 | if (!pathless) 138 | oss << "?"; 139 | oss << parameters; 140 | } 141 | if (anchor.empty() == false) 142 | oss << "#" << anchor; 143 | return oss.str(); 144 | }; // YAHTTP_MAX_URL_LENGTH) return false; 170 | size_t pos = 0; 171 | if (*(url.begin()) != '/') { // full url? 172 | if (parseSchema(url, pos) == false) return false; 173 | if (pathless) { 174 | parameters = url.substr(pos); 175 | return true; 176 | } 177 | if (parseUserPass(url, pos) == false) return false; 178 | if (parseHost(url, pos) == false) return false; 179 | } 180 | if (parsePath(url, pos) == false) return false; 181 | if (parseParameters(url, pos) == false) return false; 182 | return parseAnchor(url, pos); 183 | }; // 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "yahttp-config.h" 13 | #include "url.hpp" 14 | #include "utility.hpp" 15 | #include "exception.hpp" 16 | #include "url.hpp" 17 | #include "cookie.hpp" 18 | #include "reqresp.hpp" 19 | 20 | /*! \mainpage Yet Another HTTP Library Documentation 21 | \section sec_quick_start Quick start example 22 | 23 | @code 24 | #include 25 | 26 | int main(void) { 27 | std::ifstream ifs("request.txt"); 28 | YaHTTP::Request req; 29 | ifs >> req; 30 | 31 | std::cout << req.method " " << req.url.path << std::endl; 32 | return 0; 33 | } 34 | @endcode 35 | \author Aki Tuomi 36 | */ 37 | --------------------------------------------------------------------------------