├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── LICENSE.BSD-2-Clause ├── LICENSE.GPL-2.0 ├── README.md ├── UPSTREAM-COMMIT ├── scripts ├── archive-srcs-full.sh └── gh-label-release-assets.sh └── src ├── .gitignore ├── Makefile └── veristat.c /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | with: 17 | submodules: true 18 | - name: Install dependencies 19 | run: sudo apt-get install -y elfutils libelf-dev 20 | - name: make 21 | run: make -C src -j$(nproc) 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: workflow_dispatch 4 | 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.event.after }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | build: 11 | name: Build static veristat binary 12 | runs-on: ubuntu-22.04 13 | env: 14 | TARGETARCH: ${{ matrix.arch }} 15 | FILE_STRING_ARCH_amd64: x86-64 16 | FILE_STRING_ARCH_arm64: aarch64 17 | strategy: 18 | matrix: 19 | arch: [arm64, amd64] 20 | 21 | steps: 22 | # amd64 needs the dependencies to build veristat 23 | - name: Install dependencies (amd64) 24 | if: matrix.arch == 'amd64' 25 | run: | 26 | sudo apt-get update 27 | sudo apt-get install -y libelf-dev 28 | 29 | - name: Checkout veristat code 30 | uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 31 | with: 32 | submodules: recursive 33 | path: 'veristat' 34 | 35 | - name: Build static veristat natively for amd64 36 | if: matrix.arch == 'amd64' 37 | working-directory: 'veristat' 38 | run: | 39 | EXTRA_CFLAGS=--static \ 40 | make -j -C src V=1 41 | strip src/veristat 42 | 43 | - name: Set up QEMU 44 | uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0 45 | if: matrix.arch == 'arm64' 46 | with: 47 | platforms: arm64 48 | 49 | # The emulated build leverages Docker and Ubuntu 22.04 container image 50 | # distribution to have all the needed arm64 packages. 51 | - name: Build static veristat for arm64 with emulation 52 | if: matrix.arch == 'arm64' 53 | run: | 54 | docker run --platform linux/arm64 --rm -v $(pwd):/build ubuntu:22.04 \ 55 | bash -c "apt-get update && \ 56 | apt-get install -y make pkg-config gcc libelf-dev && \ 57 | cd /build/veristat && \ 58 | EXTRA_CFLAGS=--static \ 59 | make -j -C src V=1 && \ 60 | strip src/veristat" 61 | 62 | - name: Test veristat binary 63 | working-directory: 'veristat/src' 64 | env: 65 | ARCH: ${{ env[format('FILE_STRING_ARCH_{0}', matrix.arch)] }} 66 | run: | 67 | file ./veristat | \ 68 | tee /dev/stderr | \ 69 | grep -q "${{ env.ARCH }}" 70 | ./veristat --usage | grep -q Usage 71 | ldd ./veristat 2>&1 | \ 72 | tee /dev/stderr | \ 73 | grep -q 'not a dynamic executable' 74 | 75 | - name: Upload Artifact 76 | uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 77 | with: 78 | name: ${{ format('veristat_{0}', matrix.arch) }} 79 | path: veristat/src/veristat 80 | 81 | draft-release: 82 | name: Create a draft release 83 | runs-on: ubuntu-22.04 84 | needs: build 85 | permissions: 86 | contents: write 87 | steps: 88 | - name: Download artifacts from build 89 | uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 90 | 91 | - name: Rename binaries and compress 92 | run: | 93 | archive_amd64="veristat-${{ github.ref_name }}-amd64.tar.gz" 94 | archive_arm64="veristat-${{ github.ref_name }}-arm64.tar.gz" 95 | tar -C veristat_amd64 -I 'gzip -9' -cvf "${archive_amd64}" veristat 96 | tar -C veristat_arm64 -I 'gzip -9' -cvf "${archive_arm64}" veristat 97 | sha256sum "${archive_amd64}" > "${archive_amd64}.sha256sum" 98 | sha256sum "${archive_arm64}" > "${archive_arm64}.sha256sum" 99 | 100 | - name: Checkout veristat and libbpf code 101 | uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 102 | with: 103 | submodules: recursive 104 | path: 'veristat' 105 | 106 | - name: Package source code including submodules 107 | uses: qmonnet/git-archive-all-action@791fb850881cf58b1d1fcc9b06c01940080bba0a # v1.0.1 108 | with: 109 | output-files: veristat-all-sources-${{ github.ref_name }}.tar.gz 110 | base-repo: veristat 111 | 112 | - name: Create draft release and add artifacts 113 | uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15 114 | with: 115 | draft: true 116 | files: veristat* 117 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /srcs-full-v*.zip 2 | /srcs-full-v*.tar.gz 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libbpf"] 2 | path = libbpf 3 | url = https://github.com/libbpf/libbpf.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GPL-2.0-only OR BSD-2-Clause 2 | -------------------------------------------------------------------------------- /LICENSE.BSD-2-Clause: -------------------------------------------------------------------------------- 1 | Valid-License-Identifier: BSD-2-Clause 2 | SPDX-URL: https://spdx.org/licenses/BSD-2-Clause.html 3 | Usage-Guide: 4 | To use the BSD 2-clause "Simplified" License put the following SPDX 5 | tag/value pair into a comment according to the placement guidelines in 6 | the licensing rules documentation: 7 | SPDX-License-Identifier: BSD-2-Clause 8 | License-Text: 9 | 10 | Copyright (c) . All rights reserved. 11 | 12 | Redistribution and use in source and binary forms, with or without 13 | modification, are permitted provided that the following conditions are met: 14 | 15 | 1. Redistributions of source code must retain the above copyright notice, 16 | this list of conditions and the following disclaimer. 17 | 18 | 2. Redistributions in binary form must reproduce the above copyright 19 | notice, this list of conditions and the following disclaimer in the 20 | documentation and/or other materials provided with the distribution. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 26 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /LICENSE.GPL-2.0: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | veristat 2 | [![Build](https://github.com/libbpf/veristat/actions/workflows/build.yml/badge.svg)](https://github.com/libbpf/veristat/actions/workflows/build.yml) 3 | ======== 4 | 5 | > This is a mirror of veristat sources from 6 | > [bpf-next](https://kernel.googlesource.com/pub/scm/linux/kernel/git/bpf/bpf-next) 7 | > Linux kernel tree. Veristat is located under `tools/testing/selftests/bpf` 8 | > directory. 9 | 10 | veristat is the tool for loading, verifying, and debugging BPF object files. 11 | It allows to work with BPF object files convenient and quickly, without having 12 | to use or modify corresponding user-space parts of an application. 13 | 14 | We currently don't have a good documentation, so please refer to commit 15 | messages explaining each feature as they were added to veristat upstream: 16 | 17 | - general loading functionality ([patch](https://patchwork.kernel.org/project/netdevbpf/patch/20220909193053.577111-4-andrii@kernel.org/)); 18 | - comparison mode ([patch](https://patchwork.kernel.org/project/netdevbpf/patch/20220921164254.3630690-4-andrii@kernel.org/)); 19 | - results replay mode ([patch](https://patchwork.kernel.org/project/netdevbpf/patch/20221103055304.2904589-2-andrii@kernel.org/)); 20 | - filtering ([patch](https://patchwork.kernel.org/project/netdevbpf/patch/20220921164254.3630690-5-andrii@kernel.org/), 21 | [patch](https://patchwork.kernel.org/project/netdevbpf/patch/20221103055304.2904589-11-andrii@kernel.org/)); 22 | - stats ordering ([patch](https://patchwork.kernel.org/project/netdevbpf/patch/20221103055304.2904589-10-andrii@kernel.org/)); 23 | - controlling BPF verifier log verboseness ([patch](https://patchwork.kernel.org/project/netdevbpf/patch/20220923175913.3272430-6-andrii@kernel.org/)). 24 | 25 | Over time we hopefully will get a more usable and consolidated documentation. 26 | -------------------------------------------------------------------------------- /UPSTREAM-COMMIT: -------------------------------------------------------------------------------- 1 | 858500582386 ("Merge branch 'Prepare veristat for packaging'") 2 | -------------------------------------------------------------------------------- /scripts/archive-srcs-full.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Rely on a nifty git-archive-all.sh script from [0] to package up all the 4 | # sources, including submodules. This script just automates naming and 5 | # creation of both .zip and .tar.gz archives. It expects that this script is 6 | # run from veristat repo root and git-archive-all.sh repo is checked out at 7 | # the same level as veristat's repo. 8 | # 9 | # [0] https://github.com/fabacab/git-archive-all.sh 10 | 11 | ../git-archive-all.sh/git-archive-all.sh --format zip srcs-full-$(src/veristat -V | cut -d' ' -f2).zip 12 | ../git-archive-all.sh/git-archive-all.sh --format tar.gz srcs-full-$(src/veristat -V | cut -d' ' -f2).tar.gz 13 | 14 | git status 15 | 16 | ls -la srcs-full-* 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /scripts/gh-label-release-assets.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) 3 | 4 | set -o errexit 5 | set -o nounset 6 | set -o pipefail 7 | 8 | # Use this script to add labels to GitHub release assets for a given release. 9 | # 10 | # Based on the following console workflow: 11 | # 12 | # gh api \ 13 | # '/repos/libbpf/veristat/releases/tags/' \ 14 | # --jq '.id' 15 | # gh api \ 16 | # '/repos/libbpf/veristat/releases//assets' \ 17 | # --jq '.[] | select(.name == "").id' 18 | # gh api \ 19 | # --method PATCH \ 20 | # -H "Accept: application/vnd.github+json" \ 21 | # -H "X-GitHub-Api-Version: 2022-11-28" \ 22 | # '/repos/libbpf/veristat/releases/assets/' \ 23 | # -f name='' \ 24 | # -f label='' 25 | 26 | REPO="libbpf/veristat" 27 | 28 | usage() { 29 | echo "Update asset labels for veristat releases" 30 | echo "Usage:" 31 | echo " $0 [options] " 32 | echo "" 33 | echo "OPTIONS" 34 | echo " -h display this help" 35 | exit "$1" 36 | } 37 | 38 | OPTIND=1 39 | while getopts "h" opt; do 40 | case "$opt" in 41 | h) 42 | usage 0 43 | ;; 44 | *) 45 | usage 1 46 | ;; 47 | esac 48 | done 49 | shift $((OPTIND-1)) 50 | [[ "${1:-}" = "--" ]] && shift 51 | 52 | # Get release tag from command line 53 | if [[ "$#" -lt 1 ]]; then 54 | echo "error: missing release tag" 55 | usage 1 56 | fi 57 | release_tag="$1" 58 | echo "repo: ${REPO}, release tag: ${release_tag}" 59 | 60 | # Add labels to set for given asset names here: 61 | declare -A assets_labels=( 62 | ["veristat-all-sources-${release_tag}.tar.gz"]="Full source code with submodules (tar.gz)" 63 | ) 64 | 65 | # Get release ID 66 | release_id="$(gh api "/repos/${REPO}/releases/tags/${release_tag}" --jq '.id')" 67 | echo " found release ID ${release_id}" 68 | 69 | # For each label to set, get asset ID, prompt user for confirmation, set label 70 | for asset_name in "${!assets_labels[@]}"; do 71 | asset_id="$(gh api "/repos/${REPO}/releases/${release_id}/assets" \ 72 | --jq ".[] | select(.name == \"${asset_name}\").id")" 73 | echo " found asset ID ${asset_id}" 74 | 75 | echo "asset '${asset_name}': add label '${assets_labels[${asset_name}]}'" 76 | answer="" 77 | read -rp 'proceed? [y/N]: ' answer 78 | 79 | case "${answer}" in 80 | y|yes|Y|Yes|YES) 81 | # Note: A 404 error at this stage may be synonymous with 82 | # insufficient permissions for the token in use for gh. 83 | gh api \ 84 | --method PATCH \ 85 | -H 'Accept: application/vnd.github+json' \ 86 | -H 'X-GitHub-Api-Version: 2022-11-28' \ 87 | "/repos/${REPO}/releases/assets/${asset_id}" \ 88 | -f label="${assets_labels[${asset_name}]}" 89 | ;; 90 | *) 91 | echo "cancelled" 92 | ;; 93 | esac 94 | done 95 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | /.output 2 | /veristat 3 | /.gdb_history 4 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-2-Clause 2 | VERISTAT_VERSION ?= 0.4.1 3 | DEBUG ?= 4 | 5 | OUTPUT := .output 6 | LIBBPF_SRC := $(abspath ../libbpf/src) 7 | LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a) 8 | # Use our own libbpf API headers and Linux UAPI headers distributed with 9 | # libbpf to avoid dependency on system-wide headers, which could be missing or 10 | # outdated 11 | INCLUDES := -I$(OUTPUT) -I../libbpf/include/uapi 12 | CFLAGS := -g -Wall -DVERISTAT_VERSION=\"$(VERISTAT_VERSION)\" -O$(if $(DEBUG),0,2) 13 | ALL_CFLAGS := $(CFLAGS) $(EXTRA_CFLAGS) 14 | ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS) 15 | 16 | export VERISTAT_VERSION 17 | 18 | ifeq ($(V),1) 19 | Q = 20 | msg = 21 | else 22 | Q = @ 23 | msg = @printf ' %-8s %s%s\n' \ 24 | "$(1)" \ 25 | "$(patsubst $(abspath $(OUTPUT))/%,%,$(2))" \ 26 | "$(if $(3), $(3))"; 27 | MAKEFLAGS += --no-print-directory 28 | endif 29 | 30 | define allow-override 31 | $(if $(or $(findstring environment,$(origin $(1))),\ 32 | $(findstring command line,$(origin $(1)))),,\ 33 | $(eval $(1) = $(2))) 34 | endef 35 | 36 | $(call allow-override,CC,$(CROSS_COMPILE)cc) 37 | $(call allow-override,LD,$(CROSS_COMPILE)ld) 38 | 39 | # for installation 40 | INSTALL := install 41 | prefix := /usr/local 42 | bindir := $(prefix)/bin 43 | 44 | .PHONY: all 45 | all: veristat 46 | 47 | .PHONY: clean 48 | clean: 49 | $(call msg,CLEAN) 50 | $(Q)rm -rf $(OUTPUT) veristat 51 | 52 | .PHONY: install 53 | install: all 54 | mkdir -p $(DESTDIR)$(bindir)/ 55 | $(INSTALL) -pt $(DESTDIR)$(bindir)/ veristat 56 | 57 | $(OUTPUT) $(OUTPUT)/libbpf: 58 | $(call msg,MKDIR,$@) 59 | $(Q)mkdir -p $@ 60 | 61 | # Build libbpf 62 | $(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf 63 | $(call msg,LIB,$@) 64 | $(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \ 65 | OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \ 66 | INCLUDEDIR= LIBDIR= UAPIDIR= \ 67 | EXTRA_CFLAGS="-g -O$(if $(DEBUG),0,2) $(EXTRA_CFLAGS)" \ 68 | install 69 | 70 | # Build .c files 71 | $(OUTPUT)/%.o: %.c $(wildcard %.h) $(LIBBPF_OBJ) | $(OUTPUT) 72 | $(call msg,CC,$@) 73 | $(Q)$(CC) $(ALL_CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@ 74 | 75 | # Build application binary 76 | veristat: $(OUTPUT)/veristat.o $(LIBBPF_OBJ) | $(OUTPUT) 77 | $(call msg,BINARY,$@) 78 | $(Q)$(CC) $(ALL_CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@ 79 | 80 | # delete failed targets 81 | .DELETE_ON_ERROR: 82 | 83 | # keep intermediate (.skel.h, .bpf.o, etc) targets 84 | .SECONDARY: 85 | -------------------------------------------------------------------------------- /src/veristat.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) 2 | /* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */ 3 | #define _GNU_SOURCE 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #ifndef ARRAY_SIZE 26 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 27 | #endif 28 | 29 | #ifndef max 30 | #define max(a, b) ((a) > (b) ? (a) : (b)) 31 | #endif 32 | 33 | #ifndef min 34 | #define min(a, b) ((a) < (b) ? (a) : (b)) 35 | #endif 36 | 37 | enum stat_id { 38 | VERDICT, 39 | DURATION, 40 | TOTAL_INSNS, 41 | TOTAL_STATES, 42 | PEAK_STATES, 43 | MAX_STATES_PER_INSN, 44 | MARK_READ_MAX_LEN, 45 | SIZE, 46 | JITED_SIZE, 47 | STACK, 48 | PROG_TYPE, 49 | ATTACH_TYPE, 50 | 51 | FILE_NAME, 52 | PROG_NAME, 53 | 54 | ALL_STATS_CNT, 55 | NUM_STATS_CNT = FILE_NAME - VERDICT, 56 | }; 57 | 58 | /* In comparison mode each stat can specify up to four different values: 59 | * - A side value; 60 | * - B side value; 61 | * - absolute diff value; 62 | * - relative (percentage) diff value. 63 | * 64 | * When specifying stat specs in comparison mode, user can use one of the 65 | * following variant suffixes to specify which exact variant should be used for 66 | * ordering or filtering: 67 | * - `_a` for A side value; 68 | * - `_b` for B side value; 69 | * - `_diff` for absolute diff value; 70 | * - `_pct` for relative (percentage) diff value. 71 | * 72 | * If no variant suffix is provided, then `_b` (control data) is assumed. 73 | * 74 | * As an example, let's say instructions stat has the following output: 75 | * 76 | * Insns (A) Insns (B) Insns (DIFF) 77 | * --------- --------- -------------- 78 | * 21547 20920 -627 (-2.91%) 79 | * 80 | * Then: 81 | * - 21547 is A side value (insns_a); 82 | * - 20920 is B side value (insns_b); 83 | * - -627 is absolute diff value (insns_diff); 84 | * - -2.91% is relative diff value (insns_pct). 85 | * 86 | * For verdict there is no verdict_pct variant. 87 | * For file and program name, _a and _b variants are equivalent and there are 88 | * no _diff or _pct variants. 89 | */ 90 | enum stat_variant { 91 | VARIANT_A, 92 | VARIANT_B, 93 | VARIANT_DIFF, 94 | VARIANT_PCT, 95 | }; 96 | 97 | struct verif_stats { 98 | char *file_name; 99 | char *prog_name; 100 | 101 | long stats[NUM_STATS_CNT]; 102 | }; 103 | 104 | /* joined comparison mode stats */ 105 | struct verif_stats_join { 106 | char *file_name; 107 | char *prog_name; 108 | 109 | const struct verif_stats *stats_a; 110 | const struct verif_stats *stats_b; 111 | }; 112 | 113 | struct stat_specs { 114 | int spec_cnt; 115 | enum stat_id ids[ALL_STATS_CNT]; 116 | enum stat_variant variants[ALL_STATS_CNT]; 117 | bool asc[ALL_STATS_CNT]; 118 | bool abs[ALL_STATS_CNT]; 119 | int lens[ALL_STATS_CNT * 3]; /* 3x for comparison mode */ 120 | }; 121 | 122 | enum resfmt { 123 | RESFMT_TABLE, 124 | RESFMT_TABLE_CALCLEN, /* fake format to pre-calculate table's column widths */ 125 | RESFMT_CSV, 126 | }; 127 | 128 | enum filter_kind { 129 | FILTER_NAME, 130 | FILTER_STAT, 131 | }; 132 | 133 | enum operator_kind { 134 | OP_EQ, /* == or = */ 135 | OP_NEQ, /* != or <> */ 136 | OP_LT, /* < */ 137 | OP_LE, /* <= */ 138 | OP_GT, /* > */ 139 | OP_GE, /* >= */ 140 | }; 141 | 142 | struct filter { 143 | enum filter_kind kind; 144 | /* FILTER_NAME */ 145 | char *any_glob; 146 | char *file_glob; 147 | char *prog_glob; 148 | /* FILTER_STAT */ 149 | enum operator_kind op; 150 | int stat_id; 151 | enum stat_variant stat_var; 152 | long value; 153 | bool abs; 154 | }; 155 | 156 | static struct env { 157 | char **filenames; 158 | int filename_cnt; 159 | bool verbose; 160 | bool debug; 161 | bool quiet; 162 | bool force_checkpoints; 163 | bool force_reg_invariants; 164 | enum resfmt out_fmt; 165 | bool show_version; 166 | bool comparison_mode; 167 | bool replay_mode; 168 | int top_n; 169 | 170 | int log_level; 171 | int log_size; 172 | bool log_fixed; 173 | 174 | struct verif_stats *prog_stats; 175 | int prog_stat_cnt; 176 | 177 | /* baseline_stats is allocated and used only in comparison mode */ 178 | struct verif_stats *baseline_stats; 179 | int baseline_stat_cnt; 180 | 181 | struct verif_stats_join *join_stats; 182 | int join_stat_cnt; 183 | 184 | struct stat_specs output_spec; 185 | struct stat_specs sort_spec; 186 | 187 | struct filter *allow_filters; 188 | struct filter *deny_filters; 189 | int allow_filter_cnt; 190 | int deny_filter_cnt; 191 | 192 | int files_processed; 193 | int files_skipped; 194 | int progs_processed; 195 | int progs_skipped; 196 | int top_src_lines; 197 | } env; 198 | 199 | static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) 200 | { 201 | if (!env.verbose) 202 | return 0; 203 | if (level == LIBBPF_DEBUG && !env.debug) 204 | return 0; 205 | return vfprintf(stderr, format, args); 206 | } 207 | 208 | #ifndef VERISTAT_VERSION 209 | #define VERISTAT_VERSION "" 210 | #endif 211 | 212 | const char *argp_program_version = "veristat v" VERISTAT_VERSION; 213 | const char *argp_program_bug_address = ""; 214 | const char argp_program_doc[] = 215 | "veristat BPF verifier stats collection and comparison tool.\n" 216 | "\n" 217 | "USAGE: veristat [...]\n" 218 | " OR: veristat -C \n" 219 | " OR: veristat -R \n"; 220 | 221 | enum { 222 | OPT_LOG_FIXED = 1000, 223 | OPT_LOG_SIZE = 1001, 224 | }; 225 | 226 | static const struct argp_option opts[] = { 227 | { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" }, 228 | { "version", 'V', NULL, 0, "Print version" }, 229 | { "verbose", 'v', NULL, 0, "Verbose mode" }, 230 | { "debug", 'd', NULL, 0, "Debug mode (turns on libbpf debug logging)" }, 231 | { "log-level", 'l', "LEVEL", 0, "Verifier log level (default 0 for normal mode, 1 for verbose mode)" }, 232 | { "log-fixed", OPT_LOG_FIXED, NULL, 0, "Disable verifier log rotation" }, 233 | { "log-size", OPT_LOG_SIZE, "BYTES", 0, "Customize verifier log size (default to 16MB)" }, 234 | { "top-n", 'n', "N", 0, "Emit only up to first N results." }, 235 | { "quiet", 'q', NULL, 0, "Quiet mode" }, 236 | { "emit", 'e', "SPEC", 0, "Specify stats to be emitted" }, 237 | { "sort", 's', "SPEC", 0, "Specify sort order" }, 238 | { "output-format", 'o', "FMT", 0, "Result output format (table, csv), default is table." }, 239 | { "compare", 'C', NULL, 0, "Comparison mode" }, 240 | { "replay", 'R', NULL, 0, "Replay mode" }, 241 | { "filter", 'f', "FILTER", 0, "Filter expressions (or @filename for file with expressions)." }, 242 | { "test-states", 't', NULL, 0, 243 | "Force frequent BPF verifier state checkpointing (set BPF_F_TEST_STATE_FREQ program flag)" }, 244 | { "test-reg-invariants", 'r', NULL, 0, 245 | "Force BPF verifier failure on register invariant violation (BPF_F_TEST_REG_INVARIANTS program flag)" }, 246 | { "top-src-lines", 'S', "N", 0, "Emit N most frequent source code lines" }, 247 | {}, 248 | }; 249 | 250 | static int parse_stats(const char *stats_str, struct stat_specs *specs); 251 | static int append_filter(struct filter **filters, int *cnt, const char *str); 252 | static int append_filter_file(const char *path); 253 | 254 | static error_t parse_arg(int key, char *arg, struct argp_state *state) 255 | { 256 | void *tmp; 257 | int err; 258 | 259 | switch (key) { 260 | case 'h': 261 | argp_state_help(state, stderr, ARGP_HELP_STD_HELP); 262 | break; 263 | case 'V': 264 | env.show_version = true; 265 | break; 266 | case 'v': 267 | env.verbose = true; 268 | break; 269 | case 'd': 270 | env.debug = true; 271 | env.verbose = true; 272 | break; 273 | case 'q': 274 | env.quiet = true; 275 | break; 276 | case 'e': 277 | err = parse_stats(arg, &env.output_spec); 278 | if (err) 279 | return err; 280 | break; 281 | case 's': 282 | err = parse_stats(arg, &env.sort_spec); 283 | if (err) 284 | return err; 285 | break; 286 | case 'o': 287 | if (strcmp(arg, "table") == 0) { 288 | env.out_fmt = RESFMT_TABLE; 289 | } else if (strcmp(arg, "csv") == 0) { 290 | env.out_fmt = RESFMT_CSV; 291 | } else { 292 | fprintf(stderr, "Unrecognized output format '%s'\n", arg); 293 | return -EINVAL; 294 | } 295 | break; 296 | case 'l': 297 | errno = 0; 298 | env.log_level = strtol(arg, NULL, 10); 299 | if (errno) { 300 | fprintf(stderr, "invalid log level: %s\n", arg); 301 | argp_usage(state); 302 | } 303 | break; 304 | case OPT_LOG_FIXED: 305 | env.log_fixed = true; 306 | break; 307 | case OPT_LOG_SIZE: 308 | errno = 0; 309 | env.log_size = strtol(arg, NULL, 10); 310 | if (errno) { 311 | fprintf(stderr, "invalid log size: %s\n", arg); 312 | argp_usage(state); 313 | } 314 | break; 315 | case 't': 316 | env.force_checkpoints = true; 317 | break; 318 | case 'r': 319 | env.force_reg_invariants = true; 320 | break; 321 | case 'n': 322 | errno = 0; 323 | env.top_n = strtol(arg, NULL, 10); 324 | if (errno) { 325 | fprintf(stderr, "invalid top N specifier: %s\n", arg); 326 | argp_usage(state); 327 | } 328 | case 'C': 329 | env.comparison_mode = true; 330 | break; 331 | case 'R': 332 | env.replay_mode = true; 333 | break; 334 | case 'f': 335 | if (arg[0] == '@') 336 | err = append_filter_file(arg + 1); 337 | else if (arg[0] == '!') 338 | err = append_filter(&env.deny_filters, &env.deny_filter_cnt, arg + 1); 339 | else 340 | err = append_filter(&env.allow_filters, &env.allow_filter_cnt, arg); 341 | if (err) { 342 | fprintf(stderr, "Failed to collect program filter expressions: %d\n", err); 343 | return err; 344 | } 345 | break; 346 | case 'S': 347 | errno = 0; 348 | env.top_src_lines = strtol(arg, NULL, 10); 349 | if (errno) { 350 | fprintf(stderr, "invalid top lines N specifier: %s\n", arg); 351 | argp_usage(state); 352 | } 353 | break; 354 | case ARGP_KEY_ARG: 355 | tmp = realloc(env.filenames, (env.filename_cnt + 1) * sizeof(*env.filenames)); 356 | if (!tmp) 357 | return -ENOMEM; 358 | env.filenames = tmp; 359 | env.filenames[env.filename_cnt] = strdup(arg); 360 | if (!env.filenames[env.filename_cnt]) 361 | return -ENOMEM; 362 | env.filename_cnt++; 363 | break; 364 | default: 365 | return ARGP_ERR_UNKNOWN; 366 | } 367 | return 0; 368 | } 369 | 370 | static const struct argp argp = { 371 | .options = opts, 372 | .parser = parse_arg, 373 | .doc = argp_program_doc, 374 | }; 375 | 376 | 377 | /* Adapted from perf/util/string.c */ 378 | static bool glob_matches(const char *str, const char *pat) 379 | { 380 | while (*str && *pat && *pat != '*') { 381 | if (*str != *pat) 382 | return false; 383 | str++; 384 | pat++; 385 | } 386 | /* Check wild card */ 387 | if (*pat == '*') { 388 | while (*pat == '*') 389 | pat++; 390 | if (!*pat) /* Tail wild card matches all */ 391 | return true; 392 | while (*str) 393 | if (glob_matches(str++, pat)) 394 | return true; 395 | } 396 | return !*str && !*pat; 397 | } 398 | 399 | static bool is_bpf_obj_file(const char *path) { 400 | Elf64_Ehdr *ehdr; 401 | int fd, err = -EINVAL; 402 | Elf *elf = NULL; 403 | 404 | fd = open(path, O_RDONLY | O_CLOEXEC); 405 | if (fd < 0) 406 | return true; /* we'll fail later and propagate error */ 407 | 408 | /* ensure libelf is initialized */ 409 | (void)elf_version(EV_CURRENT); 410 | 411 | elf = elf_begin(fd, ELF_C_READ, NULL); 412 | if (!elf) 413 | goto cleanup; 414 | 415 | if (elf_kind(elf) != ELF_K_ELF || gelf_getclass(elf) != ELFCLASS64) 416 | goto cleanup; 417 | 418 | ehdr = elf64_getehdr(elf); 419 | /* Old LLVM set e_machine to EM_NONE */ 420 | if (!ehdr || ehdr->e_type != ET_REL || (ehdr->e_machine && ehdr->e_machine != EM_BPF)) 421 | goto cleanup; 422 | 423 | err = 0; 424 | cleanup: 425 | if (elf) 426 | elf_end(elf); 427 | close(fd); 428 | return err == 0; 429 | } 430 | 431 | static bool should_process_file_prog(const char *filename, const char *prog_name) 432 | { 433 | struct filter *f; 434 | int i, allow_cnt = 0; 435 | 436 | for (i = 0; i < env.deny_filter_cnt; i++) { 437 | f = &env.deny_filters[i]; 438 | if (f->kind != FILTER_NAME) 439 | continue; 440 | 441 | if (f->any_glob && glob_matches(filename, f->any_glob)) 442 | return false; 443 | if (f->any_glob && prog_name && glob_matches(prog_name, f->any_glob)) 444 | return false; 445 | if (f->file_glob && glob_matches(filename, f->file_glob)) 446 | return false; 447 | if (f->prog_glob && prog_name && glob_matches(prog_name, f->prog_glob)) 448 | return false; 449 | } 450 | 451 | for (i = 0; i < env.allow_filter_cnt; i++) { 452 | f = &env.allow_filters[i]; 453 | if (f->kind != FILTER_NAME) 454 | continue; 455 | 456 | allow_cnt++; 457 | if (f->any_glob) { 458 | if (glob_matches(filename, f->any_glob)) 459 | return true; 460 | /* If we don't know program name yet, any_glob filter 461 | * has to assume that current BPF object file might be 462 | * relevant; we'll check again later on after opening 463 | * BPF object file, at which point program name will 464 | * be known finally. 465 | */ 466 | if (!prog_name || glob_matches(prog_name, f->any_glob)) 467 | return true; 468 | } else { 469 | if (f->file_glob && !glob_matches(filename, f->file_glob)) 470 | continue; 471 | if (f->prog_glob && prog_name && !glob_matches(prog_name, f->prog_glob)) 472 | continue; 473 | return true; 474 | } 475 | } 476 | 477 | /* if there are no file/prog name allow filters, allow all progs, 478 | * unless they are denied earlier explicitly 479 | */ 480 | return allow_cnt == 0; 481 | } 482 | 483 | static struct { 484 | enum operator_kind op_kind; 485 | const char *op_str; 486 | } operators[] = { 487 | /* Order of these definitions matter to avoid situations like '<' 488 | * matching part of what is actually a '<>' operator. That is, 489 | * substrings should go last. 490 | */ 491 | { OP_EQ, "==" }, 492 | { OP_NEQ, "!=" }, 493 | { OP_NEQ, "<>" }, 494 | { OP_LE, "<=" }, 495 | { OP_LT, "<" }, 496 | { OP_GE, ">=" }, 497 | { OP_GT, ">" }, 498 | { OP_EQ, "=" }, 499 | }; 500 | 501 | static bool parse_stat_id_var(const char *name, size_t len, int *id, 502 | enum stat_variant *var, bool *is_abs); 503 | 504 | static int append_filter(struct filter **filters, int *cnt, const char *str) 505 | { 506 | struct filter *f; 507 | void *tmp; 508 | const char *p; 509 | int i; 510 | 511 | tmp = realloc(*filters, (*cnt + 1) * sizeof(**filters)); 512 | if (!tmp) 513 | return -ENOMEM; 514 | *filters = tmp; 515 | 516 | f = &(*filters)[*cnt]; 517 | memset(f, 0, sizeof(*f)); 518 | 519 | /* First, let's check if it's a stats filter of the following form: 520 | * is one of supported numerical stats (verdict is also 522 | * considered numerical, failure == 0, success == 1); 523 | * - is comparison operator (see `operators` definitions); 524 | * - is an integer (or failure/success, or false/true as 525 | * special aliases for 0 and 1, respectively). 526 | * If the form doesn't match what user provided, we assume file/prog 527 | * glob filter. 528 | */ 529 | for (i = 0; i < ARRAY_SIZE(operators); i++) { 530 | enum stat_variant var; 531 | int id; 532 | long val; 533 | const char *end = str; 534 | const char *op_str; 535 | bool is_abs; 536 | 537 | op_str = operators[i].op_str; 538 | p = strstr(str, op_str); 539 | if (!p) 540 | continue; 541 | 542 | if (!parse_stat_id_var(str, p - str, &id, &var, &is_abs)) { 543 | fprintf(stderr, "Unrecognized stat name in '%s'!\n", str); 544 | return -EINVAL; 545 | } 546 | if (id >= FILE_NAME) { 547 | fprintf(stderr, "Non-integer stat is specified in '%s'!\n", str); 548 | return -EINVAL; 549 | } 550 | 551 | p += strlen(op_str); 552 | 553 | if (strcasecmp(p, "true") == 0 || 554 | strcasecmp(p, "t") == 0 || 555 | strcasecmp(p, "success") == 0 || 556 | strcasecmp(p, "succ") == 0 || 557 | strcasecmp(p, "s") == 0 || 558 | strcasecmp(p, "match") == 0 || 559 | strcasecmp(p, "m") == 0) { 560 | val = 1; 561 | } else if (strcasecmp(p, "false") == 0 || 562 | strcasecmp(p, "f") == 0 || 563 | strcasecmp(p, "failure") == 0 || 564 | strcasecmp(p, "fail") == 0 || 565 | strcasecmp(p, "mismatch") == 0 || 566 | strcasecmp(p, "mis") == 0) { 567 | val = 0; 568 | } else { 569 | errno = 0; 570 | val = strtol(p, (char **)&end, 10); 571 | if (errno || end == p || *end != '\0' ) { 572 | fprintf(stderr, "Invalid integer value in '%s'!\n", str); 573 | return -EINVAL; 574 | } 575 | } 576 | 577 | f->kind = FILTER_STAT; 578 | f->stat_id = id; 579 | f->stat_var = var; 580 | f->op = operators[i].op_kind; 581 | f->abs = true; 582 | f->value = val; 583 | 584 | *cnt += 1; 585 | return 0; 586 | } 587 | 588 | /* File/prog filter can be specified either as '' or 589 | * '/'. In the former case is applied to 590 | * both file and program names. This seems to be way more useful in 591 | * practice. If user needs full control, they can use '/' 592 | * form to glob just program name, or '/' to glob only file 593 | * name. But usually common seems to be the most useful and 594 | * ergonomic way. 595 | */ 596 | f->kind = FILTER_NAME; 597 | p = strchr(str, '/'); 598 | if (!p) { 599 | f->any_glob = strdup(str); 600 | if (!f->any_glob) 601 | return -ENOMEM; 602 | } else { 603 | if (str != p) { 604 | /* non-empty file glob */ 605 | f->file_glob = strndup(str, p - str); 606 | if (!f->file_glob) 607 | return -ENOMEM; 608 | } 609 | if (strlen(p + 1) > 0) { 610 | /* non-empty prog glob */ 611 | f->prog_glob = strdup(p + 1); 612 | if (!f->prog_glob) { 613 | free(f->file_glob); 614 | f->file_glob = NULL; 615 | return -ENOMEM; 616 | } 617 | } 618 | } 619 | 620 | *cnt += 1; 621 | return 0; 622 | } 623 | 624 | static int append_filter_file(const char *path) 625 | { 626 | char buf[1024]; 627 | FILE *f; 628 | int err = 0; 629 | 630 | f = fopen(path, "r"); 631 | if (!f) { 632 | err = -errno; 633 | fprintf(stderr, "Failed to open filters in '%s': %d\n", path, err); 634 | return err; 635 | } 636 | 637 | while (fscanf(f, " %1023[^\n]\n", buf) == 1) { 638 | /* lines starting with # are comments, skip them */ 639 | if (buf[0] == '\0' || buf[0] == '#') 640 | continue; 641 | /* lines starting with ! are negative match filters */ 642 | if (buf[0] == '!') 643 | err = append_filter(&env.deny_filters, &env.deny_filter_cnt, buf + 1); 644 | else 645 | err = append_filter(&env.allow_filters, &env.allow_filter_cnt, buf); 646 | if (err) 647 | goto cleanup; 648 | } 649 | 650 | cleanup: 651 | fclose(f); 652 | return err; 653 | } 654 | 655 | static const struct stat_specs default_output_spec = { 656 | .spec_cnt = 8, 657 | .ids = { 658 | FILE_NAME, PROG_NAME, VERDICT, DURATION, 659 | TOTAL_INSNS, TOTAL_STATES, SIZE, JITED_SIZE 660 | }, 661 | }; 662 | 663 | static const struct stat_specs default_csv_output_spec = { 664 | .spec_cnt = 14, 665 | .ids = { 666 | FILE_NAME, PROG_NAME, VERDICT, DURATION, 667 | TOTAL_INSNS, TOTAL_STATES, PEAK_STATES, 668 | MAX_STATES_PER_INSN, MARK_READ_MAX_LEN, 669 | SIZE, JITED_SIZE, PROG_TYPE, ATTACH_TYPE, 670 | STACK, 671 | }, 672 | }; 673 | 674 | static const struct stat_specs default_sort_spec = { 675 | .spec_cnt = 2, 676 | .ids = { 677 | FILE_NAME, PROG_NAME, 678 | }, 679 | .asc = { true, true, }, 680 | }; 681 | 682 | /* sorting for comparison mode to join two data sets */ 683 | static const struct stat_specs join_sort_spec = { 684 | .spec_cnt = 2, 685 | .ids = { 686 | FILE_NAME, PROG_NAME, 687 | }, 688 | .asc = { true, true, }, 689 | }; 690 | 691 | static struct stat_def { 692 | const char *header; 693 | const char *names[4]; 694 | bool asc_by_default; 695 | bool left_aligned; 696 | } stat_defs[] = { 697 | [FILE_NAME] = { "File", {"file_name", "filename", "file"}, true /* asc */, true /* left */ }, 698 | [PROG_NAME] = { "Program", {"prog_name", "progname", "prog"}, true /* asc */, true /* left */ }, 699 | [VERDICT] = { "Verdict", {"verdict"}, true /* asc: failure, success */, true /* left */ }, 700 | [DURATION] = { "Duration (us)", {"duration", "dur"}, }, 701 | [TOTAL_INSNS] = { "Insns", {"total_insns", "insns"}, }, 702 | [TOTAL_STATES] = { "States", {"total_states", "states"}, }, 703 | [PEAK_STATES] = { "Peak states", {"peak_states"}, }, 704 | [MAX_STATES_PER_INSN] = { "Max states per insn", {"max_states_per_insn"}, }, 705 | [MARK_READ_MAX_LEN] = { "Max mark read length", {"max_mark_read_len", "mark_read"}, }, 706 | [SIZE] = { "Program size", {"prog_size"}, }, 707 | [JITED_SIZE] = { "Jited size", {"prog_size_jited"}, }, 708 | [STACK] = {"Stack depth", {"stack_depth", "stack"}, }, 709 | [PROG_TYPE] = { "Program type", {"prog_type"}, }, 710 | [ATTACH_TYPE] = { "Attach type", {"attach_type", }, }, 711 | }; 712 | 713 | static bool parse_stat_id_var(const char *name, size_t len, int *id, 714 | enum stat_variant *var, bool *is_abs) 715 | { 716 | static const char *var_sfxs[] = { 717 | [VARIANT_A] = "_a", 718 | [VARIANT_B] = "_b", 719 | [VARIANT_DIFF] = "_diff", 720 | [VARIANT_PCT] = "_pct", 721 | }; 722 | int i, j, k; 723 | 724 | /* || means we take absolute value of given stat */ 725 | *is_abs = false; 726 | if (len > 2 && name[0] == '|' && name[len - 1] == '|') { 727 | *is_abs = true; 728 | name += 1; 729 | len -= 2; 730 | } 731 | 732 | for (i = 0; i < ARRAY_SIZE(stat_defs); i++) { 733 | struct stat_def *def = &stat_defs[i]; 734 | size_t alias_len, sfx_len; 735 | const char *alias; 736 | 737 | for (j = 0; j < ARRAY_SIZE(stat_defs[i].names); j++) { 738 | alias = def->names[j]; 739 | if (!alias) 740 | continue; 741 | 742 | alias_len = strlen(alias); 743 | if (strncmp(name, alias, alias_len) != 0) 744 | continue; 745 | 746 | if (alias_len == len) { 747 | /* If no variant suffix is specified, we 748 | * assume control group (just in case we are 749 | * in comparison mode. Variant is ignored in 750 | * non-comparison mode. 751 | */ 752 | *var = VARIANT_B; 753 | *id = i; 754 | return true; 755 | } 756 | 757 | for (k = 0; k < ARRAY_SIZE(var_sfxs); k++) { 758 | sfx_len = strlen(var_sfxs[k]); 759 | if (alias_len + sfx_len != len) 760 | continue; 761 | 762 | if (strncmp(name + alias_len, var_sfxs[k], sfx_len) == 0) { 763 | *var = (enum stat_variant)k; 764 | *id = i; 765 | return true; 766 | } 767 | } 768 | } 769 | } 770 | 771 | return false; 772 | } 773 | 774 | static bool is_asc_sym(char c) 775 | { 776 | return c == '^'; 777 | } 778 | 779 | static bool is_desc_sym(char c) 780 | { 781 | return c == 'v' || c == 'V' || c == '.' || c == '!' || c == '_'; 782 | } 783 | 784 | static int parse_stat(const char *stat_name, struct stat_specs *specs) 785 | { 786 | int id; 787 | bool has_order = false, is_asc = false, is_abs = false; 788 | size_t len = strlen(stat_name); 789 | enum stat_variant var; 790 | 791 | if (specs->spec_cnt >= ARRAY_SIZE(specs->ids)) { 792 | fprintf(stderr, "Can't specify more than %zd stats\n", ARRAY_SIZE(specs->ids)); 793 | return -E2BIG; 794 | } 795 | 796 | if (len > 1 && (is_asc_sym(stat_name[len - 1]) || is_desc_sym(stat_name[len - 1]))) { 797 | has_order = true; 798 | is_asc = is_asc_sym(stat_name[len - 1]); 799 | len -= 1; 800 | } 801 | 802 | if (!parse_stat_id_var(stat_name, len, &id, &var, &is_abs)) { 803 | fprintf(stderr, "Unrecognized stat name '%s'\n", stat_name); 804 | return -ESRCH; 805 | } 806 | 807 | specs->ids[specs->spec_cnt] = id; 808 | specs->variants[specs->spec_cnt] = var; 809 | specs->asc[specs->spec_cnt] = has_order ? is_asc : stat_defs[id].asc_by_default; 810 | specs->abs[specs->spec_cnt] = is_abs; 811 | specs->spec_cnt++; 812 | 813 | return 0; 814 | } 815 | 816 | static int parse_stats(const char *stats_str, struct stat_specs *specs) 817 | { 818 | char *input, *state = NULL, *next; 819 | int err, cnt = 0; 820 | 821 | input = strdup(stats_str); 822 | if (!input) 823 | return -ENOMEM; 824 | 825 | while ((next = strtok_r(cnt++ ? NULL : input, ",", &state))) { 826 | err = parse_stat(next, specs); 827 | if (err) { 828 | free(input); 829 | return err; 830 | } 831 | } 832 | 833 | free(input); 834 | return 0; 835 | } 836 | 837 | static void free_verif_stats(struct verif_stats *stats, size_t stat_cnt) 838 | { 839 | int i; 840 | 841 | if (!stats) 842 | return; 843 | 844 | for (i = 0; i < stat_cnt; i++) { 845 | free(stats[i].file_name); 846 | free(stats[i].prog_name); 847 | } 848 | free(stats); 849 | } 850 | 851 | static char verif_log_buf[64 * 1024]; 852 | 853 | #define MAX_PARSED_LOG_LINES 100 854 | 855 | static int parse_verif_log(char * const buf, size_t buf_sz, struct verif_stats *s) 856 | { 857 | const char *cur; 858 | int pos, lines, sub_stack, cnt = 0; 859 | char *state = NULL, *token, stack[512]; 860 | 861 | buf[buf_sz - 1] = '\0'; 862 | 863 | for (pos = strlen(buf) - 1, lines = 0; pos >= 0 && lines < MAX_PARSED_LOG_LINES; lines++) { 864 | /* find previous endline or otherwise take the start of log buf */ 865 | for (cur = &buf[pos]; cur > buf && cur[0] != '\n'; cur--, pos--) { 866 | } 867 | /* next time start from end of previous line (or pos goes to <0) */ 868 | pos--; 869 | /* if we found endline, point right after endline symbol; 870 | * otherwise, stay at the beginning of log buf 871 | */ 872 | if (cur[0] == '\n') 873 | cur++; 874 | 875 | if (1 == sscanf(cur, "verification time %ld usec\n", &s->stats[DURATION])) 876 | continue; 877 | if (5 == sscanf(cur, "processed %ld insns (limit %*d) max_states_per_insn %ld total_states %ld peak_states %ld mark_read %ld", 878 | &s->stats[TOTAL_INSNS], 879 | &s->stats[MAX_STATES_PER_INSN], 880 | &s->stats[TOTAL_STATES], 881 | &s->stats[PEAK_STATES], 882 | &s->stats[MARK_READ_MAX_LEN])) 883 | continue; 884 | 885 | if (1 == sscanf(cur, "stack depth %511s", stack)) 886 | continue; 887 | } 888 | while ((token = strtok_r(cnt++ ? NULL : stack, "+", &state))) { 889 | if (sscanf(token, "%d", &sub_stack) == 0) 890 | break; 891 | s->stats[STACK] += sub_stack; 892 | } 893 | return 0; 894 | } 895 | 896 | struct line_cnt { 897 | char *line; 898 | int cnt; 899 | }; 900 | 901 | static int str_cmp(const void *a, const void *b) 902 | { 903 | const char **str1 = (const char **)a; 904 | const char **str2 = (const char **)b; 905 | 906 | return strcmp(*str1, *str2); 907 | } 908 | 909 | static int line_cnt_cmp(const void *a, const void *b) 910 | { 911 | const struct line_cnt *a_cnt = (const struct line_cnt *)a; 912 | const struct line_cnt *b_cnt = (const struct line_cnt *)b; 913 | 914 | if (a_cnt->cnt != b_cnt->cnt) 915 | return a_cnt->cnt > b_cnt->cnt ? -1 : 1; 916 | return strcmp(a_cnt->line, b_cnt->line); 917 | } 918 | 919 | static int print_top_src_lines(char * const buf, size_t buf_sz, const char *prog_name) 920 | { 921 | int lines_cap = 0; 922 | int lines_size = 0; 923 | char **lines = NULL; 924 | char *line = NULL; 925 | char *state; 926 | struct line_cnt *freq = NULL; 927 | struct line_cnt *cur; 928 | int unique_lines; 929 | int err = 0; 930 | int i; 931 | 932 | while ((line = strtok_r(line ? NULL : buf, "\n", &state))) { 933 | if (strncmp(line, "; ", 2) != 0) 934 | continue; 935 | line += 2; 936 | 937 | if (lines_size == lines_cap) { 938 | char **tmp; 939 | 940 | lines_cap = max(16, lines_cap * 2); 941 | tmp = realloc(lines, lines_cap * sizeof(*tmp)); 942 | if (!tmp) { 943 | err = -ENOMEM; 944 | goto cleanup; 945 | } 946 | lines = tmp; 947 | } 948 | lines[lines_size] = line; 949 | lines_size++; 950 | } 951 | 952 | if (lines_size == 0) 953 | goto cleanup; 954 | 955 | qsort(lines, lines_size, sizeof(*lines), str_cmp); 956 | 957 | freq = calloc(lines_size, sizeof(*freq)); 958 | if (!freq) { 959 | err = -ENOMEM; 960 | goto cleanup; 961 | } 962 | 963 | cur = freq; 964 | cur->line = lines[0]; 965 | cur->cnt = 1; 966 | for (i = 1; i < lines_size; ++i) { 967 | if (strcmp(lines[i], cur->line) != 0) { 968 | cur++; 969 | cur->line = lines[i]; 970 | cur->cnt = 0; 971 | } 972 | cur->cnt++; 973 | } 974 | unique_lines = cur - freq + 1; 975 | 976 | qsort(freq, unique_lines, sizeof(struct line_cnt), line_cnt_cmp); 977 | 978 | printf("Top source lines (%s):\n", prog_name); 979 | for (i = 0; i < min(unique_lines, env.top_src_lines); ++i) { 980 | const char *src_code = freq[i].line; 981 | const char *src_line = NULL; 982 | char *split = strrchr(freq[i].line, '@'); 983 | 984 | if (split) { 985 | src_line = split + 1; 986 | 987 | while (*src_line && isspace(*src_line)) 988 | src_line++; 989 | 990 | while (split > src_code && isspace(*split)) 991 | split--; 992 | *split = '\0'; 993 | } 994 | 995 | if (src_line) 996 | printf("%5d: (%s)\t%s\n", freq[i].cnt, src_line, src_code); 997 | else 998 | printf("%5d: %s\n", freq[i].cnt, src_code); 999 | } 1000 | printf("\n"); 1001 | 1002 | cleanup: 1003 | free(freq); 1004 | free(lines); 1005 | return err; 1006 | } 1007 | 1008 | static int guess_prog_type_by_ctx_name(const char *ctx_name, 1009 | enum bpf_prog_type *prog_type, 1010 | enum bpf_attach_type *attach_type) 1011 | { 1012 | /* We need to guess program type based on its declared context type. 1013 | * This guess can't be perfect as many different program types might 1014 | * share the same context type. So we can only hope to reasonably 1015 | * well guess this and get lucky. 1016 | * 1017 | * Just in case, we support both UAPI-side type names and 1018 | * kernel-internal names. 1019 | */ 1020 | static struct { 1021 | const char *uapi_name; 1022 | const char *kern_name; 1023 | enum bpf_prog_type prog_type; 1024 | enum bpf_attach_type attach_type; 1025 | } ctx_map[] = { 1026 | /* __sk_buff is most ambiguous, we assume TC program */ 1027 | { "__sk_buff", "sk_buff", BPF_PROG_TYPE_SCHED_CLS }, 1028 | { "bpf_sock", "sock", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND }, 1029 | { "bpf_sock_addr", "bpf_sock_addr_kern", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND }, 1030 | { "bpf_sock_ops", "bpf_sock_ops_kern", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS }, 1031 | { "sk_msg_md", "sk_msg", BPF_PROG_TYPE_SK_MSG, BPF_SK_MSG_VERDICT }, 1032 | { "bpf_cgroup_dev_ctx", "bpf_cgroup_dev_ctx", BPF_PROG_TYPE_CGROUP_DEVICE, BPF_CGROUP_DEVICE }, 1033 | { "bpf_sysctl", "bpf_sysctl_kern", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL }, 1034 | { "bpf_sockopt", "bpf_sockopt_kern", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT }, 1035 | { "sk_reuseport_md", "sk_reuseport_kern", BPF_PROG_TYPE_SK_REUSEPORT, BPF_SK_REUSEPORT_SELECT_OR_MIGRATE }, 1036 | { "bpf_sk_lookup", "bpf_sk_lookup_kern", BPF_PROG_TYPE_SK_LOOKUP, BPF_SK_LOOKUP }, 1037 | { "xdp_md", "xdp_buff", BPF_PROG_TYPE_XDP, BPF_XDP }, 1038 | /* tracing types with no expected attach type */ 1039 | { "bpf_user_pt_regs_t", "pt_regs", BPF_PROG_TYPE_KPROBE }, 1040 | { "bpf_perf_event_data", "bpf_perf_event_data_kern", BPF_PROG_TYPE_PERF_EVENT }, 1041 | /* raw_tp programs use u64[] from kernel side, we don't want 1042 | * to match on that, probably; so NULL for kern-side type 1043 | */ 1044 | { "bpf_raw_tracepoint_args", NULL, BPF_PROG_TYPE_RAW_TRACEPOINT }, 1045 | }; 1046 | int i; 1047 | 1048 | if (!ctx_name) 1049 | return -EINVAL; 1050 | 1051 | for (i = 0; i < ARRAY_SIZE(ctx_map); i++) { 1052 | if (strcmp(ctx_map[i].uapi_name, ctx_name) == 0 || 1053 | (ctx_map[i].kern_name && strcmp(ctx_map[i].kern_name, ctx_name) == 0)) { 1054 | *prog_type = ctx_map[i].prog_type; 1055 | *attach_type = ctx_map[i].attach_type; 1056 | return 0; 1057 | } 1058 | } 1059 | 1060 | return -ESRCH; 1061 | } 1062 | 1063 | static void fixup_obj(struct bpf_object *obj, struct bpf_program *prog, const char *filename) 1064 | { 1065 | struct bpf_map *map; 1066 | 1067 | bpf_object__for_each_map(map, obj) { 1068 | /* disable pinning */ 1069 | bpf_map__set_pin_path(map, NULL); 1070 | 1071 | /* fix up map size, if necessary */ 1072 | switch (bpf_map__type(map)) { 1073 | case BPF_MAP_TYPE_SK_STORAGE: 1074 | case BPF_MAP_TYPE_TASK_STORAGE: 1075 | case BPF_MAP_TYPE_INODE_STORAGE: 1076 | case BPF_MAP_TYPE_CGROUP_STORAGE: 1077 | break; 1078 | default: 1079 | if (bpf_map__max_entries(map) == 0) 1080 | bpf_map__set_max_entries(map, 1); 1081 | } 1082 | } 1083 | 1084 | /* SEC(freplace) programs can't be loaded with veristat as is, 1085 | * but we can try guessing their target program's expected type by 1086 | * looking at the type of program's first argument and substituting 1087 | * corresponding program type 1088 | */ 1089 | if (bpf_program__type(prog) == BPF_PROG_TYPE_EXT) { 1090 | const struct btf *btf = bpf_object__btf(obj); 1091 | const char *prog_name = bpf_program__name(prog); 1092 | enum bpf_prog_type prog_type; 1093 | enum bpf_attach_type attach_type; 1094 | const struct btf_type *t; 1095 | const char *ctx_name; 1096 | int id; 1097 | 1098 | if (!btf) 1099 | goto skip_freplace_fixup; 1100 | 1101 | id = btf__find_by_name_kind(btf, prog_name, BTF_KIND_FUNC); 1102 | t = btf__type_by_id(btf, id); 1103 | t = btf__type_by_id(btf, t->type); 1104 | if (!btf_is_func_proto(t) || btf_vlen(t) != 1) 1105 | goto skip_freplace_fixup; 1106 | 1107 | /* context argument is a pointer to a struct/typedef */ 1108 | t = btf__type_by_id(btf, btf_params(t)[0].type); 1109 | while (t && btf_is_mod(t)) 1110 | t = btf__type_by_id(btf, t->type); 1111 | if (!t || !btf_is_ptr(t)) 1112 | goto skip_freplace_fixup; 1113 | t = btf__type_by_id(btf, t->type); 1114 | while (t && btf_is_mod(t)) 1115 | t = btf__type_by_id(btf, t->type); 1116 | if (!t) 1117 | goto skip_freplace_fixup; 1118 | 1119 | ctx_name = btf__name_by_offset(btf, t->name_off); 1120 | 1121 | if (guess_prog_type_by_ctx_name(ctx_name, &prog_type, &attach_type) == 0) { 1122 | bpf_program__set_type(prog, prog_type); 1123 | bpf_program__set_expected_attach_type(prog, attach_type); 1124 | 1125 | if (!env.quiet) { 1126 | printf("Using guessed program type '%s' for %s/%s...\n", 1127 | libbpf_bpf_prog_type_str(prog_type), 1128 | filename, prog_name); 1129 | } 1130 | } else { 1131 | if (!env.quiet) { 1132 | printf("Failed to guess program type for freplace program with context type name '%s' for %s/%s. Consider using canonical type names to help veristat...\n", 1133 | ctx_name, filename, prog_name); 1134 | } 1135 | } 1136 | } 1137 | skip_freplace_fixup: 1138 | return; 1139 | } 1140 | 1141 | static int max_verifier_log_size(void) 1142 | { 1143 | const int SMALL_LOG_SIZE = UINT_MAX >> 8; 1144 | const int BIG_LOG_SIZE = UINT_MAX >> 2; 1145 | struct bpf_insn insns[] = { 1146 | { .code = BPF_ALU | BPF_MOV | BPF_X, .dst_reg = BPF_REG_0, }, 1147 | { .code = BPF_JMP | BPF_EXIT, }, 1148 | }; 1149 | LIBBPF_OPTS(bpf_prog_load_opts, opts, 1150 | .log_size = BIG_LOG_SIZE, 1151 | .log_buf = (void *)-1, 1152 | .log_level = 4 1153 | ); 1154 | int ret, insn_cnt = ARRAY_SIZE(insns); 1155 | static int log_size; 1156 | 1157 | if (log_size != 0) 1158 | return log_size; 1159 | 1160 | ret = bpf_prog_load(BPF_PROG_TYPE_TRACEPOINT, NULL, "GPL", insns, insn_cnt, &opts); 1161 | 1162 | if (ret == -EFAULT) 1163 | log_size = BIG_LOG_SIZE; 1164 | else /* ret == -EINVAL, big log size is not supported by the verifier */ 1165 | log_size = SMALL_LOG_SIZE; 1166 | 1167 | return log_size; 1168 | } 1169 | 1170 | static int process_prog(const char *filename, struct bpf_object *obj, struct bpf_program *prog) 1171 | { 1172 | const char *base_filename = basename(strdupa(filename)); 1173 | const char *prog_name = bpf_program__name(prog); 1174 | char *buf; 1175 | int buf_sz, log_level; 1176 | struct verif_stats *stats; 1177 | struct bpf_prog_info info; 1178 | __u32 info_len = sizeof(info); 1179 | int err = 0; 1180 | void *tmp; 1181 | int fd; 1182 | 1183 | if (!should_process_file_prog(base_filename, bpf_program__name(prog))) { 1184 | env.progs_skipped++; 1185 | return 0; 1186 | } 1187 | 1188 | tmp = realloc(env.prog_stats, (env.prog_stat_cnt + 1) * sizeof(*env.prog_stats)); 1189 | if (!tmp) 1190 | return -ENOMEM; 1191 | env.prog_stats = tmp; 1192 | stats = &env.prog_stats[env.prog_stat_cnt++]; 1193 | memset(stats, 0, sizeof(*stats)); 1194 | 1195 | if (env.verbose || env.top_src_lines > 0) { 1196 | buf_sz = env.log_size ? env.log_size : max_verifier_log_size(); 1197 | buf = malloc(buf_sz); 1198 | if (!buf) 1199 | return -ENOMEM; 1200 | /* ensure we always request stats */ 1201 | log_level = env.log_level | 4 | (env.log_fixed ? 8 : 0); 1202 | /* --top-src-lines needs verifier log */ 1203 | if (env.top_src_lines > 0 && env.log_level == 0) 1204 | log_level |= 2; 1205 | } else { 1206 | buf = verif_log_buf; 1207 | buf_sz = sizeof(verif_log_buf); 1208 | /* request only verifier stats */ 1209 | log_level = 4 | (env.log_fixed ? 8 : 0); 1210 | } 1211 | verif_log_buf[0] = '\0'; 1212 | 1213 | bpf_program__set_log_buf(prog, buf, buf_sz); 1214 | bpf_program__set_log_level(prog, log_level); 1215 | 1216 | /* increase chances of successful BPF object loading */ 1217 | fixup_obj(obj, prog, base_filename); 1218 | 1219 | if (env.force_checkpoints) 1220 | bpf_program__set_flags(prog, bpf_program__flags(prog) | BPF_F_TEST_STATE_FREQ); 1221 | if (env.force_reg_invariants) 1222 | bpf_program__set_flags(prog, bpf_program__flags(prog) | BPF_F_TEST_REG_INVARIANTS); 1223 | 1224 | err = bpf_object__load(obj); 1225 | env.progs_processed++; 1226 | 1227 | stats->file_name = strdup(base_filename); 1228 | stats->prog_name = strdup(bpf_program__name(prog)); 1229 | stats->stats[VERDICT] = err == 0; /* 1 - success, 0 - failure */ 1230 | stats->stats[SIZE] = bpf_program__insn_cnt(prog); 1231 | stats->stats[PROG_TYPE] = bpf_program__type(prog); 1232 | stats->stats[ATTACH_TYPE] = bpf_program__expected_attach_type(prog); 1233 | 1234 | memset(&info, 0, info_len); 1235 | fd = bpf_program__fd(prog); 1236 | if (fd > 0 && bpf_prog_get_info_by_fd(fd, &info, &info_len) == 0) 1237 | stats->stats[JITED_SIZE] = info.jited_prog_len; 1238 | 1239 | parse_verif_log(buf, buf_sz, stats); 1240 | 1241 | if (env.verbose) { 1242 | printf("PROCESSING %s/%s, DURATION US: %ld, VERDICT: %s, VERIFIER LOG:\n%s\n", 1243 | filename, prog_name, stats->stats[DURATION], 1244 | err ? "failure" : "success", buf); 1245 | } 1246 | if (env.top_src_lines > 0) 1247 | print_top_src_lines(buf, buf_sz, stats->prog_name); 1248 | 1249 | if (verif_log_buf != buf) 1250 | free(buf); 1251 | 1252 | return 0; 1253 | }; 1254 | 1255 | static int process_obj(const char *filename) 1256 | { 1257 | const char *base_filename = basename(strdupa(filename)); 1258 | struct bpf_object *obj = NULL, *tobj; 1259 | struct bpf_program *prog, *tprog, *lprog; 1260 | libbpf_print_fn_t old_libbpf_print_fn; 1261 | LIBBPF_OPTS(bpf_object_open_opts, opts); 1262 | int err = 0, prog_cnt = 0; 1263 | 1264 | if (!should_process_file_prog(base_filename, NULL)) { 1265 | if (env.verbose) 1266 | printf("Skipping '%s' due to filters...\n", filename); 1267 | env.files_skipped++; 1268 | return 0; 1269 | } 1270 | if (!is_bpf_obj_file(filename)) { 1271 | if (env.verbose) 1272 | printf("Skipping '%s' as it's not a BPF object file...\n", filename); 1273 | env.files_skipped++; 1274 | return 0; 1275 | } 1276 | 1277 | if (!env.quiet && env.out_fmt == RESFMT_TABLE) 1278 | printf("Processing '%s'...\n", base_filename); 1279 | 1280 | old_libbpf_print_fn = libbpf_set_print(libbpf_print_fn); 1281 | obj = bpf_object__open_file(filename, &opts); 1282 | if (!obj) { 1283 | /* if libbpf can't open BPF object file, it could be because 1284 | * that BPF object file is incomplete and has to be statically 1285 | * linked into a final BPF object file; instead of bailing 1286 | * out, report it into stderr, mark it as skipped, and 1287 | * proceed 1288 | */ 1289 | fprintf(stderr, "Failed to open '%s': %d\n", filename, -errno); 1290 | env.files_skipped++; 1291 | err = 0; 1292 | goto cleanup; 1293 | } 1294 | 1295 | env.files_processed++; 1296 | 1297 | bpf_object__for_each_program(prog, obj) { 1298 | prog_cnt++; 1299 | } 1300 | 1301 | if (prog_cnt == 1) { 1302 | prog = bpf_object__next_program(obj, NULL); 1303 | bpf_program__set_autoload(prog, true); 1304 | process_prog(filename, obj, prog); 1305 | goto cleanup; 1306 | } 1307 | 1308 | bpf_object__for_each_program(prog, obj) { 1309 | const char *prog_name = bpf_program__name(prog); 1310 | 1311 | tobj = bpf_object__open_file(filename, &opts); 1312 | if (!tobj) { 1313 | err = -errno; 1314 | fprintf(stderr, "Failed to open '%s': %d\n", filename, err); 1315 | goto cleanup; 1316 | } 1317 | 1318 | lprog = NULL; 1319 | bpf_object__for_each_program(tprog, tobj) { 1320 | const char *tprog_name = bpf_program__name(tprog); 1321 | 1322 | if (strcmp(prog_name, tprog_name) == 0) { 1323 | bpf_program__set_autoload(tprog, true); 1324 | lprog = tprog; 1325 | } else { 1326 | bpf_program__set_autoload(tprog, false); 1327 | } 1328 | } 1329 | 1330 | process_prog(filename, tobj, lprog); 1331 | bpf_object__close(tobj); 1332 | } 1333 | 1334 | cleanup: 1335 | bpf_object__close(obj); 1336 | libbpf_set_print(old_libbpf_print_fn); 1337 | return err; 1338 | } 1339 | 1340 | static int cmp_stat(const struct verif_stats *s1, const struct verif_stats *s2, 1341 | enum stat_id id, bool asc, bool abs) 1342 | { 1343 | int cmp = 0; 1344 | 1345 | switch (id) { 1346 | case FILE_NAME: 1347 | cmp = strcmp(s1->file_name, s2->file_name); 1348 | break; 1349 | case PROG_NAME: 1350 | cmp = strcmp(s1->prog_name, s2->prog_name); 1351 | break; 1352 | case ATTACH_TYPE: 1353 | case PROG_TYPE: 1354 | case SIZE: 1355 | case JITED_SIZE: 1356 | case STACK: 1357 | case VERDICT: 1358 | case DURATION: 1359 | case TOTAL_INSNS: 1360 | case TOTAL_STATES: 1361 | case PEAK_STATES: 1362 | case MAX_STATES_PER_INSN: 1363 | case MARK_READ_MAX_LEN: { 1364 | long v1 = s1->stats[id]; 1365 | long v2 = s2->stats[id]; 1366 | 1367 | if (abs) { 1368 | v1 = v1 < 0 ? -v1 : v1; 1369 | v2 = v2 < 0 ? -v2 : v2; 1370 | } 1371 | 1372 | if (v1 != v2) 1373 | cmp = v1 < v2 ? -1 : 1; 1374 | break; 1375 | } 1376 | default: 1377 | fprintf(stderr, "Unrecognized stat #%d\n", id); 1378 | exit(1); 1379 | } 1380 | 1381 | return asc ? cmp : -cmp; 1382 | } 1383 | 1384 | static int cmp_prog_stats(const void *v1, const void *v2) 1385 | { 1386 | const struct verif_stats *s1 = v1, *s2 = v2; 1387 | int i, cmp; 1388 | 1389 | for (i = 0; i < env.sort_spec.spec_cnt; i++) { 1390 | cmp = cmp_stat(s1, s2, env.sort_spec.ids[i], 1391 | env.sort_spec.asc[i], env.sort_spec.abs[i]); 1392 | if (cmp != 0) 1393 | return cmp; 1394 | } 1395 | 1396 | /* always disambiguate with file+prog, which are unique */ 1397 | cmp = strcmp(s1->file_name, s2->file_name); 1398 | if (cmp != 0) 1399 | return cmp; 1400 | return strcmp(s1->prog_name, s2->prog_name); 1401 | } 1402 | 1403 | static void fetch_join_stat_value(const struct verif_stats_join *s, 1404 | enum stat_id id, enum stat_variant var, 1405 | const char **str_val, 1406 | double *num_val) 1407 | { 1408 | long v1, v2; 1409 | 1410 | if (id == FILE_NAME) { 1411 | *str_val = s->file_name; 1412 | return; 1413 | } 1414 | if (id == PROG_NAME) { 1415 | *str_val = s->prog_name; 1416 | return; 1417 | } 1418 | 1419 | v1 = s->stats_a ? s->stats_a->stats[id] : 0; 1420 | v2 = s->stats_b ? s->stats_b->stats[id] : 0; 1421 | 1422 | switch (var) { 1423 | case VARIANT_A: 1424 | if (!s->stats_a) 1425 | *num_val = -DBL_MAX; 1426 | else 1427 | *num_val = s->stats_a->stats[id]; 1428 | return; 1429 | case VARIANT_B: 1430 | if (!s->stats_b) 1431 | *num_val = -DBL_MAX; 1432 | else 1433 | *num_val = s->stats_b->stats[id]; 1434 | return; 1435 | case VARIANT_DIFF: 1436 | if (!s->stats_a || !s->stats_b) 1437 | *num_val = -DBL_MAX; 1438 | else if (id == VERDICT) 1439 | *num_val = v1 == v2 ? 1.0 /* MATCH */ : 0.0 /* MISMATCH */; 1440 | else 1441 | *num_val = (double)(v2 - v1); 1442 | return; 1443 | case VARIANT_PCT: 1444 | if (!s->stats_a || !s->stats_b) { 1445 | *num_val = -DBL_MAX; 1446 | } else if (v1 == 0) { 1447 | if (v1 == v2) 1448 | *num_val = 0.0; 1449 | else 1450 | *num_val = v2 < v1 ? -100.0 : 100.0; 1451 | } else { 1452 | *num_val = (v2 - v1) * 100.0 / v1; 1453 | } 1454 | return; 1455 | } 1456 | } 1457 | 1458 | static int cmp_join_stat(const struct verif_stats_join *s1, 1459 | const struct verif_stats_join *s2, 1460 | enum stat_id id, enum stat_variant var, 1461 | bool asc, bool abs) 1462 | { 1463 | const char *str1 = NULL, *str2 = NULL; 1464 | double v1 = 0.0, v2 = 0.0; 1465 | int cmp = 0; 1466 | 1467 | fetch_join_stat_value(s1, id, var, &str1, &v1); 1468 | fetch_join_stat_value(s2, id, var, &str2, &v2); 1469 | 1470 | if (abs) { 1471 | v1 = fabs(v1); 1472 | v2 = fabs(v2); 1473 | } 1474 | 1475 | if (str1) 1476 | cmp = strcmp(str1, str2); 1477 | else if (v1 != v2) 1478 | cmp = v1 < v2 ? -1 : 1; 1479 | 1480 | return asc ? cmp : -cmp; 1481 | } 1482 | 1483 | static int cmp_join_stats(const void *v1, const void *v2) 1484 | { 1485 | const struct verif_stats_join *s1 = v1, *s2 = v2; 1486 | int i, cmp; 1487 | 1488 | for (i = 0; i < env.sort_spec.spec_cnt; i++) { 1489 | cmp = cmp_join_stat(s1, s2, 1490 | env.sort_spec.ids[i], 1491 | env.sort_spec.variants[i], 1492 | env.sort_spec.asc[i], 1493 | env.sort_spec.abs[i]); 1494 | if (cmp != 0) 1495 | return cmp; 1496 | } 1497 | 1498 | /* always disambiguate with file+prog, which are unique */ 1499 | cmp = strcmp(s1->file_name, s2->file_name); 1500 | if (cmp != 0) 1501 | return cmp; 1502 | return strcmp(s1->prog_name, s2->prog_name); 1503 | } 1504 | 1505 | #define HEADER_CHAR '-' 1506 | #define COLUMN_SEP " " 1507 | 1508 | static void output_header_underlines(void) 1509 | { 1510 | int i, j, len; 1511 | 1512 | for (i = 0; i < env.output_spec.spec_cnt; i++) { 1513 | len = env.output_spec.lens[i]; 1514 | 1515 | printf("%s", i == 0 ? "" : COLUMN_SEP); 1516 | for (j = 0; j < len; j++) 1517 | printf("%c", HEADER_CHAR); 1518 | } 1519 | printf("\n"); 1520 | } 1521 | 1522 | static void output_headers(enum resfmt fmt) 1523 | { 1524 | const char *fmt_str; 1525 | int i, len; 1526 | 1527 | for (i = 0; i < env.output_spec.spec_cnt; i++) { 1528 | int id = env.output_spec.ids[i]; 1529 | int *max_len = &env.output_spec.lens[i]; 1530 | 1531 | switch (fmt) { 1532 | case RESFMT_TABLE_CALCLEN: 1533 | len = snprintf(NULL, 0, "%s", stat_defs[id].header); 1534 | if (len > *max_len) 1535 | *max_len = len; 1536 | break; 1537 | case RESFMT_TABLE: 1538 | fmt_str = stat_defs[id].left_aligned ? "%s%-*s" : "%s%*s"; 1539 | printf(fmt_str, i == 0 ? "" : COLUMN_SEP, *max_len, stat_defs[id].header); 1540 | if (i == env.output_spec.spec_cnt - 1) 1541 | printf("\n"); 1542 | break; 1543 | case RESFMT_CSV: 1544 | printf("%s%s", i == 0 ? "" : ",", stat_defs[id].names[0]); 1545 | if (i == env.output_spec.spec_cnt - 1) 1546 | printf("\n"); 1547 | break; 1548 | } 1549 | } 1550 | 1551 | if (fmt == RESFMT_TABLE) 1552 | output_header_underlines(); 1553 | } 1554 | 1555 | static void prepare_value(const struct verif_stats *s, enum stat_id id, 1556 | const char **str, long *val) 1557 | { 1558 | switch (id) { 1559 | case FILE_NAME: 1560 | *str = s ? s->file_name : "N/A"; 1561 | break; 1562 | case PROG_NAME: 1563 | *str = s ? s->prog_name : "N/A"; 1564 | break; 1565 | case VERDICT: 1566 | if (!s) 1567 | *str = "N/A"; 1568 | else 1569 | *str = s->stats[VERDICT] ? "success" : "failure"; 1570 | break; 1571 | case ATTACH_TYPE: 1572 | if (!s) 1573 | *str = "N/A"; 1574 | else 1575 | *str = libbpf_bpf_attach_type_str(s->stats[ATTACH_TYPE]) ?: "N/A"; 1576 | break; 1577 | case PROG_TYPE: 1578 | if (!s) 1579 | *str = "N/A"; 1580 | else 1581 | *str = libbpf_bpf_prog_type_str(s->stats[PROG_TYPE]) ?: "N/A"; 1582 | break; 1583 | case DURATION: 1584 | case TOTAL_INSNS: 1585 | case TOTAL_STATES: 1586 | case PEAK_STATES: 1587 | case MAX_STATES_PER_INSN: 1588 | case MARK_READ_MAX_LEN: 1589 | case STACK: 1590 | case SIZE: 1591 | case JITED_SIZE: 1592 | *val = s ? s->stats[id] : 0; 1593 | break; 1594 | default: 1595 | fprintf(stderr, "Unrecognized stat #%d\n", id); 1596 | exit(1); 1597 | } 1598 | } 1599 | 1600 | static void output_stats(const struct verif_stats *s, enum resfmt fmt, bool last) 1601 | { 1602 | int i; 1603 | 1604 | for (i = 0; i < env.output_spec.spec_cnt; i++) { 1605 | int id = env.output_spec.ids[i]; 1606 | int *max_len = &env.output_spec.lens[i], len; 1607 | const char *str = NULL; 1608 | long val = 0; 1609 | 1610 | prepare_value(s, id, &str, &val); 1611 | 1612 | switch (fmt) { 1613 | case RESFMT_TABLE_CALCLEN: 1614 | if (str) 1615 | len = snprintf(NULL, 0, "%s", str); 1616 | else 1617 | len = snprintf(NULL, 0, "%ld", val); 1618 | if (len > *max_len) 1619 | *max_len = len; 1620 | break; 1621 | case RESFMT_TABLE: 1622 | if (str) 1623 | printf("%s%-*s", i == 0 ? "" : COLUMN_SEP, *max_len, str); 1624 | else 1625 | printf("%s%*ld", i == 0 ? "" : COLUMN_SEP, *max_len, val); 1626 | if (i == env.output_spec.spec_cnt - 1) 1627 | printf("\n"); 1628 | break; 1629 | case RESFMT_CSV: 1630 | if (str) 1631 | printf("%s%s", i == 0 ? "" : ",", str); 1632 | else 1633 | printf("%s%ld", i == 0 ? "" : ",", val); 1634 | if (i == env.output_spec.spec_cnt - 1) 1635 | printf("\n"); 1636 | break; 1637 | } 1638 | } 1639 | 1640 | if (last && fmt == RESFMT_TABLE) { 1641 | output_header_underlines(); 1642 | printf("Done. Processed %d files, %d programs. Skipped %d files, %d programs.\n", 1643 | env.files_processed, env.files_skipped, env.progs_processed, env.progs_skipped); 1644 | } 1645 | } 1646 | 1647 | static int parse_stat_value(const char *str, enum stat_id id, struct verif_stats *st) 1648 | { 1649 | switch (id) { 1650 | case FILE_NAME: 1651 | st->file_name = strdup(str); 1652 | if (!st->file_name) 1653 | return -ENOMEM; 1654 | break; 1655 | case PROG_NAME: 1656 | st->prog_name = strdup(str); 1657 | if (!st->prog_name) 1658 | return -ENOMEM; 1659 | break; 1660 | case VERDICT: 1661 | if (strcmp(str, "success") == 0) { 1662 | st->stats[VERDICT] = true; 1663 | } else if (strcmp(str, "failure") == 0) { 1664 | st->stats[VERDICT] = false; 1665 | } else { 1666 | fprintf(stderr, "Unrecognized verification verdict '%s'\n", str); 1667 | return -EINVAL; 1668 | } 1669 | break; 1670 | case DURATION: 1671 | case TOTAL_INSNS: 1672 | case TOTAL_STATES: 1673 | case PEAK_STATES: 1674 | case MAX_STATES_PER_INSN: 1675 | case MARK_READ_MAX_LEN: 1676 | case SIZE: 1677 | case JITED_SIZE: 1678 | case STACK: { 1679 | long val; 1680 | int err, n; 1681 | 1682 | if (sscanf(str, "%ld %n", &val, &n) != 1 || n != strlen(str)) { 1683 | err = -errno; 1684 | fprintf(stderr, "Failed to parse '%s' as integer\n", str); 1685 | return err; 1686 | } 1687 | 1688 | st->stats[id] = val; 1689 | break; 1690 | } 1691 | case PROG_TYPE: { 1692 | enum bpf_prog_type prog_type = 0; 1693 | const char *type; 1694 | 1695 | while ((type = libbpf_bpf_prog_type_str(prog_type))) { 1696 | if (strcmp(type, str) == 0) { 1697 | st->stats[id] = prog_type; 1698 | break; 1699 | } 1700 | prog_type++; 1701 | } 1702 | 1703 | if (!type) { 1704 | fprintf(stderr, "Unrecognized prog type %s\n", str); 1705 | return -EINVAL; 1706 | } 1707 | break; 1708 | } 1709 | case ATTACH_TYPE: { 1710 | enum bpf_attach_type attach_type = 0; 1711 | const char *type; 1712 | 1713 | while ((type = libbpf_bpf_attach_type_str(attach_type))) { 1714 | if (strcmp(type, str) == 0) { 1715 | st->stats[id] = attach_type; 1716 | break; 1717 | } 1718 | attach_type++; 1719 | } 1720 | 1721 | if (!type) { 1722 | fprintf(stderr, "Unrecognized attach type %s\n", str); 1723 | return -EINVAL; 1724 | } 1725 | break; 1726 | } 1727 | default: 1728 | fprintf(stderr, "Unrecognized stat #%d\n", id); 1729 | return -EINVAL; 1730 | } 1731 | return 0; 1732 | } 1733 | 1734 | static int parse_stats_csv(const char *filename, struct stat_specs *specs, 1735 | struct verif_stats **statsp, int *stat_cntp) 1736 | { 1737 | char line[4096]; 1738 | FILE *f; 1739 | int err = 0; 1740 | bool header = true; 1741 | 1742 | f = fopen(filename, "r"); 1743 | if (!f) { 1744 | err = -errno; 1745 | fprintf(stderr, "Failed to open '%s': %d\n", filename, err); 1746 | return err; 1747 | } 1748 | 1749 | *stat_cntp = 0; 1750 | 1751 | while (fgets(line, sizeof(line), f)) { 1752 | char *input = line, *state = NULL, *next; 1753 | struct verif_stats *st = NULL; 1754 | int col = 0, cnt = 0; 1755 | 1756 | if (!header) { 1757 | void *tmp; 1758 | 1759 | tmp = realloc(*statsp, (*stat_cntp + 1) * sizeof(**statsp)); 1760 | if (!tmp) { 1761 | err = -ENOMEM; 1762 | goto cleanup; 1763 | } 1764 | *statsp = tmp; 1765 | 1766 | st = &(*statsp)[*stat_cntp]; 1767 | memset(st, 0, sizeof(*st)); 1768 | 1769 | *stat_cntp += 1; 1770 | } 1771 | 1772 | while ((next = strtok_r(cnt++ ? NULL : input, ",\n", &state))) { 1773 | if (header) { 1774 | /* for the first line, set up spec stats */ 1775 | err = parse_stat(next, specs); 1776 | if (err) 1777 | goto cleanup; 1778 | continue; 1779 | } 1780 | 1781 | /* for all other lines, parse values based on spec */ 1782 | if (col >= specs->spec_cnt) { 1783 | fprintf(stderr, "Found extraneous column #%d in row #%d of '%s'\n", 1784 | col, *stat_cntp, filename); 1785 | err = -EINVAL; 1786 | goto cleanup; 1787 | } 1788 | err = parse_stat_value(next, specs->ids[col], st); 1789 | if (err) 1790 | goto cleanup; 1791 | col++; 1792 | } 1793 | 1794 | if (header) { 1795 | header = false; 1796 | continue; 1797 | } 1798 | 1799 | if (col < specs->spec_cnt) { 1800 | fprintf(stderr, "Not enough columns in row #%d in '%s'\n", 1801 | *stat_cntp, filename); 1802 | err = -EINVAL; 1803 | goto cleanup; 1804 | } 1805 | 1806 | if (!st->file_name || !st->prog_name) { 1807 | fprintf(stderr, "Row #%d in '%s' is missing file and/or program name\n", 1808 | *stat_cntp, filename); 1809 | err = -EINVAL; 1810 | goto cleanup; 1811 | } 1812 | 1813 | /* in comparison mode we can only check filters after we 1814 | * parsed entire line; if row should be ignored we pretend we 1815 | * never parsed it 1816 | */ 1817 | if (!should_process_file_prog(st->file_name, st->prog_name)) { 1818 | free(st->file_name); 1819 | free(st->prog_name); 1820 | *stat_cntp -= 1; 1821 | } 1822 | } 1823 | 1824 | if (!feof(f)) { 1825 | err = -errno; 1826 | fprintf(stderr, "Failed I/O for '%s': %d\n", filename, err); 1827 | } 1828 | 1829 | cleanup: 1830 | fclose(f); 1831 | return err; 1832 | } 1833 | 1834 | /* empty/zero stats for mismatched rows */ 1835 | static const struct verif_stats fallback_stats = { .file_name = "", .prog_name = "" }; 1836 | 1837 | static bool is_key_stat(enum stat_id id) 1838 | { 1839 | return id == FILE_NAME || id == PROG_NAME; 1840 | } 1841 | 1842 | static void output_comp_header_underlines(void) 1843 | { 1844 | int i, j, k; 1845 | 1846 | for (i = 0; i < env.output_spec.spec_cnt; i++) { 1847 | int id = env.output_spec.ids[i]; 1848 | int max_j = is_key_stat(id) ? 1 : 3; 1849 | 1850 | for (j = 0; j < max_j; j++) { 1851 | int len = env.output_spec.lens[3 * i + j]; 1852 | 1853 | printf("%s", i + j == 0 ? "" : COLUMN_SEP); 1854 | 1855 | for (k = 0; k < len; k++) 1856 | printf("%c", HEADER_CHAR); 1857 | } 1858 | } 1859 | printf("\n"); 1860 | } 1861 | 1862 | static void output_comp_headers(enum resfmt fmt) 1863 | { 1864 | static const char *table_sfxs[3] = {" (A)", " (B)", " (DIFF)"}; 1865 | static const char *name_sfxs[3] = {"_base", "_comp", "_diff"}; 1866 | int i, j, len; 1867 | 1868 | for (i = 0; i < env.output_spec.spec_cnt; i++) { 1869 | int id = env.output_spec.ids[i]; 1870 | /* key stats don't have A/B/DIFF columns, they are common for both data sets */ 1871 | int max_j = is_key_stat(id) ? 1 : 3; 1872 | 1873 | for (j = 0; j < max_j; j++) { 1874 | int *max_len = &env.output_spec.lens[3 * i + j]; 1875 | bool last = (i == env.output_spec.spec_cnt - 1) && (j == max_j - 1); 1876 | const char *sfx; 1877 | 1878 | switch (fmt) { 1879 | case RESFMT_TABLE_CALCLEN: 1880 | sfx = is_key_stat(id) ? "" : table_sfxs[j]; 1881 | len = snprintf(NULL, 0, "%s%s", stat_defs[id].header, sfx); 1882 | if (len > *max_len) 1883 | *max_len = len; 1884 | break; 1885 | case RESFMT_TABLE: 1886 | sfx = is_key_stat(id) ? "" : table_sfxs[j]; 1887 | printf("%s%-*s%s", i + j == 0 ? "" : COLUMN_SEP, 1888 | *max_len - (int)strlen(sfx), stat_defs[id].header, sfx); 1889 | if (last) 1890 | printf("\n"); 1891 | break; 1892 | case RESFMT_CSV: 1893 | sfx = is_key_stat(id) ? "" : name_sfxs[j]; 1894 | printf("%s%s%s", i + j == 0 ? "" : ",", stat_defs[id].names[0], sfx); 1895 | if (last) 1896 | printf("\n"); 1897 | break; 1898 | } 1899 | } 1900 | } 1901 | 1902 | if (fmt == RESFMT_TABLE) 1903 | output_comp_header_underlines(); 1904 | } 1905 | 1906 | static void output_comp_stats(const struct verif_stats_join *join_stats, 1907 | enum resfmt fmt, bool last) 1908 | { 1909 | const struct verif_stats *base = join_stats->stats_a; 1910 | const struct verif_stats *comp = join_stats->stats_b; 1911 | char base_buf[1024] = {}, comp_buf[1024] = {}, diff_buf[1024] = {}; 1912 | int i; 1913 | 1914 | for (i = 0; i < env.output_spec.spec_cnt; i++) { 1915 | int id = env.output_spec.ids[i], len; 1916 | int *max_len_base = &env.output_spec.lens[3 * i + 0]; 1917 | int *max_len_comp = &env.output_spec.lens[3 * i + 1]; 1918 | int *max_len_diff = &env.output_spec.lens[3 * i + 2]; 1919 | const char *base_str = NULL, *comp_str = NULL; 1920 | long base_val = 0, comp_val = 0, diff_val = 0; 1921 | 1922 | prepare_value(base, id, &base_str, &base_val); 1923 | prepare_value(comp, id, &comp_str, &comp_val); 1924 | 1925 | /* normalize all the outputs to be in string buffers for simplicity */ 1926 | if (is_key_stat(id)) { 1927 | /* key stats (file and program name) are always strings */ 1928 | if (base) 1929 | snprintf(base_buf, sizeof(base_buf), "%s", base_str); 1930 | else 1931 | snprintf(base_buf, sizeof(base_buf), "%s", comp_str); 1932 | } else if (base_str) { 1933 | snprintf(base_buf, sizeof(base_buf), "%s", base_str); 1934 | snprintf(comp_buf, sizeof(comp_buf), "%s", comp_str); 1935 | if (!base || !comp) 1936 | snprintf(diff_buf, sizeof(diff_buf), "%s", "N/A"); 1937 | else if (strcmp(base_str, comp_str) == 0) 1938 | snprintf(diff_buf, sizeof(diff_buf), "%s", "MATCH"); 1939 | else 1940 | snprintf(diff_buf, sizeof(diff_buf), "%s", "MISMATCH"); 1941 | } else { 1942 | double p = 0.0; 1943 | 1944 | if (base) 1945 | snprintf(base_buf, sizeof(base_buf), "%ld", base_val); 1946 | else 1947 | snprintf(base_buf, sizeof(base_buf), "%s", "N/A"); 1948 | if (comp) 1949 | snprintf(comp_buf, sizeof(comp_buf), "%ld", comp_val); 1950 | else 1951 | snprintf(comp_buf, sizeof(comp_buf), "%s", "N/A"); 1952 | 1953 | diff_val = comp_val - base_val; 1954 | if (!base || !comp) { 1955 | snprintf(diff_buf, sizeof(diff_buf), "%s", "N/A"); 1956 | } else { 1957 | if (base_val == 0) { 1958 | if (comp_val == base_val) 1959 | p = 0.0; /* avoid +0 (+100%) case */ 1960 | else 1961 | p = comp_val < base_val ? -100.0 : 100.0; 1962 | } else { 1963 | p = diff_val * 100.0 / base_val; 1964 | } 1965 | snprintf(diff_buf, sizeof(diff_buf), "%+ld (%+.2lf%%)", diff_val, p); 1966 | } 1967 | } 1968 | 1969 | switch (fmt) { 1970 | case RESFMT_TABLE_CALCLEN: 1971 | len = strlen(base_buf); 1972 | if (len > *max_len_base) 1973 | *max_len_base = len; 1974 | if (!is_key_stat(id)) { 1975 | len = strlen(comp_buf); 1976 | if (len > *max_len_comp) 1977 | *max_len_comp = len; 1978 | len = strlen(diff_buf); 1979 | if (len > *max_len_diff) 1980 | *max_len_diff = len; 1981 | } 1982 | break; 1983 | case RESFMT_TABLE: { 1984 | /* string outputs are left-aligned, number outputs are right-aligned */ 1985 | const char *fmt = base_str ? "%s%-*s" : "%s%*s"; 1986 | 1987 | printf(fmt, i == 0 ? "" : COLUMN_SEP, *max_len_base, base_buf); 1988 | if (!is_key_stat(id)) { 1989 | printf(fmt, COLUMN_SEP, *max_len_comp, comp_buf); 1990 | printf(fmt, COLUMN_SEP, *max_len_diff, diff_buf); 1991 | } 1992 | if (i == env.output_spec.spec_cnt - 1) 1993 | printf("\n"); 1994 | break; 1995 | } 1996 | case RESFMT_CSV: 1997 | printf("%s%s", i == 0 ? "" : ",", base_buf); 1998 | if (!is_key_stat(id)) { 1999 | printf("%s%s", i == 0 ? "" : ",", comp_buf); 2000 | printf("%s%s", i == 0 ? "" : ",", diff_buf); 2001 | } 2002 | if (i == env.output_spec.spec_cnt - 1) 2003 | printf("\n"); 2004 | break; 2005 | } 2006 | } 2007 | 2008 | if (last && fmt == RESFMT_TABLE) 2009 | output_comp_header_underlines(); 2010 | } 2011 | 2012 | static int cmp_stats_key(const struct verif_stats *base, const struct verif_stats *comp) 2013 | { 2014 | int r; 2015 | 2016 | r = strcmp(base->file_name, comp->file_name); 2017 | if (r != 0) 2018 | return r; 2019 | return strcmp(base->prog_name, comp->prog_name); 2020 | } 2021 | 2022 | static bool is_join_stat_filter_matched(struct filter *f, const struct verif_stats_join *stats) 2023 | { 2024 | static const double eps = 1e-9; 2025 | const char *str = NULL; 2026 | double value = 0.0; 2027 | 2028 | fetch_join_stat_value(stats, f->stat_id, f->stat_var, &str, &value); 2029 | 2030 | if (f->abs) 2031 | value = fabs(value); 2032 | 2033 | switch (f->op) { 2034 | case OP_EQ: return value > f->value - eps && value < f->value + eps; 2035 | case OP_NEQ: return value < f->value - eps || value > f->value + eps; 2036 | case OP_LT: return value < f->value - eps; 2037 | case OP_LE: return value <= f->value + eps; 2038 | case OP_GT: return value > f->value + eps; 2039 | case OP_GE: return value >= f->value - eps; 2040 | } 2041 | 2042 | fprintf(stderr, "BUG: unknown filter op %d!\n", f->op); 2043 | return false; 2044 | } 2045 | 2046 | static bool should_output_join_stats(const struct verif_stats_join *stats) 2047 | { 2048 | struct filter *f; 2049 | int i, allow_cnt = 0; 2050 | 2051 | for (i = 0; i < env.deny_filter_cnt; i++) { 2052 | f = &env.deny_filters[i]; 2053 | if (f->kind != FILTER_STAT) 2054 | continue; 2055 | 2056 | if (is_join_stat_filter_matched(f, stats)) 2057 | return false; 2058 | } 2059 | 2060 | for (i = 0; i < env.allow_filter_cnt; i++) { 2061 | f = &env.allow_filters[i]; 2062 | if (f->kind != FILTER_STAT) 2063 | continue; 2064 | allow_cnt++; 2065 | 2066 | if (is_join_stat_filter_matched(f, stats)) 2067 | return true; 2068 | } 2069 | 2070 | /* if there are no stat allowed filters, pass everything through */ 2071 | return allow_cnt == 0; 2072 | } 2073 | 2074 | static int handle_comparison_mode(void) 2075 | { 2076 | struct stat_specs base_specs = {}, comp_specs = {}; 2077 | struct stat_specs tmp_sort_spec; 2078 | enum resfmt cur_fmt; 2079 | int err, i, j, last_idx, cnt; 2080 | 2081 | if (env.filename_cnt != 2) { 2082 | fprintf(stderr, "Comparison mode expects exactly two input CSV files!\n\n"); 2083 | argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 2084 | return -EINVAL; 2085 | } 2086 | 2087 | err = parse_stats_csv(env.filenames[0], &base_specs, 2088 | &env.baseline_stats, &env.baseline_stat_cnt); 2089 | if (err) { 2090 | fprintf(stderr, "Failed to parse stats from '%s': %d\n", env.filenames[0], err); 2091 | return err; 2092 | } 2093 | err = parse_stats_csv(env.filenames[1], &comp_specs, 2094 | &env.prog_stats, &env.prog_stat_cnt); 2095 | if (err) { 2096 | fprintf(stderr, "Failed to parse stats from '%s': %d\n", env.filenames[1], err); 2097 | return err; 2098 | } 2099 | 2100 | /* To keep it simple we validate that the set and order of stats in 2101 | * both CSVs are exactly the same. This can be lifted with a bit more 2102 | * pre-processing later. 2103 | */ 2104 | if (base_specs.spec_cnt != comp_specs.spec_cnt) { 2105 | fprintf(stderr, "Number of stats in '%s' and '%s' differs (%d != %d)!\n", 2106 | env.filenames[0], env.filenames[1], 2107 | base_specs.spec_cnt, comp_specs.spec_cnt); 2108 | return -EINVAL; 2109 | } 2110 | for (i = 0; i < base_specs.spec_cnt; i++) { 2111 | if (base_specs.ids[i] != comp_specs.ids[i]) { 2112 | fprintf(stderr, "Stats composition differs between '%s' and '%s' (%s != %s)!\n", 2113 | env.filenames[0], env.filenames[1], 2114 | stat_defs[base_specs.ids[i]].names[0], 2115 | stat_defs[comp_specs.ids[i]].names[0]); 2116 | return -EINVAL; 2117 | } 2118 | } 2119 | 2120 | /* Replace user-specified sorting spec with file+prog sorting rule to 2121 | * be able to join two datasets correctly. Once we are done, we will 2122 | * restore the original sort spec. 2123 | */ 2124 | tmp_sort_spec = env.sort_spec; 2125 | env.sort_spec = join_sort_spec; 2126 | qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats); 2127 | qsort(env.baseline_stats, env.baseline_stat_cnt, sizeof(*env.baseline_stats), cmp_prog_stats); 2128 | env.sort_spec = tmp_sort_spec; 2129 | 2130 | /* Join two datasets together. If baseline and comparison datasets 2131 | * have different subset of rows (we match by 'object + prog' as 2132 | * a unique key) then assume empty/missing/zero value for rows that 2133 | * are missing in the opposite data set. 2134 | */ 2135 | i = j = 0; 2136 | while (i < env.baseline_stat_cnt || j < env.prog_stat_cnt) { 2137 | const struct verif_stats *base, *comp; 2138 | struct verif_stats_join *join; 2139 | void *tmp; 2140 | int r; 2141 | 2142 | base = i < env.baseline_stat_cnt ? &env.baseline_stats[i] : &fallback_stats; 2143 | comp = j < env.prog_stat_cnt ? &env.prog_stats[j] : &fallback_stats; 2144 | 2145 | if (!base->file_name || !base->prog_name) { 2146 | fprintf(stderr, "Entry #%d in '%s' doesn't have file and/or program name specified!\n", 2147 | i, env.filenames[0]); 2148 | return -EINVAL; 2149 | } 2150 | if (!comp->file_name || !comp->prog_name) { 2151 | fprintf(stderr, "Entry #%d in '%s' doesn't have file and/or program name specified!\n", 2152 | j, env.filenames[1]); 2153 | return -EINVAL; 2154 | } 2155 | 2156 | tmp = realloc(env.join_stats, (env.join_stat_cnt + 1) * sizeof(*env.join_stats)); 2157 | if (!tmp) 2158 | return -ENOMEM; 2159 | env.join_stats = tmp; 2160 | 2161 | join = &env.join_stats[env.join_stat_cnt]; 2162 | memset(join, 0, sizeof(*join)); 2163 | 2164 | r = cmp_stats_key(base, comp); 2165 | if (r == 0) { 2166 | join->file_name = base->file_name; 2167 | join->prog_name = base->prog_name; 2168 | join->stats_a = base; 2169 | join->stats_b = comp; 2170 | i++; 2171 | j++; 2172 | } else if (base != &fallback_stats && (comp == &fallback_stats || r < 0)) { 2173 | join->file_name = base->file_name; 2174 | join->prog_name = base->prog_name; 2175 | join->stats_a = base; 2176 | join->stats_b = NULL; 2177 | i++; 2178 | } else if (comp != &fallback_stats && (base == &fallback_stats || r > 0)) { 2179 | join->file_name = comp->file_name; 2180 | join->prog_name = comp->prog_name; 2181 | join->stats_a = NULL; 2182 | join->stats_b = comp; 2183 | j++; 2184 | } else { 2185 | fprintf(stderr, "%s:%d: should never reach here i=%i, j=%i", 2186 | __FILE__, __LINE__, i, j); 2187 | return -EINVAL; 2188 | } 2189 | env.join_stat_cnt += 1; 2190 | } 2191 | 2192 | /* now sort joined results according to sort spec */ 2193 | qsort(env.join_stats, env.join_stat_cnt, sizeof(*env.join_stats), cmp_join_stats); 2194 | 2195 | /* for human-readable table output we need to do extra pass to 2196 | * calculate column widths, so we substitute current output format 2197 | * with RESFMT_TABLE_CALCLEN and later revert it back to RESFMT_TABLE 2198 | * and do everything again. 2199 | */ 2200 | if (env.out_fmt == RESFMT_TABLE) 2201 | cur_fmt = RESFMT_TABLE_CALCLEN; 2202 | else 2203 | cur_fmt = env.out_fmt; 2204 | 2205 | one_more_time: 2206 | output_comp_headers(cur_fmt); 2207 | 2208 | last_idx = -1; 2209 | cnt = 0; 2210 | for (i = 0; i < env.join_stat_cnt; i++) { 2211 | const struct verif_stats_join *join = &env.join_stats[i]; 2212 | 2213 | if (!should_output_join_stats(join)) 2214 | continue; 2215 | 2216 | if (env.top_n && cnt >= env.top_n) 2217 | break; 2218 | 2219 | if (cur_fmt == RESFMT_TABLE_CALCLEN) 2220 | last_idx = i; 2221 | 2222 | output_comp_stats(join, cur_fmt, i == last_idx); 2223 | 2224 | cnt++; 2225 | } 2226 | 2227 | if (cur_fmt == RESFMT_TABLE_CALCLEN) { 2228 | cur_fmt = RESFMT_TABLE; 2229 | goto one_more_time; /* ... this time with feeling */ 2230 | } 2231 | 2232 | return 0; 2233 | } 2234 | 2235 | static bool is_stat_filter_matched(struct filter *f, const struct verif_stats *stats) 2236 | { 2237 | long value = stats->stats[f->stat_id]; 2238 | 2239 | if (f->abs) 2240 | value = value < 0 ? -value : value; 2241 | 2242 | switch (f->op) { 2243 | case OP_EQ: return value == f->value; 2244 | case OP_NEQ: return value != f->value; 2245 | case OP_LT: return value < f->value; 2246 | case OP_LE: return value <= f->value; 2247 | case OP_GT: return value > f->value; 2248 | case OP_GE: return value >= f->value; 2249 | } 2250 | 2251 | fprintf(stderr, "BUG: unknown filter op %d!\n", f->op); 2252 | return false; 2253 | } 2254 | 2255 | static bool should_output_stats(const struct verif_stats *stats) 2256 | { 2257 | struct filter *f; 2258 | int i, allow_cnt = 0; 2259 | 2260 | for (i = 0; i < env.deny_filter_cnt; i++) { 2261 | f = &env.deny_filters[i]; 2262 | if (f->kind != FILTER_STAT) 2263 | continue; 2264 | 2265 | if (is_stat_filter_matched(f, stats)) 2266 | return false; 2267 | } 2268 | 2269 | for (i = 0; i < env.allow_filter_cnt; i++) { 2270 | f = &env.allow_filters[i]; 2271 | if (f->kind != FILTER_STAT) 2272 | continue; 2273 | allow_cnt++; 2274 | 2275 | if (is_stat_filter_matched(f, stats)) 2276 | return true; 2277 | } 2278 | 2279 | /* if there are no stat allowed filters, pass everything through */ 2280 | return allow_cnt == 0; 2281 | } 2282 | 2283 | static void output_prog_stats(void) 2284 | { 2285 | const struct verif_stats *stats; 2286 | int i, last_stat_idx = 0, cnt = 0; 2287 | 2288 | if (env.out_fmt == RESFMT_TABLE) { 2289 | /* calculate column widths */ 2290 | output_headers(RESFMT_TABLE_CALCLEN); 2291 | for (i = 0; i < env.prog_stat_cnt; i++) { 2292 | stats = &env.prog_stats[i]; 2293 | if (!should_output_stats(stats)) 2294 | continue; 2295 | output_stats(stats, RESFMT_TABLE_CALCLEN, false); 2296 | last_stat_idx = i; 2297 | } 2298 | } 2299 | 2300 | /* actually output the table */ 2301 | output_headers(env.out_fmt); 2302 | for (i = 0; i < env.prog_stat_cnt; i++) { 2303 | stats = &env.prog_stats[i]; 2304 | if (!should_output_stats(stats)) 2305 | continue; 2306 | if (env.top_n && cnt >= env.top_n) 2307 | break; 2308 | output_stats(stats, env.out_fmt, i == last_stat_idx); 2309 | cnt++; 2310 | } 2311 | } 2312 | 2313 | static int handle_verif_mode(void) 2314 | { 2315 | int i, err; 2316 | 2317 | if (env.filename_cnt == 0) { 2318 | fprintf(stderr, "Please provide path to BPF object file!\n\n"); 2319 | argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 2320 | return -EINVAL; 2321 | } 2322 | 2323 | for (i = 0; i < env.filename_cnt; i++) { 2324 | err = process_obj(env.filenames[i]); 2325 | if (err) { 2326 | fprintf(stderr, "Failed to process '%s': %d\n", env.filenames[i], err); 2327 | return err; 2328 | } 2329 | } 2330 | 2331 | qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats); 2332 | 2333 | output_prog_stats(); 2334 | 2335 | return 0; 2336 | } 2337 | 2338 | static int handle_replay_mode(void) 2339 | { 2340 | struct stat_specs specs = {}; 2341 | int err; 2342 | 2343 | if (env.filename_cnt != 1) { 2344 | fprintf(stderr, "Replay mode expects exactly one input CSV file!\n\n"); 2345 | argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 2346 | return -EINVAL; 2347 | } 2348 | 2349 | err = parse_stats_csv(env.filenames[0], &specs, 2350 | &env.prog_stats, &env.prog_stat_cnt); 2351 | if (err) { 2352 | fprintf(stderr, "Failed to parse stats from '%s': %d\n", env.filenames[0], err); 2353 | return err; 2354 | } 2355 | 2356 | qsort(env.prog_stats, env.prog_stat_cnt, sizeof(*env.prog_stats), cmp_prog_stats); 2357 | 2358 | output_prog_stats(); 2359 | 2360 | return 0; 2361 | } 2362 | 2363 | int main(int argc, char **argv) 2364 | { 2365 | int err = 0, i; 2366 | 2367 | if (argp_parse(&argp, argc, argv, 0, NULL, NULL)) 2368 | return 1; 2369 | 2370 | if (env.show_version) { 2371 | printf("%s\n", argp_program_version); 2372 | return 0; 2373 | } 2374 | 2375 | if (env.verbose && env.quiet) { 2376 | fprintf(stderr, "Verbose and quiet modes are incompatible, please specify just one or neither!\n\n"); 2377 | argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 2378 | return 1; 2379 | } 2380 | if (env.verbose && env.log_level == 0) 2381 | env.log_level = 1; 2382 | 2383 | if (env.output_spec.spec_cnt == 0) { 2384 | if (env.out_fmt == RESFMT_CSV) 2385 | env.output_spec = default_csv_output_spec; 2386 | else 2387 | env.output_spec = default_output_spec; 2388 | } 2389 | if (env.sort_spec.spec_cnt == 0) 2390 | env.sort_spec = default_sort_spec; 2391 | 2392 | if (env.comparison_mode && env.replay_mode) { 2393 | fprintf(stderr, "Can't specify replay and comparison mode at the same time!\n\n"); 2394 | argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); 2395 | return 1; 2396 | } 2397 | 2398 | if (env.comparison_mode) 2399 | err = handle_comparison_mode(); 2400 | else if (env.replay_mode) 2401 | err = handle_replay_mode(); 2402 | else 2403 | err = handle_verif_mode(); 2404 | 2405 | free_verif_stats(env.prog_stats, env.prog_stat_cnt); 2406 | free_verif_stats(env.baseline_stats, env.baseline_stat_cnt); 2407 | free(env.join_stats); 2408 | for (i = 0; i < env.filename_cnt; i++) 2409 | free(env.filenames[i]); 2410 | free(env.filenames); 2411 | for (i = 0; i < env.allow_filter_cnt; i++) { 2412 | free(env.allow_filters[i].any_glob); 2413 | free(env.allow_filters[i].file_glob); 2414 | free(env.allow_filters[i].prog_glob); 2415 | } 2416 | free(env.allow_filters); 2417 | for (i = 0; i < env.deny_filter_cnt; i++) { 2418 | free(env.deny_filters[i].any_glob); 2419 | free(env.deny_filters[i].file_glob); 2420 | free(env.deny_filters[i].prog_glob); 2421 | } 2422 | free(env.deny_filters); 2423 | return -err; 2424 | } 2425 | --------------------------------------------------------------------------------