├── .gitignore ├── .gitlab-ci.yml ├── .gitmodules ├── AUTHORS ├── COPYING ├── Makefile ├── NEWS ├── README.md ├── alt_drand48.h ├── configure ├── crash_test.c ├── hanoi_test.c ├── man ├── man1 │ └── recorder_scope.1 └── man3 │ ├── RECORDER.3 │ ├── RECORDER_DECLARE.3 │ ├── RECORDER_DEFINE.3 │ ├── RECORDER_TRACE.3 │ ├── RECORDER_TWEAK.3 │ ├── record.3 │ ├── record_fast.3 │ ├── recorder_configure_format.3 │ ├── recorder_configure_output.3 │ ├── recorder_configure_show.3 │ ├── recorder_configure_type.3 │ ├── recorder_dump.3 │ ├── recorder_dump_for.3 │ ├── recorder_dump_on_common_signals.3 │ ├── recorder_dump_on_signal.3 │ ├── recorder_sort.3 │ └── recorder_trace_set.3 ├── recorder.c ├── recorder.h ├── recorder.spec ├── recorder_ring.c ├── recorder_ring.h ├── recorder_test.c ├── ring_test.c ├── ring_test.h └── scope ├── COPYING ├── MinMaxAverage.png ├── Scope.png ├── ScopeAndSliders.png ├── Timing.png ├── recorder_scope.cpp ├── recorder_scope.pro ├── recorder_slider.cpp ├── recorder_slider.h ├── recorder_view.cpp └── recorder_view.h /.gitignore: -------------------------------------------------------------------------------- 1 | .logs 2 | .objects 3 | scope/*.o 4 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | before_script: 2 | - dnf install -y git gcc gcc-c++ make findutils diffutils gawk || (apt-get update && apt-get install -y git gcc g++ make findutils gawk) 3 | - git submodule update --init --recursive 4 | 5 | test: 6 | script: 7 | - make test 8 | - make debug-test 9 | - make opt-test 10 | - make release-test 11 | 12 | make_only: 13 | script: 14 | - make 15 | - make opt 16 | - make release 17 | - make debug 18 | 19 | statistics: 20 | script: 21 | - make opt-test | grep "Duration per record" 22 | 23 | install:fedora: 24 | image: fedora:latest 25 | script: 26 | - make PREFIX=/tmp/recorder/ install 27 | 28 | install-opt:fedora: 29 | image: fedora:latest 30 | script: 31 | - make PREFIX=/tmp/recorder-opt/ install-opt 32 | 33 | install-debug:fedora: 34 | image: fedora:latest 35 | script: 36 | - make PREFIX=/tmp/recorder-debug/ install-debug 37 | 38 | install-release:fedora: 39 | image: fedora:latest 40 | script: 41 | - make PREFIX=/tmp/recorder-release/ install-release 42 | 43 | test:fedora: 44 | image: fedora:latest 45 | script: 46 | - make test 47 | 48 | test-opt:fedora: 49 | image: fedora:latest 50 | script: 51 | - make test-opt 52 | 53 | test-release:fedora: 54 | image: fedora:latest 55 | script: 56 | - make test-release 57 | 58 | test-debug:fedora: 59 | image: fedora:latest 60 | script: 61 | - make test-debug 62 | 63 | build:fedora: 64 | image: fedora:latest 65 | script: 66 | - make 67 | 68 | build-opt:fedora: 69 | image: fedora:latest 70 | script: 71 | - make opt 72 | 73 | build-debug:fedora: 74 | image: fedora:latest 75 | script: 76 | - make opt-debug 77 | 78 | build-release:fedora: 79 | image: fedora:latest 80 | script: 81 | - make opt-release 82 | 83 | test:fedora28: 84 | image: fedora:28 85 | script: 86 | - make test 87 | 88 | test:fedora29: 89 | image: fedora:29 90 | script: 91 | - make test 92 | 93 | test:fedora30: 94 | image: fedora:30 95 | script: 96 | - make test 97 | 98 | test:fedora31: 99 | image: fedora:31 100 | script: 101 | - make test 102 | 103 | test:fedora32: 104 | image: fedora:32 105 | script: 106 | - make test 107 | 108 | test:ubuntu: 109 | image: ubuntu:latest 110 | script: 111 | - make test 112 | 113 | test-opt:ubuntu: 114 | image: ubuntu:latest 115 | script: 116 | - make test-opt 117 | 118 | test-release:ubuntu: 119 | image: ubuntu:latest 120 | script: 121 | - make test-release 122 | 123 | test-debug:ubuntu: 124 | image: ubuntu:latest 125 | script: 126 | - make test-debug 127 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "make-it-quick"] 2 | path = make-it-quick 3 | url = ../make-it-quick 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | This software was brought to you by: 2 | 3 | - Christophe de Dinechin 4 | - Frediano Ziglio 5 | 6 | Thank you! 7 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # ****************************************************************************** 2 | # Makefile Recorder project 3 | # ****************************************************************************** 4 | # 5 | # File description: 6 | # 7 | # The top-level makefile for Recorder, a non-blocking flight recorder 8 | # for C or C++ programs 9 | # 10 | # 11 | # 12 | # 13 | # 14 | # 15 | # ****************************************************************************** 16 | # This software is licensed under the GNU Lesser General Public License v2+ 17 | # (C) 2017-2020, Christophe de Dinechin 18 | # ****************************************************************************** 19 | # This file is part of Recorder 20 | # 21 | # Recorder is free software: you can redistribute it and/or modify 22 | # it under the terms of the GNU Lesser General Public License as published by 23 | # the Free Software Foundation, either version 2 of the License, or 24 | # (at your option) any later version. 25 | # 26 | # Recorder is distributed in the hope that it will be useful, 27 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 28 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 29 | # GNU Lesser General Public License for more details. 30 | # 31 | # You should have received a copy of the GNU Lesser General Public License 32 | # along with Recorder, in a file named COPYING. 33 | # If not, see . 34 | # ****************************************************************************** 35 | 36 | SOURCES=recorder_ring.c recorder.c 37 | HEADERS=recorder_ring.h recorder.h 38 | PRODUCTS=recorder.dll 39 | PRODUCTS_VERSION=$(PACKAGE_VERSION) 40 | CONFIG=sigaction drand48 libregex setlinebuf 41 | MANPAGES=man/man1 man/man3 42 | 43 | # For pkg-config generation 44 | PACKAGE_NAME=recorder 45 | PACKAGE_VERSION=1.2.2 46 | PACKAGE_DESCRIPTION=Lock-free, real-time flight recorder for C or C++ programs 47 | PACKAGE_URL="http://github.com/c3d/recorder" 48 | PACKAGE_REQUIRES= 49 | PACKAGE_BUGS=christophe@dinechin.org 50 | 51 | TESTS= hanoi_test.c recorder_test.c ring_test.c crash_test.c 52 | TEST_ARGS_hanoi_test=20 2>&1 | grep "TIMING:" 53 | TEST_ARGS_crash_test= | grep "Signal handler for 11 called" 54 | 55 | MIQ=make-it-quick/ 56 | -include config.local-setup.mk 57 | LDFLAGS+= -lm -lpthread 58 | include $(MIQ)rules.mk 59 | 60 | # Override copy for man directories 61 | INSTALL.man=cp -r 62 | 63 | $(MIQ)rules.mk: 64 | git submodule update --init --recursive 65 | 66 | MAKEOVERRIDES:= 67 | scope: scope/Makefile 68 | scope: .ALWAYS 69 | cd scope && make 70 | scope/Makefile: 71 | cd scope && qmake 72 | .install: $(DO_INSTALL=scope/recorder_scope.$(DO_INSTALL)_exe) 73 | scope/recorder_scope.$(DO_INSTALL)_exe: scope 74 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | v1.2.2 Release 1.2.2 - Fix configure scripts to be more resilient 2 | 3 | - Ensure PREFIX.dll in the configuration script gets updated even if 4 | the environment sets it to some static value, e.g. /usr/lib 5 | 6 | v1.2.1 Release 1.2.1 - Fix configuration script 7 | 8 | Fix the configuration script for Fedora 9 | 10 | v1.2.0 Release 1.2.0 - Packaging updates and switch to Qt6 11 | 12 | - Add support for Qt6 in the recorder scope 13 | - Some documentation fixes 14 | - Fix a crash on exit of recorder scope 15 | - Make it possible to export more than 4 channels 16 | 17 | v1.1.0 Add support for recorder indentation 18 | - This adds the recorder_indent() function, hence a bump in minor number 19 | - Adding > at beginning of the format string indents 20 | - Adding < unindents 21 | - Adding = resets the indent 22 | - Documentation was updated accordingly 23 | 24 | v1.0.12 Add support for /trace in trace specifications 25 | - Add ability to disable all recording with /trace and trace=-1 26 | 27 | v1.0.11 Add support for -trace in trace specifications 28 | - Add ability to disable a trace with -trace in addition to trace=0 29 | 30 | v1.0.10 Add _Bool support 31 | - _Bool support was broken. Detected adding recorder support to qemu 32 | 33 | v1.0.9 Minor fixes 34 | - Update copyright header for recorder_scope.pro 35 | - Change the license for recorder_test.c from LGPL to GPL 36 | - Robustify the recorder signal handler 37 | - Add missing colon after recorder label 38 | - Fix name of 'recorder_signals' recorder 39 | - Fix format for signal (from %u to %d) 40 | - Small optimization for the common recorder_dump case 41 | - Setup alternate stack for signal handling 42 | - Use mmap() to create the alternate stack 43 | - Avoid calls to fprintf() from recorder_dump(), in order to avoid malloc() calls 44 | - Do not switch to alt stack if no mmap() - MinGW case 45 | - One more use of the %{name} macro 46 | - (gitlab/master) Fix errors or warnings in strict Ansi mode 47 | - ci: Add tests for variants such as fedora 28-31 48 | - ci: Add missing install 49 | 50 | v1.0.8 Address Fedora packaging review comments 51 | 52 | - Switch to LGPLv2+ for library, GPLv3+ for scope (for SPICE use) 53 | - Add %configure step to get the "hardened" CFLAGS 54 | - Make build verbose (temporarily at least) to make review easier 55 | - Minor changes in package descriptions spelling 56 | - Use only spaces in .spec file 57 | - Add explicit version to library name in .spec file 58 | - Avoid hardcoding a specific compression for man pages 59 | - Removing useless %postrun ldconfig path 60 | - Using `install -p` to preserve time stamps 61 | 62 | v1.0.7 Stabilization and packaging 63 | 64 | - Finalize Fedora .spec file 65 | - Add support for std::string in the recorder for C++ programs 66 | - Fix rpath for Linux libraries 67 | - Make it possible to display absolute time stamps (since midnight) 68 | - Fix the time display and recording with custom values of RECORDER_HZ 69 | - Add a function to list recorders 70 | - Fix printf format warnings in recorder_test.c 71 | _ Test: Add a simple output test for the crash case 72 | - Doc: Indicate that the recorder output is standard error by default 73 | - Offer the ability to disambiguate a command by prefixing it with `@` 74 | - Doc: Indicate that `record` or `RECORD` can be undefined 75 | - Put all recorder-specific recorders under "recorder_" 76 | - Add @output and @output_append options to recorder_trace_set 77 | - Fix issue with 64-bit test 78 | - Do not allocate a new line series is not used (memory leak) 79 | 80 | v1.0.6 Update copyright headers 81 | 82 | - Headers are now updated using an automated tool 83 | - Moved LICENSE to COPYING to conform with GNU recommendations 84 | 85 | v1.0.5 Release 1.0.5: Minor packaging improvements 86 | 87 | - Do not generate NEWS and AUTHORS, now checked in 88 | - Explain that the root of trust is recorder_trace_set, not RECORDER_TRACES 89 | - Add ENVIRONMENT VARIABLES section to recorder_scope.1 man page 90 | - Fix location of recorder_scope man page 91 | - Avoid duplicate file warnings during packaging 92 | 93 | v1.0.4 Add man pages and fix a crash risk when tracing with custom types 94 | 95 | v1.0.3 Release 1.0.3 96 | 97 | Changes in this release: 98 | - License clarification (LGPLv3 for the library components) 99 | - Improved portability on Windows platforms (MinGW, MSYS and Cygwin) 100 | - Record scope has the ability to toggle display modes using keys 101 | - Various minor fixes and improvements 102 | - Welcoming a new contributor to the code (Frediano Ziglio) 103 | -------------------------------------------------------------------------------- /alt_drand48.h: -------------------------------------------------------------------------------- 1 | #ifndef ALT_DRAND48_H 2 | #define ALT_DRAND48_H 3 | // ***************************************************************************** 4 | // alt_drand48.h Recorder project 5 | // ***************************************************************************** 6 | // 7 | // File description: 8 | // 9 | // An alternative drand48 implementation for platforms that don't have it 10 | // (most notably MinGW) 11 | // see http://pubs.opengroup.org/onlinepubs/7908799/xsh/drand48.html 12 | // 13 | // 14 | // 15 | // 16 | // 17 | // 18 | // ***************************************************************************** 19 | // This software is licensed under the GNU Lesser General Public License v2+ 20 | // (C) 2019-2020, Christophe de Dinechin 21 | // (C) 2018, Frediano Ziglio 22 | // ***************************************************************************** 23 | // This file is part of Recorder 24 | // 25 | // Recorder is free software: you can redistribute it and/or modify 26 | // it under the terms of the GNU Lesser General Public License as published by 27 | // the Free Software Foundation, either version 2 of the License, or 28 | // (at your option) any later version. 29 | // 30 | // Recorder is distributed in the hope that it will be useful, 31 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 32 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 33 | // GNU Lesser General Public License for more details. 34 | // 35 | // You should have received a copy of the GNU Lesser General Public License 36 | // along with Recorder, in a file named COPYING. 37 | // If not, see . 38 | // ***************************************************************************** 39 | 40 | #include "config.h" 41 | 42 | #ifndef HAVE_DRAND48 43 | // Missing functions on MinGW 44 | // see http://pubs.opengroup.org/onlinepubs/7908799/xsh/drand48.html 45 | static double drand48(void) 46 | { 47 | static uint64_t seed = 1; 48 | seed = (seed * UINT64_C(0x5DEECE66D) + 0xB) & UINT64_C(0xffffffffffff); 49 | return seed * (1.0 / ((double) UINT64_C(0x1000000000000))); 50 | } 51 | #endif // HAVE_DRAND48 52 | 53 | #endif // ALT_DRAND48_H 54 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ***************************************************************************** 3 | # configure Recorder project 4 | # ***************************************************************************** 5 | # 6 | # File Description: 7 | # 8 | # Configure makefile for 'recorder' with the most-common options 9 | # Those will be used by the Makefile to generate config.system-setup.mk 10 | # This script is mostly to inferface "like autoconf" for Fedora builds 11 | # 12 | # 13 | # 14 | # 15 | # 16 | # ***************************************************************************** 17 | # (C) 2019-2020 Christophe de Dinechin 18 | # This software is licensed under the GNU Lesser General Public License v2+ 19 | #****************************************************************************** 20 | # This file is part of Recorder 21 | # 22 | # Recorder is free software: you can redistribute it and/or 23 | # modify it under the terms of the GNU Lesser General Public 24 | # License as published by the Free Software Foundation, either 25 | # version 2 of the License, or (at your option) any later version. 26 | # 27 | # Recorder is distributed in the hope that it will be useful, 28 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 29 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 30 | # GNU General Public License for more details. 31 | # 32 | # You should have received a copy of the GNU General Public License 33 | # along with Recorder. If not, see . 34 | # ***************************************************************************** 35 | 36 | SETUP=config.local-setup.mk 37 | 38 | output() { 39 | echo $1 >> $SETUP 40 | } 41 | 42 | rm -f $SETUP 43 | 44 | # DD='$(DESTDIR)' 45 | while [ $# -ne 0 ]; do 46 | case "$1" in 47 | --build=*) output CONFIGURE.build=${1/--build=/}/;; 48 | --host=*) output CONFIGURE.host=${1/--host=/}/;; 49 | --program-prefix=*) output CONFIGURE.program-prefix=${1/--program-prefix=/}/;; 50 | --disable-dependency-tracking) output CONFIGURE.disable-dependency-tracking=1;; 51 | --prefix=*) output PREFIX=$DD${1/--prefix=/}/;; 52 | --exec-prefix=*) output PREFIX.exec=$DD${1/--exec-prefix=/}/;; 53 | --bindir=*) output PREFIX.bin=$DD${1/--bindir=/}/;; 54 | --sbindir=*) output PREFIX.sbin=$DD${1/--sbindir=/}/;; 55 | --sysconfigdir=*) output SYSCONFIG=$DD${1/--sysconfigdir=/}/;; 56 | --datadir=*) output PREFIX.share=$DD${1/--datadir=/}/;; 57 | --includedir=*) output PREFIX.h=$DD${1/--includedir=/}/;; 58 | --libdir=*) output PREFIX.lib=$DD${1/--libdir=/}/ 59 | output PREFIX.dll=$DD${1/--libdir=/}/;; 60 | --libexecdir=*) output PREFIX.libexec=$DD${1/--libexecdir=/}/;; 61 | --localstatedir=*) output PREFIX.var=$DD${1/--localstatedir=/}/;; 62 | --sharedstatedir==*) output PREFIX.str=$DD${1/--sharedstatedir=/}/;; 63 | --mandir==*) output PREFIX.man=$DD${1/--mandir=/}/;; 64 | --infodir==*) output PREFIX.info=$DD${1/--infodir=/}/;; 65 | esac 66 | shift 67 | done 68 | 69 | 70 | output "CFLAGS=$CFLAGS" 71 | output "CXXFLAGS=$CXXFLAGS" 72 | output "LDFLAGS=$LDFLAGS" 73 | 74 | echo "# Summary of configuration:" 75 | cat $SETUP 76 | -------------------------------------------------------------------------------- /crash_test.c: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // crash_test.c Recorder project 3 | // ***************************************************************************** 4 | // 5 | // File description: 6 | // 7 | // Test for the flight recorder 8 | // 9 | // This tests that we can dump something at crash time 10 | // 11 | // 12 | // 13 | // 14 | // 15 | // 16 | // ***************************************************************************** 17 | // This software is licensed under the GNU Lesser General Public License v2+ 18 | // (C) 2017-2020, Christophe de Dinechin 19 | // ***************************************************************************** 20 | // This file is part of Recorder 21 | // 22 | // Recorder is free software: you can redistribute it and/or modify 23 | // it under the terms of the GNU Lesser General Public License as published by 24 | // the Free Software Foundation, either version 2 of the License, or 25 | // (at your option) any later version. 26 | // 27 | // Recorder is distributed in the hope that it will be useful, 28 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 29 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 30 | // GNU Lesser General Public License for more details. 31 | // 32 | // You should have received a copy of the GNU Lesser General Public License 33 | // along with Recorder, in a file named COPYING. 34 | // If not, see . 35 | // ***************************************************************************** 36 | 37 | #include "recorder_ring.h" 38 | #include "recorder.h" 39 | 40 | #include 41 | #include 42 | #include 43 | 44 | 45 | int failed = 1; 46 | int *ptr = NULL; 47 | 48 | 49 | // ============================================================================ 50 | // 51 | // Main entry point 52 | // 53 | // ============================================================================ 54 | 55 | RECORDER(MAIN, 64, "Primary recorder for crash_test.c"); 56 | 57 | static void signal_handler(int sig) 58 | { 59 | record(MAIN, "Signal handler for %d called", sig); 60 | printf("Signal handler for %d called\n", sig); 61 | 62 | record(MAIN, "Restoring default signal handler"); 63 | signal(sig, SIG_DFL); 64 | exit(0); 65 | } 66 | 67 | 68 | int main(int argc, char **argv) 69 | { 70 | record(MAIN, "Starting crash test program"); 71 | 72 | record(MAIN, "Installing signal handler %p", signal_handler); 73 | #ifdef SIGBUS 74 | signal(SIGBUS, signal_handler); 75 | #endif // SIGBUS 76 | #ifdef SIGSEGV 77 | signal(SIGSEGV, signal_handler); 78 | #endif // SIGSEGV 79 | 80 | record(MAIN, "Installing recorder default signal handlers"); 81 | recorder_dump_on_common_signals(0, 0); 82 | 83 | record(MAIN, "Dereferencing a NULL pointer, ptr=%p", ptr); 84 | *ptr = 0; 85 | 86 | record(MAIN, "Checking results, ptr=%p failed=%d", ptr, failed); 87 | if (failed) 88 | fprintf(stderr, "The test failed (signal handler not invoked"); 89 | else 90 | fprintf(stderr, "The test succeeded (signal handler was invoked"); 91 | 92 | recorder_dump(); 93 | 94 | return failed; 95 | } 96 | -------------------------------------------------------------------------------- /hanoi_test.c: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // hanoi_test.c Recorder project 3 | // ***************************************************************************** 4 | // 5 | // File description: 6 | // 7 | // A simple illustration of the recorder on the Towers of Hanoi problem 8 | // 9 | // 10 | // 11 | // 12 | // 13 | // 14 | // 15 | // 16 | // ***************************************************************************** 17 | // This software is licensed under the GNU Lesser General Public License v2+ 18 | // (C) 2017-2020, Christophe de Dinechin 19 | // ***************************************************************************** 20 | // This file is part of Recorder 21 | // 22 | // Recorder is free software: you can redistribute it and/or modify 23 | // it under the terms of the GNU Lesser General Public License as published by 24 | // the Free Software Foundation, either version 2 of the License, or 25 | // (at your option) any later version. 26 | // 27 | // Recorder is distributed in the hope that it will be useful, 28 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 29 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 30 | // GNU Lesser General Public License for more details. 31 | // 32 | // You should have received a copy of the GNU Lesser General Public License 33 | // along with Recorder, in a file named COPYING. 34 | // If not, see . 35 | // ***************************************************************************** 36 | 37 | #include "recorder.h" 38 | #include 39 | #include 40 | 41 | 42 | RECORDER(MOVE, 1024, "Moving pieces around"); 43 | RECORDER(RECURSE, 1024, "Recursing"); 44 | RECORDER(TIMING, 32, "Timing information"); 45 | 46 | typedef enum post { LEFT, MIDDLE, RIGHT } post; 47 | const char *postName[] = { "LEFT", "MIDDLE", "RIGHT" }; 48 | 49 | void hanoi_print(int n, post left, post right, post middle) 50 | { 51 | if (n == 1) 52 | { 53 | printf("Move disk from %s to %s\n", postName[left], postName[right]); 54 | } 55 | else 56 | { 57 | printf("Recursing at level %d\n", n); 58 | hanoi_print(n-1, left, middle, right); 59 | hanoi_print(1, left, right, middle); 60 | hanoi_print(n-1, middle, right, left); 61 | printf("Exit recursion at level %d\n", n); 62 | } 63 | } 64 | 65 | void hanoi_record(int n, post left, post right, post middle) 66 | { 67 | if (n == 1) 68 | { 69 | record(MOVE,"Move disk from %s to %s", postName[left], postName[right]); 70 | } 71 | else 72 | { 73 | record(RECURSE, ">Recursing at level %d", n); 74 | hanoi_record(n-1, left, middle, right); 75 | hanoi_record(1, left, right, middle); 76 | hanoi_record(n-1, middle, right, left); 77 | record(RECURSE, "Recursing at level %d", n); 91 | hanoi_record_fast(n-1, left, middle, right); 92 | hanoi_record_fast(1, left, right, middle); 93 | hanoi_record_fast(n-1, middle, right, left); 94 | record_fast(RECURSE, " 17 | .\" %%%LICENSE_START(LGPLv2+_DOC_FULL) 18 | .\" This is free documentation; you can redistribute it and/or 19 | .\" modify it under the terms of the GNU Lesser General Public License as 20 | .\" published by the Free Software Foundation; either version 2 of 21 | .\" the License, or (at your option) any later version. 22 | .\" 23 | .\" The GNU Lesser General Public License's references to "object code" 24 | .\" and "executables" are to be interpreted as the output of any 25 | .\" document formatting or typesetting system, including 26 | .\" intermediate and printed output. 27 | .\" 28 | .\" This manual is distributed in the hope that it will be useful, 29 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 30 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 31 | .\" GNU Lesser General Public License for more details. 32 | .\" 33 | .\" You should have received a copy of the GNU General Public 34 | .\" License along with this manual; if not, see 35 | .\" . 36 | .\" %%%LICENSE_END 37 | .\" **************************************************************************** 38 | 39 | .TH recorder_scope 1 "2019-03-09" "1.0" "Recorder Library" 40 | 41 | .\" ---------------------------------------------------------------------------- 42 | .SH NAME 43 | .\" ---------------------------------------------------------------------------- 44 | record_scope \- A real-time graphing tool for recorded data 45 | 46 | 47 | .\" ---------------------------------------------------------------------------- 48 | .SH SYNOPSIS 49 | .\" ---------------------------------------------------------------------------- 50 | .B recorder_scope 51 | [options] 52 | .I channels 53 | 54 | 55 | .\" ---------------------------------------------------------------------------- 56 | .SH DESCRIPTION 57 | .\" ---------------------------------------------------------------------------- 58 | 59 | The 60 | .B recorder_scope 61 | program graphs the data collected by selected 62 | .BR record(3) 63 | statements in a program. It can also be used to update recorder 64 | configuration in real-time while the instrumented program is running. 65 | 66 | .PP 67 | The 68 | .I channels 69 | to display are selected using regular expressions. They must have been 70 | shared by the instrumented program using 71 | .BR recorder_trace_set(3) 72 | typically by setting the 73 | .B RECORDER_TRACES 74 | environment variable, or any other method that your program uses 75 | to pass a recorder configuration string to the 76 | .BR recorder_trace_set(3) 77 | function. 78 | 79 | 80 | .\" ---------------------------------------------------------------------------- 81 | .SH OPTIONS 82 | .\" ---------------------------------------------------------------------------- 83 | 84 | .PP 85 | The program accepts the following options: 86 | 87 | .TP 88 | .B \-c config 89 | Send the configuration command to the instrumented program. This makes 90 | it possible for example to dynamically adjust tracing options. 91 | 92 | .TP 93 | .B \-s slider 94 | Setup a slider on the user interface that can be used to control a 95 | specific trace value, see 96 | .BR RECORDER_TRACE(3). 97 | The slider syntax is 98 | .IR name = initial : min : max 99 | where 100 | .I name 101 | is the name of the recorder to configure, 102 | .I initial 103 | is its initial value, 104 | .I min 105 | is the lowest possible value on the slider and 106 | .I max 107 | is the highest possible value on the slider. 108 | 109 | .TP 110 | .B \-d duration 111 | Set the maximum duration in seconds for the data being displayed on the 112 | window. Data older than this duration will not be shown. 113 | 114 | .TP 115 | .B \-w samples 116 | Set the sample window, i.e. the maximum number of samples displayed 117 | simultaneously. A value of 0 will automatically select the window 118 | width in pixels. 119 | 120 | .TP 121 | .B \-t 122 | Toggle timing display in the graphs. The same effect can be achieved 123 | while the program is running using the 't' key. 124 | 125 | .TP 126 | .B \-m 127 | Toggle display of minimum and maximum values in the graphs. The same effect can be achieved 128 | while the program is running using the 'm' key. 129 | 130 | .TP 131 | .B \-a 132 | Toggle display of running average values in the graphs. The same 133 | effect can be achieved while the program is running using the 'a' key. 134 | 135 | .TP 136 | .B \-n 137 | Toggle display of the normal values in the graphs (for example if 138 | you are only interested in min and max values). The same 139 | effect can be achieved while the program is running using the 'n' key. 140 | 141 | .TP 142 | .B \-r ratio 143 | Set averaging ratio for \-m and \-a options, in percent. 144 | 145 | .TP 146 | .B \-b basename 147 | Set the basename for file when saving data or screen shots. 148 | 149 | .TP 150 | .B \-g WxH@XxY 151 | Set the window geometry to W x H pixels at position (X, Y) 152 | 153 | .\" ---------------------------------------------------------------------------- 154 | .SH EXAMPLES 155 | .\" ---------------------------------------------------------------------------- 156 | .PP 157 | If the instrumented program contains the following recorder instrumentation: 158 | 159 | .PP 160 | .EX 161 | int rate = RECORDER_TWEAK(rate); 162 | record(my_recorder, "The value of %d is %d", x, y * rate); 163 | .EE 164 | 165 | .PP 166 | then the values of 167 | .I x 168 | and 169 | .I y 170 | can be exported by running the program instrumented. For example, 171 | setting the environment variable 172 | .B RECORDER_TRACES 173 | to the value 174 | .B "my_recorder=x_value,y_value" 175 | will export the first two data values in recorder 176 | .I my_recorder 177 | under the names 178 | .I x_value 179 | and 180 | .I y_value 181 | for use by 182 | .B recorder_scope. 183 | 184 | .PP 185 | To graph these values, you would use: 186 | 187 | .PP 188 | .EX 189 | % recorder_scope x_value y_value 190 | .EE 191 | 192 | 193 | .PP 194 | To enable tracing for all recorders whose names end in "error": 195 | 196 | .PP 197 | .EX 198 | % recorder_scope -c '.*error' 199 | .EE 200 | 201 | .PP 202 | To set the 'rate' tweak to 10, giving the value of 203 | .B RECORDER_TRACE(rate) 204 | or 205 | .B RECORDER_TWEAK(rate) 206 | the value 10: 207 | 208 | .PP 209 | .EX 210 | % recorder_scope -c 'rate=10' 211 | .EE 212 | 213 | .PP 214 | To adjust the value of 'rate' using a slider starting at value 10 and 215 | where the values can be set between 10 and 39: 216 | 217 | .PP 218 | .EX 219 | % recorder_scope -s 'rate=10:2:39' 220 | .EE 221 | 222 | 223 | .\" ---------------------------------------------------------------------------- 224 | .SH ENVIRONMENT VARIABLES 225 | .\" ---------------------------------------------------------------------------- 226 | .PP 227 | The path to the shared memory file can be specified using the 228 | .B RECORDER_SHARE 229 | environment variable. That variable should be set consistently between 230 | the instrumented program and the 231 | .BR recorder_scope 232 | application. 233 | 234 | 235 | .\" ---------------------------------------------------------------------------- 236 | .SH BUGS 237 | .\" ---------------------------------------------------------------------------- 238 | .PP 239 | The 240 | .B recorder_scope 241 | program normally attempts to use OpenGL for faster rendering. This may 242 | not be convenient for example over a remote X11 connexion. In that 243 | case, you should set the environment variable 244 | .B RECORDER_NOGL=1 245 | and will have significantly lower performance. 246 | 247 | 248 | .PP 249 | Bugs should be reported using https://github.com/c3d/recorder/issues. 250 | 251 | 252 | .\" ---------------------------------------------------------------------------- 253 | .SH SEE ALSO 254 | .\" ---------------------------------------------------------------------------- 255 | .BR record(3), recorder_trace_set(3) 256 | 257 | .PP 258 | Additional documentation and tutorials can be found 259 | at https://github.com/c3d/recorder. 260 | 261 | 262 | .\" ---------------------------------------------------------------------------- 263 | .SH AUTHOR 264 | .\" ---------------------------------------------------------------------------- 265 | Written by Christophe de Dinechin 266 | -------------------------------------------------------------------------------- /man/man3/RECORDER.3: -------------------------------------------------------------------------------- 1 | .\" **************************************************************************** 2 | .\" RECORDER.3 recorder library 3 | .\" **************************************************************************** 4 | .\" 5 | .\" File Description: 6 | .\" 7 | .\" Man page for the recorder library 8 | .\" 9 | .\" This documents RECORDER, RECORDER_DEFINE and RECORDER_DECLARE 10 | .\" 11 | .\" 12 | .\" 13 | .\" 14 | .\" 15 | .\" 16 | .\" **************************************************************************** 17 | .\" (C) 2019-2020 Christophe de Dinechin 18 | .\" %%%LICENSE_START(LGPLv2+_DOC_FULL) 19 | .\" This is free documentation; you can redistribute it and/or 20 | .\" modify it under the terms of the GNU Lesser General Public License as 21 | .\" published by the Free Software Foundation; either version 2 of 22 | .\" the License, or (at your option) any later version. 23 | .\" 24 | .\" The GNU Lesser General Public License's references to "object code" 25 | .\" and "executables" are to be interpreted as the output of any 26 | .\" document formatting or typesetting system, including 27 | .\" intermediate and printed output. 28 | .\" 29 | .\" This manual is distributed in the hope that it will be useful, 30 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 31 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 32 | .\" GNU Lesser General Public License for more details. 33 | .\" 34 | .\" You should have received a copy of the GNU General Public 35 | .\" License along with this manual; if not, see 36 | .\" . 37 | .\" %%%LICENSE_END 38 | .\" **************************************************************************** 39 | 40 | .TH RECORDER 3 "2019-03-09" "1.0" "Recorder Library" 41 | 42 | .\" ---------------------------------------------------------------------------- 43 | .SH NAME 44 | .\" ---------------------------------------------------------------------------- 45 | RECORDER, RECORDER_DEFINE, RECORDER_DECLARE \- Declare and define buffers to record events 46 | 47 | 48 | .\" ---------------------------------------------------------------------------- 49 | .SH SYNOPSIS 50 | .\" ---------------------------------------------------------------------------- 51 | .nf 52 | .B #include 53 | .PP 54 | .BI "#define RECORDER(" recname ", " recsize ", " rechelp ") ..." 55 | .BI "#define RECORDER_DEFINE(" recname ", " recsize ", " rechelp ") ..." 56 | .BI "#define RECORDER_DECLARE(" recname ") ..." 57 | .fi 58 | .PP 59 | 60 | 61 | .\" ---------------------------------------------------------------------------- 62 | .SH DESCRIPTION 63 | .\" ---------------------------------------------------------------------------- 64 | .PP 65 | The 66 | .BR RECORDER() 67 | macro defines a buffer named 68 | .I recname 69 | to store up to 70 | .I recsize 71 | events for use by the 72 | .BR record (3) 73 | function. The 74 | .I rechelp 75 | string is self-documentation describing the events stored in the buffer. 76 | This self-documentation can be displayed by activating the 77 | .BR help 78 | trace, see 79 | .BR recorder_set_trace (3). 80 | 81 | .PP 82 | The 83 | .BR RECORDER_DECLARE() 84 | macro declares an event recorder, and can be used in header files. 85 | 86 | .PP 87 | The 88 | .BR RECORDER_DEFINE() 89 | macro is an alias for 90 | .BR RECORDER(), 91 | the intention being that you would use 92 | .BR RECORDER() 93 | for event recorders that are local to a file, or a 94 | .BR RECORDER_DECLARE() 95 | (in the header file) paired with 96 | .BR RECORDER_DEFINE() 97 | (in a non-header file) for event recorders that are shared across 98 | multiple translation units. 99 | 100 | .PP 101 | These macros should be used at file scope, i.e. where a function 102 | definition or global variable is allowed. 103 | 104 | .PP 105 | Each recorder is associated with a 106 | .I trace value 107 | which is an integer value that can be read using 108 | .BR RECORDER_TRACE (3). 109 | See 110 | .BR recorder_trace_set (3). 111 | 112 | 113 | .\" ---------------------------------------------------------------------------- 114 | .SH EXAMPLES 115 | .\" ---------------------------------------------------------------------------- 116 | .PP 117 | The program below records its input arguments, and crashes if passed 118 | .I crash 119 | as the first command-line argument. 120 | .PP 121 | .in +4n 122 | .EX 123 | #include 124 | #include 125 | 126 | RECORDER(program_args, 32, "Program command-line arguments"); 127 | int main(int argc, char **argv) 128 | { 129 | int a; 130 | recorder_dump_on_common_signals(0, 0); 131 | for (a = 0; a < argc; a++) 132 | record(program_args, "Argument %d is %+s", a, argv[a]); 133 | 134 | if (argc >= 2 && strcmp(argv[1], "crash") == 0) 135 | { 136 | char *ptr = NULL; 137 | strcpy(ptr, argv[1]); 138 | } 139 | } 140 | .EE 141 | .in -4n 142 | .PP 143 | When a crash occurs, previously recorded events are printed out on the 144 | console. 145 | 146 | .PP 147 | The program below is an instrumented version of the classical 148 | recursive Fibonacci computation. It uses several recorders 149 | corresponding to different types of events, and activates warnings and 150 | errors in a way that can be configured by setting an environment variable. 151 | .PP 152 | .in +4n 153 | .EX 154 | #include 155 | #include 156 | #include 157 | #include 158 | 159 | RECORDER(fib_main, 32, "Loops in fib function"); 160 | RECORDER(fib_loops, 32, "Loops in fib function"); 161 | RECORDER(fib_warning, 32, "Warnings in fib function"); 162 | RECORDER(fib_error, 32, "Errors in fib function"); 163 | 164 | int fib(int n) 165 | { 166 | if (n <= 1) { 167 | if (n < 0) 168 | record(fib_error, "fib is undefined for negative value %d", n); 169 | return n; 170 | } 171 | record(fib_loops, "Computing fib(%d)", n); 172 | int result = fib(n-1) + fib(n-2); 173 | record(fib_loops, "Computed fib(%d) = %d", n, result); 174 | return result; 175 | } 176 | 177 | int main(int argc, char **argv) 178 | { 179 | int a; 180 | recorder_dump_on_common_signals(0, 0); 181 | recorder_trace_set(".*_warning=35 .*_error"); 182 | recorder_trace_set(getenv("FIB_TRACES")); 183 | for (a = 1; a < argc; a++) { 184 | int n = atoi(argv[a]); 185 | if (n >= RECORDER_TRACE(fib_warning)) 186 | record(fib_warning, "Computing for %d may take a while", n); 187 | printf("fib(%d) = %d\n", n, fib(n)); 188 | if (n >= RECORDER_TRACE(fib_warning)) 189 | record(fib_warning, "Computation for %d finally completed", n); 190 | } 191 | } 192 | .EE 193 | .in -4n 194 | .PP 195 | This program will produce an output similar to the following: 196 | .PP 197 | .in +4n 198 | .EX 199 | % fib 1 2 3 4 10 20 30 35 10 40 -1 200 | fib(1) = 1 201 | fib(2) = 1 202 | fib(3) = 2 203 | fib(4) = 3 204 | fib(10) = 55 205 | fib(20) = 6765 206 | fib(30) = 832040 207 | [2714667 0.177725] fib_warning: Computing for 35 may take a while 208 | fib(35) = 9227465 209 | [32575370 1.859156] fib_warning: Computation for 35 finally completed 210 | fib(10) = 55 211 | [32575547 1.859171] fib_warning: Computing for 40 may take a while 212 | fib(40) = 102334155 213 | [363735828 20.527882] fib_warning: Computation for 40 finally completed 214 | [363735829 20.527887] fib_error: fib is undefined for negative value -1 215 | fib(-1) = -1 216 | .EE 217 | .in -4n 218 | The first column in trace outputs is the number of events that were 219 | recorded. THe second column is the time in seconds since the program 220 | started. 221 | 222 | .PP 223 | The same program can also be run with additional tracing or warnings, 224 | for example: 225 | .PP 226 | .in +4n 227 | .EX 228 | % FIB_TRACES="recorder_location fib_loops fib_warning=3" /tmp/fib 3 4 229 | /tmp/fib.c:33:[82 0.000496] fib_warning: Computing for 3 may take a while 230 | /tmp/fib.c:18:[83 0.000561] fib_loops: Computing fib(3) 231 | /tmp/fib.c:18:[84 0.000570] fib_loops: Computing fib(2) 232 | /tmp/fib.c:20:[85 0.000575] fib_loops: Computed fib(2) = 1 233 | /tmp/fib.c:20:[86 0.000581] fib_loops: Computed fib(3) = 2 234 | fib(3) = 2 235 | /tmp/fib.c:36:[87 0.000590] fib_warning: Computation for 3 finally completed 236 | /tmp/fib.c:33:[88 0.000596] fib_warning: Computing for 4 may take a while 237 | /tmp/fib.c:18:[89 0.000601] fib_loops: Computing fib(4) 238 | /tmp/fib.c:18:[90 0.000607] fib_loops: Computing fib(3) 239 | /tmp/fib.c:18:[91 0.000612] fib_loops: Computing fib(2) 240 | /tmp/fib.c:20:[92 0.000619] fib_loops: Computed fib(2) = 1 241 | /tmp/fib.c:20:[93 0.000625] fib_loops: Computed fib(3) = 2 242 | /tmp/fib.c:18:[94 0.000664] fib_loops: Computing fib(2) 243 | /tmp/fib.c:20:[95 0.000707] fib_loops: Computed fib(2) = 1 244 | /tmp/fib.c:20:[96 0.000724] fib_loops: Computed fib(4) = 3 245 | fib(4) = 3 246 | /tmp/fib.c:36:[97 0.000741] fib_warning: Computation for 4 finally completed 247 | .EE 248 | .in -4n 249 | 250 | 251 | .\" ---------------------------------------------------------------------------- 252 | .SH BUGS 253 | .\" ---------------------------------------------------------------------------- 254 | .PP 255 | Incorrect use of the macros generally results in nonsensical compiler diagnostics. 256 | 257 | .PP 258 | Bugs should be reported using https://github.com/c3d/recorder/issues. 259 | 260 | 261 | .\" ---------------------------------------------------------------------------- 262 | .SH SEE ALSO 263 | .\" ---------------------------------------------------------------------------- 264 | .BR record (3), 265 | .BR record_fast (3) 266 | .br 267 | .BR recorder_trace_set (3) 268 | .BR RECORDER_TRACE (3) 269 | .br 270 | .BR recorder_dump (3), 271 | .BR recorder_dump_for (3), 272 | .br 273 | .BR recorder_configure_output (3), 274 | .BR recorder_configure_show (3) 275 | .br 276 | .BR recorder_configure_format (3), 277 | .BR recorder_configure_type (3) 278 | 279 | .PP 280 | Additional documentation and tutorials can be found 281 | at https://github.com/c3d/recorder. 282 | 283 | 284 | .\" ---------------------------------------------------------------------------- 285 | .SH AUTHOR 286 | .\" ---------------------------------------------------------------------------- 287 | Written by Christophe de Dinechin 288 | -------------------------------------------------------------------------------- /man/man3/RECORDER_DECLARE.3: -------------------------------------------------------------------------------- 1 | \" RECORDER_DECLARE documented in RECORDER 2 | .so man3/RECORDER.3 3 | -------------------------------------------------------------------------------- /man/man3/RECORDER_DEFINE.3: -------------------------------------------------------------------------------- 1 | \" RECORDER_DEFINE documented in RECORDER 2 | .so man3/RECORDER.3 3 | -------------------------------------------------------------------------------- /man/man3/RECORDER_TRACE.3: -------------------------------------------------------------------------------- 1 | \" RECORDER_TRACE documented in recorder_trace_set 2 | .so man3/recorder_trace_set.3 3 | -------------------------------------------------------------------------------- /man/man3/RECORDER_TWEAK.3: -------------------------------------------------------------------------------- 1 | \" RECORDER_TWEAK documented in RECORDER_TRACE 2 | .so man3/RECORDER_TRACE.3 3 | -------------------------------------------------------------------------------- /man/man3/record.3: -------------------------------------------------------------------------------- 1 | .\" **************************************************************************** 2 | .\" record.3 recorder library 3 | .\" **************************************************************************** 4 | .\" 5 | .\" File Description: 6 | .\" 7 | .\" Man page for the recorder library 8 | .\" 9 | .\" This documents record(3) and record_fast(3) 10 | .\" 11 | .\" 12 | .\" 13 | .\" 14 | .\" 15 | .\" 16 | .\" **************************************************************************** 17 | .\" (C) 2019-2020 Christophe de Dinechin 18 | .\" %%%LICENSE_START(LGPLv2+_DOC_FULL) 19 | .\" This is free documentation; you can redistribute it and/or 20 | .\" modify it under the terms of the GNU Lesser General Public License as 21 | .\" published by the Free Software Foundation; either version 2 of 22 | .\" the License, or (at your option) any later version. 23 | .\" 24 | .\" The GNU Lesser General Public License's references to "object code" 25 | .\" and "executables" are to be interpreted as the output of any 26 | .\" document formatting or typesetting system, including 27 | .\" intermediate and printed output. 28 | .\" 29 | .\" This manual is distributed in the hope that it will be useful, 30 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 31 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 32 | .\" GNU Lesser General Public License for more details. 33 | .\" 34 | .\" You should have received a copy of the GNU General Public 35 | .\" License along with this manual; if not, see 36 | .\" . 37 | .\" %%%LICENSE_END 38 | .\" **************************************************************************** 39 | 40 | .TH record 3 "2019-03-09" "1.0" "Recorder Library" 41 | 42 | .\" ---------------------------------------------------------------------------- 43 | .SH NAME 44 | .\" ---------------------------------------------------------------------------- 45 | record, record_fast \- Fast event logging in circular buffers 46 | 47 | 48 | .\" ---------------------------------------------------------------------------- 49 | .SH SYNOPSIS 50 | .\" ---------------------------------------------------------------------------- 51 | .nf 52 | .B #include 53 | .PP 54 | .BI "ringidx_t record(recorder_name " recname ", const char *" format ", ...);" 55 | .BI "ringidx_t record_fast(recorder_name " recname ", const char *" format ", ...);" 56 | .fi 57 | .PP 58 | 59 | 60 | .\" ---------------------------------------------------------------------------- 61 | .SH DESCRIPTION 62 | .\" ---------------------------------------------------------------------------- 63 | .PP 64 | The 65 | .BR record() 66 | function stores an event in the recorder identified by 67 | .I recname. 68 | The event record includes a time stamp provided by the 69 | .BR gettimeofday(3) 70 | function (or a similar absolute time function), as well as the 71 | .I format 72 | string and up to 12 additional arguments. The 73 | .I format 74 | string should follow the 75 | .BR printf(3) 76 | conventions, with the exceptions and additions indicated below. 77 | 78 | .PP 79 | The 80 | .BR record_fast() 81 | reuses the time stamp of the previous 82 | .BR record() 83 | function for the same recorder, making it approximately twice as fast 84 | on some modern systems. The cost difference should be in the order 85 | of the cost of a call like 86 | .BR gettimeofday(3) 87 | 88 | .PP 89 | Event recording code is designed to remain active in your program at 90 | all times. Unlike typical 91 | .BR printf(3) 92 | messages, events recorded by 93 | .BR record() 94 | are not generally intended to be seen immediately, but rather to be 95 | shown laster, for example in case of crash. See 96 | .BR recorder_dump(3). 97 | Formatting only happens when the message is actually printed. 98 | 99 | .PP 100 | Events can also be 101 | .IR traced 102 | on a per-recorder basis. In that case, the 103 | .BR record() 104 | and 105 | .BR record_fast() 106 | functions will immediately format their output. See 107 | .BR recorder_set_trace(3) 108 | 109 | .SS Automatic end of line 110 | .PP 111 | Event records are always printed on an individual line. It is not 112 | necessary to add a \fB\en\fR character at the end of the format 113 | string. If one is present, it will have no effect. 114 | 115 | .EX 116 | record(my_recorder, "Hello"); 117 | record(my_recorder, "World\n"); 118 | .EE 119 | 120 | .PP 121 | The use of \fB\en\fR at other positions in the string is 122 | possible and has the usual effect. 123 | 124 | .SS Formatting character strings: %s and %+s 125 | .PP 126 | Since formatting an event may happen at an unknown time after its 127 | recording, the \fB%s\fR and \f%S\fR formats are treated like \fB%p\fR 128 | unless formatting happens as part of tracing. If you are passing a 129 | character string that is known to be constant, or at least still valid 130 | when it will be formatted, then you can use the \fB%+s\fR format 131 | instead. 132 | 133 | .EX 134 | const char *name[2] = { "false", "true" }; 135 | record(my_recorder, "Value %d is %+s", value, name[!!value]); 136 | .EE 137 | 138 | 139 | .SS Extended format characters 140 | .PP 141 | It is possible to configure additional format characters. See 142 | .BR recorder_configure_type. 143 | When this function is used, format strings may deviate notably from 144 | the 145 | .BR printf(3) 146 | standard, or may conflict with other extensions to that standard. 147 | 148 | 149 | .\" ---------------------------------------------------------------------------- 150 | .SH RETURN VALUE 151 | .\" ---------------------------------------------------------------------------- 152 | .PP 153 | The return value is the index of the event that was recorded. This 154 | value is rarely useful and can generally be ignored. 155 | 156 | 157 | .\" ---------------------------------------------------------------------------- 158 | .SH EXAMPLES 159 | .\" ---------------------------------------------------------------------------- 160 | .PP 161 | The program below records its input arguments, and crashes if passed 162 | .I crash 163 | as the first command-line argument. 164 | .PP 165 | .in +4n 166 | .EX 167 | #include 168 | #include 169 | 170 | RECORDER(program_args, 32, "Program command-line arguments"); 171 | int main(int argc, char **argv) 172 | { 173 | int a; 174 | recorder_dump_on_common_signals(0, 0); 175 | for (a = 0; a < argc; a++) 176 | record(program_args, "Argument %d is %+s", a, argv[a]); 177 | 178 | if (argc >= 2 && strcmp(argv[1], "crash") == 0) 179 | { 180 | char *ptr = NULL; 181 | strcpy(ptr, argv[1]); 182 | } 183 | } 184 | .EE 185 | .in -4n 186 | .PP 187 | When a crash occurs, previously recorded events are printed out on the 188 | console. 189 | 190 | .PP 191 | The program below is an instrumented version of the classical 192 | recursive Fibonacci computation. It uses several recorders 193 | corresponding to different types of events, and activates warnings and 194 | errors in a way that can be configured by setting an environment variable. 195 | .PP 196 | .in +4n 197 | .EX 198 | #include 199 | #include 200 | #include 201 | #include 202 | 203 | RECORDER(fib_main, 32, "Loops in fib function"); 204 | RECORDER(fib_loops, 32, "Loops in fib function"); 205 | RECORDER(fib_warning, 32, "Warnings in fib function"); 206 | RECORDER(fib_error, 32, "Errors in fib function"); 207 | 208 | int fib(int n) 209 | { 210 | if (n <= 1) { 211 | if (n < 0) 212 | record(fib_error, "fib is undefined for negative value %d", n); 213 | return n; 214 | } 215 | record(fib_loops, "Computing fib(%d)", n); 216 | int result = fib(n-1) + fib(n-2); 217 | record(fib_loops, "Computed fib(%d) = %d", n, result); 218 | return result; 219 | } 220 | 221 | int main(int argc, char **argv) 222 | { 223 | int a; 224 | recorder_dump_on_common_signals(0, 0); 225 | recorder_trace_set(".*_warning=35 .*_error"); 226 | recorder_trace_set(getenv("FIB_TRACES")); 227 | for (a = 1; a < argc; a++) { 228 | int n = atoi(argv[a]); 229 | if (n >= RECORDER_TRACE(fib_warning)) 230 | record(fib_warning, "Computing for %d may take a while", n); 231 | printf("fib(%d) = %d\n", n, fib(n)); 232 | if (n >= RECORDER_TRACE(fib_warning)) 233 | record(fib_warning, "Computation for %d finally completed", n); 234 | } 235 | } 236 | .EE 237 | .in -4n 238 | .PP 239 | This program will produce an output similar to the following: 240 | .PP 241 | .in +4n 242 | .EX 243 | % fib 1 2 3 4 10 20 30 35 10 40 -1 244 | fib(1) = 1 245 | fib(2) = 1 246 | fib(3) = 2 247 | fib(4) = 3 248 | fib(10) = 55 249 | fib(20) = 6765 250 | fib(30) = 832040 251 | [2714667 0.177725] fib_warning: Computing for 35 may take a while 252 | fib(35) = 9227465 253 | [32575370 1.859156] fib_warning: Computation for 35 finally completed 254 | fib(10) = 55 255 | [32575547 1.859171] fib_warning: Computing for 40 may take a while 256 | fib(40) = 102334155 257 | [363735828 20.527882] fib_warning: Computation for 40 finally completed 258 | [363735829 20.527887] fib_error: fib is undefined for negative value -1 259 | fib(-1) = -1 260 | .EE 261 | .in -4n 262 | The first column in trace outputs is the number of events that were 263 | recorded. THe second column is the time in seconds since the program 264 | started. 265 | 266 | .PP 267 | The same program can also be run with additional tracing or warnings, 268 | for example: 269 | .PP 270 | .in +4n 271 | .EX 272 | % FIB_TRACES="recorder_location fib_loops fib_warning=3" /tmp/fib 3 4 273 | /tmp/fib.c:33:[82 0.000496] fib_warning: Computing for 3 may take a while 274 | /tmp/fib.c:18:[83 0.000561] fib_loops: Computing fib(3) 275 | /tmp/fib.c:18:[84 0.000570] fib_loops: Computing fib(2) 276 | /tmp/fib.c:20:[85 0.000575] fib_loops: Computed fib(2) = 1 277 | /tmp/fib.c:20:[86 0.000581] fib_loops: Computed fib(3) = 2 278 | fib(3) = 2 279 | /tmp/fib.c:36:[87 0.000590] fib_warning: Computation for 3 finally completed 280 | /tmp/fib.c:33:[88 0.000596] fib_warning: Computing for 4 may take a while 281 | /tmp/fib.c:18:[89 0.000601] fib_loops: Computing fib(4) 282 | /tmp/fib.c:18:[90 0.000607] fib_loops: Computing fib(3) 283 | /tmp/fib.c:18:[91 0.000612] fib_loops: Computing fib(2) 284 | /tmp/fib.c:20:[92 0.000619] fib_loops: Computed fib(2) = 1 285 | /tmp/fib.c:20:[93 0.000625] fib_loops: Computed fib(3) = 2 286 | /tmp/fib.c:18:[94 0.000664] fib_loops: Computing fib(2) 287 | /tmp/fib.c:20:[95 0.000707] fib_loops: Computed fib(2) = 1 288 | /tmp/fib.c:20:[96 0.000724] fib_loops: Computed fib(4) = 3 289 | fib(4) = 3 290 | /tmp/fib.c:36:[97 0.000741] fib_warning: Computation for 4 finally completed 291 | .EE 292 | .in -4n 293 | 294 | 295 | .\" ---------------------------------------------------------------------------- 296 | .SH BUGS 297 | .\" ---------------------------------------------------------------------------- 298 | .PP 299 | There is a limit to the number of arguments that can be passed to 300 | .BR record() 301 | and 302 | .BR record_fast(). 303 | As of version 1.0, the maximum number is 12. If that number is 304 | exceeded, the compilation error message is not friendly. 305 | 306 | .PP 307 | Bugs should be reported using https://github.com/c3d/recorder/issues. 308 | 309 | 310 | .\" ---------------------------------------------------------------------------- 311 | .SH SEE ALSO 312 | .\" ---------------------------------------------------------------------------- 313 | .BR RECORDER_DEFINE (3), 314 | .BR RECORDER_DECLARE (3) 315 | .br 316 | .BR recorder_trace_set (3) 317 | .BR RECORDER_TRACE (3) 318 | .br 319 | .BR recorder_dump (3), 320 | .BR recorder_dump_for (3), 321 | .br 322 | .BR recorder_configure_output (3), 323 | .BR recorder_configure_show (3) 324 | .br 325 | .BR recorder_configure_format (3), 326 | .BR recorder_configure_type (3) 327 | 328 | .PP 329 | Additional documentation and tutorials can be found 330 | at https://github.com/c3d/recorder. 331 | 332 | 333 | .\" ---------------------------------------------------------------------------- 334 | .SH AUTHOR 335 | .\" ---------------------------------------------------------------------------- 336 | Written by Christophe de Dinechin 337 | -------------------------------------------------------------------------------- /man/man3/record_fast.3: -------------------------------------------------------------------------------- 1 | \" record fast documented in record 2 | .so man3/record.3 3 | -------------------------------------------------------------------------------- /man/man3/recorder_configure_format.3: -------------------------------------------------------------------------------- 1 | \" recorder_configure_format documented in recorder_configure_output 2 | .so man3/recorder_configure_output.3 3 | -------------------------------------------------------------------------------- /man/man3/recorder_configure_output.3: -------------------------------------------------------------------------------- 1 | .\" **************************************************************************** 2 | .\" record_configure_output.3 recorder library 3 | .\" **************************************************************************** 4 | .\" 5 | .\" File Description: 6 | .\" 7 | .\" Man page for the recorder library 8 | .\" 9 | .\" This documents 10 | .\" record_configure_output(3) 11 | .\" record_configure_show(3) 12 | .\" record_configure_format(3) 13 | .\" 14 | .\" 15 | .\" **************************************************************************** 16 | .\" (C) 2019-2020 Christophe de Dinechin 17 | .\" %%%LICENSE_START(LGPLv3+_DOC_FULL) 18 | .\" This is free documentation; you can redistribute it and/or 19 | .\" modify it under the terms of the GNU Lesser General Public License as 20 | .\" published by the Free Software Foundation; either version 2 of 21 | .\" the License, or (at your option) any later version. 22 | .\" 23 | .\" The GNU Lesser General Public License's references to "object code" 24 | .\" and "executables" are to be interpreted as the output of any 25 | .\" document formatting or typesetting system, including 26 | .\" intermediate and printed output. 27 | .\" 28 | .\" This manual is distributed in the hope that it will be useful, 29 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 30 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 31 | .\" GNU Lesser General Public License for more details. 32 | .\" 33 | .\" You should have received a copy of the GNU General Public 34 | .\" License along with this manual; if not, see 35 | .\" . 36 | .\" %%%LICENSE_END 37 | .\" **************************************************************************** 38 | 39 | .TH record 3 "2019-03-09" "1.0" "Recorder Library" 40 | 41 | .\" ---------------------------------------------------------------------------- 42 | .SH NAME 43 | .\" ---------------------------------------------------------------------------- 44 | 45 | record_configure_output \- Configure recorder output 46 | record_configure_show \- Configure recorder output function 47 | record_configure_format \- Configure recorder event formatting 48 | 49 | 50 | .\" ---------------------------------------------------------------------------- 51 | .SH SYNOPSIS 52 | .\" ---------------------------------------------------------------------------- 53 | .nf 54 | .B #include 55 | .PP 56 | .BI "typedef unsigned (*recorder_show_fn) (const char *" text "," 57 | .BI " size_t" len "," 58 | .BI " void *" output ");" 59 | .BI "typedef void (*recorder_format_fn)(recorder_show_fn " show "," 60 | .BI " void *" output "," 61 | .BI " const char * "label "," 62 | .BI " const char *" location "," 63 | .BI " uintptr_t" order "," 64 | .BI " uintptr_t" timestamp "," 65 | .BI " const char *" message ");" 66 | .PP 67 | .BI "void *recorder_configure_output(void *" output ");" 68 | .BI "recorder_show_fn recorder_configure_show(recorder_show_fn" show ");" 69 | .BI "recorder_format_fn recorder_configure_format(recorder_format_fn" format ");" 70 | 71 | .fi 72 | .PP 73 | 74 | 75 | .\" ---------------------------------------------------------------------------- 76 | .SH DESCRIPTION 77 | .\" ---------------------------------------------------------------------------- 78 | .PP 79 | The 80 | .BR recorder_configure_output() 81 | function configures the output for the recorder output function. The default 82 | output function assumes that 83 | .I output 84 | is a 85 | .B FILE * 86 | and uses 87 | .B stderr 88 | if NULL. Since recorder tracing is often used for debugging, it may be 89 | a good idea to make the output non-buffered using 90 | .B "setvbuf(output,NULL,_IONBF,0);" 91 | 92 | .PP 93 | The 94 | .BR recorder_configure_show() 95 | function configures the output funtion for the recorder. This output 96 | function receives the 97 | .I output 98 | value defined by 99 | .BR recorder_configure_output. 100 | 101 | .PP 102 | The 103 | .BR recorder_configure_format() 104 | function configures the event record formatting function. 105 | 106 | 107 | .\" ---------------------------------------------------------------------------- 108 | .SH RETURN VALUE 109 | .\" ---------------------------------------------------------------------------- 110 | .PP 111 | The functions return the previously configured value. 112 | 113 | 114 | 115 | 116 | .\" ---------------------------------------------------------------------------- 117 | .SH BUGS 118 | .\" ---------------------------------------------------------------------------- 119 | 120 | .PP 121 | Bugs should be reported using https://github.com/c3d/recorder/issues. 122 | 123 | 124 | .\" ---------------------------------------------------------------------------- 125 | .SH SEE ALSO 126 | .\" ---------------------------------------------------------------------------- 127 | .BR record (3), 128 | .BR record_fast (3), 129 | .br 130 | .BR RECORDER_DEFINE (3), 131 | .BR RECORDER_DECLARE (3) 132 | .br 133 | .BR recorder_trace_set (3) 134 | .BR RECORDER_TRACE (3) 135 | .br 136 | .BR recorder_dump (3), 137 | .BR recorder_dump_for (3), 138 | 139 | .PP 140 | Additional documentation and tutorials can be found 141 | at https://github.com/c3d/recorder. 142 | 143 | 144 | .\" ---------------------------------------------------------------------------- 145 | .SH AUTHOR 146 | .\" ---------------------------------------------------------------------------- 147 | Written by Christophe de Dinechin 148 | -------------------------------------------------------------------------------- /man/man3/recorder_configure_show.3: -------------------------------------------------------------------------------- 1 | \" recorder_configure_show documented in recorder_configure_output 2 | .so man3/recorder_configure_output.3 3 | -------------------------------------------------------------------------------- /man/man3/recorder_configure_type.3: -------------------------------------------------------------------------------- 1 | .\" **************************************************************************** 2 | .\" record_configure_type.3 recorder library 3 | .\" **************************************************************************** 4 | .\" 5 | .\" File Description: 6 | .\" 7 | .\" Man page for the recorder library 8 | .\" 9 | .\" This documents 10 | .\" record_configure_type(3) 11 | .\" 12 | .\" 13 | .\" 14 | .\" 15 | .\" **************************************************************************** 16 | .\" (C) 2019-2020 Christophe de Dinechin 17 | .\" %%%LICENSE_START(LGPLv2+_DOC_FULL) 18 | .\" This is free documentation; you can redistribute it and/or 19 | .\" modify it under the terms of the GNU Lesser General Public License as 20 | .\" published by the Free Software Foundation; either version 2 of 21 | .\" the License, or (at your option) any later version. 22 | .\" 23 | .\" The GNU Lesser General Public License's references to "object code" 24 | .\" and "executables" are to be interpreted as the output of any 25 | .\" document formatting or typesetting system, including 26 | .\" intermediate and printed output. 27 | .\" 28 | .\" This manual is distributed in the hope that it will be useful, 29 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 30 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 31 | .\" GNU Lesser General Public License for more details. 32 | .\" 33 | .\" You should have received a copy of the GNU General Public 34 | .\" License along with this manual; if not, see 35 | .\" . 36 | .\" %%%LICENSE_END 37 | .\" **************************************************************************** 38 | 39 | .TH recorder_configure_type 3 "2019-03-09" "1.0" "Recorder Library" 40 | 41 | .\" ---------------------------------------------------------------------------- 42 | .SH NAME 43 | .\" ---------------------------------------------------------------------------- 44 | record_configure_type \- Configure format characters for event records 45 | 46 | 47 | .\" ---------------------------------------------------------------------------- 48 | .SH SYNOPSIS 49 | .\" ---------------------------------------------------------------------------- 50 | .nf 51 | .B #include 52 | .PP 53 | .BI "typedef size_t (*recorder_type_fn)(intptr_t " trace "," 54 | .BI " const char *" format "," 55 | .BI " char *" buffer "," 56 | .BI " size_t" length "," 57 | .BI " uintptr_t" data ");" 58 | .BI "recorder_type_fn recorder_configure_type(uint8_t" id ", recorder_type_fn" type ");" 59 | 60 | .fi 61 | .PP 62 | 63 | 64 | .\" ---------------------------------------------------------------------------- 65 | .SH DESCRIPTION 66 | .\" ---------------------------------------------------------------------------- 67 | .PP 68 | The 69 | .BR record_configure_type 70 | configures a type formatting character identified by 71 | .I id 72 | that can then be used in 73 | .BR record(3) 74 | format strings. 75 | This is normally used to add special extensions to the printf format 76 | that deal with to frequently used data types in the program. In the 77 | example below, we use 78 | .B "record_configure_type('t', todo_format)" 79 | so that the 80 | .B %t 81 | format sequence will format 82 | .B struct todo * 83 | pointers. 84 | 85 | .PP 86 | The function identified by 87 | .I type 88 | is called to perform the actual data formatting. It receives the 89 | following arguments: 90 | 91 | .TP 92 | .I trace 93 | is the tracing value associated to the recorder. This can be used to 94 | implement safe pointer rendering similar to 95 | .B %+s 96 | i.e. only derefrence the given pointer if you know that it is safe to 97 | do so. Note that if the format string is 98 | .BI "%+"id 99 | then the value of 100 | .I trace 101 | will be non-zero. 102 | 103 | .TP 104 | .I format 105 | is the actual format string, which allows your 106 | .I type 107 | function to handle format modifiers or precision indicators similar to 108 | the way printf does. 109 | 110 | .TP 111 | .I buffer 112 | is the buffer where your function should write its output. 113 | 114 | .TP 115 | .I length 116 | is the amount of space available in the buffer. 117 | 118 | .TP 119 | .I data 120 | is the actual value stored in the recorder event, for example a 121 | pointer to your data. 122 | 123 | 124 | .\" ---------------------------------------------------------------------------- 125 | .SH RETURN VALUE 126 | .\" ---------------------------------------------------------------------------- 127 | .PP 128 | The return value of 129 | .B recorder_configure_type() 130 | is the previous function handling the given format identifier. 131 | 132 | .PP 133 | The 134 | .I type 135 | function should return the size it would have written, in the same way 136 | .BR snprintf(3) 137 | does, i.e. the number of characters that it would have written, 138 | irrespective of the size of the buffer. 139 | 140 | 141 | .\" ---------------------------------------------------------------------------- 142 | .SH EXAMPLES 143 | .\" ---------------------------------------------------------------------------- 144 | .PP 145 | The following program uses 146 | .B recorder_configure_type() 147 | to associate the function 148 | .B todo_format() 149 | to the 150 | .B %t 151 | format sequence. The function interprets its argument as a 152 | .B struct todo * 153 | pointer. The main program builds a to-do list from command-line 154 | arguments, but it contains a subtle bug that causes it to crash if any 155 | of the arguments happens to be "crash". 156 | 157 | .PP 158 | .in +4n 159 | .EX 160 | #include 161 | #include 162 | #include 163 | #include 164 | 165 | RECORDER(todo, 32, "To-Do List"); 166 | 167 | typedef struct todo 168 | { 169 | const char *name; 170 | struct todo *next; 171 | } todo; 172 | 173 | size_t todo_format(intptr_t tracing, 174 | const char *format, 175 | char *buffer, size_t size, 176 | uintptr_t arg) 177 | { 178 | struct todo *t = (struct todo *) arg; 179 | if (!tracing || !t) 180 | return snprintf(buffer, size, "%p", arg); 181 | return snprintf(buffer, size, "todo@%p('%s')", t, t->name); 182 | } 183 | 184 | int main(int argc, char **argv) 185 | { 186 | int a; 187 | todo *list = NULL; 188 | int crash = 0; 189 | recorder_dump_on_common_signals(0, 0); 190 | recorder_configure_type('t', todo_format); 191 | for (a = 1; a < argc; a++) 192 | { 193 | record(todo, "Argument %d is '%+s'", a, argv[a]); 194 | todo *next = malloc(sizeof(todo)); 195 | next->name = argv[a]; 196 | next->next = list; 197 | record(todo, "Created entry %t previous was %t", next, list); 198 | list = next; 199 | crash += strcmp(argv[a], "crash") == 0; 200 | } 201 | 202 | while (list || crash) 203 | { 204 | todo *next = list->next; 205 | record(todo, "Had item %t", list); 206 | free(list); 207 | list = next; 208 | } 209 | } 210 | .EE 211 | .in -4n 212 | 213 | .PP 214 | The following shows what happens if you run the program normally 215 | .PP 216 | .in +4n 217 | .EX 218 | .B % todo eat pray love 219 | .EE 220 | .in -4n 221 | 222 | .PP 223 | The following shows what happens if you trace the program: the 224 | .B struct todo * 225 | values are formatted, showing additional information. 226 | 227 | .in +4n 228 | .EX 229 | .B % RECORDER_TRACES=todo todo eat pray love 230 | [34 0.000273] todo: Argument 1 is 'eat' 231 | [35 0.000339] todo: Created entry todo@0x50ba80('eat') previous was (nil) 232 | [36 0.000352] todo: Argument 2 is 'pray' 233 | [37 0.000360] todo: Created entry todo@0x50ba60('pray') previous was todo@0x50ba80('eat') 234 | [38 0.000368] todo: Argument 3 is 'love' 235 | [39 0.000373] todo: Created entry todo@0x50ba40('love') previous was todo@0x50ba60('pray') 236 | [40 0.000386] todo: Had item todo@0x50ba40('love') 237 | [41 0.000394] todo: Had item todo@0x50ba60('pray') 238 | [42 0.000477] todo: Had item todo@0x50ba80('eat') 239 | .EE 240 | .in -4n 241 | 242 | .PP 243 | The following shows what happens if the program crashes due to bad 244 | input: the crash dump no longer indicates the content of the todo 245 | pointers, since it would be unsafe to dereference them at that time. 246 | 247 | .PP 248 | .in +4n 249 | .EX 250 | .B % todo crash and burn 251 | [...] 252 | [25 0.000052] recorders: Configure type 't' to 0x4011c6 from (nil) 253 | [26 0.000067] todo: Argument 1 is 'crash' 254 | [27 0.000146] todo: Created entry 0x1f9d260 previous was (nil) 255 | [28 0.000150] todo: Argument 2 is 'and' 256 | [29 0.000150] todo: Created entry 0x1f9d280 previous was 0x1f9d260 257 | [30 0.000150] todo: Argument 3 is 'burn' 258 | [31 0.000150] todo: Created entry 0x1f9d2a0 previous was 0x1f9d280 259 | [32 0.000151] todo: Had item 0x1f9d2a0 260 | [33 0.000152] todo: Had item 0x1f9d280 261 | [34 0.000152] todo: Had item 0x1f9d260 262 | [35 0.000172] signals: Received signal Segmentation fault (11) si_addr=0x8, dumping recorder 263 | [36 0.000209] recorders: Recorder dump 264 | .EE 265 | .in -4n 266 | 267 | .PP 268 | Finally, the following shows what happens if you activate tracing and 269 | the program crashes. In that case, the tracing part at the top shows 270 | the detailed information. However, during the crash dump, the same 271 | events are replayed again (putting them in context with other events), 272 | this time without dereferencing the pointer. 273 | 274 | .PP 275 | .in +4n 276 | .EX 277 | .B % RECORDER_TRACES=todo todo crash and burn 278 | [...] 279 | [34 0.000184] todo: Argument 1 is 'crash' 280 | [35 0.000240] todo: Created entry todo@0xf6aa80('crash') previous was (nil) 281 | [36 0.000250] todo: Argument 2 is 'and' 282 | [37 0.000255] todo: Created entry todo@0xf6aa60('and') previous was todo@0xf6aa80('crash') 283 | [38 0.000260] todo: Argument 3 is 'burn' 284 | [39 0.000265] todo: Created entry todo@0xf6aa40('burn') previous was todo@0xf6aa60('and') 285 | [40 0.000270] todo: Had item todo@0xf6aa40('burn') 286 | [41 0.000275] todo: Had item todo@0xf6aa60('and') 287 | [42 0.000279] todo: Had item todo@0xf6aa80('crash') 288 | [...] 289 | [33 0.000180] recorders: Configure type 't' to 0x4011c6 from (nil) 290 | [34 0.000184] todo: Argument 1 is 'crash' 291 | [35 0.000240] todo: Created entry 0xf6aa80 previous was (nil) 292 | [36 0.000250] todo: Argument 2 is 'and' 293 | [37 0.000255] todo: Created entry 0xf6aa60 previous was 0xf6aa80 294 | [38 0.000260] todo: Argument 3 is 'burn' 295 | [39 0.000265] todo: Created entry 0xf6aa40 previous was 0xf6aa60 296 | [40 0.000270] todo: Had item 0xf6aa40 297 | [41 0.000275] todo: Had item 0xf6aa60 298 | [42 0.000279] todo: Had item 0xf6aa80 299 | [43 0.000297] signals: Received signal Segmentation fault (11) si_addr=0x8, dumping recorder 300 | [44 0.000309] recorders: Recorder dump 301 | .EE 302 | .in -4n 303 | 304 | 305 | .\" ---------------------------------------------------------------------------- 306 | .SH BUGS 307 | .\" ---------------------------------------------------------------------------- 308 | .PP 309 | Using this function will render your format strings wildly 310 | incompatible with the standard 311 | .BR printf(3) 312 | format, possibly making your code less readable. 313 | 314 | .PP 315 | There is a very limited number of possible type identifiers. Using 316 | this feature in a shared library may cause conflicts with other code 317 | that would also want to override the same format character. 318 | 319 | .PP 320 | It is possible to override standard format characters using this 321 | function. Whether this is a bug or a feature remains to be seen. 322 | 323 | .PP 324 | Bugs should be reported using https://github.com/c3d/recorder/issues. 325 | 326 | 327 | .\" ---------------------------------------------------------------------------- 328 | .SH SEE ALSO 329 | .\" ---------------------------------------------------------------------------- 330 | .BR RECORDER_DEFINE (3), 331 | .BR RECORDER_DECLARE (3) 332 | .br 333 | .BR recorder_trace_set (3) 334 | .BR RECORDER_TRACE (3) 335 | .br 336 | .BR recorder_dump (3), 337 | .BR recorder_dump_for (3), 338 | .br 339 | .BR recorder_configure_output (3), 340 | .BR recorder_configure_show (3) 341 | .br 342 | .BR recorder_configure_format (3), 343 | .BR recorder_configure_type (3) 344 | 345 | .PP 346 | Additional documentation and tutorials can be found 347 | at https://github.com/c3d/recorder. 348 | 349 | 350 | .\" ---------------------------------------------------------------------------- 351 | .SH AUTHOR 352 | .\" ---------------------------------------------------------------------------- 353 | Written by Christophe de Dinechin 354 | -------------------------------------------------------------------------------- /man/man3/recorder_dump.3: -------------------------------------------------------------------------------- 1 | .\" **************************************************************************** 2 | .\" recorder_dump.3 recorder library 3 | .\" **************************************************************************** 4 | .\" 5 | .\" File Description: 6 | .\" 7 | .\" Man page for the recorder library 8 | .\" 9 | .\" This documents recorder_dump(3), recorder_dump_for(3), recorder_sort(3) 10 | .\" 11 | .\" 12 | .\" 13 | .\" 14 | .\" 15 | .\" 16 | .\" **************************************************************************** 17 | .\" (C) 2019-2020 Christophe de Dinechin 18 | .\" %%%LICENSE_START(LGPLv2+_DOC_FULL) 19 | .\" This is free documentation; you can redistribute it and/or 20 | .\" modify it under the terms of the GNU Lesser General Public License as 21 | .\" published by the Free Software Foundation; either version 2 of 22 | .\" the License, or (at your option) any later version. 23 | .\" 24 | .\" The GNU Lesser General Public License's references to "object code" 25 | .\" and "executables" are to be interpreted as the output of any 26 | .\" document formatting or typesetting system, including 27 | .\" intermediate and printed output. 28 | .\" 29 | .\" This manual is distributed in the hope that it will be useful, 30 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 31 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 32 | .\" GNU Lesser General Public License for more details. 33 | .\" 34 | .\" You should have received a copy of the GNU General Public 35 | .\" License along with this manual; if not, see 36 | .\" . 37 | .\" %%%LICENSE_END 38 | .\" **************************************************************************** 39 | 40 | .TH recorder_dump 3 "2019-03-09" "1.0" "Recorder Library" 41 | 42 | .\" ---------------------------------------------------------------------------- 43 | .SH NAME 44 | .\" ---------------------------------------------------------------------------- 45 | recorder_dump \- Dump all recorder entries 46 | .br 47 | recorder_dump_for \- Dump selected recorders 48 | .br 49 | recorder_sort \- Fine-controlled recorder dump 50 | .br 51 | recorder_dump_on_signal \- Dump the recorder when receiving a signal 52 | .br 53 | recorder_dump_on_common_signals \- Dump the recorder for standard signals 54 | 55 | 56 | .\" ---------------------------------------------------------------------------- 57 | .SH SYNOPSIS 58 | .\" ---------------------------------------------------------------------------- 59 | .nf 60 | .B #include 61 | .PP 62 | .BI "typedef unsigned (*recorder_show_fn) (const char *" text "," 63 | .BI " size_t" len "," 64 | .BI " void *" output ");" 65 | .BI "typedef void (*recorder_format_fn)(recorder_show_fn " show "," 66 | .BI " void *" output "," 67 | .BI " const char * "label "," 68 | .BI " const char *" location "," 69 | .BI " uintptr_t" order "," 70 | .BI " uintptr_t" timestamp "," 71 | .BI " const char *" message ");" 72 | .BI "typedef size_t (*recorder_type_fn)(intptr_t " trace "," 73 | .BI " const char *" format "," 74 | .BI " char *" buffer "," 75 | .BI " size_t" length "," 76 | .BI " uintptr_t" data ");" 77 | .PP 78 | .BI "unsigned recorder_dump(void);" 79 | .BI "unsigned recorder_dump_for(const char *" pattern ");" 80 | .BI "unsigned recorder_sort(const char *" pattern "," 81 | .BI " recorder_format_fn " format "," 82 | .BI " recorder_show_fn " show "," 83 | .BI " void * " show_arg ");" 84 | .BI "void recorder_dump_on_signal(int " signal ");" 85 | .BI "void recorder_dump_on_common_signals(unsigned " add "," 86 | .BI " unsigned " remove ");" 87 | .fi 88 | .PP 89 | 90 | 91 | .\" ---------------------------------------------------------------------------- 92 | .SH DESCRIPTION 93 | .\" ---------------------------------------------------------------------------- 94 | .PP 95 | The 96 | .BR recorder_dump() 97 | function dumps the content of all the event recorders, in the order in 98 | which they were recorded. The 99 | .BR recorder_dump_for() 100 | function only dumps the recorders selected by the regular expression 101 | .I pattern. 102 | The 103 | .BR recorder_sort() 104 | function dumps the recorders selected by regular expression 105 | .I pattern 106 | using 107 | .I format 108 | to format an event record, 109 | .I show 110 | to show it, and passing 111 | .I show_arg 112 | to the function 113 | .I show. 114 | 115 | .PP 116 | Calls to 117 | .BR recorder_dump() 118 | and 119 | .BR recorder_dump_for() 120 | are equivalents to calls to 121 | .BR recorder_sort 122 | where standard default values for 123 | .I format, 124 | .I show 125 | and 126 | .I arg. 127 | These values can be changed using 128 | .BR recorder_configure_format(3), 129 | .BR recorder_configure_show(3) 130 | and 131 | .BR recorder_configure_output(3) 132 | respectively. 133 | 134 | .PP 135 | The 136 | .BR recorder_dump_on_signal() 137 | function ensures that 138 | .BR recorder_dump() 139 | is called if the programs receives the given signal. 140 | The 141 | .BR recorder_dump_on_common_signals() 142 | ensures that a recorder dump happens if any of a common set of 143 | signals is received by the program (SIGQUIT, SIGILL, SIGABRT, SIGBUS, 144 | SIGSEGV, SIGSYS, SIGXCPU, SIGXFSZ, SIGINFO, SIGUSR1, SIGUSR2, 145 | SIGSTKFLT and SIGPWR, insofar as they are defined for the operating system). 146 | The 147 | .I add 148 | and 149 | .I remove 150 | are bit masks that can be used to add or remove other signals compared 151 | to the default list. 152 | 153 | 154 | .\" ---------------------------------------------------------------------------- 155 | .SH RETURN VALUE 156 | .\" ---------------------------------------------------------------------------- 157 | .PP 158 | The 159 | .BR recorder_dump(), 160 | .BR recorder_dump_for() 161 | and 162 | .BR recorder_sort() 163 | functions return the number of event records that were dumped. 164 | 165 | 166 | .\" ---------------------------------------------------------------------------- 167 | .SH ENVIRONMENT VARIABLES 168 | .\" ---------------------------------------------------------------------------- 169 | .PP 170 | The 171 | .BR recorder_dump_on_common_signals() 172 | function also reads the following environment variables: 173 | 174 | .TP 175 | .B RECORDER_TRACES 176 | A trace description, in the format expected by 177 | .BR recorder_trace_set(3). 178 | 179 | .TP 180 | .B RECORDER_TWEAKS 181 | Like RECORDER_TRACES, but normally used for tweaking configuration 182 | values as opposed to tracing. 183 | 184 | .TP 185 | .B RECORDER_DUMP 186 | Activates a background thread to dump the given pattern at regular intervals. 187 | 188 | 189 | .\" ---------------------------------------------------------------------------- 190 | .SH EXAMPLES 191 | .\" ---------------------------------------------------------------------------- 192 | .PP 193 | The program below records its input arguments, and crashes if passed 194 | .I crash 195 | as the first command-line argument. 196 | .PP 197 | .in +4n 198 | .EX 199 | #include 200 | #include 201 | 202 | RECORDER(program_args, 32, "Program command-line arguments"); 203 | int main(int argc, char **argv) 204 | { 205 | int a; 206 | recorder_dump_on_common_signals(0, 0); 207 | for (a = 0; a < argc; a++) 208 | record(program_args, "Argument %d is %+s", a, argv[a]); 209 | 210 | if (argc >= 2 && strcmp(argv[1], "crash") == 0) 211 | { 212 | char *ptr = NULL; 213 | strcpy(ptr, argv[1]); 214 | } 215 | } 216 | .EE 217 | .in -4n 218 | .PP 219 | When a crash occurs, previously recorded events are printed out on the 220 | console. 221 | 222 | .PP 223 | The program below is an instrumented version of the classical 224 | recursive Fibonacci computation. It uses several recorders 225 | corresponding to different types of events, and activates warnings and 226 | errors in a way that can be configured by setting an environment variable. 227 | .PP 228 | .in +4n 229 | .EX 230 | #include 231 | #include 232 | #include 233 | #include 234 | 235 | RECORDER(fib_main, 32, "Loops in fib function"); 236 | RECORDER(fib_loops, 32, "Loops in fib function"); 237 | RECORDER(fib_warning, 32, "Warnings in fib function"); 238 | RECORDER(fib_error, 32, "Errors in fib function"); 239 | 240 | int fib(int n) 241 | { 242 | if (n <= 1) { 243 | if (n < 0) 244 | record(fib_error, "fib is undefined for negative value %d", n); 245 | return n; 246 | } 247 | record(fib_loops, "Computing fib(%d)", n); 248 | int result = fib(n-1) + fib(n-2); 249 | record(fib_loops, "Computed fib(%d) = %d", n, result); 250 | return result; 251 | } 252 | 253 | int main(int argc, char **argv) 254 | { 255 | int a; 256 | recorder_dump_on_common_signals(0, 0); 257 | recorder_trace_set(".*_warning=35 .*_error"); 258 | recorder_trace_set(getenv("FIB_TRACES")); 259 | for (a = 1; a < argc; a++) { 260 | int n = atoi(argv[a]); 261 | if (n >= RECORDER_TRACE(fib_warning)) 262 | record(fib_warning, "Computing for %d may take a while", n); 263 | printf("fib(%d) = %d\n", n, fib(n)); 264 | if (n >= RECORDER_TRACE(fib_warning)) 265 | record(fib_warning, "Computation for %d finally completed", n); 266 | } 267 | } 268 | .EE 269 | .in -4n 270 | .PP 271 | This program will produce an output similar to the following: 272 | .PP 273 | .in +4n 274 | .EX 275 | % fib 1 2 3 4 10 20 30 35 10 40 -1 276 | fib(1) = 1 277 | fib(2) = 1 278 | fib(3) = 2 279 | fib(4) = 3 280 | fib(10) = 55 281 | fib(20) = 6765 282 | fib(30) = 832040 283 | [2714667 0.177725] fib_warning: Computing for 35 may take a while 284 | fib(35) = 9227465 285 | [32575370 1.859156] fib_warning: Computation for 35 finally completed 286 | fib(10) = 55 287 | [32575547 1.859171] fib_warning: Computing for 40 may take a while 288 | fib(40) = 102334155 289 | [363735828 20.527882] fib_warning: Computation for 40 finally completed 290 | [363735829 20.527887] fib_error: fib is undefined for negative value -1 291 | fib(-1) = -1 292 | .EE 293 | .in -4n 294 | The first column in trace outputs is the number of events that were 295 | recorded. THe second column is the time in seconds since the program 296 | started. 297 | 298 | .PP 299 | The same program can also be run with additional tracing or warnings, 300 | for example: 301 | .PP 302 | .in +4n 303 | .EX 304 | % FIB_TRACES="recorder_location fib_loops fib_warning=3" /tmp/fib 3 4 305 | /tmp/fib.c:33:[82 0.000496] fib_warning: Computing for 3 may take a while 306 | /tmp/fib.c:18:[83 0.000561] fib_loops: Computing fib(3) 307 | /tmp/fib.c:18:[84 0.000570] fib_loops: Computing fib(2) 308 | /tmp/fib.c:20:[85 0.000575] fib_loops: Computed fib(2) = 1 309 | /tmp/fib.c:20:[86 0.000581] fib_loops: Computed fib(3) = 2 310 | fib(3) = 2 311 | /tmp/fib.c:36:[87 0.000590] fib_warning: Computation for 3 finally completed 312 | /tmp/fib.c:33:[88 0.000596] fib_warning: Computing for 4 may take a while 313 | /tmp/fib.c:18:[89 0.000601] fib_loops: Computing fib(4) 314 | /tmp/fib.c:18:[90 0.000607] fib_loops: Computing fib(3) 315 | /tmp/fib.c:18:[91 0.000612] fib_loops: Computing fib(2) 316 | /tmp/fib.c:20:[92 0.000619] fib_loops: Computed fib(2) = 1 317 | /tmp/fib.c:20:[93 0.000625] fib_loops: Computed fib(3) = 2 318 | /tmp/fib.c:18:[94 0.000664] fib_loops: Computing fib(2) 319 | /tmp/fib.c:20:[95 0.000707] fib_loops: Computed fib(2) = 1 320 | /tmp/fib.c:20:[96 0.000724] fib_loops: Computed fib(4) = 3 321 | fib(4) = 3 322 | /tmp/fib.c:36:[97 0.000741] fib_warning: Computation for 4 finally completed 323 | .EE 324 | .in -4n 325 | 326 | .\" ---------------------------------------------------------------------------- 327 | .SH BUGS 328 | .\" ---------------------------------------------------------------------------- 329 | .PP 330 | The current signal handling mechanism does not yet use 331 | .BR sigaltstack(2) 332 | and consequently is not robust to stack overflow. It is also known to 333 | fail for repeated signals. SIGINFO (and the associated Control-T 334 | keyboard shortcut) do not exist on Linux, which is a pity. 335 | 336 | .PP 337 | Bugs should be reported using https://github.com/c3d/recorder/issues. 338 | 339 | 340 | .\" ---------------------------------------------------------------------------- 341 | .SH SEE ALSO 342 | .\" ---------------------------------------------------------------------------- 343 | .BR RECORDER_DEFINE (3), 344 | .BR RECORDER_DECLARE (3) 345 | .br 346 | .BR recorder_dump (3), 347 | .BR recorder_dump_for (3), 348 | .br 349 | .BR recorder_configure_output (3), 350 | .BR recorder_configure_show (3) 351 | .br 352 | .BR recorder_configure_format (3), 353 | .BR recorder_configure_type (3) 354 | 355 | .PP 356 | Additional documentation and tutorials can be found 357 | at https://github.com/c3d/recorder. 358 | 359 | 360 | .\" ---------------------------------------------------------------------------- 361 | .SH AUTHOR 362 | .\" ---------------------------------------------------------------------------- 363 | Written by Christophe de Dinechin 364 | -------------------------------------------------------------------------------- /man/man3/recorder_dump_for.3: -------------------------------------------------------------------------------- 1 | \" recorder_dump_for documented in recorder_dump 2 | .so man3/recorder_dump.3 3 | -------------------------------------------------------------------------------- /man/man3/recorder_dump_on_common_signals.3: -------------------------------------------------------------------------------- 1 | \" recorder_dump_on_common_signals documented in recorder_dump 2 | .so man3/recorder_dump.3 3 | -------------------------------------------------------------------------------- /man/man3/recorder_dump_on_signal.3: -------------------------------------------------------------------------------- 1 | \" recorder_dump_on_signal documented in recorder_dump 2 | .so man3/recorder_dump.3 3 | -------------------------------------------------------------------------------- /man/man3/recorder_sort.3: -------------------------------------------------------------------------------- 1 | \" recorder_sort documented in recorder_dump 2 | .so man3/recorder_dump.3 3 | -------------------------------------------------------------------------------- /man/man3/recorder_trace_set.3: -------------------------------------------------------------------------------- 1 | .\" **************************************************************************** 2 | .\" recorder_trace_set.3 recorder library 3 | .\" **************************************************************************** 4 | .\" 5 | .\" File Description: 6 | .\" 7 | .\" Man page for the recorder library 8 | .\" 9 | .\" This documents recorder_trace_set(3) and RECORDER_TRACE 10 | .\" 11 | .\" 12 | .\" 13 | .\" 14 | .\" 15 | .\" 16 | .\" **************************************************************************** 17 | .\" (C) 2019-2020 Christophe de Dinechin 18 | .\" %%%LICENSE_START(LGPLv2+_DOC_FULL) 19 | .\" This is free documentation; you can redistribute it and/or 20 | .\" modify it under the terms of the GNU Lesser General Public License as 21 | .\" published by the Free Software Foundation; either version 2 of 22 | .\" the License, or (at your option) any later version. 23 | .\" 24 | .\" The GNU Lesser General Public License's references to "object code" 25 | .\" and "executables" are to be interpreted as the output of any 26 | .\" document formatting or typesetting system, including 27 | .\" intermediate and printed output. 28 | .\" 29 | .\" This manual is distributed in the hope that it will be useful, 30 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 31 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 32 | .\" GNU Lesser General Public License for more details. 33 | .\" 34 | .\" You should have received a copy of the GNU General Public 35 | .\" License along with this manual; if not, see 36 | .\" . 37 | .\" %%%LICENSE_END 38 | .\" **************************************************************************** 39 | 40 | .TH recorder_trace_set 3 "2019-03-09" "1.0" "Recorder Library" 41 | 42 | .\" ---------------------------------------------------------------------------- 43 | .SH NAME 44 | .\" ---------------------------------------------------------------------------- 45 | recorder_trace_set \- Configure tracing for event recorders 46 | .br 47 | RECORDER_TRACE \- Retrieve configured value 48 | 49 | 50 | .\" ---------------------------------------------------------------------------- 51 | .SH SYNOPSIS 52 | .\" ---------------------------------------------------------------------------- 53 | .nf 54 | .B #include 55 | .PP 56 | .BI "int recorder_trace_set(const char * " trace_set ");" 57 | .BI "#define RECORDER_TRACE(" trace_name ") ..." 58 | .fi 59 | .PP 60 | 61 | 62 | .\" ---------------------------------------------------------------------------- 63 | .SH DESCRIPTION 64 | .\" ---------------------------------------------------------------------------- 65 | .PP 66 | The 67 | .BR recorder_trace_set() 68 | function configures tracing for event recorder, based on a trace 69 | description given in 70 | .I trace_set. 71 | The function accepts a NULL input. A typical usage is to call it twice 72 | at the beginning of the program, the first time to set some default 73 | configuration for important recorders such as warnings or errors, the 74 | second time to pass some environment variable letting users configure 75 | tracing easily. 76 | 77 | .PP 78 | .EX 79 | /* Activate tracing for all _warning and _error event recorders */ 80 | recorder_trace_set(".*_(warning|error)"); 81 | 82 | /* User trace configuration, possible overriding the above */ 83 | recorder_trace_set(getenv("MY_PROGRAM_TRACES")); 84 | .EE 85 | 86 | .PP 87 | The trace description is a space-delimited list of trace 88 | actions. Alternatively, a colon 89 | .B (:) 90 | can also be used as a separator. 91 | 92 | .PP 93 | Each trace action starts with a regular expression used to select 94 | event recorders by name. That regular expression can optionally be 95 | followed by an equal sign 96 | .B = 97 | itself followed by either a numerical value or a comma-separated list 98 | of names. 99 | 100 | .SS Tracing 101 | .PP 102 | If a numerical value is given, this value is associated to the 103 | selected recorder(s), and can be read in the program by using 104 | .BI "RECORDER_TRACE(" trace_name ")." 105 | 106 | .PP 107 | The configured value is primarily used to activate tracing for the 108 | .BR record(3) 109 | and 110 | .BR record_fast(3) 111 | functions. When the value is non-zero, these functions will 112 | immediately show the events (tracing mode). Otherwise, they will 113 | simply record the events. 114 | 115 | .PP 116 | If no equal sign is given, the numerical value 1 is used. 117 | 118 | .SS Sharing for real-time graphing 119 | .PP 120 | If a comma-separated list of names is given, the corresponding 121 | event recorder is 122 | .I shared 123 | for use by external programs such as 124 | .BR recorder_scope(1). 125 | All events must have the same number and type of arguments. The 126 | comma-separated list sets the names of the data channels that can then 127 | be graphed in real-time. 128 | 129 | .SS Special trace selectors 130 | .PP 131 | A number of trace selectors have a special meaning, and are normally 132 | intended for the user of the program. 133 | 134 | .TP 135 | .B help 136 | displays the list of all available recorders, along with the 137 | associated self-documentation string. 138 | 139 | .TP 140 | .B share 141 | sets the name of the shared-memory file used to communicate with 142 | external programs, see 143 | .BR recorder_share (3). 144 | 145 | .TP 146 | .B dump 147 | causes a recorder dump. See 148 | .BR recorder_dump (3). 149 | 150 | .TP 151 | .B traces 152 | lists the current values for all trace configurations. 153 | 154 | .TP 155 | .B all 156 | activates all traces. 157 | 158 | 159 | .\" ---------------------------------------------------------------------------- 160 | .SH RETURN VALUE 161 | .\" ---------------------------------------------------------------------------- 162 | .PP 163 | The function can return one of: 164 | 165 | .TP 166 | .B RECORDER_TRACE_OK 167 | Thec trace description was successfully applied 168 | 169 | .TP 170 | .B RECORDER_TRACE_INVALID_NAME 171 | One of the trace selection regular expressions is invalid. 172 | 173 | .TP 174 | .B RECORDER_TRACE_INVALID_VALUE 175 | One of the trace configuration values is neither a numerical value nor a 176 | comma-separated list of names. 177 | 178 | 179 | .\" ---------------------------------------------------------------------------- 180 | .SH EXAMPLES 181 | .\" ---------------------------------------------------------------------------- 182 | .PP 183 | The program below records its input arguments, and crashes if passed 184 | .I crash 185 | as the first command-line argument. 186 | .PP 187 | .in +4n 188 | .EX 189 | #include 190 | #include 191 | 192 | RECORDER(program_args, 32, "Program command-line arguments"); 193 | int main(int argc, char **argv) 194 | { 195 | int a; 196 | recorder_dump_on_common_signals(0, 0); 197 | for (a = 0; a < argc; a++) 198 | record(program_args, "Argument %d is %+s", a, argv[a]); 199 | 200 | if (argc >= 2 && strcmp(argv[1], "crash") == 0) 201 | { 202 | char *ptr = NULL; 203 | strcpy(ptr, argv[1]); 204 | } 205 | } 206 | .EE 207 | .in -4n 208 | .PP 209 | When a crash occurs, previously recorded events are printed out on the 210 | console. 211 | 212 | .PP 213 | The program below is an instrumented version of the classical 214 | recursive Fibonacci computation. It uses several recorders 215 | corresponding to different types of events, and activates warnings and 216 | errors in a way that can be configured by setting an environment variable. 217 | .PP 218 | .in +4n 219 | .EX 220 | #include 221 | #include 222 | #include 223 | #include 224 | 225 | RECORDER(fib_main, 32, "Loops in fib function"); 226 | RECORDER(fib_loops, 32, "Loops in fib function"); 227 | RECORDER(fib_warning, 32, "Warnings in fib function"); 228 | RECORDER(fib_error, 32, "Errors in fib function"); 229 | 230 | int fib(int n) 231 | { 232 | if (n <= 1) { 233 | if (n < 0) 234 | record(fib_error, "fib is undefined for negative value %d", n); 235 | return n; 236 | } 237 | record(fib_loops, "Computing fib(%d)", n); 238 | int result = fib(n-1) + fib(n-2); 239 | record(fib_loops, "Computed fib(%d) = %d", n, result); 240 | return result; 241 | } 242 | 243 | int main(int argc, char **argv) 244 | { 245 | int a; 246 | recorder_dump_on_common_signals(0, 0); 247 | recorder_trace_set(".*_warning=35 .*_error"); 248 | recorder_trace_set(getenv("FIB_TRACES")); 249 | for (a = 1; a < argc; a++) { 250 | int n = atoi(argv[a]); 251 | if (n >= RECORDER_TRACE(fib_warning)) 252 | record(fib_warning, "Computing for %d may take a while", n); 253 | printf("fib(%d) = %d\n", n, fib(n)); 254 | if (n >= RECORDER_TRACE(fib_warning)) 255 | record(fib_warning, "Computation for %d finally completed", n); 256 | } 257 | } 258 | .EE 259 | .in -4n 260 | .PP 261 | This program will produce an output similar to the following: 262 | .PP 263 | .in +4n 264 | .EX 265 | % fib 1 2 3 4 10 20 30 35 10 40 -1 266 | fib(1) = 1 267 | fib(2) = 1 268 | fib(3) = 2 269 | fib(4) = 3 270 | fib(10) = 55 271 | fib(20) = 6765 272 | fib(30) = 832040 273 | [2714667 0.177725] fib_warning: Computing for 35 may take a while 274 | fib(35) = 9227465 275 | [32575370 1.859156] fib_warning: Computation for 35 finally completed 276 | fib(10) = 55 277 | [32575547 1.859171] fib_warning: Computing for 40 may take a while 278 | fib(40) = 102334155 279 | [363735828 20.527882] fib_warning: Computation for 40 finally completed 280 | [363735829 20.527887] fib_error: fib is undefined for negative value -1 281 | fib(-1) = -1 282 | .EE 283 | .in -4n 284 | The first column in trace outputs is the number of events that were 285 | recorded. THe second column is the time in seconds since the program 286 | started. 287 | 288 | .PP 289 | The same program can also be run with additional tracing or warnings, 290 | for example: 291 | .PP 292 | .in +4n 293 | .EX 294 | % FIB_TRACES="recorder_location fib_loops fib_warning=3" /tmp/fib 3 4 295 | /tmp/fib.c:33:[82 0.000496] fib_warning: Computing for 3 may take a while 296 | /tmp/fib.c:18:[83 0.000561] fib_loops: Computing fib(3) 297 | /tmp/fib.c:18:[84 0.000570] fib_loops: Computing fib(2) 298 | /tmp/fib.c:20:[85 0.000575] fib_loops: Computed fib(2) = 1 299 | /tmp/fib.c:20:[86 0.000581] fib_loops: Computed fib(3) = 2 300 | fib(3) = 2 301 | /tmp/fib.c:36:[87 0.000590] fib_warning: Computation for 3 finally completed 302 | /tmp/fib.c:33:[88 0.000596] fib_warning: Computing for 4 may take a while 303 | /tmp/fib.c:18:[89 0.000601] fib_loops: Computing fib(4) 304 | /tmp/fib.c:18:[90 0.000607] fib_loops: Computing fib(3) 305 | /tmp/fib.c:18:[91 0.000612] fib_loops: Computing fib(2) 306 | /tmp/fib.c:20:[92 0.000619] fib_loops: Computed fib(2) = 1 307 | /tmp/fib.c:20:[93 0.000625] fib_loops: Computed fib(3) = 2 308 | /tmp/fib.c:18:[94 0.000664] fib_loops: Computing fib(2) 309 | /tmp/fib.c:20:[95 0.000707] fib_loops: Computed fib(2) = 1 310 | /tmp/fib.c:20:[96 0.000724] fib_loops: Computed fib(4) = 3 311 | fib(4) = 3 312 | /tmp/fib.c:36:[97 0.000741] fib_warning: Computation for 4 finally completed 313 | .EE 314 | .in -4n 315 | 316 | .\" ---------------------------------------------------------------------------- 317 | .SH BUGS 318 | .\" ---------------------------------------------------------------------------- 319 | .PP 320 | When a recorder is shared, incorrect data can be fetched by the client 321 | graphing program if the types and number of arguments is not 322 | consistent for all entries in the recorder. 323 | 324 | .PP 325 | Bugs should be reported using https://github.com/c3d/recorder/issues. 326 | 327 | 328 | .\" ---------------------------------------------------------------------------- 329 | .SH SEE ALSO 330 | .\" ---------------------------------------------------------------------------- 331 | .BR RECORDER_DEFINE (3), 332 | .BR RECORDER_DECLARE (3) 333 | .br 334 | .BR recorder_dump (3), 335 | .BR recorder_dump_for (3), 336 | .br 337 | .BR recorder_configure_output (3), 338 | .BR recorder_configure_show (3) 339 | .br 340 | .BR recorder_configure_format (3), 341 | .BR recorder_configure_type (3) 342 | 343 | .PP 344 | Additional documentation and tutorials can be found 345 | at https://github.com/c3d/recorder. 346 | 347 | 348 | .\" ---------------------------------------------------------------------------- 349 | .SH AUTHOR 350 | .\" ---------------------------------------------------------------------------- 351 | Written by Christophe de Dinechin 352 | -------------------------------------------------------------------------------- /recorder.spec: -------------------------------------------------------------------------------- 1 | Name: recorder 2 | Version: 1.0.10 3 | Release: 1%{?dist} 4 | Summary: Lock-free, real-time flight recorder for C or C++ programs 5 | License: LGPLv2+ 6 | Url: https://github.com/c3d/%{name} 7 | Source: https://github.com/c3d/%{name}/archive/v%{version}/%{name}-%{version}.tar.gz 8 | BuildRequires: make >= 3.82 9 | BuildRequires: make-it-quick >= 0.2.5 10 | BuildRequires: gcc 11 | BuildRequires: gcc-c++ 12 | 13 | %description 14 | Flight recorder for C and C++ programs using printf-like 'record' statements. 15 | 16 | %package devel 17 | Summary: Development files for recorder library 18 | Requires: %{name}%{?_isa} = %{version}-%{release} 19 | %description devel 20 | Development files for the flight recorder library. 21 | 22 | %package scope 23 | Summary: A real-time graphing tool for data collected by recorder library 24 | License: GPLv3+ 25 | %if 0%{?fedora} >= 32 26 | BuildRequires: qt-devel 27 | %else 28 | BuildRequires: qt5-devel 29 | %endif 30 | BuildRequires: qt5-qtcharts-devel 31 | Requires: %{name}%{?_isa} = %{version}-%{release} 32 | %description scope 33 | Instrumentation that draws real-time charts, processes or saves data 34 | collected by the flight_recorder library. 35 | 36 | %prep 37 | %autosetup -n %{name}-%{version} 38 | %configure 39 | 40 | %build 41 | %make_build COLORIZE= TARGET=opt V=1 42 | (cd scope && \ 43 | %{qmake_qt5} \ 44 | INSTALL_BINDIR=%{_bindir} \ 45 | INSTALL_LIBDIR=%{_libdir} \ 46 | INSTALL_DATADIR=%{_datadir} \ 47 | INSTALL_MANDIR=%{_mandir} && \ 48 | make) 49 | 50 | %check 51 | %make_build COLORIZE= TARGET=opt V=1 check 52 | 53 | %install 54 | %make_install COLORIZE= TARGET=opt DOC_INSTALL= 55 | %make_install -C scope INSTALL_ROOT=%{buildroot} 56 | 57 | %files 58 | %license COPYING 59 | %doc README.md 60 | %doc AUTHORS 61 | %doc NEWS 62 | %{_libdir}/lib%{name}.so.1 63 | %{_libdir}/lib%{name}.so.%{version} 64 | 65 | %files devel 66 | %{_libdir}/lib%{name}.so 67 | %dir %{_includedir}/%{name} 68 | %{_includedir}/%{name}/* 69 | %{_datadir}/pkgconfig/%{name}.pc 70 | %{_mandir}/man3/*.3.* 71 | 72 | %files scope 73 | %{_bindir}/recorder_scope 74 | %{_mandir}/man1/*.1.* 75 | 76 | %changelog 77 | * Fri Jun 26 2020 Christophe de Dinechin - 1.0.10-1 78 | - Release 1.0.10, Add _Bool support 79 | 80 | * Tue Jun 23 2020 Christophe de Dinechin - 1.0.9-1 81 | - Release 1.0.9, compatibility with Fedora 33 82 | 83 | * Thu Jan 30 2020 Fedora Release Engineering - 1.0.8-3 84 | - Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild 85 | 86 | * Fri Jul 26 2019 Fedora Release Engineering - 1.0.8-2 87 | - Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild 88 | 89 | * Fri May 3 2019 Christophe de Dinechin - 1.0.8-1 90 | - Adjust Fedora package to address review comments 91 | 92 | * Fri Apr 26 2019 Christophe de Dinechin - 1.0.7-1 93 | - Initial Fedora package from upstream release 94 | -------------------------------------------------------------------------------- /recorder_ring.c: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // recorder_ring.c Recorder project 3 | // ***************************************************************************** 4 | // 5 | // File description: 6 | // 7 | // Implement common ring functionality 8 | // 9 | // 10 | // 11 | // 12 | // 13 | // 14 | // 15 | // 16 | // ***************************************************************************** 17 | // This software is licensed under the GNU Lesser General Public License v2+ 18 | // (C) 2017-2020, Christophe de Dinechin 19 | // ***************************************************************************** 20 | // This file is part of Recorder 21 | // 22 | // Recorder is free software: you can redistribute it and/or modify 23 | // it under the terms of the GNU Lesser General Public License as published by 24 | // the Free Software Foundation, either version 2 of the License, or 25 | // (at your option) any later version. 26 | // 27 | // Recorder is distributed in the hope that it will be useful, 28 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 29 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 30 | // GNU Lesser General Public License for more details. 31 | // 32 | // You should have received a copy of the GNU Lesser General Public License 33 | // along with Recorder, in a file named COPYING. 34 | // If not, see . 35 | // ***************************************************************************** 36 | 37 | #include "recorder_ring.h" 38 | #include 39 | #include 40 | 41 | typedef intptr_t ringdiff_t; 42 | 43 | 44 | recorder_ring_p recorder_ring_init(recorder_ring_p ring, 45 | size_t size, size_t item_size) 46 | // ---------------------------------------------------------------------------- 47 | // Initialize a ring 48 | // ---------------------------------------------------------------------------- 49 | { 50 | ring->size = size; 51 | ring->item_size = item_size; 52 | ring->reader = 0; 53 | ring->writer = 0; 54 | ring->commit = 0; 55 | ring->overflow = 0; 56 | return ring; 57 | } 58 | 59 | 60 | #ifndef RECORDER_STANDALONE 61 | recorder_ring_p recorder_ring_new(size_t size, size_t item_size) 62 | // ---------------------------------------------------------------------------- 63 | // Create a new ring with the given name 64 | // ---------------------------------------------------------------------------- 65 | { 66 | recorder_ring_p ring = malloc(sizeof(recorder_ring_t) + size * item_size); 67 | recorder_ring_init(ring, size, item_size); 68 | return ring; 69 | } 70 | 71 | 72 | void recorder_ring_delete(recorder_ring_p ring) 73 | // ---------------------------------------------------------------------------- 74 | // Delete the given ring from the list 75 | // ---------------------------------------------------------------------------- 76 | { 77 | free(ring); 78 | } 79 | #endif 80 | 81 | 82 | extern size_t recorder_ring_readable(recorder_ring_p ring, ringidx_t *reader) 83 | // ---------------------------------------------------------------------------- 84 | // Return number of elements readable in the ring 85 | // ---------------------------------------------------------------------------- 86 | { 87 | if (!reader) 88 | reader = &ring->reader; 89 | size_t readable = ring->commit - *reader; 90 | if (readable > ring->size) 91 | readable = ring->size; 92 | return readable; 93 | } 94 | 95 | 96 | extern size_t recorder_ring_writable(recorder_ring_p ring) 97 | // ---------------------------------------------------------------------------- 98 | // Return number of elements that can be written in the ring 99 | // ---------------------------------------------------------------------------- 100 | { 101 | const ringidx_t size = ring->size; 102 | ringidx_t reader = ring->reader; 103 | ringidx_t writer = ring->writer; 104 | ringidx_t written = writer - reader; 105 | ringidx_t writable = size - written - 1; 106 | 107 | // Check if we overflowed 108 | if (written >= size - 1) 109 | writable = 0; 110 | 111 | return writable; 112 | } 113 | 114 | 115 | void *recorder_ring_peek(recorder_ring_p ring) 116 | // ---------------------------------------------------------------------------- 117 | // Peek the next entry that would be read in the ring and advance by 1 118 | // ---------------------------------------------------------------------------- 119 | { 120 | char *data = (char *) (ring + 1); 121 | const size_t size = ring->size; 122 | const size_t item_size = ring->item_size; 123 | ringidx_t reader = ring->reader; 124 | ringidx_t commit = ring->commit; 125 | size_t written = commit - reader; 126 | if (written >= size) 127 | { 128 | ringidx_t minR = commit - size + 1; 129 | ringidx_t skip = minR - reader; 130 | recorder_ring_add_fetch(ring->overflow, skip); 131 | reader = recorder_ring_add_fetch(ring->reader, skip); 132 | written = commit - reader; 133 | } 134 | return written ? data + reader % size * item_size : NULL; 135 | } 136 | 137 | 138 | ringidx_t recorder_ring_read(recorder_ring_p ring, 139 | void *destination, 140 | size_t count, 141 | ringidx_t *reader_ptr, 142 | recorder_ring_block_fn read_block, 143 | recorder_ring_block_fn read_overflow) 144 | // ---------------------------------------------------------------------------- 145 | // Ring up to 'count' elements, return number of elements read 146 | // ---------------------------------------------------------------------------- 147 | // If enough data is available in the ring buffer, the elements read are 148 | // guaranteed to be contiguous. 149 | { 150 | const size_t size = ring->size; 151 | const size_t item_size = ring->item_size; 152 | char *ptr = destination; 153 | char *data = (char *) (ring + 1); 154 | ringidx_t reader, writer, commit, available, to_copy; 155 | ringidx_t first_reader, next_reader; 156 | ringidx_t idx, to_end; 157 | size_t this_round, byte_count; 158 | 159 | if (!reader_ptr) 160 | reader_ptr = &ring->reader; 161 | 162 | // First commit to reading a given amount of contiguous data 163 | do 164 | { 165 | reader = *reader_ptr; 166 | commit = ring->commit; 167 | writer = ring->writer; 168 | available = commit - reader; 169 | to_copy = count; 170 | 171 | // Check if we want to copy more than available 172 | if (to_copy > available) 173 | if (!read_block || !read_block(ring, reader, reader + to_copy)) 174 | to_copy = available; 175 | 176 | // Check if write may have overwritten beyond our read point 177 | if (writer - reader >= size) 178 | { 179 | // If so, catch up 180 | ringidx_t first_valid = writer - size + 1; 181 | if (!read_overflow || !read_overflow(ring, reader, first_valid)) 182 | { 183 | ringidx_t skip = first_valid - reader; 184 | recorder_ring_add_fetch(ring->overflow, skip); 185 | recorder_ring_add_fetch(*reader_ptr, skip); 186 | reader = first_valid; 187 | } 188 | } 189 | 190 | // Then copy data in contiguous memcpy chunks (normally at most two) 191 | ptr = destination; 192 | first_reader = reader; 193 | next_reader = first_reader + to_copy; 194 | while (to_copy) 195 | { 196 | // Compute how much we can copy in one memcpy 197 | idx = reader % size; 198 | to_end = size - idx; 199 | this_round = to_copy < to_end ? to_copy : to_end; 200 | byte_count = this_round * item_size; 201 | 202 | // Copy data from buffer into destination 203 | memcpy(ptr, data + idx * item_size, byte_count); 204 | ptr += byte_count; 205 | to_copy -= this_round; 206 | reader += this_round; 207 | } 208 | } while (!recorder_ring_compare_exchange(*reader_ptr, 209 | first_reader, next_reader)); 210 | 211 | // Return number of items effectively read 212 | return count - to_copy; 213 | } 214 | 215 | 216 | ringidx_t recorder_ring_write(recorder_ring_p ring, 217 | const void *source, 218 | size_t count, 219 | recorder_ring_block_fn write_block, 220 | recorder_ring_block_fn commit_block, 221 | ringidx_t *writer_ptr) 222 | // ---------------------------------------------------------------------------- 223 | // Write 'count' elements from 'ptr' into 'rb', return entry idx 224 | // ---------------------------------------------------------------------------- 225 | { 226 | const size_t size = ring->size; 227 | const size_t item_size = ring->item_size; 228 | const char * ptr = source; 229 | char * data = (char *) (ring + 1); 230 | size_t to_copy = count; 231 | ringidx_t reader, writer, idx, available, to_end, first_writer; 232 | size_t this_round, byte_count; 233 | 234 | 235 | // First commit to writing a given amount of contiguous data 236 | do 237 | { 238 | reader = ring->reader; 239 | writer = ring->writer; 240 | available = size + reader - writer; 241 | to_copy = count; 242 | 243 | // Check if we want to copy more than can be written 244 | if (to_copy > available) 245 | if (write_block && !write_block(ring, writer, writer + to_copy)) 246 | to_copy = available; 247 | 248 | } while (!recorder_ring_compare_exchange(ring->writer, 249 | writer, writer + to_copy)); 250 | 251 | // Record first writer, to see if we will be the one committing 252 | first_writer = writer; 253 | if (writer_ptr) 254 | *writer_ptr = writer; 255 | 256 | // Then copy data in contiguous memcpy chunks (normally at most two) 257 | while (to_copy) 258 | { 259 | // Compute how much we can copy in one memcpy 260 | idx = writer % size; 261 | to_end = size - idx; 262 | this_round = to_copy < to_end ? to_copy : to_end; 263 | byte_count = this_round * item_size; 264 | 265 | // Copy data from buffer into destination 266 | memcpy(data + idx * item_size, ptr, byte_count); 267 | ptr += byte_count; 268 | to_copy -= this_round; 269 | writer += this_round; 270 | } 271 | 272 | // Commit buffer change, but only if commit is first_writer. 273 | // Otherwise, some other write is still copying its data, we must spin. 274 | ringidx_t expected = first_writer; 275 | while (!recorder_ring_compare_exchange(ring->commit, expected, writer)) 276 | { 277 | if (!commit_block || !commit_block(ring, ring->commit, first_writer)) 278 | { 279 | // Skip forward 280 | recorder_ring_fetch_add(ring->commit, writer - first_writer); 281 | break; 282 | } 283 | expected = first_writer; 284 | } 285 | 286 | // Return number of items effectively written 287 | return count - to_copy; 288 | } 289 | -------------------------------------------------------------------------------- /recorder_ring.h: -------------------------------------------------------------------------------- 1 | #ifndef RECORDER_RING_H 2 | #define RECORDER_RING_H 3 | // ***************************************************************************** 4 | // recorder_ring.h Recorder project 5 | // ***************************************************************************** 6 | // 7 | // File description: 8 | // 9 | // A ring (circular buffer) with multiple writers, generally single reader. 10 | // 11 | // This implementation is supposed to work in multi-CPU configurations 12 | // without using locks, only using atomic primitives 13 | // 14 | // 15 | // 16 | // 17 | // 18 | // ***************************************************************************** 19 | // This software is licensed under the GNU Lesser General Public License v2+ 20 | // (C) 2017-2020, Christophe de Dinechin 21 | // ***************************************************************************** 22 | // This file is part of Recorder 23 | // 24 | // Recorder is free software: you can redistribute it and/or modify 25 | // it under the terms of the GNU Lesser General Public License as published by 26 | // the Free Software Foundation, either version 2 of the License, or 27 | // (at your option) any later version. 28 | // 29 | // Recorder is distributed in the hope that it will be useful, 30 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 31 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 32 | // GNU Lesser General Public License for more details. 33 | // 34 | // You should have received a copy of the GNU Lesser General Public License 35 | // along with Recorder, in a file named COPYING. 36 | // If not, see . 37 | // ***************************************************************************** 38 | /* 39 | * Implementation of a thread-safe, non-blocking ring buffer. 40 | * This ring buffer can work in multi-processor scenarios or multi-threaded 41 | * systems, and can be used to store records of any type. It can also safely 42 | * manage sequences of consecutive records. A typical use case is strings 43 | * in a console print buffer, which have variable length. 44 | * 45 | * How this works: 46 | * Each buffer is represented by 47 | * - an array A of N items (for performance, N should be a power of 2), 48 | * - a reader index R, 49 | * - a writer index W 50 | * - a commit index C 51 | * - an overflow counter O 52 | * 53 | * The core invariants of the structure are (ignoring integer overflow) 54 | * 1. R <= C <= W 55 | * 2. overflowed = W - R >= N 56 | * 57 | * Reading entries from the buffer consist in the following steps: 58 | * 1. If the buffer overflowed, "catch up": 59 | * 1a. Set R to W-N+1 60 | * 2b. Increase O to record the overflow 61 | * 2. There is readable data iff R < C. If so: 62 | * 2a. Read A[R % N] (possibly blocking) 63 | * 2b. Atomically increase R 64 | * 65 | * Writing E entries in the buffer consists in the following steps: 66 | * 1. Atomically increase W, fetching the old W (fetch_and_add) 67 | * 2. Copy the entries in A[oldW % N] (possibly blocking) 68 | * 3. Wait until C == oldW, and Atomically set C to W (possibly blocking) 69 | * 70 | * Step 2 can block if the reader has not caught up yet. 71 | * Step 3 can block if another writer has still not updated C 72 | * 73 | * Important notes: 74 | * The code as written is safe for use with C++ objects. Do not attempt 75 | * to replace data copies with memcpy(), as it would most likely not 76 | * improve speed (because of the "modulo" to perform) and would break C++. 77 | * 78 | * In theory, if you use the buffer long enough, all indexes will ultimately 79 | * wrap around. This is why all comparisons are done with something like 80 | * (int) (writer - reader) >= size 81 | * instead of a solution that would fail when writer wraps around 82 | * writer >= reader + size 83 | * It is therefore assumed that you will never create a buffer of a 84 | * size larger than 2^31 on a 32-bit machine. Probably OK. 85 | * 86 | */ 87 | 88 | // **************************************************************************** 89 | // 90 | // If .h included from a C++ program, select the C++ version 91 | // 92 | // **************************************************************************** 93 | 94 | #include 95 | #include 96 | #include 97 | 98 | 99 | #ifdef __cplusplus 100 | extern "C" { 101 | #endif // __cplusplus 102 | 103 | 104 | // ============================================================================ 105 | // 106 | // Compiler dependencies 107 | // 108 | // ============================================================================ 109 | 110 | #ifdef __GNUC__ 111 | #define RECORDER_RING_MAYBE_UNUSED __attribute__((unused)) 112 | #else // !__GNUC__ 113 | #define RECORDER_RING_MAYBE_UNUSED 114 | #endif // __GNUC__ 115 | 116 | 117 | 118 | // ============================================================================ 119 | // 120 | // Atomic built-ins 121 | // 122 | // ============================================================================ 123 | 124 | 125 | 126 | #ifdef RECORDER_NO_ATOMICS 127 | 128 | #define recorder_ring_fetch_add(Value, Offset) (Value += Offset) 129 | #define recorder_ring_add_fetch(Value, Offset) ((Value += Offset), Value) 130 | #define recorder_ring_compare_exchange(Val, Exp, New) ((Val = New), true) 131 | 132 | #else 133 | 134 | #ifdef __GNUC__ 135 | 136 | // GCC-compatible compiler: use built-in atomic operations 137 | #define recorder_ring_fetch_add(Value, Offset) \ 138 | __atomic_fetch_add(&Value, Offset, __ATOMIC_ACQUIRE) 139 | 140 | #define recorder_ring_add_fetch(Value, Offset) \ 141 | __atomic_add_fetch(&Value, Offset, __ATOMIC_ACQUIRE) 142 | 143 | #define recorder_ring_compare_exchange(Value, Expected, New) \ 144 | __atomic_compare_exchange_n(&Value, &Expected, New, \ 145 | 0, __ATOMIC_RELEASE, __ATOMIC_RELAXED) 146 | 147 | #else // ! __GNUC__ 148 | 149 | #warning "Compiler not supported yet" 150 | #define recorder_ring_fetch_add(Value, Offset) (Value += Offset) 151 | #define recorder_ring_add_fetch(Value, Offset) ((Value += Offset), Value) 152 | #define recorder_ring_compare_exchange(Val, Exp, New) ((Val = New), true) 153 | 154 | #endif 155 | 156 | #endif // RECORDER_NO_ATOMICS 157 | 158 | 159 | // ============================================================================ 160 | // 161 | // C-style ring buffer type 162 | // 163 | // ============================================================================ 164 | 165 | typedef uintptr_t ringidx_t; 166 | 167 | typedef struct recorder_ring 168 | // ---------------------------------------------------------------------------- 169 | // Header for ring buffers 170 | // ---------------------------------------------------------------------------- 171 | { 172 | size_t size; // Number of elements in data array 173 | size_t item_size; // Size of the elements 174 | ringidx_t reader; // Reader index 175 | ringidx_t writer; // Writer index 176 | ringidx_t commit; // Last commited write 177 | ringidx_t overflow; // Overflowed writes 178 | } recorder_ring_t, *recorder_ring_p; 179 | 180 | /* Deal with blocking situations on given ring 181 | - Return true if situation is handled and operation can proceed 182 | - Return false will abort read or write operation. 183 | - May be NULL to implement default non-blocking mode 184 | The functions take from/to as argument bys design, because the 185 | corresponding values may have been changed in the ring 186 | by the time the block-handling function get to read them. */ 187 | typedef bool (*recorder_ring_block_fn)(recorder_ring_p, 188 | ringidx_t from, ringidx_t to); 189 | 190 | extern recorder_ring_p recorder_ring_init(recorder_ring_p ring, 191 | size_t size, size_t item_size); 192 | #ifndef RECORDER_STANDALONE 193 | extern recorder_ring_p recorder_ring_new(size_t size, size_t item_size); 194 | extern void recorder_ring_delete(recorder_ring_p ring); 195 | #endif // RECORDER_STANDALONE 196 | extern size_t recorder_ring_readable(recorder_ring_p ring, ringidx_t *reader); 197 | extern size_t recorder_ring_writable(recorder_ring_p ring); 198 | extern size_t recorder_ring_read(recorder_ring_p ring, 199 | void *data, size_t count, 200 | ringidx_t *reader, 201 | recorder_ring_block_fn read_block, 202 | recorder_ring_block_fn read_overflow); 203 | extern void * recorder_ring_peek(recorder_ring_p ring); 204 | extern ringidx_t recorder_ring_write(recorder_ring_p ring, 205 | const void *data, size_t count, 206 | recorder_ring_block_fn write_block, 207 | recorder_ring_block_fn commit_block, 208 | ringidx_t *writer); 209 | 210 | 211 | 212 | #define RECORDER_RING_TYPE_DECLARE(Ring, Type) \ 213 | /* ----------------------------------------------------------------*/ \ 214 | /* Declare a ring buffer type with Size elements of given Type */ \ 215 | /* ----------------------------------------------------------------*/ \ 216 | \ 217 | typedef struct Ring \ 218 | { \ 219 | recorder_ring_t ring; \ 220 | Type data[0]; \ 221 | } Ring; \ 222 | \ 223 | typedef bool (*Ring##_block_fn)(Ring *,ringidx_t, ringidx_t); \ 224 | \ 225 | static inline RECORDER_RING_MAYBE_UNUSED \ 226 | Ring *Ring##_new(size_t size) \ 227 | { \ 228 | return (Ring *) recorder_ring_new(size, sizeof(Type)); \ 229 | } \ 230 | \ 231 | static inline RECORDER_RING_MAYBE_UNUSED \ 232 | void Ring##_delete(Ring *rb) \ 233 | { \ 234 | recorder_ring_delete(&rb->ring); \ 235 | } \ 236 | \ 237 | static inline RECORDER_RING_MAYBE_UNUSED \ 238 | Type * Ring##_peek(Ring *rb) \ 239 | { \ 240 | return (Type *) recorder_ring_peek(&rb->ring); \ 241 | } \ 242 | \ 243 | static inline RECORDER_RING_MAYBE_UNUSED \ 244 | size_t Ring##_read(Ring *rb, \ 245 | Type *ptr, \ 246 | size_t count, \ 247 | ringidx_t *reader, \ 248 | Ring##_block_fn block, \ 249 | Ring##_block_fn overflow) \ 250 | { \ 251 | return recorder_ring_read(&rb->ring, ptr, count, reader, \ 252 | (recorder_ring_block_fn) block, \ 253 | (recorder_ring_block_fn) overflow); \ 254 | } \ 255 | \ 256 | static inline RECORDER_RING_MAYBE_UNUSED \ 257 | size_t Ring##_write(Ring *rb, \ 258 | Type *ptr, \ 259 | size_t count, \ 260 | Ring##_block_fn wrblk, \ 261 | Ring##_block_fn cmblk, \ 262 | ringidx_t *writer) \ 263 | { \ 264 | return recorder_ring_write(&rb->ring, ptr, count, \ 265 | (recorder_ring_block_fn) wrblk, \ 266 | (recorder_ring_block_fn) cmblk, \ 267 | writer); \ 268 | } \ 269 | \ 270 | static inline RECORDER_RING_MAYBE_UNUSED \ 271 | ringidx_t Ring##_readable(Ring *rb) \ 272 | { \ 273 | return recorder_ring_readable(&rb->ring); \ 274 | } \ 275 | \ 276 | static inline RECORDER_RING_MAYBE_UNUSED \ 277 | ringidx_t Ring##_writable(Ring *rb) \ 278 | { \ 279 | return recorder_ring_writable(&rb->ring); \ 280 | } 281 | 282 | 283 | 284 | // ============================================================================ 285 | // 286 | // Static ring buffer allocation 287 | // 288 | // ============================================================================ 289 | 290 | #define RECORDER_RING_DECLARE(Name, Type, Size) \ 291 | /* ----------------------------------------------------------------*/ \ 292 | /* Declare a named ring buffer with helper functions to access it */ \ 293 | /* ----------------------------------------------------------------*/ \ 294 | /* This is what you should use in headers */ \ 295 | \ 296 | extern struct Name##_ring \ 297 | { \ 298 | recorder_ring_t ring; \ 299 | Type data[Size]; \ 300 | } Name; \ 301 | \ 302 | static inline RECORDER_RING_MAYBE_UNUSED \ 303 | size_t Name##_readable() \ 304 | { \ 305 | return recorder_ring_readable(&Name.ring, NULL); \ 306 | } \ 307 | \ 308 | static inline RECORDER_RING_MAYBE_UNUSED \ 309 | size_t Name##_writable() \ 310 | { \ 311 | return recorder_ring_writable(&Name.ring); \ 312 | } \ 313 | \ 314 | static inline RECORDER_RING_MAYBE_UNUSED \ 315 | Type * Name##_peek() \ 316 | { \ 317 | return (Type *) recorder_ring_peek(&Name.ring); \ 318 | } \ 319 | \ 320 | static inline RECORDER_RING_MAYBE_UNUSED \ 321 | size_t Name##_read(Type *ptr, ringidx_t count) \ 322 | { \ 323 | return recorder_ring_read(&Name.ring, ptr, count, \ 324 | NULL, NULL, NULL); \ 325 | } \ 326 | \ 327 | static inline RECORDER_RING_MAYBE_UNUSED \ 328 | size_t Name##_write(Type *ptr, ringidx_t count) \ 329 | { \ 330 | return recorder_ring_write(&Name.ring, ptr, count, \ 331 | NULL, NULL, NULL); \ 332 | } \ 333 | \ 334 | static inline RECORDER_RING_MAYBE_UNUSED \ 335 | size_t Name##_block_read(Type *ptr, \ 336 | size_t count, \ 337 | ringidx_t *reader, \ 338 | recorder_ring_block_fn block, \ 339 | recorder_ring_block_fn overflow) \ 340 | { \ 341 | return recorder_ring_read(&Name.ring, ptr, count, \ 342 | reader, block, overflow); \ 343 | } \ 344 | \ 345 | static inline RECORDER_RING_MAYBE_UNUSED \ 346 | size_t Name##_block_write(const Type *ptr, \ 347 | size_t count, \ 348 | recorder_ring_block_fn write_block, \ 349 | recorder_ring_block_fn commit_block, \ 350 | ringidx_t *pos) \ 351 | { \ 352 | return recorder_ring_write(&Name.ring, ptr, count, \ 353 | write_block, commit_block, pos); \ 354 | } 355 | 356 | 357 | #define RECORDER_RING_DEFINE(Name, Type, Size) \ 358 | /* ----------------------------------------------------------------*/ \ 359 | /* Define a named ring buffer */ \ 360 | /* ----------------------------------------------------------------*/ \ 361 | \ 362 | struct Name##_ring Name = \ 363 | { \ 364 | { Size, sizeof(Type), 0, 0, 0, 0 } \ 365 | }; 366 | 367 | 368 | #ifdef __cplusplus 369 | } 370 | #endif // __cplusplus 371 | 372 | #endif // RECORDER_RING_H 373 | -------------------------------------------------------------------------------- /recorder_test.c: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // recorder_test.c Recorder project 3 | // ***************************************************************************** 4 | // 5 | // File description: 6 | // 7 | // Test for the flight recorder 8 | // 9 | // This tests that we can record things and dump them. 10 | // 11 | // 12 | // 13 | // 14 | // 15 | // 16 | // ***************************************************************************** 17 | // This software is licensed under the GNU General Public License v3+ 18 | // (C) 2017-2020, Christophe de Dinechin 19 | // (C) 2018, Frediano Ziglio 20 | // ***************************************************************************** 21 | // This file is part of Recorder 22 | // 23 | // Recorder is free software: you can redistribute it and/or modify 24 | // it under the terms of the GNU General Public License as published by 25 | // the Free Software Foundation, either version 3 of the License, 26 | // or (at your option) any later version. 27 | // 28 | // Recorder is distributed in the hope that it will be useful, 29 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 30 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 31 | // GNU General Public License for more details. 32 | // 33 | // You should have received a copy of the GNU General Public License 34 | // along with Recorder, in a file named COPYING. 35 | // If not, see . 36 | // ***************************************************************************** 37 | 38 | #include "recorder_ring.h" 39 | #include "recorder.h" 40 | #include "alt_drand48.h" 41 | 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | 52 | 53 | int failed = 0; 54 | 55 | RECORDER(MAIN, 64, "Global operations in 'main()'"); 56 | RECORDER(Pauses, 256, "Pauses during blocking operations"); 57 | RECORDER(Special, 64, "Special operations to the recorder"); 58 | RECORDER(SpeedTest, 32, "Recorder speed test"); 59 | RECORDER(SpeedInfo, 32, "Recorder information during speed test"); 60 | RECORDER(FastSpeedTest, 32, "Fast recorder speed test"); 61 | 62 | 63 | 64 | // ============================================================================ 65 | // 66 | // Flight recorder testing 67 | // 68 | // ============================================================================ 69 | 70 | uintptr_t recorder_count = 0; 71 | unsigned pauses_count = 0; 72 | 73 | #define INFO(...) \ 74 | do \ 75 | { \ 76 | record(MAIN, __VA_ARGS__); \ 77 | char buf[256]; \ 78 | snprintf(buf, sizeof(buf), __VA_ARGS__); \ 79 | puts(buf); \ 80 | } while(0) 81 | 82 | #define VERBOSE(...) if (debug) INFO(__VA_ARGS__) 83 | 84 | #define FAIL(...) \ 85 | do \ 86 | { \ 87 | record(MAIN, "FAILURE"); \ 88 | record(MAIN, __VA_ARGS__); \ 89 | char buf[256]; \ 90 | snprintf(buf, sizeof(buf), __VA_ARGS__); \ 91 | puts(buf); \ 92 | failed = 1; \ 93 | } while(0) 94 | 95 | unsigned thread_id = 0; 96 | unsigned threads_to_stop = 0; 97 | 98 | void dawdle(unsigned minimumMs, unsigned deltaMs) 99 | { 100 | struct timespec tm; 101 | tm.tv_sec = 0; 102 | tm.tv_nsec = (minimumMs + drand48() * deltaMs) * 1000000; 103 | record(Pauses, "Pausing #%u %ld.%03dus", 104 | recorder_ring_fetch_add(pauses_count, 1), 105 | tm.tv_nsec / 1000, tm.tv_nsec % 1000); 106 | nanosleep(&tm, NULL); 107 | } 108 | 109 | // RECORDER(SpeedTest, 32, "Recorder speed test"); 110 | RECORDER_TWEAK_DEFINE(sleep_time, 0, "Sleep time between records"); 111 | RECORDER_TWEAK_DEFINE(sleep_time_delta, 0, "Variations in sleep time between records"); 112 | 113 | void *recorder_thread(void *thread) 114 | { 115 | uintptr_t i = 0; 116 | unsigned tid = (unsigned) (uintptr_t) thread; 117 | uintptr_t last_time = recorder_tick(); 118 | while (!threads_to_stop) 119 | { 120 | i++; 121 | uintptr_t current_time = recorder_tick(); 122 | record(SpeedTest, "[thread %u] Recording %u, mod %u after %ld", tid, i, i % 500, 123 | current_time - last_time); 124 | last_time = current_time; 125 | if (RECORDER_TWEAK(sleep_time)) 126 | { 127 | struct timespec tm; 128 | uint64_t wait_time = (uint64_t) 129 | (RECORDER_TWEAK(sleep_time) + drand48()*RECORDER_TWEAK(sleep_time_delta)); 130 | tm.tv_sec = 0; 131 | tm.tv_nsec = wait_time * 1000; 132 | nanosleep(&tm, NULL); 133 | } 134 | } 135 | recorder_ring_fetch_add(recorder_count, i); 136 | recorder_ring_fetch_add(threads_to_stop, -1); 137 | return NULL; 138 | } 139 | 140 | void *recorder_fast_thread(void *thread) 141 | { 142 | uintptr_t i = 0; 143 | unsigned tid = (unsigned) (uintptr_t) thread; 144 | while (!threads_to_stop) 145 | { 146 | i++; 147 | RECORD_FAST(FastSpeedTest, "[thread %u] Fast recording %u mod %u", 148 | tid, i, i % 700); 149 | } 150 | recorder_ring_fetch_add(recorder_count, i); 151 | recorder_ring_fetch_add(threads_to_stop, -1); 152 | return NULL; 153 | } 154 | 155 | typedef struct example { int x; int y; int z; } example_t; 156 | 157 | size_t show_struct(intptr_t trace, 158 | const char *format, char *buffer, size_t len, uintptr_t data) 159 | { 160 | example_t *e = (example_t *) data; 161 | size_t s = trace 162 | ? snprintf(buffer, len, "example(%d, %d, %d)", e->x, e->y, e->z) 163 | : snprintf(buffer, len, "example(%p)", e); 164 | return s; 165 | } 166 | 167 | void flight_recorder_test(int argc, char **argv) 168 | { 169 | int i, j; 170 | unsigned count = argc >= 2 ? atoi(argv[1]) : 16; 171 | unsigned howLong = argc >= 3 ? atoi(argv[2]) : 1; 172 | 173 | INFO("Testing recorder version %u.%02u.%02u", 174 | RECORDER_VERSION_MAJOR(RECORDER_CURRENT_VERSION), 175 | RECORDER_VERSION_MINOR(RECORDER_CURRENT_VERSION), 176 | RECORDER_VERSION_PATCH(RECORDER_CURRENT_VERSION)); 177 | if (RECORDER_CURRENT_VERSION != RECORDER_VERSION(1,2,2)) 178 | FAIL("Testing an unexpected version of the recorder, " 179 | "update RECORDER_CURRENT_VERSION"); 180 | 181 | for (i = 0; i < 2; i++) 182 | { 183 | recorder_count = 0; 184 | 185 | INFO("Launching %u %s recorder thread%s", 186 | count, i ? "fast" : "normal", count>1?"s":""); 187 | record(MAIN, "Starting %s speed test for %us with %u threads", 188 | i ? "fast" : "normal", howLong, count); 189 | 190 | pthread_t tid; 191 | for (j = 0; j < count; j++) 192 | pthread_create(&tid, NULL, 193 | i ? recorder_fast_thread : recorder_thread, 194 | (void *) (intptr_t) j); 195 | 196 | INFO("%s recorder testing in progress, please wait about %ds", 197 | i ? "Fast" : "Normal", howLong); 198 | unsigned sleepTime = howLong; 199 | do { sleepTime = sleep(sleepTime); } while (sleepTime); 200 | INFO("%s recorder testing completed, stopping threads", 201 | i ? "Fast" : "Normal"); 202 | threads_to_stop = count; 203 | 204 | while(threads_to_stop) 205 | { 206 | record(Pauses, "Waiting for recorder threads to stop, %u remaining", 207 | threads_to_stop); 208 | dawdle(1, 0); 209 | } 210 | INFO("%s test: all threads have stopped, %"PRIuPTR" iterations", 211 | i ? "Fast" : "Normal", recorder_count); 212 | 213 | recorder_count += (recorder_count == 0); 214 | printf("Recorder test analysis (%s):\n" 215 | " Iterations = %8"PRIuPTR"\n" 216 | " Iterations / ms = %8"PRIuPTR"\n" 217 | " Duration per record = %8uns\n" 218 | " Number of threads = %8u\n", 219 | i ? "Fast version" : "Normal version", 220 | recorder_count, 221 | recorder_count / (howLong * 1000), 222 | (unsigned) (howLong * 1000000000ULL / recorder_count), 223 | count); 224 | 225 | INFO("Recorder test complete (%s), %u threads.", 226 | i ? "Fast version" : "Normal version", count); 227 | INFO(" Iterations = %10"PRIuPTR, recorder_count); 228 | INFO(" Iterations / ms = %10"PRIuPTR, recorder_count / (howLong * 1000)); 229 | INFO(" Record cost = %10uns", 230 | (unsigned) (howLong * 1000000000ULL / recorder_count)); 231 | } 232 | 233 | record(Special, "Sizeof int=%u intptr_t=%u float=%u double=%u", 234 | sizeof(int), sizeof(intptr_t), sizeof(float), sizeof(double)); 235 | 236 | record(Special, "Float 3.1415 = %f", 3.1415f); 237 | record(Special, "Float X 3.1415 = %x", 3.1415f); 238 | record(Special, "Double 3.1415 = %f", 3.1415); 239 | record(Special, "Double X 3.1415 = %x", 3.1415); 240 | record(Special, "Large %d %u %ld %lu %f %s", 241 | 1,2u,3l,4lu,5.0,"six"); 242 | record(Special, "Larger %d %u %ld %lu %f %s %p %g", 243 | 1,2u,3l,4lu,5.0,"six",(void *) 7,8.0); 244 | record(Special, "Largest %d %u %ld %lu %f %s %p %g %x %lu %u", 245 | 1,2u,3l,4lu,5.0,"six",(void *) 7,8.0, 9, 10, 11); 246 | record(Special, "Format '%8s' '%-8s' '%8.2f' '%-8.2f'", 247 | "abc", "def", 1.2345, 2.3456); 248 | record(Special, "Format '%*s' '%*.*f'", 249 | 8, "abc", 8, 2, 1.2345); 250 | 251 | recorder_configure_type('E', show_struct); 252 | example_t x1 = { 1, 2, 3 }; 253 | example_t x2 = { 42, -42, 42*42 }; 254 | 255 | record(Special, "Struct dump %E then %E", &x1, &x2); 256 | 257 | recorder_dump_for("Special"); 258 | recorder_dump(); 259 | 260 | if (getenv("KEEP_RUNNING")) 261 | { 262 | uintptr_t k = 0; 263 | uintptr_t last_k = 0; 264 | uintptr_t last_tick = recorder_tick(); 265 | while(true) 266 | { 267 | k++; 268 | record(FastSpeedTest, "[thread %u] Recording %u, mod %u", 269 | (unsigned) (200 * sin(0.03 * k) * sin (0.000718231*k) + 200), 270 | (unsigned) (k * drand48()), 271 | k % 627); 272 | // dawdle(1, 3); 273 | uintptr_t tick = recorder_tick(); 274 | if (tick - last_tick > RECORDER_HZ/1000) 275 | { 276 | record(SpeedInfo, "Iterations per millisecond: %lu (%f ns)", 277 | k - last_k, 1e6 / (k - last_k)); 278 | last_k = k; 279 | last_tick = tick; 280 | } 281 | } 282 | } 283 | } 284 | 285 | 286 | 287 | // ============================================================================ 288 | // 289 | // Main entry point 290 | // 291 | // ============================================================================ 292 | 293 | int main(int argc, char **argv) 294 | { 295 | recorder_dump_on_common_signals(0, 0); 296 | flight_recorder_test(argc, argv); 297 | return failed; 298 | } 299 | -------------------------------------------------------------------------------- /ring_test.c: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // ring_test.c Recorder project 3 | // ***************************************************************************** 4 | // 5 | // File description: 6 | // 7 | // Test ring buffer with multiple concurrent writers, one reader, 8 | // and variable-size writes. This corresponds to the use of the ring 9 | // buffer as a circular print buffer, where we want messages to be 10 | // in order, and not mixed one with another. 11 | // 12 | // The test writes messages with different length. However, the length 13 | // can be determined from the first letter. It then checks that messages 14 | // are not garbled by other threads. 15 | // 16 | // ***************************************************************************** 17 | // This software is licensed under the GNU Lesser General Public License v2+ 18 | // (C) 2017-2020, Christophe de Dinechin 19 | // (C) 2018, Frediano Ziglio 20 | // ***************************************************************************** 21 | // This file is part of Recorder 22 | // 23 | // Recorder is free software: you can redistribute it and/or modify 24 | // it under the terms of the GNU Lesser General Public License as published by 25 | // the Free Software Foundation, either version 2 of the License, or 26 | // (at your option) any later version. 27 | // 28 | // Recorder is distributed in the hope that it will be useful, 29 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 30 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 31 | // GNU Lesser General Public License for more details. 32 | // 33 | // You should have received a copy of the GNU Lesser General Public License 34 | // along with Recorder, in a file named COPYING. 35 | // If not, see . 36 | // ***************************************************************************** 37 | 38 | #include "recorder_ring.h" 39 | #include "recorder.h" 40 | #include "alt_drand48.h" 41 | 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | 50 | 51 | int failed = 0; 52 | 53 | 54 | 55 | // ============================================================================ 56 | // 57 | // Recorders that we will use here 58 | // 59 | // ============================================================================ 60 | 61 | RECORDER(MAIN, 64, "Global operations in 'main()'"); 62 | RECORDER(Pauses, 256, "Pauses during blocking operations"); 63 | RECORDER(Reads, 256, "Reading from the ring"); 64 | RECORDER(Writes, 256, "Writing into the ring"); 65 | RECORDER(Special, 64, "Special operations to the recorder"); 66 | RECORDER(SpeedTest, 32, "Recorder speed test"); 67 | RECORDER(Timing, 64, "Timing information"); 68 | 69 | 70 | 71 | // ============================================================================ 72 | // 73 | // Ringbuffer test 74 | // 75 | // ============================================================================ 76 | 77 | #include "ring_test.h" 78 | 79 | 80 | // Generate N writer threads. Each writer attempts to read if there is 81 | // enough room for it. It writes one of a set of known strings. 82 | // The test is to check that we only get exactly the known strings 83 | // and not some jumble of strings from different sizes. 84 | // Notice that there is something special about the initials 85 | 86 | int debug = 0; 87 | 88 | const char *testStrings[] = 89 | { 90 | "All your bases are belong to us", 91 | "Be yourself, everyone else is already taken", 92 | "Can't you read?", 93 | "Des cubes?", 94 | "Extraordinary claims require extraordinary evidence", 95 | "Fool!", 96 | "Gastoooon!", 97 | "History has a tendency to repeat itself", 98 | "I see no reason to believe you exist", 99 | "Jealousy is all the fun you think they had", 100 | "Kangaroos everywhere", 101 | "Le pelican est avec le kangourou le seul marsupial volant a avoir " 102 | "une poche ventrale sous le bec", 103 | "Make it so", 104 | "Ni pour ni contre, bien au contraire", 105 | "Oh, des poules!", 106 | "Petit, mais costaud", 107 | "Q", 108 | "Rarely have mere mortals developed code of such low quality", 109 | "Sympa, ce sofa si soft", 110 | "Total verrueckt", 111 | "Under capitalism, man exploits man, " 112 | "under communism it's just the opposite", 113 | "Va, cours, vole et nous venge", 114 | "Whaaaaaat?!?", 115 | "Xenodocheionology is apparently a pathologic love of hotels", 116 | "Y a-t-il un developpeur pour sauver ce code", 117 | "Zero seems like an odd value here" 118 | }; 119 | 120 | 121 | #define INFO(...) \ 122 | do \ 123 | { \ 124 | char buf[256]; \ 125 | int len = snprintf(buf, sizeof(buf), \ 126 | "R%5zu W%5zu C%5zu L%5zu: ", \ 127 | buffer.ring.reader, buffer.ring.writer, \ 128 | buffer.ring.commit, \ 129 | buffer_writable()); \ 130 | len += snprintf(buf + len, sizeof(buf) - len, __VA_ARGS__); \ 131 | puts(buf); \ 132 | } while(0) 133 | 134 | #define VERBOSE(...) if (debug) INFO(__VA_ARGS__) 135 | 136 | #define FAIL(...) \ 137 | do \ 138 | { \ 139 | char buf[256]; \ 140 | int len = snprintf(buf, sizeof(buf), \ 141 | "R%5zu W%5zu C%5zu L%5zu: " \ 142 | "FAILED: ", \ 143 | buffer.ring.reader, \ 144 | buffer.ring.writer, \ 145 | buffer.ring.commit, \ 146 | buffer_writable()); \ 147 | len += snprintf(buf + len, sizeof(buf) - len, __VA_ARGS__); \ 148 | puts(buf); \ 149 | failed = 1; \ 150 | recorder_dump(); \ 151 | } while(0) 152 | 153 | 154 | unsigned count_write_blocked = 0; 155 | unsigned count_write_spins = 0; 156 | unsigned count_commit_blocked = 0; 157 | unsigned count_commit_spins = 0; 158 | unsigned count_read_blocked = 0; 159 | unsigned count_read_spins = 0; 160 | unsigned count_writes = 0; 161 | unsigned count_written = 0; 162 | unsigned count_reads = 0; 163 | unsigned count_read = 0; 164 | unsigned count_read_overflow = 0; 165 | 166 | unsigned thread_id = 0; 167 | unsigned threads_to_stop = 0; 168 | 169 | void dawdle(unsigned minimumMs) 170 | { 171 | struct timespec tm; 172 | tm.tv_sec = 0; 173 | tm.tv_nsec = + minimumMs * (1000 * 1000 + drand48() * 2000000); 174 | record(Pauses, "Pausing %ld.%03dus", tm.tv_nsec / 1000, tm.tv_nsec % 1000); 175 | nanosleep(&tm, NULL); 176 | } 177 | 178 | 179 | bool writer_block(recorder_ring_t *rb, ringidx_t oldW, ringidx_t lastW) 180 | { 181 | record(Writes, "Blocking write old=%u last=%u", oldW, lastW); 182 | 183 | recorder_ring_fetch_add(count_write_blocked, 1); 184 | 185 | /* Wait until reader is beyond the last item we are going to write */ 186 | record(Writes,"Blocking write %zu-%zu", oldW, lastW); 187 | while ((intptr_t) (lastW - rb->reader) >= (intptr_t) (rb->size - 1)) 188 | { 189 | recorder_ring_fetch_add(count_write_spins, 1); 190 | VERBOSE("Blocking write ahead %d %zu-%zu", 191 | (int) (lastW - rb->reader - rb->size), 192 | oldW, lastW); 193 | record(Pauses,"Blocking write ahead %d %zu-%zu", 194 | (int) (lastW - rb->reader - rb->size), 195 | oldW, lastW); 196 | dawdle(5); 197 | } 198 | VERBOSE("Unblocked write ahead %d %zu-%zu", 199 | (int) (lastW - rb->reader - rb->size), 200 | oldW, lastW); 201 | record(Writes, "Unblocking old=%u last=%u", 202 | oldW, lastW); 203 | 204 | /* It's now safe to keep writing */ 205 | return true; 206 | } 207 | 208 | 209 | bool commit_block(recorder_ring_t *rb, ringidx_t commit, ringidx_t oldW) 210 | { 211 | record(Writes, "Blocking commit current=%u need=%u", commit, oldW); 212 | 213 | recorder_ring_fetch_add(count_commit_blocked, 1); 214 | 215 | /* Wait until reader is beyond the last item we are going to write */ 216 | record(Reads,"Blocking commit %zu-%zu", commit, oldW); 217 | while (rb->commit != oldW) 218 | { 219 | recorder_ring_fetch_add(count_commit_spins, 1); 220 | VERBOSE("Blocking commit, at %zu, need %zu", rb->commit, oldW); 221 | record(Pauses,"Blocking commit %zu-%zu-%zu", commit, rb->commit, oldW); 222 | dawdle(1); 223 | } 224 | VERBOSE("Unblocked commit was %zu, needed %zu, now %zu", 225 | commit, oldW, rb->commit); 226 | record(Writes, "Unblocking commit, was %zu, needed %zu, now %zu", 227 | commit, oldW, rb->commit); 228 | 229 | /* It's now safe to keep writing */ 230 | return true; 231 | } 232 | 233 | 234 | void *writer_thread(void *data) 235 | { 236 | const unsigned numberOfTests = sizeof(testStrings) / sizeof(*testStrings); 237 | unsigned tid = recorder_ring_fetch_add(thread_id, 1); 238 | record(MAIN, "Entering writer thread %u", tid); 239 | 240 | while (!threads_to_stop) 241 | { 242 | int index = drand48() * numberOfTests; 243 | const char *str = testStrings[index]; 244 | int len = strlen(str); 245 | VERBOSE("Write #%02d '%s' size %u", tid, str, len); 246 | recorder_ring_fetch_add(count_writes, 1); 247 | record(Writes, "Writing '%s'", str); 248 | ringidx_t wr = 0; 249 | size_t size = buffer_block_write(str, len, 250 | writer_block, commit_block, &wr); 251 | record(Writes, "Wrote '%s' size %zu at index %u", str, size, wr); 252 | recorder_ring_fetch_add(count_written, 1); 253 | 254 | VERBOSE("Wrote #%02d '%s' at offset %lu-%lu size %u", 255 | tid, str, wr, wr + len - 1, len); 256 | } 257 | unsigned toStop = recorder_ring_fetch_add(threads_to_stop, -1U); 258 | record(MAIN, "Exiting thread %u, stopping %u more", tid, toStop); 259 | return NULL; 260 | } 261 | 262 | 263 | bool reader_block(recorder_ring_t *rb, ringidx_t curR, ringidx_t lastR) 264 | { 265 | record(Reads, "Blocked curR=%zu lastR=%zu", curR, lastR); 266 | recorder_ring_fetch_add(count_read_blocked, 1); 267 | while ((intptr_t) (rb->commit - lastR) < 0) 268 | { 269 | recorder_ring_fetch_add(count_read_spins, 1); 270 | VERBOSE("Blocking read commit=%zu lastR=%zu", rb->commit, lastR); 271 | record(Pauses, "Blocking read commit=%zu last=%zu", rb->commit, lastR); 272 | dawdle(1); 273 | } 274 | record(Reads, "Unblocking commit=%zu lastR=%zu", rb->commit, lastR); 275 | return true; // We waited until commit caught up, so we can keep reading 276 | } 277 | 278 | 279 | unsigned overflow_handler_called = 0; 280 | 281 | bool reader_overflow(recorder_ring_t *rb, ringidx_t curR, ringidx_t minR) 282 | { 283 | size_t skip = minR - curR; 284 | record(Reads, "Overflow currentR=%u minR=%u skip=%u", curR, minR, skip); 285 | 286 | recorder_ring_fetch_add(count_read_overflow, 1); 287 | VERBOSE("Reader overflow %zu reader %zu -> %zu, skip %zu", 288 | rb->overflow, rb->reader, minR, skip); 289 | recorder_ring_fetch_add(overflow_handler_called, 1); 290 | 291 | 292 | record(Reads, "End overflow minReader=%u skip=%u", minR, skip); 293 | 294 | // Writer actually blocks until reader catches up, so we can keep reading 295 | return 1; 296 | } 297 | 298 | 299 | void *reader_thread(void *data) 300 | { 301 | char buf[256]; 302 | unsigned tid = recorder_ring_fetch_add(thread_id, 1); 303 | ringidx_t rd = 0; 304 | 305 | record(MAIN, "Entering reader thread tid %u", tid); 306 | 307 | while (threads_to_stop != 1) 308 | { 309 | // Read initial byte, the capital at beginning of message 310 | unsigned overflow = buffer.ring.overflow; 311 | unsigned readable = buffer_readable(); 312 | 313 | if (overflow) 314 | { 315 | VERBOSE("Reader overflow #%02d is %u", 316 | tid, overflow); 317 | buffer.ring.overflow = 0; 318 | } 319 | 320 | char *ptr = buf; 321 | unsigned size = 0; 322 | if (readable) 323 | { 324 | // Reported that we can't read. Check if it's overflow 325 | size = buffer_block_read(buf, 1, &rd,reader_block,reader_overflow); 326 | if (size == 0) 327 | { 328 | FAIL("Blocking read did not get data"); 329 | } 330 | } 331 | record(Reads, "Index %u Readable: %u, Size: %u, Overflow %u", 332 | rd, readable, size, overflow); 333 | if (size == 0) 334 | continue; 335 | 336 | if (size > 1) 337 | { 338 | FAIL("Returned initial size %u is too large", size); 339 | exit(-1); 340 | } 341 | 342 | char initial = ptr[0]; 343 | if (initial < 'A' || initial > 'Z') 344 | { 345 | FAIL("First byte is '%c' (0x%x)", initial, initial); 346 | exit(-2); 347 | } 348 | unsigned index = initial - 'A'; 349 | const char *test = testStrings[index]; 350 | unsigned testLen = strlen(test); 351 | record(Reads, "Initial %c (%d), expecting '%s' length %u", 352 | initial, initial, test, testLen); 353 | 354 | // Read the rest of the buffer based on input length 355 | VERBOSE("Reading #%02d '%c' %u bytes", tid, initial, testLen); 356 | recorder_ring_fetch_add(count_reads, 1); 357 | size += buffer_block_read(buf + size, testLen - size, &rd, 358 | reader_block, reader_overflow); 359 | recorder_ring_fetch_add(count_read, 1); 360 | record(Reads, "Index %u: Read %u bytes out of %u at index %u", 361 | rd, size, testLen); 362 | 363 | if (testLen != size) 364 | { 365 | FAIL("Length for '%c' is %u, should be %u", 366 | initial, size, testLen); 367 | exit(-3); 368 | } 369 | 370 | if (memcmp(ptr, test, testLen) != 0) 371 | { 372 | ptr[testLen] = 0; 373 | FAIL("Data miscompare, had %u bytes '%s' != '%s'", 374 | size, ptr, test); 375 | exit(-4); 376 | } 377 | buffer.ring.reader = rd; 378 | 379 | ptr[size] = 0; 380 | VERBOSE("Read #%02d '%s' %u bytes", tid, ptr, testLen); 381 | } 382 | 383 | unsigned toStop = recorder_ring_fetch_add(threads_to_stop, -1); 384 | record(MAIN, "Exiting reader thread tid %u, %u more to stop", tid, toStop); 385 | 386 | return NULL; 387 | } 388 | 389 | 390 | int ringbuffer_test(int argc, char **argv) 391 | { 392 | pthread_t tid; 393 | 394 | record(MAIN, "Entering ringbuffer test argc=%d", argc); 395 | INFO("Launching reader thread"); 396 | pthread_create(&tid, NULL, reader_thread, NULL); 397 | 398 | int i, count = argc >= 2 ? atoi(argv[1]) : 16; 399 | if (count < 0) 400 | { 401 | debug = 1; 402 | count = -count; 403 | } 404 | 405 | INFO("Launching %d writer thread%s", count, count>1?"s":""); 406 | for (i = 0; i < count; i++) 407 | pthread_create(&tid, NULL, writer_thread, NULL); 408 | 409 | 410 | unsigned howLong = argc >= 3 ? atoi(argv[2]) : 1; 411 | INFO("Testing in progress, please wait about %ds", howLong); 412 | unsigned sleepTime = howLong; 413 | do { sleepTime = sleep(sleepTime); } while (sleepTime); 414 | INFO("Testing completed successfully:"); 415 | record(MAIN, "Stopping threads"); 416 | threads_to_stop = count + 1; 417 | 418 | while(threads_to_stop) 419 | { 420 | record(Pauses, "Waiting for ring test threads to stop, %u remaining", 421 | threads_to_stop); 422 | dawdle(1); 423 | } 424 | 425 | printf("Test analysis:\n" 426 | " Initiated Writes = %8u (Requests to write in buffer)\n" 427 | " Completed Writes = %8u (Writes that were finished, %3d.%02d%%)\n" 428 | " Blocked Writes = %8u (Writes that blocked, %3d.%02d%%)\n" 429 | " Spinning Writes = %8u (Number of spins waiting to write)\n" 430 | " Blocked Commits = %8u (Commits that blocked, %3d.%02d%%)\n" 431 | " Spinning Commits = %8u (Number of spins waiting to commit)\n" 432 | " Initiated Reads = %8u (Requests to read from buffer)\n" 433 | " Completed Reads = %8u (Number of reads that finished)\n" 434 | " Blocked Reads = %8u (Reads that blocked, %3d.%02d%%)\n" 435 | " Spinning Reads = %8u (Number of spins waiting to read)\n" 436 | " Overflow Reads = %8u (Number of read overflows)\n", 437 | count_writes, count_written, 438 | 100 * count_written / count_writes, 439 | (10000 * count_written / count_writes) % 100, 440 | count_write_blocked, 441 | 100 * count_write_blocked / count_writes, 442 | (10000 * count_write_blocked / count_writes) % 100, 443 | count_write_spins, 444 | count_commit_blocked, 445 | 100 * count_commit_blocked / count_writes, 446 | (10000 * count_commit_blocked / count_writes) % 100, 447 | count_commit_spins, 448 | count_reads, count_read, count_read_blocked, 449 | 100 * count_read_blocked / count_reads, 450 | (10000 * count_read_blocked / count_reads) % 100, 451 | count_read_spins, 452 | count_read_overflow); 453 | 454 | 455 | return 0; 456 | } 457 | 458 | 459 | RECORDER_RING_DECLARE(speed_test, recorder_entry, 512); 460 | RECORDER_RING_DEFINE(speed_test, recorder_entry, 512); 461 | 462 | 463 | static inline ringidx_t special_ring_write(recorder_ring_p ring, 464 | recorder_entry *source) 465 | // ---------------------------------------------------------------------------- 466 | // Optimized version 467 | // ---------------------------------------------------------------------------- 468 | { 469 | const size_t size = 512; 470 | recorder_entry * data = (recorder_entry *) (ring + 1); 471 | ringidx_t writer = recorder_ring_fetch_add(ring->writer, 1); 472 | data[writer % size] = *source; 473 | return writer; 474 | } 475 | 476 | 477 | void compare_performance_of_common_operations(unsigned loops) 478 | { 479 | unsigned i; 480 | uintptr_t start, duration; 481 | double cost; 482 | recorder_entry entry = { 0 }; 483 | void * ptrs[256] = { NULL }; 484 | 485 | #define TEST(Info, Code) \ 486 | record(Timing, "Test: " Info); \ 487 | start = recorder_tick(); \ 488 | for (i = 0; i < loops; i++) { Code; } \ 489 | duration = recorder_tick() - start; \ 490 | cost = 1e9 * duration / RECORDER_HZ / loops; \ 491 | record(Timing, Info " cost is %.6f ns", cost); 492 | 493 | TEST("regular ring_write", speed_test_write(&entry, 1)); 494 | TEST("special ring_write", special_ring_write(&speed_test.ring, &entry)); 495 | TEST("fetch-add", 496 | entry.order = recorder_ring_fetch_add(recorder_order, 1); 497 | special_ring_write(&speed_test.ring, &entry)); 498 | TEST("recorder_tick()", 499 | entry.timestamp = recorder_tick(); 500 | special_ring_write(&speed_test.ring, &entry)); 501 | TEST("tick + fetch-add", 502 | entry.order = recorder_ring_fetch_add(recorder_order, 1); 503 | entry.timestamp = recorder_tick(); 504 | special_ring_write(&speed_test.ring, &entry)); 505 | TEST("tick + fetch-add + copy", 506 | entry.order = recorder_ring_fetch_add(recorder_order, 1); 507 | entry.timestamp = recorder_tick(); 508 | entry.args[0] = i; 509 | entry.args[1] = 3-i; 510 | entry.args[2] = i * 1081; 511 | entry.args[3] = i ^ 0xFE; 512 | special_ring_write(&speed_test.ring, &entry)); 513 | #ifdef CLOCK_MONOTONIC 514 | TEST("clock_gettime + copy", 515 | struct timespec ts; 516 | clock_gettime(CLOCK_MONOTONIC, &ts); 517 | entry.order = recorder_ring_fetch_add(recorder_order, 1); 518 | entry.timestamp = ts.tv_sec * 1000000L + ts.tv_nsec / 1000; 519 | entry.args[0] = i; 520 | entry.args[1] = 3-i; 521 | entry.args[2] = i * 1081; 522 | entry.args[3] = i ^ 0xFE; 523 | special_ring_write(&speed_test.ring, &entry)); 524 | #endif 525 | 526 | TEST("RECORD", record(SpeedTest, "Speed test %u", i)); 527 | TEST("RECORD_FAST", RECORD_FAST(SpeedTest, "Speed test %u", i)); 528 | 529 | TEST("malloc(512)", 530 | free(ptrs[i % 256]); 531 | ptrs[i % 256] = malloc(32)); 532 | TEST("malloc(jigsaw)", 533 | free(ptrs[i % 256]); 534 | ptrs[i % 256] = malloc(512 + i % 7777 * 13)); 535 | TEST("memcpy", 536 | memcpy(ptrs[i % 256], ptrs[(i+1) % 256], 512)); 537 | TEST("gettimeofday", recorder_tick()); 538 | TEST("snprintf", snprintf(ptrs[i % 256], 512, "Speed test %u", i)); 539 | 540 | FILE *f = fopen("test.out", "w"); 541 | TEST("fprintf", fprintf(f, "Speed test %u", i)); 542 | fclose(f); 543 | 544 | f = fopen("test.out", "w"); 545 | TEST("fprintf + fflush", fprintf(f, "Speed test %u", i); fflush(f)); 546 | fclose(f); 547 | 548 | recorder_dump_for("Timing"); 549 | } 550 | 551 | 552 | 553 | // ============================================================================ 554 | // 555 | // Main entry point 556 | // 557 | // ============================================================================ 558 | 559 | int main(int argc, char **argv) 560 | { 561 | recorder_trace_set(".*_(warning|error)"); 562 | recorder_dump_on_common_signals(0, 0); 563 | ringbuffer_test(argc, argv); 564 | if (failed) 565 | recorder_dump(); // Try to figure out what failed 566 | compare_performance_of_common_operations(100000); 567 | return failed; 568 | } 569 | -------------------------------------------------------------------------------- /ring_test.h: -------------------------------------------------------------------------------- 1 | #ifndef RING_TEST_H 2 | #define RING_TEST_H 3 | // ***************************************************************************** 4 | // ring_test.h Recorder project 5 | // ***************************************************************************** 6 | // 7 | // File description: 8 | // 9 | // Header for ring_test.c 10 | // 11 | // 12 | // 13 | // 14 | // 15 | // 16 | // 17 | // 18 | // ***************************************************************************** 19 | // This software is licensed under the GNU Lesser General Public License v2+ 20 | // (C) 2017-2020, Christophe de Dinechin 21 | // ***************************************************************************** 22 | // This file is part of Recorder 23 | // 24 | // Recorder is free software: you can redistribute it and/or modify 25 | // it under the terms of the GNU Lesser General Public License as published by 26 | // the Free Software Foundation, either version 2 of the License, or 27 | // (at your option) any later version. 28 | // 29 | // Recorder is distributed in the hope that it will be useful, 30 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 31 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 32 | // GNU Lesser General Public License for more details. 33 | // 34 | // You should have received a copy of the GNU Lesser General Public License 35 | // along with Recorder, in a file named COPYING. 36 | // If not, see . 37 | // ***************************************************************************** 38 | 39 | // Place these declaration in a header to avoid clang warnings 40 | #define RECORDER_RING(Name, Type, Size) \ 41 | RECORDER_RING_DECLARE(Name, Type, Size) \ 42 | RECORDER_RING_DEFINE (Name, Type, Size) 43 | RECORDER_RING(buffer, char, 1024); 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /scope/MinMaxAverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c3d/recorder/8b0b079b5462a75db285dd0a240735cb052c131c/scope/MinMaxAverage.png -------------------------------------------------------------------------------- /scope/Scope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c3d/recorder/8b0b079b5462a75db285dd0a240735cb052c131c/scope/Scope.png -------------------------------------------------------------------------------- /scope/ScopeAndSliders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c3d/recorder/8b0b079b5462a75db285dd0a240735cb052c131c/scope/ScopeAndSliders.png -------------------------------------------------------------------------------- /scope/Timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c3d/recorder/8b0b079b5462a75db285dd0a240735cb052c131c/scope/Timing.png -------------------------------------------------------------------------------- /scope/recorder_scope.cpp: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // recorder_scope.cpp Recorder project 3 | // ***************************************************************************** 4 | // 5 | // File description: 6 | // 7 | // A quick oscilloscope-like visualizer for the flight recorder 8 | // 9 | // 10 | // 11 | // 12 | // 13 | // 14 | // 15 | // 16 | // ***************************************************************************** 17 | // This software is licensed under the GNU General Public License v3+ 18 | // (C) 2017-2020, Christophe de Dinechin 19 | // (C) 2018, Frediano Ziglio 20 | // ***************************************************************************** 21 | // This file is part of Recorder 22 | // 23 | // Recorder is free software: you can redistribute it and/or modify 24 | // it under the terms of the GNU General Public License as published by 25 | // the Free Software Foundation, either version 3 of the License, or 26 | // (at your option) any later version. 27 | // 28 | // Recorder is distributed in the hope that it will be useful, 29 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 30 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 31 | // GNU General Public License for more details. 32 | // 33 | // You should have received a copy of the GNU General Public License 34 | // along with Recorder, in a file named COPYING. 35 | // If not, see . 36 | // ***************************************************************************** 37 | 38 | #include "recorder_view.h" 39 | #include "recorder_slider.h" 40 | #include "recorder.h" 41 | 42 | #include 43 | #include 44 | #include 45 | 46 | 47 | static void usage(const char *progname) 48 | // ---------------------------------------------------------------------------- 49 | // Display usage information about the program 50 | // ---------------------------------------------------------------------------- 51 | { 52 | printf("Usage: %s [[-c config][-s slider][chan_re]...]\n" 53 | "\n" 54 | " Arguments:\n" 55 | " chan_re : Add view with channels matching regexp\n" 56 | " -c config : Send configuration command\n" 57 | " -s slider : Setup a control slider\n" 58 | " -d delay : Set max delay in seconds\n" 59 | " -w samples : Set max width in samples (0 = window width)\n" 60 | " -t : Show/hide time graph\n" 61 | " -m : Show/hide min/max graph\n" 62 | " -a : Show/hide average graph\n" 63 | " -n : Show/hide normal vaue graph\n" 64 | " -r ratio : Set averaging ratio in percent\n" 65 | " -b basename : Set basename for saving data\n" 66 | " -g WxH@XxY : Set window geometry to W x H pixels\n" 67 | "\n" 68 | " Configuration syntax for -c matches RECORDER_TRACES syntax\n" 69 | " Slider syntax is slider[=value[:min:max]]\n" 70 | "\n" 71 | " See http://github.com/c3d/recorder for more information\n" 72 | "\n" 73 | " Examples of arguments:\n" 74 | " -c '.*errors' : Enable display of all errors\n" 75 | " -c rate=10 : Set 'rate' tweak to 10\n" 76 | " -s rate=10:2:39 : Create slider to set 'rate'\n" 77 | " initial value 10, range 2 to 39\n" 78 | " my_graph : Show graph for channel 'my_graph'\n" 79 | " (min|max)_rate : Show min_rate and max_rate graph\n", 80 | progname); 81 | } 82 | 83 | 84 | int main(int argc, char *argv[]) 85 | // ---------------------------------------------------------------------------- 86 | // Create the main widget for the oscilloscope and display it 87 | // ---------------------------------------------------------------------------- 88 | { 89 | recorder_trace_set(".*_warning|.*_error"); 90 | const char *path = recorder_export_file(); 91 | recorder_chans_p chans = recorder_chans_open(path); 92 | if (!chans) 93 | { 94 | fprintf(stderr, "Unable to open recorder shared memory '%s'\n", path); 95 | return 1; 96 | } 97 | 98 | QApplication a(argc, argv); 99 | QMainWindow window; 100 | QWidget *widget = new QWidget; 101 | QVBoxLayout *layout = new QVBoxLayout; 102 | int views = 0; 103 | int configurations = 0; 104 | int width = -1, height = -1; 105 | int posx = -1, posy = -1; 106 | for (int a = 1; a < argc; a++) 107 | { 108 | QString arg = argv[a]; 109 | if (arg == "-h") 110 | { 111 | usage(argv[0]); 112 | } 113 | else if (arg == "-n") 114 | { 115 | RecorderView::showNormal = !RecorderView::showNormal; 116 | } 117 | else if (arg == "-t") 118 | { 119 | RecorderView::showTiming = !RecorderView::showTiming; 120 | } 121 | else if (arg == "-m") 122 | { 123 | RecorderView::showMinMax = !RecorderView::showMinMax; 124 | } 125 | else if (arg == "-a") 126 | { 127 | RecorderView::showAverage = !RecorderView::showAverage; 128 | } 129 | else if (arg == "-c" && a+1 < argc) 130 | { 131 | if (!recorder_chans_configure(chans, argv[++a])) 132 | { 133 | fprintf(stderr, "Insufficient command space to send '%s'\n", 134 | argv[a]); 135 | return 3; 136 | } 137 | configurations++; 138 | } 139 | else if (arg == "-s" && a+1 < argc) 140 | { 141 | QGroupBox *slider = RecorderSlider::make(path, chans, argv[++a]); 142 | layout->addWidget(slider); 143 | } 144 | else if (arg == "-d" && a+1 < argc) 145 | { 146 | RecorderView::maxDuration = strtod(argv[++a], NULL); 147 | } 148 | else if (arg == "-w" && a+1 < argc) 149 | { 150 | RecorderView::maxWidth = strtoul(argv[++a], NULL, 10); 151 | } 152 | else if (arg == "-r" && a+1 < argc) 153 | { 154 | double ratio = strtod(argv[++a], NULL); 155 | if (ratio <= 0.0 || ratio >= 100.0) 156 | fprintf(stderr, "Ratio %f must be in 0-100\n", ratio); 157 | else 158 | RecorderView::averagingRatio = ratio * 0.01; 159 | } 160 | else if (arg == "-b" && a+1 < argc) 161 | { 162 | RecorderView::saveBaseName = argv[++a]; 163 | } 164 | else if (arg == "-g" && a+1 < argc) 165 | { 166 | int rc = sscanf(argv[++a], "%dx%d@%dx%d", 167 | &width, &height, &posx, &posy); 168 | if (rc != 2 && rc != 4) 169 | fprintf(stderr, "-g %s was invalid, " 170 | "width=%d, height=%d, x=%d, y=%d\n", 171 | argv[a], width, height, posx, posy); 172 | } 173 | else if (arg[0] == '-') 174 | { 175 | fprintf(stderr, "Invalid option %s\n", argv[a]); 176 | usage(argv[0]); 177 | } 178 | else 179 | { 180 | RecorderView *view = new RecorderView(path, chans, argv[a]); 181 | layout->addWidget(view); 182 | views++; 183 | } 184 | } 185 | 186 | if (views == 0 && configurations == 0) 187 | { 188 | RecorderView *recorderView = new RecorderView(path, chans, ".*"); 189 | layout->addWidget(recorderView); 190 | } 191 | 192 | int result = 0; 193 | if (views > 0 || configurations == 0) 194 | { 195 | layout->setContentsMargins(4, 4, 4, 4); 196 | widget->setLayout(layout); 197 | window.setCentralWidget(widget); 198 | if (width > 0 && height > 0) 199 | window.resize(width, height); 200 | if (posx > 0 && posy > 0) 201 | window.move(posx, posy); 202 | window.show(); 203 | result = a.exec(); 204 | } 205 | 206 | recorder_chans_close(chans); 207 | return result; 208 | } 209 | -------------------------------------------------------------------------------- /scope/recorder_scope.pro: -------------------------------------------------------------------------------- 1 | # ****************************************************************************** 2 | # recorder_scope.pro Recorder project 3 | # ****************************************************************************** 4 | # 5 | # File description: 6 | # 7 | # Project for the flight recorder "scope" 8 | # 9 | # 10 | # 11 | # 12 | # 13 | # 14 | # 15 | # 16 | # ****************************************************************************** 17 | # This software is licensed under the GNU General Public License v3+ 18 | # (C) 2017-2019, Christophe de Dinechin 19 | # (C) 2018, Frediano Ziglio 20 | # ****************************************************************************** 21 | # This file is part of Recorder 22 | # 23 | # Recorder is free software: you can redistribute it and/or modify 24 | # it under the terms of the GNU General Public License 25 | # as published by the Free Software Foundation, either version 3 26 | # of the License, or (at your option) any later version. 27 | # 28 | # Recorder is distributed in the hope that it will be useful, 29 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 30 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 31 | # GNU General Public License for more details. 32 | # 33 | # You should have received a copy of the GNU General Public License 34 | # along with Recorder, in a file named COPYING. 35 | # If not, see . 36 | # ****************************************************************************** 37 | 38 | QT += charts 39 | 40 | INCLUDEPATH += .. 41 | 42 | HEADERS += recorder_view.h 43 | 44 | SOURCES += recorder_view.cpp \ 45 | recorder_scope.cpp \ 46 | recorder_slider.cpp 47 | 48 | LIBS += -L.. -lrecorder 49 | 50 | target.files = recorder_scope 51 | target.path = $$INSTALL_BINDIR 52 | INSTALLS += target 53 | -------------------------------------------------------------------------------- /scope/recorder_slider.cpp: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // recorder_slider.cpp Recorder project 3 | // ***************************************************************************** 4 | // 5 | // File description: 6 | // 7 | // Slider that can be used to adjust a recorder tweakable in target app 8 | // 9 | // 10 | // 11 | // 12 | // 13 | // 14 | // 15 | // 16 | // ***************************************************************************** 17 | // This software is licensed under the GNU General Public License v3+ 18 | // (C) 2017-2020, Christophe de Dinechin 19 | // ***************************************************************************** 20 | // This file is part of Recorder 21 | // 22 | // Recorder is free software: you can redistribute it and/or modify 23 | // it under the terms of the GNU General Public License as published by 24 | // the Free Software Foundation, either version 3 of the License, or 25 | // (at your option) any later version. 26 | // 27 | // Recorder is distributed in the hope that it will be useful, 28 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 29 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 30 | // GNU General Public License for more details. 31 | // 32 | // You should have received a copy of the GNU General Public License 33 | // along with Recorder, in a file named COPYING. 34 | // If not, see . 35 | // ***************************************************************************** 36 | 37 | #include "recorder_slider.h" 38 | #include 39 | #include 40 | 41 | RecorderSlider::RecorderSlider(const char *filename, 42 | recorder_chans_p &chans, 43 | const char *specification, 44 | QGroupBox *group, 45 | QLabel *minLabel, 46 | QLabel *maxLabel, 47 | QLabel *valueLabel, 48 | QWidget *parent) 49 | // ---------------------------------------------------------------------------- 50 | // Define a slider according to the input spec 51 | // ---------------------------------------------------------------------------- 52 | : QSlider(Qt::Horizontal, parent), 53 | filename(filename), 54 | chans(chans), 55 | specification(specification), 56 | sourceChanged(false), 57 | name(specification), 58 | min(0), 59 | max(100), 60 | group(group), 61 | minLabel(minLabel), 62 | maxLabel(maxLabel), 63 | valueLabel(valueLabel) 64 | { 65 | QObject::connect(this, &QSlider::valueChanged, 66 | this, &RecorderSlider::valueChanged); 67 | setup(specification); 68 | } 69 | 70 | 71 | RecorderSlider::~RecorderSlider() 72 | // ---------------------------------------------------------------------------- 73 | // Destructor for slider has nothing to do 74 | // ---------------------------------------------------------------------------- 75 | { 76 | } 77 | 78 | 79 | QGroupBox *RecorderSlider::make(const char *filename, 80 | recorder_chans_p &chans, 81 | const char *spec) 82 | // ---------------------------------------------------------------------------- 83 | // Build a control group holding the slider and associated labels 84 | // ---------------------------------------------------------------------------- 85 | { 86 | QGroupBox *group = new QGroupBox; 87 | QGridLayout *layout = new QGridLayout; 88 | QLabel *minLabel = new QLabel; 89 | QLabel *maxLabel = new QLabel; 90 | QLabel *valueLabel = new QLabel; 91 | RecorderSlider *slider = new RecorderSlider(filename, chans, spec, 92 | group, 93 | minLabel, maxLabel, 94 | valueLabel); 95 | layout->addWidget(minLabel, 0, 0); 96 | layout->addWidget(slider, 0, 1); 97 | layout->addWidget(maxLabel, 0, 2); 98 | layout->addWidget(valueLabel, 1, 1); 99 | layout->setContentsMargins(0, 0, 0, 0); 100 | valueLabel->setAlignment(Qt::AlignCenter); 101 | group->setLayout(layout); 102 | 103 | return group; 104 | } 105 | 106 | 107 | void RecorderSlider::setup(const char *specification) 108 | // ---------------------------------------------------------------------------- 109 | // Parse the specification into name, min and max 110 | // ---------------------------------------------------------------------------- 111 | // This accepts specifications that look like: 112 | // name 113 | // name=3 114 | // name=3:-10:10 115 | { 116 | int value = 0; 117 | QString spec = specification; 118 | int eq = spec.indexOf('='); 119 | if (eq < 0) 120 | { 121 | name = spec; 122 | } 123 | else 124 | { 125 | name = spec.mid(0, eq); 126 | spec = spec.mid(eq+1); 127 | 128 | QStringList args = spec.split(':'); 129 | switch(args.length()) 130 | { 131 | case 3: 132 | min = args[1].toInt(); 133 | max = args[2].toInt(); 134 | /* Falls through */ 135 | case 1: 136 | value = args[0].toInt(); 137 | break; 138 | default: 139 | fprintf(stderr, 140 | "Invalid slider specification %s\n" 141 | " Expecting one of:\n" 142 | " name\n" 143 | " name=value\n" 144 | " name=value:min:max\n" 145 | " Example: -s slider=0:-10:10\n", 146 | specification); 147 | } 148 | } 149 | 150 | setRange(min, max); 151 | setValue(value); 152 | 153 | group->setTitle(name); 154 | minLabel->setText(QString("%1").arg(min)); 155 | maxLabel->setText(QString("%1").arg(max)); 156 | } 157 | 158 | 159 | void RecorderSlider::updateSetup() 160 | // ---------------------------------------------------------------------------- 161 | // Nothing yet 162 | // ---------------------------------------------------------------------------- 163 | { 164 | } 165 | 166 | 167 | void RecorderSlider::valueChanged(int value) 168 | // ---------------------------------------------------------------------------- 169 | // When the value changes, send the configuration to the target 170 | // ---------------------------------------------------------------------------- 171 | { 172 | valueLabel->setText(QString("%1").arg(value)); 173 | 174 | QString config; 175 | QTextStream ts(&config); 176 | ts << name << "=" << value; 177 | const char *constData = config.toUtf8().data(); 178 | if (!recorder_chans_configure(chans, constData)) 179 | fprintf(stderr, "Configuration %s failed\n", constData); 180 | } 181 | -------------------------------------------------------------------------------- /scope/recorder_slider.h: -------------------------------------------------------------------------------- 1 | #ifndef RECORDER_SLIDER_H 2 | #define RECORDER_SLIDER_H 3 | // ***************************************************************************** 4 | // recorder_slider.h Recorder project 5 | // ***************************************************************************** 6 | // 7 | // File description: 8 | // 9 | // Slider that can be used to adjust a recorder tweakable in target app 10 | // 11 | // 12 | // 13 | // 14 | // 15 | // 16 | // 17 | // 18 | // ***************************************************************************** 19 | // This software is licensed under the GNU General Public License v3+ 20 | // (C) 2017-2020, Christophe de Dinechin 21 | // ***************************************************************************** 22 | // This file is part of Recorder 23 | // 24 | // Recorder is free software: you can redistribute it and/or modify 25 | // it under the terms of the GNU General Public License as published by 26 | // the Free Software Foundation, either version 3 of the License, or 27 | // (at your option) any later version. 28 | // 29 | // Recorder is distributed in the hope that it will be useful, 30 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 31 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 32 | // GNU General Public License for more details. 33 | // 34 | // You should have received a copy of the GNU General Public License 35 | // along with Recorder, in a file named COPYING. 36 | // If not, see . 37 | // ***************************************************************************** 38 | 39 | #include "recorder.h" 40 | 41 | #include 42 | #include 43 | #include 44 | 45 | class RecorderSlider : public QSlider 46 | // ---------------------------------------------------------------------------- 47 | // Slider for recorder data 48 | // ---------------------------------------------------------------------------- 49 | { 50 | public: 51 | explicit RecorderSlider(const char *filename, 52 | recorder_chans_p &chans, 53 | const char *specification, 54 | QGroupBox *group, 55 | QLabel *minLabel, 56 | QLabel *maxLabel, 57 | QLabel *valueLabel, 58 | QWidget *parent = 0); 59 | ~RecorderSlider(); 60 | 61 | public: 62 | static QGroupBox *make(const char *filename, 63 | recorder_chans_p &chans, 64 | const char *specification); 65 | void setup(const char *specification); 66 | void updateSetup(); 67 | void valueChanged(int value); 68 | 69 | private: 70 | const char * filename; 71 | recorder_chans_p &chans; 72 | const char * specification; 73 | bool sourceChanged; 74 | QString name; 75 | int min; 76 | int max; 77 | QGroupBox * group; 78 | QLabel * minLabel; 79 | QLabel * maxLabel; 80 | QLabel * valueLabel; 81 | }; 82 | 83 | #endif // RECORDER_SLIDER_H 84 | -------------------------------------------------------------------------------- /scope/recorder_view.cpp: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // recorder_view.cpp Recorder project 3 | // ***************************************************************************** 4 | // 5 | // File description: 6 | // 7 | // View that fetches data from the flight recorder and displays it 8 | // 9 | // 10 | // 11 | // 12 | // 13 | // 14 | // 15 | // 16 | // ***************************************************************************** 17 | // This software is licensed under the GNU General Public License v3+ 18 | // (C) 2017-2020, Christophe de Dinechin 19 | // (C) 2018, Frediano Ziglio 20 | // ***************************************************************************** 21 | // This file is part of Recorder 22 | // 23 | // Recorder is free software: you can redistribute it and/or modify 24 | // it under the terms of the GNU General Public License as published by 25 | // the Free Software Foundation, either version 3 of the License, or 26 | // (at your option) any later version. 27 | // 28 | // Recorder is distributed in the hope that it will be useful, 29 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 30 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 31 | // GNU General Public License for more details. 32 | // 33 | // You should have received a copy of the GNU General Public License 34 | // along with Recorder, in a file named COPYING. 35 | // If not, see . 36 | // ***************************************************************************** 37 | 38 | #include "recorder_view.h" 39 | 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | static const double timeUnit = 1e-6; 48 | static const double timeScale = 1.0 / timeUnit; 49 | 50 | 51 | RecorderView::RecorderView(const char *filename, 52 | recorder_chans_p &chans, 53 | const char *pattern, 54 | QWidget *parent) 55 | // ---------------------------------------------------------------------------- 56 | // Constructor opens the shared memory for recorder data 57 | // ---------------------------------------------------------------------------- 58 | : QChartView(parent), 59 | filename(filename), pattern(pattern), chans(chans), 60 | sourceChanged(false), 61 | viewHasNormal(showNormal), 62 | viewHasTiming(showTiming), 63 | viewHasMinMax(showMinMax), 64 | viewHasAverage(showAverage) 65 | { 66 | xAxis = new QValueAxis; 67 | yAxis = new QValueAxis; // Or QLogValueAxis? 68 | tAxis = new QValueAxis; 69 | xAxis->setRange(0, 20.0); 70 | yAxis->setRange(-10.0, 10.0); 71 | tAxis->setRange(0, 100.0); 72 | if (viewHasTiming) 73 | chart->addAxis(tAxis, Qt::AlignRight); 74 | 75 | chart = new QChart(); 76 | chart->layout()->setContentsMargins(0, 0, 0, 0); 77 | // chart->legend()->hide(); 78 | chart->addAxis(xAxis, Qt::AlignBottom); 79 | chart->addAxis(yAxis, Qt::AlignLeft); 80 | setChart(chart); 81 | 82 | QObject::connect(chart->scene(), &QGraphicsScene::changed, 83 | this, &RecorderView::sceneChanged); 84 | 85 | // Timer setup 86 | dataUpdater.setInterval(0); 87 | dataUpdater.setSingleShot(true); 88 | QObject::connect(&dataUpdater, &QTimer::timeout, 89 | this, &RecorderView::updateSeries); 90 | chart->setTheme(QChart::ChartThemeBlueCerulean); 91 | 92 | // Data construction 93 | if (chans) 94 | setup(); 95 | } 96 | 97 | 98 | RecorderView::~RecorderView() 99 | // ---------------------------------------------------------------------------- 100 | // Destructor suspends data acquisition and deletes view elements 101 | // ---------------------------------------------------------------------------- 102 | { 103 | dataUpdater.stop(); 104 | delete xAxis; 105 | delete yAxis; 106 | delete tAxis; 107 | delete chart; 108 | } 109 | 110 | 111 | void RecorderView::setup() 112 | // ---------------------------------------------------------------------------- 113 | // Setup the channels that match the pattern 114 | // ---------------------------------------------------------------------------- 115 | { 116 | unsigned i = 0; 117 | recorder_chan_p chan = NULL; 118 | bool hasGL = getenv ("RECORDER_NOGL") == NULL; 119 | const char *colors[] = { 120 | "yellow", "red", "lightgreen", "orange", 121 | "cyan", "lightgray", "pink", "lightyellow", 122 | }; 123 | const unsigned numColors = sizeof(colors) / sizeof(colors[0]); 124 | 125 | while (true) 126 | { 127 | chan = recorder_chan_find(chans, pattern, chan); 128 | if (!chan) 129 | break; 130 | if (chanList.indexOf(chan) != -1) 131 | break; 132 | 133 | const char *name = recorder_chan_name(chan); 134 | const char *info = recorder_chan_description(chan); 135 | const char *unit = recorder_chan_unit(chan); 136 | recorder_data min = recorder_chan_min(chan); 137 | recorder_data max = recorder_chan_max(chan); 138 | 139 | bool hasNormal = viewHasNormal; 140 | bool hasMin = viewHasMinMax; 141 | bool hasMax = viewHasMinMax; 142 | bool hasAverage = viewHasAverage; 143 | bool hasTiming = viewHasTiming; 144 | series_et type = NONE; 145 | 146 | while (true) 147 | { 148 | type = hasNormal ? (hasNormal = false, NORMAL) 149 | : hasMin ? (hasMin = false, MINIMUM) 150 | : hasMax ? (hasMax = false, MAXIMUM) 151 | : hasAverage ? (hasAverage = false, AVERAGE) 152 | : hasTiming ? (hasTiming = false, TIMING) 153 | : NONE; 154 | if (type == NONE) 155 | break; 156 | 157 | int colorIndex = i++ % numColors; 158 | QLineSeries *series = new QLineSeries; 159 | 160 | chart->addSeries(series); 161 | seriesList.append(series); 162 | data.append(Points()); 163 | chanList.append(chan); 164 | readerIndex.append(0); 165 | seriesType.append(type); 166 | 167 | QPen pen(QBrush(QColor(colors[colorIndex])), 2.0); 168 | pen.setCosmetic(true); 169 | series->setPen(pen); 170 | series->setUseOpenGL(hasGL); 171 | series->attachAxis(xAxis); 172 | series->attachAxis(type == TIMING ? tAxis : yAxis); 173 | 174 | QString fancyName = name; 175 | switch(type) 176 | { 177 | case MINIMUM: fancyName += " (min)"; break; 178 | case MAXIMUM: fancyName += " (max)"; break; 179 | case AVERAGE: fancyName += " (avg)"; break; 180 | case TIMING: fancyName += " (dur)"; break; 181 | default: break; 182 | } 183 | 184 | printf("Channel #%u %s (%s): %ld %s-%ld %s\n", i, 185 | fancyName.toUtf8().data(), 186 | info, min.signed_value, unit, 187 | max.signed_value, unit); 188 | 189 | series->setName(fancyName); 190 | } 191 | } 192 | } 193 | 194 | 195 | void RecorderView::updateSetup() 196 | // ---------------------------------------------------------------------------- 197 | // Setup the channels that match the pattern 198 | // ---------------------------------------------------------------------------- 199 | { 200 | // Update chans only once (it's a pointer shared across all views) 201 | static QMutex chansUpdateMutex; 202 | chansUpdateMutex.lock(); 203 | if (!recorder_chans_valid(chans)) 204 | { 205 | fprintf(stderr, "Recorder channels became invalid, re-initializing\n"); 206 | recorder_chans_close(chans); 207 | chans = recorder_chans_open(filename); 208 | } 209 | chansUpdateMutex.unlock(); 210 | 211 | // Update the view with the new channels 212 | chart->removeAllSeries(); 213 | data.clear(); 214 | seriesList.clear(); 215 | chanList.clear(); 216 | readerIndex.clear(); 217 | seriesType.clear(); 218 | setup(); 219 | } 220 | 221 | 222 | void RecorderView::updateSeries() 223 | // ---------------------------------------------------------------------------- 224 | // Update all data series by reading the latest data 225 | // ---------------------------------------------------------------------------- 226 | { 227 | if (!recorder_chans_valid(chans)) 228 | { 229 | if (!sourceChanged) 230 | { 231 | // A new program started with the same shared memory file 232 | // Wait a bit to make sure all channels are setup by new instance 233 | // Then update this view. 234 | // All views will presumably fail at the same time and get updated 235 | sourceChanged = true; 236 | dataUpdater.setInterval(100); 237 | dataUpdater.start(); 238 | return; 239 | } 240 | } 241 | if (sourceChanged) 242 | { 243 | updateSetup(); 244 | sourceChanged = false; 245 | } 246 | 247 | size_t numSeries = seriesList.size(); 248 | size_t width = 249 | maxWidth > 0 ? maxWidth 250 | : maxDuration > 0 ? this->width() * 10 251 | : this->width(); 252 | double minX = -1.0, maxX = 1.0; 253 | double minY = -1.0, maxY = 1.0; 254 | double maxT = timeUnit; 255 | bool first = true; 256 | bool updated = false; 257 | 258 | for (size_t s = 0; s < numSeries; s++) 259 | { 260 | recorder_chan_p chan = chanList[s]; 261 | ringidx_t &ridx = readerIndex[s]; 262 | size_t readable = recorder_chan_readable(chan, &ridx); 263 | QLineSeries *series = seriesList[s]; 264 | Points &dataPoints = data[s]; 265 | series_et type = seriesType[s]; 266 | 267 | if (readable) 268 | { 269 | size_t dataLen = dataPoints.size(); 270 | if (dataLen > width) 271 | { 272 | dataPoints.remove(0, dataLen - width); 273 | dataLen = width; 274 | } 275 | if (readable > width) 276 | readable = width; 277 | 278 | QVector recorderDataRead(2 * readable); 279 | Points pointsRead(readable); 280 | 281 | recorder_data *rbuf = recorderDataRead.data(); 282 | QPointF *pbuf = pointsRead.data(); 283 | size_t count = recorder_chan_read(chan, rbuf, readable, &ridx); 284 | 285 | Q_ASSERT(count <= readable); 286 | double scale = 1.0 / RECORDER_HZ; 287 | if (count) 288 | { 289 | switch(recorder_chan_type(chan)) 290 | { 291 | case RECORDER_NONE: 292 | Q_ASSERT(!"Recorder channel has invalid type NONE"); 293 | break; 294 | case RECORDER_INVALID: 295 | // Recorder format is invalid, put some fake data 296 | for (size_t p = 0; p < count; p++) 297 | pbuf[p] = QPointF(p, p % 32); 298 | break; 299 | case RECORDER_SIGNED: 300 | for (size_t p = 0; p < count; p++) 301 | pbuf[p] = QPointF(rbuf[2*p].unsigned_value * scale, 302 | rbuf[2*p+1].signed_value); 303 | break; 304 | case RECORDER_UNSIGNED: 305 | for (size_t p = 0; p < count; p++) 306 | pbuf[p] = QPointF(rbuf[2*p].unsigned_value * scale, 307 | rbuf[2*p+1].unsigned_value); 308 | break; 309 | case RECORDER_REAL: 310 | for (size_t p = 0; p < count; p++) 311 | pbuf[p] = QPointF(rbuf[2 * p].unsigned_value * scale, 312 | rbuf[2 * p + 1].real_value); 313 | break; 314 | } 315 | 316 | size_t newLen = count + dataLen; 317 | size_t toRemove = newLen > width ? newLen - width : 0; 318 | if (toRemove) 319 | dataPoints.remove(0, toRemove); 320 | if (count != readable) 321 | pointsRead.resize(count); 322 | dataPoints.append(pointsRead); 323 | 324 | switch(type) 325 | { 326 | case NORMAL: 327 | series->replace(dataPoints); 328 | break; 329 | case MINIMUM: 330 | series->replace(minimum(dataPoints)); 331 | break; 332 | case MAXIMUM: 333 | series->replace(maximum(dataPoints)); 334 | break; 335 | case AVERAGE: 336 | series->replace(average(dataPoints)); 337 | break; 338 | case TIMING: 339 | series->replace(timing(dataPoints)); 340 | break; 341 | default: 342 | Q_ASSERT(!"Unkown series type"); 343 | } 344 | updated = true; 345 | } 346 | } 347 | 348 | QPointF *pbuf = dataPoints.data(); 349 | size_t count = dataPoints.size(); 350 | qreal lastT = pbuf[0].x(); 351 | for (size_t p = 0; p < count; p++) 352 | { 353 | double x = pbuf[p].x(); 354 | double y = pbuf[p].y(); 355 | if (first) 356 | { 357 | minX = x; 358 | maxX = x; 359 | minY = y; 360 | maxY = y; 361 | maxT = 0; 362 | first = false; 363 | } 364 | else 365 | { 366 | if (maxX < x) 367 | maxX = x; 368 | if (minX > x) 369 | minX = x; 370 | if (type == TIMING) 371 | { 372 | double t = (x - lastT) * timeScale; 373 | if (maxT < t) 374 | maxT = t; 375 | lastT = x; 376 | } 377 | else 378 | { 379 | if (maxY < y) 380 | maxY = y; 381 | if (minY > y) 382 | minY = y; 383 | } 384 | } 385 | } 386 | 387 | if (maxDuration > 0.0) 388 | { 389 | minX = maxX - maxDuration; 390 | size_t lowP = 0; 391 | for (size_t p = 0; p < count; p++) 392 | { 393 | double x = pbuf[p].x(); 394 | if (x < minX) 395 | lowP = p; 396 | } 397 | if (lowP > 0) 398 | dataPoints.remove(0, lowP); 399 | } 400 | } 401 | 402 | if (updated) 403 | { 404 | double range = fabs(maxY - minY); 405 | double scale = 1; 406 | while (scale < range) 407 | { 408 | scale *= 2; 409 | if (scale >= range) 410 | break; 411 | scale *= 2.5; 412 | if (scale >= range) 413 | break; 414 | scale *= 2; 415 | } 416 | minY = floor(minY / scale) * scale; 417 | maxY = ceil(maxY / scale) * scale; 418 | 419 | xAxis->setRange(minX, maxX); 420 | yAxis->setRange(minY, maxY); 421 | 422 | if (tAxis) 423 | { 424 | double range = maxT; 425 | double scale = 1; 426 | while (scale < range) 427 | { 428 | scale *= 2; 429 | if (scale >= range) 430 | break; 431 | scale *= 2.5; 432 | if (scale >= range) 433 | break; 434 | scale *= 2; 435 | } 436 | maxT = ceil(maxT / scale) * scale; 437 | tAxis->setRange(0, maxT); 438 | } 439 | } 440 | else 441 | { 442 | dataUpdater.setInterval(30); 443 | dataUpdater.start(); 444 | } 445 | } 446 | 447 | 448 | void RecorderView::sceneChanged() 449 | // ---------------------------------------------------------------------------- 450 | // Trigger a data update when a scene change occured 451 | // ---------------------------------------------------------------------------- 452 | { 453 | dataUpdater.setInterval(0); 454 | dataUpdater.start(); 455 | } 456 | 457 | 458 | void RecorderView::keyPressEvent(QKeyEvent *event) 459 | // ---------------------------------------------------------------------------- 460 | // Respond to 'space' key by capturing image and log values 461 | // ---------------------------------------------------------------------------- 462 | { 463 | int key = event->key(); 464 | bool saveCSV = false; 465 | bool saveImage = false; 466 | 467 | switch (key) 468 | { 469 | case ' ': 470 | saveCSV = saveImage = true; 471 | break; 472 | case 'i': case 'I': 473 | saveImage = true; 474 | break; 475 | case 'c': case 'C': 476 | saveCSV = true; 477 | break; 478 | case 'n': case 'N': 479 | viewHasNormal = !viewHasNormal; 480 | sourceChanged = true; 481 | break; 482 | case 't': case 'T': 483 | viewHasTiming = !viewHasTiming; 484 | sourceChanged = true; 485 | if (viewHasTiming) 486 | chart->addAxis(tAxis, Qt::AlignRight); 487 | else 488 | chart->removeAxis(tAxis); 489 | break; 490 | case 'm': case 'M': 491 | viewHasMinMax = !viewHasMinMax; 492 | sourceChanged = true; 493 | break; 494 | case 'a': case 'A': 495 | viewHasAverage = !viewHasAverage; 496 | sourceChanged = true; 497 | break; 498 | } 499 | 500 | if (!saveImage && !saveCSV) 501 | return; 502 | 503 | // Find a name that can be used both for CSV and PNG file 504 | static unsigned index = 0; 505 | QString basename = saveBaseName + "%1"; 506 | QString name = basename.arg(++index); 507 | while ((saveCSV && QFileInfo(name + ".csv").exists()) || 508 | (saveImage && QFileInfo(name + ".png").exists())) 509 | name = QString(basename).arg(++index); 510 | 511 | if (saveImage) 512 | { 513 | bool hasGL = getenv ("RECORDER_NOGL") == NULL; 514 | 515 | QPixmap pixmap(size()); 516 | QPainter painter(&pixmap); 517 | for (auto s : seriesList) 518 | s->setUseOpenGL(false); 519 | render(&painter); 520 | for (auto s : seriesList) 521 | s->setUseOpenGL(hasGL); 522 | pixmap.save(name + ".png"); 523 | } 524 | 525 | if (saveCSV) 526 | { 527 | // Use data directly from series, as it may have gone through processing 528 | // for example through timing() or average() functions 529 | QVector data; 530 | for (auto s : seriesList) 531 | data.append(s->points()); 532 | 533 | QByteArray cname = (name + ".csv").toUtf8(); 534 | FILE *f = fopen(cname.data(), "w"); 535 | if (f) 536 | { 537 | size_t columns = data.size(); 538 | size_t rows = data[0].size(); 539 | for (size_t r = 0; r < rows; r++) 540 | { 541 | double t = data[0][r].x(); 542 | fprintf(f, "%f", t); 543 | for (size_t c = 0; c < columns; c++) 544 | { 545 | Q_ASSERT(data[c][r].x() == t); 546 | double value = data[c][r].y(); 547 | fprintf(f, ",%f", value); 548 | } 549 | fprintf(f, "\n"); 550 | } 551 | fclose(f); 552 | } 553 | else 554 | { 555 | fprintf(stderr, "Error opening %s: %s\n", cname.data(), strerror(errno)); 556 | } 557 | } 558 | } 559 | 560 | 561 | RecorderView::Points RecorderView::minimum(const RecorderView::Points &data) 562 | // ---------------------------------------------------------------------------- 563 | // Compute the running minimum for the input values 564 | // ---------------------------------------------------------------------------- 565 | { 566 | Points result(data); 567 | QPointF *pbuf = result.data(); 568 | size_t count = result.size(); 569 | qreal min = std::numeric_limits::max(); 570 | qreal r = averagingRatio; 571 | 572 | for (size_t p = 0; p < count; p++) 573 | { 574 | qreal &y = pbuf[p].ry(); 575 | if (min > y) 576 | min = y; 577 | else 578 | min = r * min + (1-r) * y; 579 | y = min; 580 | } 581 | 582 | return result; 583 | } 584 | 585 | 586 | RecorderView::Points RecorderView::maximum(const RecorderView::Points &data) 587 | // ---------------------------------------------------------------------------- 588 | // Compute the running maximum for the input values 589 | // ---------------------------------------------------------------------------- 590 | { 591 | Points result(data); 592 | QPointF *pbuf = result.data(); 593 | size_t count = result.size(); 594 | qreal max = std::numeric_limits::min(); 595 | qreal r = averagingRatio; 596 | 597 | for (size_t p = 0; p < count; p++) 598 | { 599 | qreal &y = pbuf[p].ry(); 600 | if (max < y) 601 | max = y; 602 | else 603 | max = r * max + (1-r) * y; 604 | y = max; 605 | } 606 | 607 | return result; 608 | } 609 | 610 | 611 | RecorderView::Points RecorderView::average(const RecorderView::Points &data) 612 | // ---------------------------------------------------------------------------- 613 | // Compute the running average for the input values 614 | // ---------------------------------------------------------------------------- 615 | { 616 | Points result(data); 617 | QPointF *pbuf = result.data(); 618 | size_t count = result.size(); 619 | qreal avg = 0.0; 620 | for (size_t p = 0; p < count; p++) 621 | avg += pbuf[p].y(); 622 | avg /= count ? count : 1; 623 | qreal r = averagingRatio; 624 | 625 | for (size_t p = 0; p < count; p++) 626 | { 627 | qreal &y = pbuf[p].ry(); 628 | avg = r * avg + (1-r) * y; 629 | y = avg; 630 | } 631 | 632 | return result; 633 | } 634 | 635 | 636 | RecorderView::Points RecorderView::timing(const RecorderView::Points &data) 637 | // ---------------------------------------------------------------------------- 638 | // Compute timing information about the input values 639 | // ---------------------------------------------------------------------------- 640 | { 641 | Points result(data); 642 | QPointF *pbuf = result.data(); 643 | size_t count = result.size(); 644 | qreal last = data[0].x(); 645 | 646 | for (size_t p = 0; p < count; p++) 647 | { 648 | qreal t = pbuf[p].x(); 649 | qreal &y = pbuf[p].ry(); 650 | y = (t - last) * timeScale; 651 | last = t; 652 | } 653 | 654 | return result; 655 | } 656 | 657 | 658 | double RecorderView::maxDuration = 0.0; 659 | unsigned RecorderView::maxWidth = 0; 660 | double RecorderView::averagingRatio = 0.99; 661 | bool RecorderView::showNormal = true; 662 | bool RecorderView::showTiming = false; 663 | bool RecorderView::showMinMax = false; 664 | bool RecorderView::showAverage = false; 665 | QString RecorderView::saveBaseName = "recorder_scope_data-"; 666 | -------------------------------------------------------------------------------- /scope/recorder_view.h: -------------------------------------------------------------------------------- 1 | #ifndef RECORDER_VIEW_H 2 | #define RECORDER_VIEW_H 3 | // ***************************************************************************** 4 | // recorder_view.h Recorder project 5 | // ***************************************************************************** 6 | // 7 | // File description: 8 | // 9 | // View source that feeds from the flight recorder 10 | // 11 | // 12 | // 13 | // 14 | // 15 | // 16 | // 17 | // 18 | // ***************************************************************************** 19 | // This software is licensed under the GNU General Public License v3+ 20 | // (C) 2017-2020, Christophe de Dinechin 21 | // ***************************************************************************** 22 | // This file is part of Recorder 23 | // 24 | // Recorder is free software: you can redistribute it and/or modify 25 | // it under the terms of the GNU General Public License as published by 26 | // the Free Software Foundation, either version 3 of the License, or 27 | // (at your option) any later version. 28 | // 29 | // Recorder is distributed in the hope that it will be useful, 30 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 31 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 32 | // GNU General Public License for more details. 33 | // 34 | // You should have received a copy of the GNU General Public License 35 | // along with Recorder, in a file named COPYING. 36 | // If not, see . 37 | // ***************************************************************************** 38 | 39 | #include "recorder.h" 40 | 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | 52 | class RecorderView : public QChartView 53 | // ---------------------------------------------------------------------------- 54 | // View for recorder data 55 | // ---------------------------------------------------------------------------- 56 | { 57 | Q_OBJECT 58 | public: 59 | explicit RecorderView(const char *filename, 60 | recorder_chans_p &chans, 61 | const char *pattern, 62 | QWidget *parent = 0); 63 | ~RecorderView(); 64 | void setup(); 65 | void updateSetup(); 66 | 67 | public slots: 68 | void updateSeries(); 69 | void sceneChanged(); 70 | 71 | public: 72 | void keyPressEvent(QKeyEvent *event) override; 73 | 74 | public: 75 | static double maxDuration; 76 | static unsigned maxWidth; 77 | static double averagingRatio; 78 | static bool showNormal; 79 | static bool showTiming; 80 | static bool showMinMax; 81 | static bool showAverage; 82 | static QString saveBaseName; 83 | 84 | private: 85 | typedef enum { NONE, NORMAL, MINIMUM, MAXIMUM, AVERAGE, TIMING } series_et; 86 | const char * filename; 87 | const char * pattern; 88 | recorder_chans_p &chans; 89 | bool sourceChanged; 90 | 91 | typedef QVector Points; 92 | QVector data; 93 | QVector seriesList; 94 | QVector chanList; 95 | QVector readerIndex; 96 | QVector seriesType; 97 | 98 | QChart * chart; 99 | QValueAxis * xAxis; 100 | QValueAxis * yAxis; 101 | QValueAxis * tAxis; 102 | QTimer dataUpdater; 103 | 104 | bool viewHasNormal; 105 | bool viewHasTiming; 106 | bool viewHasMinMax; 107 | bool viewHasAverage; 108 | 109 | static Points minimum(const Points &); 110 | static Points maximum(const Points &); 111 | static Points average(const Points &); 112 | static Points timing(const Points &); 113 | }; 114 | 115 | #endif // RECORDER_VIEW_H 116 | --------------------------------------------------------------------------------