已开启
新增嵌入式rt-test实时测试用例 #92
Algernon67创建于 2024年12月19日
新增嵌入式rt-test实时测试用例 #92
已开启
从refs/pull/92/head合入到master
共 78 个文件变更+20936-0
| @@ -0,0 +1,339 @@ | |||
| 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 | + <one line to give the program's name and a brief idea of what it does.> | ||
| 294 | + Copyright (C) <year> <name of author> | ||
| 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 | + <signature of Ty Coon>, 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. | ||
| @@ -0,0 +1,26 @@ | |||
| 1 | +# Use CentOS Stream 9 as base image | ||
L | |||
| 2 | +FROM centos:stream9 | ||
| 3 | + | ||
| 4 | +# Install required packages for RT-Tests | ||
| 5 | +RUN dnf -y update && \ | ||
| 6 | + dnf install -y \ | ||
| 7 | + gcc \ | ||
| 8 | + make \ | ||
| 9 | + numactl-devel \ | ||
| 10 | + util-linux \ | ||
| 11 | + git && \ | ||
| 12 | + git clone https://git.kernel.org/pub/scm/utils/rt-tests/rt-tests.git /opt/rt-tests && \ | ||
L 这是上游的地址吗 拉下来是否要换成当前的地址 ![]() ![]() | |||
| 13 | + cd /opt/rt-tests && \ | ||
| 14 | + make && \ | ||
| 15 | + make install && \ | ||
| 16 | + dnf remove -y \ | ||
| 17 | + git \ | ||
| 18 | + make && \ | ||
| 19 | + dnf clean all | ||
| 20 | + | ||
| 21 | + | ||
| 22 | +# Set the working directory to the RT-Tests directory | ||
| 23 | +WORKDIR /opt/rt-tests | ||
| 24 | + | ||
| 25 | +# Set the entrypoint to a shell | ||
| 26 | +ENTRYPOINT ["/bin/bash"] | ||
| @@ -0,0 +1,278 @@ | |||
| 1 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +include feature/test-feature.mak | ||
| 3 | + | ||
| 4 | +VERSION = 2.8 | ||
| 5 | +CC = $(CROSS_COMPILE)gcc | ||
| 6 | +AR = $(CROSS_COMPILE)ar | ||
| 7 | + | ||
| 8 | +OBJDIR = bld | ||
| 9 | + | ||
| 10 | +sources = cyclictest.c \ | ||
| 11 | + hackbench.c \ | ||
| 12 | + pip_stress.c \ | ||
| 13 | + pi_stress.c \ | ||
| 14 | + pmqtest.c \ | ||
| 15 | + ptsematest.c \ | ||
| 16 | + rt-migrate-test.c \ | ||
| 17 | + signaltest.c \ | ||
| 18 | + sigwaittest.c \ | ||
| 19 | + svsematest.c \ | ||
| 20 | + cyclicdeadline.c \ | ||
| 21 | + deadline_test.c \ | ||
| 22 | + queuelat.c \ | ||
| 23 | + ssdd.c \ | ||
| 24 | + oslat.c | ||
| 25 | + | ||
| 26 | +TARGETS = $(sources:.c=) | ||
| 27 | +LIBS = -lrt -lpthread | ||
| 28 | +RTTESTLIB = -lrttest -L$(OBJDIR) | ||
| 29 | +EXTRA_LIBS ?= -ldl # for get_cpu | ||
| 30 | +RTTESTNUMA = -lrttestnuma -lnuma | ||
| 31 | +DESTDIR ?= | ||
| 32 | +prefix ?= /usr/local | ||
| 33 | +bindir ?= $(prefix)/bin | ||
| 34 | +mandir ?= $(prefix)/share/man | ||
| 35 | + | ||
| 36 | +CFLAGS ?= -Wall -Wno-nonnull -Wextra -Wno-sign-compare | ||
| 37 | +CPPFLAGS += -D_GNU_SOURCE -Isrc/include | ||
| 38 | +LDFLAGS ?= | ||
| 39 | + | ||
| 40 | +PYLIB ?= $(shell python3 -m get_pylib) | ||
| 41 | + | ||
| 42 | +# Check for optional libcpupower dependency | ||
| 43 | +ifneq ($(no_libcpupower), 1) | ||
| 44 | +ifeq ($(call test-feature,libcpupower), 0) | ||
| 45 | +CPPFLAGS += -DHAVE_LIBCPUPOWER_SUPPORT | ||
| 46 | +LIBCPUPOWER += -lcpupower | ||
| 47 | +else | ||
| 48 | +$(warning libcpupower is missing, building without --deepest-idle-state support.) | ||
| 49 | +$(warning Please install libcpupower-dev/kernel-tools-libs-devel) | ||
| 50 | +endif | ||
| 51 | +else | ||
| 52 | +$(warning libcpupower disabled, building without --deepest-idle-state support.) | ||
| 53 | +endif | ||
| 54 | + | ||
| 55 | +# Check for errors, such as python3 not available | ||
| 56 | +ifeq (${PYLIB},) | ||
| 57 | + undefine PYLIB | ||
| 58 | +endif | ||
| 59 | + | ||
| 60 | +MANPAGES = src/cyclictest/cyclictest.8 \ | ||
| 61 | + src/pi_tests/pi_stress.8 \ | ||
| 62 | + src/ptsematest/ptsematest.8 \ | ||
| 63 | + src/rt-migrate-test/rt-migrate-test.8 \ | ||
| 64 | + src/sigwaittest/sigwaittest.8 \ | ||
| 65 | + src/svsematest/svsematest.8 \ | ||
| 66 | + src/pmqtest/pmqtest.8 \ | ||
| 67 | + src/hackbench/hackbench.8 \ | ||
| 68 | + src/signaltest/signaltest.8 \ | ||
| 69 | + src/pi_tests/pip_stress.8 \ | ||
| 70 | + src/queuelat/queuelat.8 \ | ||
| 71 | + src/queuelat/determine_maximum_mpps.8 \ | ||
| 72 | + src/sched_deadline/deadline_test.8 \ | ||
| 73 | + src/ssdd/ssdd.8 \ | ||
| 74 | + src/sched_deadline/cyclicdeadline.8 \ | ||
| 75 | + src/oslat/oslat.8 | ||
| 76 | + | ||
| 77 | +ifdef PYLIB | ||
| 78 | + MANPAGES += src/cyclictest/get_cyclictest_snapshot.8 \ | ||
| 79 | + src/hwlatdetect/hwlatdetect.8 | ||
| 80 | +endif | ||
| 81 | + | ||
| 82 | +ifeq ($(MAN_COMPRESSION),gzip) | ||
| 83 | + MANPAGES := $(MANPAGES:.8=.8.gz) | ||
| 84 | +else ifeq ($(MAN_COMPRESSION),bzip2) | ||
| 85 | + MANPAGES := $(MANPAGES:.8=.8.bz2) | ||
| 86 | +endif | ||
| 87 | + | ||
| 88 | +ifndef DEBUG | ||
| 89 | + CFLAGS += -O2 -g | ||
| 90 | +else | ||
| 91 | + CFLAGS += -O0 -g | ||
| 92 | +endif | ||
| 93 | + | ||
| 94 | +# We make some gueses on how to compile rt-tests based on the machine type | ||
| 95 | +# and the ostype. These can often be overridden. | ||
| 96 | +dumpmachine := $(shell $(CC) -dumpmachine) | ||
| 97 | + | ||
| 98 | +# The ostype is typically something like linux or android | ||
| 99 | +ostype := $(lastword $(subst -, ,$(dumpmachine))) | ||
| 100 | + | ||
| 101 | +machinetype := $(shell echo $(dumpmachine)| \ | ||
| 102 | + sed -e 's/-.*//' -e 's/i.86/i386/' -e 's/mips.*/mips/' -e 's/ppc.*/powerpc/') | ||
| 103 | + | ||
| 104 | +include src/arch/android/Makefile | ||
| 105 | + | ||
| 106 | +VPATH = src/cyclictest: | ||
| 107 | +VPATH += src/signaltest: | ||
| 108 | +VPATH += src/pi_tests: | ||
| 109 | +VPATH += src/rt-migrate-test: | ||
| 110 | +VPATH += src/ptsematest: | ||
| 111 | +VPATH += src/sigwaittest: | ||
| 112 | +VPATH += src/svsematest: | ||
| 113 | +VPATH += src/pmqtest: | ||
| 114 | +VPATH += src/lib: | ||
| 115 | +VPATH += src/hackbench: | ||
| 116 | +VPATH += src/sched_deadline: | ||
| 117 | +VPATH += src/queuelat: | ||
| 118 | +VPATH += src/ssdd: | ||
| 119 | +VPATH += src/oslat: | ||
| 120 | + | ||
| 121 | +$(OBJDIR)/%.o: %.c | $(OBJDIR) | ||
| 122 | + $(CC) -D VERSION=$(VERSION) -c $< $(CFLAGS) $(CPPFLAGS) -o $@ | ||
| 123 | + | ||
| 124 | +# Pattern rule to generate dependency files from .c files | ||
| 125 | +$(OBJDIR)/%.d: %.c | $(OBJDIR) | ||
| 126 | + @$(CC) -MM $(CFLAGS) $(CPPFLAGS) $< | sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' > $@ || rm -f $@ | ||
| 127 | + | ||
| 128 | +.PHONY: all | ||
| 129 | +all: $(TARGETS) hwlatdetect get_cyclictest_snapshot | $(OBJDIR) | ||
| 130 | + | ||
| 131 | +$(OBJDIR): | ||
| 132 | + mkdir $(OBJDIR) | ||
| 133 | + | ||
| 134 | +# Include dependency files, automatically generate them if needed. | ||
| 135 | +-include $(addprefix $(OBJDIR)/,$(sources:.c=.d)) | ||
| 136 | + | ||
| 137 | +cyclictest: $(OBJDIR)/cyclictest.o $(OBJDIR)/librttest.a $(OBJDIR)/librttestnuma.a | ||
| 138 | + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS) $(RTTESTLIB) $(RTTESTNUMA) $(LIBCPUPOWER) | ||
| 139 | + | ||
| 140 | +cyclicdeadline: $(OBJDIR)/cyclicdeadline.o $(OBJDIR)/librttest.a | ||
| 141 | + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS) $(RTTESTLIB) | ||
| 142 | + | ||
| 143 | +deadline_test: $(OBJDIR)/deadline_test.o $(OBJDIR)/librttest.a | ||
| 144 | + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS) $(RTTESTLIB) | ||
| 145 | + | ||
| 146 | +signaltest: $(OBJDIR)/signaltest.o $(OBJDIR)/librttest.a $(OBJDIR)/librttestnuma.a | ||
| 147 | + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS) $(RTTESTLIB) $(RTTESTNUMA) | ||
| 148 | + | ||
| 149 | +pi_stress: $(OBJDIR)/pi_stress.o $(OBJDIR)/librttest.a | ||
| 150 | + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS) $(RTTESTLIB) | ||
| 151 | + | ||
| 152 | +hwlatdetect: src/hwlatdetect/hwlatdetect.py | ||
| 153 | + chmod +x src/hwlatdetect/hwlatdetect.py | ||
| 154 | + ln -s src/hwlatdetect/hwlatdetect.py hwlatdetect | ||
| 155 | + | ||
| 156 | +get_cyclictest_snapshot: src/cyclictest/get_cyclictest_snapshot.py | ||
| 157 | + chmod +x src/cyclictest/get_cyclictest_snapshot.py | ||
| 158 | + ln -s src/cyclictest/get_cyclictest_snapshot.py get_cyclictest_snapshot | ||
| 159 | + | ||
| 160 | +rt-migrate-test: $(OBJDIR)/rt-migrate-test.o $(OBJDIR)/librttest.a | ||
| 161 | + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS) $(RTTESTLIB) | ||
| 162 | + | ||
| 163 | +ptsematest: $(OBJDIR)/ptsematest.o $(OBJDIR)/librttest.a | ||
| 164 | + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS) $(RTTESTLIB) $(EXTRA_LIBS) | ||
| 165 | + | ||
| 166 | +sigwaittest: $(OBJDIR)/sigwaittest.o $(OBJDIR)/librttest.a | ||
| 167 | + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS) $(RTTESTLIB) $(EXTRA_LIBS) | ||
| 168 | + | ||
| 169 | +svsematest: $(OBJDIR)/svsematest.o $(OBJDIR)/librttest.a | ||
| 170 | + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS) $(RTTESTLIB) $(EXTRA_LIBS) | ||
| 171 | + | ||
| 172 | +pmqtest: $(OBJDIR)/pmqtest.o $(OBJDIR)/librttest.a | ||
| 173 | + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS) $(RTTESTLIB) $(EXTRA_LIBS) | ||
| 174 | + | ||
| 175 | +pip_stress: $(OBJDIR)/pip_stress.o $(OBJDIR)/librttest.a | ||
| 176 | + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS) $(RTTESTLIB) | ||
| 177 | + | ||
| 178 | +hackbench: $(OBJDIR)/hackbench.o | ||
| 179 | + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS) | ||
| 180 | + | ||
| 181 | +queuelat: $(OBJDIR)/queuelat.o $(OBJDIR)/librttest.a | ||
| 182 | + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS) $(RTTESTLIB) | ||
| 183 | + | ||
| 184 | +ssdd: $(OBJDIR)/ssdd.o $(OBJDIR)/librttest.a | ||
| 185 | + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS) $(RTTESTLIB) | ||
| 186 | + | ||
| 187 | +oslat: $(OBJDIR)/oslat.o $(OBJDIR)/librttest.a $(OBJDIR)/librttestnuma.a | ||
| 188 | + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIBS) $(RTTESTLIB) $(RTTESTNUMA) | ||
| 189 | + | ||
| 190 | +%.8.gz: %.8 | ||
| 191 | + gzip -nc $< > $@ | ||
| 192 | + | ||
| 193 | +%.8.bz2: %.8 | ||
| 194 | + bzip2 -c $< > $@ | ||
| 195 | + | ||
| 196 | +LIBOBJS =$(addprefix $(OBJDIR)/,rt-error.o rt-get_cpu.o rt-sched.o rt-utils.o \ | ||
| 197 | + histogram.o) | ||
| 198 | +$(OBJDIR)/librttest.a: $(LIBOBJS) | ||
| 199 | + $(AR) rcs $@ $^ | ||
| 200 | + | ||
| 201 | +LIBNUMAOBJS =$(addprefix $(OBJDIR)/,rt-numa.o) | ||
| 202 | +$(OBJDIR)/librttestnuma.a: $(LIBNUMAOBJS) | ||
| 203 | + $(AR) rcs $@ $^ | ||
| 204 | + | ||
| 205 | +CLEANUP = $(TARGETS) *.o .depend *.*~ *.orig *.rej *.d *.a *.8.gz *.8.bz2 | ||
| 206 | +CLEANUP += $(if $(wildcard .git), ChangeLog) | ||
| 207 | + | ||
| 208 | +.PHONY: clean | ||
| 209 | +clean: | ||
| 210 | + for F in $(CLEANUP); do find -type f -name $$F | xargs rm -f; done | ||
| 211 | + rm -f rt-tests-*.tar | ||
| 212 | + rm -f hwlatdetect | ||
| 213 | + rm -f get_cyclictest_snapshot | ||
| 214 | + rm -f tags | ||
| 215 | + | ||
| 216 | +RPMDIRS = BUILD BUILDROOT RPMS SRPMS SPECS | ||
| 217 | +.PHONY: distclean | ||
| 218 | +distclean: clean | ||
| 219 | + rm -rf $(RPMDIRS) releases *.tar.gz *.tar.asc tmp | ||
| 220 | + | ||
| 221 | +.PHONY: rebuild | ||
| 222 | +rebuild: | ||
| 223 | + $(MAKE) clean | ||
| 224 | + $(MAKE) all | ||
| 225 | + | ||
| 226 | +.PHONY: install | ||
| 227 | +install: all install_manpages install_hwlatdetect install_get_cyclictest_snapshot | ||
| 228 | + mkdir -p "$(DESTDIR)$(bindir)" | ||
| 229 | + cp $(TARGETS) "$(DESTDIR)$(bindir)" | ||
| 230 | + install src/queuelat/determine_maximum_mpps.sh "${DESTDIR}${bindir}" | ||
| 231 | + | ||
| 232 | +.PHONY: install_hwlatdetect | ||
| 233 | +install_hwlatdetect: hwlatdetect | ||
| 234 | + if test -n "$(PYLIB)" ; then \ | ||
| 235 | + mkdir -p "$(DESTDIR)$(bindir)" ; \ | ||
| 236 | + install -D -m 755 src/hwlatdetect/hwlatdetect.py $(DESTDIR)$(PYLIB)/hwlatdetect.py ; \ | ||
| 237 | + rm -f "$(DESTDIR)$(bindir)/hwlatdetect" ; \ | ||
| 238 | + ln -s $(PYLIB)/hwlatdetect.py "$(DESTDIR)$(bindir)/hwlatdetect" ; \ | ||
| 239 | + fi | ||
| 240 | + | ||
| 241 | +.PHONY: install_get_cyclictest_snapshot | ||
| 242 | +install_get_cyclictest_snapshot: get_cyclictest_snapshot | ||
| 243 | + if test -n "${PYLIB}" ; then \ | ||
| 244 | + mkdir -p "${DESTDIR}${bindir}" ; \ | ||
| 245 | + install -D -m 755 src/cyclictest/get_cyclictest_snapshot.py ${DESTDIR}${PYLIB}/get_cyclictest_snapshot.py ; \ | ||
| 246 | + rm -f "${DESTDIR}${bindir}/get_cyclictest_snapshot" ; \ | ||
| 247 | + ln -s ${PYLIB}/get_cyclictest_snapshot.py "${DESTDIR}${bindir}/get_cyclictest_snapshot" ; \ | ||
| 248 | + fi | ||
| 249 | + | ||
| 250 | +.PHONY: install_manpages | ||
| 251 | +install_manpages: $(MANPAGES) | ||
| 252 | + mkdir -p "$(DESTDIR)$(mandir)/man8" | ||
| 253 | + cp $(MANPAGES) "$(DESTDIR)$(mandir)/man8" | ||
| 254 | + | ||
| 255 | +.PHONY: tarball | ||
| 256 | +tarball: | ||
| 257 | + git archive --worktree-attributes --prefix=rt-tests-${VERSION}/ -o rt-tests-${VERSION}.tar v${VERSION} | ||
| 258 | + | ||
| 259 | +.PHONY: help | ||
| 260 | +help: | ||
| 261 | + @echo "" | ||
| 262 | + @echo " rt-tests useful Makefile targets:" | ||
| 263 | + @echo "" | ||
| 264 | + @echo " all : build all tests (default" | ||
| 265 | + @echo " install : install tests to local filesystem" | ||
| 266 | + @echo " clean : remove object files" | ||
| 267 | + @echo " distclean : remove all generated files" | ||
| 268 | + @echo " tarball : make a rt-tests tarball suitable for release" | ||
| 269 | + @echo " help : print this message" | ||
| 270 | + | ||
| 271 | +# Universal Ctags warns about the backward compatible option '--extra' and | ||
| 272 | +# wants it to be called '--extras'. | ||
| 273 | +CTAGS_BIN = ctags | ||
| 274 | +CTAGS_EXTRA := $(shell $(CTAGS_BIN) --version 2>&1 | grep -iq universal && echo extras || echo extra) | ||
| 275 | + | ||
| 276 | +.PHONY: tags | ||
| 277 | +tags: | ||
| 278 | + $(CTAGS_BIN) -R --$(CTAGS_EXTRA)=+f --c-kinds=+p --exclude=tmp --exclude=BUILD * | ||
The file is empty
| @@ -0,0 +1,12 @@ | |||
| 1 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +all: feature-libcpupower | ||
| 3 | + | ||
| 4 | +feature-libcpupower: $(OBJDIR)/test-libcpupower.o | ||
| 5 | + | ||
| 6 | +$(OBJDIR)/test-libcpupower.o: feature/test-libcpupower.c | ||
| 7 | + @$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< -lcpupower -o $@ | ||
| 8 | + | ||
| 9 | +.PHONY: clean | ||
| 10 | + | ||
| 11 | +clean: | ||
| 12 | + rm -f $(OBJDIR)/test-*.o | ||
| @@ -0,0 +1,5 @@ | |||
| 1 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +define test-feature | ||
| 3 | +$(shell $(MAKE) OBJDIR="$(OBJDIR)" CFLAGS="$(CFLAGS)" CPPFLAGS="$(CPPFLAGS)" LDFLAGS="$(LDFLAGS)" \ | ||
| 4 | +-f feature/Makefile feature-$1 clean >/dev/null 2>/dev/null; echo $$?) | ||
| 5 | +endef | ||
| @@ -0,0 +1,8 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +int main(void) | ||
| 5 | +{ | ||
| 6 | + int rv = cpuidle_state_count(0); | ||
| 7 | + return rv; | ||
| 8 | +} | ||
| @@ -0,0 +1,16 @@ | |||
| 1 | +#!/usr/bin/python3 | ||
| 2 | + | ||
| 3 | +# This file is used during the make process | ||
| 4 | + | ||
| 5 | +import sysconfig | ||
| 6 | + | ||
| 7 | +# Older versions of python don't have this scheme | ||
| 8 | +# but produce the answer we expect with 'posix_prefix' | ||
| 9 | +# Newer versions of python insert 'local' in the path unless we use rpm_prefix | ||
| 10 | +SCHEME = 'rpm_prefix' | ||
| 11 | + | ||
| 12 | +if not SCHEME in sysconfig.get_scheme_names(): | ||
| 13 | + SCHEME = 'posix_prefix' | ||
| 14 | + | ||
| 15 | +PYLIB = sysconfig.get_path('purelib', SCHEME) | ||
| 16 | +print(PYLIB) | ||
| @@ -0,0 +1,24 @@ | |||
| 1 | + | ||
| 2 | +# Android use Bionic for libc, and this does not have | ||
| 3 | +# - pthread barriers | ||
| 4 | +# - pthread_[gs]etaffinity | ||
| 5 | +# | ||
| 6 | +# This is all handled by bionic.h based on flags we set here. | ||
| 7 | + | ||
| 8 | +# Typically see something like "aarch64-linux-android". However, in some | ||
| 9 | +# buildsystems, it will be a variation of -androidabe. | ||
| 10 | +ost=$(findstring android, $(ostype)) | ||
| 11 | +ifeq (android,$(ost)) | ||
| 12 | + USE_BIONIC := 1 | ||
| 13 | + CFLAGS += -DPTHREAD_BIONIC | ||
| 14 | + | ||
| 15 | + LDFLAGS += -pie | ||
| 16 | +# -lrt and -lpthread is in standard bionic library, no standalone library | ||
| 17 | + LIBS := $(filter-out -lrt,$(LIBS)) | ||
| 18 | + LIBS := $(filter-out -lpthread,$(LIBS)) | ||
| 19 | + | ||
| 20 | +# Currently, only these binaries will compile and link properly for android | ||
| 21 | +# - cyclictest | ||
| 22 | +# - hackbench | ||
| 23 | + sources := cyclictest.c hackbench.c | ||
| 24 | +endif | ||
| @@ -0,0 +1,5 @@ | |||
| 1 | +Module.symvers | ||
| 2 | +backfire.ko | ||
| 3 | +backfire.mod.c | ||
| 4 | +modules.order | ||
| 5 | + | ||
| @@ -0,0 +1,17 @@ | |||
| 1 | +# If KERNELRELEASE is defined, we've been invoked from the | ||
| 2 | +# kernel build system and can use its language | ||
| 3 | +ifneq ($(KERNELRELEASE),) | ||
| 4 | +obj-m := backfire.o | ||
| 5 | + | ||
| 6 | +# otherwise we were called directly from the command | ||
| 7 | +# line; invoke the kernel build system. | ||
| 8 | +else | ||
| 9 | +KERNELDIR ?= /lib/modules/$(shell uname -r)/build | ||
| 10 | + | ||
| 11 | +modules modules_install clean:: | ||
| 12 | + make -C $(KERNELDIR) M=$(CURDIR) $@ | ||
| 13 | + | ||
| 14 | +clean:: | ||
| 15 | + rm -f *.o Module.markers modules.order | ||
| 16 | + | ||
| 17 | +endif | ||
| @@ -0,0 +1,6 @@ | |||
| 1 | +obj-m := backfire.o | ||
| 2 | +KDIR := /lib/modules/$(shell uname -r)/build | ||
| 3 | +PWD := $(shell pwd) | ||
| 4 | + | ||
| 5 | +default: | ||
| 6 | + $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules | ||
| @@ -0,0 +1,37 @@ | |||
| 1 | +.TH "backfire" "4" "0.1" "" "Driver" | ||
| 2 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | +.SH "NAME" | ||
| 4 | +.LP | ||
| 5 | +backfire \- send a signal from driver to user | ||
| 6 | +.SH "DESCRIPTION" | ||
| 7 | +.LP | ||
| 8 | +The \fBbackfire\fR driver reads a numerical string that is sent to the | ||
| 9 | +\fB/dev/backfire\fR device and sends the corresponding signal to the calling | ||
| 10 | +user program. Reading from \fB/dev/backfire\fR returns the time of the day | ||
| 11 | +when the most recent sent request was serviced or 0, if a sent request was | ||
| 12 | +not yet received. The time of the day is displayed in seconds since | ||
| 13 | +1970-01-01 00:00:00 UTC followed by the fraction of the second in | ||
| 14 | +microseconds separated by a comma. | ||
| 15 | +.SH "PURPOSE" | ||
| 16 | +.LP | ||
| 17 | +The \fBbackfire\fR driver is normally used in combination with the program | ||
| 18 | +\fBsendme\fR to benchmark the performance of the kernel's signal sending | ||
| 19 | +capabilities. | ||
| 20 | +.SH "EXAMPLES" | ||
| 21 | +.LP | ||
| 22 | +.nf | ||
| 23 | +head -1 /dev/backfire | ||
| 24 | +0,0 | ||
| 25 | +trap "echo Got signal 7" 7 | ||
| 26 | +echo 7 >/dev/backfire | ||
| 27 | +Got signal 7 | ||
| 28 | +head -1 /dev/backfire | ||
| 29 | +1234567890,123456 | ||
| 30 | +.fi | ||
| 31 | +.LP | ||
| 32 | +.SH "AUTHORS" | ||
| 33 | +.LP | ||
| 34 | +Carsten Emde <C.Emde@osadl.org> | ||
| 35 | +.SH "SEE ALSO" | ||
| 36 | +.LP | ||
| 37 | +sendme(8) | ||
| @@ -0,0 +1,135 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +/* | ||
| 3 | + * backfire - send signal back to caller | ||
| 4 | + * Copyright (C) 2007 Carsten Emde <C.Emde@osadl.org> | ||
| 5 | + */ | ||
| 6 | + | ||
| 7 | + | ||
| 8 | + | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | +static spinlock_t backfire_state_lock = SPIN_LOCK_UNLOCKED; | ||
| 25 | +static int backfire_open_cnt; /* #times opened */ | ||
| 26 | +static int backfire_open_mode; /* special open modes */ | ||
| 27 | +static struct timeval sendtime; /* when the most recent signal was sent */ | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | +/* | ||
| 32 | + * These are the file operation function for user access to /dev/backfire | ||
| 33 | + */ | ||
| 34 | +static ssize_t | ||
| 35 | +backfire_read(struct file *file, char *buf, size_t count, loff_t *ppos) | ||
| 36 | +{ | ||
| 37 | + return snprintf(buf, count, "%d,%d\n", (int) sendtime.tv_sec, | ||
| 38 | + (int) sendtime.tv_usec); | ||
| 39 | +} | ||
| 40 | + | ||
| 41 | +static ssize_t | ||
| 42 | +backfire_write(struct file *file, const char *buf, size_t count, loff_t *ppos) | ||
| 43 | +{ | ||
| 44 | + int signo; | ||
| 45 | + struct pid *pid; | ||
| 46 | + | ||
| 47 | + if (sscanf(buf, "%d", &signo) >= 1) { | ||
| 48 | + if (signo > 0 && signo < 32) { | ||
| 49 | + pid = get_pid(task_pid(current)); | ||
| 50 | + do_gettimeofday(&sendtime); | ||
| 51 | + kill_pid(pid, signo, 1); | ||
| 52 | + } else | ||
| 53 | + printk(KERN_ERR "Invalid signal no. %d\n", signo); | ||
| 54 | + } | ||
| 55 | + return strlen(buf); | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +static int | ||
| 59 | +backfire_open(struct inode *inode, struct file *file) | ||
| 60 | +{ | ||
| 61 | + spin_lock(&backfire_state_lock); | ||
| 62 | + | ||
| 63 | + if ((backfire_open_cnt && (file->f_flags & O_EXCL)) || | ||
| 64 | + (backfire_open_mode & BACKFIRE_EXCL)) { | ||
| 65 | + spin_unlock(&backfire_state_lock); | ||
| 66 | + return -EBUSY; | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + if (file->f_flags & O_EXCL) | ||
| 70 | + backfire_open_mode |= BACKFIRE_EXCL; | ||
| 71 | + if (file->f_mode & 2) | ||
| 72 | + backfire_open_mode |= BACKFIRE_WRITE; | ||
| 73 | + backfire_open_cnt++; | ||
| 74 | + | ||
| 75 | + spin_unlock(&backfire_state_lock); | ||
| 76 | + | ||
| 77 | + return 0; | ||
| 78 | +} | ||
| 79 | + | ||
| 80 | +static int | ||
| 81 | +backfire_release(struct inode *inode, struct file *file) | ||
| 82 | +{ | ||
| 83 | + spin_lock(&backfire_state_lock); | ||
| 84 | + | ||
| 85 | + backfire_open_cnt--; | ||
| 86 | + | ||
| 87 | + if (backfire_open_cnt == 1 && backfire_open_mode & BACKFIRE_EXCL) | ||
| 88 | + backfire_open_mode &= ~BACKFIRE_EXCL; | ||
| 89 | + if (file->f_mode & 2) | ||
| 90 | + backfire_open_mode &= ~BACKFIRE_WRITE; | ||
| 91 | + | ||
| 92 | + spin_unlock(&backfire_state_lock); | ||
| 93 | + | ||
| 94 | + return 0; | ||
| 95 | +} | ||
| 96 | + | ||
| 97 | +static struct file_operations backfire_fops = { | ||
| 98 | + .owner = THIS_MODULE, | ||
| 99 | + .llseek = no_llseek, | ||
| 100 | + .open = backfire_open, | ||
| 101 | + .read = backfire_read, | ||
| 102 | + .write = backfire_write, | ||
| 103 | + .release = backfire_release, | ||
| 104 | +}; | ||
| 105 | + | ||
| 106 | +static struct miscdevice backfire_dev = { | ||
| 107 | + BACKFIRE_MINOR, | ||
| 108 | + "backfire", | ||
| 109 | + &backfire_fops | ||
| 110 | +}; | ||
| 111 | + | ||
| 112 | +static int __init backfire_init(void) | ||
| 113 | +{ | ||
| 114 | + int ret; | ||
| 115 | + | ||
| 116 | + ret = misc_register(&backfire_dev); | ||
| 117 | + if (ret) | ||
| 118 | + printk(KERN_ERR "backfire: can't register dynamic misc device\n"); | ||
| 119 | + else | ||
| 120 | + printk(KERN_INFO "backfire driver misc device %d\n", | ||
| 121 | + backfire_dev.minor); | ||
| 122 | + return ret; | ||
| 123 | +} | ||
| 124 | + | ||
| 125 | +static void __exit backfire_exit(void) | ||
| 126 | +{ | ||
| 127 | + misc_deregister(&backfire_dev); | ||
| 128 | +} | ||
| 129 | + | ||
| 130 | +module_init(backfire_init); | ||
| 131 | +module_exit(backfire_exit); | ||
| 132 | + | ||
| 133 | +MODULE_LICENSE("GPL"); | ||
| 134 | +MODULE_AUTHOR("Carsten Emde <C.Emde@osadl.org>"); | ||
| 135 | +MODULE_DESCRIPTION("Send signal back to caller"); | ||
| @@ -0,0 +1,43 @@ | |||
| 1 | +.TH "sendme" "8" "0.2" "" "" | ||
| 2 | +# SPDX-License-Identifier: GPL-2.0-only | ||
| 3 | +.SH "NAME" | ||
| 4 | +.LP | ||
| 5 | +\fBsendme\fR \- Send a signal from driver to user and measure time intervals | ||
| 6 | +.SH "SYNTAX" | ||
| 7 | +.LP | ||
| 8 | +sendme [-a|-a PROC] [-b USEC] [-l loops] [-p PRIO] | ||
| 9 | +.br | ||
| 10 | +.SH "DESCRIPTION" | ||
| 11 | +.LP | ||
| 12 | +The program \fBsendme\fR uses the \fBbackfire\fR driver to send a signal from driver to user. It then reads the timestamp from the driver and calculates the time intervals to call the driver and to receive the signal from the driver. | ||
| 13 | +.SH "OPTIONS" | ||
| 14 | +.TP | ||
| 15 | +.B \-a, \-\-affinity[=PROC] | ||
| 16 | +Run on processor number PROC. If PROC is not specified, run on current processor. | ||
| 17 | +.TP | ||
| 18 | +.B \-b, \-\-breaktrace=USEC | ||
| 19 | +Send break trace command when latency > USEC. This is a debugging option to control the latency tracer in the realtime preemption patch. | ||
| 20 | +It is useful to track down unexpected large latencies on a system. | ||
| 21 | +.TP | ||
| 22 | +.B \-l, \-\-loops=LOOPS | ||
| 23 | +Set the number of loops. The default is 0 (endless). This option is useful for automated tests with a given number of test cycles. Sendme is stopped once the number of timer intervals has been reached. | ||
| 24 | +.TP | ||
| 25 | +.B \-p, \-\-prio=PRIO | ||
| 26 | +Set the priority of the process. | ||
| 27 | +.SH "FILES" | ||
| 28 | +backfire.ko | ||
| 29 | +.SH "EXAMPLES" | ||
| 30 | +.LP | ||
| 31 | +.nf | ||
| 32 | +# modprobe backfire | ||
| 33 | +# sendme -a -p99 -l1000000 | ||
| 34 | +Samples: 1000000 | ||
| 35 | +To: Min 0, Cur 0, Avg 1, Max 11 | ||
| 36 | +From: Min 2, Cur 3, Avg 3, Max 43 | ||
| 37 | +.fi | ||
| 38 | +.SH "AUTHORS" | ||
| 39 | +.LP | ||
| 40 | +Carsten Emde <C.Emde@osadl.org> | ||
| 41 | +.SH "SEE ALSO" | ||
| 42 | +.LP | ||
| 43 | +backfire(4) | ||
| @@ -0,0 +1,289 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | +/* | ||
| 3 | + * sendme.c | ||
| 4 | + * | ||
| 5 | + * Copyright (C) 2009 Carsten Emde <C.Emde@osadl.org> | ||
| 6 | + * | ||
| 7 | + */ | ||
| 8 | + | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | +enum { | ||
| 31 | + AFFINITY_UNSPECIFIED, | ||
| 32 | + AFFINITY_SPECIFIED, | ||
| 33 | + AFFINITY_USECURRENT | ||
| 34 | +}; | ||
| 35 | +static int setaffinity = AFFINITY_UNSPECIFIED; | ||
| 36 | + | ||
| 37 | +static int affinity; | ||
| 38 | +static int tracelimit; | ||
| 39 | +static int priority; | ||
| 40 | +static int shutdown; | ||
| 41 | +static int max_cycles; | ||
| 42 | +static volatile struct timeval after; | ||
| 43 | +static int interval = 1000; | ||
| 44 | + | ||
| 45 | +static int kernvar(int mode, const char *name, char *value, size_t sizeofvalue) | ||
| 46 | +{ | ||
| 47 | + char filename[128]; | ||
| 48 | + char *fileprefix = get_debugfileprefix(); | ||
| 49 | + int retval = 1; | ||
| 50 | + int path; | ||
| 51 | + size_t len_prefix = strlen(fileprefix), len_name = strlen(name); | ||
| 52 | + | ||
| 53 | + if (len_prefix + len_name + 1 > sizeof(filename)) { | ||
| 54 | + errno = ENOMEM; | ||
| 55 | + return 1; | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + memcpy(filename, fileprefix, len_prefix); | ||
| 59 | + memcpy(filename + len_prefix, name, len_name + 1); | ||
| 60 | + | ||
| 61 | + path = open(filename, mode); | ||
| 62 | + if (path >= 0) { | ||
| 63 | + if (mode == O_RDONLY) { | ||
| 64 | + int got; | ||
| 65 | + if ((got = read(path, value, sizeofvalue)) > 0) { | ||
| 66 | + retval = 0; | ||
| 67 | + value[got-1] = '\0'; | ||
| 68 | + } | ||
| 69 | + } else if (mode == O_WRONLY) { | ||
| 70 | + if (write(path, value, sizeofvalue) == sizeofvalue) | ||
| 71 | + retval = 0; | ||
| 72 | + } | ||
| 73 | + close(path); | ||
| 74 | + } | ||
| 75 | + return retval; | ||
| 76 | +} | ||
| 77 | + | ||
| 78 | +void signalhandler(int signo) | ||
| 79 | +{ | ||
| 80 | + struct timeval tv; | ||
| 81 | + | ||
| 82 | + gettimeofday(&tv, NULL); | ||
| 83 | + after = tv; | ||
| 84 | + if (signo == SIGINT || signo == SIGTERM) | ||
| 85 | + shutdown = 1; | ||
| 86 | +} | ||
| 87 | + | ||
| 88 | +void stop_tracing(void) | ||
| 89 | +{ | ||
| 90 | + kernvar(O_WRONLY, "tracing_enabled", "0", 1); | ||
| 91 | +} | ||
| 92 | + | ||
| 93 | +static void display_help(void) | ||
| 94 | +{ | ||
| 95 | + printf("sendme V %1.2f\n", VERSION); | ||
| 96 | + puts("Usage: sendme <options>"); | ||
| 97 | + puts("Function: send a signal from driver to userspace"); | ||
| 98 | + puts( | ||
| 99 | + "Options:\n" | ||
| 100 | + "-a [NUM] --affinity pin to current processor\n" | ||
| 101 | + " with NUM pin to processor NUM\n" | ||
| 102 | + "-b USEC --breaktrace=USEC send break trace command when latency > USEC\n" | ||
| 103 | + "-i INTV --interval=INTV base interval of thread in us default=1000\n" | ||
| 104 | + "-l LOOPS --loops=LOOPS number of loops: default=0(endless)\n" | ||
| 105 | + "-p PRIO --prio=PRIO priority\n"); | ||
| 106 | + exit(1); | ||
| 107 | +} | ||
| 108 | + | ||
| 109 | +static void process_options (int argc, char *argv[]) | ||
| 110 | +{ | ||
| 111 | + int error = 0; | ||
| 112 | + int max_cpus = sysconf(_SC_NPROCESSORS_CONF); | ||
| 113 | + | ||
| 114 | + for (;;) { | ||
| 115 | + int option_index = 0; | ||
| 116 | + /** Options for getopt */ | ||
| 117 | + static struct option long_options[] = { | ||
| 118 | + {"affinity", optional_argument, NULL, 'a'}, | ||
| 119 | + {"breaktrace", required_argument, NULL, 'b'}, | ||
| 120 | + {"interval", required_argument, NULL, 'i'}, | ||
| 121 | + {"loops", required_argument, NULL, 'l'}, | ||
| 122 | + {"priority", required_argument, NULL, 'p'}, | ||
| 123 | + {"help", no_argument, NULL, '?'}, | ||
| 124 | + {NULL, 0, NULL, 0} | ||
| 125 | + }; | ||
| 126 | + int c = getopt_long (argc, argv, "a::b:i:l:p:", | ||
| 127 | + long_options, &option_index); | ||
| 128 | + if (c == -1) | ||
| 129 | + break; | ||
| 130 | + switch (c) { | ||
| 131 | + case 'a': | ||
| 132 | + if (optarg != NULL) { | ||
| 133 | + affinity = atoi(optarg); | ||
| 134 | + setaffinity = AFFINITY_SPECIFIED; | ||
| 135 | + } else if (optind < argc && atoi(argv[optind])) { | ||
| 136 | + affinity = atoi(argv[optind]); | ||
| 137 | + setaffinity = AFFINITY_SPECIFIED; | ||
| 138 | + } else | ||
| 139 | + setaffinity = AFFINITY_USECURRENT; | ||
| 140 | + break; | ||
| 141 | + case 'b': tracelimit = atoi(optarg); break; | ||
| 142 | + case 'i': interval = atoi(optarg); break; | ||
| 143 | + case 'l': max_cycles = atoi(optarg); break; | ||
| 144 | + case 'p': priority = atoi(optarg); break; | ||
| 145 | + case '?': error = 1; break; | ||
| 146 | + } | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + if (setaffinity == AFFINITY_SPECIFIED) { | ||
| 150 | + if (affinity < 0) | ||
| 151 | + error = 1; | ||
| 152 | + if (affinity >= max_cpus) { | ||
| 153 | + fprintf(stderr, "ERROR: CPU #%d not found, only %d CPUs available\n", | ||
| 154 | + affinity, max_cpus); | ||
| 155 | + error = 1; | ||
| 156 | + } | ||
| 157 | + } | ||
| 158 | + | ||
| 159 | + if (priority < 0 || priority > 99) | ||
| 160 | + error = 1; | ||
| 161 | + | ||
| 162 | + if (error) | ||
| 163 | + display_help (); | ||
| 164 | +} | ||
| 165 | + | ||
| 166 | +int main(int argc, char *argv[]) | ||
| 167 | +{ | ||
| 168 | + int path; | ||
| 169 | + cpu_set_t mask; | ||
| 170 | + int policy = SCHED_FIFO; | ||
| 171 | + struct sched_param schedp; | ||
| 172 | + struct flock fl; | ||
| 173 | + int retval = 0; | ||
| 174 | + | ||
| 175 | + process_options(argc, argv); | ||
| 176 | + | ||
| 177 | + if (check_privs()) | ||
| 178 | + return 1; | ||
| 179 | + | ||
| 180 | + if (mlockall(MCL_CURRENT|MCL_FUTURE) == -1) { | ||
| 181 | + perror("mlockall"); | ||
| 182 | + return 1; | ||
| 183 | + } | ||
| 184 | + | ||
| 185 | + memset(&schedp, 0, sizeof(schedp)); | ||
| 186 | + schedp.sched_priority = priority; | ||
| 187 | + sched_setscheduler(0, policy, &schedp); | ||
| 188 | + | ||
| 189 | + if (setaffinity != AFFINITY_UNSPECIFIED) { | ||
| 190 | + CPU_ZERO(&mask); | ||
| 191 | + if (setaffinity == AFFINITY_USECURRENT) { | ||
| 192 | + get_cpu_setup(); | ||
| 193 | + affinity = get_cpu(); | ||
| 194 | + } | ||
| 195 | + CPU_SET(affinity, &mask); | ||
| 196 | + if (sched_setaffinity(0, sizeof(mask), &mask) == -1) | ||
| 197 | + fprintf(stderr, "WARNING: Could not set CPU affinity " | ||
| 198 | + "to CPU #%d\n", affinity); | ||
| 199 | + } | ||
| 200 | + | ||
| 201 | + path = open("/dev/backfire", O_RDWR); | ||
| 202 | + if (path < 0) { | ||
| 203 | + fprintf(stderr, "ERROR: Could not access backfire device, " | ||
| 204 | + "try 'modprobe backfire'\n" | ||
| 205 | + "If the module backfire can't be loaded, " | ||
| 206 | + "it may need to be built first.\n" | ||
| 207 | + "Execute 'cd src/backfire; make' in the " | ||
| 208 | + "rt-tests directory (requires rt-tests\n" | ||
| 209 | + "sources and kernel-devel package).\n"); | ||
| 210 | + return 1; | ||
| 211 | + } | ||
| 212 | + fl.l_type = F_WRLCK; | ||
| 213 | + fl.l_whence = SEEK_SET; | ||
| 214 | + fl.l_start = 0; | ||
| 215 | + fl.l_len = 1; | ||
| 216 | + if (fcntl(path, F_SETLK, &fl) == -1) { | ||
| 217 | + fprintf(stderr, "ERRROR: backfire device locked\n"); | ||
| 218 | + retval = 1; | ||
| 219 | + } else { | ||
| 220 | + char sigtest[8]; | ||
| 221 | + char timestamp[32]; | ||
| 222 | + struct timeval before, sendtime, diff; | ||
| 223 | + unsigned int diffno = 0; | ||
| 224 | + unsigned int mindiff1 = UINT_MAX, maxdiff1 = 0; | ||
| 225 | + unsigned int mindiff2 = UINT_MAX, maxdiff2 = 0; | ||
| 226 | + double sumdiff1 = 0.0, sumdiff2 = 0.0; | ||
| 227 | + | ||
| 228 | + if (tracelimit) | ||
| 229 | + kernvar(O_WRONLY, "tracing_enabled", "1", 1); | ||
| 230 | + | ||
| 231 | + sprintf(sigtest, "%d", SIGTEST); | ||
| 232 | + signal(SIGTEST, signalhandler); | ||
| 233 | + signal(SIGINT, signalhandler); | ||
| 234 | + signal(SIGTERM, signalhandler); | ||
| 235 | + | ||
| 236 | + while (1) { | ||
| 237 | + struct timespec ts; | ||
| 238 | + | ||
| 239 | + ts.tv_sec = interval / USEC_PER_SEC; | ||
| 240 | + ts.tv_nsec = (interval % USEC_PER_SEC) * 1000; | ||
| 241 | + | ||
| 242 | + gettimeofday(&before, NULL); | ||
| 243 | + write(path, sigtest, strlen(sigtest)); | ||
| 244 | + while (after.tv_sec == 0); | ||
| 245 | + read(path, timestamp, sizeof(timestamp)); | ||
| 246 | + if (sscanf(timestamp, "%lu,%lu\n", &sendtime.tv_sec, | ||
| 247 | + &sendtime.tv_usec) != 2) | ||
| 248 | + break; | ||
| 249 | + diffno++; | ||
| 250 | + if(max_cycles && diffno >= max_cycles) | ||
| 251 | + shutdown = 1; | ||
| 252 | + | ||
| 253 | + printf("Samples: %8d\n", diffno); | ||
| 254 | + timersub(&sendtime, &before, &diff); | ||
| 255 | + if (diff.tv_usec < mindiff1) | ||
| 256 | + mindiff1 = diff.tv_usec; | ||
| 257 | + if (diff.tv_usec > maxdiff1) | ||
| 258 | + maxdiff1 = diff.tv_usec; | ||
| 259 | + sumdiff1 += (double) diff.tv_usec; | ||
| 260 | + printf("To: Min %4d, Cur %4d, Avg %4d, Max %4d\n", | ||
| 261 | + mindiff1, (int) diff.tv_usec, | ||
| 262 | + (int) ((sumdiff1 / diffno) + 0.5), | ||
| 263 | + maxdiff1); | ||
| 264 | + | ||
| 265 | + timersub(&after, &sendtime, &diff); | ||
| 266 | + if (diff.tv_usec < mindiff2) | ||
| 267 | + mindiff2 = diff.tv_usec; | ||
| 268 | + if (diff.tv_usec > maxdiff2) | ||
| 269 | + maxdiff2 = diff.tv_usec; | ||
| 270 | + sumdiff2 += (double) diff.tv_usec; | ||
| 271 | + printf("From: Min %4d, Cur %4d, Avg %4d, Max %4d\n", | ||
| 272 | + mindiff2, (int) diff.tv_usec, | ||
| 273 | + (int) ((sumdiff2 / diffno) + 0.5), | ||
| 274 | + maxdiff2); | ||
| 275 | + after.tv_sec = 0; | ||
| 276 | + if ((tracelimit && diff.tv_usec > tracelimit) || | ||
| 277 | + shutdown) { | ||
| 278 | + if (tracelimit) | ||
| 279 | + stop_tracing(); | ||
| 280 | + break; | ||
| 281 | + } | ||
| 282 | + nanosleep(&ts, NULL); | ||
| 283 | + printf("\033[3A"); | ||
| 284 | + } | ||
| 285 | + } | ||
| 286 | + | ||
| 287 | + close(path); | ||
| 288 | + return retval; | ||
| 289 | +} | ||
| @@ -0,0 +1,198 @@ | |||
| 1 | +.\" Hey, EMACS: -*- nroff -*- | ||
| 2 | +.TH CYCLICTEST 8 "April 22, 2016" | ||
| 3 | +# SPDX-License-Identifier: GPL-2.0-only | ||
| 4 | +.\" Please adjust this date whenever revising the manpage. | ||
| 5 | +.\" | ||
| 6 | +.\" Some roff macros, for reference: | ||
| 7 | +.\" .nh disable hyphenation | ||
| 8 | +.\" .hy enable hyphenation | ||
| 9 | +.\" .ad l left justify | ||
| 10 | +.\" .ad b justify to both left and right margins | ||
| 11 | +.\" .nf disable filling | ||
| 12 | +.\" .fi enable filling | ||
| 13 | +.\" .br insert line break | ||
| 14 | +.\" .sp <n> insert n+1 empty lines | ||
| 15 | +.\" for manpage-specific macros, see man(7) | ||
| 16 | +.SH NAME | ||
| 17 | +cyclictest \- High resolution test program | ||
| 18 | +.SH SYNOPSIS | ||
| 19 | +.SY cyclictest | ||
| 20 | +.RI "[ \-hfmnqrsvMS ] [\-a " proc " ] [\-A " align " ] [\-b " usec " ] [\-c " clock " ] [\-d " dist " ] \ | ||
| 21 | +[\-h " histogram " ] [\-i " intv " ] [\-\-json " filename " ] [\-l " loop " ] [\-o " red " ] \ | ||
| 22 | +[\-p " prio " ] [\-t " num " ] [\-D " time "] [\-w] [\-W] [\-y " policy " ] [ \-S | \-U ]" | ||
| 23 | + | ||
| 24 | +.\" .SH DESCRIPTION | ||
| 25 | +.\" This manual page documents briefly the | ||
| 26 | +.\" .B cyclictest commands. | ||
| 27 | +.\" .PP | ||
| 28 | +.\" \fI<whatever>\fP escape sequences to invode bold face and italics, respectively. | ||
| 29 | +.\" \fBcyclictest\fP is a program that... | ||
| 30 | +.SH OPTIONS | ||
| 31 | +These programs follow the usual GNU command line syntax, with long | ||
| 32 | +options starting with two dashes ('\-\-'). | ||
| 33 | +.br | ||
| 34 | +A summary of options is included below. | ||
| 35 | +.\" For a complete description, see the Info files. | ||
| 36 | +.TP | ||
| 37 | +.B \-a, \-\-affinity[=PROC-SET] | ||
| 38 | +Run threads on the set of processors given by PROC-SET. If PROC-SET is not | ||
| 39 | +specified, all processors will be used. Threads will be assigned to processors | ||
| 40 | +in the set in numeric order, in a round\-robin fashion. | ||
| 41 | +.br | ||
| 42 | +The set of processors can be specified as A,B,C, or A-C, or A-B,D-F, and so on*. | ||
| 43 | +The ! character can be used to negate a set. For example, !B-D means to use all | ||
| 44 | +available CPUs except B through D. The cpu numbers are the same as shown in the | ||
| 45 | +.I processor | ||
| 46 | +field in /proc/cpuinfo. See numa(3) for more information on specifying CPU sets. | ||
| 47 | +* Support for CPU sets requires libnuma version >= 2. For libnuma v1, PROC-SET, | ||
| 48 | +if specified, must be a single CPU number. | ||
| 49 | +.TP | ||
| 50 | +.B \-A, \-\-align=USEC | ||
| 51 | +Align thread wakeups to a specific offset in microseconds | ||
| 52 | +.TP | ||
| 53 | +.B \-b, \-\-breaktrace=USEC | ||
| 54 | +Send break trace command when latency > USEC | ||
| 55 | +.TP | ||
| 56 | +.B \-c, \-\-clock=CLOCK | ||
| 57 | +select clock | ||
| 58 | +.br | ||
| 59 | +0 = CLOCK_MONOTONIC (default) | ||
| 60 | +.br | ||
| 61 | +1 = CLOCK_REALTIME | ||
| 62 | +.TP | ||
| 63 | +.B \-\-deepest\-idle\-state=n | ||
| 64 | +Reduce exit from idle latency by limiting idle state up to n on used cpus (-1 disables all idle states). Power management is not suppresed on other cpus. | ||
| 65 | +.TP | ||
| 66 | +.B \-\-default\-system | ||
| 67 | +Don't attempt to tune the system from cyclictest. Power management is not suppressed. This might give poorer results, but will allow you to discover if you need to tune the system. | ||
| 68 | +.TP | ||
| 69 | +.B \-d, \-\-distance=DIST | ||
| 70 | +Distance of thread intervals in us, default = 500 | ||
| 71 | +.TP | ||
| 72 | +.B \-D, \-\-duration=TIME | ||
| 73 | +Specify a length for the test run. | ||
| 74 | +.br | ||
| 75 | +Append 'm', 'h', or 'd' to specify minutes, hours or days. | ||
| 76 | +.TP | ||
| 77 | +.B \-F, \-\-fifo=<path> | ||
| 78 | +Create a named pipe at path and write stats to it | ||
| 79 | +.TP | ||
| 80 | +.B \-h, \-\-histogram=US | ||
| 81 | +Dump latency histogram to stdout after the run. US is the max latency time to be be tracked in microseconds. This option runs all threads at the same priority. | ||
| 82 | +.TP | ||
| 83 | +.B \-H, \-\-histofall=MAXLATENCYINUS | ||
| 84 | +Same as -h except that an additional histogram column is displayed at the right that contains summary data of all thread histograms. If cyclictest runs a single thread only, the -H option is equivalent to -h. | ||
| 85 | +.TP | ||
| 86 | +.B \-\-histfile=<path> | ||
| 87 | +Dump the latency histogram to <path> instead of stdout. | ||
| 88 | +.TP | ||
| 89 | +.B \-i, \-\-interval=INTV | ||
| 90 | +Set the base interval of the thread(s) in microseconds (default is 1000us). This sets the interval of the first thread. See also \-d. | ||
| 91 | +.TP | ||
| 92 | +.B \-\-json=FILENAME | ||
| 93 | +Write final results into FILENAME, JSON formatted. | ||
| 94 | +.TP | ||
| 95 | +.B \-\-laptop | ||
| 96 | +Save battery when running cyclictest. This will give you poorer realtime results, but will not drain your battery so quickly. | ||
| 97 | +.TP | ||
| 98 | +.B \-\-latency=PM_Q0S | ||
| 99 | +power management latency target value. This value is written to /dev/cpu_dma_latency and affects c-states. The default is 0 | ||
| 100 | +.TP | ||
| 101 | +.B \-l, \-\-loops=LOOPS | ||
| 102 | +Set the number of loops. The default is 0 (endless). This option is useful for automated tests with a given number of test cycles. Cyclictest is stopped once the number of timer intervals has been reached. | ||
| 103 | +.TP | ||
| 104 | +.B \-\-mainaffinity=CPUSET | ||
| 105 | +Run the main thread on CPU #N. This only affects the main thread and not the measurement threads | ||
| 106 | +.TP | ||
| 107 | +.B \-m, \-\-mlockall | ||
| 108 | +Lock current and future memory allocations to prevent being paged out | ||
| 109 | +.TP | ||
| 110 | +.B \\-M, \-\-refresh_on_max | ||
| 111 | +Delay updating the screen until a new max latency is hit. (useful for | ||
| 112 | +running cyclictest on low-bandwidth connections) | ||
| 113 | +.TP | ||
| 114 | +.B \-N, \-\-nsecs | ||
| 115 | +Show results in nanoseconds instead of microseconds, which is the default unit. | ||
| 116 | +.TP | ||
| 117 | +.B \-o, \-\-oscope=RED | ||
| 118 | +Oscilloscope mode, reduce verbose output by RED. | ||
| 119 | +.TP | ||
| 120 | +.B \-p, \-\-prio=PRIO | ||
| 121 | +Set the priority of the first thread. The given priority is set to the first test thread. Each further thread gets a lower priority: | ||
| 122 | +Priority(Thread N) = max(Priority(Thread N\-1) \- 1, 0) | ||
| 123 | +.TP | ||
| 124 | +.B \-\-policy=NAME | ||
| 125 | +set the scheduler policy of the measurement threads | ||
| 126 | +where NAME is one of: other, normal, batch, idle, fifo, rr | ||
| 127 | +.TP | ||
| 128 | +.B \-\-priospread | ||
| 129 | +spread priority levels starting at a specified value | ||
| 130 | +.TP | ||
| 131 | +.B \-q, \-\-quiet | ||
| 132 | +Print a summary only on exit. Useful for automated tests, where only the summary output needs to be captured. | ||
| 133 | +.TP | ||
| 134 | +.B \-r, \-\-relative | ||
| 135 | +Use relative timers instead of absolute. The default behaviour of the tests is to use absolute timers. This option is there for completeness and should not be used for reproducible tests. | ||
| 136 | +.TP | ||
| 137 | +.B \-R, \-\-resolution | ||
| 138 | +Check clock resolution, calling clock_gettime() many times. List of lock_gettime() values will be reported with -X | ||
| 139 | +.TP | ||
| 140 | +.B \-\-secaligned [USEC] | ||
| 141 | +align thread wakeups to the next full second and apply the optional offset. | ||
| 142 | +.TP | ||
| 143 | +.B \-s, \-\-system | ||
| 144 | +Use sys_nanosleep and sys_setitimer instead of posix timers. Note, that \-s can only be used with one thread because itimers are per process and not per thread. \-s uses the nanosleep syscall and is not restricted to one thread. | ||
| 145 | +.TP | ||
| 146 | +.B \\-S, \-\-smp | ||
| 147 | +Set options for standard testing on SMP systems. Equivalent to using | ||
| 148 | +the options: "\-t \-a" as well keeping any specified priority | ||
| 149 | +equal across all threads | ||
| 150 | +.TP | ||
| 151 | +.B \-\-spike=<trigger> | ||
| 152 | +record all spikes > trigger | ||
| 153 | +.TP | ||
| 154 | +.B \-\-spike-nodes=[num of nodes] | ||
| 155 | +These are the maximum number of spikes we can record. | ||
| 156 | +.br | ||
| 157 | +The default is 1024 if not specified. | ||
| 158 | +.TP | ||
| 159 | +.B \\-\-smi | ||
| 160 | +Enable SMI count/detection on processors with SMI count support. | ||
| 161 | +.TP | ||
| 162 | +.B \-t, \-\-threads[=NUM] | ||
| 163 | +Set the number of test threads (default is 1). Create NUM test threads. If NUM is not specified, NUM is set to | ||
| 164 | +the number of available CPUs. See \-d, \-i and \-p for further information. | ||
| 165 | +.TP | ||
| 166 | +.B \-\-tracemark | ||
| 167 | +write a trace mark when \-b latency is exceeded. | ||
| 168 | +.TP | ||
| 169 | +.B \-u, \-\-unbuffered | ||
| 170 | +force unbuffered output for live processing | ||
| 171 | +.TP | ||
| 172 | +.B \-v, \-\-verbose | ||
| 173 | +Output values on stdout for statistics. This option is used to gather statistical information about the latency distribution. The output is sent to stdout. The output format is: | ||
| 174 | + | ||
| 175 | +n:c:v | ||
| 176 | + | ||
| 177 | +where n=task number c=count v=latency value in us. | ||
| 178 | +.TP | ||
| 179 | +.B \-\-dbg_cyclictest | ||
| 180 | +Print info userful for debugging cyclictest | ||
| 181 | +.TP | ||
| 182 | +.B \-x, \-\-posix_timers | ||
| 183 | +Use POSIX timers instead of clock_nanosleep. | ||
| 184 | + | ||
| 185 | +.SH SEE ALSO | ||
| 186 | +.BR numa (3), | ||
| 187 | +.BR numactl (8), | ||
| 188 | +.\" .br | ||
| 189 | +.\" The programs are documented fully by | ||
| 190 | +.\" .IR "The Rise and Fall of a Fooish Bar" , | ||
| 191 | +.\" available via the Info system. | ||
| 192 | +.SH AUTHOR | ||
| 193 | +cyclictest was written by Thomas Gleixner <tglx@linuxtronix.de>. | ||
| 194 | +.PP | ||
| 195 | +This manual page was written by Alessio Igor Bogani <abogani@texware.it>, | ||
| 196 | +for the Debian project (but may be used by others). | ||
| 197 | +.br | ||
| 198 | +Updated by John Kacur <jkacur@redhat.com> | ||
| @@ -0,0 +1,45 @@ | |||
| 1 | +.\" Hey, EMACS: -*- nroff -*- | ||
| 2 | +.TH GET_CYCLICTEST_SNAPSHOT 8 "July 6, 2020" | ||
| 3 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 4 | +.\" Please adjust this date whenever revising the manpage. | ||
| 5 | +.\" | ||
| 6 | +.\" Some roff macros, for reference: | ||
| 7 | +.\" .nh disable hyphenation | ||
| 8 | +.\" .hy enable hyphenation | ||
| 9 | +.\" .ad l left justify | ||
| 10 | +.\" .ad b justify to both left and right margins | ||
| 11 | +.\" .nf disable filling | ||
| 12 | +.\" .fi enable filling | ||
| 13 | +.\" .br insert line break | ||
| 14 | +.\" .sp <n> insert n+1 empty lines | ||
| 15 | +.\" for manpage-specific macros, see man(7) | ||
| 16 | +.SH NAME | ||
| 17 | +get_cyclictest_snapshot \- Get a snapshot of running instances of cyclictest | ||
| 18 | +.SH SYNOPSIS | ||
| 19 | +.SY get_cyclictest_snapshot | ||
| 20 | +.OP [\-h]\ [\-l]\ [\-s\ [pid [pid ...]]] | ||
| 21 | +.br | ||
| 22 | +.OP [\-p\ [pid\ [pid ...]]] | ||
| 23 | +.SH OPTIONS | ||
| 24 | +Without options, this program will send USR2 to cyclictest to create a snapshot and then print it out. | ||
| 25 | +.br | ||
| 26 | +.TP | ||
| 27 | +Optional arguments: | ||
| 28 | +.TP | ||
| 29 | +.B -h, --help | ||
| 30 | +show this help message and exit | ||
| 31 | +.TP | ||
| 32 | +.B -l, --list | ||
| 33 | +list the main pid(s) of running instances of cyclictest | ||
| 34 | +.TP | ||
| 35 | +.B -s [pid [pid ...]], --snapshot [pid [pid ...]] | ||
| 36 | +take a snapshot of running instances of cyclictest | ||
| 37 | +.br | ||
| 38 | +by sending USR2 to cyclictest | ||
| 39 | +.TP | ||
| 40 | +.B -p [pid [pid ...]], --print [pid [pid ...]] | ||
| 41 | +print the snapshots | ||
| 42 | +.SH SEE ALSO | ||
| 43 | +.BR cyclictest (8), | ||
| 44 | +.SH AUTHOR | ||
| 45 | +get_cyclictest_snapshot was written by John Kacur <jkacur at redhat dot com> | ||
| @@ -0,0 +1,102 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
| 2 | +""" Program to get a snapshot of a running instance of cyclictest """ | ||
| 3 | + | ||
| 4 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 5 | +# Copyright (C) 2020 John Kacur <jkacur@redhat.com> | ||
| 6 | + | ||
| 7 | +import subprocess | ||
| 8 | +import argparse | ||
| 9 | +import re | ||
| 10 | +import glob | ||
| 11 | +import sys | ||
| 12 | + | ||
| 13 | +parser = argparse.ArgumentParser(description='Get a snapshot of running instances of cyclictest') | ||
| 14 | +parser.add_argument('-l', '--list', action='store_true', help='list the main pid(s) of running instances of cyclictest') | ||
| 15 | +parser.add_argument('-s', '--snapshot', nargs='*', metavar='pid', help='take a snapshot of running instances of cyclictest') | ||
| 16 | +parser.add_argument('-p', '--print', nargs='*', metavar='pid', help='print the snapshots') | ||
| 17 | +args = parser.parse_args() | ||
| 18 | + | ||
| 19 | +class Snapshot: | ||
| 20 | + """ Class for getting a snapshot of a running cyclictest instance """ | ||
| 21 | + | ||
| 22 | + warned = False | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + def print_warning(cls): | ||
| 26 | + """ print a warning one time only even if called multiple times """ | ||
| 27 | + if not cls.warned: | ||
| 28 | + cls.warned = True | ||
| 29 | + print("No cyclictest instance found") | ||
| 30 | + | ||
| 31 | + def __init__(self): | ||
| 32 | + self.pids = [] | ||
| 33 | + self.shm_files = [] | ||
| 34 | + self.refresh() | ||
| 35 | + | ||
| 36 | + def refresh(self): | ||
| 37 | + """ Create a list of running cyclictest instances. """ | ||
| 38 | + self.pids = [] | ||
| 39 | + self.shm_files = glob.glob('/dev/shm/cyclictest*') | ||
| 40 | + self.shm_files.sort() | ||
| 41 | + for shm_file in self.shm_files: | ||
| 42 | + pid = re.search('[0-9]*$', shm_file).group() | ||
| 43 | + self.pids += [pid] | ||
| 44 | + | ||
| 45 | + def take_snapshot(self, spids=None): | ||
| 46 | + """ Send USR2 to all running instances of cyclictest, | ||
| 47 | + or just to a specific pid (spids) if specified. """ | ||
| 48 | + if spids is None: | ||
| 49 | + if not self.pids: | ||
| 50 | + Snapshot.print_warning() | ||
| 51 | + for pid in self.pids: | ||
| 52 | + subprocess.run(["kill", "-s", "USR2", pid]) | ||
| 53 | + else: | ||
| 54 | + for pid in spids: | ||
| 55 | + subprocess.run(["kill", "-s", "USR2", pid]) | ||
| 56 | + | ||
| 57 | + def print_pids(self): | ||
| 58 | + """ Print the list of pids of running cyclictest instances. """ | ||
| 59 | + if not self.pids: | ||
| 60 | + Snapshot.print_warning() | ||
| 61 | + for pid in self.pids: | ||
| 62 | + print(pid) | ||
| 63 | + | ||
| 64 | + def print(self, spids=None): | ||
| 65 | + """ Print the data in /dev/shm/cyclictest* """ | ||
| 66 | + if spids is None: | ||
| 67 | + if not self.shm_files: | ||
| 68 | + Snapshot.print_warning() | ||
| 69 | + for shm_file in self.shm_files: | ||
| 70 | + with open(shm_file, 'r') as f: | ||
| 71 | + data = f.read() | ||
| 72 | + print(data) | ||
| 73 | + else: | ||
| 74 | + for spid in spids: | ||
| 75 | + if spid in self.pids: | ||
| 76 | + shm_file = '/dev/shm/cyclictest' + spid | ||
| 77 | + with open(shm_file, 'r') as f: | ||
| 78 | + data = f.read() | ||
| 79 | + print(data) | ||
| 80 | + else: | ||
| 81 | + Snapshot.print_warning() | ||
| 82 | + | ||
| 83 | +snapshot = Snapshot() | ||
| 84 | + | ||
| 85 | +if args.list: | ||
| 86 | + snapshot.print_pids() | ||
| 87 | + | ||
| 88 | +if args.snapshot is not None: | ||
| 89 | + if args.snapshot: | ||
| 90 | + snapshot.take_snapshot(args.snapshot) | ||
| 91 | + else: | ||
| 92 | + snapshot.take_snapshot() | ||
| 93 | + | ||
| 94 | +if args.print is not None: | ||
| 95 | + if args.print: | ||
| 96 | + snapshot.print(args.print) | ||
| 97 | + else: | ||
| 98 | + snapshot.print() | ||
| 99 | + | ||
| 100 | +if len(sys.argv) == 1: | ||
| 101 | + snapshot.take_snapshot() | ||
| 102 | + snapshot.print() | ||
| @@ -0,0 +1,85 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +/* | ||
| 3 | + * A numa library for cyclictest. | ||
| 4 | + * | ||
| 5 | + * (C) 2010 John Kacur <jkacur@redhat.com> | ||
| 6 | + * (C) 2010 Clark Williams <williams@redhat.com> | ||
| 7 | + * | ||
| 8 | + */ | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +static int numa = 0; | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | +static void * | ||
| 21 | +threadalloc(size_t size, int node) | ||
| 22 | +{ | ||
| 23 | + if (node == -1) | ||
| 24 | + return malloc(size); | ||
| 25 | + return numa_alloc_onnode(size, node); | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | +static void | ||
| 29 | +threadfree(void *ptr, size_t size, int node) | ||
| 30 | +{ | ||
| 31 | + if (node == -1) | ||
| 32 | + free(ptr); | ||
| 33 | + else | ||
| 34 | + numa_free(ptr, size); | ||
| 35 | +} | ||
| 36 | + | ||
| 37 | +static void rt_numa_set_numa_run_on_node(int node, int cpu) | ||
| 38 | +{ | ||
| 39 | + int res; | ||
| 40 | + res = numa_run_on_node(node); | ||
| 41 | + if (res) | ||
| 42 | + warn("Could not set NUMA node %d for thread %d: %s\n", | ||
| 43 | + node, cpu, strerror(errno)); | ||
| 44 | + return; | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +static void *rt_numa_numa_alloc_onnode(size_t size, int node, int cpu) | ||
| 48 | +{ | ||
| 49 | + void *stack; | ||
| 50 | + stack = numa_alloc_onnode(size, node); | ||
| 51 | + if (stack == NULL) | ||
| 52 | + fatal("failed to allocate %d bytes on node %d for cpu %d\n", | ||
| 53 | + size, node, cpu); | ||
| 54 | + return stack; | ||
| 55 | +} | ||
| 56 | + | ||
| 57 | +/* | ||
| 58 | + * Use new bit mask CPU affinity behavior | ||
| 59 | + */ | ||
| 60 | +static int rt_numa_numa_node_of_cpu(int cpu) | ||
| 61 | +{ | ||
| 62 | + int node; | ||
| 63 | + node = numa_node_of_cpu(cpu); | ||
| 64 | + if (node == -1) | ||
| 65 | + fatal("invalid cpu passed to numa_node_of_cpu(%d)\n", cpu); | ||
| 66 | + return node; | ||
| 67 | +} | ||
| 68 | + | ||
| 69 | +static inline unsigned int rt_numa_bitmask_isbitset( const struct bitmask *mask, | ||
| 70 | + unsigned long i) | ||
| 71 | +{ | ||
| 72 | + return numa_bitmask_isbitset(mask,i); | ||
| 73 | +} | ||
| 74 | + | ||
| 75 | +static inline struct bitmask* rt_numa_parse_cpustring(const char* s) | ||
| 76 | +{ | ||
| 77 | + return numa_parse_cpustring_all(s); | ||
| 78 | +} | ||
| 79 | + | ||
| 80 | +static inline void rt_bitmask_free(struct bitmask *mask) | ||
| 81 | +{ | ||
| 82 | + numa_bitmask_free(mask); | ||
| 83 | +} | ||
| 84 | + | ||
| 85 | + | ||
| @@ -0,0 +1,114 @@ | |||
| 1 | +.TH "hackbench" "8" "September 19, 2020" "" "" | ||
| 2 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | +.SH "NAME" | ||
| 4 | +hackbench \- scheduler benchmark/stress test | ||
| 5 | +.SH "SYNOPSIS" | ||
| 6 | +.B hackbench | ||
| 7 | +.RI "[\-f|\-\-fds NUM] " | ||
| 8 | +.RI "[\-F|\-\-fifo] " | ||
| 9 | +.RI "[\-g|\-\-groups NUM] " | ||
| 10 | +.RI "[\-h|\-\-help] " | ||
| 11 | +.RI "[\-l|\-\-loops LOOPS] " | ||
| 12 | +.RI "[\-p|\-\-pipe] " | ||
| 13 | +.RI "[\-s|\-\-datasize SIZE] " | ||
| 14 | +.RI "[\-T|\-\-threads]" | ||
| 15 | +.RI "[\-P|\-\-process]" | ||
| 16 | + | ||
| 17 | +.SH "DESCRIPTION" | ||
| 18 | +Hackbench is both a benchmark and a stress test for the Linux kernel | ||
| 19 | +scheduler. It's main job is to create a specified number of pairs of | ||
| 20 | +schedulable entities (either threads or traditional processes) which | ||
| 21 | +communicate via either sockets or pipes and time how long it takes for | ||
| 22 | +each pair to send data back and forth. | ||
| 23 | + | ||
| 24 | +.SH "OPTIONS" | ||
| 25 | +These programs follow the usual GNU command line syntax, with long | ||
| 26 | +options starting with two dashes ("\-\-"). | ||
| 27 | +.br | ||
| 28 | +A summary of options is included below. | ||
| 29 | +.TP | ||
| 30 | +.B \-f, \-\-fds=NUM | ||
| 31 | +Defines how many file descriptors each child should use. | ||
| 32 | +Note that the effective number will be twice the amount you set here, | ||
| 33 | +as the sender and receiver children will each open the given amount of | ||
| 34 | +file descriptors. | ||
| 35 | +.TP | ||
| 36 | +.B \-F,\-\-fifo | ||
| 37 | +Change the main thread to SCHED_FIFO after creating workers. | ||
| 38 | +This allows the management thread to run after many workers are created. | ||
| 39 | +.TP | ||
| 40 | +.B \-g, \-\-groups=NUM | ||
| 41 | +Defines how many groups of senders and receivers should be started | ||
| 42 | +.TP | ||
| 43 | +.B \-h, \-\-help | ||
| 44 | +.TP | ||
| 45 | +.B \-l, \-\-loops=LOOPS | ||
| 46 | +How many messages each sender/receiver pair should send | ||
| 47 | +.TP | ||
| 48 | +.B \-p, \-\-pipe | ||
| 49 | +Sends the data via a pipe instead of the socket (default) | ||
| 50 | +.TP | ||
| 51 | +.B \-s, \-\-datasize=SIZE | ||
| 52 | +Sets the amount of data to send in each message | ||
| 53 | +.TP | ||
| 54 | +.B \-T, \-\-threads | ||
| 55 | +Each sender/receiver child will be a POSIX thread of the parent. | ||
| 56 | +.TP | ||
| 57 | +.B \-P, \-\-process | ||
| 58 | +Hackbench will use fork() on all children (default behaviour) | ||
| 59 | +.br | ||
| 60 | +Shows a simple help screen | ||
| 61 | +.SH "EXAMPLES" | ||
| 62 | +.LP | ||
| 63 | +Running hackbench without any options will give default behaviour, | ||
| 64 | +using fork() and sending data between senders and receivers via sockets. | ||
| 65 | +.LP | ||
| 66 | +user@host: ~ $ hackbench | ||
| 67 | +.br | ||
| 68 | +Running in process mode with 10 groups using 40 file descriptors each (== 400 tasks) | ||
| 69 | +.br | ||
| 70 | +Each sender will pass 100 messages of 100 bytes | ||
| 71 | +.br | ||
| 72 | +Time: 0.890 | ||
| 73 | +.LP | ||
| 74 | +To use pipes between senders and receivers and using threads instead of fork(), run | ||
| 75 | +.LP | ||
| 76 | +user@host: ~ $ hackbench \-\-pipe \-\-threads (or hackbench \-p \-T) | ||
| 77 | +.br | ||
| 78 | +Running in threaded mode with 10 groups using 40 file descriptors each (== 400 tasks) | ||
| 79 | +.br | ||
| 80 | +Each sender will pass 100 messages of 100 bytes | ||
| 81 | +.br | ||
| 82 | +Time: 0.497 | ||
| 83 | +.LP | ||
| 84 | +Set the datasize to 512 bytes, do 200 messages per sender/receiver pairs and use 15 groups | ||
| 85 | +using 25 file descriptors per child, in process mode. | ||
| 86 | +.LP | ||
| 87 | +user@host: ~ $ hackbench \-s 512 \-l 200 \-g 15 \-f 25 \-P | ||
| 88 | +.br | ||
| 89 | +Running in process mode with 15 groups using 50 file descriptors each (== 750 tasks) | ||
| 90 | +.br | ||
| 91 | +Each sender will pass 200 messages of 512 bytes | ||
| 92 | +.br | ||
| 93 | +Time: 4.497 | ||
| 94 | +.SH "AUTHORS" | ||
| 95 | +.LP | ||
| 96 | +hackbench was written by Rusty Russell <rusty@rustcorp.com.au> | ||
| 97 | +with contributions from Yanmin Zhang <yanmin_zhang@linux.intel.com>, | ||
| 98 | +Ingo Molnar <mingo@elte.hu> and David Sommerseth <davids@redhat.com> | ||
| 99 | + | ||
| 100 | +This manual page was written by Clark Williams <williams@redhat.com> | ||
| 101 | +and David Sommerseth <davids@redhat.com> | ||
| 102 | +.SH "HISTORY" | ||
| 103 | +This version of hackbench is based on the code downloaded from http://people.redhat.com/mingo/cfs\-scheduler/tools/hackbench.c. | ||
| 104 | +Yanmin Zhang merged the original hackbench code from | ||
| 105 | +.br | ||
| 106 | +http://devresources.linuxfoundation.org/craiger/hackbench/src/hackbench.c | ||
| 107 | +which uses fork() and a modified version from | ||
| 108 | +.br | ||
| 109 | +http://www.bullopensource.org/posix/pi\-futex/hackbench_pth.c | ||
| 110 | +which uses pthread only and gave the possibility to change | ||
| 111 | +behaviour at run time. Hackbench have since then gone through some | ||
| 112 | +more rewriting to improve error handling and proper tracking of fork()ed | ||
| 113 | +children, to avoid leaving zombies on the system if hackbench stops | ||
| 114 | +unexpectedly. | ||
| @@ -0,0 +1,576 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +/* | ||
| 3 | + * This is the latest version of hackbench.c, that tests scheduler and | ||
| 4 | + * unix-socket (or pipe) performance. | ||
| 5 | + * | ||
| 6 | + * Usage: hackbench [-pipe] <num groups> [process|thread] [loops] | ||
| 7 | + * | ||
| 8 | + * Build it with: | ||
| 9 | + * gcc -g -Wall -O2 -o hackbench hackbench.c -lpthread | ||
| 10 | + * | ||
| 11 | + * Downloaded from http://people.redhat.com/mingo/cfs-scheduler/tools/hackbench.c | ||
| 12 | + * February 19 2010. | ||
| 13 | + * | ||
| 14 | + */ | ||
| 15 | + | ||
| 16 | +/* Test groups of 20 processes spraying to 20 receivers */ | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + | ||
| 35 | + | ||
| 36 | + | ||
| 37 | +static unsigned int datasize = 100; | ||
| 38 | +static unsigned int loops = 100; | ||
| 39 | +static unsigned int num_groups = 10; | ||
| 40 | +static unsigned int num_fds = 20; | ||
| 41 | +static unsigned int fifo = 0; | ||
| 42 | + | ||
| 43 | +/* | ||
| 44 | + * 0 means thread mode and others mean process (default) | ||
| 45 | + */ | ||
| 46 | + | ||
| 47 | + | ||
| 48 | + | ||
| 49 | +static unsigned int process_mode = PROCESS_MODE; | ||
| 50 | + | ||
| 51 | +static int use_pipes = 0; | ||
| 52 | +static int use_inet = 0; | ||
| 53 | + | ||
| 54 | +struct sender_context { | ||
| 55 | + unsigned int num_fds; | ||
| 56 | + int ready_out; | ||
| 57 | + int wakefd; | ||
| 58 | + int out_fds[0]; | ||
| 59 | +}; | ||
| 60 | + | ||
| 61 | +struct receiver_context { | ||
| 62 | + unsigned int num_packets; | ||
| 63 | + int in_fds[2]; | ||
| 64 | + int ready_out; | ||
| 65 | + int wakefd; | ||
| 66 | +}; | ||
| 67 | + | ||
| 68 | + | ||
| 69 | +typedef union { | ||
| 70 | + pthread_t threadid; | ||
| 71 | + pid_t pid; | ||
| 72 | +} childinfo_t; | ||
| 73 | + | ||
| 74 | +childinfo_t *child_tab = NULL; | ||
| 75 | +unsigned int total_children = 0; | ||
| 76 | +unsigned int signal_caught = 0; | ||
| 77 | + | ||
| 78 | +static jmp_buf jmpbuf; | ||
| 79 | + | ||
| 80 | +inline static void sneeze(const char *msg) { | ||
| 81 | + /* Avoid calling these functions when called from a code path | ||
| 82 | + * which involves sigcatcher(), as they are not reentrant safe. | ||
| 83 | + */ | ||
| 84 | + if( !signal_caught ) { | ||
| 85 | + fprintf(stderr, "%s (error: %s)\n", msg, strerror(errno)); | ||
| 86 | + } | ||
| 87 | +} | ||
| 88 | + | ||
| 89 | +static void barf(const char *msg) | ||
| 90 | +{ | ||
| 91 | + sneeze(msg); | ||
| 92 | + exit(1); | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +static void print_usage_exit(int error) | ||
| 96 | +{ | ||
| 97 | + printf("hackbench V %1.2f\n", VERSION); | ||
| 98 | + printf("Usage:\n" | ||
| 99 | + "hackbench <options>\n\n" | ||
| 100 | + "-f --fds=NUM number of fds\n" | ||
| 101 | + "-F --fifo use SCHED_FIFO for main thread\n" | ||
| 102 | + "-g --groups=NUM number of groups to be used\n" | ||
| 103 | + "-h --help print this message\n" | ||
| 104 | + "-l --loops=LOOPS how many message should be send\n" | ||
| 105 | + "-p --pipe send data via a pipe\n" | ||
| 106 | + "-i --inet send data via a inet tcp connection\n" | ||
| 107 | + "-s --datasize=SIZE message size\n" | ||
| 108 | + "-T --threads use POSIX threads\n" | ||
| 109 | + "-P --process use fork (default)\n" | ||
| 110 | + ); | ||
| 111 | + exit(error); | ||
| 112 | +} | ||
| 113 | + | ||
| 114 | +static int inet_socketpair(int fds[2]) | ||
| 115 | +{ | ||
| 116 | + int s1, s2; | ||
| 117 | + struct sockaddr_in sin; | ||
| 118 | + unsigned long ul = 1; | ||
| 119 | + | ||
| 120 | + if ((s1 = socket(AF_INET, SOCK_STREAM, 0)) < 0) | ||
| 121 | + barf("socket"); | ||
| 122 | + if ((s2 = socket(AF_INET, SOCK_STREAM, 0)) < 0) | ||
| 123 | + barf("socket"); | ||
| 124 | + | ||
| 125 | + socklen_t len = sizeof(sin); | ||
| 126 | + bzero(&sin, len); | ||
| 127 | + sin.sin_family = AF_INET; | ||
| 128 | + sin.sin_port = 0; | ||
| 129 | + sin.sin_addr.s_addr = inet_addr("127.0.0.1"); | ||
| 130 | + | ||
| 131 | + if (bind(s1, &sin, len) < 0) | ||
| 132 | + barf("bind"); | ||
| 133 | + if (getsockname(s1, &sin, &len) < 0) | ||
| 134 | + barf("getsockname"); | ||
| 135 | + if (listen(s1, 10) < 0) | ||
| 136 | + barf("listen"); | ||
| 137 | + if (ioctl(s2, FIONBIO, &ul) < 0) | ||
| 138 | + barf("ioctl"); | ||
| 139 | + if (ioctl(s1, FIONBIO, &ul) < 0) | ||
| 140 | + barf("ioctl"); | ||
| 141 | + if (connect(s2, &sin, len) < 0) | ||
| 142 | + barf("connect"); | ||
| 143 | + if ((fds[0] = accept(s1, &sin, &len)) < 0) | ||
| 144 | + barf("accept"); | ||
| 145 | + ul = 0; | ||
| 146 | + if (ioctl(s2, FIONBIO, &ul) < 0) | ||
| 147 | + barf("ioctl"); | ||
| 148 | + fds[1] = s2; | ||
| 149 | + close(s1); | ||
| 150 | + | ||
| 151 | + return 0; | ||
| 152 | +} | ||
| 153 | + | ||
| 154 | +static void fdpair(int fds[2]) | ||
| 155 | +{ | ||
| 156 | + if (use_pipes) { | ||
| 157 | + if (pipe(fds) == 0) | ||
| 158 | + return; | ||
| 159 | + } else if (use_inet) { | ||
| 160 | + if (inet_socketpair(fds) == 0) | ||
| 161 | + return; | ||
| 162 | + } else { | ||
| 163 | + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0) | ||
| 164 | + return; | ||
| 165 | + } | ||
| 166 | + barf("Creating fdpair"); | ||
| 167 | +} | ||
| 168 | + | ||
| 169 | +/* Block until we're ready to go */ | ||
| 170 | +static void ready(int ready_out, int wakefd) | ||
| 171 | +{ | ||
| 172 | + char dummy = '*'; | ||
| 173 | + struct pollfd pollfd = { .fd = wakefd, .events = POLLIN }; | ||
| 174 | + | ||
| 175 | + /* Tell them we're ready. */ | ||
| 176 | + if (write(ready_out, &dummy, 1) != 1) | ||
| 177 | + barf("CLIENT: ready write"); | ||
| 178 | + | ||
| 179 | + /* Wait for "GO" signal */ | ||
| 180 | + if (poll(&pollfd, 1, -1) != 1) | ||
| 181 | + barf("poll"); | ||
| 182 | +} | ||
| 183 | + | ||
| 184 | +static void reset_worker_signals(void) | ||
| 185 | +{ | ||
| 186 | + signal(SIGTERM, SIG_DFL); | ||
| 187 | + signal(SIGINT, SIG_DFL); | ||
| 188 | +} | ||
| 189 | + | ||
| 190 | +/* Sender sprays loops messages down each file descriptor */ | ||
| 191 | +static void *sender(struct sender_context *ctx) | ||
| 192 | +{ | ||
| 193 | + char data[datasize]; | ||
| 194 | + unsigned int i, j; | ||
| 195 | + | ||
| 196 | + reset_worker_signals(); | ||
| 197 | + ready(ctx->ready_out, ctx->wakefd); | ||
| 198 | + memset(&data, '-', datasize); | ||
| 199 | + | ||
| 200 | + /* Now pump to every receiver. */ | ||
| 201 | + for (i = 0; i < loops; i++) { | ||
| 202 | + for (j = 0; j < ctx->num_fds; j++) { | ||
| 203 | + int ret; | ||
| 204 | + size_t done = 0; | ||
| 205 | + | ||
| 206 | +again: | ||
| 207 | + ret = write(ctx->out_fds[j], data + done, sizeof(data)-done); | ||
| 208 | + if (ret < 0) | ||
| 209 | + barf("SENDER: write"); | ||
| 210 | + done += ret; | ||
| 211 | + if (done < sizeof(data)) | ||
| 212 | + goto again; | ||
| 213 | + } | ||
| 214 | + } | ||
| 215 | + | ||
| 216 | + return NULL; | ||
| 217 | +} | ||
| 218 | + | ||
| 219 | + | ||
| 220 | +/* One receiver per fd */ | ||
| 221 | +static void *receiver(struct receiver_context* ctx) | ||
| 222 | +{ | ||
| 223 | + unsigned int i; | ||
| 224 | + | ||
| 225 | + reset_worker_signals(); | ||
| 226 | + if (process_mode == PROCESS_MODE) | ||
| 227 | + close(ctx->in_fds[1]); | ||
| 228 | + | ||
| 229 | + /* Wait for start... */ | ||
| 230 | + ready(ctx->ready_out, ctx->wakefd); | ||
| 231 | + | ||
| 232 | + /* Receive them all */ | ||
| 233 | + for (i = 0; i < ctx->num_packets; i++) { | ||
| 234 | + char data[datasize]; | ||
| 235 | + int ret; | ||
| 236 | + size_t done = 0; | ||
| 237 | + | ||
| 238 | +again: | ||
| 239 | + ret = read(ctx->in_fds[0], data + done, datasize - done); | ||
| 240 | + if (ret < 0) | ||
| 241 | + barf("SERVER: read"); | ||
| 242 | + done += ret; | ||
| 243 | + if (done < datasize) | ||
| 244 | + goto again; | ||
| 245 | + } | ||
| 246 | + if (ctx) { | ||
| 247 | + free(ctx); | ||
| 248 | + } | ||
| 249 | + return NULL; | ||
| 250 | +} | ||
| 251 | + | ||
| 252 | +static int create_worker(childinfo_t *child, void *ctx, void *(*func)(void *)) | ||
| 253 | +{ | ||
| 254 | + pthread_attr_t attr; | ||
| 255 | + int err; | ||
| 256 | + | ||
| 257 | + switch (process_mode) { | ||
| 258 | + case PROCESS_MODE: /* process mode */ | ||
| 259 | + /* Fork the sender/receiver child. */ | ||
| 260 | + switch ((child->pid = fork())) { | ||
| 261 | + case -1: | ||
| 262 | + sneeze("fork()"); | ||
| 263 | + return -1; | ||
| 264 | + case 0: | ||
| 265 | + (*func) (ctx); | ||
| 266 | + exit(0); | ||
| 267 | + } | ||
| 268 | + break; | ||
| 269 | + | ||
| 270 | + case THREAD_MODE: /* threaded mode */ | ||
| 271 | + if (pthread_attr_init(&attr) != 0) { | ||
| 272 | + sneeze("pthread_attr_init()"); | ||
| 273 | + return -1; | ||
| 274 | + } | ||
| 275 | + | ||
| 276 | + | ||
| 277 | + if (pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN) != 0) { | ||
| 278 | + sneeze("pthread_attr_setstacksize()"); | ||
| 279 | + return -1; | ||
| 280 | + } | ||
| 281 | + | ||
| 282 | + | ||
| 283 | + if ((err=pthread_create(&child->threadid, &attr, func, ctx)) != 0) { | ||
| 284 | + sneeze("pthread_create failed()"); | ||
| 285 | + return -1; | ||
| 286 | + } | ||
| 287 | + break; | ||
| 288 | + } | ||
| 289 | + return 0; | ||
| 290 | +} | ||
| 291 | + | ||
| 292 | +void signal_workers(childinfo_t *children, unsigned int num_children) | ||
| 293 | +{ | ||
| 294 | + unsigned int i; | ||
| 295 | + printf("signaling %d worker threads to terminate\n", num_children); | ||
| 296 | + for (i=0; i < num_children; i++) { | ||
| 297 | + kill(children[i].pid, SIGTERM); | ||
| 298 | + } | ||
| 299 | +} | ||
| 300 | + | ||
| 301 | +unsigned int reap_workers(childinfo_t *child, unsigned int totchld, unsigned int dokill) | ||
| 302 | +{ | ||
| 303 | + unsigned int i, rc = 0; | ||
| 304 | + int status, err; | ||
| 305 | + void *thr_status; | ||
| 306 | + | ||
| 307 | + if (dokill) { | ||
| 308 | + fprintf(stderr, "sending SIGTERM to all child processes\n"); | ||
| 309 | + signal(SIGTERM, SIG_IGN); | ||
| 310 | + signal_workers(child, totchld); | ||
| 311 | + } | ||
| 312 | + | ||
| 313 | + for( i = 0; i < totchld; i++ ) { | ||
| 314 | + int pid; | ||
| 315 | + switch( process_mode ) { | ||
| 316 | + case PROCESS_MODE: /* process mode */ | ||
| 317 | + fflush(stdout); | ||
| 318 | + pid = wait(&status); | ||
| 319 | + if (pid == -1 && errno == ECHILD) | ||
| 320 | + break; | ||
| 321 | + if (!WIFEXITED(status)) | ||
| 322 | + rc++; | ||
| 323 | + break; | ||
| 324 | + case THREAD_MODE: /* threaded mode */ | ||
| 325 | + err = pthread_join(child[i].threadid, &thr_status); | ||
| 326 | + if( err != 0 ) { | ||
| 327 | + sneeze("pthread_join()"); | ||
| 328 | + rc++; | ||
| 329 | + } | ||
| 330 | + break; | ||
| 331 | + } | ||
| 332 | + } | ||
| 333 | + return rc; | ||
| 334 | +} | ||
| 335 | + | ||
| 336 | +/* One group of senders and receivers */ | ||
| 337 | +static unsigned int group(childinfo_t *child, | ||
| 338 | + unsigned int tab_offset, | ||
| 339 | + unsigned int num_fds, | ||
| 340 | + int ready_out, | ||
| 341 | + int wakefd) | ||
| 342 | +{ | ||
| 343 | + unsigned int i; | ||
| 344 | + struct sender_context* snd_ctx = malloc (sizeof(struct sender_context) | ||
| 345 | + +num_fds*sizeof(int)); | ||
| 346 | + int err; | ||
| 347 | + | ||
| 348 | + if (!snd_ctx) { | ||
| 349 | + sneeze("malloc() [sender ctx]"); | ||
| 350 | + return 0; | ||
| 351 | + } | ||
| 352 | + | ||
| 353 | + | ||
| 354 | + for (i = 0; i < num_fds; i++) { | ||
| 355 | + int fds[2]; | ||
| 356 | + struct receiver_context* ctx = malloc (sizeof(*ctx)); | ||
| 357 | + | ||
| 358 | + if (!ctx) { | ||
| 359 | + sneeze("malloc() [receiver ctx]"); | ||
| 360 | + return (i > 0 ? i-1 : 0); | ||
| 361 | + } | ||
| 362 | + | ||
| 363 | + | ||
| 364 | + /* Create the pipe between client and server */ | ||
| 365 | + fdpair(fds); | ||
| 366 | + | ||
| 367 | + ctx->num_packets = num_fds*loops; | ||
| 368 | + ctx->in_fds[0] = fds[0]; | ||
| 369 | + ctx->in_fds[1] = fds[1]; | ||
| 370 | + ctx->ready_out = ready_out; | ||
| 371 | + ctx->wakefd = wakefd; | ||
| 372 | + | ||
| 373 | + err = create_worker(&child[tab_offset+i], ctx, | ||
| 374 | + (void *)(void *)receiver); | ||
| 375 | + if(err) { | ||
| 376 | + return (i > 0 ? i-1 : 0); | ||
| 377 | + } | ||
| 378 | + snd_ctx->out_fds[i] = fds[1]; | ||
| 379 | + if (process_mode == PROCESS_MODE) | ||
| 380 | + close(fds[0]); | ||
| 381 | + } | ||
| 382 | + | ||
| 383 | + snd_ctx->ready_out = ready_out; | ||
| 384 | + snd_ctx->wakefd = wakefd; | ||
| 385 | + snd_ctx->num_fds = num_fds; | ||
| 386 | + | ||
| 387 | + /* Now we have all the fds, fork the senders */ | ||
| 388 | + for (i = 0; i < num_fds; i++) { | ||
| 389 | + err = create_worker(&child[tab_offset+num_fds+i], snd_ctx, | ||
| 390 | + (void *)(void *)sender); | ||
| 391 | + if(err) { | ||
| 392 | + return (num_fds+i)-1; | ||
| 393 | + } | ||
| 394 | + } | ||
| 395 | + | ||
| 396 | + /* Close the fds we have left */ | ||
| 397 | + if (process_mode == PROCESS_MODE) | ||
| 398 | + for (i = 0; i < num_fds; i++) | ||
| 399 | + close(snd_ctx->out_fds[i]); | ||
| 400 | + | ||
| 401 | + /* Return number of children to reap */ | ||
| 402 | + return num_fds * 2; | ||
| 403 | +} | ||
| 404 | + | ||
| 405 | +static void process_options(int argc, char *argv[]) | ||
| 406 | +{ | ||
| 407 | + for(;;) { | ||
| 408 | + static struct option longopts[] = { | ||
| 409 | + {"fds", required_argument, NULL, 'f'}, | ||
| 410 | + {"fifo", no_argument, NULL, 'F'}, | ||
| 411 | + {"groups", required_argument, NULL, 'g'}, | ||
| 412 | + {"help", no_argument, NULL, 'h'}, | ||
| 413 | + {"loops", required_argument, NULL, 'l'}, | ||
| 414 | + {"pipe", no_argument, NULL, 'p'}, | ||
| 415 | + {"inet", no_argument, NULL, 'i'}, | ||
| 416 | + {"datasize", required_argument, NULL, 's'}, | ||
| 417 | + {"threads", no_argument, NULL, 'T'}, | ||
| 418 | + {"processes", no_argument, NULL, 'P'}, | ||
| 419 | + {NULL, 0, NULL, 0} | ||
| 420 | + }; | ||
| 421 | + | ||
| 422 | + int c = getopt_long(argc, argv, "f:Fg:hl:pis:TP", | ||
| 423 | + longopts, NULL); | ||
| 424 | + if (c == -1) { | ||
| 425 | + break; | ||
| 426 | + } | ||
| 427 | + switch (c) { | ||
| 428 | + case 'f': | ||
| 429 | + num_fds = atoi(optarg); | ||
| 430 | + if (atoi(optarg) <= 0) { | ||
| 431 | + fprintf(stderr, "%s: --fds|-f requires an integer > 0\n", argv[0]); | ||
| 432 | + print_usage_exit(1); | ||
| 433 | + } | ||
| 434 | + break; | ||
| 435 | + case 'F': | ||
| 436 | + fifo = 1; | ||
| 437 | + break; | ||
| 438 | + case 'g': | ||
| 439 | + num_groups = atoi(optarg); | ||
| 440 | + if (atoi(optarg) <= 0) { | ||
| 441 | + fprintf(stderr, "%s: --groups|-g requires an integer > 0\n", argv[0]); | ||
| 442 | + print_usage_exit(1); | ||
| 443 | + } | ||
| 444 | + break; | ||
| 445 | + case 'h': | ||
| 446 | + print_usage_exit(0); | ||
| 447 | + break; | ||
| 448 | + case 'l': | ||
| 449 | + loops = atoi(optarg); | ||
| 450 | + if (atoi(optarg) <= 0) { | ||
| 451 | + fprintf(stderr, "%s: --loops|-l requires an integer > 0\n", argv[0]); | ||
| 452 | + print_usage_exit(1); | ||
| 453 | + } | ||
| 454 | + break; | ||
| 455 | + case 'p': | ||
| 456 | + use_pipes = 1; | ||
| 457 | + break; | ||
| 458 | + case 'i': | ||
| 459 | + use_inet = 1; | ||
| 460 | + break; | ||
| 461 | + case 's': | ||
| 462 | + datasize = atoi(optarg); | ||
| 463 | + if (atoi(optarg) <= 0) { | ||
| 464 | + fprintf(stderr, "%s: --datasize|-s requires an integer > 0\n", argv[0]); | ||
| 465 | + print_usage_exit(1); | ||
| 466 | + } | ||
| 467 | + break; | ||
| 468 | + case 'T': | ||
| 469 | + process_mode = THREAD_MODE; | ||
| 470 | + break; | ||
| 471 | + case 'P': | ||
| 472 | + process_mode = PROCESS_MODE; | ||
| 473 | + break; | ||
| 474 | + default: | ||
| 475 | + print_usage_exit(1); | ||
| 476 | + } | ||
| 477 | + } | ||
| 478 | + | ||
| 479 | + if (use_pipes && use_inet) { | ||
| 480 | + fprintf(stderr, "%s: --pipe|-p and --inet|-i cannot be used together\n", argv[0]); | ||
| 481 | + print_usage_exit(1); | ||
| 482 | + } | ||
| 483 | +} | ||
| 484 | + | ||
| 485 | +void sigcatcher(int sig) { | ||
| 486 | + /* All caught signals will cause the program to exit */ | ||
| 487 | + signal_caught = 1; | ||
| 488 | + fprintf(stderr, "Signal %d caught, longjmp'ing out!\n", sig); | ||
| 489 | + signal(sig, SIG_IGN); | ||
| 490 | + longjmp(jmpbuf, 1); | ||
| 491 | +} | ||
| 492 | + | ||
| 493 | +int main(int argc, char *argv[]) | ||
| 494 | +{ | ||
| 495 | + unsigned int i; | ||
| 496 | + struct timeval start, stop, diff; | ||
| 497 | + int readyfds[2], wakefds[2]; | ||
| 498 | + char dummy; | ||
| 499 | + volatile int timer_started = 0; | ||
| 500 | + struct sched_param sp; | ||
| 501 | + | ||
| 502 | + process_options (argc, argv); | ||
| 503 | + | ||
| 504 | + printf("Running in %s mode with %d groups using %d file descriptors each (== %d tasks)\n", | ||
| 505 | + (process_mode == THREAD_MODE ? "threaded" : "process"), | ||
| 506 | + num_groups, 2*num_fds, num_groups*(num_fds*2)); | ||
| 507 | + printf("Each sender will pass %d messages of %d bytes\n", loops, datasize); | ||
| 508 | + fflush(NULL); | ||
| 509 | + | ||
| 510 | + child_tab = calloc(num_fds * 2 * num_groups, sizeof(childinfo_t)); | ||
| 511 | + if (!child_tab) | ||
| 512 | + barf("main:malloc()"); | ||
| 513 | + | ||
| 514 | + fdpair(readyfds); | ||
| 515 | + fdpair(wakefds); | ||
| 516 | + | ||
| 517 | + /* Catch some signals */ | ||
| 518 | + signal(SIGINT, sigcatcher); | ||
| 519 | + signal(SIGTERM, sigcatcher); | ||
| 520 | + signal(SIGHUP, SIG_IGN); | ||
| 521 | + | ||
| 522 | + if (setjmp(jmpbuf) == 0) { | ||
| 523 | + total_children = 0; | ||
| 524 | + for (i = 0; i < num_groups; i++) { | ||
| 525 | + unsigned int c = group(child_tab, total_children, num_fds, readyfds[1], wakefds[0]); | ||
| 526 | + if( c != (num_fds*2) ) { | ||
| 527 | + fprintf(stderr, "%i children started. Expected %i\n", c, num_fds*2); | ||
| 528 | + reap_workers(child_tab, total_children + c, 1); | ||
| 529 | + barf("Creating workers"); | ||
| 530 | + } | ||
| 531 | + total_children += c; | ||
| 532 | + } | ||
| 533 | + if (fifo) { | ||
| 534 | + /* make main a realtime task so that we can manage the workers */ | ||
| 535 | + sp.sched_priority = 1; | ||
| 536 | + if (sched_setscheduler(0, SCHED_FIFO, &sp) < 0) | ||
| 537 | + barf("can't change to fifo in main"); | ||
| 538 | + } | ||
| 539 | + | ||
| 540 | + /* Wait for everyone to be ready */ | ||
| 541 | + for (i = 0; i < total_children; i++) | ||
| 542 | + if (read(readyfds[0], &dummy, 1) != 1) { | ||
| 543 | + reap_workers(child_tab, total_children, 1); | ||
| 544 | + barf("Reading for readyfds"); | ||
| 545 | + } | ||
| 546 | + | ||
| 547 | + gettimeofday(&start, NULL); | ||
| 548 | + timer_started = 1; | ||
| 549 | + | ||
| 550 | + /* Kick them off */ | ||
| 551 | + if (write(wakefds[1], &dummy, 1) != 1) { | ||
| 552 | + reap_workers(child_tab, total_children, 1); | ||
| 553 | + barf("Writing to start senders"); | ||
| 554 | + } | ||
| 555 | + } | ||
| 556 | + else { | ||
| 557 | + fprintf(stderr, "longjmp'ed out, reaping children\n"); | ||
| 558 | + signal(SIGINT, SIG_IGN); | ||
| 559 | + signal(SIGTERM, SIG_IGN); | ||
| 560 | + } | ||
| 561 | + | ||
| 562 | + /* Reap them all */ | ||
| 563 | + reap_workers(child_tab, total_children, signal_caught); | ||
| 564 | + | ||
| 565 | + gettimeofday(&stop, NULL); | ||
| 566 | + | ||
| 567 | + /* Print time... */ | ||
| 568 | + if (timer_started) { | ||
| 569 | + timersub(&stop, &start, &diff); | ||
| 570 | + printf("Time: %lu.%03lu\n", diff.tv_sec, diff.tv_usec/1000); | ||
| 571 | + } | ||
| 572 | + else | ||
| 573 | + fprintf(stderr, "No measurements available\n"); | ||
| 574 | + free(child_tab); | ||
| 575 | + exit(0); | ||
| 576 | +} | ||
| @@ -0,0 +1,138 @@ | |||
| 1 | +Introduction: | ||
| 2 | +------------- | ||
| 3 | + | ||
| 4 | +The module hwlat is a special purpose kernel module that is | ||
| 5 | +used to detect if System Management Interrupts (SMIs) are causing event | ||
| 6 | +latencies in the Linux RT kernel. | ||
| 7 | + | ||
| 8 | +SMIs are usually not serviced by the Linux kernel. They are set up by | ||
| 9 | +BIOS code and are serviced by BIOS code, usually for critical events | ||
| 10 | +such as management of thermal sensors and fans. Sometimes though, SMIs | ||
| 11 | +are used for other tasks and those tasks can spend an inordinate | ||
| 12 | +amount of time in the handler (sometimes measured in milliseconds). | ||
| 13 | +Obviously if you are trying to keep event service latencies down in the | ||
| 14 | +microsecond range, this is a problem. | ||
| 15 | + | ||
| 16 | +The SMI detector works by hogging the cpu for configurable amounts of | ||
| 17 | +time (by calling stop_machine()), polling the Time Stamp Counter (TSC) | ||
| 18 | +register for some period, then looking for gaps in the TSC data. Any | ||
| 19 | +gap indicates a time when the polling was interrupted and since the | ||
| 20 | +machine is stopped and interrupts turned off the only thing that could | ||
| 21 | +do that would be an SMI. | ||
| 22 | + | ||
| 23 | +Note that the SMI detector should *NEVER* be used in a production | ||
| 24 | +environment. It is intended to be run manually to determine if the | ||
| 25 | +hardware platform has a problem with long SMI service routines. | ||
| 26 | + | ||
| 27 | +Usage: | ||
| 28 | +------ | ||
| 29 | + | ||
| 30 | +Loading the module hwlat passing the parameter "enabled=1" is the only | ||
| 31 | +step required to start the hwlat. It is possible to define a threshold | ||
| 32 | +in microseconds (us) above which latency spikes will be taken in account | ||
| 33 | +(parameter "threshold="). | ||
| 34 | + | ||
| 35 | +Example: | ||
| 36 | + | ||
| 37 | + # insmod ./hwlat.ko enabled=1 threshold=100 | ||
| 38 | + | ||
| 39 | +After the module is loaded, it creates a directory named "hwlat" under | ||
| 40 | +the debugfs mountpoint, "/debugfs/hwlat" for this text. It is necessary | ||
| 41 | +to have debugfs mounted. | ||
| 42 | + | ||
| 43 | +avg_smi_interval_us - average interval (usecs) between SMI latency spikes | ||
| 44 | +latency_threshold_us - minimum latency value to be considered (usecs) | ||
| 45 | +max_sample_us - maximum SMI latency spike observed (usecs) | ||
| 46 | +ms_between_samples - interval between samples (ms) | ||
| 47 | +ms_per_sample - sampling time (ms) | ||
| 48 | +sample_us - last sample (usecs). continuously updated, may be used | ||
| 49 | + to plot graphs or to create histograms | ||
| 50 | +smi_count - how many latency spikes have been observed | ||
| 51 | + | ||
| 52 | + | ||
| 53 | + # cd /debugfs/hwlat | ||
| 54 | + | ||
| 55 | + # cat * | ||
| 56 | + 0 (avg_smi_interval_us) | ||
| 57 | + 100 (latency_threshold_us) | ||
| 58 | + 0 (latency_threshold_us) | ||
| 59 | + 5000 (ms_between_samples) | ||
| 60 | + 1 (ms_per_sample) | ||
| 61 | + 3 (sample_us) | ||
| 62 | + 0 (smi_count) | ||
| 63 | + | ||
| 64 | + | ||
| 65 | +The default values for ms_between_samples and ms_per_sample define that every | ||
| 66 | +5000ms (5s) samples will be collected during 1ms. It is possible to change the | ||
| 67 | +sampling time or the sampling interval writing to the related files. To collect | ||
| 68 | +samples during 5ms every 50ms: | ||
| 69 | + | ||
| 70 | + # echo 5 > ms_per_sample | ||
| 71 | + # echo 50 > ms_between_samples | ||
| 72 | + | ||
| 73 | + | ||
| 74 | + # cat ms_between_samples | ||
| 75 | + 50 | ||
| 76 | + # cat ms_per_sample | ||
| 77 | + 5 | ||
| 78 | + | ||
| 79 | +After a while, data may be verified to test the existence of SMI induced | ||
| 80 | +latencies: | ||
| 81 | + | ||
| 82 | + # cat smi_count | ||
| 83 | + 2 | ||
| 84 | + # cat max_sample_us | ||
| 85 | + 468 | ||
| 86 | + # cat avg_smi_interval_us | ||
| 87 | + 1356306050 | ||
| 88 | + | ||
| 89 | +Depending on the latencies observed it is important to better adjust the | ||
| 90 | +sampling intervals to obtain more accurate measurements. Care must be taken | ||
| 91 | +to not create a continuous sampling situation, that might be perceived by the | ||
| 92 | +kernel as a deadlock. | ||
| 93 | + | ||
| 94 | +Python Script | ||
| 95 | +------------- | ||
| 96 | + | ||
| 97 | +A python script is provided in in the kernel scripts directory named | ||
| 98 | +'hwlatdetect'. This script handles mounting/unmounting the debugfs, | ||
| 99 | +loading/unloading the hwlat module, setting various parameters | ||
| 100 | +for the SMI detector and then starting and stopping the detection. The | ||
| 101 | +hwlatdetect script handles the following arguments: | ||
| 102 | + | ||
| 103 | +Usage: hwlatdetect [options] | ||
| 104 | + | ||
| 105 | +Options: | ||
| 106 | + -h, --help show this help message and exit | ||
| 107 | + --duration=DURATION total time to test for SMIs (<n>{smdw}) | ||
| 108 | + --threshold=THRESHOLD | ||
| 109 | + value above which is considered an SMI (microseconds | ||
| 110 | + --interval=INTERVAL time between samples (milliseconds) | ||
| 111 | + --sample_width=SAMPLE_WIDTH | ||
| 112 | + time to actually measure (milliseconds) | ||
| 113 | + --report=REPORT filename for sample data | ||
| 114 | + --cleanup force unload of module and umount of debugfs | ||
| 115 | + --debug turn on debugging prints | ||
| 116 | + --quiet turn off all screen output | ||
| 117 | + --watch print sample data to stdout as it arrives | ||
| 118 | + | ||
| 119 | + | ||
| 120 | +An example run looks like this: | ||
| 121 | + | ||
| 122 | + $ sudo hwlatdetect --duration=1m --report=smi.txt | ||
| 123 | + hwlatdetect: test duration 60 seconds | ||
| 124 | + parameters: | ||
| 125 | + Latency threshold: 100us | ||
| 126 | + Non-sampling gap: 5ms | ||
| 127 | + Sample length: 20ms | ||
| 128 | + Output File: smi.txt | ||
| 129 | + Starting test | ||
| 130 | + test finished | ||
| 131 | + Max Latency: 427us | ||
| 132 | + Samples recorded: 2201 | ||
| 133 | + Samples exceeding threshold: 7 | ||
| 134 | + sample data written to smi.txt | ||
| 135 | + $ | ||
| 136 | + | ||
| 137 | +The above run generated 2201 samples of which 27 exceeded the 100 | ||
| 138 | +microsecond threshold. | ||
| @@ -0,0 +1,116 @@ | |||
| 1 | +.\" Hey, EMACS: -*- nroff -*- | ||
| 2 | +.TH HWLATDETECT 8 "May 12, 2009" | ||
| 3 | +# SPDX-License-Identifier: GPL-2.0-only | ||
| 4 | +.\" Please adjust this date whenever revising the manpage. | ||
| 5 | +.\" | ||
| 6 | +.\" Some roff macros, for reference: | ||
| 7 | +.\" .nh disable hyphenation | ||
| 8 | +.\" .hy enable hyphenation | ||
| 9 | +.\" .ad l left justify | ||
| 10 | +.\" .ad b justify to both left and right margins | ||
| 11 | +.\" .nf disable filling | ||
| 12 | +.\" .fi enable filling | ||
| 13 | +.\" .br insert line break | ||
| 14 | +.\" .sp <n> insert n+1 empty lines | ||
| 15 | +.\" for manpage-specific macros, see man(7) | ||
| 16 | +.SH NAME | ||
| 17 | +hwlatdetect \- program to control the ftrace kernel hardware latency detection | ||
| 18 | +.SH SYNOPSIS | ||
| 19 | +.B hwlatdetect | ||
| 20 | +.RI "[ \-\-duration=<time> ] [\-\-threshold=<usecs> ] \ | ||
| 21 | +[\-\-window=<time interval> ] [\-\-width=<time interval> ] \ | ||
| 22 | +[\-\-hardlimit=<microsecond value> ] [\-\-report=<path> ] \ | ||
| 23 | +[\-\-debug ] [\-\-quiet ] [--watch] | ||
| 24 | + | ||
| 25 | +.\" .SH DESCRIPTION | ||
| 26 | +.\" This manual page documents briefly the | ||
| 27 | +.\" .B hwlatdetect commands. | ||
| 28 | +.\" .PP | ||
| 29 | +.\" \fI<whatever>\fP escape sequences to invoke bold face and italics, respectively. | ||
| 30 | +.\" \fBhwlatdetect\fP is a program that... | ||
| 31 | +.SH DESCRIPTION | ||
| 32 | +\fBhwlatdetect\fP is a program that controls the ftrace kernel hardware | ||
| 33 | +latency detector (hwlatdetector).hwlatdetector is used to detect large | ||
| 34 | +system latencies induced by the behavior of certain underlying hardware | ||
| 35 | +or firmware, independent of Linux itself. | ||
| 36 | +The code was developed originally to detect SMIs (System Management Interrupts) | ||
| 37 | +on x86 systems, however there is nothing x86 specific about it. | ||
| 38 | +It was originally written for use by the "RT" patch set | ||
| 39 | +since the Real Time kernel is highly latency sensitive. | ||
| 40 | + | ||
| 41 | +SMIs are usually not serviced by the Linux kernel, which typically does not | ||
| 42 | +even know that they are occurring. SMIs are instead are set up by BIOS code | ||
| 43 | +and are serviced by BIOS code, usually for "critical" events such as | ||
| 44 | +management of thermal sensors and fans. Sometimes though, SMIs are used for | ||
| 45 | +other tasks and those tasks can spend an inordinate amount of time in the | ||
| 46 | +handler (sometimes measured in milliseconds). Obviously this is a problem if | ||
| 47 | +you are trying to keep event service latencies down in the microsecond range. | ||
| 48 | + | ||
| 49 | +The ftrace hardware latency detector works by hogging all of the cpus | ||
| 50 | +for configurable amounts of time (by calling stop_machine()), polling | ||
| 51 | +the CPU Time Stamp Counter for some period, then looking for gaps in | ||
| 52 | +the TSC data. Any gap indicates a time when the polling was | ||
| 53 | +interrupted and since the machine is stopped and interrupts turned off | ||
| 54 | +the only thing that could do that would be an SMI. | ||
| 55 | + | ||
| 56 | +The hwlatdetector script manages the mounting/unmounting of the | ||
| 57 | +debugfs as well as interacting with the ftrace hwlatdetector | ||
| 58 | +If the debugfs is already mounted then hwlatdetector will not | ||
| 59 | +unmount it after a run. | ||
| 60 | + | ||
| 61 | +.SH OPTIONS | ||
| 62 | +.TP | ||
| 63 | +.B \-\-duration=<time>{s,m,d} | ||
| 64 | +Run the detector logic in for the specified duration. The duration is | ||
| 65 | +a base 10 integer number that defaults to a value in seconds. An | ||
| 66 | +optional suffix may be specified to indicate minutes, hours or days. | ||
| 67 | + | ||
| 68 | +.TP | ||
| 69 | +.B \-\-threshold=<microsecond value> | ||
| 70 | +Specify the TSC gap used to detect an SMI. Any gap value greater than | ||
| 71 | +<threshold> is considered to be the result of an SMI occurring. | ||
| 72 | + | ||
| 73 | +.TP | ||
| 74 | +.B \-\-hardlimit=<microsecond value> | ||
| 75 | +The test is considered to fail if a value above the hardlimit occurs. | ||
| 76 | +This affects the exit value of hwlatdetect | ||
| 77 | + | ||
| 78 | +.TP | ||
| 79 | +.B \-\-window=<time value>{us,ms,s,m,d} | ||
| 80 | +specify the size of the sample window. Converted to microseconds when | ||
| 81 | +passed to the kernel. | ||
| 82 | + | ||
| 83 | +.TP | ||
| 84 | +.B \-\-width=<time value>{us,ms,s,m,d} | ||
| 85 | +The amount of time within the sample window where the detector is | ||
| 86 | +actually sampling. Must be less than the \-\-window value. | ||
| 87 | +.TP | ||
| 88 | +.B \-\-report=FILENAME | ||
| 89 | +Specify the output filename of the detector report. Default | ||
| 90 | +behavior is to print to standard output | ||
| 91 | +.TP | ||
| 92 | +.B \-\-cpu-list=CPU-LIST | ||
| 93 | +Specify the CPUs for hwlat thread to move across. | ||
| 94 | +.TP | ||
| 95 | +.B \-\-debug | ||
| 96 | +Turn on debug prints | ||
| 97 | +.TP | ||
| 98 | +.B \-\-quiet | ||
| 99 | +Turn off all information prints | ||
| 100 | +.TP | ||
| 101 | +.B \-\-watch | ||
| 102 | +print sample data to stdout as it arrives | ||
| 103 | + | ||
| 104 | +.\" .SH SEE ALSO | ||
| 105 | +.\" .BR bar (1), | ||
| 106 | +.\" .BR baz (1). | ||
| 107 | +.\" .br | ||
| 108 | +.\" The programs are documented fully by | ||
| 109 | +.\" .IR "The Rise and Fall of a Fooish Bar" , | ||
| 110 | +.\" available via the Info system. | ||
| 111 | +.SH AUTHOR | ||
| 112 | +.B hwlatdetect | ||
| 113 | +was written by Clark Williams <williams@redhat.com> | ||
| 114 | +.br | ||
| 115 | +.B hwlat_detector.ko | ||
| 116 | +was written by Jon Masters <jcm@redhat.com> | ||
| @@ -0,0 +1,544 @@ | |||
| 1 | +#!/usr/bin/python3 | ||
| 2 | +""" Module to detect smis """ | ||
| 3 | + | ||
| 4 | +# SPDX-License-Identifier: GPL-2.0-only | ||
| 5 | + | ||
| 6 | +# (C) 2018,2019 Clark Williams <williams@redhat.com> | ||
| 7 | +# (C) 2015,2016 Clark Williams <williams@redhat.com> | ||
| 8 | +# (C) 2009 Clark Williams <williams@redhat.com> | ||
| 9 | + | ||
| 10 | +import abc | ||
| 11 | +import errno | ||
| 12 | +import os | ||
| 13 | +import os.path | ||
| 14 | +import subprocess | ||
| 15 | +import sys | ||
| 16 | +import time | ||
| 17 | + | ||
| 18 | +version = "0.8" | ||
| 19 | +debugging = False | ||
| 20 | +quiet = False | ||
| 21 | +watch = False | ||
| 22 | + | ||
| 23 | + | ||
| 24 | +def debug(dstr): | ||
| 25 | + """ print debugging string if debugging is on """ | ||
| 26 | + if debugging: | ||
| 27 | + print(dstr) | ||
| 28 | + | ||
| 29 | + | ||
| 30 | +def info(istr): | ||
| 31 | + " print info string if quiet is not on """ | ||
| 32 | + if not quiet: | ||
| 33 | + print(istr) | ||
| 34 | + | ||
| 35 | + | ||
| 36 | +# | ||
| 37 | +# Class used to manage mounting and umounting the debugfs | ||
| 38 | +# filesystem. Note that if an instance of this class mounts | ||
| 39 | +# the debugfs, it will unmount when cleaning up, but if it | ||
| 40 | +# discovers that debugfs is already mounted, it will leave | ||
| 41 | +# it mounted. | ||
| 42 | +# | ||
| 43 | +class DebugFS: | ||
| 44 | + """ class to manage mounting/umounting the debugfs """ | ||
| 45 | + def __init__(self): | ||
| 46 | + self.premounted = False | ||
| 47 | + self.mounted = False | ||
| 48 | + self.mountpoint = '' | ||
| 49 | + with open('/proc/mounts') as f: | ||
| 50 | + for l in f: | ||
| 51 | + field = l.split() | ||
| 52 | + if field[2] == "debugfs": | ||
| 53 | + self.premounted = True | ||
| 54 | + self.mountpoint = field[1] | ||
| 55 | + break | ||
| 56 | + | ||
| 57 | + def mount(self, path='/sys/kernel/debug'): | ||
| 58 | + """ mount debugfs unless already mounted """ | ||
| 59 | + if self.premounted or self.mounted: | ||
| 60 | + debug("not mounting debugfs") | ||
| 61 | + return True | ||
| 62 | + debug(f"mounting debugfs at {path}") | ||
| 63 | + self.mountpoint = path | ||
| 64 | + cmd = ['/bin/mount', '-t', 'debugfs', 'none', path] | ||
| 65 | + self.mounted = (subprocess.call(cmd) == 0) | ||
| 66 | + if not self.mounted: | ||
| 67 | + raise RuntimeError("Failed to mount debugfs") | ||
| 68 | + return self.mounted | ||
| 69 | + | ||
| 70 | + def umount(self): | ||
| 71 | + """ unmount debugfs if not premounted """ | ||
| 72 | + if self.premounted or not self.mounted: | ||
| 73 | + debug("not umounting debugfs") | ||
| 74 | + return True | ||
| 75 | + debug("umounting debugfs") | ||
| 76 | + cmd = ['/bin/umount', self.mountpoint] | ||
| 77 | + self.mounted = subprocess.call(cmd) != 0 | ||
| 78 | + if self.mounted: | ||
| 79 | + raise RuntimeError("Failed to umount debugfs") | ||
| 80 | + return not self.mounted | ||
| 81 | + | ||
| 82 | + def getval(self, item, nonblocking=False): | ||
| 83 | + """ get value of mountpoint/item """ | ||
| 84 | + path = os.path.join(self.mountpoint, item) | ||
| 85 | + if nonblocking is False: | ||
| 86 | + with open(path) as f: | ||
| 87 | + val = f.readline() | ||
| 88 | + else: | ||
| 89 | + f = os.fdopen(os.open(path, os.O_RDONLY | os.O_NONBLOCK), "r") | ||
| 90 | + try: | ||
| 91 | + val = f.readline() | ||
| 92 | + except OSError as e: | ||
| 93 | + print(f"errno: {e}") | ||
| 94 | + if e.errno == errno.EAGAIN: | ||
| 95 | + val = None | ||
| 96 | + else: | ||
| 97 | + raise | ||
| 98 | + f.close() | ||
| 99 | + return val | ||
| 100 | + | ||
| 101 | + def putval(self, item, value): | ||
| 102 | + """ write value to mountpoint/item """ | ||
| 103 | + path = os.path.join(self.mountpoint, item) | ||
| 104 | + with open(path, "w") as f: | ||
| 105 | + f.write(str(value)) | ||
| 106 | + f.flush() | ||
| 107 | + | ||
| 108 | + def getpath(self, item): | ||
| 109 | + """ get mountpoint/item """ | ||
| 110 | + return os.path.join(self.mountpoint, item) | ||
| 111 | + | ||
| 112 | + | ||
| 113 | +class DetectorNotAvailable(Exception): | ||
| 114 | + """ Exception class for when tracer is not available """ | ||
| 115 | + | ||
| 116 | + def __init__(self, name, msg): | ||
| 117 | + self.args = (name, msg) | ||
| 118 | + self.name = name | ||
| 119 | + self.msg = msg | ||
| 120 | + | ||
| 121 | + | ||
| 122 | +class Detector: | ||
| 123 | + """ base class for detector modules """ | ||
| 124 | + | ||
| 125 | + def __init__(self): | ||
| 126 | + self.type = "unknown" | ||
| 127 | + if os.getuid() != 0: | ||
| 128 | + raise RuntimeError("Must be root") | ||
| 129 | + self.debugfs = DebugFS() | ||
| 130 | + if not self.debugfs.mount(): | ||
| 131 | + raise RuntimeError("failed to mount debugfs") | ||
| 132 | + self.samples = [] | ||
| 133 | + self.testduration = 30 # in seconds | ||
| 134 | + self.have_msr = False | ||
| 135 | + self.initsmi = [] | ||
| 136 | + if os.path.exists('/usr/sbin/rdmsr'): | ||
| 137 | + self.have_msr = True | ||
| 138 | + self.initsmi = self.getsmicounts() | ||
| 139 | + self.dma_latency_handle = None | ||
| 140 | + | ||
| 141 | + def getsmicounts(self): | ||
| 142 | + counts = [] | ||
| 143 | + if self.have_msr: | ||
| 144 | + p = subprocess.Popen(['/usr/sbin/rdmsr', '-a', '-d', '0x34'], stdout=subprocess.PIPE) | ||
| 145 | + p.wait() | ||
| 146 | + counts = [int(x.strip()) for x in p.stdout.readlines()] | ||
| 147 | + return counts | ||
| 148 | + | ||
| 149 | + # methods for preventing/enabling c-state transitions | ||
| 150 | + # | ||
| 151 | + # opening /dev/cpu_dma_latency and writing a 32-bit zero to that file will | ||
| 152 | + # prevent c-state transitions while the file descriptor is open. | ||
| 153 | + # | ||
| 154 | + # use c_states_off() to disable c-state transitions | ||
| 155 | + # use c_states_on() to close the file descriptor and re-enable c-states | ||
| 156 | + # | ||
| 157 | + def c_states_off(self): | ||
| 158 | + """ disable c-state transitions """ | ||
| 159 | + if os.path.exists("/dev/cpu_dma_latency"): | ||
| 160 | + self.dma_latency_handle = os.open("/dev/cpu_dma_latency", os.O_WRONLY) | ||
| 161 | + os.write(self.dma_latency_handle, b'\x00\x00\x00\x00') | ||
| 162 | + debug("c-states disabled") | ||
| 163 | + | ||
| 164 | + def c_states_on(self): | ||
| 165 | + """ close the file descriptor and re-enable c-states """ | ||
| 166 | + if self.dma_latency_handle: | ||
| 167 | + os.close(self.dma_latency_handle) | ||
| 168 | + debug("c-states enabled") | ||
| 169 | + | ||
| 170 | + | ||
| 171 | + def cleanup(self): | ||
| 172 | + ''' abstract cleanup method, must override ''' | ||
| 173 | + | ||
| 174 | + | ||
| 175 | + def get(self, field): | ||
| 176 | + ''' get the value of a debugfs field ''' | ||
| 177 | + | ||
| 178 | + | ||
| 179 | + def set(self, field, val): | ||
| 180 | + ''' set a value in a debugfs field ''' | ||
| 181 | + | ||
| 182 | + | ||
| 183 | + def save(self, output=None): | ||
| 184 | + ''' save sample data to output ''' | ||
| 185 | + | ||
| 186 | + | ||
| 187 | + def display(self): | ||
| 188 | + ''' output the sample data as a string ''' | ||
| 189 | + | ||
| 190 | + def start(self): | ||
| 191 | + """ enable the detector """ | ||
| 192 | + count = 0 | ||
| 193 | + threshold = int(self.get("threshold")) | ||
| 194 | + self.c_states_off() | ||
| 195 | + debug(f"enabling detector module (threshold: {threshold})") | ||
| 196 | + self.set("enable", 1) | ||
| 197 | + while self.get("enable") == 0: | ||
| 198 | + debug("still disabled, retrying in a bit") | ||
| 199 | + count += 1 | ||
| 200 | + time.sleep(0.1) | ||
| 201 | + debug(f"retrying enable of detector module ({count})") | ||
| 202 | + self.set("enable", 1) | ||
| 203 | + if self.get("threshold") != threshold: | ||
| 204 | + debug("start: threshold reset by start, fixing") | ||
| 205 | + self.set("threshold", threshold) | ||
| 206 | + debug(f"detector module enabled (threshold: {int(self.get('threshold'))})") | ||
| 207 | + | ||
| 208 | + def stop(self): | ||
| 209 | + """ disable the detector """ | ||
| 210 | + count = 0 | ||
| 211 | + debug("disabling detector module") | ||
| 212 | + self.set("enable", 0) | ||
| 213 | + while self.get("enable") == 1: | ||
| 214 | + debug("still enabled, retrying in a bit") | ||
| 215 | + count += 1 | ||
| 216 | + time.sleep(0.1) | ||
| 217 | + debug(f"retrying disable of detector module({count})") | ||
| 218 | + self.set("enable", 0) | ||
| 219 | + self.c_states_on() | ||
| 220 | + debug("detector module disabled") | ||
| 221 | + | ||
| 222 | + | ||
| 223 | + def detect(self): | ||
| 224 | + ''' get detector output ''' | ||
| 225 | + | ||
| 226 | + | ||
| 227 | +class Tracer(Detector): | ||
| 228 | + """ Class to handle running the hwlat tracer module of ftrace """ | ||
| 229 | + | ||
| 230 | + __field_translation = { | ||
| 231 | + 'width' : "hwlat_detector/width", | ||
| 232 | + 'window' : "hwlat_detector/window", | ||
| 233 | + 'enable' : "tracing_on", | ||
| 234 | + 'threshold' : "tracing_thresh", | ||
| 235 | + 'cpumask' : "tracing_cpumask", | ||
| 236 | + } | ||
| 237 | + | ||
| 238 | + class Sample: | ||
| 239 | + 'private class for tracer sample data' | ||
| 240 | + __slots__ = 'cpu', 'timestamp', 'inner', 'outer' | ||
| 241 | + | ||
| 242 | + def __init__(self, line): | ||
| 243 | + fields = line.split() | ||
| 244 | + self.cpu = int(fields[1][1:-1]) | ||
| 245 | + i, o = fields[6].split('/') | ||
| 246 | + ts = fields[7][3:] | ||
| 247 | + self.timestamp = str(ts) | ||
| 248 | + self.inner = int(i) | ||
| 249 | + self.outer = int(o) | ||
| 250 | + | ||
| 251 | + def __str__(self): | ||
| 252 | + return f"ts: {self.timestamp}, inner:{self.inner}, outer:{self.outer}, cpu:{self.cpu}" | ||
| 253 | + | ||
| 254 | + def display(self): | ||
| 255 | + """ convert object to string and print """ | ||
| 256 | + print(str(self)) | ||
| 257 | + | ||
| 258 | + def largest(self): | ||
| 259 | + """ return largest value of inner or outer """ | ||
| 260 | + return max(self.inner, self.outer) | ||
| 261 | + | ||
| 262 | + def translate(self, field): | ||
| 263 | + path = self.debugfs.getpath('tracing') | ||
| 264 | + if field not in Tracer.__field_translation: | ||
| 265 | + return os.path.join(path, field) | ||
| 266 | + return os.path.join(path, Tracer.__field_translation[field]) | ||
| 267 | + | ||
| 268 | + def __init__(self): | ||
| 269 | + super().__init__() | ||
| 270 | + path = self.debugfs.getpath('tracing/hwlat_detector') | ||
| 271 | + if not os.path.exists(path): | ||
| 272 | + raise DetectorNotAvailable("hwlat", "hwlat tracer not available") | ||
| 273 | + self.type = "tracer" | ||
| 274 | + self.samples = [] | ||
| 275 | + self.set("enable", 0) | ||
| 276 | + self.set('current_tracer', 'hwlat') | ||
| 277 | + | ||
| 278 | + def set(self, field, val): | ||
| 279 | + path = self.translate(field) | ||
| 280 | + self.debugfs.putval(path, str(val)) | ||
| 281 | + | ||
| 282 | + def get(self, field): | ||
| 283 | + if field == "count": | ||
| 284 | + return len(self.samples) | ||
| 285 | + if field == "max": | ||
| 286 | + max = 0 | ||
| 287 | + for values in self.samples: | ||
| 288 | + s = int(values.largest()) | ||
| 289 | + if s > max: | ||
| 290 | + max = s | ||
| 291 | + return max | ||
| 292 | + return self.debugfs.getval(self.translate(field)) | ||
| 293 | + | ||
| 294 | + def detect(self): | ||
| 295 | + self.samples = [] | ||
| 296 | + testend = time.time() + self.testduration | ||
| 297 | + pollcnt = 0 | ||
| 298 | + self.start() | ||
| 299 | + try: | ||
| 300 | + while time.time() < testend: | ||
| 301 | + pollcnt += 1 | ||
| 302 | + val = self.get_sample() | ||
| 303 | + while val: | ||
| 304 | + self.samples.append(val) | ||
| 305 | + if watch: | ||
| 306 | + val.display() | ||
| 307 | + val = self.get_sample() | ||
| 308 | + time.sleep(0.1) | ||
| 309 | + except KeyboardInterrupt as e: | ||
| 310 | + print("interrupted") | ||
| 311 | + self.stop() | ||
| 312 | + return self.samples | ||
| 313 | + | ||
| 314 | + def get_sample(self): | ||
| 315 | + val = None | ||
| 316 | + line = self.debugfs.getval("tracing/trace_pipe", nonblocking=True) | ||
| 317 | + if line: | ||
| 318 | + val = self.Sample(line) | ||
| 319 | + return val | ||
| 320 | + | ||
| 321 | + def save(self, output=None): | ||
| 322 | + """ save samples """ | ||
| 323 | + if output: | ||
| 324 | + with open(output, "w") as f: | ||
| 325 | + for s in self.samples: | ||
| 326 | + f.write(f"{s}\n") | ||
| 327 | + print(f"report saved to {output} ({len(self.samples)} samples)") | ||
| 328 | + | ||
| 329 | + def display(self): | ||
| 330 | + for s in self.samples: | ||
| 331 | + s.display() | ||
| 332 | + | ||
| 333 | + def cleanup(self): | ||
| 334 | + self.set("tracing_on", "0") | ||
| 335 | + self.set("current_tracer", "nop") | ||
| 336 | + if not self.debugfs.umount(): | ||
| 337 | + raise RuntimeError("Failed to unmount debugfs") | ||
| 338 | + | ||
| 339 | + | ||
| 340 | +def seconds(sval): | ||
| 341 | + "convert input string to value in seconds" | ||
| 342 | + if sval.isdigit(): | ||
| 343 | + return int(sval) | ||
| 344 | + if sval[-2].isalpha(): | ||
| 345 | + raise RuntimeError(f"illegal suffix for seconds: '{sval[-2:-1]}'") | ||
| 346 | + if sval[-1:] == 's': | ||
| 347 | + return int(sval[0:-1]) | ||
| 348 | + if sval[-1:] == 'm': | ||
| 349 | + return int(sval[0:-1]) * 60 | ||
| 350 | + if sval[-1:] == 'h': | ||
| 351 | + return int(sval[0:-1]) * 3600 | ||
| 352 | + if sval[-1:] == 'd': | ||
| 353 | + return int(sval[0:-1]) * 86400 | ||
| 354 | + if sval[-1:] == 'w': | ||
| 355 | + return int(sval[0:-1]) * 86400 * 7 | ||
| 356 | + raise RuntimeError(f"invalid input for seconds: '{sval}'") | ||
| 357 | + | ||
| 358 | + | ||
| 359 | +def milliseconds(sval): | ||
| 360 | + "convert input string to millsecond value" | ||
| 361 | + if sval.isdigit(): | ||
| 362 | + return int(sval) | ||
| 363 | + if sval[-2:] == 'ms': | ||
| 364 | + return int(sval[0:-2]) | ||
| 365 | + if sval[-1] == 's': | ||
| 366 | + return int(sval[0:-2]) * 1000 | ||
| 367 | + if sval[-1] == 'm': | ||
| 368 | + return int(sval[0:-1]) * 1000 * 60 | ||
| 369 | + if sval[-1] == 'h': | ||
| 370 | + return int(sval[0:-1]) * 1000 * 60 * 60 | ||
| 371 | + raise RuntimeError(f"invalid input for milliseconds: {sval}") | ||
| 372 | + | ||
| 373 | + | ||
| 374 | +def microseconds(sval): | ||
| 375 | + "convert input string to microsecond value" | ||
| 376 | + if sval.isdigit(): | ||
| 377 | + return int(sval) | ||
| 378 | + if sval[-2:] == 'ms': | ||
| 379 | + return int(sval[0:-2]) * 1000 | ||
| 380 | + if sval[-2:] == 'us': | ||
| 381 | + return int(sval[0:-2]) | ||
| 382 | + if sval[-1:] == 's': | ||
| 383 | + return int(sval[0:-1]) * 1000 * 1000 | ||
| 384 | + raise RuntimeError(f"invalid input for microseconds: '{sval}'") | ||
| 385 | + | ||
| 386 | + | ||
| 387 | +if __name__ == '__main__': | ||
| 388 | + from argparse import ArgumentParser | ||
| 389 | + | ||
| 390 | + parser = ArgumentParser() | ||
| 391 | + parser.add_argument("--duration", default=None, | ||
| 392 | + dest="duration", | ||
| 393 | + help="total time to test for hardware latency: <n>{smdw}") | ||
| 394 | + | ||
| 395 | + parser.add_argument("--threshold", default=None, | ||
| 396 | + dest="threshold", | ||
| 397 | + help="value above which is considered an hardware latency") | ||
| 398 | + | ||
| 399 | + parser.add_argument("--hardlimit", default=None, | ||
| 400 | + dest="hardlimit", | ||
| 401 | + help="value above which the test is considered to fail") | ||
| 402 | + | ||
| 403 | + parser.add_argument("--window", default=None, | ||
| 404 | + dest="window", | ||
| 405 | + help="time between samples") | ||
| 406 | + | ||
| 407 | + parser.add_argument("--width", default=None, | ||
| 408 | + dest="width", | ||
| 409 | + help="time to actually measure") | ||
| 410 | + | ||
| 411 | + parser.add_argument("--report", default=None, | ||
| 412 | + dest="report", | ||
| 413 | + help="filename for sample data") | ||
| 414 | + | ||
| 415 | + parser.add_argument("--cpu-list", default=None, | ||
| 416 | + dest="cpulist", | ||
| 417 | + help="the CPUs for hwlat thread to move across") | ||
| 418 | + | ||
| 419 | + parser.add_argument("--debug", action="store_true", default=False, | ||
| 420 | + dest="debug", | ||
| 421 | + help="turn on debugging prints") | ||
| 422 | + | ||
| 423 | + parser.add_argument("--quiet", action="store_true", default=False, | ||
| 424 | + dest="quiet", | ||
| 425 | + help="turn off all screen output") | ||
| 426 | + | ||
| 427 | + parser.add_argument("--watch", action="store_true", default=False, | ||
| 428 | + dest="watch", | ||
| 429 | + help="print sample data to stdout as it arrives") | ||
| 430 | + | ||
| 431 | + args = parser.parse_args() | ||
| 432 | + | ||
| 433 | + # need these before creating detector instance | ||
| 434 | + if args.debug: | ||
| 435 | + debugging = True | ||
| 436 | + quiet = False | ||
| 437 | + debug("debugging prints turned on") | ||
| 438 | + | ||
| 439 | + if args.quiet: | ||
| 440 | + quiet = True | ||
| 441 | + debugging = False | ||
| 442 | + | ||
| 443 | + detect = Tracer() | ||
| 444 | + | ||
| 445 | + if args.threshold: | ||
| 446 | + t = microseconds(args.threshold) | ||
| 447 | + detect.set("threshold", t) | ||
| 448 | + debug(f"threshold set to {t}us") | ||
| 449 | + | ||
| 450 | + if args.hardlimit: | ||
| 451 | + hardlimit = microseconds(args.hardlimit) | ||
| 452 | + else: | ||
| 453 | + hardlimit = int(detect.get("threshold")) | ||
| 454 | + debug(f"hardlimit set to {hardlimit}us") | ||
| 455 | + | ||
| 456 | + if args.window: | ||
| 457 | + w = microseconds(args.window) | ||
| 458 | + width = w//2 | ||
| 459 | + if w < int(detect.get("width")): | ||
| 460 | + debug(f"shrinking width to {width} for new window of {w}") | ||
| 461 | + detect.set("width", width) | ||
| 462 | + debug(f"window parameter = {w}") | ||
| 463 | + detect.set("window", w) | ||
| 464 | + debug(f"window for sampling set to {w}us") | ||
| 465 | + | ||
| 466 | + if args.width: | ||
| 467 | + w = microseconds(args.width) | ||
| 468 | + if w > int(detect.get("window")): | ||
| 469 | + debug(f"widening window to {w*2} for new width of {w}") | ||
| 470 | + detect.set("window", w*2) | ||
| 471 | + debug(f"width parameter = {w}") | ||
| 472 | + detect.set("width", w) | ||
| 473 | + debug(f"sample width set to {w}us") | ||
| 474 | + | ||
| 475 | + if args.duration: | ||
| 476 | + detect.testduration = seconds(args.duration) | ||
| 477 | + else: | ||
| 478 | + detect.testduration = 120 # 2 minutes | ||
| 479 | + debug(f"test duration is {detect.testduration}s") | ||
| 480 | + | ||
| 481 | + if args.watch: | ||
| 482 | + watch = True | ||
| 483 | + | ||
| 484 | + reportfile = args.report | ||
| 485 | + | ||
| 486 | + if args.cpulist: | ||
| 487 | + cpumask = 0 | ||
| 488 | + for c in args.cpulist.split(','): | ||
| 489 | + l, r = 0, 0 | ||
| 490 | + if '-' in c: | ||
| 491 | + l, r = map(int, c.split('-')) | ||
| 492 | + else: | ||
| 493 | + l, r = map(int, [c, c]) | ||
| 494 | + for i in range(l, r + 1): | ||
| 495 | + cpumask |= (1 << i) | ||
| 496 | + debug(f"set tracing_cpumask to {cpumask:x}") | ||
| 497 | + detect.set("cpumask", f"{cpumask:x}") | ||
| 498 | + | ||
| 499 | + info(f"hwlatdetect: test duration {detect.testduration} seconds") | ||
| 500 | + info(f" detector: {detect.type}") | ||
| 501 | + info(" parameters:") | ||
| 502 | + info(f" CPU list: {args.cpulist}") | ||
| 503 | + info(f" Latency threshold: {int(detect.get('threshold'))}us") | ||
| 504 | + info(f" Sample window: {int(detect.get('window'))}us") | ||
| 505 | + info(f" Sample width: {int(detect.get('width'))}us") | ||
| 506 | + info(f" Non-sampling period: {(int(detect.get('window')) - int(detect.get('width')))}us") | ||
| 507 | + info(f" Output File: {reportfile}") | ||
| 508 | + info("\nStarting test") | ||
| 509 | + | ||
| 510 | + detect.detect() | ||
| 511 | + | ||
| 512 | + info("test finished") | ||
| 513 | + | ||
| 514 | + max_latency = detect.get("max") | ||
| 515 | + if max_latency == 0: | ||
| 516 | + info("Max Latency: Below threshold") | ||
| 517 | + else: | ||
| 518 | + info(f"Max Latency: {int(max_latency)}us") | ||
| 519 | + | ||
| 520 | + info(f"Samples recorded: {len(detect.samples)}") | ||
| 521 | + | ||
| 522 | + exceeding = detect.get("count") | ||
| 523 | + info(f"Samples exceeding threshold: {exceeding}") | ||
| 524 | + | ||
| 525 | + if detect.have_msr: | ||
| 526 | + finishsmi = detect.getsmicounts() | ||
| 527 | + total_smis = 0 | ||
| 528 | + for i, count in enumerate(finishsmi): | ||
| 529 | + if count > detect.initsmi[i]: | ||
| 530 | + smis = count - detect.initsmi[i] | ||
| 531 | + total_smis += smis | ||
| 532 | + print(f"{smis} SMIs occured on cpu {i}") | ||
| 533 | + info(f"SMIs during run: {total_smis}") | ||
| 534 | + | ||
| 535 | + maxlatency = int(detect.get("max")) | ||
| 536 | + | ||
| 537 | + if reportfile: | ||
| 538 | + detect.save(reportfile) | ||
| 539 | + | ||
| 540 | + if not watch: | ||
| 541 | + detect.display() | ||
| 542 | + | ||
| 543 | + detect.cleanup() | ||
| 544 | + sys.exit(maxlatency > hardlimit) | ||
| @@ -0,0 +1,43 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | + | ||
| 3 | + | ||
| 4 | + | ||
| 5 | + | ||
| 6 | + | ||
| 7 | + | ||
| 8 | +/* | ||
| 9 | + * We do not have pthread_barrier_t available, but since we are not | ||
| 10 | + * going to use them for anything useful, just typedef them to int | ||
| 11 | + */ | ||
| 12 | +typedef int pthread_barrier_t; | ||
| 13 | +typedef int pthread_barrierattr_t; | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | +static inline int pthread_barrier_wait(pthread_barrier_t *barrier) | ||
| 20 | +{ | ||
| 21 | + return PTHREAD_BARRIER_SERIAL_THREAD; | ||
| 22 | +} | ||
| 23 | + | ||
| 24 | +static inline int pthread_barrier_destroy(pthread_barrier_t *barrier) | ||
| 25 | +{ | ||
| 26 | + return 0; | ||
| 27 | +} | ||
| 28 | +static inline int pthread_barrier_init(pthread_barrier_t * barrier, | ||
| 29 | + const pthread_barrierattr_t * attr, | ||
| 30 | + unsigned count) | ||
| 31 | +{ | ||
| 32 | + return 0; | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +static inline int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize, | ||
| 36 | + const cpu_set_t *cpuset) | ||
| 37 | +{ | ||
| 38 | + return sched_setaffinity(0, cpusetsize, cpuset); | ||
| 39 | +} | ||
| 40 | + | ||
| 41 | + | ||
| 42 | + | ||
| 43 | + | ||
| @@ -0,0 +1,42 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | + | ||
| 3 | + | ||
| 4 | + | ||
| 5 | +struct histogram { | ||
| 6 | + unsigned long *buckets; | ||
| 7 | + unsigned long width; // interval covered by one bucket | ||
| 8 | + unsigned long num; // number of buckets | ||
| 9 | + unsigned long events; // number of events logged | ||
| 10 | + | ||
| 11 | + unsigned long *oflows; // events when overflow happened | ||
| 12 | + unsigned long oflow_bufsize; // number of overflows that can be logged | ||
| 13 | + unsigned long oflow_count; // number of events that overflowed | ||
| 14 | + uint64_t oflow_magnitude; // sum of how many buckets overflowed by | ||
| 15 | +}; | ||
| 16 | + | ||
| 17 | +struct histoset { | ||
| 18 | + struct histogram *histos; // Group of related histograms (e.g. per cpu) | ||
| 19 | + struct histogram *sum; // Accumulates events from all histos | ||
| 20 | + unsigned long num_histos; // Not including sum | ||
| 21 | + unsigned long num_buckets; | ||
| 22 | +}; | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | +int hist_init(struct histogram *h, unsigned long width, unsigned long num); | ||
| 29 | +int hist_init_oflow(struct histogram *h, unsigned long num); | ||
| 30 | +void hist_destroy(struct histogram *h); | ||
| 31 | +int hist_sample(struct histogram *h, uint64_t sample); | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + | ||
| 35 | + | ||
| 36 | +int hset_init(struct histoset *hs, unsigned long histos, unsigned long bucket_width, | ||
| 37 | + unsigned long num_buckets, unsigned long overflow); | ||
| 38 | +void hset_destroy(struct histoset *hs); | ||
| 39 | +void hset_print_bucket(struct histoset *hs, FILE *f, const char *pre, | ||
| 40 | + unsigned long bucket, unsigned long flags); | ||
| 41 | +void hist_print_json(struct histogram *h, FILE *f); | ||
| 42 | +void hist_print_oflows(struct histogram *h, FILE *f); | ||
| @@ -0,0 +1,133 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | + | ||
| 3 | + | ||
| 4 | + | ||
| 5 | + | ||
| 6 | + | ||
| 7 | + | ||
| 8 | + | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | +/* test timeout */ | ||
| 28 | + | ||
| 29 | + | ||
| 30 | +/* determine if the C library supports Priority Inheritance mutexes */ | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + | ||
| 35 | + | ||
| 36 | + | ||
| 37 | + | ||
| 38 | + | ||
| 39 | + | ||
| 40 | + | ||
| 41 | + | ||
| 42 | + | ||
| 43 | + | ||
| 44 | +/* cursor control */ | ||
| 45 | + | ||
| 46 | + | ||
| 47 | + | ||
| 48 | + | ||
| 49 | + do { info(verbose, fmt, # | ||
| 50 | + | ||
| 51 | + do { debug(debugging, fmt, # | ||
| 52 | + | ||
| 53 | + do { err_msg(fmt, # | ||
| 54 | + | ||
| 55 | + | ||
| 56 | + | ||
| 57 | + | ||
| 58 | + | ||
| 59 | + | ||
| 60 | + | ||
| 61 | + | ||
| 62 | + | ||
| 63 | +/* number of consecutive watchdog hits before quitting */ | ||
| 64 | + | ||
| 65 | + | ||
| 66 | +struct group_parameters { | ||
| 67 | + | ||
| 68 | + /* group id (index) */ | ||
| 69 | + int id; | ||
| 70 | + | ||
| 71 | + /* cpu this group is bound to */ | ||
| 72 | + long cpu; | ||
| 73 | + | ||
| 74 | + /* threads in the group */ | ||
| 75 | + pthread_t low_tid; | ||
| 76 | + pthread_t med_tid; | ||
| 77 | + pthread_t high_tid; | ||
| 78 | + | ||
| 79 | + /* number of machine iterations to perform */ | ||
| 80 | + int inversions; | ||
| 81 | + | ||
| 82 | + /* group mutex */ | ||
| 83 | + pthread_mutex_t mutex; | ||
| 84 | + | ||
| 85 | + /* state barriers */ | ||
| 86 | + pthread_barrier_t start_barrier; | ||
| 87 | + pthread_barrier_t locked_barrier; | ||
| 88 | + pthread_barrier_t elevate_barrier; | ||
| 89 | + pthread_barrier_t finish_barrier; | ||
| 90 | + | ||
| 91 | + /* Either everyone goes through the loop, or else no-ones does */ | ||
| 92 | + pthread_barrier_t loop_barr; | ||
| 93 | + pthread_mutex_t loop_mtx; /* Protect access to int loop */ | ||
| 94 | + int loop; /* boolean, loop or not, connected to shutdown */ | ||
| 95 | + | ||
| 96 | + /* state variables */ | ||
| 97 | + volatile int watchdog; | ||
| 98 | + | ||
| 99 | + /* total number of inversions performed */ | ||
| 100 | + unsigned long total; | ||
| 101 | + | ||
| 102 | + /* total watchdog hits */ | ||
| 103 | + int watchdog_hits; | ||
| 104 | + | ||
| 105 | +}; | ||
| 106 | + | ||
| 107 | +/* forward prototypes */ | ||
| 108 | +void *low_priority(void *arg); | ||
| 109 | +void *med_priority(void *arg); | ||
| 110 | +void *high_priority(void *arg); | ||
| 111 | +void *reporter(void *arg); | ||
| 112 | +void *watchdog(void *arg); | ||
| 113 | +int setup_thread_attr(pthread_attr_t *attr, struct sched_attr *sa, | ||
| 114 | + cpu_set_t *mask); | ||
| 115 | +int set_cpu_affinity(cpu_set_t *test_mask, cpu_set_t *admin_mask); | ||
| 116 | +void process_command_line(int argc, char **argv); | ||
| 117 | +void usage(int error); | ||
| 118 | +int block_signals(void); | ||
| 119 | +int allow_sigterm(void); | ||
| 120 | +void set_shutdown_flag(void); | ||
| 121 | +int initialize_group(struct group_parameters *group); | ||
| 122 | +int create_group(struct group_parameters *group); | ||
| 123 | +unsigned long total_inversions(void); | ||
| 124 | +void banner(void); | ||
| 125 | +void summary(void); | ||
| 126 | +void write_stats(FILE *f, void *data); | ||
| 127 | +void wait_for_termination(void); | ||
| 128 | +int barrier_init(pthread_barrier_t *b, const pthread_barrierattr_t *attr, | ||
| 129 | + unsigned int count, const char *name); | ||
| 130 | +void setup_sched_attr(struct sched_attr *attr, int policy, int prio); | ||
| 131 | +void setup_sched_config(int policy); | ||
| 132 | + | ||
| 133 | + | ||
| @@ -0,0 +1,42 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | + | ||
| 3 | + | ||
| 4 | + | ||
| 5 | + | ||
| 6 | + | ||
| 7 | + | ||
| 8 | + | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | +void low(pid_t pid); /* low priority process */ | ||
| 22 | +void medium(void); /* medium priority process */ | ||
| 23 | +void high(pid_t pid); /* high priority process */ | ||
| 24 | +void init_state(void); | ||
| 25 | + | ||
| 26 | +void *mmap_page(void); | ||
| 27 | +long process_shared_mutex_available(void); | ||
| 28 | +void Pthread_mutexattr_init(pthread_mutexattr_t *attr); | ||
| 29 | +void Pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared); | ||
| 30 | +void Pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol); | ||
| 31 | +void Pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr); | ||
| 32 | +void Pthread_mutex_lock(pthread_mutex_t *mutex); | ||
| 33 | +void Pthread_mutex_unlock(pthread_mutex_t *mutex); | ||
| 34 | + | ||
| 35 | +void init_shared_pthread_mutex(pthread_mutex_t *mutex, int protocol); | ||
| 36 | +int set_rt_prio(pid_t pid, int prio, int policy); | ||
| 37 | +int get_rt_prio(pid_t pid); | ||
| 38 | + | ||
| 39 | + | ||
| 40 | + | ||
| 41 | + | ||
| 42 | + | ||
| @@ -0,0 +1,20 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | + | ||
| 3 | + | ||
| 4 | + | ||
| 5 | + | ||
| 6 | + | ||
| 7 | + | ||
| 8 | + | ||
| 9 | + | ||
| 10 | +void err_exit(int err, char *fmt, ...) __attribute__((noreturn)); | ||
| 11 | +void err_msg(char *fmt, ...); | ||
| 12 | +void err_msg_n(int err, char *fmt, ...); | ||
| 13 | +void err_quit(char *fmt, ...) __attribute__((noreturn)); | ||
| 14 | +void debug(int enable, char *fmt, ...); | ||
| 15 | +void info(int enable, char *fmt, ...); | ||
| 16 | +void warn(char *fmt, ...); | ||
| 17 | +void fatal(char *fmt, ...) __attribute__((noreturn)); | ||
| 18 | +void err_doit(int err, const char *fmt, va_list ap); | ||
| 19 | + | ||
| 20 | + | ||
| @@ -0,0 +1,47 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | + | ||
| 3 | + | ||
| 4 | + | ||
| 5 | + | ||
| 6 | + | ||
| 7 | + | ||
| 8 | + | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | +static inline int get_cpu_setup(void) { return 0; } | ||
| 13 | +static inline int get_cpu(void) | ||
| 14 | +{ | ||
| 15 | + int c,s; | ||
| 16 | + /* Show the source of get_cpu */ | ||
| 17 | + | ||
| 18 | + fprintf(stderr, "__NR_getcpu\n"); | ||
| 19 | + | ||
| 20 | + s = syscall(__NR_getcpu, &c, NULL, NULL); | ||
| 21 | + return (s == -1) ? s : c; | ||
| 22 | +} | ||
| 23 | + | ||
| 24 | + && __GLIBC__>=2 && __GLIBC_MINOR__>=6 | ||
| 25 | + | ||
| 26 | +static inline int get_cpu_setup(void) { return 0; } | ||
| 27 | +static inline int get_cpu(void) { return sched_getcpu(); } | ||
| 28 | + | ||
| 29 | +extern int get_cpu_setup(void); | ||
| 30 | +extern int (*get_cpu)(void); | ||
| 31 | +extern int (*get_cpu_vdsop)(unsigned int *, unsigned int *, void *); | ||
| 32 | + | ||
| 33 | +static inline int getcpu_vdso(void) | ||
| 34 | +{ | ||
| 35 | + unsigned int c,s; | ||
| 36 | + /* Show the source of get_cpu */ | ||
| 37 | + | ||
| 38 | + fprintf(stderr, "getcpu_vdso\n"); | ||
| 39 | + | ||
| 40 | + s = get_cpu_vdsop(&c, NULL, NULL); | ||
| 41 | + return (s == -1) ? s : c; | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | + | ||
| 45 | + | ||
| 46 | + | ||
| 47 | + | ||
| @@ -0,0 +1,21 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | + | ||
| 3 | + | ||
| 4 | + | ||
| 5 | + | ||
| 6 | + | ||
| 7 | +enum { | ||
| 8 | + AFFINITY_UNSPECIFIED, | ||
| 9 | + AFFINITY_SPECIFIED, | ||
| 10 | + AFFINITY_USEALL | ||
| 11 | +}; | ||
| 12 | + | ||
| 13 | +int numa_initialize(void); | ||
| 14 | + | ||
| 15 | +int get_available_cpus(struct bitmask *cpumask); | ||
| 16 | +int cpu_for_thread_sp(int thread_num, int max_cpus, struct bitmask *cpumask); | ||
| 17 | +int cpu_for_thread_ua(int thread_num, int max_cpus); | ||
| 18 | + | ||
| 19 | +int parse_cpumask(char *str, int max_cpus, struct bitmask **cpumask); | ||
| 20 | + | ||
| 21 | + | ||
| @@ -0,0 +1,71 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +/* | ||
| 3 | + * rt-sched.h - sched_setattr() and sched_getattr() API | ||
| 4 | + * (C) Dario Faggioli <raistlin@linux.it>, 2009, 2010 | ||
| 5 | + * Copyright (C) 2014 BMW Car IT GmbH, Daniel Wagner <daniel.wagner@bmw-carit.de | ||
| 6 | + */ | ||
| 7 | + | ||
| 8 | +/* This file is based on Dario Faggioli's libdl. Eventually it will be | ||
| 9 | + replaced by a proper implemenation of this API. */ | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + | ||
| 35 | + | ||
| 36 | + | ||
| 37 | + | ||
| 38 | + | ||
| 39 | + | ||
| 40 | + | ||
| 41 | + | ||
| 42 | + | ||
| 43 | + | ||
| 44 | + | ||
| 45 | +struct sched_attr { | ||
| 46 | + uint32_t size; | ||
| 47 | + uint32_t sched_policy; | ||
| 48 | + uint64_t sched_flags; | ||
| 49 | + | ||
| 50 | + /* SCHED_NORMAL, SCHED_BATCH */ | ||
| 51 | + int32_t sched_nice; | ||
| 52 | + | ||
| 53 | + /* SCHED_FIFO, SCHED_RR */ | ||
| 54 | + uint32_t sched_priority; | ||
| 55 | + | ||
| 56 | + /* SCHED_DEADLINE */ | ||
| 57 | + uint64_t sched_runtime; | ||
| 58 | + uint64_t sched_deadline; | ||
| 59 | + uint64_t sched_period; | ||
| 60 | +}; | ||
| 61 | + | ||
| 62 | +int sched_setattr(pid_t pid, | ||
| 63 | + const struct sched_attr *attr, | ||
| 64 | + unsigned int flags); | ||
| 65 | + | ||
| 66 | +int sched_getattr(pid_t pid, | ||
| 67 | + struct sched_attr *attr, | ||
| 68 | + unsigned int size, | ||
| 69 | + unsigned int flags); | ||
| 70 | + | ||
| 71 | + | ||
| @@ -0,0 +1,89 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | + | ||
| 3 | + | ||
| 4 | + | ||
| 5 | + | ||
| 6 | + | ||
| 7 | + | ||
| 8 | + | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | +int check_privs(void); | ||
| 14 | +char *get_debugfileprefix(void); | ||
| 15 | +int mount_debugfs(char *); | ||
| 16 | +int get_tracers(char ***); | ||
| 17 | +int valid_tracer(char *); | ||
| 18 | + | ||
| 19 | +int setevent(char *event, char *val); | ||
| 20 | +int event_enable(char *event); | ||
| 21 | +int event_disable(char *event); | ||
| 22 | +int event_enable_all(void); | ||
| 23 | +int event_disable_all(void); | ||
| 24 | + | ||
| 25 | +const char *policy_to_string(int policy); | ||
| 26 | +uint32_t string_to_policy(const char *str); | ||
| 27 | + | ||
| 28 | +pid_t gettid(void); | ||
| 29 | + | ||
| 30 | +int parse_time_string(char *val); | ||
| 31 | +int parse_mem_string(char *str, uint64_t *val); | ||
| 32 | + | ||
| 33 | +void enable_trace_mark(void); | ||
| 34 | +void tracemark(char *fmt, ...) __attribute__((format(printf, 1, 2))); | ||
| 35 | +void disable_trace_mark(void); | ||
| 36 | + | ||
| 37 | + | ||
| 38 | + | ||
| 39 | + | ||
| 40 | + | ||
| 41 | + | ||
| 42 | + | ||
| 43 | + | ||
| 44 | + | ||
| 45 | + | ||
| 46 | +static inline void tsnorm(struct timespec *ts) | ||
| 47 | +{ | ||
| 48 | + while (ts->tv_nsec >= NSEC_PER_SEC) { | ||
| 49 | + ts->tv_nsec -= NSEC_PER_SEC; | ||
| 50 | + ts->tv_sec++; | ||
| 51 | + } | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +static inline int tsgreater(struct timespec *a, struct timespec *b) | ||
| 55 | +{ | ||
| 56 | + return ((a->tv_sec > b->tv_sec) || | ||
| 57 | + (a->tv_sec == b->tv_sec && a->tv_nsec > b->tv_nsec)); | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +static inline int64_t calcdiff(struct timespec t1, struct timespec t2) | ||
| 61 | +{ | ||
| 62 | + int64_t diff = USEC_PER_SEC * (long long)((int) t1.tv_sec - (int) t2.tv_sec); | ||
| 63 | + diff += ((int) t1.tv_nsec - (int) t2.tv_nsec) / 1000; | ||
| 64 | + return diff; | ||
| 65 | +} | ||
| 66 | + | ||
| 67 | +static inline int64_t calcdiff_ns(struct timespec t1, struct timespec t2) | ||
| 68 | +{ | ||
| 69 | + int64_t diff; | ||
| 70 | + diff = NSEC_PER_SEC * (int64_t)((int) t1.tv_sec - (int) t2.tv_sec); | ||
| 71 | + diff += ((int) t1.tv_nsec - (int) t2.tv_nsec); | ||
| 72 | + return diff; | ||
| 73 | +} | ||
| 74 | + | ||
| 75 | +static inline int64_t calctime(struct timespec t) | ||
| 76 | +{ | ||
| 77 | + int64_t time; | ||
| 78 | + time = USEC_PER_SEC * t.tv_sec; | ||
| 79 | + time += ((int) t.tv_nsec) / 1000; | ||
| 80 | + return time; | ||
| 81 | +} | ||
| 82 | + | ||
| 83 | +void rt_init(int argc, char *argv[]); | ||
| 84 | + | ||
| 85 | +void rt_write_json(const char *filename, int return_code, | ||
| 86 | + void (*cb)(FILE *, void *), | ||
| 87 | + void *data); | ||
| 88 | + | ||
| 89 | + | ||
| @@ -0,0 +1,181 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +/* | ||
| 3 | + * Latency histograms | ||
| 4 | + * | ||
| 5 | + * Copyright 2023 Red Hat Inc. | ||
| 6 | + */ | ||
| 7 | + | ||
| 8 | + | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | +int hist_init(struct histogram *h, unsigned long width, unsigned long num) | ||
| 15 | +{ | ||
| 16 | + memset(h, 0, sizeof(*h)); | ||
| 17 | + h->width = width; | ||
| 18 | + h->num = num; | ||
| 19 | + | ||
| 20 | + h->buckets = calloc(num, sizeof(unsigned long)); | ||
| 21 | + if (!h->buckets) | ||
| 22 | + return -ENOMEM; | ||
| 23 | + | ||
| 24 | + return 0; | ||
| 25 | +} | ||
| 26 | + | ||
| 27 | +int hist_init_oflow(struct histogram *h, unsigned long num) | ||
| 28 | +{ | ||
| 29 | + h->oflow_bufsize = num; | ||
| 30 | + h->oflows = calloc(num, sizeof(unsigned long)); | ||
| 31 | + if (!h->oflows) | ||
| 32 | + return -ENOMEM; | ||
| 33 | + | ||
| 34 | + return 0; | ||
| 35 | +} | ||
| 36 | + | ||
| 37 | +void hist_destroy(struct histogram *h) | ||
| 38 | +{ | ||
| 39 | + free(h->oflows); | ||
| 40 | + h->oflows = NULL; | ||
| 41 | + free(h->buckets); | ||
| 42 | + h->buckets = NULL; | ||
| 43 | +} | ||
| 44 | + | ||
| 45 | +int hist_sample(struct histogram *h, uint64_t sample) | ||
| 46 | +{ | ||
| 47 | + unsigned long bucket = sample / h->width; | ||
| 48 | + unsigned long extra; | ||
| 49 | + unsigned long event = h->events++; | ||
| 50 | + int ret; | ||
| 51 | + | ||
| 52 | + if (bucket < h->num) { | ||
| 53 | + h->buckets[bucket]++; | ||
| 54 | + return 0; | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + ret = HIST_OVERFLOW; | ||
| 58 | + extra = bucket - h->num; | ||
| 59 | + if (h->oflow_magnitude + extra > h->oflow_magnitude) | ||
| 60 | + h->oflow_magnitude += extra; | ||
| 61 | + else | ||
| 62 | + ret |= HIST_OVERFLOW_MAG; | ||
| 63 | + | ||
| 64 | + if (h->oflows) { | ||
| 65 | + if (h->oflow_count < h->oflow_bufsize) | ||
| 66 | + h->oflows[h->oflow_count] = event; | ||
| 67 | + else | ||
| 68 | + ret |= HIST_OVERFLOW_LOG; | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + h->oflow_count++; | ||
| 72 | + return ret; | ||
| 73 | +} | ||
| 74 | + | ||
| 75 | +int hset_init(struct histoset *hs, unsigned long num_histos, | ||
| 76 | + unsigned long bucket_width, unsigned long num_buckets, | ||
| 77 | + unsigned long overflow) | ||
| 78 | +{ | ||
| 79 | + unsigned long i; | ||
| 80 | + | ||
| 81 | + if (num_histos == 0) | ||
| 82 | + return -EINVAL; | ||
| 83 | + | ||
| 84 | + hs->num_histos = num_histos; | ||
| 85 | + hs->num_buckets = num_buckets; | ||
| 86 | + hs->histos = calloc(num_histos, sizeof(struct histogram)); | ||
| 87 | + if (!hs->histos) | ||
| 88 | + return -ENOMEM; | ||
| 89 | + | ||
| 90 | + for (i = 0; i < num_histos; i++) { | ||
| 91 | + if (hist_init(&hs->histos[i], bucket_width, num_buckets)) | ||
| 92 | + goto fail; | ||
| 93 | + if (overflow && hist_init_oflow(&hs->histos[i], overflow)) | ||
| 94 | + goto fail; | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | + return 0; | ||
| 98 | + | ||
| 99 | +fail: | ||
| 100 | + hset_destroy(hs); | ||
| 101 | + return -ENOMEM; | ||
| 102 | +} | ||
| 103 | + | ||
| 104 | +void hset_destroy(struct histoset *hs) | ||
| 105 | +{ | ||
| 106 | + unsigned long i; | ||
| 107 | + | ||
| 108 | + if (hs->histos) { | ||
| 109 | + for (i = 0; i < hs->num_histos; i++) | ||
| 110 | + hist_destroy(&hs->histos[i]); | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + free(hs->histos); | ||
| 114 | + hs->histos = NULL; | ||
| 115 | +} | ||
| 116 | + | ||
| 117 | +void hset_print_bucket(struct histoset *hs, FILE *f, const char *pre, | ||
| 118 | + unsigned long bucket, unsigned long flags) | ||
| 119 | +{ | ||
| 120 | + unsigned long long sum = 0; | ||
| 121 | + unsigned long i; | ||
| 122 | + | ||
| 123 | + if (bucket >= hs->num_buckets) | ||
| 124 | + return; | ||
| 125 | + | ||
| 126 | + for (i = 0; i < hs->num_histos; i++) | ||
| 127 | + sum += hs->histos[i].buckets[bucket]; | ||
| 128 | + | ||
| 129 | + if (sum == 0) | ||
| 130 | + return; | ||
| 131 | + if (pre) | ||
| 132 | + fprintf(f, "%s", pre); | ||
| 133 | + | ||
| 134 | + for (i = 0; i < hs->num_histos; i++) { | ||
| 135 | + unsigned long val = hs->histos[i].buckets[bucket]; | ||
| 136 | + | ||
| 137 | + if (i != 0) | ||
| 138 | + fprintf(f, "\t"); | ||
| 139 | + fprintf(f, "%06lu", val); | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + if (flags & HSET_PRINT_SUM) | ||
| 143 | + fprintf(f, "\t%06llu", sum); | ||
| 144 | + | ||
| 145 | + fprintf(f, "\n"); | ||
| 146 | +} | ||
| 147 | + | ||
| 148 | +void hist_print_json(struct histogram *h, FILE *f) | ||
| 149 | +{ | ||
| 150 | + unsigned long i; | ||
| 151 | + bool comma = false; | ||
| 152 | + | ||
| 153 | + for (i = 0; i < h->num; i++) { | ||
| 154 | + unsigned long val = h->buckets[i]; | ||
| 155 | + | ||
| 156 | + if (val != 0) { | ||
| 157 | + if (comma) | ||
| 158 | + fprintf(f, ","); | ||
| 159 | + fprintf(f, "\n \"%lu\": %lu", i, val); | ||
| 160 | + comma = true; | ||
| 161 | + } | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + fprintf(f, "\n"); | ||
| 165 | +} | ||
| 166 | + | ||
| 167 | +void hist_print_oflows(struct histogram *h, FILE *f) | ||
| 168 | +{ | ||
| 169 | + unsigned long i; | ||
| 170 | + | ||
| 171 | + for (i = 0; i < h->oflow_count; i++) { | ||
| 172 | + if (i >= h->oflow_bufsize) | ||
| 173 | + break; | ||
| 174 | + if (i != 0) | ||
| 175 | + fprintf(f, " "); | ||
| 176 | + fprintf(f, "%05lu", h->oflows[i]); | ||
| 177 | + } | ||
| 178 | + | ||
| 179 | + if (i >= h->oflow_bufsize) | ||
| 180 | + fprintf(f, " # %05lu others", h->oflow_count - h->oflow_bufsize); | ||
| 181 | +} | ||
| @@ -0,0 +1,101 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +/* | ||
| 3 | + * Copyright (C) 2009 John Kacur <jkacur@redhat.com> | ||
| 4 | + * | ||
| 5 | + * error routines, similar to those found in | ||
| 6 | + * Advanced Programming in the UNIX Environment 2nd ed. | ||
| 7 | + */ | ||
| 8 | + | ||
| 9 | + | ||
| 10 | +/* Print an error message, plus a message for err and exit with error err */ | ||
| 11 | +void err_exit(int err, char *fmt, ...) | ||
| 12 | +{ | ||
| 13 | + va_list ap; | ||
| 14 | + va_start(ap, fmt); | ||
| 15 | + err_doit(err, fmt, ap); | ||
| 16 | + va_end(ap); | ||
| 17 | + exit(err); | ||
| 18 | +} | ||
| 19 | + | ||
| 20 | +/* print an error message and return */ | ||
| 21 | +void err_msg(char *fmt, ...) | ||
| 22 | +{ | ||
| 23 | + va_list ap; | ||
| 24 | + va_start(ap, fmt); | ||
| 25 | + err_doit(0, fmt, ap); | ||
| 26 | + va_end(ap); | ||
| 27 | + return; | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +/* Print an error message, plus a message for err, and return */ | ||
| 31 | +void err_msg_n(int err, char *fmt, ...) | ||
| 32 | +{ | ||
| 33 | + va_list ap; | ||
| 34 | + va_start(ap, fmt); | ||
| 35 | + err_doit(err, fmt, ap); | ||
| 36 | + va_end(ap); | ||
| 37 | + return; | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +/* print an error message and quit */ | ||
| 41 | +void err_quit(char *fmt, ...) | ||
| 42 | +{ | ||
| 43 | + va_list ap; | ||
| 44 | + va_start(ap, fmt); | ||
| 45 | + err_doit(0, fmt, ap); | ||
| 46 | + va_end(ap); | ||
| 47 | + exit(1); | ||
| 48 | +} | ||
| 49 | + | ||
| 50 | +void debug(int enable, char *fmt, ...) | ||
| 51 | +{ | ||
| 52 | + if (enable) { | ||
| 53 | + va_list ap; | ||
| 54 | + | ||
| 55 | + va_start(ap, fmt); | ||
| 56 | + fputs("DEBUG: ", stderr); | ||
| 57 | + err_doit(0, fmt, ap); | ||
| 58 | + va_end(ap); | ||
| 59 | + } | ||
| 60 | +} | ||
| 61 | + | ||
| 62 | +void info(int enable, char *fmt, ...) | ||
| 63 | +{ | ||
| 64 | + if (enable) { | ||
| 65 | + va_list ap; | ||
| 66 | + | ||
| 67 | + va_start(ap, fmt); | ||
| 68 | + fputs("INFO: ", stderr); | ||
| 69 | + err_doit(0, fmt, ap); | ||
| 70 | + va_end(ap); | ||
| 71 | + } | ||
| 72 | +} | ||
| 73 | + | ||
| 74 | +void warn(char *fmt, ...) | ||
| 75 | +{ | ||
| 76 | + va_list ap; | ||
| 77 | + | ||
| 78 | + va_start(ap, fmt); | ||
| 79 | + fputs("WARN: ", stderr); | ||
| 80 | + err_doit(0, fmt, ap); | ||
| 81 | + va_end(ap); | ||
| 82 | +} | ||
| 83 | + | ||
| 84 | +void fatal(char *fmt, ...) | ||
| 85 | +{ | ||
| 86 | + va_list ap; | ||
| 87 | + | ||
| 88 | + va_start(ap, fmt); | ||
| 89 | + fputs("FATAL: ", stderr); | ||
| 90 | + err_doit(0, fmt, ap); | ||
| 91 | + va_end(ap); | ||
| 92 | + exit(EXIT_FAILURE); | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +void err_doit(int err, const char *fmt, va_list ap) | ||
| 96 | +{ | ||
| 97 | + vfprintf(stderr, fmt, ap); | ||
| 98 | + if (err) | ||
| 99 | + fprintf(stderr, ": %s\n", strerror(err)); | ||
| 100 | + return; | ||
| 101 | +} | ||
| @@ -0,0 +1,28 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +/* | ||
| 3 | + * Copyright (C) 2009 John Kacur <jkacur@redhat.com> | ||
| 4 | + */ | ||
| 5 | + | ||
| 6 | + | ||
| 7 | + | ||
| 8 | + && __GLIBC__>=2 && __GLIBC_MINOR__>=6 | ||
| 9 | + | ||
| 10 | +int (*get_cpu)(void); | ||
| 11 | +int (*get_cpu_vdsop)(unsigned int *, unsigned int *, void *); | ||
| 12 | + | ||
| 13 | +int get_cpu_setup(void) | ||
| 14 | +{ | ||
| 15 | + void *handle = dlopen("linux-vdso.so.1", RTLD_LAZY); | ||
| 16 | + get_cpu_vdsop = NULL; | ||
| 17 | + if (handle) { | ||
| 18 | + get_cpu_vdsop = dlsym(handle, "getcpu"); | ||
| 19 | + dlclose(handle); | ||
| 20 | + if (get_cpu_vdsop) { | ||
| 21 | + get_cpu = getcpu_vdso; | ||
| 22 | + return 0; | ||
| 23 | + } | ||
| 24 | + } | ||
| 25 | + return -1; | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | + | ||
| @@ -0,0 +1,148 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +/* | ||
| 3 | + * Copyright 2020 Daniel Wagner <dwagner@suse.de> | ||
| 4 | + * Copyright 2020 John Kacur <jkacur@redhat.com> | ||
| 5 | + */ | ||
| 6 | + | ||
| 7 | + | ||
| 8 | + | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | +/* | ||
| 17 | + * numa_available() must be called before any other calls to the numa library | ||
| 18 | + * returns 1 if numa is available, or 0 if numa is not available | ||
| 19 | + */ | ||
| 20 | +int numa_initialize(void) | ||
| 21 | +{ | ||
| 22 | + static int is_initialized; // Only call numa_available once | ||
| 23 | + static int numa; | ||
| 24 | + | ||
| 25 | + if (is_initialized == 1) | ||
| 26 | + return numa; | ||
| 27 | + | ||
| 28 | + if (numa_available() != -1) | ||
| 29 | + numa = 1; | ||
| 30 | + | ||
| 31 | + is_initialized = 1; | ||
| 32 | + | ||
| 33 | + return numa; | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +int get_available_cpus(struct bitmask *cpumask) | ||
| 37 | +{ | ||
| 38 | + cpu_set_t cpuset; | ||
| 39 | + int ret; | ||
| 40 | + | ||
| 41 | + if (cpumask) | ||
| 42 | + return numa_bitmask_weight(cpumask); | ||
| 43 | + | ||
| 44 | + CPU_ZERO(&cpuset); | ||
| 45 | + | ||
| 46 | + ret = sched_getaffinity(0, sizeof(cpu_set_t), &cpuset); | ||
| 47 | + if (ret < 0) | ||
| 48 | + fatal("sched_getaffinity failed: %m\n"); | ||
| 49 | + | ||
| 50 | + return CPU_COUNT(&cpuset); | ||
| 51 | +} | ||
| 52 | + | ||
| 53 | +int cpu_for_thread_sp(int thread_num, int max_cpus, struct bitmask *cpumask) | ||
| 54 | +{ | ||
| 55 | + int m, cpu, i, num_cpus; | ||
| 56 | + | ||
| 57 | + num_cpus = numa_bitmask_weight(cpumask); | ||
| 58 | + | ||
| 59 | + if (num_cpus == 0) | ||
| 60 | + fatal("No allowable cpus to run on\n"); | ||
| 61 | + | ||
| 62 | + m = thread_num % num_cpus; | ||
| 63 | + | ||
| 64 | + /* there are num_cpus bits set, we want position of m'th one */ | ||
| 65 | + for (i = 0, cpu = 0; i < max_cpus; i++) { | ||
| 66 | + if (numa_bitmask_isbitset(cpumask, i)) { | ||
| 67 | + if (cpu == m) | ||
| 68 | + return i; | ||
| 69 | + cpu++; | ||
| 70 | + } | ||
| 71 | + } | ||
| 72 | + warn("Bug in cpu mask handling code.\n"); | ||
| 73 | + return 0; | ||
| 74 | +} | ||
| 75 | + | ||
| 76 | +/* cpu_for_thread AFFINITY_USEALL */ | ||
| 77 | +int cpu_for_thread_ua(int thread_num, int max_cpus) | ||
| 78 | +{ | ||
| 79 | + int res, num_cpus, i, m, cpu; | ||
| 80 | + cpu_set_t cpuset; | ||
| 81 | + | ||
| 82 | + CPU_ZERO(&cpuset); | ||
| 83 | + | ||
| 84 | + res = sched_getaffinity(0, sizeof(cpu_set_t), &cpuset); | ||
| 85 | + if (res != 0) | ||
| 86 | + fatal("sched_getaffinity failed: %s\n", strerror(res)); | ||
| 87 | + | ||
| 88 | + num_cpus = CPU_COUNT(&cpuset); | ||
| 89 | + m = thread_num % num_cpus; | ||
| 90 | + | ||
| 91 | + for (i = 0, cpu = 0; i < max_cpus; i++) { | ||
| 92 | + if (CPU_ISSET(i, &cpuset)) { | ||
| 93 | + if (cpu == m) | ||
| 94 | + return i; | ||
| 95 | + cpu++; | ||
| 96 | + } | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + warn("Bug in cpu mask handling code.\n"); | ||
| 100 | + return 0; | ||
| 101 | +} | ||
| 102 | + | ||
| 103 | +/* | ||
| 104 | + * After this function is called, affinity_mask is the intersection of | ||
| 105 | + * the user supplied affinity mask and the affinity mask from the run | ||
| 106 | + * time environment | ||
| 107 | + */ | ||
| 108 | +static void use_current_cpuset(int max_cpus, struct bitmask *cpumask) | ||
| 109 | +{ | ||
| 110 | + struct bitmask *curmask; | ||
| 111 | + int i; | ||
| 112 | + | ||
| 113 | + curmask = numa_allocate_cpumask(); | ||
| 114 | + numa_sched_getaffinity(getpid(), curmask); | ||
| 115 | + | ||
| 116 | + /* | ||
| 117 | + * Clear bits that are not set in both the cpuset from the | ||
| 118 | + * environment, and in the user specified affinity. | ||
| 119 | + */ | ||
| 120 | + for (i = 0; i < max_cpus; i++) { | ||
| 121 | + if ((!numa_bitmask_isbitset(cpumask, i)) || | ||
| 122 | + (!numa_bitmask_isbitset(curmask, i))) | ||
| 123 | + numa_bitmask_clearbit(cpumask, i); | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + numa_bitmask_free(curmask); | ||
| 127 | +} | ||
| 128 | + | ||
| 129 | +int parse_cpumask(char *str, int max_cpus, struct bitmask **cpumask) | ||
| 130 | +{ | ||
| 131 | + struct bitmask *mask; | ||
| 132 | + | ||
| 133 | + mask = numa_parse_cpustring_all(str); | ||
| 134 | + if (!mask) | ||
| 135 | + return -ENOMEM; | ||
| 136 | + | ||
| 137 | + if (numa_bitmask_weight(mask) == 0) { | ||
| 138 | + numa_bitmask_free(mask); | ||
| 139 | + *cpumask = NULL; | ||
| 140 | + return 0; | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + if (strchr(str, '!') != NULL || strchr(str, '+') != NULL) | ||
| 144 | + use_current_cpuset(max_cpus, mask); | ||
| 145 | + *cpumask = mask; | ||
| 146 | + | ||
| 147 | + return 0; | ||
| 148 | +} | ||
| @@ -0,0 +1,30 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +/* | ||
| 3 | + * rt-sched.h - sched_setattr() and sched_getattr() API | ||
| 4 | + * | ||
| 5 | + * (C) Dario Faggioli <raistlin@linux.it>, 2009, 2010 | ||
| 6 | + * Copyright (C) 2014 BMW Car IT GmbH, Daniel Wagner <daniel.wagner@bmw-carit.de | ||
| 7 | + */ | ||
| 8 | + | ||
| 9 | +/* This file is based on Dario Faggioli's libdl. Eventually it will be | ||
| 10 | + replaced by a proper implemenation of this API. */ | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | +int sched_setattr(pid_t pid, | ||
| 18 | + const struct sched_attr *attr, | ||
| 19 | + unsigned int flags) | ||
| 20 | +{ | ||
| 21 | + return syscall(__NR_sched_setattr, pid, attr, flags); | ||
| 22 | +} | ||
| 23 | + | ||
| 24 | +int sched_getattr(pid_t pid, | ||
| 25 | + struct sched_attr *attr, | ||
| 26 | + unsigned int size, | ||
| 27 | + unsigned int flags) | ||
| 28 | +{ | ||
| 29 | + return syscall(__NR_sched_getattr, pid, attr, size, flags); | ||
| 30 | +} | ||
| @@ -0,0 +1,595 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +/* | ||
| 3 | + * Copyright (C) 2009 Carsten Emde <carsten.emde@osadl.org> | ||
| 4 | + * Copyright (C) 2010 Clark Williams <williams@redhat.com> | ||
| 5 | + * Copyright (C) 2015 John Kacur <jkacur@redhat.com> | ||
| 6 | + * | ||
| 7 | + * based on functions from cyclictest that has | ||
| 8 | + * (C) 2008-2009 Clark Williams <williams@redhat.com> | ||
| 9 | + * (C) 2005-2007 Thomas Gleixner <tglx@linutronix.de> | ||
| 10 | + */ | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + | ||
| 35 | +static char debugfileprefix[MAX_PATH]; | ||
| 36 | +static char *fileprefix; | ||
| 37 | +static int trace_fd = -1; | ||
| 38 | +static int tracemark_fd = -1; | ||
| 39 | +static __thread char tracebuf[TRACEBUFSIZ]; | ||
| 40 | +static char test_cmdline[MAX_COMMAND_LINE]; | ||
| 41 | +static char ts_start[MAX_TS_SIZE]; | ||
| 42 | + | ||
| 43 | +/* | ||
| 44 | + * Finds the tracing directory in a mounted debugfs | ||
| 45 | + */ | ||
| 46 | +char *get_debugfileprefix(void) | ||
| 47 | +{ | ||
| 48 | + char type[100]; | ||
| 49 | + FILE *fp; | ||
| 50 | + int size; | ||
| 51 | + int found = 0; | ||
| 52 | + struct stat s; | ||
| 53 | + | ||
| 54 | + if (debugfileprefix[0] != '\0') | ||
| 55 | + goto out; | ||
| 56 | + | ||
| 57 | + /* look in the "standard" mount point first */ | ||
| 58 | + if ((stat("/sys/kernel/debug/tracing", &s) == 0) && S_ISDIR(s.st_mode)) { | ||
| 59 | + strcpy(debugfileprefix, "/sys/kernel/debug/tracing/"); | ||
| 60 | + goto out; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + /* now look in the "other standard" place */ | ||
| 64 | + if ((stat("/debug/tracing", &s) == 0) && S_ISDIR(s.st_mode)) { | ||
| 65 | + strcpy(debugfileprefix, "/debug/tracing/"); | ||
| 66 | + goto out; | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + /* oh well, parse /proc/mounts and see if it's there */ | ||
| 70 | + if ((fp = fopen("/proc/mounts", "r")) == NULL) | ||
| 71 | + goto out; | ||
| 72 | + | ||
| 73 | + while (fscanf(fp, "%*s %" | ||
| 74 | + STR(MAX_PATH) | ||
| 75 | + "s %99s %*s %*d %*d\n", | ||
| 76 | + debugfileprefix, type) == 2) { | ||
| 77 | + if (strcmp(type, "debugfs") == 0) { | ||
| 78 | + found = 1; | ||
| 79 | + break; | ||
| 80 | + } | ||
| 81 | + /* stupid check for systemd-style autofs mount */ | ||
| 82 | + if ((strcmp(debugfileprefix, "/sys/kernel/debug") == 0) && | ||
| 83 | + (strcmp(type, "systemd") == 0)) { | ||
| 84 | + found = 1; | ||
| 85 | + break; | ||
| 86 | + } | ||
| 87 | + } | ||
| 88 | + fclose(fp); | ||
| 89 | + | ||
| 90 | + if (!found) { | ||
| 91 | + debugfileprefix[0] = '\0'; | ||
| 92 | + goto out; | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + size = sizeof(debugfileprefix) - strlen(debugfileprefix); | ||
| 96 | + strncat(debugfileprefix, "/tracing/", size); | ||
| 97 | + | ||
| 98 | +out: | ||
| 99 | + return debugfileprefix; | ||
| 100 | +} | ||
| 101 | + | ||
| 102 | +int mount_debugfs(char *path) | ||
| 103 | +{ | ||
| 104 | + char *mountpoint = path; | ||
| 105 | + char cmd[MAX_PATH]; | ||
| 106 | + char *prefix; | ||
| 107 | + int ret; | ||
| 108 | + | ||
| 109 | + /* if it's already mounted just return */ | ||
| 110 | + prefix = get_debugfileprefix(); | ||
| 111 | + if (strlen(prefix) != 0) { | ||
| 112 | + info(1, "debugfs mountpoint: %s\n", prefix); | ||
| 113 | + return 0; | ||
| 114 | + } | ||
| 115 | + if (!mountpoint) | ||
| 116 | + mountpoint = "/sys/kernel/debug"; | ||
| 117 | + | ||
| 118 | + sprintf(cmd, "mount -t debugfs debugfs %s", mountpoint); | ||
| 119 | + ret = system(cmd); | ||
| 120 | + if (ret != 0) { | ||
| 121 | + fprintf(stderr, "Error mounting debugfs at %s: %s\n", | ||
| 122 | + mountpoint, strerror(errno)); | ||
| 123 | + return -1; | ||
| 124 | + } | ||
| 125 | + return 0; | ||
| 126 | +} | ||
| 127 | + | ||
| 128 | +static char **tracer_list; | ||
| 129 | +static char *tracer_buffer; | ||
| 130 | +static int num_tracers; | ||
| 131 | + | ||
| 132 | + | ||
| 133 | +/* | ||
| 134 | + * return a list of the tracers configured into the running kernel | ||
| 135 | + */ | ||
| 136 | + | ||
| 137 | +int get_tracers(char ***list) | ||
| 138 | +{ | ||
| 139 | + int ret; | ||
| 140 | + FILE *fp; | ||
| 141 | + char buffer[CHUNKSZ]; | ||
| 142 | + char *prefix = get_debugfileprefix(); | ||
| 143 | + char *tmpbuf = NULL; | ||
| 144 | + char *ptr; | ||
| 145 | + int tmpsz = 0; | ||
| 146 | + | ||
| 147 | + /* if we've already parse it, return what we have */ | ||
| 148 | + if (tracer_list) { | ||
| 149 | + *list = tracer_list; | ||
| 150 | + return num_tracers; | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + /* open the tracing file available_tracers */ | ||
| 154 | + sprintf(buffer, "%savailable_tracers", prefix); | ||
| 155 | + if ((fp = fopen(buffer, "r")) == NULL) | ||
| 156 | + fatal("Can't open %s for reading\n", buffer); | ||
| 157 | + | ||
| 158 | + /* allocate initial buffer */ | ||
| 159 | + ptr = tmpbuf = malloc(CHUNKSZ); | ||
| 160 | + if (ptr == NULL) | ||
| 161 | + fatal("error allocating initial space for tracer list\n"); | ||
| 162 | + | ||
| 163 | + /* read in the list of available tracers */ | ||
| 164 | + while ((ret = fread(buffer, sizeof(char), CHUNKSZ, fp))) { | ||
| 165 | + if ((ptr+ret+1) > (tmpbuf+tmpsz)) { | ||
| 166 | + tmpbuf = realloc(tmpbuf, tmpsz + CHUNKSZ); | ||
| 167 | + if (tmpbuf == NULL) | ||
| 168 | + fatal("error allocating space for list of valid tracers\n"); | ||
| 169 | + tmpsz += CHUNKSZ; | ||
| 170 | + } | ||
| 171 | + strncpy(ptr, buffer, ret); | ||
| 172 | + ptr += ret; | ||
| 173 | + } | ||
| 174 | + fclose(fp); | ||
| 175 | + if (tmpsz == 0) | ||
| 176 | + fatal("error reading available tracers\n"); | ||
| 177 | + | ||
| 178 | + tracer_buffer = tmpbuf; | ||
| 179 | + | ||
| 180 | + /* get a buffer for the pointers to tracers */ | ||
| 181 | + if (!(tracer_list = malloc(sizeof(char *)))) | ||
| 182 | + fatal("error allocatinging tracer list buffer\n"); | ||
| 183 | + | ||
| 184 | + /* parse the buffer */ | ||
| 185 | + ptr = strtok(tmpbuf, " \t\n\r"); | ||
| 186 | + do { | ||
| 187 | + tracer_list[num_tracers++] = ptr; | ||
| 188 | + tracer_list = realloc(tracer_list, sizeof(char*)*(num_tracers+1)); | ||
| 189 | + tracer_list[num_tracers] = NULL; | ||
| 190 | + } while ((ptr = strtok(NULL, " \t\n\r")) != NULL); | ||
| 191 | + | ||
| 192 | + /* return the list and number of tracers */ | ||
| 193 | + *list = tracer_list; | ||
| 194 | + return num_tracers; | ||
| 195 | +} | ||
| 196 | + | ||
| 197 | + | ||
| 198 | +/* | ||
| 199 | + * return zero if tracername is not a valid tracer, non-zero if it is | ||
| 200 | + */ | ||
| 201 | + | ||
| 202 | +int valid_tracer(char *tracername) | ||
| 203 | +{ | ||
| 204 | + char **list; | ||
| 205 | + int ntracers; | ||
| 206 | + int i; | ||
| 207 | + | ||
| 208 | + ntracers = get_tracers(&list); | ||
| 209 | + if (ntracers == 0 || tracername == NULL) | ||
| 210 | + return 0; | ||
| 211 | + for (i = 0; i < ntracers; i++) | ||
| 212 | + if (strncmp(list[i], tracername, strlen(list[i])) == 0) | ||
| 213 | + return 1; | ||
| 214 | + return 0; | ||
| 215 | +} | ||
| 216 | + | ||
| 217 | +/* | ||
| 218 | + * enable event tracepoint | ||
| 219 | + */ | ||
| 220 | +int setevent(char *event, char *val) | ||
| 221 | +{ | ||
| 222 | + char *prefix = get_debugfileprefix(); | ||
| 223 | + char buffer[MAX_PATH]; | ||
| 224 | + int fd; | ||
| 225 | + int ret; | ||
| 226 | + | ||
| 227 | + sprintf(buffer, "%s%s", prefix, event); | ||
| 228 | + if ((fd = open(buffer, O_WRONLY)) < 0) { | ||
| 229 | + warn("unable to open %s\n", buffer); | ||
| 230 | + return -1; | ||
| 231 | + } | ||
| 232 | + if ((ret = write(fd, val, strlen(val))) < 0) { | ||
| 233 | + warn("unable to write %s to %s\n", val, buffer); | ||
| 234 | + close(fd); | ||
| 235 | + return -1; | ||
| 236 | + } | ||
| 237 | + close(fd); | ||
| 238 | + return 0; | ||
| 239 | +} | ||
| 240 | + | ||
| 241 | +int event_enable_all(void) | ||
| 242 | +{ | ||
| 243 | + return setevent("events/enable", "1"); | ||
| 244 | +} | ||
| 245 | + | ||
| 246 | +int event_disable_all(void) | ||
| 247 | +{ | ||
| 248 | + return setevent("events/enable", "0"); | ||
| 249 | +} | ||
| 250 | + | ||
| 251 | +int event_enable(char *event) | ||
| 252 | +{ | ||
| 253 | + char path[MAX_PATH]; | ||
| 254 | + | ||
| 255 | + sprintf(path, "events/%s/enable", event); | ||
| 256 | + return setevent(path, "1"); | ||
| 257 | +} | ||
| 258 | + | ||
| 259 | +int event_disable(char *event) | ||
| 260 | +{ | ||
| 261 | + char path[MAX_PATH]; | ||
| 262 | + | ||
| 263 | + sprintf(path, "events/%s/enable", event); | ||
| 264 | + return setevent(path, "0"); | ||
| 265 | +} | ||
| 266 | + | ||
| 267 | +int check_privs(void) | ||
| 268 | +{ | ||
| 269 | + int policy = sched_getscheduler(0); | ||
| 270 | + struct sched_param param, old_param; | ||
| 271 | + | ||
| 272 | + /* if we're already running a realtime scheduler | ||
| 273 | + * then we *should* be able to change things later | ||
| 274 | + */ | ||
| 275 | + if (policy == SCHED_FIFO || policy == SCHED_RR) | ||
| 276 | + return 0; | ||
| 277 | + | ||
| 278 | + /* first get the current parameters */ | ||
| 279 | + if (sched_getparam(0, &old_param)) { | ||
| 280 | + fprintf(stderr, "unable to get scheduler parameters\n"); | ||
| 281 | + return 1; | ||
| 282 | + } | ||
| 283 | + param = old_param; | ||
| 284 | + | ||
| 285 | + /* try to change to SCHED_FIFO */ | ||
| 286 | + param.sched_priority = 1; | ||
| 287 | + if (sched_setscheduler(0, SCHED_FIFO, ¶m)) { | ||
| 288 | + fprintf(stderr, "Unable to change scheduling policy!\n"); | ||
| 289 | + fprintf(stderr, "either run as root or join realtime group\n"); | ||
| 290 | + return 1; | ||
| 291 | + } | ||
| 292 | + | ||
| 293 | + /* we're good; change back and return success */ | ||
| 294 | + return sched_setscheduler(0, policy, &old_param); | ||
| 295 | +} | ||
| 296 | + | ||
| 297 | +const char *policy_to_string(int policy) | ||
| 298 | +{ | ||
| 299 | + switch (policy) { | ||
| 300 | + case SCHED_OTHER: | ||
| 301 | + return "SCHED_OTHER"; | ||
| 302 | + case SCHED_FIFO: | ||
| 303 | + return "SCHED_FIFO"; | ||
| 304 | + case SCHED_RR: | ||
| 305 | + return "SCHED_RR"; | ||
| 306 | + case SCHED_BATCH: | ||
| 307 | + return "SCHED_BATCH"; | ||
| 308 | + case SCHED_IDLE: | ||
| 309 | + return "SCHED_IDLE"; | ||
| 310 | + case SCHED_DEADLINE: | ||
| 311 | + return "SCHED_DEADLINE"; | ||
| 312 | + } | ||
| 313 | + | ||
| 314 | + return "unknown"; | ||
| 315 | +} | ||
| 316 | + | ||
| 317 | +uint32_t string_to_policy(const char *str) | ||
| 318 | +{ | ||
| 319 | + if (!strcmp(str, "other")) | ||
| 320 | + return SCHED_OTHER; | ||
| 321 | + else if (!strcmp(str, "fifo")) | ||
| 322 | + return SCHED_FIFO; | ||
| 323 | + else if (!strcmp(str, "rr")) | ||
| 324 | + return SCHED_RR; | ||
| 325 | + else if (!strcmp(str, "batch")) | ||
| 326 | + return SCHED_BATCH; | ||
| 327 | + else if (!strcmp(str, "idle")) | ||
| 328 | + return SCHED_IDLE; | ||
| 329 | + else if (!strcmp(str, "deadline")) | ||
| 330 | + return SCHED_DEADLINE; | ||
| 331 | + | ||
| 332 | + return 0; | ||
| 333 | +} | ||
| 334 | + | ||
| 335 | +pid_t gettid(void) | ||
| 336 | +{ | ||
| 337 | + return syscall(SYS_gettid); | ||
| 338 | +} | ||
| 339 | + | ||
| 340 | +/* | ||
| 341 | + * parse an input value as a base10 value followed by an optional | ||
| 342 | + * suffix. The input value is presumed to be in seconds, unless | ||
| 343 | + * followed by a modifier suffix: m=minutes, h=hours, d=days | ||
| 344 | + * | ||
| 345 | + * the return value is a value in seconds | ||
| 346 | + */ | ||
| 347 | +int parse_time_string(char *val) | ||
| 348 | +{ | ||
| 349 | + char *end; | ||
| 350 | + int t = strtol(val, &end, 10); | ||
| 351 | + if (end) { | ||
| 352 | + switch (*end) { | ||
| 353 | + case 'm': | ||
| 354 | + case 'M': | ||
| 355 | + t *= 60; | ||
| 356 | + break; | ||
| 357 | + | ||
| 358 | + case 'h': | ||
| 359 | + case 'H': | ||
| 360 | + t *= 60*60; | ||
| 361 | + break; | ||
| 362 | + | ||
| 363 | + case 'd': | ||
| 364 | + case 'D': | ||
| 365 | + t *= 24*60*60; | ||
| 366 | + break; | ||
| 367 | + | ||
| 368 | + } | ||
| 369 | + } | ||
| 370 | + return t; | ||
| 371 | +} | ||
| 372 | + | ||
| 373 | +int parse_mem_string(char *str, uint64_t *val) | ||
| 374 | +{ | ||
| 375 | + char *endptr; | ||
| 376 | + int v = strtol(str, &endptr, 10); | ||
| 377 | + | ||
| 378 | + if (!*endptr) | ||
| 379 | + return v; | ||
| 380 | + | ||
| 381 | + switch (*endptr) { | ||
| 382 | + case 'g': | ||
| 383 | + case 'G': | ||
| 384 | + v *= 1024; | ||
| 385 | + __attribute__ ((fallthrough)); | ||
| 386 | + case 'm': | ||
| 387 | + case 'M': | ||
| 388 | + v *= 1024; | ||
| 389 | + __attribute__ ((fallthrough)); | ||
| 390 | + case 'k': | ||
| 391 | + case 'K': | ||
| 392 | + v *= 1024; | ||
| 393 | + __attribute__ ((fallthrough)); | ||
| 394 | + case 'b': | ||
| 395 | + case 'B': | ||
| 396 | + break; | ||
| 397 | + default: | ||
| 398 | + return -1; | ||
| 399 | + } | ||
| 400 | + | ||
| 401 | + *val = v; | ||
| 402 | + | ||
| 403 | + return 0; | ||
| 404 | +} | ||
| 405 | + | ||
| 406 | +static void open_tracemark_fd(void) | ||
| 407 | +{ | ||
| 408 | + char path[MAX_PATH]; | ||
| 409 | + | ||
| 410 | + /* | ||
| 411 | + * open the tracemark file if it's not already open | ||
| 412 | + */ | ||
| 413 | + if (tracemark_fd < 0) { | ||
| 414 | + sprintf(path, "%s/%s", fileprefix, "trace_marker"); | ||
| 415 | + tracemark_fd = open(path, O_WRONLY); | ||
| 416 | + if (tracemark_fd < 0) { | ||
| 417 | + warn("unable to open trace_marker file: %s\n", path); | ||
| 418 | + return; | ||
| 419 | + } | ||
| 420 | + } | ||
| 421 | + | ||
| 422 | + /* | ||
| 423 | + * if we're not tracing and the tracing_on fd is not open, | ||
| 424 | + * open the tracing_on file so that we can stop the trace | ||
| 425 | + * if we hit a breaktrace threshold | ||
| 426 | + */ | ||
| 427 | + if (trace_fd < 0) { | ||
| 428 | + sprintf(path, "%s/%s", fileprefix, "tracing_on"); | ||
| 429 | + if ((trace_fd = open(path, O_WRONLY)) < 0) | ||
| 430 | + warn("unable to open tracing_on file: %s\n", path); | ||
| 431 | + } | ||
| 432 | +} | ||
| 433 | + | ||
| 434 | +static void close_tracemark_fd(void) | ||
| 435 | +{ | ||
| 436 | + if (tracemark_fd > 0) | ||
| 437 | + close(tracemark_fd); | ||
| 438 | + | ||
| 439 | + if (trace_fd > 0) | ||
| 440 | + close(trace_fd); | ||
| 441 | +} | ||
| 442 | + | ||
| 443 | +static int trace_file_exists(char *name) | ||
| 444 | +{ | ||
| 445 | + struct stat sbuf; | ||
| 446 | + char *tracing_prefix = get_debugfileprefix(); | ||
| 447 | + char path[MAX_PATH]; | ||
| 448 | + strcat(strcpy(path, tracing_prefix), name); | ||
| 449 | + return stat(path, &sbuf) ? 0 : 1; | ||
| 450 | +} | ||
| 451 | + | ||
| 452 | +static void debugfs_prepare(void) | ||
| 453 | +{ | ||
| 454 | + if (mount_debugfs(NULL)) | ||
| 455 | + fatal("could not mount debugfs"); | ||
| 456 | + | ||
| 457 | + fileprefix = get_debugfileprefix(); | ||
| 458 | + if (!trace_file_exists("tracing_enabled") && | ||
| 459 | + !trace_file_exists("tracing_on")) | ||
| 460 | + warn("tracing_enabled or tracing_on not found\n" | ||
| 461 | + "debug fs not mounted"); | ||
| 462 | +} | ||
| 463 | + | ||
| 464 | +void tracemark(char *fmt, ...) | ||
| 465 | +{ | ||
| 466 | + va_list ap; | ||
| 467 | + int len; | ||
| 468 | + | ||
| 469 | + /* bail out if we're not tracing */ | ||
| 470 | + /* or if the kernel doesn't support trace_mark */ | ||
| 471 | + if (tracemark_fd < 0 || trace_fd < 0) | ||
| 472 | + return; | ||
| 473 | + | ||
| 474 | + va_start(ap, fmt); | ||
| 475 | + len = vsnprintf(tracebuf, TRACEBUFSIZ, fmt, ap); | ||
| 476 | + va_end(ap); | ||
| 477 | + | ||
| 478 | + /* write the tracemark message */ | ||
| 479 | + write(tracemark_fd, tracebuf, len); | ||
| 480 | + | ||
| 481 | + /* now stop any trace */ | ||
| 482 | + write(trace_fd, "0\n", 2); | ||
| 483 | +} | ||
| 484 | + | ||
| 485 | +void enable_trace_mark(void) | ||
| 486 | +{ | ||
| 487 | + debugfs_prepare(); | ||
| 488 | + open_tracemark_fd(); | ||
| 489 | +} | ||
| 490 | + | ||
| 491 | +void disable_trace_mark(void) | ||
| 492 | +{ | ||
| 493 | + close_tracemark_fd(); | ||
| 494 | +} | ||
| 495 | + | ||
| 496 | +static void get_timestamp(char *tsbuf) | ||
| 497 | +{ | ||
| 498 | + struct timeval tv; | ||
| 499 | + struct tm *tm; | ||
| 500 | + time_t t; | ||
| 501 | + | ||
| 502 | + gettimeofday(&tv, NULL); | ||
| 503 | + t = tv.tv_sec; | ||
| 504 | + tm = localtime(&t); | ||
| 505 | + /* RFC 2822-compliant date format */ | ||
| 506 | + strftime(tsbuf, MAX_TS_SIZE, "%a, %d %b %Y %T %z", tm); | ||
| 507 | +} | ||
| 508 | + | ||
| 509 | +void rt_init(int argc, char *argv[]) | ||
| 510 | +{ | ||
| 511 | + int offset = 0; | ||
| 512 | + int len, i; | ||
| 513 | + | ||
| 514 | + test_cmdline[0] = '\0'; | ||
| 515 | + | ||
| 516 | + /* | ||
| 517 | + * getopt_long() permutes the contents of argv as it scans, so | ||
| 518 | + * that eventually all the nonoptions are at the end. Make a | ||
| 519 | + * copy before calling getopt_long(). | ||
| 520 | + */ | ||
| 521 | + for (i = 0; i < argc;) { | ||
| 522 | + len = strlen(argv[i]); | ||
| 523 | + if (offset + len + 1 >= MAX_COMMAND_LINE) | ||
| 524 | + break; | ||
| 525 | + | ||
| 526 | + strcat(test_cmdline, argv[i]); | ||
| 527 | + i++; | ||
| 528 | + if (i < argc) | ||
| 529 | + strcat(test_cmdline, " "); | ||
| 530 | + | ||
| 531 | + offset += len + 1; | ||
| 532 | + } | ||
| 533 | + | ||
| 534 | + get_timestamp(ts_start); | ||
| 535 | +} | ||
| 536 | + | ||
| 537 | +void rt_write_json(const char *filename, int return_code, | ||
| 538 | + void (*cb)(FILE *, void *), | ||
| 539 | + void *data) | ||
| 540 | +{ | ||
| 541 | + unsigned char buf[1]; | ||
| 542 | + struct utsname uts; | ||
| 543 | + char ts_end[MAX_TS_SIZE]; | ||
| 544 | + FILE *f, *s; | ||
| 545 | + size_t n; | ||
| 546 | + int rt = 0; | ||
| 547 | + | ||
| 548 | + if (!filename || !strcmp("-", filename)) { | ||
| 549 | + f = stdout; | ||
| 550 | + } else { | ||
| 551 | + f = fopen(filename, "w"); | ||
| 552 | + if (!f) | ||
| 553 | + err_exit(errno, "Failed to open '%s'\n", filename); | ||
| 554 | + } | ||
| 555 | + | ||
| 556 | + get_timestamp(ts_end); | ||
| 557 | + | ||
| 558 | + s = fopen("/sys/kernel/realtime", "r"); | ||
| 559 | + if (s) { | ||
| 560 | + n = fread(buf, 1, 1, s); | ||
| 561 | + if (n == 1 && buf[0] == '1') | ||
| 562 | + rt = 1; | ||
| 563 | + fclose(s); | ||
| 564 | + } | ||
| 565 | + | ||
| 566 | + if (uname(&uts)) | ||
| 567 | + err_exit(errno, "Could not retrieve system information"); | ||
| 568 | + | ||
| 569 | + fprintf(f, "{\n"); | ||
| 570 | + fprintf(f, " \"file_version\": 1,\n"); | ||
| 571 | + fprintf(f, " \"cmdline:\": \"%s\",\n", test_cmdline); | ||
| 572 | + fprintf(f, " \"rt_test_version:\": \"%1.2f\",\n", VERSION); | ||
| 573 | + fprintf(f, " \"start_time\": \"%s\",\n", ts_start); | ||
| 574 | + fprintf(f, " \"end_time\": \"%s\",\n", ts_end); | ||
| 575 | + fprintf(f, " \"return_code\": %d,\n", return_code); | ||
| 576 | + fprintf(f, " \"sysinfo\": {\n"); | ||
| 577 | + fprintf(f, " \"sysname\": \"%s\",\n", uts.sysname); | ||
| 578 | + fprintf(f, " \"nodename\": \"%s\",\n", uts.nodename); | ||
| 579 | + fprintf(f, " \"release\": \"%s\",\n", uts.release); | ||
| 580 | + fprintf(f, " \"version\": \"%s\",\n", uts.version); | ||
| 581 | + fprintf(f, " \"machine\": \"%s\",\n", uts.machine); | ||
| 582 | + fprintf(f, " \"realtime\": %d\n", rt); | ||
| 583 | + | ||
| 584 | + if (cb) { | ||
| 585 | + fprintf(f, " },\n"); | ||
| 586 | + (cb)(f, data); | ||
| 587 | + } else { | ||
| 588 | + fprintf(f, " }\n"); | ||
| 589 | + } | ||
| 590 | + | ||
| 591 | + fprintf(f, "}\n"); | ||
| 592 | + | ||
| 593 | + if (!filename || strcmp("-", filename)) | ||
| 594 | + fclose(f); | ||
| 595 | +} | ||
| @@ -0,0 +1,78 @@ | |||
| 1 | +.TH OSLAT 8 "August 17, 2020" | ||
| 2 | +# SPDX-License-Identifier: GPL-3.0-only | ||
| 3 | +.\" for manpage-specific macros, see man(7) | ||
| 4 | +.SH NAME | ||
| 5 | +oslat \- OS Latency Detector | ||
| 6 | +.SH SYNOPSIS | ||
| 7 | +.SY oslat | ||
| 8 | +.RI "[ \-shvz ] [ \-b " bucket-size " ] [ \-B " bias " ] [ \-c " cpu-list " ] \ | ||
| 9 | +[ \-C " cpu-main-thread " ] [ \-f " rt-prio " ] [ \-\-json " filename " ] \ | ||
| 10 | +[ \-m " workload-mem " ] [\-t " runtime " ] [ \-T " trace-threshold " ] \ | ||
| 11 | +[ \-w " workload " ] [ \-W " bucket-width " ]" | ||
| 12 | +.SH DESCRIPTION | ||
| 13 | +.B oslat | ||
| 14 | +is an open source userspace polling mode stress program to detect OS level | ||
| 15 | +latency. The program runs a busy loop with no or various workloads, collecting | ||
| 16 | +TSC information and measuring the time frequently during the process. | ||
| 17 | +.SH OPTIONS | ||
| 18 | +.TP | ||
| 19 | +.B \-b, \-\-bucket-size=N | ||
| 20 | +Specify the number of the buckets (4-1024). | ||
| 21 | +.TP | ||
| 22 | +.B \-B, \-\-bias | ||
| 23 | +Add a bias to all the buckets using the estimated mininum. | ||
| 24 | +.TP | ||
| 25 | +.B \-c, \-\-cpu-list=CPULIST | ||
| 26 | +Specify CPUs to run on. For example, '1,3,5,7-15'. | ||
| 27 | +.TP | ||
| 28 | +.B \-C, \-\-cpu-main-thread=CORE | ||
| 29 | +Specify which CPU the main thread runs on. Default is cpu0. | ||
| 30 | +.TP | ||
| 31 | +.B \-f, \-\-rtprio=PRIORITY | ||
| 32 | +Using specific SCHED_FIFO priority (1-99). Otherwise use the default | ||
| 33 | +priority, normally it will be SCHED_OTHER. | ||
| 34 | +.TP | ||
| 35 | +.B \-\-json=FILENAME | ||
| 36 | +Write final results into FILENAME, JSON formatted. | ||
| 37 | +.TP | ||
| 38 | +.B \-m, \-\-workload-mem=SIZE | ||
| 39 | +Size of the memory to use for the workload (e.g., 4K, 1M). | ||
| 40 | +Total memory usage will be this value multiplies 2*N, | ||
| 41 | +because there will be src/dst buffers for each thread, and | ||
| 42 | +N is the number of processors for testing. | ||
| 43 | +.TP | ||
| 44 | +.B \-D, \-\-duration=TIME | ||
| 45 | +Specify test duration, e.g., 60, 20m, 2H (m/M: minutes, h/H: hours, d/D: days). | ||
| 46 | +By default the unit is s/second. | ||
| 47 | +.TP | ||
| 48 | +.B \-T, \-\-trace-threshold=THRESHOLD | ||
| 49 | +Stop the test when threshold triggered (in USEC). At the meantime, print a | ||
| 50 | +marker in ftrace and stop ftrace too. | ||
| 51 | +.TP | ||
| 52 | +.B \-w, \-\-workload=WORKLOAD | ||
| 53 | +Specify a kind of workload, default is no workload. Options: "no", "memmove". | ||
| 54 | +.TP | ||
| 55 | +.B \-s, \-\-single-preheat | ||
| 56 | +Use a single thread when measuring latency at preheat stage | ||
| 57 | +NOTE: please make sure the CPU frequency on all testing cores | ||
| 58 | +are locked before using this parmater. If you don't know how | ||
| 59 | +to lock the freq then please don't use this parameter. | ||
| 60 | +.TP | ||
| 61 | +.B \-W, \-\-bucket-width | ||
| 62 | +Interval between buckets in nanoseconds | ||
| 63 | + | ||
| 64 | +NOTE: Widths not a multiple of 1000 cause ns-precision output | ||
| 65 | +You are responsible for considering the impact of measurement | ||
| 66 | +overhead at the nanosecond scale. | ||
| 67 | +.TP | ||
| 68 | +.B \-h, \-\-help | ||
| 69 | +Show the help message. | ||
| 70 | +.TP | ||
| 71 | +.B \-v, \-\-version | ||
| 72 | +Show the version of the program. | ||
| 73 | +.TP | ||
| 74 | +.B \-z, \-\-zero-omit | ||
| 75 | +Don't display buckets in the output histogram if all zeros. | ||
| 76 | +.SH AUTHOR | ||
| 77 | +.B oslat | ||
| 78 | +was written by Peter Xu <peterx@redhat.com>. | ||
| @@ -0,0 +1,956 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-3.0-only | ||
| 2 | +/* | ||
| 3 | + * oslat - OS latency detector | ||
| 4 | + * | ||
| 5 | + * Copyright 2020 Red Hat Inc. | ||
| 6 | + * | ||
| 7 | + * Authors: Peter Xu <peterx@redhat.com> | ||
| 8 | + * | ||
| 9 | + * Some of the utility code based on sysjitter-1.3: | ||
| 10 | + * Copyright 2010-2015 David Riddoch <david@riddoch.org.uk> | ||
| 11 | + */ | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + | ||
| 35 | + | ||
| 36 | + | ||
| 37 | + | ||
| 38 | + | ||
| 39 | + | ||
| 40 | + | ||
| 41 | + | ||
| 42 | + | ||
| 43 | + | ||
| 44 | + | ||
| 45 | + | ||
| 46 | + | ||
| 47 | + | ||
| 48 | + | ||
| 49 | + | ||
| 50 | + | ||
| 51 | + | ||
| 52 | + | ||
| 53 | + | ||
| 54 | +static inline void frc(uint64_t *pval) | ||
| 55 | +{ | ||
| 56 | + uint32_t low, high; | ||
| 57 | + /* See rdtsc_ordered() of Linux */ | ||
| 58 | + __asm__ __volatile__("lfence"); | ||
| 59 | + __asm__ __volatile__("rdtsc" : "=a" (low), "=d" (high)); | ||
| 60 | + *pval = ((uint64_t) high << 32) | low; | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | + | ||
| 64 | +static inline void frc(uint64_t *pval) | ||
| 65 | +{ | ||
| 66 | + __asm__ __volatile__("rdtsc" : "=A" (*pval)); | ||
| 67 | +} | ||
| 68 | + | ||
| 69 | + | ||
| 70 | +static inline void frc(uint64_t *pval) | ||
| 71 | +{ | ||
| 72 | + __asm__ __volatile__("mfspr %0, 268\n" : "=r" (*pval)); | ||
| 73 | +} | ||
| 74 | + | ||
| 75 | + | ||
| 76 | + | ||
| 77 | + | ||
| 78 | +static unsigned int measure_counter_mhz(void) | ||
| 79 | +{ | ||
| 80 | + unsigned int val; | ||
| 81 | + | ||
| 82 | + __asm__ __volatile__("mrs %0, cntfrq_el0" : "=r" (val)); | ||
| 83 | + | ||
| 84 | + return val / 1e6; | ||
| 85 | +} | ||
| 86 | + | ||
| 87 | +static inline void frc(uint64_t *pval) | ||
| 88 | +{ | ||
| 89 | + /* | ||
| 90 | + * This isb() is required to prevent that the counter value | ||
| 91 | + * is speculated. | ||
| 92 | + */ | ||
| 93 | + __asm__ __volatile__("isb" : : : "memory"); | ||
| 94 | + __asm__ __volatile__("mrs %0, cntvct_el0" : "=r" (*pval) :: "memory"); | ||
| 95 | + /* | ||
| 96 | + * This isb() is required to prevent the processor from accessing | ||
| 97 | + * memory appearing in program order after the read of the counter | ||
| 98 | + * before the counter has been read. Which would skew the counter value | ||
| 99 | + * to a later point than intended. | ||
| 100 | + */ | ||
| 101 | + __asm__ __volatile__("isb" : : : "memory"); | ||
| 102 | + | ||
| 103 | +} | ||
| 104 | + | ||
| 105 | + | ||
| 106 | + | ||
| 107 | + | ||
| 108 | + | ||
| 109 | + | ||
| 110 | + | ||
| 111 | + | ||
| 112 | + | ||
| 113 | +typedef uint64_t stamp_t; /* timestamp */ | ||
| 114 | +typedef uint64_t cycles_t; /* number of cycles */ | ||
| 115 | + | ||
| 116 | + | ||
| 117 | + | ||
| 118 | + | ||
| 119 | +enum command { | ||
| 120 | + WAIT, | ||
| 121 | + GO, | ||
| 122 | + STOP | ||
| 123 | +}; | ||
| 124 | + | ||
| 125 | +enum workload_type { | ||
| 126 | + WORKLOAD_NONE = 0, | ||
| 127 | + WORKLOAD_MEMMOVE, | ||
| 128 | + WORKLOAD_NUM, | ||
| 129 | +}; | ||
| 130 | + | ||
| 131 | +/* This workload needs pre-allocated memory */ | ||
| 132 | + | ||
| 133 | + | ||
| 134 | +typedef void (*workload_fn)(char *src, char *dst, size_t size); | ||
| 135 | + | ||
| 136 | +struct workload { | ||
| 137 | + const char *w_name; | ||
| 138 | + uint64_t w_flags; | ||
| 139 | + workload_fn w_fn; | ||
| 140 | +}; | ||
| 141 | + | ||
| 142 | +/* We'll have buckets 1us, 2us, ..., (BUCKET_SIZE) us. */ | ||
| 143 | + | ||
| 144 | + | ||
| 145 | +/* Default size of the workloads per thread (in bytes, which is 16KB) */ | ||
| 146 | + | ||
| 147 | + | ||
| 148 | +/* By default, no workload */ | ||
| 149 | + | ||
| 150 | + | ||
| 151 | +struct thread { | ||
| 152 | + int core_i; | ||
| 153 | + pthread_t thread_id; | ||
| 154 | + | ||
| 155 | + /* NOTE! this is also how many ticks per us */ | ||
| 156 | + unsigned int counter_mhz; | ||
| 157 | + cycles_t int_total; | ||
| 158 | + stamp_t frc_start; | ||
| 159 | + stamp_t frc_stop; | ||
| 160 | + cycles_t runtime; | ||
| 161 | + stamp_t *buckets; | ||
| 162 | + uint64_t minlat; | ||
| 163 | + /* Maximum latency detected */ | ||
| 164 | + uint64_t maxlat; | ||
| 165 | + /* | ||
| 166 | + * The extra part of the interruptions that cannot be put into even the | ||
| 167 | + * biggest bucket. We'll use this to calculate a more accurate average at | ||
| 168 | + * the end of the tests. | ||
| 169 | + */ | ||
| 170 | + uint64_t overflow_sum; | ||
| 171 | + int memory_allocated; | ||
| 172 | + | ||
| 173 | + /* Buffers used for the workloads */ | ||
| 174 | + char *src_buf; | ||
| 175 | + char *dst_buf; | ||
| 176 | + | ||
| 177 | + /* These variables are calculated after the test */ | ||
| 178 | + double average; | ||
| 179 | +}; | ||
| 180 | + | ||
| 181 | +struct global { | ||
| 182 | + /* Configuration. */ | ||
| 183 | + unsigned int runtime_secs; | ||
| 184 | + bool preheat; | ||
| 185 | + /* | ||
| 186 | + * Number of threads running for current test | ||
| 187 | + * (either pre heat or real run) | ||
| 188 | + */ | ||
| 189 | + unsigned int n_threads; | ||
| 190 | + /* Number of threads to test for the real run */ | ||
| 191 | + unsigned int n_threads_total; | ||
| 192 | + struct timeval tv_start; | ||
| 193 | + int rtprio; | ||
| 194 | + int bucket_size; | ||
| 195 | + bool bucket_size_param; | ||
| 196 | + int bucket_width; | ||
| 197 | + int unit_per_us; | ||
| 198 | + int precision; | ||
| 199 | + int trace_threshold; | ||
| 200 | + int runtime; | ||
| 201 | + /* The core that we run the main thread. Default is cpu0 */ | ||
| 202 | + int cpu_main_thread; | ||
| 203 | + char *cpu_list; | ||
| 204 | + char *app_name; | ||
| 205 | + struct workload *workload; | ||
| 206 | + uint64_t workload_mem_size; | ||
| 207 | + int enable_bias; | ||
| 208 | + uint64_t bias; | ||
| 209 | + int quiet; | ||
| 210 | + int single_preheat_thread; | ||
| 211 | + int output_omit_zero_buckets; | ||
| 212 | + char jsonfile[MAX_PATH]; | ||
| 213 | + | ||
| 214 | + /* Mutable state. */ | ||
| 215 | + volatile enum command cmd; | ||
| 216 | + volatile unsigned int n_threads_started; | ||
| 217 | + volatile unsigned int n_threads_running; | ||
| 218 | + volatile unsigned int n_threads_finished; | ||
| 219 | +}; | ||
| 220 | + | ||
| 221 | +static struct global g; | ||
| 222 | + | ||
| 223 | +static void workload_nop(char *dst __attribute__ ((unused)), char *src __attribute__ ((unused)), size_t size __attribute__ ((unused))) | ||
| 224 | +{ | ||
| 225 | + /* Nop */ | ||
| 226 | +} | ||
| 227 | + | ||
| 228 | +static void workload_memmove(char *dst, char *src, size_t size) | ||
| 229 | +{ | ||
| 230 | + memmove(dst, src, size); | ||
| 231 | +} | ||
| 232 | + | ||
| 233 | +struct workload workload_list[WORKLOAD_NUM] = { | ||
| 234 | + { "no", 0, workload_nop }, | ||
| 235 | + { "memmove", WORK_NEED_MEM, workload_memmove }, | ||
| 236 | +}; | ||
| 237 | + | ||
| 238 | + | ||
| 239 | + do { \ | ||
| 240 | + if (!(x)) \ | ||
| 241 | + test_fail( | ||
| 242 | + } while (0) | ||
| 243 | + | ||
| 244 | + | ||
| 245 | + | ||
| 246 | +static void test_fail(const char *what, int line) | ||
| 247 | +{ | ||
| 248 | + fprintf(stderr, "ERROR:\n"); | ||
| 249 | + fprintf(stderr, "ERROR: TEST(%s)\n", what); | ||
| 250 | + fprintf(stderr, "ERROR: at line %d\n", line); | ||
| 251 | + fprintf(stderr, "ERROR: errno=%d (%s)\n", errno, strerror(errno)); | ||
| 252 | + fprintf(stderr, "ERROR:\n"); | ||
| 253 | + exit(1); | ||
| 254 | +} | ||
| 255 | + | ||
| 256 | +static int move_to_core(int core_i) | ||
| 257 | +{ | ||
| 258 | + cpu_set_t cpus; | ||
| 259 | + | ||
| 260 | + CPU_ZERO(&cpus); | ||
| 261 | + CPU_SET(core_i, &cpus); | ||
| 262 | + return sched_setaffinity(0, sizeof(cpus), &cpus); | ||
| 263 | +} | ||
| 264 | + | ||
| 265 | + | ||
| 266 | +static cycles_t __measure_counter_hz(void) | ||
| 267 | +{ | ||
| 268 | + struct timeval tvs, tve; | ||
| 269 | + stamp_t s, e; | ||
| 270 | + double sec; | ||
| 271 | + | ||
| 272 | + frc(&s); | ||
| 273 | + e = s; | ||
| 274 | + gettimeofday(&tvs, NULL); | ||
| 275 | + while (e - s < 1000000) | ||
| 276 | + frc(&e); | ||
| 277 | + gettimeofday(&tve, NULL); | ||
| 278 | + sec = tve.tv_sec - tvs.tv_sec + (tve.tv_usec - tvs.tv_usec) / 1e6; | ||
| 279 | + return (cycles_t) ((e - s) / sec); | ||
| 280 | +} | ||
| 281 | + | ||
| 282 | +static unsigned int measure_counter_mhz(void) | ||
| 283 | +{ | ||
| 284 | + cycles_t m, mprev, d; | ||
| 285 | + | ||
| 286 | + mprev = __measure_counter_hz(); | ||
| 287 | + do { | ||
| 288 | + m = __measure_counter_hz(); | ||
| 289 | + if (m > mprev) | ||
| 290 | + d = m - mprev; | ||
| 291 | + else | ||
| 292 | + d = mprev - m; | ||
| 293 | + mprev = m; | ||
| 294 | + } while (d > m / 1000); | ||
| 295 | + | ||
| 296 | + return (unsigned int) (m / 1000000); | ||
| 297 | +} | ||
| 298 | + | ||
| 299 | + | ||
| 300 | +static void thread_init(struct thread *t) | ||
| 301 | +{ | ||
| 302 | + t->counter_mhz = measure_counter_mhz(); | ||
| 303 | + t->maxlat = 0; | ||
| 304 | + t->overflow_sum = 0; | ||
| 305 | + t->minlat = (uint64_t)-1; | ||
| 306 | + | ||
| 307 | + /* NOTE: all the buffers are not freed until the process quits. */ | ||
| 308 | + if (!t->memory_allocated) { | ||
| 309 | + TEST(t->buckets = calloc(1, sizeof(t->buckets[0]) * g.bucket_size)); | ||
| 310 | + if (g.workload->w_flags & WORK_NEED_MEM) { | ||
| 311 | + TEST0(posix_memalign((void **)&t->src_buf, getpagesize(), | ||
| 312 | + g.workload_mem_size)); | ||
| 313 | + memset(t->src_buf, 0, g.workload_mem_size); | ||
| 314 | + TEST0(posix_memalign((void **)&t->dst_buf, getpagesize(), | ||
| 315 | + g.workload_mem_size)); | ||
| 316 | + memset(t->dst_buf, 0, g.workload_mem_size); | ||
| 317 | + } | ||
| 318 | + t->memory_allocated = 1; | ||
| 319 | + } else { | ||
| 320 | + /* Clear the buckets */ | ||
| 321 | + memset(t->buckets, 0, sizeof(t->buckets[0]) * g.bucket_size); | ||
| 322 | + } | ||
| 323 | +} | ||
| 324 | + | ||
| 325 | +static float cycles_to_sec(const struct thread *t, uint64_t cycles) | ||
| 326 | +{ | ||
| 327 | + return cycles / (t->counter_mhz * 1e6); | ||
| 328 | +} | ||
| 329 | + | ||
| 330 | +static void insert_bucket(struct thread *t, stamp_t value) | ||
| 331 | +{ | ||
| 332 | + int index; | ||
| 333 | + unsigned int lat; | ||
| 334 | + uint64_t extra; | ||
| 335 | + double us; | ||
| 336 | + | ||
| 337 | + lat = (value * g.unit_per_us) / t->counter_mhz; | ||
| 338 | + us = (double)lat / g.unit_per_us; | ||
| 339 | + if (!g.preheat && g.trace_threshold && us >= g.trace_threshold) { | ||
| 340 | + char *line = "%s: Trace threshold (%d us) triggered on cpu %d with %.*f us!\n"; | ||
| 341 | + tracemark(line, g.app_name, g.trace_threshold, t->core_i, | ||
| 342 | + g.precision, us); | ||
| 343 | + err_quit(line, g.app_name, g.trace_threshold, t->core_i, | ||
| 344 | + g.precision, us); | ||
| 345 | + } | ||
| 346 | + | ||
| 347 | + /* Update max latency */ | ||
| 348 | + if (lat > t->maxlat) | ||
| 349 | + t->maxlat = lat; | ||
| 350 | + | ||
| 351 | + if (lat < t->minlat) | ||
| 352 | + t->minlat = lat; | ||
| 353 | + | ||
| 354 | + if (g.bias) { | ||
| 355 | + /* t->bias will be set after pre-heat if user enabled it */ | ||
| 356 | + lat -= g.bias; | ||
| 357 | + /* | ||
| 358 | + * Negative should hardly happen, but if it happens, we assume we're in | ||
| 359 | + * the smallest bucket. | ||
| 360 | + */ | ||
| 361 | + if (lat <= 0) | ||
| 362 | + lat = 1; | ||
| 363 | + } | ||
| 364 | + | ||
| 365 | + index = lat / g.bucket_width; | ||
| 366 | + assert(index >= 0); | ||
| 367 | + | ||
| 368 | + /* Too big the jitter; put into the last bucket */ | ||
| 369 | + if (index >= g.bucket_size) { | ||
| 370 | + /* Keep the extra bit (in bucket width multiples) */ | ||
| 371 | + extra = index - g.bucket_size; | ||
| 372 | + if (t->overflow_sum + extra < t->overflow_sum) { | ||
| 373 | + /* The uint64_t even overflowed itself; bail out */ | ||
| 374 | + printf("Accumulated overflow too much!\n"); | ||
| 375 | + exit(1); | ||
| 376 | + } | ||
| 377 | + t->overflow_sum += extra; | ||
| 378 | + index = g.bucket_size - 1; | ||
| 379 | + } | ||
| 380 | + | ||
| 381 | + t->buckets[index]++; | ||
| 382 | + if (t->buckets[index] == 0) { | ||
| 383 | + printf("Bucket %d overflowed\n", index); | ||
| 384 | + exit(1); | ||
| 385 | + } | ||
| 386 | +} | ||
| 387 | + | ||
| 388 | +static void doit(struct thread *t) | ||
| 389 | +{ | ||
| 390 | + stamp_t ts1, ts2; | ||
| 391 | + workload_fn workload_fn = g.workload->w_fn; | ||
| 392 | + | ||
| 393 | + frc(&ts2); | ||
| 394 | + do { | ||
| 395 | + workload_fn(t->dst_buf, t->src_buf, g.workload_mem_size); | ||
| 396 | + frc(&ts1); | ||
| 397 | + insert_bucket(t, ts1 - ts2); | ||
| 398 | + ts2 = ts1; | ||
| 399 | + } while (g.cmd == GO); | ||
| 400 | +} | ||
| 401 | + | ||
| 402 | +static int set_fifo_prio(int prio) | ||
| 403 | +{ | ||
| 404 | + struct sched_param param; | ||
| 405 | + | ||
| 406 | + memset(¶m, 0, sizeof(param)); | ||
| 407 | + param.sched_priority = prio; | ||
| 408 | + return sched_setscheduler(0, SCHED_FIFO, ¶m); | ||
| 409 | +} | ||
| 410 | + | ||
| 411 | +static void *thread_main(void *arg) | ||
| 412 | +{ | ||
| 413 | + /* Important thing to note here is that once we start bashing the CPU, we | ||
| 414 | + * need to keep doing so to prevent the core from changing frequency or | ||
| 415 | + * dropping into a low power state. | ||
| 416 | + */ | ||
| 417 | + struct thread *t = arg; | ||
| 418 | + | ||
| 419 | + /* Alloc memory in the thread itself after setting affinity to get the | ||
| 420 | + * best chance of getting numa-local memory. Doesn't matter so much for | ||
| 421 | + * the "struct thread" since we expect that to stay cache resident. | ||
| 422 | + */ | ||
| 423 | + TEST(move_to_core(t->core_i) == 0); | ||
| 424 | + if (g.rtprio) | ||
| 425 | + TEST(set_fifo_prio(g.rtprio) == 0); | ||
| 426 | + | ||
| 427 | + /* Don't bash the cpu until all threads have got going. */ | ||
| 428 | + atomic_inc(&g.n_threads_started); | ||
| 429 | + while (g.cmd == WAIT) | ||
| 430 | + usleep(1000); | ||
| 431 | + | ||
| 432 | + thread_init(t); | ||
| 433 | + | ||
| 434 | + /* Ensure we all start at the same time. */ | ||
| 435 | + atomic_inc(&g.n_threads_running); | ||
| 436 | + while (g.n_threads_running != g.n_threads) | ||
| 437 | + relax(); | ||
| 438 | + | ||
| 439 | + frc(&t->frc_start); | ||
| 440 | + doit(t); | ||
| 441 | + frc(&t->frc_stop); | ||
| 442 | + | ||
| 443 | + t->runtime = t->frc_stop - t->frc_start; | ||
| 444 | + | ||
| 445 | + /* Wait for everyone to finish so we don't disturb them by exiting and | ||
| 446 | + * waking the main thread. | ||
| 447 | + */ | ||
| 448 | + atomic_inc(&g.n_threads_finished); | ||
| 449 | + while (g.n_threads_finished != g.n_threads) | ||
| 450 | + relax(); | ||
| 451 | + | ||
| 452 | + return NULL; | ||
| 453 | +} | ||
| 454 | + | ||
| 455 | + | ||
| 456 | + printf("%12s:\t", label); \ | ||
| 457 | + for (i = 0; i < g.n_threads; ++i) \ | ||
| 458 | + printf(" %"fmt, val); \ | ||
| 459 | + printf("%s\n", end); \ | ||
| 460 | + } while (0) | ||
| 461 | + | ||
| 462 | + | ||
| 463 | + printf("%12s:\t", label); \ | ||
| 464 | + for (i = 0; i < g.n_threads; ++i) \ | ||
| 465 | + printf(" %.*f", g.precision, \ | ||
| 466 | + (double)(val) / g.unit_per_us); \ | ||
| 467 | + printf("%s\n", end); \ | ||
| 468 | + } while (0) | ||
| 469 | + | ||
| 470 | +static double bucket_to_lat(int bucket) | ||
| 471 | +{ | ||
| 472 | + return (g.bias + (bucket + 1) * (double)g.bucket_width) / g.unit_per_us; | ||
| 473 | +} | ||
| 474 | + | ||
| 475 | +void calculate(struct thread *t) | ||
| 476 | +{ | ||
| 477 | + int j; | ||
| 478 | + unsigned int i; | ||
| 479 | + double sum; | ||
| 480 | + uint64_t count; | ||
| 481 | + | ||
| 482 | + for (i = 0; i < g.n_threads; ++i) { | ||
| 483 | + /* Calculate average */ | ||
| 484 | + sum = count = 0; | ||
| 485 | + for (j = 0; j < g.bucket_size; j++) { | ||
| 486 | + sum += t[i].buckets[j] * bucket_to_lat(j); | ||
| 487 | + count += t[i].buckets[j]; | ||
| 488 | + } | ||
| 489 | + /* Add the extra amount of huge spikes in */ | ||
| 490 | + sum += t->overflow_sum * g.bucket_width; | ||
| 491 | + t[i].average = sum / count; | ||
| 492 | + } | ||
| 493 | +} | ||
| 494 | + | ||
| 495 | +static void write_summary(struct thread *t) | ||
| 496 | +{ | ||
| 497 | + int j, print_dotdotdot = 0; | ||
| 498 | + unsigned long int i, k; | ||
| 499 | + char bucket_name[64]; | ||
| 500 | + | ||
| 501 | + calculate(t); | ||
| 502 | + | ||
| 503 | + putfield("Core", t[i].core_i, "d", ""); | ||
| 504 | + putfield("Counter Freq", t[i].counter_mhz, "u", " (MHz)"); | ||
| 505 | + | ||
| 506 | + for (j = 0; j < g.bucket_size; j++) { | ||
| 507 | + if (j < g.bucket_size-1 && g.output_omit_zero_buckets) { | ||
| 508 | + for (k = 0; k < g.n_threads; k++) { | ||
| 509 | + if (t[k].buckets[j] != 0) | ||
| 510 | + break; | ||
| 511 | + } | ||
| 512 | + if (k == g.n_threads) { | ||
| 513 | + print_dotdotdot = 1; | ||
| 514 | + continue; | ||
| 515 | + } | ||
| 516 | + } | ||
| 517 | + | ||
| 518 | + if (print_dotdotdot) { | ||
| 519 | + printf(" ...\n"); | ||
| 520 | + print_dotdotdot = 0; | ||
| 521 | + } | ||
| 522 | + | ||
| 523 | + snprintf(bucket_name, sizeof(bucket_name), "%03.*f (us)", | ||
| 524 | + g.precision, bucket_to_lat(j)); | ||
| 525 | + putfield(bucket_name, t[i].buckets[j], PRIu64, | ||
| 526 | + (j == g.bucket_size - 1) ? " (including overflows)" : ""); | ||
| 527 | + } | ||
| 528 | + | ||
| 529 | + putfieldp("Minimum", t[i].minlat, " (us)"); | ||
| 530 | + putfield("Average", t[i].average, ".3lf", " (us)"); | ||
| 531 | + putfieldp("Maximum", t[i].maxlat, " (us)"); | ||
| 532 | + putfieldp("Max-Min", t[i].maxlat - t[i].minlat, " (us)"); | ||
| 533 | + putfield("Duration", cycles_to_sec(&(t[i]), t[i].runtime), | ||
| 534 | + ".3f", " (sec)"); | ||
| 535 | + printf("\n"); | ||
| 536 | +} | ||
| 537 | + | ||
| 538 | +static void write_summary_json(FILE *f, void *data) | ||
| 539 | +{ | ||
| 540 | + struct thread *t = data; | ||
| 541 | + int j, comma; | ||
| 542 | + unsigned long int i; | ||
| 543 | + | ||
| 544 | + fprintf(f, " \"num_threads\": %d,\n", g.n_threads); | ||
| 545 | + fprintf(f, " \"thread\": {\n"); | ||
| 546 | + for (i = 0; i < g.n_threads; ++i) { | ||
| 547 | + fprintf(f, " \"%lu\": {\n", i); | ||
| 548 | + fprintf(f, " \"cpu\": %d,\n", t[i].core_i); | ||
| 549 | + fprintf(f, " \"freq\": %d,\n", t[i].counter_mhz); | ||
| 550 | + fprintf(f, " \"min\": %" PRIu64 ",\n", t[i].minlat); | ||
| 551 | + fprintf(f, " \"avg\": %3lf,\n", t[i].average); | ||
| 552 | + fprintf(f, " \"max\": %" PRIu64 ",\n", t[i].maxlat); | ||
| 553 | + fprintf(f, " \"duration\": %.3f,\n", | ||
| 554 | + cycles_to_sec(&(t[i]), t[i].runtime)); | ||
| 555 | + fprintf(f, " \"histogram\": {"); | ||
| 556 | + for (j = 0, comma = 0; j < g.bucket_size; j++) { | ||
| 557 | + if (t[i].buckets[j] == 0) | ||
| 558 | + continue; | ||
| 559 | + fprintf(f, "%s", comma ? ",\n" : "\n"); | ||
| 560 | + fprintf(f, " \"%.*f\": %" PRIu64, | ||
| 561 | + g.precision, bucket_to_lat(j), t[i].buckets[j]); | ||
| 562 | + comma = 1; | ||
| 563 | + } | ||
| 564 | + if (comma) | ||
| 565 | + fprintf(f, "\n"); | ||
| 566 | + fprintf(f, " }\n"); | ||
| 567 | + fprintf(f, " }%s\n", i == g.n_threads - 1 ? "" : ","); | ||
| 568 | + } | ||
| 569 | + fprintf(f, " }\n"); | ||
| 570 | +} | ||
| 571 | + | ||
| 572 | +static void run_expt(struct thread *threads, int runtime_secs, bool preheat) | ||
| 573 | +{ | ||
| 574 | + unsigned long int i; | ||
| 575 | + | ||
| 576 | + g.runtime_secs = runtime_secs; | ||
| 577 | + g.preheat = preheat; | ||
| 578 | + g.n_threads_started = 0; | ||
| 579 | + g.n_threads_running = 0; | ||
| 580 | + g.n_threads_finished = 0; | ||
| 581 | + g.cmd = WAIT; | ||
| 582 | + | ||
| 583 | + for (i = 0; i < g.n_threads; ++i) | ||
| 584 | + TEST0(pthread_create(&(threads[i].thread_id), NULL, | ||
| 585 | + thread_main, &(threads[i]))); | ||
| 586 | + while (g.n_threads_started != g.n_threads) | ||
| 587 | + usleep(1000); | ||
| 588 | + | ||
| 589 | + gettimeofday(&g.tv_start, NULL); | ||
| 590 | + g.cmd = GO; | ||
| 591 | + | ||
| 592 | + alarm(runtime_secs); | ||
| 593 | + | ||
| 594 | + /* Go to sleep until the threads have done their stuff. */ | ||
| 595 | + for (i = 0; i < g.n_threads; ++i) | ||
| 596 | + pthread_join(threads[i].thread_id, NULL); | ||
| 597 | +} | ||
| 598 | + | ||
| 599 | +static void handle_alarm(int code __attribute__ ((unused))) | ||
| 600 | +{ | ||
| 601 | + g.cmd = STOP; | ||
| 602 | +} | ||
| 603 | + | ||
| 604 | +static void usage(int error) | ||
| 605 | +{ | ||
| 606 | + printf("Usage:\n" | ||
| 607 | + "oslat <options>\n\n" | ||
| 608 | + "This is an OS latency detector by running busy loops on specified cores.\n" | ||
| 609 | + "Please run this tool using root.\n\n" | ||
| 610 | + "Available options:\n\n" | ||
| 611 | + "-b, --bucket-size Specify the number of the buckets (4-1024)\n" | ||
| 612 | + "-B, --bias Add a bias to all the buckets using the estimated mininum\n" | ||
| 613 | + "-c, --cpu-list Specify CPUs to run on, e.g. '1,3,5,7-15'\n" | ||
| 614 | + "-C, --cpu-main-thread Specify which CPU the main thread runs on. Default is cpu0.\n" | ||
| 615 | + "-D, --duration Specify test duration, e.g., 60, 20m, 2H\n" | ||
| 616 | + " (m/M: minutes, h/H: hours, d/D: days)\n" | ||
| 617 | + " --json=FILENAME write final results into FILENAME, JSON formatted\n" | ||
| 618 | + "-f, --rtprio Using SCHED_FIFO priority (1-99)\n" | ||
| 619 | + "-m, --workload-mem Size of the memory to use for the workload (e.g., 4K, 1M).\n" | ||
| 620 | + " Total memory usage will be this value multiplies 2*N,\n" | ||
| 621 | + " because there will be src/dst buffers for each thread, and\n" | ||
| 622 | + " N is the number of processors for testing.\n" | ||
| 623 | + "-q --quiet print a summary only on exit\n" | ||
| 624 | + "-s, --single-preheat Use a single thread when measuring latency at preheat stage\n" | ||
| 625 | + " NOTE: please make sure the CPU frequency on all testing cores\n" | ||
| 626 | + " are locked before using this parmater. If you don't know how\n" | ||
| 627 | + " to lock the freq then please don't use this parameter.\n" | ||
| 628 | + "-T, --trace-threshold Stop the test when threshold triggered (in us),\n" | ||
| 629 | + " print a marker in ftrace and stop ftrace too.\n" | ||
| 630 | + "-v, --version Display the version of the software.\n" | ||
| 631 | + "-w, --workload Specify a kind of workload, default is no workload\n" | ||
| 632 | + " (options: no, memmove)\n" | ||
| 633 | + "-W, --bucket-width Interval between buckets in nanoseconds\n" | ||
| 634 | + " NOTE: Widths not a multiple of 1000 cause ns-precision output\n" | ||
| 635 | + " You are responsible for considering the impact of measurement\n" | ||
| 636 | + " overhead at the nanosecond scale.\n" | ||
| 637 | + "-z, --zero-omit Don't display buckets in the output histogram if all zeros.\n" | ||
| 638 | + ); | ||
| 639 | + exit(error); | ||
| 640 | +} | ||
| 641 | + | ||
| 642 | +static int workload_select(char *name) | ||
| 643 | +{ | ||
| 644 | + int i = 0; | ||
| 645 | + | ||
| 646 | + for (i = 0; i < WORKLOAD_NUM; i++) { | ||
| 647 | + if (!strcmp(name, workload_list[i].w_name)) { | ||
| 648 | + g.workload = &workload_list[i]; | ||
| 649 | + return 0; | ||
| 650 | + } | ||
| 651 | + } | ||
| 652 | + | ||
| 653 | + return -1; | ||
| 654 | +} | ||
| 655 | + | ||
| 656 | +enum option_value { | ||
| 657 | + OPT_BUCKETSIZE = 1, OPT_BUCKETWIDTH, OPT_CPU_LIST, OPT_CPU_MAIN_THREAD, | ||
| 658 | + OPT_DURATION, OPT_JSON, OPT_RT_PRIO, OPT_HELP, OPT_TRACE_TH, | ||
| 659 | + OPT_WORKLOAD, OPT_WORKLOAD_MEM, OPT_BIAS, | ||
| 660 | + OPT_QUIET, OPT_SINGLE_PREHEAT, OPT_ZERO_OMIT, | ||
| 661 | + OPT_VERSION | ||
| 662 | +}; | ||
| 663 | + | ||
| 664 | +/* Process commandline options */ | ||
| 665 | +static void parse_options(int argc, char *argv[]) | ||
| 666 | +{ | ||
| 667 | + while (1) { | ||
| 668 | + int option_index = 0; | ||
| 669 | + static struct option options[] = { | ||
| 670 | + { "bucket-size", required_argument, NULL, OPT_BUCKETSIZE }, | ||
| 671 | + { "bucket-width", required_argument, NULL, OPT_BUCKETWIDTH }, | ||
| 672 | + { "cpu-list", required_argument, NULL, OPT_CPU_LIST }, | ||
| 673 | + { "cpu-main-thread", required_argument, NULL, OPT_CPU_MAIN_THREAD}, | ||
| 674 | + { "duration", required_argument, NULL, OPT_DURATION }, | ||
| 675 | + { "json", required_argument, NULL, OPT_JSON }, | ||
| 676 | + { "rtprio", required_argument, NULL, OPT_RT_PRIO }, | ||
| 677 | + { "help", no_argument, NULL, OPT_HELP }, | ||
| 678 | + { "trace-threshold", required_argument, NULL, OPT_TRACE_TH }, | ||
| 679 | + { "workload", required_argument, NULL, OPT_WORKLOAD }, | ||
| 680 | + { "workload-mem", required_argument, NULL, OPT_WORKLOAD_MEM }, | ||
| 681 | + { "bias", no_argument, NULL, OPT_BIAS }, | ||
| 682 | + { "quiet", no_argument, NULL, OPT_QUIET }, | ||
| 683 | + { "single-preheat", no_argument, NULL, OPT_SINGLE_PREHEAT }, | ||
| 684 | + { "zero-omit", no_argument, NULL, OPT_ZERO_OMIT }, | ||
| 685 | + { "version", no_argument, NULL, OPT_VERSION }, | ||
| 686 | + { NULL, 0, NULL, 0 }, | ||
| 687 | + }; | ||
| 688 | + int i, c = getopt_long(argc, argv, "b:Bc:C:D:f:hm:qsw:W:T:vz", | ||
| 689 | + options, &option_index); | ||
| 690 | + long ncores; | ||
| 691 | + | ||
| 692 | + if (c == -1) | ||
| 693 | + break; | ||
| 694 | + | ||
| 695 | + switch (c) { | ||
| 696 | + case OPT_BUCKETSIZE: | ||
| 697 | + case 'b': | ||
| 698 | + g.bucket_size_param = true; | ||
| 699 | + g.bucket_size = strtol(optarg, NULL, 10); | ||
| 700 | + if (g.bucket_size > 1024 || g.bucket_size <= 4) { | ||
| 701 | + printf("Illegal bucket size: %s (should be: 4-1024)\n", | ||
| 702 | + optarg); | ||
| 703 | + exit(1); | ||
| 704 | + } | ||
| 705 | + break; | ||
| 706 | + case OPT_BUCKETWIDTH: | ||
| 707 | + case 'W': | ||
| 708 | + g.bucket_width = strtol(optarg, NULL, 10); | ||
| 709 | + if (g.bucket_width <= 0) { | ||
| 710 | + printf("Illegal bucket width: %s\n", optarg); | ||
| 711 | + exit(1); | ||
| 712 | + } | ||
| 713 | + if (g.bucket_width % 1000) { | ||
| 714 | + g.unit_per_us = 1000; | ||
| 715 | + g.precision = 3; | ||
| 716 | + } else { | ||
| 717 | + g.bucket_width /= 1000; | ||
| 718 | + } | ||
| 719 | + break; | ||
| 720 | + case OPT_BIAS: | ||
| 721 | + case 'B': | ||
| 722 | + g.enable_bias = 1; | ||
| 723 | + break; | ||
| 724 | + case OPT_CPU_LIST: | ||
| 725 | + case 'c': | ||
| 726 | + g.cpu_list = strdup(optarg); | ||
| 727 | + break; | ||
| 728 | + case OPT_CPU_MAIN_THREAD: | ||
| 729 | + case 'C': | ||
| 730 | + ncores = sysconf(_SC_NPROCESSORS_CONF); | ||
| 731 | + g.cpu_main_thread = strtol(optarg, NULL, 10); | ||
| 732 | + if (g.cpu_main_thread < 0 || g.cpu_main_thread > ncores) { | ||
| 733 | + printf("Illegal core for main thread: %s (should be: 0-%ld)\n", | ||
| 734 | + optarg, ncores); | ||
| 735 | + exit(1); | ||
| 736 | + } | ||
| 737 | + break; | ||
| 738 | + case OPT_DURATION: | ||
| 739 | + case 'D': | ||
| 740 | + g.runtime = parse_time_string(optarg); | ||
| 741 | + if (!g.runtime) { | ||
| 742 | + printf("Illegal runtime: %s\n", optarg); | ||
| 743 | + exit(1); | ||
| 744 | + } | ||
| 745 | + break; | ||
| 746 | + case OPT_RT_PRIO: | ||
| 747 | + case 'f': | ||
| 748 | + g.rtprio = strtol(optarg, NULL, 10); | ||
| 749 | + if (g.rtprio < 1 || g.rtprio > 99) { | ||
| 750 | + printf("Illegal RT priority: %s (should be: 1-99)\n", optarg); | ||
| 751 | + exit(1); | ||
| 752 | + } | ||
| 753 | + break; | ||
| 754 | + case OPT_JSON: | ||
| 755 | + strncpy(g.jsonfile, optarg, strnlen(optarg, MAX_PATH-1)); | ||
| 756 | + break; | ||
| 757 | + case OPT_TRACE_TH: | ||
| 758 | + case 'T': | ||
| 759 | + g.trace_threshold = strtol(optarg, NULL, 10); | ||
| 760 | + if (g.trace_threshold <= 0) { | ||
| 761 | + printf("Parameter --trace-threshold needs to be positive\n"); | ||
| 762 | + exit(1); | ||
| 763 | + } | ||
| 764 | + enable_trace_mark(); | ||
| 765 | + break; | ||
| 766 | + case OPT_WORKLOAD: | ||
| 767 | + case 'w': | ||
| 768 | + if (workload_select(optarg)) { | ||
| 769 | + printf("Unknown workload '%s'. Please choose from: ", optarg); | ||
| 770 | + for (i = 0; i < WORKLOAD_NUM; i++) { | ||
| 771 | + printf("'%s'", workload_list[i].w_name); | ||
| 772 | + if (i != WORKLOAD_NUM - 1) | ||
| 773 | + printf(", "); | ||
| 774 | + } | ||
| 775 | + printf("\n\n"); | ||
| 776 | + exit(1); | ||
| 777 | + } | ||
| 778 | + break; | ||
| 779 | + case OPT_WORKLOAD_MEM: | ||
| 780 | + case 'm': | ||
| 781 | + if (parse_mem_string(optarg, &g.workload_mem_size)) { | ||
| 782 | + printf("Unknown workload memory size '%s'.\n\n", optarg); | ||
| 783 | + exit(1); | ||
| 784 | + } | ||
| 785 | + break; | ||
| 786 | + case OPT_QUIET: | ||
| 787 | + case 'q': | ||
| 788 | + g.quiet = 1; | ||
| 789 | + break; | ||
| 790 | + case OPT_SINGLE_PREHEAT: | ||
| 791 | + case 's': | ||
| 792 | + /* | ||
| 793 | + * Only use one core for pre-heat. Then if --bias is used, the | ||
| 794 | + * bias will be exactly the min value of the pre-heat core. | ||
| 795 | + */ | ||
| 796 | + g.single_preheat_thread = true; | ||
| 797 | + break; | ||
| 798 | + case OPT_VERSION: | ||
| 799 | + case 'v': | ||
| 800 | + /* | ||
| 801 | + * We always print the version before parsing options | ||
| 802 | + * so just exit | ||
| 803 | + */ | ||
| 804 | + exit(0); | ||
| 805 | + break; | ||
| 806 | + case OPT_ZERO_OMIT: | ||
| 807 | + case 'z': | ||
| 808 | + g.output_omit_zero_buckets = 1; | ||
| 809 | + break; | ||
| 810 | + case OPT_HELP: | ||
| 811 | + case 'h': | ||
| 812 | + usage(0); | ||
| 813 | + break; | ||
| 814 | + default: | ||
| 815 | + usage(1); | ||
| 816 | + break; | ||
| 817 | + } | ||
| 818 | + } | ||
| 819 | + | ||
| 820 | + if (!g.bucket_size_param && g.precision == 3 && g.bucket_width < 1000) | ||
| 821 | + g.bucket_size = BUCKET_SIZE * 1000 / g.bucket_width; | ||
| 822 | +} | ||
| 823 | + | ||
| 824 | +void dump_globals(void) | ||
| 825 | +{ | ||
| 826 | + printf("Total runtime: \t\t%d seconds\n", g.runtime); | ||
| 827 | + printf("Thread priority: \t"); | ||
| 828 | + if (g.rtprio) | ||
| 829 | + printf("SCHED_FIFO:%d\n", g.rtprio); | ||
| 830 | + else | ||
| 831 | + printf("default\n"); | ||
| 832 | + printf("CPU list: \t\t%s\n", g.cpu_list ?: "(all cores)"); | ||
| 833 | + printf("CPU for main thread: \t%d\n", g.cpu_main_thread); | ||
| 834 | + printf("Workload: \t\t%s\n", g.workload->w_name); | ||
| 835 | + printf("Workload mem: \t\t%"PRIu64" (KiB)\n", | ||
| 836 | + (g.workload->w_flags & WORK_NEED_MEM) ? | ||
| 837 | + (g.workload_mem_size / 1024) : 0); | ||
| 838 | + printf("Preheat cores: \t\t%d\n", g.single_preheat_thread ? | ||
| 839 | + 1 : g.n_threads_total); | ||
| 840 | + printf("\n"); | ||
| 841 | +} | ||
| 842 | + | ||
| 843 | +static void record_bias(struct thread *t) | ||
| 844 | +{ | ||
| 845 | + unsigned long int i; | ||
| 846 | + uint64_t bias = (uint64_t)-1; | ||
| 847 | + | ||
| 848 | + if (!g.enable_bias) | ||
| 849 | + return; | ||
| 850 | + | ||
| 851 | + /* Record the min value of minlat on all the threads */ | ||
| 852 | + for (i = 0; i < g.n_threads; ++i) { | ||
| 853 | + if (t[i].minlat < bias) | ||
| 854 | + bias = t[i].minlat; | ||
| 855 | + } | ||
| 856 | + g.bias = bias; | ||
| 857 | + printf("Global bias set to %.*f (us)\n", g.precision, | ||
| 858 | + (double)bias / g.unit_per_us); | ||
| 859 | +} | ||
| 860 | + | ||
| 861 | +int main(int argc, char *argv[]) | ||
| 862 | +{ | ||
| 863 | + struct thread *threads; | ||
| 864 | + int n_cores; | ||
| 865 | + unsigned long int i; | ||
| 866 | + struct bitmask *cpu_set = NULL; | ||
| 867 | + | ||
| 868 | + | ||
| 869 | + printf("This architecture is not yet supported. " | ||
| 870 | + "Please implement frc() function first for %s.\n", argv[0]); | ||
| 871 | + return 0; | ||
| 872 | + | ||
| 873 | + if (numa_available() == -1) { | ||
| 874 | + printf("ERROR: Could not initialize libnuma\n"); | ||
| 875 | + exit(1); | ||
| 876 | + } | ||
| 877 | + | ||
| 878 | + rt_init(argc, argv); | ||
| 879 | + | ||
| 880 | + g.app_name = argv[0]; | ||
| 881 | + g.rtprio = 0; | ||
| 882 | + g.bucket_size = BUCKET_SIZE; | ||
| 883 | + g.bucket_width = 1; | ||
| 884 | + g.unit_per_us = 1; | ||
| 885 | + g.runtime = 1; | ||
| 886 | + g.workload = &workload_list[WORKLOAD_DEFAULT]; | ||
| 887 | + g.workload_mem_size = WORKLOAD_MEM_SIZE; | ||
| 888 | + /* Run the main thread on cpu0 by default */ | ||
| 889 | + g.cpu_main_thread = 0; | ||
| 890 | + printf("oslat V %1.2f\n", VERSION); | ||
| 891 | + parse_options(argc, argv); | ||
| 892 | + | ||
| 893 | + TEST(mlockall(MCL_CURRENT | MCL_FUTURE) == 0); | ||
| 894 | + | ||
| 895 | + if (!g.cpu_list) | ||
| 896 | + g.cpu_list = strdup("all"); | ||
| 897 | + | ||
| 898 | + cpu_set = numa_parse_cpustring_all(g.cpu_list); | ||
| 899 | + if (!cpu_set) | ||
| 900 | + fatal("oslat: numa_parse_cpustring_all failed.\n"); | ||
| 901 | + n_cores = numa_bitmask_weight(cpu_set); | ||
| 902 | + | ||
| 903 | + TEST(threads = calloc(1, n_cores * sizeof(threads[0]))); | ||
| 904 | + for (i = 0; n_cores && i < cpu_set->size; i++) { | ||
| 905 | + if (numa_bitmask_isbitset(cpu_set, i) && move_to_core(i) == 0) { | ||
| 906 | + threads[g.n_threads_total++].core_i = i; | ||
| 907 | + n_cores--; | ||
| 908 | + } | ||
| 909 | + } | ||
| 910 | + | ||
| 911 | + if (numa_bitmask_isbitset(cpu_set, 0) && g.rtprio) | ||
| 912 | + printf("WARNING: Running SCHED_FIFO workload on CPU 0 may hang the thread\n"); | ||
| 913 | + | ||
| 914 | + numa_bitmask_free(cpu_set); | ||
| 915 | + | ||
| 916 | + TEST(move_to_core(g.cpu_main_thread) == 0); | ||
| 917 | + | ||
| 918 | + signal(SIGALRM, handle_alarm); | ||
| 919 | + signal(SIGINT, handle_alarm); | ||
| 920 | + signal(SIGTERM, handle_alarm); | ||
| 921 | + | ||
| 922 | + if (!g.quiet) | ||
| 923 | + dump_globals(); | ||
| 924 | + | ||
| 925 | + if (!g.quiet) | ||
| 926 | + printf("Pre-heat for 1 seconds...\n"); | ||
| 927 | + if (g.single_preheat_thread) | ||
| 928 | + g.n_threads = 1; | ||
| 929 | + else | ||
| 930 | + g.n_threads = g.n_threads_total; | ||
| 931 | + run_expt(threads, 1, true); | ||
| 932 | + record_bias(threads); | ||
| 933 | + | ||
| 934 | + if (!g.quiet) | ||
| 935 | + printf("Test starts...\n"); | ||
| 936 | + /* Reset n_threads to always run on all the cores */ | ||
| 937 | + g.n_threads = g.n_threads_total; | ||
| 938 | + run_expt(threads, g.runtime, false); | ||
| 939 | + | ||
| 940 | + if (!g.quiet) | ||
| 941 | + printf("Test completed.\n\n"); | ||
| 942 | + | ||
| 943 | + write_summary(threads); | ||
| 944 | + | ||
| 945 | + if (strlen(g.jsonfile) != 0) | ||
| 946 | + rt_write_json(g.jsonfile, 0, write_summary_json, threads); | ||
| 947 | + | ||
| 948 | + if (g.cpu_list) { | ||
| 949 | + free(g.cpu_list); | ||
| 950 | + g.cpu_list = NULL; | ||
| 951 | + } | ||
| 952 | + | ||
| 953 | + disable_trace_mark(); | ||
| 954 | + | ||
| 955 | + return 0; | ||
| 956 | +} | ||
| @@ -0,0 +1,339 @@ | |||
| 1 | + GNU GENERAL PUBLIC LICENSE | ||
| 2 | + Version 2, June 1991 | ||
| 3 | + | ||
| 4 | + Copyright (C) 1989, 1991 Free Software Foundation, Inc. | ||
| 5 | + 675 Mass Ave, Cambridge, MA 02139, 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 Library 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 | + <one line to give the program's name and a brief idea of what it does.> | ||
| 294 | + Copyright (C) 19yy <name of author> | ||
| 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 | ||
| 307 | + along with this program; if not, write to the Free Software | ||
| 308 | + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 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) 19yy 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 | + <signature of Ty Coon>, 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 Library General | ||
| 339 | +Public License instead of this License. | ||
| @@ -0,0 +1,80 @@ | |||
| 1 | +DESCRIPTION | ||
| 2 | + | ||
| 3 | +The pi_stress program is a stress test that is intended to exercise | ||
| 4 | +kernel and C library code paths for POSIX mutexes using the Priority | ||
| 5 | +Inheritance attribute (PTHREAD_PRIO_INHERIT). | ||
| 6 | + | ||
| 7 | +The program consists of an admin thread (main), a reporter thread and | ||
| 8 | +some number of groups of three threads called "inversion | ||
| 9 | +groups". These thread groups are called that because they cause a | ||
| 10 | +condition called Priority Inversion, where a high priority thread is | ||
| 11 | +blocked due to a low-priority thread holding a shared | ||
| 12 | +resource. Priority inversion with no contravening logic is a deadlock | ||
| 13 | +condition. | ||
| 14 | + | ||
| 15 | +Each inversion group consists of three threads: | ||
| 16 | + | ||
| 17 | +1. A high-priority thread | ||
| 18 | +2. A medium-priority thread | ||
| 19 | +3. A low-priority thread | ||
| 20 | + | ||
| 21 | +The threads run through a state machine designed to guarantee that a | ||
| 22 | +low-priority thread holds a mutex while a medium priority thread runs | ||
| 23 | +(keeping the low-priority thread from releasing the mutex). The | ||
| 24 | +high-priority thread attempts to acquire the mutex and is blocked | ||
| 25 | +because of the low-priority thread holding it. If priority inheritence | ||
| 26 | +is working, the low-priority thread will receive a priority boost | ||
| 27 | +(will inherit the high-priority thread's priority) and will then run | ||
| 28 | +and release the mutex, averting a deadlock. | ||
| 29 | + | ||
| 30 | +On a multi-processor system, the admin and reporter threads are run | ||
| 31 | +one one processor while the inversion groups are run on another | ||
| 32 | +processor. | ||
| 33 | + | ||
| 34 | + | ||
| 35 | +STEPS TO RUN | ||
| 36 | + | ||
| 37 | +Install the pi_tests rpm. If you are running Fedora Core, the simplest | ||
| 38 | +way do to that is to fetchthe rt-userspace.repo file from: | ||
| 39 | + | ||
| 40 | +http://people.redhat.com/~williams/rt-userspace.repo | ||
| 41 | + | ||
| 42 | +and install that file in /etc/yum.repos.d, then run: | ||
| 43 | + | ||
| 44 | +$ yum install pi_stress | ||
| 45 | + | ||
| 46 | +If you are not running Fedora, you can fetch the SRPM and build it, or | ||
| 47 | +fetch the tarball from: | ||
| 48 | + | ||
| 49 | +http://people.redhat.com/~williams/tests | ||
| 50 | + | ||
| 51 | +Once you've installed it, to see the options provided by pi_stress you | ||
| 52 | +can run it with the --help option to get a usage message or read the | ||
| 53 | +man page for more detail. | ||
| 54 | + | ||
| 55 | +Since the stress test runs as a number of realtime threads, it's | ||
| 56 | +usually a good idea to run the test while logged into the test machine | ||
| 57 | +from another system. Its always a good idea to have a window up | ||
| 58 | +that's either a serial console to the test machine or something that's | ||
| 59 | +continually printing the contents of the test system's syslog | ||
| 60 | +(e.g. 'tail -f /var/log/messages'). | ||
| 61 | + | ||
| 62 | +Login to the test machine and run pi_stress as root: | ||
| 63 | + | ||
| 64 | +$ sudo /usr/sbin/pi_stress | ||
| 65 | + | ||
| 66 | +This will run the test with the defaults of: | ||
| 67 | + | ||
| 68 | + - no stop time | ||
| 69 | + - 10 inversion groups | ||
| 70 | + | ||
| 71 | +A banner will be printed indicating what defaults are in use and then | ||
| 72 | +a display will be updated showing the current number of inversions | ||
| 73 | +that have been performed. To stop the test, press any key and a | ||
| 74 | +summary will be printed to show how many inversions were performed and | ||
| 75 | +how long the test ran. | ||
| 76 | + | ||
| 77 | +To run the test for a specifed time, use the --duraton=<seconds> | ||
| 78 | +option. | ||
| 79 | + | ||
| 80 | + | ||
| @@ -0,0 +1,104 @@ | |||
| 1 | +.\" Process this file with | ||
| 2 | +.\" groff -man -Tascii pi_stress.8 | ||
| 3 | +.\" | ||
| 4 | +.\"{{{}}} | ||
| 5 | +.\"{{{ Title | ||
| 6 | +.TH pi_stress 8 "Nov 27, 2006" "" "Linux System Administrator's Manual" | ||
| 7 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 8 | +.\"}}} | ||
| 9 | +.\"{{{ Name | ||
| 10 | +.SH NAME | ||
| 11 | +pi_stress \- a stress test for POSIX Priority Inheritance mutexes | ||
| 12 | +.\"}}} | ||
| 13 | +.\"{{{ Synopsis | ||
| 14 | +.\" Usage: pi_stress [-d] [-D TIME] [-g N] [-h] [-i N ] [--json FILENAME ] [-m] [-p] [-q] [-r] [-s OPTS] [-u] [-v] [-V] | ||
| 15 | +.SH SYNOPSIS | ||
| 16 | +.B pi_stress | ||
| 17 | +.RB [ \-d|\-\-debug] | ||
| 18 | +.RB [ \-D|\-\-duration | ||
| 19 | +.iR TIME ] | ||
| 20 | +.RB [ \-g|\-\-groups | ||
| 21 | +.IR N ] | ||
| 22 | +.RB [ \-i|\-\-inversions | ||
| 23 | +.IR INV ] | ||
| 24 | +.RB [ \-\-json | ||
| 25 | +.IR FILENAME ] | ||
| 26 | +.RB [ \-m|\-\-mlockall ] | ||
| 27 | +.RB [ \-p|\-\-prompt ] | ||
| 28 | +.RB [ \-q|\-\-quiet ] | ||
| 29 | +.RB [ \-r|\-\-rr ] | ||
| 30 | +.RB [ \-s|\-\-sched | ||
| 31 | +.IR OPTS ] | ||
| 32 | +.RB [ \-u|\-\-uniprocessor ] | ||
| 33 | +.RB [ \-v|\-\-verbose ] | ||
| 34 | +.RB [ \-V|\-\-version ] | ||
| 35 | +.br | ||
| 36 | +.SH DESCRIPTION | ||
| 37 | +.B pi_stress | ||
| 38 | +is a program used to stress the | ||
| 39 | +.IR priority-inheritance | ||
| 40 | +code paths for POSIX mutexes, in both the Linux kernel and the C | ||
| 41 | +library. It runs as a realtime-priority task and launches | ||
| 42 | +.IR "inversion machine" | ||
| 43 | +thread groups. Each inversion group causes a | ||
| 44 | +.IR "priority inversion" | ||
| 45 | +condition that will deadlock if | ||
| 46 | +.IR "priority inheritance" | ||
| 47 | +doesn't work. | ||
| 48 | + | ||
| 49 | +.SH OPTIONS | ||
| 50 | +.IP "\-d|\-\-debug" | ||
| 51 | +Run in debug mode; lots of extra prints | ||
| 52 | +.IP "\-D TIME, \-\-duration=TIME" | ||
| 53 | +Specify a length for the test run. | ||
| 54 | +.br | ||
| 55 | +Append 'm', 'h', or 'd' to specify minutes, hours or days. | ||
| 56 | +.IP "\-g N|\-\-groups=N" | ||
| 57 | +The number of inversion groups to run. Defaults to 10. | ||
| 58 | +.IP "\-h|\-\-help" | ||
| 59 | +Display a short help message and options. | ||
| 60 | +.IP "\-i N|\-\-inversions=N" | ||
| 61 | +.I N | ||
| 62 | +number of inversion conditions. This is the total number of inversions | ||
| 63 | +for all inversion groups. Default is \-1 for infinite. | ||
| 64 | +.IP "\-\-json=FILENAME" | ||
| 65 | +Write final results into | ||
| 66 | +.I FILENAME | ||
| 67 | +, JSON formatted. | ||
| 68 | +.IP "\-m|\-\-mlockall" | ||
| 69 | +Call mlockall to lock current and future memory allocations and | ||
| 70 | +prevent being paged out | ||
| 71 | +.IP "\-p|\-\-prompt" | ||
| 72 | +Prompt before actually starting the stress test | ||
| 73 | +.IP "\-q|\-\-quiet" | ||
| 74 | +Suppress running output | ||
| 75 | +.IP "\-r|\-\-rr" | ||
| 76 | +Run inversion group threads as SCHED_RR (round-robin). The default is | ||
| 77 | +to run the inversion threads as SCHED_FIFO. | ||
| 78 | +.IP "\-s OPTS|\-\-sched OPTS" | ||
| 79 | +scheduling options per thread type: | ||
| 80 | +.br | ||
| 81 | +id=[high|med|low], | ||
| 82 | +.br | ||
| 83 | +policy=[fifo,rr],priority=N, | ||
| 84 | +.br | ||
| 85 | +policy=deadline,runtime=N,deadline=N,period=N | ||
| 86 | +.IP "\-u|\-\-uniprocessor" | ||
| 87 | +Run all threads on one processor. The default is to run all inversion | ||
| 88 | +group threads on one processor and the admin threads (reporting | ||
| 89 | +thread, keyboard reader, etc.) on a different processor. | ||
| 90 | +.IP "\-v|\-\-verbose" | ||
| 91 | +Run with verbose messages | ||
| 92 | +.IP "\-V|\-\-version" | ||
| 93 | +Print version number | ||
| 94 | +.SH CAVEATS | ||
| 95 | +The pi_stress test threads run as SCHED_FIFO or SCHED_RR threads, | ||
| 96 | +which means that they can starve critical system threads. It is | ||
| 97 | +advisable to change the scheduling policy of critical system threads | ||
| 98 | +to be SCHED_FIFO prior to running pi_stress and use a priority of 10 | ||
| 99 | +or higher, to prevent those threads from being starved by the stress | ||
| 100 | +test. | ||
| 101 | +.SH BUGS | ||
| 102 | +No documented bugs. | ||
| 103 | +.SH AUTHOR | ||
| 104 | +Clark Williams <williams@redhat.com> | ||
| @@ -0,0 +1,1378 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +/* | ||
| 3 | + * pi_stress - Priority Inheritance stress test | ||
| 4 | + * Copyright (C) 2006, 2007 Clark Williams <williams@redhat.com> | ||
| 5 | + */ | ||
| 6 | + | ||
| 7 | +/* | ||
| 8 | + * This program stress tests pthreads priority inheritance mutexes | ||
| 9 | + | ||
| 10 | + * The logic is built upon the state machine that performs the "classic_pi" | ||
| 11 | + * deadlock scenario. A state machine or "inversion group" is a group of three | ||
| 12 | + * threads as described below. | ||
| 13 | + * | ||
| 14 | + * The basic premise here is to set up a deadlock scenario and confirm that PI | ||
| 15 | + * mutexes resolve the situation. Three worker threads will be created from the | ||
| 16 | + * main thread: low, medium and high priority threads that use SCHED_FIFO as | ||
| 17 | + * their scheduling policy. The low priority thread claims a mutex and then | ||
| 18 | + * starts "working". The medium priority thread starts and preempts the low | ||
| 19 | + * priority thread. Then the high priority thread runs and attempts to claim | ||
| 20 | + * the mutex owned by the low priority thread. Without priority inheritance, | ||
| 21 | + * this will deadlock the program. With priority inheritance, the low priority | ||
| 22 | + * thread receives a priority boost, finishes it's "work" and releases the mutex | ||
| 23 | + * which allows the high priority thread to run and finish and then the medium | ||
| 24 | + * priority thread finishes. | ||
| 25 | + * | ||
| 26 | + * That's the theory, anyway... | ||
| 27 | + * | ||
| 28 | + * CW - 2006 | ||
| 29 | + */ | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +/* the length of the test */ | ||
| 34 | +/* default is infinite */ | ||
| 35 | +int duration = -1; | ||
| 36 | + | ||
| 37 | +/* times for starting and finishing the stress test */ | ||
| 38 | +time_t start, finish; | ||
| 39 | + | ||
| 40 | +/* the number of groups to create */ | ||
| 41 | +int ngroups; | ||
| 42 | + | ||
| 43 | +/* the number of times a group causes a priority inversion situation */ | ||
| 44 | +/* default to infinite */ | ||
| 45 | +int inversions = -1; | ||
| 46 | + | ||
| 47 | +/* turn on lots of prints */ | ||
| 48 | +int verbose; | ||
| 49 | + | ||
| 50 | +/* turn on pi_debugging prints */ | ||
| 51 | +int debugging; | ||
| 52 | + | ||
| 53 | +/* turn off all prints, default = 0 (off) */ | ||
| 54 | +int quiet; | ||
| 55 | + | ||
| 56 | +/* filename for JSON output */ | ||
| 57 | +char jsonfile[MAX_PATH]; | ||
| 58 | + | ||
| 59 | +/* prompt to start test */ | ||
| 60 | +int prompt; | ||
| 61 | + | ||
| 62 | +/* report interval */ | ||
| 63 | +unsigned long report_interval = (unsigned long)SEC_TO_USEC(0.75); | ||
| 64 | + | ||
| 65 | +int shutdown; /* global indicating we should shut down */ | ||
| 66 | +pthread_mutex_t shutdown_mtx; /* associated mutex */ | ||
| 67 | + | ||
| 68 | +/* indicate if errors have occurred */ | ||
| 69 | +int have_errors; | ||
| 70 | + | ||
| 71 | +/* indicated that keyboard interrupt has happened */ | ||
| 72 | +int interrupted; | ||
| 73 | + | ||
| 74 | +/* force running on one cpu */ | ||
| 75 | +int uniprocessor; | ||
| 76 | + | ||
| 77 | +/* lock all memory */ | ||
| 78 | +int lockall; | ||
| 79 | + | ||
| 80 | +pthread_barrier_t all_threads_ready; | ||
| 81 | +pthread_barrier_t all_threads_done; | ||
| 82 | + | ||
| 83 | +cpu_set_t test_cpu_mask, admin_cpu_mask; | ||
| 84 | + | ||
| 85 | +int policy = SCHED_FIFO; | ||
| 86 | + | ||
| 87 | +/* scheduling attributes per thread */ | ||
| 88 | +struct sched_attr low_sa; | ||
| 89 | +struct sched_attr med_sa; | ||
| 90 | +struct sched_attr high_sa; | ||
| 91 | +struct sched_attr admin_sa; | ||
| 92 | + | ||
| 93 | +unsigned int sa_initialized; | ||
| 94 | + | ||
| 95 | +struct group_parameters *groups; | ||
| 96 | + | ||
| 97 | +/* number of online processors */ | ||
| 98 | +long num_processors; | ||
| 99 | + | ||
| 100 | +int main(int argc, char **argv) | ||
| 101 | +{ | ||
| 102 | + int status; | ||
| 103 | + struct sched_param thread_param; | ||
| 104 | + int i; | ||
| 105 | + int retval = FAILURE; | ||
| 106 | + int core; | ||
| 107 | + int nthreads; | ||
| 108 | + | ||
| 109 | + /* Make sure we see all message, even those on stdout. */ | ||
| 110 | + setvbuf(stdout, NULL, _IONBF, 0); | ||
| 111 | + | ||
| 112 | + /* get the number of processors */ | ||
| 113 | + num_processors = sysconf(_SC_NPROCESSORS_ONLN); | ||
| 114 | + | ||
| 115 | + /* calculate the number of inversion groups to run */ | ||
| 116 | + ngroups = num_processors == 1 ? 1 : num_processors - 1; | ||
| 117 | + | ||
| 118 | + | ||
| 119 | + /* process command line arguments */ | ||
| 120 | + rt_init(argc, argv); | ||
| 121 | + process_command_line(argc, argv); | ||
| 122 | + | ||
| 123 | + /* set default sched attributes */ | ||
| 124 | + setup_sched_config(policy); | ||
| 125 | + | ||
| 126 | + /* lock memory */ | ||
| 127 | + if (lockall) | ||
| 128 | + if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) { | ||
| 129 | + pi_error("mlockall failed\n"); | ||
| 130 | + return FAILURE; | ||
| 131 | + } | ||
| 132 | + /* boost main's priority (so we keep running) :) */ | ||
| 133 | + thread_param.sched_priority = admin_sa.sched_priority; | ||
| 134 | + status = pthread_setschedparam(pthread_self(), admin_sa.sched_policy, | ||
| 135 | + &thread_param); | ||
| 136 | + if (status) { | ||
| 137 | + pi_error("%s: boosting to max priority: 0x%x\n", __func__, | ||
| 138 | + status); | ||
| 139 | + return FAILURE; | ||
| 140 | + } | ||
| 141 | + /* block unwanted signals */ | ||
| 142 | + block_signals(); | ||
| 143 | + | ||
| 144 | + /* allocate our groups array */ | ||
| 145 | + groups = calloc(ngroups, sizeof(struct group_parameters)); | ||
| 146 | + if (groups == NULL) { | ||
| 147 | + pi_error("%s: failed to allocate %d groups\n", __func__, | ||
| 148 | + ngroups); | ||
| 149 | + return FAILURE; | ||
| 150 | + } | ||
| 151 | + /* set up CPU affinity masks */ | ||
| 152 | + if (set_cpu_affinity(&test_cpu_mask, &admin_cpu_mask)) | ||
| 153 | + return FAILURE; | ||
| 154 | + | ||
| 155 | + nthreads = ngroups * NUM_TEST_THREADS + NUM_ADMIN_THREADS; | ||
| 156 | + | ||
| 157 | + /* set up our ready barrier */ | ||
| 158 | + if (barrier_init(&all_threads_ready, NULL, nthreads, | ||
| 159 | + "all_threads_ready")) | ||
| 160 | + return FAILURE; | ||
| 161 | + | ||
| 162 | + /* set up our done barrier */ | ||
| 163 | + if (barrier_init(&all_threads_done, NULL, nthreads, "all_threads_done")) | ||
| 164 | + return FAILURE; | ||
| 165 | + | ||
| 166 | + /* create the groups */ | ||
| 167 | + pi_info("Creating %d test groups\n", ngroups); | ||
| 168 | + for (core = 0; core < num_processors; core++) | ||
| 169 | + if (CPU_ISSET(core, &test_cpu_mask)) | ||
| 170 | + break; | ||
| 171 | + for (i = 0; i < ngroups; i++) { | ||
| 172 | + groups[i].id = i; | ||
| 173 | + groups[i].cpu = core++; | ||
| 174 | + if (core >= num_processors) | ||
| 175 | + core = 0; | ||
| 176 | + if (create_group(&groups[i]) != SUCCESS) | ||
| 177 | + return FAILURE; | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + /* prompt if requested */ | ||
| 181 | + if (prompt) { | ||
| 182 | + printf("Press return to start test: "); | ||
| 183 | + getchar(); | ||
| 184 | + } | ||
| 185 | + /* report */ | ||
| 186 | + banner(); | ||
| 187 | + start = time(NULL); | ||
| 188 | + | ||
| 189 | + /* turn loose the threads */ | ||
| 190 | + pi_info("Releasing all threads\n"); | ||
| 191 | + status = pthread_barrier_wait(&all_threads_ready); | ||
| 192 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 193 | + pi_error("%s: pthread_barrier_wait(all_threads_ready): 0x%x\n", | ||
| 194 | + __func__, status); | ||
| 195 | + set_shutdown_flag(); | ||
| 196 | + return FAILURE; | ||
| 197 | + } | ||
| 198 | + | ||
| 199 | + reporter(NULL); | ||
| 200 | + | ||
| 201 | + if (!quiet) { | ||
| 202 | + fputs(DOWN_ONE, stdout); | ||
| 203 | + printf("Stopping test\n"); | ||
| 204 | + } | ||
| 205 | + set_shutdown_flag(); | ||
| 206 | + | ||
| 207 | + /* wait for all threads to notice the shutdown flag */ | ||
| 208 | + if (have_errors == 0 && interrupted == 0) { | ||
| 209 | + pi_info("waiting for all threads to complete\n"); | ||
| 210 | + status = pthread_barrier_wait(&all_threads_done); | ||
| 211 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 212 | + pi_error("%s: pthread_barrier_wait(all_threads_ready): 0x%x\n", __func__, status); | ||
| 213 | + return FAILURE; | ||
| 214 | + } | ||
| 215 | + pi_info("All threads terminated!\n"); | ||
| 216 | + retval = SUCCESS; | ||
| 217 | + } else | ||
| 218 | + kill(0, SIGTERM); | ||
| 219 | + finish = time(NULL); | ||
| 220 | + summary(); | ||
| 221 | + | ||
| 222 | + if (strlen(jsonfile) != 0) | ||
| 223 | + rt_write_json(jsonfile, retval, write_stats, NULL); | ||
| 224 | + | ||
| 225 | + if (lockall) | ||
| 226 | + munlockall(); | ||
| 227 | + exit(retval); | ||
| 228 | +} | ||
| 229 | + | ||
| 230 | +int | ||
| 231 | +setup_thread_attr(pthread_attr_t *attr, struct sched_attr *sa, | ||
| 232 | + cpu_set_t *mask) | ||
| 233 | +{ | ||
| 234 | + int status; | ||
| 235 | + struct sched_param thread_param; | ||
| 236 | + | ||
| 237 | + status = pthread_attr_init(attr); | ||
| 238 | + if (status) { | ||
| 239 | + pi_error | ||
| 240 | + ("%s: initializing thread attribute: 0x%x\n", __func__, | ||
| 241 | + status); | ||
| 242 | + return FAILURE; | ||
| 243 | + } | ||
| 244 | + status = pthread_attr_setaffinity_np(attr, sizeof(cpu_set_t), mask); | ||
| 245 | + if (status) { | ||
| 246 | + pi_error("%s: setting affinity attribute: 0x%x\n", __func__, | ||
| 247 | + status); | ||
| 248 | + return FAILURE; | ||
| 249 | + } | ||
| 250 | + | ||
| 251 | + /* | ||
| 252 | + * The pthread API does not yet support SCHED_DEADLINE, defer the | ||
| 253 | + * thread configuration to setup_thread() | ||
| 254 | + */ | ||
| 255 | + if (sa->sched_policy == SCHED_DEADLINE) | ||
| 256 | + return SUCCESS; | ||
| 257 | + | ||
| 258 | + status = pthread_attr_setschedpolicy(attr, sa->sched_policy); | ||
| 259 | + if (status) { | ||
| 260 | + pi_error | ||
| 261 | + ("%s: setting attribute policy to %s: 0x%x\n", __func__, | ||
| 262 | + policy_to_string(sa->sched_policy), | ||
| 263 | + status); | ||
| 264 | + return FAILURE; | ||
| 265 | + } | ||
| 266 | + status = pthread_attr_setinheritsched(attr, PTHREAD_EXPLICIT_SCHED); | ||
| 267 | + if (status) { | ||
| 268 | + pi_error | ||
| 269 | + ("%s: setting explicit scheduling inheritance: 0x%x\n", | ||
| 270 | + __func__, status); | ||
| 271 | + return FAILURE; | ||
| 272 | + } | ||
| 273 | + thread_param.sched_priority = sa->sched_priority; | ||
| 274 | + status = pthread_attr_setschedparam(attr, &thread_param); | ||
| 275 | + if (status) { | ||
| 276 | + pi_error("%s: setting scheduler param: 0x%x\n", __func__, | ||
| 277 | + status); | ||
| 278 | + return FAILURE; | ||
| 279 | + } | ||
| 280 | + return SUCCESS; | ||
| 281 | +} | ||
| 282 | + | ||
| 283 | +int set_cpu_affinity(cpu_set_t *test_mask, cpu_set_t *admin_mask) | ||
| 284 | +{ | ||
| 285 | + int status, i, admin_proc; | ||
| 286 | + cpu_set_t current_mask; | ||
| 287 | + | ||
| 288 | + /* handle uniprocessor case */ | ||
| 289 | + if (num_processors == 1 || uniprocessor) { | ||
| 290 | + CPU_ZERO(admin_mask); | ||
| 291 | + CPU_ZERO(test_mask); | ||
| 292 | + CPU_SET(0, admin_mask); | ||
| 293 | + CPU_SET(0, test_mask); | ||
| 294 | + pi_info("admin and test threads running on one processor\n"); | ||
| 295 | + return SUCCESS; | ||
| 296 | + } | ||
| 297 | + /* | ||
| 298 | + * first set our main thread to run on the first | ||
| 299 | + * scheduleable processor we can find | ||
| 300 | + */ | ||
| 301 | + status = sched_getaffinity(0, sizeof(cpu_set_t), ¤t_mask); | ||
| 302 | + if (status) { | ||
| 303 | + pi_error("failed getting CPU affinity mask: 0x%x\n", status); | ||
| 304 | + return FAILURE; | ||
| 305 | + } | ||
| 306 | + for (i = 0; i < num_processors; i++) { | ||
| 307 | + if (CPU_ISSET(i, ¤t_mask)) | ||
| 308 | + break; | ||
| 309 | + } | ||
| 310 | + if (i >= num_processors) { | ||
| 311 | + pi_error("No schedulable CPU found for main!\n"); | ||
| 312 | + return FAILURE; | ||
| 313 | + } | ||
| 314 | + admin_proc = i; | ||
| 315 | + CPU_ZERO(admin_mask); | ||
| 316 | + CPU_SET(admin_proc, admin_mask); | ||
| 317 | + status = sched_setaffinity(0, sizeof(cpu_set_t), admin_mask); | ||
| 318 | + if (status) { | ||
| 319 | + pi_error("%s: setting CPU affinity mask: 0x%x\n", __func__, | ||
| 320 | + status); | ||
| 321 | + return FAILURE; | ||
| 322 | + } | ||
| 323 | + pi_info("Admin thread running on processor: %d\n", i); | ||
| 324 | + | ||
| 325 | + /* Set test affinity so that tests run on the non-admin processors */ | ||
| 326 | + CPU_ZERO(test_mask); | ||
| 327 | + for (i = admin_proc + 1; i < num_processors; i++) | ||
| 328 | + CPU_SET(i, test_mask); | ||
| 329 | + | ||
| 330 | + if (admin_proc + 1 == num_processors - 1) | ||
| 331 | + pi_info("Test threads running on processor: %ld\n", | ||
| 332 | + num_processors - 1); | ||
| 333 | + else | ||
| 334 | + pi_info("Test threads running on processors: %d-%d\n", | ||
| 335 | + admin_proc + 1, (int)num_processors - 1); | ||
| 336 | + | ||
| 337 | + return SUCCESS; | ||
| 338 | +} | ||
| 339 | + | ||
| 340 | +/* clear all watchdog counters */ | ||
| 341 | +void watchdog_clear(void) | ||
| 342 | +{ | ||
| 343 | + int i; | ||
| 344 | + | ||
| 345 | + for (i = 0; i < ngroups; i++) | ||
| 346 | + groups[i].watchdog = 0; | ||
| 347 | +} | ||
| 348 | + | ||
| 349 | +/* check for zero watchdog counters */ | ||
| 350 | +int watchdog_check(void) | ||
| 351 | +{ | ||
| 352 | + int i; | ||
| 353 | + int failures = 0; | ||
| 354 | + struct group_parameters *g; | ||
| 355 | + | ||
| 356 | + for (i = 0; i < ngroups; i++) { | ||
| 357 | + g = &groups[i]; | ||
| 358 | + if (g->watchdog == 0) { | ||
| 359 | + /* don't report deadlock if group is finished */ | ||
| 360 | + if (g->inversions == g->total) | ||
| 361 | + continue; | ||
| 362 | + if (++g->watchdog_hits >= WATCHDOG_LIMIT) { | ||
| 363 | + pi_error | ||
| 364 | + ("WATCHDOG triggered: group %d is deadlocked!\n", | ||
| 365 | + i); | ||
| 366 | + failures++; | ||
| 367 | + } | ||
| 368 | + } else | ||
| 369 | + g->watchdog_hits = 0; | ||
| 370 | + } | ||
| 371 | + return failures ? FAILURE : SUCCESS; | ||
| 372 | +} | ||
| 373 | + | ||
| 374 | +int pending_interrupt(void) | ||
| 375 | +{ | ||
| 376 | + sigset_t pending; | ||
| 377 | + | ||
| 378 | + if (sigpending(&pending) < 0) { | ||
| 379 | + pi_error("from sigpending: %s\n", strerror(errno)); | ||
| 380 | + return 0; | ||
| 381 | + } | ||
| 382 | + | ||
| 383 | + return interrupted = sigismember(&pending, SIGINT); | ||
| 384 | +} | ||
| 385 | + | ||
| 386 | +/* | ||
| 387 | + * this routine serves two purposes: | ||
| 388 | + * 1. report progress | ||
| 389 | + * 2. check for deadlocks | ||
| 390 | + */ | ||
| 391 | +void *reporter(void *arg __attribute__ ((unused))) | ||
| 392 | +{ | ||
| 393 | + int status; | ||
| 394 | + int end = 0; | ||
| 395 | + struct timespec ts; | ||
| 396 | + | ||
| 397 | + ts.tv_sec = 0; | ||
| 398 | + ts.tv_nsec = USEC_TO_NSEC(report_interval); | ||
| 399 | + | ||
| 400 | + tsnorm(&ts); | ||
| 401 | + | ||
| 402 | + if (duration >= 0) | ||
| 403 | + end = duration + time(NULL); | ||
| 404 | + | ||
| 405 | + /* sleep initially to let everything get up and running */ | ||
| 406 | + status = clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL); | ||
| 407 | + if (status) { | ||
| 408 | + pi_error("from clock_nanosleep: %s\n", strerror(status)); | ||
| 409 | + return NULL; | ||
| 410 | + } | ||
| 411 | + | ||
| 412 | + pi_debug("%s: starting report loop\n", __func__); | ||
| 413 | + pi_info("Press Control-C to stop test\nCurrent Inversions:\n"); | ||
| 414 | + | ||
| 415 | + for (;;) { | ||
| 416 | + pthread_mutex_lock(&shutdown_mtx); | ||
| 417 | + if (shutdown) { | ||
| 418 | + pthread_mutex_unlock(&shutdown_mtx); | ||
| 419 | + break; | ||
| 420 | + } | ||
| 421 | + pthread_mutex_unlock(&shutdown_mtx); | ||
| 422 | + | ||
| 423 | + /* wait for our reporting interval */ | ||
| 424 | + status = clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL); | ||
| 425 | + if (status) { | ||
| 426 | + pi_error("from clock_nanosleep: %s\n", strerror(status)); | ||
| 427 | + break; | ||
| 428 | + } | ||
| 429 | + | ||
| 430 | + /* check for signaled shutdown */ | ||
| 431 | + if (!quiet) { | ||
| 432 | + pthread_mutex_lock(&shutdown_mtx); | ||
| 433 | + if (shutdown == 0) { | ||
| 434 | + fputs(UP_ONE, stdout); | ||
| 435 | + printf("Current Inversions: %lu\n", | ||
| 436 | + total_inversions()); | ||
| 437 | + } | ||
| 438 | + pthread_mutex_unlock(&shutdown_mtx); | ||
| 439 | + } | ||
| 440 | + | ||
| 441 | + /* if we specified a duration, see if it has expired */ | ||
| 442 | + if (end && time(NULL) > end) { | ||
| 443 | + pi_info("duration reached (%d seconds)\n", duration); | ||
| 444 | + set_shutdown_flag(); | ||
| 445 | + continue; | ||
| 446 | + } | ||
| 447 | + /* check for a pending SIGINT */ | ||
| 448 | + if (pending_interrupt()) { | ||
| 449 | + pi_info("Keyboard Interrupt!\n"); | ||
| 450 | + break; | ||
| 451 | + } | ||
| 452 | + /* check watchdog stuff */ | ||
| 453 | + if ((watchdog_check())) { | ||
| 454 | + pi_error("%s stopping due to watchdog event\n", __func__); | ||
| 455 | + set_shutdown_flag(); | ||
| 456 | + break; | ||
| 457 | + } | ||
| 458 | + /* clear watchdog counters */ | ||
| 459 | + watchdog_clear(); | ||
| 460 | + | ||
| 461 | + } | ||
| 462 | + pi_debug("%s: finished\n", __func__); | ||
| 463 | + set_shutdown_flag(); | ||
| 464 | + return NULL; | ||
| 465 | +} | ||
| 466 | + | ||
| 467 | +int verify_cpu(int cpu) | ||
| 468 | +{ | ||
| 469 | + int status; | ||
| 470 | + int err; | ||
| 471 | + cpu_set_t mask; | ||
| 472 | + | ||
| 473 | + CPU_ZERO(&mask); | ||
| 474 | + | ||
| 475 | + status = sched_getaffinity(0, sizeof(cpu_set_t), &mask); | ||
| 476 | + if (status == -1) { | ||
| 477 | + err = errno; | ||
| 478 | + fprintf(stderr, "sched_getaffinity %s\n", strerror(err)); | ||
| 479 | + exit(1); | ||
| 480 | + } | ||
| 481 | + | ||
| 482 | + if (CPU_ISSET(cpu, &mask)) | ||
| 483 | + return SUCCESS; | ||
| 484 | + return FAILURE; | ||
| 485 | +} | ||
| 486 | + | ||
| 487 | +void *low_priority(void *arg) | ||
| 488 | +{ | ||
| 489 | + int status; | ||
| 490 | + int unbounded; | ||
| 491 | + unsigned long count = 0; | ||
| 492 | + struct group_parameters *p = (struct group_parameters *)arg; | ||
| 493 | + pthread_barrier_t *loop_barr = &p->loop_barr; | ||
| 494 | + pthread_mutex_t *loop_mtx = &p->loop_mtx; | ||
| 495 | + int *loop = &p->loop; | ||
| 496 | + | ||
| 497 | + allow_sigterm(); | ||
| 498 | + | ||
| 499 | + if (verify_cpu(p->cpu) != SUCCESS) { | ||
| 500 | + pi_error("low_priority[%d]: not bound to %ld\n", p->id, p->cpu); | ||
| 501 | + return NULL; | ||
| 502 | + } | ||
| 503 | + | ||
| 504 | + pi_debug("low_priority[%d]: entering ready state\n", p->id); | ||
| 505 | + /* wait for all threads to be ready */ | ||
| 506 | + status = pthread_barrier_wait(&all_threads_ready); | ||
| 507 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 508 | + pi_error | ||
| 509 | + ("low_priority[%d]: pthread_barrier_wait(all_threads_ready): %x", | ||
| 510 | + p->id, status); | ||
| 511 | + return NULL; | ||
| 512 | + } | ||
| 513 | + | ||
| 514 | + unbounded = (p->inversions < 0); | ||
| 515 | + | ||
| 516 | + pi_debug("low_priority[%d]: starting inversion loop\n", p->id); | ||
| 517 | + | ||
| 518 | + for (;;) { | ||
| 519 | + /* | ||
| 520 | + * We can't set the 'loop' boolean here, because some flags | ||
| 521 | + * may have already reached the loop_barr | ||
| 522 | + */ | ||
| 523 | + if (!unbounded && (p->total >= (unsigned int)p->inversions)) | ||
| 524 | + set_shutdown_flag(); | ||
| 525 | + | ||
| 526 | + /* Either all threads go through the loop_barr, or none do */ | ||
| 527 | + pthread_mutex_lock(loop_mtx); | ||
| 528 | + if (*loop == 0) { | ||
| 529 | + pthread_mutex_unlock(loop_mtx); | ||
| 530 | + break; | ||
| 531 | + } | ||
| 532 | + pthread_mutex_unlock(loop_mtx); | ||
| 533 | + | ||
| 534 | + status = pthread_barrier_wait(loop_barr); | ||
| 535 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 536 | + pi_error("%s[%d]: pthread_barrier_wait(loop): %x\n", | ||
| 537 | + __func__, p->id, status); | ||
| 538 | + return NULL; | ||
| 539 | + } | ||
| 540 | + | ||
| 541 | + /* Only one Thread needs to check the shutdown status */ | ||
| 542 | + if (status == PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 543 | + pthread_mutex_lock(&shutdown_mtx); | ||
| 544 | + if (shutdown) { | ||
| 545 | + pthread_mutex_lock(loop_mtx); | ||
| 546 | + *loop = 0; | ||
| 547 | + pthread_mutex_unlock(loop_mtx); | ||
| 548 | + } | ||
| 549 | + pthread_mutex_unlock(&shutdown_mtx); | ||
| 550 | + } | ||
| 551 | + | ||
| 552 | + /* initial state */ | ||
| 553 | + pi_debug("low_priority[%d]: entering start wait (%ld)\n", p->id, | ||
| 554 | + count++); | ||
| 555 | + status = pthread_barrier_wait(&p->start_barrier); | ||
| 556 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 557 | + pi_error | ||
| 558 | + ("low_priority[%d]: pthread_barrier_wait(start): %x\n", | ||
| 559 | + p->id, status); | ||
| 560 | + return NULL; | ||
| 561 | + } | ||
| 562 | + | ||
| 563 | + pi_debug("low_priority[%d]: claiming mutex\n", p->id); | ||
| 564 | + pthread_mutex_lock(&p->mutex); | ||
| 565 | + pi_debug("low_priority[%d]: mutex locked\n", p->id); | ||
| 566 | + | ||
| 567 | + pi_debug("low_priority[%d]: entering locked wait\n", p->id); | ||
| 568 | + status = pthread_barrier_wait(&p->locked_barrier); | ||
| 569 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 570 | + pi_error | ||
| 571 | + ("low_priority[%d]: pthread_barrier_wait(locked): %x\n", | ||
| 572 | + p->id, status); | ||
| 573 | + /* release the mutex */ | ||
| 574 | + pi_debug("low_priority[%d]: unlocking mutex\n", p->id); | ||
| 575 | + pthread_mutex_unlock(&p->mutex); | ||
| 576 | + return NULL; | ||
| 577 | + } | ||
| 578 | + | ||
| 579 | + /* wait for priority boost */ | ||
| 580 | + pi_debug("low_priority[%d]: entering elevated wait\n", p->id); | ||
| 581 | + status = pthread_barrier_wait(&p->elevate_barrier); | ||
| 582 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 583 | + pi_error | ||
| 584 | + ("low_priority[%d]: pthread_barrier_wait(elevate): %x\n", | ||
| 585 | + p->id, status); | ||
| 586 | + /* release the mutex */ | ||
| 587 | + pi_debug("low_priority[%d]: unlocking mutex\n", p->id); | ||
| 588 | + pthread_mutex_unlock(&p->mutex); | ||
| 589 | + return NULL; | ||
| 590 | + } | ||
| 591 | + | ||
| 592 | + /* release the mutex */ | ||
| 593 | + pi_debug("low_priority[%d]: unlocking mutex\n", p->id); | ||
| 594 | + pthread_mutex_unlock(&p->mutex); | ||
| 595 | + | ||
| 596 | + /* finish state */ | ||
| 597 | + pi_debug("low_priority[%d]: entering finish wait\n", p->id); | ||
| 598 | + status = pthread_barrier_wait(&p->finish_barrier); | ||
| 599 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 600 | + pi_error | ||
| 601 | + ("low_priority[%d]: pthread_barrier_wait(elevate): %x\n", | ||
| 602 | + p->id, status); | ||
| 603 | + return NULL; | ||
| 604 | + } | ||
| 605 | + } | ||
| 606 | + set_shutdown_flag(); | ||
| 607 | + pi_debug("low_priority[%d]: entering done barrier\n", p->id); | ||
| 608 | + /* wait for all threads to finish */ | ||
| 609 | + status = pthread_barrier_wait(&all_threads_done); | ||
| 610 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 611 | + pi_error | ||
| 612 | + ("low_priority[%d]: pthread_barrier_wait(all_threads_done): %x", | ||
| 613 | + p->id, status); | ||
| 614 | + return NULL; | ||
| 615 | + } | ||
| 616 | + pi_debug("low_priority[%d]: exiting\n", p->id); | ||
| 617 | + return NULL; | ||
| 618 | +} | ||
| 619 | + | ||
| 620 | +void *med_priority(void *arg) | ||
| 621 | +{ | ||
| 622 | + int status; | ||
| 623 | + int unbounded; | ||
| 624 | + unsigned long count = 0; | ||
| 625 | + struct group_parameters *p = (struct group_parameters *)arg; | ||
| 626 | + pthread_barrier_t *loop_barr = &p->loop_barr; | ||
| 627 | + pthread_mutex_t *loop_mtx = &p->loop_mtx; | ||
| 628 | + int *loop = &p->loop; | ||
| 629 | + | ||
| 630 | + allow_sigterm(); | ||
| 631 | + | ||
| 632 | + if (verify_cpu(p->cpu) != SUCCESS) { | ||
| 633 | + pi_error("med_priority[%d]: not bound to %ld\n", p->id, p->cpu); | ||
| 634 | + return NULL; | ||
| 635 | + } | ||
| 636 | + | ||
| 637 | + pi_debug("med_priority[%d]: entering ready state\n", p->id); | ||
| 638 | + /* wait for all threads to be ready */ | ||
| 639 | + status = pthread_barrier_wait(&all_threads_ready); | ||
| 640 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 641 | + pi_error | ||
| 642 | + ("med_priority[%d]: pthread_barrier_wait(all_threads_ready): %x", | ||
| 643 | + p->id, status); | ||
| 644 | + return NULL; | ||
| 645 | + } | ||
| 646 | + | ||
| 647 | + unbounded = (p->inversions < 0); | ||
| 648 | + | ||
| 649 | + pi_debug("med_priority[%d]: starting inversion loop\n", p->id); | ||
| 650 | + for (;;) { | ||
| 651 | + if (!unbounded && (p->total >= (unsigned int)p->inversions)) | ||
| 652 | + set_shutdown_flag(); | ||
| 653 | + | ||
| 654 | + /* Either all threads go through the loop_barr, or none do */ | ||
| 655 | + pthread_mutex_lock(loop_mtx); | ||
| 656 | + if (*loop == 0) { | ||
| 657 | + pthread_mutex_unlock(loop_mtx); | ||
| 658 | + break; | ||
| 659 | + } | ||
| 660 | + pthread_mutex_unlock(loop_mtx); | ||
| 661 | + | ||
| 662 | + status = pthread_barrier_wait(loop_barr); | ||
| 663 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 664 | + pi_error("%s[%d]: pthread_barrier_wait(loop): %x\n", | ||
| 665 | + __func__, p->id, status); | ||
| 666 | + return NULL; | ||
| 667 | + } | ||
| 668 | + | ||
| 669 | + /* Only one Thread needs to check the shutdown status */ | ||
| 670 | + if (status == PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 671 | + pthread_mutex_lock(&shutdown_mtx); | ||
| 672 | + if (shutdown) { | ||
| 673 | + pthread_mutex_lock(loop_mtx); | ||
| 674 | + *loop = 0; | ||
| 675 | + pthread_mutex_unlock(loop_mtx); | ||
| 676 | + } | ||
| 677 | + pthread_mutex_unlock(&shutdown_mtx); | ||
| 678 | + } | ||
| 679 | + | ||
| 680 | + /* start state */ | ||
| 681 | + pi_debug("med_priority[%d]: entering start state (%ld)\n", p->id, | ||
| 682 | + count++); | ||
| 683 | + status = pthread_barrier_wait(&p->start_barrier); | ||
| 684 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 685 | + pi_error | ||
| 686 | + ("med_priority[%d]: pthread_barrier_wait(start): %x", | ||
| 687 | + p->id, status); | ||
| 688 | + return NULL; | ||
| 689 | + } | ||
| 690 | + pi_debug("med_priority[%d]: entering elevate state\n", p->id); | ||
| 691 | + status = pthread_barrier_wait(&p->elevate_barrier); | ||
| 692 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 693 | + pi_error ("med_priority[%d]: pthread_barrier_wait(elevate): %x", p->id, status); | ||
| 694 | + return NULL; | ||
| 695 | + } | ||
| 696 | + | ||
| 697 | + pi_debug("med_priority[%d]: entering finish state\n", p->id); | ||
| 698 | + status = pthread_barrier_wait(&p->finish_barrier); | ||
| 699 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 700 | + pi_error | ||
| 701 | + ("med_priority[%d]: pthread_barrier_wait(finished): %x", | ||
| 702 | + p->id, status); | ||
| 703 | + return NULL; | ||
| 704 | + } | ||
| 705 | + } | ||
| 706 | + set_shutdown_flag(); | ||
| 707 | + | ||
| 708 | + pi_debug("med_priority[%d]: entering done barrier\n", p->id); | ||
| 709 | + /* wait for all threads to finish */ | ||
| 710 | + if (have_errors == 0) { | ||
| 711 | + status = pthread_barrier_wait(&all_threads_done); | ||
| 712 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 713 | + pi_error | ||
| 714 | + ("med_priority[%d]: pthread_barrier_wait(all_threads_done): %x", | ||
| 715 | + p->id, status); | ||
| 716 | + return NULL; | ||
| 717 | + } | ||
| 718 | + } | ||
| 719 | + /* exit */ | ||
| 720 | + pi_debug("med_priority[%d]: exiting\n", p->id); | ||
| 721 | + return NULL; | ||
| 722 | +} | ||
| 723 | + | ||
| 724 | +void *high_priority(void *arg) | ||
| 725 | +{ | ||
| 726 | + int status; | ||
| 727 | + int unbounded; | ||
| 728 | + unsigned long count = 0; | ||
| 729 | + struct group_parameters *p = (struct group_parameters *)arg; | ||
| 730 | + pthread_barrier_t *loop_barr = &p->loop_barr; | ||
| 731 | + pthread_mutex_t *loop_mtx = &p->loop_mtx; | ||
| 732 | + int *loop = &p->loop; | ||
| 733 | + cpu_set_t cpu_mask; | ||
| 734 | + int i; | ||
| 735 | + | ||
| 736 | + if (high_sa.sched_policy == SCHED_DEADLINE) { | ||
| 737 | + CPU_ZERO(&cpu_mask); | ||
| 738 | + for (i = 0; i < num_processors; i++) | ||
| 739 | + CPU_SET(i, &cpu_mask); | ||
| 740 | + status = sched_setaffinity(0, sizeof(cpu_set_t), &cpu_mask); | ||
| 741 | + if (status < 0) { | ||
| 742 | + pi_error | ||
| 743 | + ("high_priority[%d]: set cpu affinity*dl): %x\n", | ||
| 744 | + p->id, status); | ||
| 745 | + return NULL; | ||
| 746 | + } | ||
| 747 | + | ||
| 748 | + status = sched_setattr(gettid(), &high_sa, 0); | ||
| 749 | + if (status < 0) { | ||
| 750 | + pi_error | ||
| 751 | + ("high_priority[%d]: sched_setattr(dl): %x\n", | ||
| 752 | + p->id, status); | ||
| 753 | + return NULL; | ||
| 754 | + } | ||
| 755 | + } | ||
| 756 | + | ||
| 757 | + allow_sigterm(); | ||
| 758 | + if (verify_cpu(p->cpu) != SUCCESS) { | ||
| 759 | + pi_error("high_priority[%d]: not bound to %ld\n", p->id, p->cpu); | ||
| 760 | + return NULL; | ||
| 761 | + } | ||
| 762 | + | ||
| 763 | + pi_debug("high_priority[%d]: entering ready state\n", p->id); | ||
| 764 | + | ||
| 765 | + /* wait for all threads to be ready */ | ||
| 766 | + status = pthread_barrier_wait(&all_threads_ready); | ||
| 767 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 768 | + pi_error | ||
| 769 | + ("high_priority[%d]: pthread_barrier_wait(all_threads_ready): %x", | ||
| 770 | + p->id, status); | ||
| 771 | + return NULL; | ||
| 772 | + } | ||
| 773 | + unbounded = (p->inversions < 0); | ||
| 774 | + pi_debug("high_priority[%d]: starting inversion loop\n", p->id); | ||
| 775 | + for (;;) { | ||
| 776 | + if (!unbounded && (p->total >= (unsigned int)p->inversions)) { | ||
| 777 | + set_shutdown_flag(); | ||
| 778 | + } | ||
| 779 | + | ||
| 780 | + /* Either all threads go through the loop_barr, or none do */ | ||
| 781 | + pthread_mutex_lock(loop_mtx); | ||
| 782 | + if (*loop == 0) { | ||
| 783 | + pthread_mutex_unlock(loop_mtx); | ||
| 784 | + break; | ||
| 785 | + } | ||
| 786 | + pthread_mutex_unlock(loop_mtx); | ||
| 787 | + | ||
| 788 | + status = pthread_barrier_wait(loop_barr); | ||
| 789 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 790 | + pi_error("%s[%d]: pthread_barrier_wait(loop): %x\n", | ||
| 791 | + __func__, p->id, status); | ||
| 792 | + return NULL; | ||
| 793 | + } | ||
| 794 | + | ||
| 795 | + /* Only one Thread needs to check the shutdown status */ | ||
| 796 | + if (status == PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 797 | + pthread_mutex_lock(&shutdown_mtx); | ||
| 798 | + if (shutdown) { | ||
| 799 | + pthread_mutex_lock(loop_mtx); | ||
| 800 | + *loop = 0; | ||
| 801 | + pthread_mutex_unlock(loop_mtx); | ||
| 802 | + } | ||
| 803 | + pthread_mutex_unlock(&shutdown_mtx); | ||
| 804 | + } | ||
| 805 | + pi_debug("high_priority[%d]: entering start state (%d)\n", p->id, | ||
| 806 | + count++); | ||
| 807 | + status = pthread_barrier_wait(&p->start_barrier); | ||
| 808 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 809 | + pi_error | ||
| 810 | + ("high_priority[%d]: pthread_barrier_wait(start): %x", | ||
| 811 | + p->id, status); | ||
| 812 | + return NULL; | ||
| 813 | + } | ||
| 814 | + | ||
| 815 | + pi_debug("high_priority[%d]: entering running state\n", p->id); | ||
| 816 | + status = pthread_barrier_wait(&p->locked_barrier); | ||
| 817 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 818 | + pi_error | ||
| 819 | + ("high_priority[%d]: pthread_barrier_wait(running): %x", | ||
| 820 | + p->id, status); | ||
| 821 | + return NULL; | ||
| 822 | + } | ||
| 823 | + pi_debug("high_priority[%d]: locking mutex\n", p->id); | ||
| 824 | + pthread_mutex_lock(&p->mutex); | ||
| 825 | + pi_debug("high_priority[%d]: got mutex\n", p->id); | ||
| 826 | + | ||
| 827 | + pi_debug("high_priority[%d]: unlocking mutex\n", p->id); | ||
| 828 | + pthread_mutex_unlock(&p->mutex); | ||
| 829 | + pi_debug("high_priority[%d]: entering finish state\n", p->id); | ||
| 830 | + | ||
| 831 | + status = pthread_barrier_wait(&p->finish_barrier); | ||
| 832 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 833 | + pi_error | ||
| 834 | + ("high_priority[%d]: pthread_barrier_wait(finish): %x", p->id, status); | ||
| 835 | + return NULL; | ||
| 836 | + } | ||
| 837 | + /* update the group stats */ | ||
| 838 | + p->total++; | ||
| 839 | + | ||
| 840 | + /* update the watchdog counter */ | ||
| 841 | + p->watchdog++; | ||
| 842 | + | ||
| 843 | + } | ||
| 844 | + set_shutdown_flag(); | ||
| 845 | + | ||
| 846 | + pi_debug("high_priority[%d]: entering done barrier\n", p->id); | ||
| 847 | + | ||
| 848 | + if (have_errors == 0) { | ||
| 849 | + /* wait for all threads to finish */ | ||
| 850 | + status = pthread_barrier_wait(&all_threads_done); | ||
| 851 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) { | ||
| 852 | + pi_error | ||
| 853 | + ("high_priority[%d]: pthread_barrier_wait(all_threads_done): %x", | ||
| 854 | + p->id, status); | ||
| 855 | + return NULL; | ||
| 856 | + } | ||
| 857 | + } | ||
| 858 | + /* exit */ | ||
| 859 | + pi_debug("high_priority[%d]: exiting\n", p->id); | ||
| 860 | + return NULL; | ||
| 861 | +} | ||
| 862 | + | ||
| 863 | +void usage(int error) | ||
| 864 | +{ | ||
| 865 | + printf("pi_stress V %1.2f\n", VERSION); | ||
| 866 | + printf("Usage:\n" | ||
| 867 | + "pi_stress <options>\n\n" | ||
| 868 | + "-d --debug turn on debug prints\n" | ||
| 869 | + "-D TIME --duration=TIME length of test run in seconds (default is infinite)\n" | ||
| 870 | + " Append 'm', 'h', or 'd'\n" | ||
| 871 | + " to specify minutes, hours or days.\n" | ||
| 872 | + "-g N --groups=N set the number of inversion groups\n" | ||
| 873 | + "-h --help print this message\n" | ||
| 874 | + "-i INV --inversions=INV number of inversions per group (default is infinite)\n" | ||
| 875 | + " --json=FILENAME write final results into FILENAME, JSON formatted\n" | ||
| 876 | + "-m --mlockall lock current and future memory\n" | ||
| 877 | + "-p --prompt prompt before starting the test\n" | ||
| 878 | + "-q --quiet suppress running output\n" | ||
| 879 | + "-r --rr use SCHED_RR for test threads [SCHED_FIFO]\n" | ||
| 880 | + "-s OPTS --sched OPTS scheduling options per thread type:\n" | ||
| 881 | + " id=[high|med|low] select thread\n" | ||
| 882 | + " ,policy=[fifo,rr] scheduling class [SCHED_FIFO, SCHED_RR]\n" | ||
| 883 | + " ,priority=N scheduling priority\n" | ||
| 884 | + " ,policy=[deadline] scheduling class [DEADLINE]\n" | ||
| 885 | + " ,runtime=N\n" | ||
| 886 | + " ,deadline=N\n" | ||
| 887 | + " ,period=N\n" | ||
| 888 | + "-u --uniprocessor force all threads to run on one processor\n" | ||
| 889 | + "-v --verbose lots of output\n" | ||
| 890 | + "-V --version print version number on output\n" | ||
| 891 | + ); | ||
| 892 | + exit(error); | ||
| 893 | +} | ||
| 894 | + | ||
| 895 | +/* block all signals (called from main) */ | ||
| 896 | +int block_signals(void) | ||
| 897 | +{ | ||
| 898 | + int status; | ||
| 899 | + sigset_t sigset; | ||
| 900 | + | ||
| 901 | + /* mask off all signals */ | ||
| 902 | + status = sigfillset(&sigset); | ||
| 903 | + if (status) { | ||
| 904 | + pi_error("setting up full signal set %s\n", strerror(status)); | ||
| 905 | + return FAILURE; | ||
| 906 | + } | ||
| 907 | + status = pthread_sigmask(SIG_BLOCK, &sigset, NULL); | ||
| 908 | + if (status) { | ||
| 909 | + pi_error("setting signal mask: %s\n", strerror(status)); | ||
| 910 | + return FAILURE; | ||
| 911 | + } | ||
| 912 | + return SUCCESS; | ||
| 913 | +} | ||
| 914 | + | ||
| 915 | +/* allow SIGTERM delivery (called from worker threads) */ | ||
| 916 | +int allow_sigterm(void) | ||
| 917 | +{ | ||
| 918 | + int status; | ||
| 919 | + sigset_t sigset; | ||
| 920 | + | ||
| 921 | + status = sigemptyset(&sigset); | ||
| 922 | + if (status) { | ||
| 923 | + pi_error("creating empty signal set: %s\n", strerror(status)); | ||
| 924 | + return FAILURE; | ||
| 925 | + } | ||
| 926 | + status = sigaddset(&sigset, SIGTERM); | ||
| 927 | + if (status) { | ||
| 928 | + pi_error("adding SIGTERM to signal set: %s\n", strerror(status)); | ||
| 929 | + return FAILURE; | ||
| 930 | + } | ||
| 931 | + status = pthread_sigmask(SIG_UNBLOCK, &sigset, NULL); | ||
| 932 | + if (status) { | ||
| 933 | + pi_error("unblocking SIGTERM: %s\n", strerror(status)); | ||
| 934 | + return FAILURE; | ||
| 935 | + } | ||
| 936 | + return SUCCESS; | ||
| 937 | +} | ||
| 938 | + | ||
| 939 | +/* clean up before exiting */ | ||
| 940 | +void set_shutdown_flag(void) | ||
| 941 | +{ | ||
| 942 | + pthread_mutex_lock(&shutdown_mtx); | ||
| 943 | + if (shutdown == 0) { | ||
| 944 | + /* tell anyone that's looking that we're done */ | ||
| 945 | + pi_info("setting shutdown flag\n"); | ||
| 946 | + shutdown = 1; | ||
| 947 | + } | ||
| 948 | + pthread_mutex_unlock(&shutdown_mtx); | ||
| 949 | +} | ||
| 950 | + | ||
| 951 | +/* set up a test group */ | ||
| 952 | +int initialize_group(struct group_parameters *group) | ||
| 953 | +{ | ||
| 954 | + int status; | ||
| 955 | + pthread_mutexattr_t mutex_attr; | ||
| 956 | + | ||
| 957 | + group->inversions = inversions; | ||
| 958 | + | ||
| 959 | + /* setup default attributes for the group mutex */ | ||
| 960 | + /* (make it a PI mutex) */ | ||
| 961 | + status = pthread_mutexattr_init(&mutex_attr); | ||
| 962 | + if (status) { | ||
| 963 | + pi_error("initializing mutex attribute: %s\n", strerror(status)); | ||
| 964 | + return FAILURE; | ||
| 965 | + } | ||
| 966 | + | ||
| 967 | + /* set priority inheritance attribute for mutex */ | ||
| 968 | + status = pthread_mutexattr_setprotocol(&mutex_attr, | ||
| 969 | + PTHREAD_PRIO_INHERIT); | ||
| 970 | + if (status) { | ||
| 971 | + pi_error("setting mutex attribute policy: %s\n", strerror(status)); | ||
| 972 | + return FAILURE; | ||
| 973 | + } | ||
| 974 | + /* initialize the group mutex */ | ||
| 975 | + status = pthread_mutex_init(&group->mutex, &mutex_attr); | ||
| 976 | + if (status) { | ||
| 977 | + pi_error("initializing mutex: %s\n", strerror(status)); | ||
| 978 | + return FAILURE; | ||
| 979 | + } | ||
| 980 | + | ||
| 981 | + /* initialize the group barriers */ | ||
| 982 | + if (barrier_init(&group->start_barrier, NULL, NUM_TEST_THREADS, | ||
| 983 | + "start_barrier")) | ||
| 984 | + return FAILURE; | ||
| 985 | + | ||
| 986 | + if (barrier_init(&group->locked_barrier, NULL, 2, "locked_barrier")) | ||
| 987 | + return FAILURE; | ||
| 988 | + | ||
| 989 | + if (barrier_init(&group->elevate_barrier, NULL, 2, "elevate_barrier")) | ||
| 990 | + return FAILURE; | ||
| 991 | + | ||
| 992 | + if (barrier_init | ||
| 993 | + (&group->finish_barrier, NULL, NUM_TEST_THREADS, "finish_barrier")) | ||
| 994 | + return FAILURE; | ||
| 995 | + | ||
| 996 | + if (barrier_init(&group->loop_barr, NULL, NUM_TEST_THREADS, | ||
| 997 | + "loop_barrier")) | ||
| 998 | + return FAILURE; | ||
| 999 | + | ||
| 1000 | + status = pthread_mutex_init(&group->loop_mtx, NULL); | ||
| 1001 | + if (status) { | ||
| 1002 | + pi_error("pthread_mutex_init, status = %d\n", status); | ||
| 1003 | + return FAILURE; | ||
| 1004 | + } | ||
| 1005 | + | ||
| 1006 | + status = pthread_mutex_lock(&group->loop_mtx); | ||
| 1007 | + if (status) { | ||
| 1008 | + pi_error("pthread_mutex_lock, status = %d\n", status); | ||
| 1009 | + return FAILURE; | ||
| 1010 | + } | ||
| 1011 | + | ||
| 1012 | + group->loop = 1; | ||
| 1013 | + | ||
| 1014 | + status = pthread_mutex_unlock(&group->loop_mtx); | ||
| 1015 | + if (status) { | ||
| 1016 | + pi_error("pthread_mutex_unlock, status = %d\n", status); | ||
| 1017 | + return FAILURE; | ||
| 1018 | + } | ||
| 1019 | + | ||
| 1020 | + return SUCCESS; | ||
| 1021 | +} | ||
| 1022 | + | ||
| 1023 | +/* setup and create a groups threads */ | ||
| 1024 | +int create_group(struct group_parameters *group) | ||
| 1025 | +{ | ||
| 1026 | + int status; | ||
| 1027 | + pthread_attr_t thread_attr; | ||
| 1028 | + cpu_set_t mask; | ||
| 1029 | + | ||
| 1030 | + /* initialize group structure */ | ||
| 1031 | + status = initialize_group(group); | ||
| 1032 | + if (status) { | ||
| 1033 | + pi_error("initializing group %d\n", group->id); | ||
| 1034 | + return FAILURE; | ||
| 1035 | + } | ||
| 1036 | + | ||
| 1037 | + CPU_ZERO(&mask); | ||
| 1038 | + CPU_SET(group->cpu, &mask); | ||
| 1039 | + | ||
| 1040 | + pi_debug("group %d bound to cpu %ld\n", group->id, group->cpu); | ||
| 1041 | + | ||
| 1042 | + /* start the low priority thread */ | ||
| 1043 | + pi_debug("creating low priority thread\n"); | ||
| 1044 | + if (setup_thread_attr(&thread_attr, &low_sa, &mask)) | ||
| 1045 | + return FAILURE; | ||
| 1046 | + status = pthread_create(&group->low_tid, | ||
| 1047 | + &thread_attr, low_priority, group); | ||
| 1048 | + if (status != 0) { | ||
| 1049 | + pi_error("creating low_priority thread: %s\n", strerror(status)); | ||
| 1050 | + return FAILURE; | ||
| 1051 | + } | ||
| 1052 | + | ||
| 1053 | + /* create the medium priority thread */ | ||
| 1054 | + pi_debug("creating medium priority thread\n"); | ||
| 1055 | + if (setup_thread_attr(&thread_attr, &med_sa, &mask)) | ||
| 1056 | + return FAILURE; | ||
| 1057 | + status = pthread_create(&group->med_tid, | ||
| 1058 | + &thread_attr, med_priority, group); | ||
| 1059 | + if (status != 0) { | ||
| 1060 | + pi_error("creating med_priority thread: %s\n", strerror(status)); | ||
| 1061 | + return FAILURE; | ||
| 1062 | + } | ||
| 1063 | + | ||
| 1064 | + /* create the high priority thread */ | ||
| 1065 | + pi_debug("creating high priority thread\n"); | ||
| 1066 | + if (setup_thread_attr(&thread_attr, &high_sa, &mask)) | ||
| 1067 | + return FAILURE; | ||
| 1068 | + status = pthread_create(&group->high_tid, | ||
| 1069 | + &thread_attr, high_priority, group); | ||
| 1070 | + if (status != 0) { | ||
| 1071 | + pi_error("creating high_priority thread: %s\n", strerror(status)); | ||
| 1072 | + set_shutdown_flag(); | ||
| 1073 | + return FAILURE; | ||
| 1074 | + } | ||
| 1075 | + return SUCCESS; | ||
| 1076 | +} | ||
| 1077 | + | ||
| 1078 | +unsigned long parse_unsigned(const char *str) | ||
| 1079 | +{ | ||
| 1080 | + unsigned long n; | ||
| 1081 | + char *p; | ||
| 1082 | + | ||
| 1083 | + errno = 0; | ||
| 1084 | + n = strtoul(str, &p, 10); | ||
| 1085 | + | ||
| 1086 | + if ((errno == ERANGE && n == ULONG_MAX) | ||
| 1087 | + || (errno != 0 && n == 0)) { | ||
| 1088 | + pi_error("parsing number failed: %s\n", str); | ||
| 1089 | + exit(EXIT_FAILURE); | ||
| 1090 | + } | ||
| 1091 | + | ||
| 1092 | + return n; | ||
| 1093 | +} | ||
| 1094 | + | ||
| 1095 | +long parse_signed(const char *str) | ||
| 1096 | +{ | ||
| 1097 | + long n; | ||
| 1098 | + char *p; | ||
| 1099 | + | ||
| 1100 | + errno = 0; | ||
| 1101 | + n = strtol(str, &p, 10); | ||
| 1102 | + | ||
| 1103 | + if ((errno == ERANGE && (n == LONG_MAX || n == LONG_MIN)) | ||
| 1104 | + || (errno != 0 && n == 0)) { | ||
| 1105 | + pi_error("parsing number failed: %s\n", str); | ||
| 1106 | + exit(EXIT_FAILURE); | ||
| 1107 | + } | ||
| 1108 | + | ||
| 1109 | + return n; | ||
| 1110 | +} | ||
| 1111 | + | ||
| 1112 | +int process_sched_line(const char *arg) | ||
| 1113 | +{ | ||
| 1114 | + char *buf, *k, *v; | ||
| 1115 | + const char del[] = ",="; | ||
| 1116 | + struct sched_attr sa = { 0, }; | ||
| 1117 | + char *id = NULL; | ||
| 1118 | + int retval = SUCCESS; | ||
| 1119 | + | ||
| 1120 | + buf = strdupa(arg); | ||
| 1121 | + | ||
| 1122 | + k = strsep(&buf, del); | ||
| 1123 | + while (k) { | ||
| 1124 | + v = strsep(&buf, del); | ||
| 1125 | + if (!v) | ||
| 1126 | + break; | ||
| 1127 | + | ||
| 1128 | + if (!strcmp(k, "id")) | ||
| 1129 | + id = v; | ||
| 1130 | + else if (!strcmp(k, "policy")) | ||
| 1131 | + sa.sched_policy = string_to_policy(v); | ||
| 1132 | + else if (!strcmp(k, "nice")) | ||
| 1133 | + sa.sched_nice = parse_signed(v); | ||
| 1134 | + else if (!strcmp(k, "priority")) | ||
| 1135 | + sa.sched_priority = parse_unsigned(v); | ||
| 1136 | + else if (!strcmp(k, "runtime")) | ||
| 1137 | + sa.sched_runtime = parse_unsigned(v); | ||
| 1138 | + else if (!strcmp(k, "deadline")) | ||
| 1139 | + sa.sched_deadline = parse_unsigned(v); | ||
| 1140 | + else if (!strcmp(k, "period")) | ||
| 1141 | + sa.sched_period = parse_unsigned(v); | ||
| 1142 | + | ||
| 1143 | + k = strsep(&buf, del); | ||
| 1144 | + } | ||
| 1145 | + | ||
| 1146 | + if (!id) | ||
| 1147 | + return FAILURE; | ||
| 1148 | + | ||
| 1149 | + /* | ||
| 1150 | + * We do not validate the options, instead we pass all garbage | ||
| 1151 | + * to the kernel and see what's happening | ||
| 1152 | + */ | ||
| 1153 | + | ||
| 1154 | + if (!strcmp(id, "low")) { | ||
| 1155 | + memcpy(&low_sa, &sa, sizeof(struct sched_attr)); | ||
| 1156 | + sa_initialized |= SA_INIT_LOW; | ||
| 1157 | + } else if (!strcmp(id, "med")) { | ||
| 1158 | + memcpy(&med_sa, &sa, sizeof(struct sched_attr)); | ||
| 1159 | + sa_initialized |= SA_INIT_MED; | ||
| 1160 | + } else if (!strcmp(id, "high")) { | ||
| 1161 | + memcpy(&high_sa, &sa, sizeof(struct sched_attr)); | ||
| 1162 | + sa_initialized |= SA_INIT_HIGH; | ||
| 1163 | + } else { | ||
| 1164 | + retval = FAILURE; | ||
| 1165 | + } | ||
| 1166 | + | ||
| 1167 | + return retval; | ||
| 1168 | +} | ||
| 1169 | + | ||
| 1170 | +enum option_values { | ||
| 1171 | + OPT_DEBUG=1, OPT_DURATION, OPT_GROUPS, OPT_HELP, OPT_INVERSIONS, | ||
| 1172 | + OPT_JSON, OPT_MLOCKALL, OPT_PROMPT, OPT_QUIET, OPT_RR, OPT_SCHED, | ||
| 1173 | + OPT_UNIPROCESSOR, OPT_VERBOSE, OPT_VERSION, | ||
| 1174 | +}; | ||
| 1175 | + | ||
| 1176 | +void process_command_line(int argc, char **argv) | ||
| 1177 | +{ | ||
| 1178 | + for (;;) { | ||
| 1179 | + struct option options[] = { | ||
| 1180 | + {"debug", no_argument, NULL, OPT_DEBUG}, | ||
| 1181 | + {"duration", required_argument, NULL, OPT_DURATION}, | ||
| 1182 | + {"groups", required_argument, NULL, OPT_GROUPS}, | ||
| 1183 | + {"help", no_argument, NULL, OPT_HELP}, | ||
| 1184 | + {"inversions", required_argument, NULL, OPT_INVERSIONS}, | ||
| 1185 | + {"json", required_argument, NULL, OPT_JSON}, | ||
| 1186 | + {"mlockall", no_argument, NULL, OPT_MLOCKALL}, | ||
| 1187 | + {"prompt", no_argument, NULL, OPT_PROMPT}, | ||
| 1188 | + {"quiet", no_argument, NULL, OPT_QUIET}, | ||
| 1189 | + {"rr", no_argument, NULL, OPT_RR}, | ||
| 1190 | + {"sched", required_argument, NULL, OPT_SCHED}, | ||
| 1191 | + {"uniprocessor", no_argument, NULL, OPT_UNIPROCESSOR}, | ||
| 1192 | + {"verbose", no_argument, NULL, OPT_VERBOSE}, | ||
| 1193 | + {"version", no_argument, NULL, OPT_VERSION}, | ||
| 1194 | + {NULL, 0, NULL, 0}, | ||
| 1195 | + }; | ||
| 1196 | + | ||
| 1197 | + int c = getopt_long(argc, argv, "+hD:vqi:g:rs:pdVum", options, NULL); | ||
| 1198 | + if (c == -1) | ||
| 1199 | + break; | ||
| 1200 | + switch (c) { | ||
| 1201 | + case OPT_DEBUG: | ||
| 1202 | + case 'd': | ||
| 1203 | + debugging = 1; | ||
| 1204 | + break; | ||
| 1205 | + case OPT_DURATION: | ||
| 1206 | + case 'D': | ||
| 1207 | + duration = parse_time_string(optarg); | ||
| 1208 | + break; | ||
| 1209 | + case OPT_GROUPS: | ||
| 1210 | + case 'g': | ||
| 1211 | + ngroups = strtol(optarg, NULL, 10); | ||
| 1212 | + if (ngroups > num_processors) { | ||
| 1213 | + pi_error("the number of groups cannot exceed the number of online processors (%ld)\n", num_processors); | ||
| 1214 | + exit(-1); | ||
| 1215 | + } | ||
| 1216 | + pi_info("number of groups set to %d\n", ngroups); | ||
| 1217 | + break; | ||
| 1218 | + case OPT_HELP: | ||
| 1219 | + case 'h': | ||
| 1220 | + usage(0); | ||
| 1221 | + break; | ||
| 1222 | + case OPT_INVERSIONS: | ||
| 1223 | + case 'i': | ||
| 1224 | + inversions = strtol(optarg, NULL, 10); | ||
| 1225 | + pi_info("doing %d inversion per group\n", inversions); | ||
| 1226 | + break; | ||
| 1227 | + case OPT_JSON: | ||
| 1228 | + strncpy(jsonfile, optarg, strnlen(optarg, MAX_PATH-1)); | ||
| 1229 | + break; | ||
| 1230 | + case OPT_MLOCKALL: | ||
| 1231 | + case 'm': | ||
| 1232 | + lockall = 1; | ||
| 1233 | + break; | ||
| 1234 | + case OPT_PROMPT: | ||
| 1235 | + case 'p': | ||
| 1236 | + prompt = 1; | ||
| 1237 | + break; | ||
| 1238 | + case OPT_QUIET: | ||
| 1239 | + case 'q': | ||
| 1240 | + verbose = 0; | ||
| 1241 | + quiet = 1; | ||
| 1242 | + break; | ||
| 1243 | + case OPT_RR: | ||
| 1244 | + case 'r': | ||
| 1245 | + policy = SCHED_RR; | ||
| 1246 | + break; | ||
| 1247 | + case OPT_SCHED: | ||
| 1248 | + case 's': | ||
| 1249 | + if (process_sched_line(optarg)) | ||
| 1250 | + pi_error("ignoring invalid options '%s'\n", optarg); | ||
| 1251 | + break; | ||
| 1252 | + case OPT_UNIPROCESSOR: | ||
| 1253 | + case 'u': | ||
| 1254 | + uniprocessor = 1; | ||
| 1255 | + break; | ||
| 1256 | + case OPT_VERBOSE: | ||
| 1257 | + case 'v': | ||
| 1258 | + verbose = 1; | ||
| 1259 | + quiet = 0; | ||
| 1260 | + break; | ||
| 1261 | + case OPT_VERSION: | ||
| 1262 | + case 'V': | ||
| 1263 | + printf("pi_stress v%1.2f ", VERSION); | ||
| 1264 | + exit(0); | ||
| 1265 | + default: | ||
| 1266 | + usage(1); | ||
| 1267 | + break; | ||
| 1268 | + } | ||
| 1269 | + } | ||
| 1270 | +} | ||
| 1271 | + | ||
| 1272 | +/* total the number of inversions that have been performed */ | ||
| 1273 | +unsigned long total_inversions(void) | ||
| 1274 | +{ | ||
| 1275 | + int i; | ||
| 1276 | + unsigned long total = 0; | ||
| 1277 | + | ||
| 1278 | + for (i = 0; i < ngroups; i++) | ||
| 1279 | + total += groups[i].total; | ||
| 1280 | + return total; | ||
| 1281 | +} | ||
| 1282 | + | ||
| 1283 | +void print_sched_attr(const char *name, struct sched_attr *sa) | ||
| 1284 | +{ | ||
| 1285 | + printf(" %6s thread", name); | ||
| 1286 | + printf(" %s", policy_to_string(sa->sched_policy)); | ||
| 1287 | + | ||
| 1288 | + switch (sa->sched_policy) { | ||
| 1289 | + case SCHED_OTHER: | ||
| 1290 | + printf(" nice %d\n", sa->sched_nice); | ||
| 1291 | + break; | ||
| 1292 | + case SCHED_FIFO: | ||
| 1293 | + case SCHED_RR: | ||
| 1294 | + printf(" priority %d\n", sa->sched_priority); | ||
| 1295 | + break; | ||
| 1296 | + case SCHED_DEADLINE: | ||
| 1297 | + printf(" runtime %" PRIu64 " deadline %" PRIu64 " period %" PRIu64 "\n", | ||
| 1298 | + sa->sched_runtime, sa->sched_deadline, | ||
| 1299 | + sa->sched_period); | ||
| 1300 | + break; | ||
| 1301 | + } | ||
| 1302 | +} | ||
| 1303 | + | ||
| 1304 | +void banner(void) | ||
| 1305 | +{ | ||
| 1306 | + if (quiet) | ||
| 1307 | + return; | ||
| 1308 | + | ||
| 1309 | + printf("Starting PI Stress Test\n"); | ||
| 1310 | + printf("Number of thread groups: %d\n", ngroups); | ||
| 1311 | + if (duration >= 0) | ||
| 1312 | + printf("Duration of test run: %d seconds\n", duration); | ||
| 1313 | + else | ||
| 1314 | + printf("Duration of test run: infinite\n"); | ||
| 1315 | + if (inversions < 0) | ||
| 1316 | + printf("Number of inversions per group: unlimited\n"); | ||
| 1317 | + else | ||
| 1318 | + printf("Number of inversions per group: %d\n", inversions); | ||
| 1319 | + print_sched_attr("Admin", &admin_sa); | ||
| 1320 | + printf("%d groups of 3 threads will be created\n", ngroups); | ||
| 1321 | + print_sched_attr("High", &high_sa); | ||
| 1322 | + print_sched_attr("Med", &med_sa); | ||
| 1323 | + print_sched_attr("Low", &low_sa); | ||
| 1324 | + printf("\n"); | ||
| 1325 | +} | ||
| 1326 | + | ||
| 1327 | +void summary(void) | ||
| 1328 | +{ | ||
| 1329 | + time_t interval = finish - start; | ||
| 1330 | + struct tm *t = gmtime(&interval); | ||
| 1331 | + | ||
| 1332 | + printf("Total inversion performed: %lu\n", total_inversions()); | ||
| 1333 | + printf("Test Duration: %d days, %d hours, %d minutes, %d seconds\n", | ||
| 1334 | + t->tm_yday, t->tm_hour, t->tm_min, t->tm_sec); | ||
| 1335 | +} | ||
| 1336 | + | ||
| 1337 | +void write_stats(FILE *f, void *data __attribute__ ((unused))) | ||
| 1338 | +{ | ||
| 1339 | + fprintf(f, " \"inversion\": %lu\n", total_inversions()); | ||
| 1340 | +} | ||
| 1341 | + | ||
| 1342 | +int | ||
| 1343 | +barrier_init(pthread_barrier_t *b, const pthread_barrierattr_t *attr, | ||
| 1344 | + unsigned int count, const char *name) | ||
| 1345 | +{ | ||
| 1346 | + int status; | ||
| 1347 | + | ||
| 1348 | + status = pthread_barrier_init(b, attr, count); | ||
| 1349 | + if (status != 0) { | ||
| 1350 | + pi_error("barrier_init: failed to initialize: %s\n", name); | ||
| 1351 | + pi_error("status = %d\n", status); | ||
| 1352 | + return FAILURE; | ||
| 1353 | + } | ||
| 1354 | + | ||
| 1355 | + return SUCCESS; | ||
| 1356 | +} | ||
| 1357 | + | ||
| 1358 | +void setup_sched_attr(struct sched_attr *attr, int policy, int prio) | ||
| 1359 | +{ | ||
| 1360 | + attr->sched_policy = policy; | ||
| 1361 | + attr->sched_priority = prio; | ||
| 1362 | +} | ||
| 1363 | + | ||
| 1364 | +void setup_sched_config(int policy) | ||
| 1365 | +{ | ||
| 1366 | + int prio_min; | ||
| 1367 | + | ||
| 1368 | + prio_min = sched_get_priority_min(policy); | ||
| 1369 | + | ||
| 1370 | + if (!(sa_initialized & SA_INIT_LOW)) | ||
| 1371 | + setup_sched_attr(&low_sa, policy, prio_min + 0); | ||
| 1372 | + if (!(sa_initialized & SA_INIT_MED)) | ||
| 1373 | + setup_sched_attr(&med_sa, policy, prio_min + 1); | ||
| 1374 | + if (!(sa_initialized & SA_INIT_HIGH)) | ||
| 1375 | + setup_sched_attr(&high_sa, policy, prio_min + 2); | ||
| 1376 | + if (!(sa_initialized & SA_INIT_ADMIN)) | ||
| 1377 | + setup_sched_attr(&admin_sa, policy, prio_min + 3); | ||
| 1378 | +} | ||
| @@ -0,0 +1,48 @@ | |||
| 1 | +.\" | ||
| 2 | +.TH PIP\ STRESS 8 "September 17, 2018" | ||
| 3 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 4 | +.SH NAME | ||
| 5 | +.B pip_stress \- Priority Inheritance with processes | ||
| 6 | +.SH SYNOPSIS | ||
| 7 | +.B pip_stress | ||
| 8 | + | ||
| 9 | +.SH DESCRIPTION | ||
| 10 | +This program demonstrates the technique of using priority inheritance (PI) | ||
| 11 | +mutexes with processes instead of threads. | ||
| 12 | +The way to do this is to obtain some shared memory - in this case with | ||
| 13 | +mmap that backs a pthread_mutex_t since this will support PI. | ||
| 14 | +Pay particular attention to how this is intialized to support processes. | ||
| 15 | +Function init_shared_pthread_mutex() does this by setting the | ||
| 16 | +pthread_mutexattr to PTHREAD_PROCESS_SHARED and the mutex protocol to | ||
| 17 | +PTHREAD_PRIO_INHERIT. | ||
| 18 | +In this program we purposely try to invoke a classic priority inversion. | ||
| 19 | +A low priority process grabs the mutex and does some work. | ||
| 20 | +A high priority process comes a long and is blocked since the mutex is taken. | ||
| 21 | +A medium priority process that doesn't require the mutex then takes the | ||
| 22 | +processor. Because the processes are restricted to one cpu, the low priority | ||
| 23 | +processes never makes any progress because the medium priority process | ||
| 24 | +runs in an infinite loop. This is a priority inversion because the | ||
| 25 | +medium priority process is running at the expensive of the high priority | ||
| 26 | +process. However, since we have used PRIO_INHERIT and are running on a | ||
| 27 | +machine that supports preemption, the high priority process will lend it's | ||
| 28 | +priority to the low priority process which will preempt the medium priority | ||
| 29 | +process. The low priority process will then release the mutex which the | ||
| 30 | +high priority process can obtain. When the high priority process gets to run | ||
| 31 | +it kills the medium priority process. | ||
| 32 | +The state structure keeps track of the progress. Although this program | ||
| 33 | +is set up to likely trigger an inversion, there is no guarantee that | ||
| 34 | +scheduling will make that happen. After the program completes it reports | ||
| 35 | +whether a priority inversion occurred or not. In either case this program | ||
| 36 | +demonstrates how to use priority inheritance mutexes with processes. | ||
| 37 | +In fact, you would be better off to avoid scenarios in which a priority | ||
| 38 | +inversion occurs if possible - this program tries to trigger them just | ||
| 39 | +to show that it works. If you are having difficulty triggering an inversion, | ||
| 40 | +merely increase the time that the low priority process sleeps while | ||
| 41 | +holding the lock. (usleep); | ||
| 42 | +Also note that you have to run as a user with permission to change | ||
| 43 | +scheduling priorities. | ||
| 44 | +.BR | ||
| 45 | +.SH AUTHOR | ||
| 46 | +pip_stress was written by John Kacur <jkacur at redhat.com> | ||
| 47 | +.PP | ||
| 48 | +This manual page was also written by John Kacur | ||
| @@ -0,0 +1,368 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +/* | ||
| 3 | + Pip stress - Priority Inheritance with processes | ||
| 4 | + | ||
| 5 | + Copyright (C) 2009, John Kacur <jkacur@redhat.com> | ||
| 6 | +*/ | ||
| 7 | + | ||
| 8 | +/* | ||
| 9 | + * This program demonstrates the technique of using priority inheritance (PI) | ||
| 10 | + * mutexes with processes instead of threads. | ||
| 11 | + * The way to do this is to obtain some shared memory - in this case with | ||
| 12 | + * mmap that backs a pthread_mutex_t since this will support PI. | ||
| 13 | + * Pay particular attention to how this is intialized to support processes. | ||
| 14 | + * Function init_shared_pthread_mutex() does this by setting the | ||
| 15 | + * pthread_mutexattr to PTHREAD_PROCESS_SHARED and the mutex protocol to | ||
| 16 | + * PTHREAD_PRIO_INHERIT. | ||
| 17 | + * In this program we purposely try to invoke a classic priority inversion. | ||
| 18 | + * A low priority process grabs the mutex and does some work. | ||
| 19 | + * A high priority process comes a long and is blocked since the mutex is taken. | ||
| 20 | + * A medium priority process that doesn't require the mutex then takes the | ||
| 21 | + * processor. Because the processes are restricted to one cpu, the low priority | ||
| 22 | + * processes never makes any progress because the medium priority process | ||
| 23 | + * runs in an infinite loop. This is a priority inversion because the | ||
| 24 | + * medium priority process is running at the expensive of the high priority | ||
| 25 | + * process. However, since we have used PRIO_INHERIT and are running on a | ||
| 26 | + * machine that supports preemption, the high priority process will lend it's | ||
| 27 | + * priority to the low priority process which will preempt the medium priority | ||
| 28 | + * process. The low priority process will then release the mutex which the | ||
| 29 | + * high priority process can obtain. When the high priority process gets to run | ||
| 30 | + * it kills the medium priority process. | ||
| 31 | + * The state structure keeps track of the progress. Although this program | ||
| 32 | + * is set up to likely trigger an inversion, there is no guarantee that | ||
| 33 | + * scheduling will make that happen. After the program completes it reports | ||
| 34 | + * whether a priority inversion occurred or not. In either case this program | ||
| 35 | + * demonstrates how to use priority inheritance mutexes with processes. | ||
| 36 | + * In fact, you would be better off to avoid scenarios in which a priority | ||
| 37 | + * inversion occurs if possible - this program tries to trigger them just | ||
| 38 | + * to show that it works. If you are having difficulty triggering an inversion, | ||
| 39 | + * merely increase the time that the low priority process sleeps while | ||
| 40 | + * holding the lock. (usleep); | ||
| 41 | + * Also note that you have to run as a user with permission to change | ||
| 42 | + * scheduling priorities. | ||
| 43 | + */ | ||
| 44 | + | ||
| 45 | + | ||
| 46 | + | ||
| 47 | + | ||
| 48 | + | ||
| 49 | + | ||
| 50 | +/* default time for low priority thread usleep */ | ||
| 51 | +useconds_t usleep_val = 500; | ||
| 52 | + | ||
| 53 | +pthread_mutex_t *resource; | ||
| 54 | + | ||
| 55 | +/* This records the state to determine whether a priority inversion occurred */ | ||
| 56 | +struct State { | ||
| 57 | + int low_owns_resource; | ||
| 58 | + int high_started; | ||
| 59 | + int high_owns_resource; | ||
| 60 | + int medium_started; | ||
| 61 | + int inversion; | ||
| 62 | + pthread_mutex_t *mutex; | ||
| 63 | +}; | ||
| 64 | + | ||
| 65 | +struct State *statep; | ||
| 66 | + | ||
| 67 | +const int policy = SCHED_FIFO; | ||
| 68 | +static int prio_min; /* Initialized for the minimum priority of policy */ | ||
| 69 | + | ||
| 70 | +static void usage(int error) | ||
| 71 | +{ | ||
| 72 | + printf("pip_stress V %1.2f\n", VERSION); | ||
| 73 | + printf("Usage:\n" | ||
| 74 | + "pip_stress <options>\n"\ | ||
| 75 | + "-h --help Show this help menu.\n" | ||
| 76 | + ); | ||
| 77 | + exit(error); | ||
| 78 | +} | ||
| 79 | + | ||
| 80 | +int main(int argc, char *argv[]) | ||
| 81 | +{ | ||
| 82 | + void *mptr; /* memory pointer */ | ||
| 83 | + pid_t pid1, pid2; | ||
| 84 | + cpu_set_t set, *setp = &set; | ||
| 85 | + int res; | ||
| 86 | + int *minimum_priority = (int*)&prio_min; | ||
| 87 | + | ||
| 88 | + for (;;) { | ||
| 89 | + struct option long_options[] = { | ||
| 90 | + { "help", no_argument, NULL, 'h' }, | ||
| 91 | + { NULL, 0, NULL, 0 }, | ||
| 92 | + }; | ||
| 93 | + | ||
| 94 | + int c = getopt_long(argc, argv, "s:h", long_options, NULL); | ||
| 95 | + if (c == -1) | ||
| 96 | + break; | ||
| 97 | + switch (c) { | ||
| 98 | + case 'h': | ||
| 99 | + usage(0); | ||
| 100 | + break; | ||
| 101 | + default: | ||
| 102 | + usage(1); | ||
| 103 | + break; | ||
| 104 | + }; | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + *minimum_priority = sched_get_priority_min(policy); | ||
| 108 | + | ||
| 109 | + if (check_privs()) | ||
| 110 | + exit(1); | ||
| 111 | + | ||
| 112 | + mptr = mmap_page(); /* Get a page of shared memory */ | ||
| 113 | + resource = (pthread_mutex_t*)mptr; /* point our lock to it */ | ||
| 114 | + mptr += sizeof(pthread_mutex_t); /* advance the memory pointer */ | ||
| 115 | + | ||
| 116 | + /* Initialize our mutex via the resource pointer */ | ||
| 117 | + init_shared_pthread_mutex(resource, PTHREAD_PRIO_INHERIT); | ||
| 118 | + | ||
| 119 | + statep = (struct State*)mptr; | ||
| 120 | + mptr += sizeof(struct State); | ||
| 121 | + | ||
| 122 | + init_state(); /* Initialize the state structure */ | ||
| 123 | + | ||
| 124 | + statep->mutex = (pthread_mutex_t*)mptr; /* point the next lock to it */ | ||
| 125 | + mptr += sizeof(pthread_mutex_t); /* advance the memory pointer */ | ||
| 126 | + | ||
| 127 | + /* Initialize our State mutex */ | ||
| 128 | + init_shared_pthread_mutex(statep->mutex, PTHREAD_PRIO_NONE); | ||
| 129 | + | ||
| 130 | + set_rt_prio(0, prio_min, policy); | ||
| 131 | + | ||
| 132 | + /* We restrict this program to the first cpu, inorder to increase | ||
| 133 | + * the likelihood of a priority inversion */ | ||
| 134 | + CPU_ZERO(setp); | ||
| 135 | + CPU_SET(0, setp); | ||
| 136 | + res = sched_setaffinity(0, sizeof(set), setp); | ||
| 137 | + if (res == -1) { | ||
| 138 | + int err = errno; | ||
| 139 | + err_msg("sched_setaffinity: "); | ||
| 140 | + err_exit(err, NULL); | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + pid1 = fork(); | ||
| 144 | + if (pid1 == -1) { | ||
| 145 | + perror("fork"); | ||
| 146 | + exit(1); | ||
| 147 | + } else if (pid1 != 0) { /* parent code */ | ||
| 148 | + low(pid1); | ||
| 149 | + } else { /* child code */ | ||
| 150 | + pid2 = fork(); /* parent code */ | ||
| 151 | + if (pid2 == -1) { | ||
| 152 | + perror("fork: "); | ||
| 153 | + exit(1); | ||
| 154 | + } else if (pid2 != 0) { /* parent code */ | ||
| 155 | + high(pid2); | ||
| 156 | + } else { /* child code */ | ||
| 157 | + medium(); | ||
| 158 | + } | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + exit(0); | ||
| 162 | +} | ||
| 163 | + | ||
| 164 | +/* Initialize the structure that tracks when a priority inversion occurs */ | ||
| 165 | +void init_state(void) | ||
| 166 | +{ | ||
| 167 | + /* Init the State structure */ | ||
| 168 | + statep->low_owns_resource = 0; | ||
| 169 | + statep->high_started = 0; | ||
| 170 | + statep->high_owns_resource = 0; | ||
| 171 | + statep->medium_started = 0; | ||
| 172 | + /* Assume an inversion will occur until proven false */ | ||
| 173 | + statep->inversion = 1; | ||
| 174 | +} | ||
| 175 | + | ||
| 176 | +/* @pid = high priority process pid */ | ||
| 177 | +void low(pid_t pid) | ||
| 178 | +{ | ||
| 179 | + int status; | ||
| 180 | + Pthread_mutex_lock(resource); | ||
| 181 | + Pthread_mutex_lock(statep->mutex); | ||
| 182 | + statep->low_owns_resource = 1; | ||
| 183 | + if (statep->high_owns_resource || | ||
| 184 | + statep->medium_started) { | ||
| 185 | + statep->inversion = 0; | ||
| 186 | + } | ||
| 187 | + Pthread_mutex_unlock(statep->mutex); | ||
| 188 | + usleep(usleep_val); | ||
| 189 | + Pthread_mutex_unlock(resource); | ||
| 190 | + waitpid(pid, &status, 0); | ||
| 191 | +} | ||
| 192 | + | ||
| 193 | +void medium(void) | ||
| 194 | +{ | ||
| 195 | + set_rt_prio(0, prio_min+1, policy); | ||
| 196 | + Pthread_mutex_lock(statep->mutex); | ||
| 197 | + statep->medium_started = 1; | ||
| 198 | + if (!statep->high_started) | ||
| 199 | + statep->inversion = 0; | ||
| 200 | + Pthread_mutex_unlock(statep->mutex); | ||
| 201 | + | ||
| 202 | + for(;;); /* infinite loop */ | ||
| 203 | +} | ||
| 204 | + | ||
| 205 | +/* @pid = medium priority process pid */ | ||
| 206 | +void high(pid_t pid) | ||
| 207 | +{ | ||
| 208 | + int status; | ||
| 209 | + set_rt_prio(0, prio_min+2, policy); | ||
| 210 | + | ||
| 211 | + /* Must come after raising the priority */ | ||
| 212 | + Pthread_mutex_lock(statep->mutex); | ||
| 213 | + statep->high_started = 1; | ||
| 214 | + Pthread_mutex_unlock(statep->mutex); | ||
| 215 | + | ||
| 216 | + Pthread_mutex_lock(resource); | ||
| 217 | + Pthread_mutex_lock(statep->mutex); | ||
| 218 | + statep->high_owns_resource = 1; | ||
| 219 | + if (!statep->low_owns_resource || !statep->medium_started) | ||
| 220 | + statep->inversion = 0; | ||
| 221 | + Pthread_mutex_unlock(statep->mutex); | ||
| 222 | + Pthread_mutex_unlock(resource); | ||
| 223 | + kill(pid, SIGKILL); /* kill the medium thread */ | ||
| 224 | + waitpid(pid, &status, 0); | ||
| 225 | + | ||
| 226 | + Pthread_mutex_lock(statep->mutex); | ||
| 227 | + | ||
| 228 | + if (statep->inversion) | ||
| 229 | + printf("Successfully used priority inheritance to handle an inversion\n"); | ||
| 230 | + else | ||
| 231 | + printf("No inversion incurred\n"); | ||
| 232 | + Pthread_mutex_unlock(statep->mutex); | ||
| 233 | +} | ||
| 234 | + | ||
| 235 | +/* mmap a page of anonymous shared memory */ | ||
| 236 | +void *mmap_page(void) | ||
| 237 | +{ | ||
| 238 | + void *mptr; | ||
| 239 | + long pgsize = sysconf(_SC_PAGE_SIZE); | ||
| 240 | + | ||
| 241 | + mptr = mmap(NULL, pgsize, PROTRW, MMAP_FLAGS, 0, 0); | ||
| 242 | + if (mptr == MAP_FAILED) { | ||
| 243 | + perror("In function mmap_page - mmap"); | ||
| 244 | + exit(1); | ||
| 245 | + } | ||
| 246 | + | ||
| 247 | + return mptr; | ||
| 248 | +} | ||
| 249 | + | ||
| 250 | +long process_shared_mutex_available(void) | ||
| 251 | +{ | ||
| 252 | + long res = -1; /* undefined */ | ||
| 253 | + | ||
| 254 | + res = sysconf(_SC_THREAD_PROCESS_SHARED); | ||
| 255 | + if (res == -1) { | ||
| 256 | + int err = errno; /* save the error number */ | ||
| 257 | + err_msg("%s: sysconf(_SC_THREAD_PROCESS_SHARED): ", __func__); | ||
| 258 | + err_exit(err, NULL); | ||
| 259 | + } | ||
| 260 | + | ||
| 261 | + | ||
| 262 | + | ||
| 263 | + return res; | ||
| 264 | +} | ||
| 265 | + | ||
| 266 | +void Pthread_mutexattr_init(pthread_mutexattr_t *attr) | ||
| 267 | +{ | ||
| 268 | + int err; | ||
| 269 | + err = pthread_mutexattr_init(attr); | ||
| 270 | + if (err) { | ||
| 271 | + err_msg("%s: pthread_mutexattr_init(): ", __func__); | ||
| 272 | + err_exit(err, NULL); | ||
| 273 | + } | ||
| 274 | +} | ||
| 275 | + | ||
| 276 | +void Pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared) | ||
| 277 | +{ | ||
| 278 | + int err; | ||
| 279 | + err = pthread_mutexattr_setpshared(attr, pshared); | ||
| 280 | + if (err) { | ||
| 281 | + err_msg("%s: pthread_mutexattr_setpshared(): ", __func__); | ||
| 282 | + err_exit(err, NULL); | ||
| 283 | + } | ||
| 284 | +} | ||
| 285 | + | ||
| 286 | +void Pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol) | ||
| 287 | +{ | ||
| 288 | + int err; | ||
| 289 | + err = pthread_mutexattr_setprotocol(attr, protocol); | ||
| 290 | + if (err) { | ||
| 291 | + err_msg("%s: pthread_mutexattr_setprotocol(): ", __func__); | ||
| 292 | + err_exit(err, NULL); | ||
| 293 | + } | ||
| 294 | +} | ||
| 295 | + | ||
| 296 | +void Pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr) | ||
| 297 | +{ | ||
| 298 | + int err; | ||
| 299 | + err = pthread_mutex_init(mutex, attr); | ||
| 300 | + if (err) { | ||
| 301 | + err_msg("%s: pthread_mutex_init(): ", __func__); | ||
| 302 | + err_exit(err, NULL); | ||
| 303 | + } | ||
| 304 | +} | ||
| 305 | + | ||
| 306 | +void Pthread_mutex_lock(pthread_mutex_t *mutex) | ||
| 307 | +{ | ||
| 308 | + int err; | ||
| 309 | + err = pthread_mutex_lock(mutex); | ||
| 310 | + if (err) { | ||
| 311 | + err_msg("%s: pthread_mutex_lock(): ", __func__); | ||
| 312 | + err_exit(err, NULL); | ||
| 313 | + } | ||
| 314 | +} | ||
| 315 | + | ||
| 316 | +void Pthread_mutex_unlock(pthread_mutex_t *mutex) | ||
| 317 | +{ | ||
| 318 | + int err; | ||
| 319 | + err = pthread_mutex_unlock(mutex); | ||
| 320 | + if (err) { | ||
| 321 | + err_msg("%s: pthread_mutex_unlock(): ", __func__); | ||
| 322 | + err_exit(err, NULL); | ||
| 323 | + } | ||
| 324 | +} | ||
| 325 | + | ||
| 326 | +void init_shared_pthread_mutex(pthread_mutex_t *mutex, int protocol) | ||
| 327 | +{ | ||
| 328 | + pthread_mutexattr_t attr; | ||
| 329 | + | ||
| 330 | + process_shared_mutex_available(); | ||
| 331 | + | ||
| 332 | + Pthread_mutexattr_init(&attr); | ||
| 333 | + Pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); | ||
| 334 | + Pthread_mutexattr_setprotocol(&attr, protocol); | ||
| 335 | + | ||
| 336 | + Pthread_mutex_init(mutex, &attr); | ||
| 337 | +} | ||
| 338 | + | ||
| 339 | +/* Set the priority and policy of a process */ | ||
| 340 | +int set_rt_prio(pid_t pid, int prio, int policy) | ||
| 341 | +{ | ||
| 342 | + int err; | ||
| 343 | + struct sched_param param; | ||
| 344 | + struct sched_param *pparam = ¶m; | ||
| 345 | + pparam->sched_priority = prio; | ||
| 346 | + err = sched_setscheduler(pid, policy, pparam); | ||
| 347 | + if (err) { | ||
| 348 | + err = errno; /* save the errno */ | ||
| 349 | + err_msg_n(err, "%s: sched_setscheduler(): ", __func__); | ||
| 350 | + err_msg("%s: prio = %d\n", __func__, prio); | ||
| 351 | + err_msg("%s: pparam->sched_priority = %d\n", __func__, pparam->sched_priority); | ||
| 352 | + err_msg("%s: policy = %d\n", __func__, policy); | ||
| 353 | + } | ||
| 354 | + return err; /* 0 on success */ | ||
| 355 | +} | ||
| 356 | + | ||
| 357 | +int get_rt_prio(pid_t pid) | ||
| 358 | +{ | ||
| 359 | + int err; | ||
| 360 | + struct sched_param param; | ||
| 361 | + err = sched_getparam(pid, ¶m); | ||
| 362 | + if (err) { | ||
| 363 | + err = errno; /* save the errno */ | ||
| 364 | + err_msg_n(err, "%s: get_rt_prio(): ", __func__); | ||
| 365 | + return -1; | ||
| 366 | + } | ||
| 367 | + return param.sched_priority; | ||
| 368 | +} | ||
| @@ -0,0 +1,128 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +/* | ||
| 3 | + * sigtest - simple little program to verify signal behavior | ||
| 4 | + * Copyright (C) 2006, 2007 Clark Williams <williams@redhat.com> | ||
| 5 | +*/ | ||
| 6 | + | ||
| 7 | + | ||
| 8 | + | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | +int | ||
| 19 | +setup_timer(timer_t *timer) | ||
| 20 | +{ | ||
| 21 | + int status; | ||
| 22 | + struct sigevent sigev; | ||
| 23 | + | ||
| 24 | + memset(&sigev, 0, sizeof(sigev)); | ||
| 25 | + sigev.sigev_notify = SIGEV_SIGNAL; | ||
| 26 | + sigev.sigev_signo = TIMER_SIGNAL; | ||
| 27 | + status = timer_create(CLOCK_MONOTONIC, &sigev, timer); | ||
| 28 | + if (status) { | ||
| 29 | + fprintf(stderr,"error from timer_create: %s\n", strerror(errno)); | ||
| 30 | + return FAILURE; | ||
| 31 | + } | ||
| 32 | + return SUCCESS; | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +int | ||
| 36 | +start_timer(timer_t t, int sec, int nsec) | ||
| 37 | +{ | ||
| 38 | + int status; | ||
| 39 | + struct itimerspec it; | ||
| 40 | + | ||
| 41 | + // set up as a one-shot | ||
| 42 | + memset(&it, 0, sizeof(it)); | ||
| 43 | + it.it_value.tv_sec = sec; | ||
| 44 | + it.it_value.tv_nsec = nsec; | ||
| 45 | + status = timer_settime(t, 0, &it, NULL); | ||
| 46 | + if (status) | ||
| 47 | + fprintf(stderr,"starting timer: %s\n", strerror(errno)); | ||
| 48 | + return status; | ||
| 49 | +} | ||
| 50 | + | ||
| 51 | +int | ||
| 52 | +wait_for_signal(void) | ||
| 53 | +{ | ||
| 54 | + int signo; | ||
| 55 | + sigset_t sigset; | ||
| 56 | + | ||
| 57 | + if (sigemptyset(&sigset)) { | ||
| 58 | + fprintf(stderr,"creating empty signal wait set: %s\n", strerror(errno)); | ||
| 59 | + return -1; | ||
| 60 | + } | ||
| 61 | + if (sigaddset(&sigset, SIGINT)) { | ||
| 62 | + fprintf(stderr,"adding SIGINT to signal set: %s\n", strerror(errno)); | ||
| 63 | + return -1; | ||
| 64 | + } | ||
| 65 | + if (sigaddset(&sigset, TIMER_SIGNAL)) { | ||
| 66 | + fprintf(stderr,"adding TIMER_SIGNAL to signal set: %s\n", strerror(errno)); | ||
| 67 | + return -1; | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + if (sigwait(&sigset, &signo)) { | ||
| 71 | + fprintf(stderr,"waiting for signal: %s\n", strerror(errno)); | ||
| 72 | + return -1; | ||
| 73 | + } | ||
| 74 | + return signo; | ||
| 75 | +} | ||
| 76 | + | ||
| 77 | +int | ||
| 78 | +block_signals(void) | ||
| 79 | +{ | ||
| 80 | + int status; | ||
| 81 | + sigset_t sigset; | ||
| 82 | + | ||
| 83 | + // mask off all signals | ||
| 84 | + status = sigfillset(&sigset); | ||
| 85 | + if (status) { | ||
| 86 | + fprintf(stderr,"setting up full signal set %d\n", status); | ||
| 87 | + return FAILURE; | ||
| 88 | + } | ||
| 89 | + status = pthread_sigmask(SIG_BLOCK, &sigset, NULL); | ||
| 90 | + if (status) { | ||
| 91 | + fprintf(stderr,"setting signal mask: %d\n", status); | ||
| 92 | + return FAILURE; | ||
| 93 | + } | ||
| 94 | + return SUCCESS; | ||
| 95 | +} | ||
| 96 | + | ||
| 97 | +int | ||
| 98 | +main(int argc, char **argv) | ||
| 99 | +{ | ||
| 100 | + int status; | ||
| 101 | + timer_t timer; | ||
| 102 | + unsigned long count = 0; | ||
| 103 | + int stop_test = 0; | ||
| 104 | + | ||
| 105 | + block_signals(); | ||
| 106 | + setup_timer(&timer); | ||
| 107 | + printf("Press Ctrl-C to stop\n"); | ||
| 108 | + while (stop_test == 0) { | ||
| 109 | + if (start_timer(timer, 0, 500000000)) { | ||
| 110 | + stop_test = 1; | ||
| 111 | + continue; | ||
| 112 | + } | ||
| 113 | + status = wait_for_signal(); | ||
| 114 | + if (status == SIGINT) { | ||
| 115 | + fputs("\033[1B", stdout); | ||
| 116 | + stop_test = 1; | ||
| 117 | + } | ||
| 118 | + else if (status == TIMER_SIGNAL) { | ||
| 119 | + printf("count: %lu\n", ++count); | ||
| 120 | + fputs("\033[1A", stdout); | ||
| 121 | + } | ||
| 122 | + else { | ||
| 123 | + fprintf(stderr, "WTF?\n"); | ||
| 124 | + return -1; | ||
| 125 | + } | ||
| 126 | + } | ||
| 127 | + return 0; | ||
| 128 | +} | ||
| @@ -0,0 +1,665 @@ | |||
| 1 | +// SPDX-License-Identifier: LGPL-2.1-or-later | ||
| 2 | +/* | ||
| 3 | + * Classic Priority Inversion deadlock test case | ||
| 4 | + * Copyright (C) 2006 Free Software Foundation, Inc. | ||
| 5 | + * This file is part of the GNU C Library. | ||
| 6 | + * Contributed by Clark Williams<williams@redhat.com>, 2006 | ||
| 7 | + */ | ||
| 8 | + | ||
| 9 | +/* This program tests Priority Inheritance mutexes and their ability | ||
| 10 | + to avoid Priority Inversion deadlocks | ||
| 11 | + | ||
| 12 | + The basic premise here is to set up a deadlock scenario and confirm that PI | ||
| 13 | + mutexes resolve the situation. Three worker threads will be created from the | ||
| 14 | + main thread: low, medium and high priority threads that use SCHED_FIFO as | ||
| 15 | + their scheduling policy. The low priority thread claims a mutex and then | ||
| 16 | + starts "working". The medium priority thread starts and preempts the low | ||
| 17 | + priority thread. Then the high priority thread runs and attempts to claim | ||
| 18 | + the mutex owned by the low priority thread. Without priority inheritance, | ||
| 19 | + this will deadlock the program. With priority inheritance, the low priority | ||
| 20 | + thread receives a priority boost, finishes it's "work" and releases the mutex, | ||
| 21 | + which allows the high priority thread to run and finish and then the medium | ||
| 22 | + priority thread finishes. | ||
| 23 | + | ||
| 24 | + That's the theory, anyway... | ||
| 25 | + | ||
| 26 | + CW - 2006 */ | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + | ||
| 35 | + | ||
| 36 | + | ||
| 37 | + | ||
| 38 | +/* test timeout */ | ||
| 39 | + | ||
| 40 | + | ||
| 41 | +/* determine if the C library supports Priority Inheritance mutexes */ | ||
| 42 | + | ||
| 43 | + | ||
| 44 | + | ||
| 45 | + | ||
| 46 | + | ||
| 47 | + | ||
| 48 | +int use_pi_mutex = HAVE_PI_MUTEX; | ||
| 49 | + | ||
| 50 | + | ||
| 51 | + | ||
| 52 | + | ||
| 53 | +/* the number of times we cause a priority inversion situation */ | ||
| 54 | +int inversions = 1; | ||
| 55 | + | ||
| 56 | +/* the file handle used by the error reporting routine */ | ||
| 57 | +FILE *errout; | ||
| 58 | + | ||
| 59 | + | ||
| 60 | + | ||
| 61 | + | ||
| 62 | + { "no-pi", no_argument, NULL, 1003}, | ||
| 63 | + | ||
| 64 | + case 1002: verbose=1; break; \ | ||
| 65 | + case 1003: use_pi_mutex = 0; break; | ||
| 66 | + | ||
| 67 | +int verbose = 0; | ||
| 68 | + | ||
| 69 | +/* define priorities for the threads */ | ||
| 70 | + | ||
| 71 | + | ||
| 72 | + | ||
| 73 | + | ||
| 74 | + | ||
| 75 | + | ||
| 76 | +enum thread_names | ||
| 77 | +{ LOW = 0, MEDIUM, HIGH, NUM_WORKER_THREADS }; | ||
| 78 | + | ||
| 79 | +pthread_mutex_t mutex; | ||
| 80 | +pthread_mutexattr_t mutex_attr; | ||
| 81 | + | ||
| 82 | +pthread_barrier_t all_threads_ready; | ||
| 83 | +pthread_barrier_t all_threads_done; | ||
| 84 | + | ||
| 85 | +/* state barriers */ | ||
| 86 | +pthread_barrier_t start_barrier; | ||
| 87 | +pthread_barrier_t locked_barrier; | ||
| 88 | +pthread_barrier_t elevate_barrier; | ||
| 89 | +pthread_barrier_t finish_barrier; | ||
| 90 | + | ||
| 91 | +volatile int deadlocked = 0; | ||
| 92 | +volatile int high_has_run = 0; | ||
| 93 | +volatile int low_unlocked = 0; | ||
| 94 | + | ||
| 95 | +cpu_set_t cpu_mask; | ||
| 96 | + | ||
| 97 | +struct thread_parameters | ||
| 98 | +{ | ||
| 99 | + pthread_t tid; | ||
| 100 | + int inversions; | ||
| 101 | +} thread_parameters[NUM_WORKER_THREADS]; | ||
| 102 | + | ||
| 103 | +/* forward prototypes */ | ||
| 104 | +void *low_priority (void *arg); | ||
| 105 | +void *med_priority (void *arg); | ||
| 106 | +void *high_priority (void *arg); | ||
| 107 | +int setup_thread_attr (pthread_attr_t * attr, int prio, cpu_set_t * mask); | ||
| 108 | +int set_cpu_affinity (cpu_set_t * mask); | ||
| 109 | +void error (char *, ...); | ||
| 110 | +void info (char *, ...); | ||
| 111 | + | ||
| 112 | +void | ||
| 113 | +prepare (int argc, char **argv) | ||
| 114 | +{ | ||
| 115 | + struct sched_param thread_param; | ||
| 116 | + int max = sched_get_priority_max (SCHED_FIFO); | ||
| 117 | + int status; | ||
| 118 | + | ||
| 119 | + errout = stdout; | ||
| 120 | + | ||
| 121 | + /* boost test skeleton to max priority (so we keep running) :) */ | ||
| 122 | + thread_param.sched_priority = SKEL_PRIO (max); | ||
| 123 | + status = pthread_setschedparam (pthread_self (), SCHED_FIFO, &thread_param); | ||
| 124 | + if (status) | ||
| 125 | + error ("main: boosting to max priority: 0x%x\n", status); | ||
| 126 | +} | ||
| 127 | + | ||
| 128 | + | ||
| 129 | + | ||
| 130 | +int | ||
| 131 | +initialize_barriers (void) | ||
| 132 | +{ | ||
| 133 | + int status; | ||
| 134 | + | ||
| 135 | + status = | ||
| 136 | + pthread_barrier_init (&all_threads_ready, NULL, NUM_WORKER_THREADS + 1); | ||
| 137 | + if (status) | ||
| 138 | + { | ||
| 139 | + error ("initialize_barriers: failed to initialize all_threads_ready\n"); | ||
| 140 | + return FAILURE; | ||
| 141 | + } | ||
| 142 | + status = | ||
| 143 | + pthread_barrier_init (&all_threads_done, NULL, NUM_WORKER_THREADS + 1); | ||
| 144 | + if (status) | ||
| 145 | + { | ||
| 146 | + error ("initialize_barriers: failed to initialize all_threads_done\n"); | ||
| 147 | + return FAILURE; | ||
| 148 | + } | ||
| 149 | + status = pthread_barrier_init (&start_barrier, NULL, NUM_WORKER_THREADS); | ||
| 150 | + if (status) | ||
| 151 | + { | ||
| 152 | + error ("initialize_barriers: failed to initialize start_barrier\n"); | ||
| 153 | + return FAILURE; | ||
| 154 | + } | ||
| 155 | + status = pthread_barrier_init (&locked_barrier, NULL, 2); | ||
| 156 | + if (status) | ||
| 157 | + { | ||
| 158 | + error ("initializing_barriers: failed to intialize locked_barrier\n"); | ||
| 159 | + return FAILURE; | ||
| 160 | + } | ||
| 161 | + status = pthread_barrier_init (&elevate_barrier, NULL, 2); | ||
| 162 | + if (status) | ||
| 163 | + { | ||
| 164 | + error ("initializing_barriers: failed to initialize elevate_barrier\n"); | ||
| 165 | + return FAILURE; | ||
| 166 | + } | ||
| 167 | + status = pthread_barrier_init (&finish_barrier, NULL, NUM_WORKER_THREADS); | ||
| 168 | + if (status) | ||
| 169 | + { | ||
| 170 | + error ("initializing_barriers: failed to initialize finish_barrier\n"); | ||
| 171 | + return FAILURE; | ||
| 172 | + } | ||
| 173 | + return SUCCESS; | ||
| 174 | +} | ||
| 175 | + | ||
| 176 | +void | ||
| 177 | +cleanup (void) | ||
| 178 | +{ | ||
| 179 | + int i; | ||
| 180 | + int status; | ||
| 181 | + for (i = 0; i < NUM_WORKER_THREADS; i++) | ||
| 182 | + { | ||
| 183 | + status = pthread_kill (thread_parameters[i].tid, SIGQUIT); | ||
| 184 | + if (status) | ||
| 185 | + error ("cleanup: error sending SIGQUIT to thread %d\n", | ||
| 186 | + thread_parameters[i].tid); | ||
| 187 | + } | ||
| 188 | +} | ||
| 189 | + | ||
| 190 | +void | ||
| 191 | +handler (int signal) | ||
| 192 | +{ | ||
| 193 | + info ("handler: %s fired\n", sys_siglist[signal]); | ||
| 194 | + cleanup (); | ||
| 195 | + if (signal == SIGALRM) | ||
| 196 | + { | ||
| 197 | + error ("handler: DEADLOCK detected!\n"); | ||
| 198 | + deadlocked = 1; | ||
| 199 | + } | ||
| 200 | +} | ||
| 201 | + | ||
| 202 | +int | ||
| 203 | +MAIN (int argc, char **argv) | ||
| 204 | +{ | ||
| 205 | + int status; | ||
| 206 | + int prio_max; | ||
| 207 | + pthread_attr_t thread_attr; | ||
| 208 | + struct sched_param thread_param; | ||
| 209 | + | ||
| 210 | + errout = stdout; | ||
| 211 | + | ||
| 212 | + /* initialize default attributes for the mutex */ | ||
| 213 | + status = pthread_mutexattr_init (&mutex_attr); | ||
| 214 | + if (status) | ||
| 215 | + { | ||
| 216 | + error ("main: initializing mutex attribute: 0x%x\n", status); | ||
| 217 | + return FAILURE; | ||
| 218 | + } | ||
| 219 | + | ||
| 220 | + if (use_pi_mutex) | ||
| 221 | + { | ||
| 222 | + /* set priority inheritance attribute for mutex */ | ||
| 223 | + status = pthread_mutexattr_setprotocol (&mutex_attr, | ||
| 224 | + PTHREAD_PRIO_INHERIT); | ||
| 225 | + if (status) | ||
| 226 | + { | ||
| 227 | + error ("main: setting mutex attribute policy: 0x%x\n", status); | ||
| 228 | + return FAILURE; | ||
| 229 | + } | ||
| 230 | + } | ||
| 231 | + info ("main: Priority Inheritance turned %s\n", | ||
| 232 | + use_pi_mutex ? "on" : "off"); | ||
| 233 | + | ||
| 234 | + /* initialize our mutex */ | ||
| 235 | + status = pthread_mutex_init (&mutex, &mutex_attr); | ||
| 236 | + if (status) | ||
| 237 | + { | ||
| 238 | + error ("main: initializing mutex: 0x%x\n", status); | ||
| 239 | + return FAILURE; | ||
| 240 | + } | ||
| 241 | + | ||
| 242 | + /* set up our barriers */ | ||
| 243 | + status = initialize_barriers (); | ||
| 244 | + if (status) | ||
| 245 | + return FAILURE; | ||
| 246 | + | ||
| 247 | + /* set up CPU affinity so we only use one processor */ | ||
| 248 | + if (set_cpu_affinity (&cpu_mask)) | ||
| 249 | + return FAILURE; | ||
| 250 | + | ||
| 251 | + /* boost us to max priority (so we keep running) :) */ | ||
| 252 | + prio_max = sched_get_priority_max (SCHED_FIFO); | ||
| 253 | + thread_param.sched_priority = MAIN_PRIO (prio_max); | ||
| 254 | + status = pthread_setschedparam (pthread_self (), SCHED_FIFO, &thread_param); | ||
| 255 | + if (status) | ||
| 256 | + { | ||
| 257 | + error ("main: boosting to max priority: 0x%x\n", status); | ||
| 258 | + /* Don't fail if we don't have the right privledges */ | ||
| 259 | + return SUCCESS; | ||
| 260 | + } | ||
| 261 | + | ||
| 262 | + /* start the low priority thread */ | ||
| 263 | + info ("main: creating low priority thread\n"); | ||
| 264 | + setup_thread_attr (&thread_attr, LOW_PRIO (prio_max), &cpu_mask); | ||
| 265 | + thread_parameters[LOW].inversions = inversions; | ||
| 266 | + status = pthread_create (&thread_parameters[LOW].tid, | ||
| 267 | + &thread_attr, | ||
| 268 | + low_priority, &thread_parameters[LOW]); | ||
| 269 | + if (status != 0) | ||
| 270 | + { | ||
| 271 | + error ("main: creating low_priority thread: 0x%x\n", status); | ||
| 272 | + return FAILURE; | ||
| 273 | + } | ||
| 274 | + | ||
| 275 | + /* create the medium priority thread */ | ||
| 276 | + info ("main: creating medium priority thread\n"); | ||
| 277 | + setup_thread_attr (&thread_attr, MED_PRIO (prio_max), &cpu_mask); | ||
| 278 | + thread_parameters[MEDIUM].inversions = inversions; | ||
| 279 | + status = pthread_create (&thread_parameters[MEDIUM].tid, | ||
| 280 | + &thread_attr, | ||
| 281 | + med_priority, &thread_parameters[MEDIUM]); | ||
| 282 | + if (status != 0) | ||
| 283 | + { | ||
| 284 | + error ("main: creating med_priority thread: 0x%x\n", status); | ||
| 285 | + return FAILURE; | ||
| 286 | + } | ||
| 287 | + | ||
| 288 | + /* create the high priority thread */ | ||
| 289 | + info ("main: creating high priority thread\n"); | ||
| 290 | + if (setup_thread_attr (&thread_attr, HIGH_PRIO (prio_max), &cpu_mask)) | ||
| 291 | + return FAILURE; | ||
| 292 | + thread_parameters[HIGH].inversions = inversions; | ||
| 293 | + status = pthread_create (&thread_parameters[HIGH].tid, | ||
| 294 | + &thread_attr, | ||
| 295 | + high_priority, &thread_parameters[HIGH]); | ||
| 296 | + if (status != 0) | ||
| 297 | + { | ||
| 298 | + error ("main: creating high_priority thread: 0x%x\n", status); | ||
| 299 | + cleanup (); | ||
| 300 | + return FAILURE; | ||
| 301 | + } | ||
| 302 | + | ||
| 303 | + signal (SIGINT, handler); | ||
| 304 | + | ||
| 305 | + info ("main: releasing all threads\n"); | ||
| 306 | + status = pthread_barrier_wait (&all_threads_ready); | ||
| 307 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 308 | + { | ||
| 309 | + error ("main: pthread_barrier_wait(all_threads_ready): 0x%x\n", status); | ||
| 310 | + cleanup (); | ||
| 311 | + return FAILURE; | ||
| 312 | + } | ||
| 313 | + info ("main: all threads initialized\n"); | ||
| 314 | + | ||
| 315 | + info ("main: waiting for threads to finish\n"); | ||
| 316 | + | ||
| 317 | + status = pthread_barrier_wait (&all_threads_done); | ||
| 318 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 319 | + { | ||
| 320 | + error ("main: pthread_barrier_wait(all_threads_done): 0x%x\n", status); | ||
| 321 | + cleanup (); | ||
| 322 | + return FAILURE; | ||
| 323 | + } | ||
| 324 | + info ("main: all threads terminated!\n"); | ||
| 325 | + if (deadlocked) | ||
| 326 | + { | ||
| 327 | + info ("main: test failed\n"); | ||
| 328 | + return FAILURE; | ||
| 329 | + } | ||
| 330 | + info ("main: test passed\n"); | ||
| 331 | + return SUCCESS; | ||
| 332 | +} | ||
| 333 | + | ||
| 334 | + | ||
| 335 | +int | ||
| 336 | +setup_thread_attr (pthread_attr_t * attr, int prio, cpu_set_t * mask) | ||
| 337 | +{ | ||
| 338 | + int status; | ||
| 339 | + struct sched_param thread_param; | ||
| 340 | + | ||
| 341 | + status = pthread_attr_init (attr); | ||
| 342 | + if (status) | ||
| 343 | + { | ||
| 344 | + error ("setup_thread_attr: initializing thread attribute: 0x%x\n", | ||
| 345 | + status); | ||
| 346 | + return FAILURE; | ||
| 347 | + } | ||
| 348 | + status = pthread_attr_setschedpolicy (attr, SCHED_FIFO); | ||
| 349 | + if (status) | ||
| 350 | + { | ||
| 351 | + error | ||
| 352 | + ("setup_thread_attr: setting attribute policy to SCHED_FIFO: 0x%x\n", | ||
| 353 | + status); | ||
| 354 | + return FAILURE; | ||
| 355 | + } | ||
| 356 | + status = pthread_attr_setinheritsched (attr, PTHREAD_EXPLICIT_SCHED); | ||
| 357 | + if (status) | ||
| 358 | + { | ||
| 359 | + error | ||
| 360 | + ("setup_thread_attr: setting explicit scheduling inheritance: 0x%x\n", | ||
| 361 | + status); | ||
| 362 | + return FAILURE; | ||
| 363 | + } | ||
| 364 | + thread_param.sched_priority = prio; | ||
| 365 | + status = pthread_attr_setschedparam (attr, &thread_param); | ||
| 366 | + if (status) | ||
| 367 | + { | ||
| 368 | + error ("setup_thread_attr: setting scheduler param: 0x%x\n", status); | ||
| 369 | + return FAILURE; | ||
| 370 | + } | ||
| 371 | + status = pthread_attr_setaffinity_np (attr, sizeof (cpu_set_t), mask); | ||
| 372 | + if (status) | ||
| 373 | + { | ||
| 374 | + error ("setup_thread_attr: setting affinity attribute: 0x%x\n", status); | ||
| 375 | + return FAILURE; | ||
| 376 | + } | ||
| 377 | + return SUCCESS; | ||
| 378 | +} | ||
| 379 | + | ||
| 380 | +int | ||
| 381 | +set_cpu_affinity (cpu_set_t * cpu_set) | ||
| 382 | +{ | ||
| 383 | + int status, i; | ||
| 384 | + cpu_set_t current_mask, new_mask; | ||
| 385 | + | ||
| 386 | + /* Now set our CPU affinity to only run one one processor */ | ||
| 387 | + status = sched_getaffinity (0, sizeof (cpu_set_t), ¤t_mask); | ||
| 388 | + if (status) | ||
| 389 | + { | ||
| 390 | + error ("set_cpu_affinity: getting CPU affinity mask: 0x%x\n", status); | ||
| 391 | + return FAILURE; | ||
| 392 | + } | ||
| 393 | + for (i = 0; i < sizeof (cpu_set_t) * 8; i++) | ||
| 394 | + { | ||
| 395 | + if (CPU_ISSET (i, ¤t_mask)) | ||
| 396 | + break; | ||
| 397 | + } | ||
| 398 | + if (i >= sizeof (cpu_set_t) * 8) | ||
| 399 | + { | ||
| 400 | + error ("set_cpu_affinity: No schedulable CPU found!\n"); | ||
| 401 | + return FAILURE; | ||
| 402 | + } | ||
| 403 | + CPU_ZERO (&new_mask); | ||
| 404 | + CPU_SET (i, &new_mask); | ||
| 405 | + status = sched_setaffinity (0, sizeof (cpu_set_t), &new_mask); | ||
| 406 | + if (status) | ||
| 407 | + { | ||
| 408 | + error ("set_cpu_affinity: setting CPU affinity mask: 0x%x\n", status); | ||
| 409 | + return FAILURE; | ||
| 410 | + } | ||
| 411 | + info ("set_cpu_affinity: using processr %d\n", i); | ||
| 412 | + *cpu_set = new_mask; | ||
| 413 | + return SUCCESS; | ||
| 414 | +} | ||
| 415 | + | ||
| 416 | +void | ||
| 417 | +report_threadinfo (char *name) | ||
| 418 | +{ | ||
| 419 | + int status; | ||
| 420 | + struct sched_param thread_param; | ||
| 421 | + int thread_policy; | ||
| 422 | + | ||
| 423 | + status = | ||
| 424 | + pthread_getschedparam (pthread_self (), &thread_policy, &thread_param); | ||
| 425 | + if (status) | ||
| 426 | + { | ||
| 427 | + error ("report_threadinfo: failed to get scheduler param: 0x%x\n", | ||
| 428 | + status); | ||
| 429 | + pthread_mutex_unlock (&mutex); | ||
| 430 | + exit (FAILURE); | ||
| 431 | + } | ||
| 432 | + info ("%s: running as %s thread at priority %d\n", | ||
| 433 | + name, thread_policy == SCHED_FIFO ? "FIFO" : | ||
| 434 | + thread_policy == SCHED_RR ? "RR" : "OTHER", | ||
| 435 | + thread_param.sched_priority); | ||
| 436 | +} | ||
| 437 | + | ||
| 438 | +void * | ||
| 439 | +low_priority (void *arg) | ||
| 440 | +{ | ||
| 441 | + int status; | ||
| 442 | + struct thread_parameters *p = (struct thread_parameters *) arg; | ||
| 443 | + | ||
| 444 | + report_threadinfo ("low_priority"); | ||
| 445 | + | ||
| 446 | + info ("low_priority: entering ready state\n"); | ||
| 447 | + | ||
| 448 | + /* wait for all threads to be ready */ | ||
| 449 | + status = pthread_barrier_wait (&all_threads_ready); | ||
| 450 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 451 | + { | ||
| 452 | + error ("low_priority: pthread_barrier_wait(all_threads_ready): %x", | ||
| 453 | + status); | ||
| 454 | + return NULL; | ||
| 455 | + } | ||
| 456 | + | ||
| 457 | + info ("low_priority: starting inversion loop (%d)\n", p->inversions); | ||
| 458 | + while (p->inversions-- > 0) | ||
| 459 | + { | ||
| 460 | + /* initial state */ | ||
| 461 | + info ("low_priority: entering start wait (%d)\n", p->inversions + 1); | ||
| 462 | + status = pthread_barrier_wait (&start_barrier); | ||
| 463 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 464 | + { | ||
| 465 | + error ("low_priority: pthread_barrier_wait(start): %x\n", status); | ||
| 466 | + return NULL; | ||
| 467 | + } | ||
| 468 | + info ("low_priority: claiming mutex\n"); | ||
| 469 | + pthread_mutex_lock (&mutex); | ||
| 470 | + info ("low_priority: mutex locked\n"); | ||
| 471 | + | ||
| 472 | + info ("low_priority: entering locked wait\n"); | ||
| 473 | + status = pthread_barrier_wait (&locked_barrier); | ||
| 474 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 475 | + { | ||
| 476 | + error ("low_priority: pthread_barrier_wait(locked): %x\n", status); | ||
| 477 | + return NULL; | ||
| 478 | + } | ||
| 479 | + | ||
| 480 | + /* wait for priority boost */ | ||
| 481 | + info ("low_priority: entering elevated wait\n"); | ||
| 482 | + low_unlocked = 0; /* prevent race with med_priority */ | ||
| 483 | + status = pthread_barrier_wait (&elevate_barrier); | ||
| 484 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 485 | + { | ||
| 486 | + error ("low_priority: pthread_barrier_wait(elevate): %x\n", status); | ||
| 487 | + return NULL; | ||
| 488 | + } | ||
| 489 | + low_unlocked = 1; | ||
| 490 | + | ||
| 491 | + /* release the mutex */ | ||
| 492 | + info ("low_priority: unlocking mutex\n"); | ||
| 493 | + pthread_mutex_unlock (&mutex); | ||
| 494 | + | ||
| 495 | + /* finish state */ | ||
| 496 | + info ("low_priority: entering finish wait\n"); | ||
| 497 | + status = pthread_barrier_wait (&finish_barrier); | ||
| 498 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 499 | + { | ||
| 500 | + error ("low_priority: pthread_barrier_wait(elevate): %x\n", status); | ||
| 501 | + return NULL; | ||
| 502 | + } | ||
| 503 | + | ||
| 504 | + } | ||
| 505 | + /* let main know we're done */ | ||
| 506 | + info ("low_priority: entering exit state\n"); | ||
| 507 | + status = pthread_barrier_wait (&all_threads_done); | ||
| 508 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 509 | + { | ||
| 510 | + error ("low_priority: pthread_barrier_wait(all_threads_done): %x", | ||
| 511 | + status); | ||
| 512 | + return NULL; | ||
| 513 | + } | ||
| 514 | + info ("low_priority: exiting\n"); | ||
| 515 | + return NULL; | ||
| 516 | +} | ||
| 517 | + | ||
| 518 | +void * | ||
| 519 | +med_priority (void *arg) | ||
| 520 | +{ | ||
| 521 | + int status; | ||
| 522 | + struct thread_parameters *p = (struct thread_parameters *) arg; | ||
| 523 | + | ||
| 524 | + report_threadinfo ("med_priority"); | ||
| 525 | + | ||
| 526 | + info ("med_priority: entering ready state\n"); | ||
| 527 | + /* wait for all threads to be ready */ | ||
| 528 | + status = pthread_barrier_wait (&all_threads_ready); | ||
| 529 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 530 | + { | ||
| 531 | + error ("med_priority: pthread_barrier_wait(all_threads_ready): %x", | ||
| 532 | + status); | ||
| 533 | + return NULL; | ||
| 534 | + } | ||
| 535 | + | ||
| 536 | + info ("med_priority: starting inversion loop (%d)\n", p->inversions); | ||
| 537 | + while (p->inversions-- > 0) | ||
| 538 | + { | ||
| 539 | + /* start state */ | ||
| 540 | + info ("med_priority: entering start state (%d)\n", p->inversions + 1); | ||
| 541 | + status = pthread_barrier_wait (&start_barrier); | ||
| 542 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 543 | + { | ||
| 544 | + error ("med_priority: pthread_barrier_wait(start): %x", status); | ||
| 545 | + return NULL; | ||
| 546 | + } | ||
| 547 | + info ("med_priority: entering elevate state\n"); | ||
| 548 | + do | ||
| 549 | + { | ||
| 550 | + status = pthread_barrier_wait (&elevate_barrier); | ||
| 551 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 552 | + { | ||
| 553 | + error ("med_priority: pthread_barrier_wait(elevate): %x", | ||
| 554 | + status); | ||
| 555 | + return NULL; | ||
| 556 | + } | ||
| 557 | + } | ||
| 558 | + while (!high_has_run && !low_unlocked); | ||
| 559 | + info ("med_priority: entering finish state\n"); | ||
| 560 | + status = pthread_barrier_wait (&finish_barrier); | ||
| 561 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 562 | + { | ||
| 563 | + error ("med_priority: pthread_barrier_wait(finished): %x", status); | ||
| 564 | + return NULL; | ||
| 565 | + } | ||
| 566 | + } | ||
| 567 | + | ||
| 568 | + info ("med_priority: entering exit state\n"); | ||
| 569 | + status = pthread_barrier_wait (&all_threads_done); | ||
| 570 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 571 | + { | ||
| 572 | + error ("med_priority: pthread_barrier_wait(all_threads_done): %x", | ||
| 573 | + status); | ||
| 574 | + return NULL; | ||
| 575 | + } | ||
| 576 | + info ("med_priority: exiting\n"); | ||
| 577 | + return NULL; | ||
| 578 | +} | ||
| 579 | + | ||
| 580 | +void * | ||
| 581 | +high_priority (void *arg) | ||
| 582 | +{ | ||
| 583 | + int status; | ||
| 584 | + struct thread_parameters *p = (struct thread_parameters *) arg; | ||
| 585 | + | ||
| 586 | + report_threadinfo ("high_priority"); | ||
| 587 | + | ||
| 588 | + info ("high_priority: entering ready state\n"); | ||
| 589 | + | ||
| 590 | + /* wait for all threads to be ready */ | ||
| 591 | + status = pthread_barrier_wait (&all_threads_ready); | ||
| 592 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 593 | + { | ||
| 594 | + error ("high_priority: pthread_barrier_wait(all_threads_ready): %x", | ||
| 595 | + status); | ||
| 596 | + return NULL; | ||
| 597 | + } | ||
| 598 | + | ||
| 599 | + info ("high_priority: starting inversion loop (%d)\n", p->inversions); | ||
| 600 | + while (p->inversions-- > 0) | ||
| 601 | + { | ||
| 602 | + high_has_run = 0; | ||
| 603 | + info ("high_priority: entering start state (%d)\n", p->inversions + 1); | ||
| 604 | + status = pthread_barrier_wait (&start_barrier); | ||
| 605 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 606 | + { | ||
| 607 | + error ("high_priority: pthread_barrier_wait(start): %x", status); | ||
| 608 | + return NULL; | ||
| 609 | + } | ||
| 610 | + info ("high_priority: entering running state\n"); | ||
| 611 | + status = pthread_barrier_wait (&locked_barrier); | ||
| 612 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 613 | + { | ||
| 614 | + error ("high_priority: pthread_barrier_wait(running): %x", status); | ||
| 615 | + return NULL; | ||
| 616 | + } | ||
| 617 | + info ("high_priority: locking mutex\n"); | ||
| 618 | + pthread_mutex_lock (&mutex); | ||
| 619 | + info ("high_priority: got mutex\n"); | ||
| 620 | + high_has_run = 1; | ||
| 621 | + info ("high_priority: unlocking mutex\n"); | ||
| 622 | + pthread_mutex_unlock (&mutex); | ||
| 623 | + info ("high_priority: entering finish state\n"); | ||
| 624 | + status = pthread_barrier_wait (&finish_barrier); | ||
| 625 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 626 | + { | ||
| 627 | + error ("high_priority: pthread_barrier_wait(finish): %x", status); | ||
| 628 | + return NULL; | ||
| 629 | + } | ||
| 630 | + } | ||
| 631 | + | ||
| 632 | + info ("high_priority: entering exit state\n"); | ||
| 633 | + status = pthread_barrier_wait (&all_threads_done); | ||
| 634 | + if (status && status != PTHREAD_BARRIER_SERIAL_THREAD) | ||
| 635 | + { | ||
| 636 | + error ("high_priority: pthread_barrier_wait(all_threads_done): %x", | ||
| 637 | + status); | ||
| 638 | + return NULL; | ||
| 639 | + } | ||
| 640 | + info ("high_priority: exiting\n"); | ||
| 641 | + return NULL; | ||
| 642 | +} | ||
| 643 | + | ||
| 644 | +void | ||
| 645 | +error (char *fmt, ...) | ||
| 646 | +{ | ||
| 647 | + va_list ap; | ||
| 648 | + fputs ("ERROR: ", errout); | ||
| 649 | + va_start (ap, fmt); | ||
| 650 | + vfprintf (errout, fmt, ap); | ||
| 651 | + va_end (ap); | ||
| 652 | +} | ||
| 653 | + | ||
| 654 | +void | ||
| 655 | +info (char *fmt, ...) | ||
| 656 | +{ | ||
| 657 | + if (verbose) | ||
| 658 | + { | ||
| 659 | + va_list ap; | ||
| 660 | + va_start (ap, fmt); | ||
| 661 | + vprintf (fmt, ap); | ||
| 662 | + va_end (ap); | ||
| 663 | + } | ||
| 664 | +} | ||
| 665 | + | ||
| @@ -0,0 +1,86 @@ | |||
| 1 | +.TH "pmqtest" "8" "0.1" "" "" | ||
| 2 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | +.SH "NAME" | ||
| 4 | +.LP | ||
| 5 | +\fBpmqtest\fR \- Start pairs of threads and measure the latency of interprocess communication with POSIX messages queues | ||
| 6 | +.SH "SYNTAX" | ||
| 7 | +.LP | ||
| 8 | +pmqtest [-a|-a PROC] [-b USEC] [-d DIST] [-D TIME] [-f TO] [-h] [-i INTV] [--json FILENAME] [-l LOOPS] [-p PRIO] [-q] [-S] [-t|-t NUM] | ||
| 9 | +.br | ||
| 10 | +.SH "DESCRIPTION" | ||
| 11 | +.LP | ||
| 12 | +The program \fBpmqtest\fR starts pairs of threads that are synchronized via mq_send/mw_receive() and measures the latency between sending and receiving the message. | ||
| 13 | +.SH "OPTIONS" | ||
| 14 | +.TP | ||
| 15 | +.B \-a, \-\-affinity[=PROC] | ||
| 16 | +Run on processor number PROC. If PROC is not specified, run on current processor. | ||
| 17 | +.TP | ||
| 18 | +.B \-b, \-\-breaktrace=USEC | ||
| 19 | +Send break trace command when latency > USEC. This is a debugging option to control the latency tracer in the realtime preemption patch. | ||
| 20 | +It is useful to track down unexpected large latencies of a system. | ||
| 21 | +.TP | ||
| 22 | +.B \-d, \-\-distance=DIST | ||
| 23 | +Set the distance of thread intervals in microseconds (default is 500 us). When pmqtest is called with the -t option and more than one thread is created, then this distance value is added to the interval of the threads: Interval(thread N) = Interval(thread N-1) + DIST | ||
| 24 | +.TP | ||
| 25 | +.B \-D, \-\-duration=TIME | ||
| 26 | +Specify a length for the test run. | ||
| 27 | +.br | ||
| 28 | +Append 'm', 'h', or 'd' to specify minutes, hours or days. | ||
| 29 | +.TP | ||
| 30 | +.B \-h, \-\-help | ||
| 31 | +Print help message | ||
| 32 | +.TP | ||
| 33 | +.B \-f, \-\-forcetimeout=TO | ||
| 34 | +Set an artificial delay of the send function to force timeout of the receiver, requires the -T option | ||
| 35 | +.TP | ||
| 36 | +.B \-i, \-\-interval=INTV | ||
| 37 | +Set the base interval of the thread(s) in microseconds (default is 1000 us). This sets the interval of the first thread. See also -d. | ||
| 38 | +.TP | ||
| 39 | +.B \-\-json=FILENAME | ||
| 40 | +Write final results into FILENAME, JSON formatted. | ||
| 41 | +.TP | ||
| 42 | +.B \-l, \-\-loops=LOOPS | ||
| 43 | +Set the number of loops. The default is 0 (endless). This option is useful for automated tests with a given number of test cycles. pmqtest is stopped once the number of timer intervals has been reached. | ||
| 44 | +.TP | ||
| 45 | +.B \-p, \-\-prio=PRIO | ||
| 46 | +Set the priority of the process. | ||
| 47 | +.TP | ||
| 48 | +.B \-q, \-\-quiet | ||
| 49 | +Print a summary only on exit. Useful for automated tests, where only the summary output needs to be captured. | ||
| 50 | +.TP | ||
| 51 | +.B \-S, \-\-smp | ||
| 52 | +Test mode for symmetric multi-processing, implies -a and -t and uses the same priority on all threads. | ||
| 53 | +.TP | ||
| 54 | +.B \-t, \-\-threads[=NUM] | ||
| 55 | +Set the number of test threads (default is 1, if this option is not given). If NUM is specified, create NUM test threads. If NUM is not specified, NUM is set to the number of available CPUs. | ||
| 56 | +.TP | ||
| 57 | +.B \-T, \-\-timeout=TO | ||
| 58 | +Use mq_timedreceive() instead of mq_receive() and specify timeout TO in seconds. | ||
| 59 | +.SH "EXAMPLES" | ||
| 60 | +The following example was running on an 8-way processor: | ||
| 61 | +.LP | ||
| 62 | +.nf | ||
| 63 | +# pmqtest -Sp99 -i100 -d0 | ||
| 64 | +#0: ID10047, P99, CPU0, I100; #1: ID10048, P99, CPU0, Cycles 153695 | ||
| 65 | +#2: ID10049, P99, CPU1, I100; #3: ID10050, P99, CPU1, Cycles 154211 | ||
| 66 | +#4: ID10051, P99, CPU2, I100; #5: ID10052, P99, CPU2, Cycles 156823 | ||
| 67 | +#6: ID10053, P99, CPU3, I100; #7: ID10054, P99, CPU3, Cycles 158202 | ||
| 68 | +#8: ID10055, P99, CPU4, I100; #9: ID10056, P99, CPU4, Cycles 153399 | ||
| 69 | +#10: ID10057, P99, CPU5, I100; #11: ID10058, P99, CPU5, Cycles 153992 | ||
| 70 | +#12: ID10059, P99, CPU6, I100; #13: ID10060, P99, CPU6, Cycles 156576 | ||
| 71 | +#14: ID10061, P99, CPU7, I100; #15: ID10062, P99, CPU7, Cycles 157957 | ||
| 72 | +#1 -> #0, Min 1, Cur 8, Avg 5, Max 18 | ||
| 73 | +#3 -> #2, Min 1, Cur 4, Avg 5, Max 18 | ||
| 74 | +#5 -> #4, Min 1, Cur 5, Avg 5, Max 19 | ||
| 75 | +#7 -> #6, Min 1, Cur 4, Avg 4, Max 17 | ||
| 76 | +#9 -> #8, Min 1, Cur 9, Avg 5, Max 18 | ||
| 77 | +#11 -> #10, Min 1, Cur 8, Avg 5, Max 18 | ||
| 78 | +#13 -> #12, Min 1, Cur 4, Avg 5, Max 29 | ||
| 79 | +#15 -> #14, Min 1, Cur 8, Avg 4, Max 17 | ||
| 80 | +.fi | ||
| 81 | +.SH "AUTHORS" | ||
| 82 | +.LP | ||
| 83 | +Carsten Emde <C.Emde@osadl.org> | ||
| 84 | +.SH "SEE ALSO" | ||
| 85 | +.LP | ||
| 86 | +mq_send(3p), mq_receive(3p) | ||
| @@ -0,0 +1,660 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +/* | ||
| 3 | + * pmqtest.c | ||
| 4 | + * | ||
| 5 | + * Copyright (C) 2009 Carsten Emde <C.Emde@osadl.org> | ||
| 6 | + */ | ||
| 7 | + | ||
| 8 | + | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + | ||
| 35 | +char *syncmsg = "Syncing"; | ||
| 36 | +char *testmsg = "Testing"; | ||
| 37 | + | ||
| 38 | +enum { | ||
| 39 | + AFFINITY_UNSPECIFIED, | ||
| 40 | + AFFINITY_SPECIFIED, | ||
| 41 | + AFFINITY_USEALL | ||
| 42 | +}; | ||
| 43 | + | ||
| 44 | +struct params { | ||
| 45 | + int num; | ||
| 46 | + int cpu; | ||
| 47 | + int priority; | ||
| 48 | + int affinity; | ||
| 49 | + int sender; | ||
| 50 | + int samples; | ||
| 51 | + int max_cycles; | ||
| 52 | + int tracelimit; | ||
| 53 | + int tid; | ||
| 54 | + int shutdown; | ||
| 55 | + int stopped; | ||
| 56 | + struct timespec delay; | ||
| 57 | + unsigned int mindiff, maxdiff; | ||
| 58 | + double sumdiff; | ||
| 59 | + struct timeval sent, received, diff; | ||
| 60 | + pthread_t threadid; | ||
| 61 | + int timeout; | ||
| 62 | + int forcetimeout; | ||
| 63 | + int timeoutcount; | ||
| 64 | + mqd_t syncmq, testmq; | ||
| 65 | + char recvsyncmsg[MSG_SIZE]; | ||
| 66 | + char recvtestmsg[MSG_SIZE]; | ||
| 67 | + struct params *neighbor; | ||
| 68 | +}; | ||
| 69 | + | ||
| 70 | +void *pmqthread(void *param) | ||
| 71 | +{ | ||
| 72 | + int mustgetcpu = 0; | ||
| 73 | + struct params *par = param; | ||
| 74 | + cpu_set_t mask; | ||
| 75 | + int policy = SCHED_FIFO; | ||
| 76 | + struct sched_param schedp; | ||
| 77 | + struct timespec ts; | ||
| 78 | + | ||
| 79 | + memset(&schedp, 0, sizeof(schedp)); | ||
| 80 | + schedp.sched_priority = par->priority; | ||
| 81 | + sched_setscheduler(0, policy, &schedp); | ||
| 82 | + | ||
| 83 | + if (par->cpu != -1) { | ||
| 84 | + CPU_ZERO(&mask); | ||
| 85 | + CPU_SET(par->cpu, &mask); | ||
| 86 | + if (sched_setaffinity(0, sizeof(mask), &mask) == -1) | ||
| 87 | + fprintf(stderr, "WARNING: Could not set CPU affinity " | ||
| 88 | + "to CPU #%d\n", par->cpu); | ||
| 89 | + } else | ||
| 90 | + mustgetcpu = 1; | ||
| 91 | + | ||
| 92 | + par->tid = gettid(); | ||
| 93 | + | ||
| 94 | + while (!par->shutdown) { | ||
| 95 | + if (par->sender) { | ||
| 96 | + | ||
| 97 | + /* Optionally force receiver timeout */ | ||
| 98 | + if (par->forcetimeout) { | ||
| 99 | + struct timespec senddelay; | ||
| 100 | + | ||
| 101 | + senddelay.tv_sec = par->forcetimeout; | ||
| 102 | + senddelay.tv_nsec = 0; | ||
| 103 | + clock_nanosleep(CLOCK_MONOTONIC, 0, &senddelay, NULL); | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + /* Send message: Start of latency measurement ... */ | ||
| 107 | + gettimeofday(&par->sent, NULL); | ||
| 108 | + if (mq_send(par->testmq, testmsg, strlen(testmsg), 1) != 0) { | ||
| 109 | + fprintf(stderr, "could not send test message\n"); | ||
| 110 | + par->shutdown = 1; | ||
| 111 | + } | ||
| 112 | + par->samples++; | ||
| 113 | + if (par->max_cycles && par->samples >= par->max_cycles) | ||
| 114 | + par->shutdown = 1; | ||
| 115 | + if (mustgetcpu) | ||
| 116 | + par->cpu = get_cpu(); | ||
| 117 | + /* Wait until receiver ready */ | ||
| 118 | + if (par->timeout) { | ||
| 119 | + clock_gettime(CLOCK_REALTIME, &ts); | ||
| 120 | + ts.tv_sec += par->timeout; | ||
| 121 | + | ||
| 122 | + if (mq_timedreceive(par->syncmq, par->recvsyncmsg, MSG_SIZE, NULL, &ts) | ||
| 123 | + != strlen(syncmsg)) { | ||
| 124 | + fprintf(stderr, "could not receive sync message\n"); | ||
| 125 | + par->shutdown = 1; | ||
| 126 | + } | ||
| 127 | + } | ||
| 128 | + if (mq_receive(par->syncmq, par->recvsyncmsg, MSG_SIZE, NULL) != | ||
| 129 | + strlen(syncmsg)) { | ||
| 130 | + perror("could not receive sync message"); | ||
| 131 | + par->shutdown = 1; | ||
| 132 | + } | ||
| 133 | + if (!par->shutdown && strcmp(syncmsg, par->recvsyncmsg)) { | ||
| 134 | + fprintf(stderr, "ERROR: Sync message mismatch detected\n"); | ||
| 135 | + fprintf(stderr, " %s != %s\n", syncmsg, par->recvsyncmsg); | ||
| 136 | + par->shutdown = 1; | ||
| 137 | + } | ||
| 138 | + } else { | ||
| 139 | + /* Receiver */ | ||
| 140 | + if (par->timeout) { | ||
| 141 | + clock_gettime(CLOCK_REALTIME, &ts); | ||
| 142 | + par->timeoutcount = 0; | ||
| 143 | + ts.tv_sec += par->timeout; | ||
| 144 | + do { | ||
| 145 | + if (mq_timedreceive(par->testmq, par->recvtestmsg, | ||
| 146 | + MSG_SIZE, NULL, &ts) != strlen(testmsg)) { | ||
| 147 | + if (!par->forcetimeout || errno != ETIMEDOUT) { | ||
| 148 | + perror("could not receive test message"); | ||
| 149 | + par->shutdown = 1; | ||
| 150 | + break; | ||
| 151 | + } | ||
| 152 | + if (errno == ETIMEDOUT) { | ||
| 153 | + par->timeoutcount++; | ||
| 154 | + clock_gettime(CLOCK_REALTIME, &ts); | ||
| 155 | + ts.tv_sec += par->timeout; | ||
| 156 | + } | ||
| 157 | + } else | ||
| 158 | + break; | ||
| 159 | + } while (1); | ||
| 160 | + } else { | ||
| 161 | + if (mq_receive(par->testmq, par->recvtestmsg, MSG_SIZE, NULL) != | ||
| 162 | + strlen(testmsg)) { | ||
| 163 | + perror("could not receive test message"); | ||
| 164 | + par->shutdown = 1; | ||
| 165 | + } | ||
| 166 | + } | ||
| 167 | + /* ... Received the message: End of latency measurement */ | ||
| 168 | + gettimeofday(&par->received, NULL); | ||
| 169 | + | ||
| 170 | + if (!par->shutdown && strcmp(testmsg, par->recvtestmsg)) { | ||
| 171 | + fprintf(stderr, "ERROR: Test message mismatch detected\n"); | ||
| 172 | + fprintf(stderr, " %s != %s\n", testmsg, par->recvtestmsg); | ||
| 173 | + par->shutdown = 1; | ||
| 174 | + } | ||
| 175 | + par->samples++; | ||
| 176 | + timersub(&par->received, &par->neighbor->sent, | ||
| 177 | + &par->diff); | ||
| 178 | + | ||
| 179 | + if (par->diff.tv_usec < par->mindiff) | ||
| 180 | + par->mindiff = par->diff.tv_usec; | ||
| 181 | + if (par->diff.tv_usec > par->maxdiff) | ||
| 182 | + par->maxdiff = par->diff.tv_usec; | ||
| 183 | + par->sumdiff += (double) par->diff.tv_usec; | ||
| 184 | + if (par->tracelimit && par->maxdiff > par->tracelimit) { | ||
| 185 | + char tracing_enabled_file[MAX_PATH]; | ||
| 186 | + | ||
| 187 | + strcpy(tracing_enabled_file, get_debugfileprefix()); | ||
| 188 | + strcat(tracing_enabled_file, "tracing_on"); | ||
| 189 | + int tracing_enabled = | ||
| 190 | + open(tracing_enabled_file, O_WRONLY); | ||
| 191 | + if (tracing_enabled >= 0) { | ||
| 192 | + write(tracing_enabled, "0", 1); | ||
| 193 | + close(tracing_enabled); | ||
| 194 | + } else | ||
| 195 | + fatal("Could not access %s\n", tracing_enabled_file); | ||
| 196 | + par->shutdown = 1; | ||
| 197 | + par->neighbor->shutdown = 1; | ||
| 198 | + } | ||
| 199 | + | ||
| 200 | + if (par->max_cycles && par->samples >= par->max_cycles) | ||
| 201 | + par->shutdown = 1; | ||
| 202 | + if (mustgetcpu) | ||
| 203 | + par->cpu = get_cpu(); | ||
| 204 | + clock_nanosleep(CLOCK_MONOTONIC, 0, &par->delay, NULL); | ||
| 205 | + | ||
| 206 | + /* Tell receiver that we are ready for the next measurement */ | ||
| 207 | + if (mq_send(par->syncmq, syncmsg, strlen(syncmsg), 1) != 0) { | ||
| 208 | + fprintf(stderr, "could not send sync message\n"); | ||
| 209 | + par->shutdown = 1; | ||
| 210 | + } | ||
| 211 | + } | ||
| 212 | + } | ||
| 213 | + par->stopped = 1; | ||
| 214 | + return NULL; | ||
| 215 | +} | ||
| 216 | + | ||
| 217 | +static void display_help(int error) | ||
| 218 | +{ | ||
| 219 | + printf("pmqtest V %1.2f\n", VERSION); | ||
| 220 | + printf("Usage:\n" | ||
| 221 | + "pmqtest <options>\n\n" | ||
| 222 | + "Function: test POSIX message queue latency\n\n" | ||
| 223 | + "Available options:\n\n" | ||
| 224 | + "-a [NUM] --affinity run thread #N on processor #N, if possible\n" | ||
| 225 | + " with NUM pin all threads to the processor NUM\n" | ||
| 226 | + "-b USEC --breaktrace=USEC send break trace command when latency > USEC\n" | ||
| 227 | + "-d DIST --distance=DIST distance of thread intervals in us default=500\n" | ||
| 228 | + "-D TIME --duration=TIME specify a length for the test run.\n" | ||
| 229 | + " Append 'm', 'h', or 'd' to specify\n" | ||
| 230 | + " minutes, hours or days.\n" | ||
| 231 | + "-f TO --forcetimeout=TO force timeout of mq_timedreceive(), requires -T\n" | ||
| 232 | + "-h --help print this help message\n" | ||
| 233 | + "-i INTV --interval=INTV base interval of thread in us default=1000\n" | ||
| 234 | + " --json=FILENAME write final results into FILENAME, JSON formatted\n" | ||
| 235 | + "-l LOOPS --loops=LOOPS number of loops: default=0(endless)\n" | ||
| 236 | + "-p PRIO --prio=PRIO priority\n" | ||
| 237 | + "-q --quiet print a summary only on exit\n" | ||
| 238 | + "-S --smp SMP testing: options -a -t and same priority\n" | ||
| 239 | + " of all threads\n" | ||
| 240 | + "-t --threads one thread per available processor\n" | ||
| 241 | + "-t [NUM] --threads=NUM number of threads:\n" | ||
| 242 | + " without NUM, threads = max_cpus\n" | ||
| 243 | + " without -t default = 1\n" | ||
| 244 | + "-T TO --timeout=TO use mq_timedreceive() instead of mq_receive()\n" | ||
| 245 | + " with timeout TO in seconds\n"); | ||
| 246 | + exit(error); | ||
| 247 | +} | ||
| 248 | + | ||
| 249 | +static int setaffinity = AFFINITY_UNSPECIFIED; | ||
| 250 | +static int affinity; | ||
| 251 | +static int tracelimit; | ||
| 252 | +static int priority; | ||
| 253 | +static int num_threads = 1; | ||
| 254 | +static int max_cycles; | ||
| 255 | +static int duration; | ||
| 256 | +static int interval = 1000; | ||
| 257 | +static int distance = 500; | ||
| 258 | +static int smp; | ||
| 259 | +static int sameprio; | ||
| 260 | +static int timeout; | ||
| 261 | +static int forcetimeout; | ||
| 262 | +static int quiet; | ||
| 263 | +static char jsonfile[MAX_PATH]; | ||
| 264 | + | ||
| 265 | +enum option_value { | ||
| 266 | + OPT_AFFINITY=1, OPT_BREAKTRACE, OPT_DISTANCE, OPT_DURATION, | ||
| 267 | + OPT_FORCETIMEOUT, OPT_HELP, OPT_INTERVAL, OPT_JSON, OPT_LOOPS, | ||
| 268 | + OPT_PRIORITY, OPT_QUIET, OPT_SMP, OPT_THREADS, OPT_TIMEOUT | ||
| 269 | +}; | ||
| 270 | + | ||
| 271 | +static void process_options(int argc, char *argv[]) | ||
| 272 | +{ | ||
| 273 | + int error = 0; | ||
| 274 | + int max_cpus = sysconf(_SC_NPROCESSORS_CONF); | ||
| 275 | + | ||
| 276 | + for (;;) { | ||
| 277 | + int option_index = 0; | ||
| 278 | + /** Options for getopt */ | ||
| 279 | + static struct option long_options[] = { | ||
| 280 | + {"affinity", optional_argument, NULL, OPT_AFFINITY}, | ||
| 281 | + {"breaktrace", required_argument, NULL, OPT_BREAKTRACE}, | ||
| 282 | + {"distance", required_argument, NULL, OPT_DISTANCE}, | ||
| 283 | + {"duration", required_argument, NULL, OPT_DURATION}, | ||
| 284 | + {"forcetimeout",required_argument, NULL, OPT_FORCETIMEOUT}, | ||
| 285 | + {"help", no_argument, NULL, OPT_HELP}, | ||
| 286 | + {"interval", required_argument, NULL, OPT_INTERVAL}, | ||
| 287 | + {"json", required_argument, NULL, OPT_JSON }, | ||
| 288 | + {"loops", required_argument, NULL, OPT_LOOPS}, | ||
| 289 | + {"priority", required_argument, NULL, OPT_PRIORITY}, | ||
| 290 | + {"quiet", no_argument, NULL, OPT_QUIET}, | ||
| 291 | + {"smp", no_argument, NULL, OPT_SMP}, | ||
| 292 | + {"threads", optional_argument, NULL, OPT_THREADS}, | ||
| 293 | + {"timeout", required_argument, NULL, OPT_TIMEOUT}, | ||
| 294 | + {NULL, 0, NULL, 0} | ||
| 295 | + }; | ||
| 296 | + int c = getopt_long (argc, argv, "a::b:d:D:f:i:l:p:qSt::T:", | ||
| 297 | + long_options, &option_index); | ||
| 298 | + if (c == -1) | ||
| 299 | + break; | ||
| 300 | + switch (c) { | ||
| 301 | + case OPT_AFFINITY: | ||
| 302 | + case 'a': | ||
| 303 | + if (smp) { | ||
| 304 | + warn("-a ignored due to --smp\n"); | ||
| 305 | + break; | ||
| 306 | + } | ||
| 307 | + if (optarg != NULL) { | ||
| 308 | + affinity = atoi(optarg); | ||
| 309 | + setaffinity = AFFINITY_SPECIFIED; | ||
| 310 | + } else if (optind < argc && atoi(argv[optind])) { | ||
| 311 | + affinity = atoi(argv[optind]); | ||
| 312 | + setaffinity = AFFINITY_SPECIFIED; | ||
| 313 | + } else { | ||
| 314 | + setaffinity = AFFINITY_USEALL; | ||
| 315 | + } | ||
| 316 | + break; | ||
| 317 | + case OPT_BREAKTRACE: | ||
| 318 | + case 'b': | ||
| 319 | + tracelimit = atoi(optarg); | ||
| 320 | + break; | ||
| 321 | + case OPT_DISTANCE: | ||
| 322 | + case 'd': | ||
| 323 | + distance = atoi(optarg); | ||
| 324 | + break; | ||
| 325 | + case OPT_DURATION: | ||
| 326 | + case 'D': | ||
| 327 | + duration = parse_time_string(optarg); | ||
| 328 | + break; | ||
| 329 | + case OPT_FORCETIMEOUT: | ||
| 330 | + case 'f': | ||
| 331 | + forcetimeout = atoi(optarg); | ||
| 332 | + break; | ||
| 333 | + case OPT_HELP: | ||
| 334 | + case '?': | ||
| 335 | + case 'h': | ||
| 336 | + display_help(0); | ||
| 337 | + break; | ||
| 338 | + case OPT_INTERVAL: | ||
| 339 | + case 'i': | ||
| 340 | + interval = atoi(optarg); | ||
| 341 | + break; | ||
| 342 | + case OPT_JSON: | ||
| 343 | + strncpy(jsonfile, optarg, strnlen(optarg, MAX_PATH-1)); | ||
| 344 | + break; | ||
| 345 | + case OPT_LOOPS: | ||
| 346 | + case 'l': | ||
| 347 | + max_cycles = atoi(optarg); | ||
| 348 | + break; | ||
| 349 | + case OPT_PRIORITY: | ||
| 350 | + case 'p': | ||
| 351 | + priority = atoi(optarg); | ||
| 352 | + break; | ||
| 353 | + case OPT_QUIET: | ||
| 354 | + case 'q': | ||
| 355 | + quiet = 1; | ||
| 356 | + break; | ||
| 357 | + case OPT_SMP: | ||
| 358 | + case 'S': | ||
| 359 | + smp = 1; | ||
| 360 | + num_threads = max_cpus; | ||
| 361 | + setaffinity = AFFINITY_USEALL; | ||
| 362 | + break; | ||
| 363 | + case OPT_THREADS: | ||
| 364 | + case 't': | ||
| 365 | + if (smp) { | ||
| 366 | + warn("-t ignored due to --smp\n"); | ||
| 367 | + break; | ||
| 368 | + } | ||
| 369 | + if (optarg != NULL) | ||
| 370 | + num_threads = atoi(optarg); | ||
| 371 | + else if (optind < argc && atoi(argv[optind])) | ||
| 372 | + num_threads = atoi(argv[optind]); | ||
| 373 | + else | ||
| 374 | + num_threads = max_cpus; | ||
| 375 | + break; | ||
| 376 | + case OPT_TIMEOUT: | ||
| 377 | + case 'T': | ||
| 378 | + timeout = atoi(optarg); | ||
| 379 | + break; | ||
| 380 | + default: | ||
| 381 | + display_help(1); | ||
| 382 | + break; | ||
| 383 | + } | ||
| 384 | + } | ||
| 385 | + | ||
| 386 | + if (setaffinity == AFFINITY_SPECIFIED) { | ||
| 387 | + if (affinity < 0) | ||
| 388 | + error = 1; | ||
| 389 | + if (affinity >= max_cpus) { | ||
| 390 | + fprintf(stderr, "ERROR: CPU #%d not found, only %d CPUs available\n", | ||
| 391 | + affinity, max_cpus); | ||
| 392 | + error = 1; | ||
| 393 | + } | ||
| 394 | + } | ||
| 395 | + | ||
| 396 | + if (num_threads < 0) | ||
| 397 | + error = 1; | ||
| 398 | + | ||
| 399 | + if (priority < 0 || priority > 99) | ||
| 400 | + error = 1; | ||
| 401 | + | ||
| 402 | + if (num_threads < 1) | ||
| 403 | + error = 1; | ||
| 404 | + | ||
| 405 | + if (forcetimeout && !timeout) | ||
| 406 | + error = 1; | ||
| 407 | + | ||
| 408 | + if (duration < 0) | ||
| 409 | + error = 1; | ||
| 410 | + | ||
| 411 | + if (priority && smp) | ||
| 412 | + sameprio = 1; | ||
| 413 | + | ||
| 414 | + if (error) | ||
| 415 | + display_help(error); | ||
| 416 | +} | ||
| 417 | + | ||
| 418 | + | ||
| 419 | +static int volatile shutdown; | ||
| 420 | + | ||
| 421 | +static void sighand(int sig __attribute__ ((unused))) | ||
| 422 | +{ | ||
| 423 | + shutdown = 1; | ||
| 424 | +} | ||
| 425 | + | ||
| 426 | +static void print_stat(FILE *fp __attribute__ ((unused)), | ||
| 427 | + struct params *receiver, struct params *sender, | ||
| 428 | + int verbose __attribute__ ((unused)), int quiet) | ||
| 429 | +{ | ||
| 430 | + int i; | ||
| 431 | + | ||
| 432 | + if (quiet) | ||
| 433 | + return; | ||
| 434 | + | ||
| 435 | + for (i = 0; i < num_threads; i++) { | ||
| 436 | + printf("#%1d: ID%d, P%d, CPU%d, I%ld; #%1d: ID%d, P%d, CPU%d, TO %d, Cycles %d\n", | ||
| 437 | + i*2, receiver[i].tid, receiver[i].priority, receiver[i].cpu, | ||
| 438 | + receiver[i].delay.tv_nsec / 1000, | ||
| 439 | + i*2+1, sender[i].tid, sender[i].priority, sender[i].cpu, | ||
| 440 | + receiver[i].timeoutcount, sender[i].samples); | ||
| 441 | + } | ||
| 442 | + for (i = 0; i < num_threads; i++) { | ||
| 443 | + printf("#%d -> #%d, Min %4d, Cur %4d, Avg %4d, Max %4d\n", | ||
| 444 | + i*2+1, i*2, | ||
| 445 | + receiver[i].mindiff, (int) receiver[i].diff.tv_usec, | ||
| 446 | + (int) ((receiver[i].sumdiff / receiver[i].samples) + 0.5), | ||
| 447 | + receiver[i].maxdiff); | ||
| 448 | + } | ||
| 449 | +} | ||
| 450 | + | ||
| 451 | +struct params_stats { | ||
| 452 | + struct params *receiver; | ||
| 453 | + struct params *sender; | ||
| 454 | +}; | ||
| 455 | + | ||
| 456 | +static void write_stats(FILE *f, void *data) | ||
| 457 | +{ | ||
| 458 | + struct params_stats *ps = data; | ||
| 459 | + struct params *s, *r; | ||
| 460 | + int i; | ||
| 461 | + | ||
| 462 | + fprintf(f, " \"num_threads\": %d,\n", num_threads); | ||
| 463 | + fprintf(f, " \"thread\": {\n"); | ||
| 464 | + for (i = 0; i < num_threads; i++) { | ||
| 465 | + s = &ps->sender[i]; | ||
| 466 | + r = &ps->receiver[i]; | ||
| 467 | + fprintf(f, " \"%u\": {\n", i); | ||
| 468 | + fprintf(f, " \"sender\": {\n"); | ||
| 469 | + fprintf(f, " \"cpu\": %d,\n", s->cpu); | ||
| 470 | + fprintf(f, " \"priority\": %d,\n", s->priority); | ||
| 471 | + fprintf(f, " \"samples\": %d,\n", s->samples); | ||
| 472 | + fprintf(f, " \"interval\": %ld\n", r->delay.tv_nsec/1000); | ||
| 473 | + fprintf(f, " },\n"); | ||
| 474 | + fprintf(f, " \"receiver\": {\n"); | ||
| 475 | + fprintf(f, " \"cpu\": %d,\n", r->cpu); | ||
| 476 | + fprintf(f, " \"priority\": %d,\n", r->priority); | ||
| 477 | + fprintf(f, " \"timeoutcount\": %d,\n", r->timeoutcount); | ||
| 478 | + fprintf(f, " \"min\": %d,\n", r->mindiff); | ||
| 479 | + fprintf(f, " \"avg\": %.2f,\n", r->sumdiff/r->samples); | ||
| 480 | + fprintf(f, " \"max\": %d\n", r->maxdiff); | ||
| 481 | + fprintf(f, " }\n"); | ||
| 482 | + fprintf(f, " }%s\n", i == num_threads - 1 ? "" : ","); | ||
| 483 | + } | ||
| 484 | + fprintf(f, " }\n"); | ||
| 485 | +} | ||
| 486 | + | ||
| 487 | +int main(int argc, char *argv[]) | ||
| 488 | +{ | ||
| 489 | + int i; | ||
| 490 | + int max_cpus = sysconf(_SC_NPROCESSORS_CONF); | ||
| 491 | + struct params *receiver = NULL; | ||
| 492 | + struct params *sender = NULL; | ||
| 493 | + sigset_t sigset; | ||
| 494 | + int oldsamples = INT_MAX; | ||
| 495 | + int oldtimeoutcount = INT_MAX; | ||
| 496 | + struct timespec maindelay; | ||
| 497 | + int oflag = O_CREAT|O_RDWR; | ||
| 498 | + struct mq_attr mqstat; | ||
| 499 | + | ||
| 500 | + memset(&mqstat, 0, sizeof(mqstat)); | ||
| 501 | + mqstat.mq_maxmsg = 1; | ||
| 502 | + mqstat.mq_msgsize = 8; | ||
| 503 | + mqstat.mq_flags = 0; | ||
| 504 | + | ||
| 505 | + rt_init(argc, argv); | ||
| 506 | + process_options(argc, argv); | ||
| 507 | + | ||
| 508 | + if (check_privs()) | ||
| 509 | + return 1; | ||
| 510 | + | ||
| 511 | + if (mlockall(MCL_CURRENT|MCL_FUTURE) == -1) { | ||
| 512 | + perror("mlockall"); | ||
| 513 | + return 1; | ||
| 514 | + } | ||
| 515 | + | ||
| 516 | + sigemptyset(&sigset); | ||
| 517 | + sigaddset(&sigset, SIGTERM); | ||
| 518 | + sigaddset(&sigset, SIGINT); | ||
| 519 | + sigaddset(&sigset, SIGALRM); | ||
| 520 | + pthread_sigmask(SIG_SETMASK, &sigset, NULL); | ||
| 521 | + | ||
| 522 | + signal(SIGINT, sighand); | ||
| 523 | + signal(SIGTERM, sighand); | ||
| 524 | + signal(SIGALRM, sighand); | ||
| 525 | + | ||
| 526 | + if (duration) | ||
| 527 | + alarm(duration); | ||
| 528 | + | ||
| 529 | + receiver = calloc(num_threads, sizeof(struct params)); | ||
| 530 | + sender = calloc(num_threads, sizeof(struct params)); | ||
| 531 | + if (receiver == NULL || sender == NULL) | ||
| 532 | + goto nomem; | ||
| 533 | + | ||
| 534 | + for (i = 0; i < num_threads; i++) { | ||
| 535 | + char mqname[19]; | ||
| 536 | + | ||
| 537 | + sprintf(mqname, SYNCMQ_NAME, i); | ||
| 538 | + receiver[i].syncmq = mq_open(mqname, oflag, 0777, &mqstat); | ||
| 539 | + if (receiver[i].syncmq == (mqd_t) -1) { | ||
| 540 | + fprintf(stderr, "could not open POSIX message queue #1\n"); | ||
| 541 | + return 1; | ||
| 542 | + } | ||
| 543 | + sprintf(mqname, TESTMQ_NAME, i); | ||
| 544 | + receiver[i].testmq = mq_open(mqname, oflag, 0777, &mqstat); | ||
| 545 | + if (receiver[i].testmq == (mqd_t) -1) { | ||
| 546 | + fprintf(stderr, "could not open POSIX message queue #2\n"); | ||
| 547 | + return 1; | ||
| 548 | + } | ||
| 549 | + | ||
| 550 | + receiver[i].mindiff = UINT_MAX; | ||
| 551 | + receiver[i].maxdiff = 0; | ||
| 552 | + receiver[i].sumdiff = 0.0; | ||
| 553 | + | ||
| 554 | + receiver[i].num = i; | ||
| 555 | + receiver[i].cpu = i; | ||
| 556 | + switch (setaffinity) { | ||
| 557 | + case AFFINITY_UNSPECIFIED: receiver[i].cpu = -1; break; | ||
| 558 | + case AFFINITY_SPECIFIED: receiver[i].cpu = affinity; break; | ||
| 559 | + case AFFINITY_USEALL: receiver[i].cpu = i % max_cpus; break; | ||
| 560 | + } | ||
| 561 | + receiver[i].priority = priority; | ||
| 562 | + receiver[i].tracelimit = tracelimit; | ||
| 563 | + if (priority > 1 && !sameprio) | ||
| 564 | + priority--; | ||
| 565 | + receiver[i].delay.tv_sec = interval / USEC_PER_SEC; | ||
| 566 | + receiver[i].delay.tv_nsec = (interval % USEC_PER_SEC) * 1000; | ||
| 567 | + interval += distance; | ||
| 568 | + receiver[i].max_cycles = max_cycles; | ||
| 569 | + receiver[i].sender = 0; | ||
| 570 | + receiver[i].neighbor = &sender[i]; | ||
| 571 | + receiver[i].timeout = timeout; | ||
| 572 | + receiver[i].forcetimeout = forcetimeout; | ||
| 573 | + pthread_create(&receiver[i].threadid, NULL, pmqthread, &receiver[i]); | ||
| 574 | + memcpy(&sender[i], &receiver[i], sizeof(receiver[0])); | ||
| 575 | + sender[i].sender = 1; | ||
| 576 | + sender[i].neighbor = &receiver[i]; | ||
| 577 | + pthread_create(&sender[i].threadid, NULL, pmqthread, &sender[i]); | ||
| 578 | + } | ||
| 579 | + | ||
| 580 | + maindelay.tv_sec = 0; | ||
| 581 | + maindelay.tv_nsec = 50000000; /* 50 ms */ | ||
| 582 | + | ||
| 583 | + sigemptyset(&sigset); | ||
| 584 | + pthread_sigmask(SIG_SETMASK, &sigset, NULL); | ||
| 585 | + | ||
| 586 | + do { | ||
| 587 | + int newsamples = 0, newtimeoutcount = 0; | ||
| 588 | + int minsamples = INT_MAX; | ||
| 589 | + | ||
| 590 | + for (i = 0; i < num_threads; i++) { | ||
| 591 | + newsamples += receiver[i].samples; | ||
| 592 | + newtimeoutcount += receiver[i].timeoutcount; | ||
| 593 | + if (receiver[i].samples < minsamples) | ||
| 594 | + minsamples = receiver[i].samples; | ||
| 595 | + } | ||
| 596 | + | ||
| 597 | + if (minsamples > 1 && (shutdown || newsamples > oldsamples || | ||
| 598 | + newtimeoutcount > oldtimeoutcount)) { | ||
| 599 | + print_stat(stdout, receiver, sender, 0, quiet); | ||
| 600 | + if (!quiet) | ||
| 601 | + printf("\033[%dA", num_threads*2); | ||
| 602 | + } | ||
| 603 | + | ||
| 604 | + fflush(NULL); | ||
| 605 | + | ||
| 606 | + oldsamples = 0; | ||
| 607 | + oldtimeoutcount = 0; | ||
| 608 | + for (i = 0; i < num_threads; i++) { | ||
| 609 | + oldsamples += receiver[i].samples; | ||
| 610 | + oldtimeoutcount += receiver[i].timeoutcount; | ||
| 611 | + } | ||
| 612 | + | ||
| 613 | + nanosleep(&maindelay, NULL); | ||
| 614 | + | ||
| 615 | + for (i = 0; i < num_threads; i++) | ||
| 616 | + shutdown |= receiver[i].shutdown | sender[i].shutdown; | ||
| 617 | + | ||
| 618 | + } while (!shutdown); | ||
| 619 | + | ||
| 620 | + if (!quiet) | ||
| 621 | + printf("\033[%dB", num_threads*2 + 2); | ||
| 622 | + else | ||
| 623 | + print_stat(stdout, receiver, sender, 0, 0); | ||
| 624 | + | ||
| 625 | + for (i = 0; i < num_threads; i++) { | ||
| 626 | + receiver[i].shutdown = 1; | ||
| 627 | + sender[i].shutdown = 1; | ||
| 628 | + } | ||
| 629 | + | ||
| 630 | + for (i = 0; i < num_threads; i++) { | ||
| 631 | + if (!receiver[i].stopped) | ||
| 632 | + pthread_kill(receiver[i].threadid, SIGTERM); | ||
| 633 | + if (!sender[i].stopped) | ||
| 634 | + pthread_kill(sender[i].threadid, SIGTERM); | ||
| 635 | + } | ||
| 636 | + nanosleep(&maindelay, NULL); | ||
| 637 | + for (i = 0; i < num_threads; i++) { | ||
| 638 | + char mqname[19]; | ||
| 639 | + | ||
| 640 | + mq_close(receiver[i].syncmq); | ||
| 641 | + sprintf(mqname, SYNCMQ_NAME, i); | ||
| 642 | + mq_unlink(mqname); | ||
| 643 | + | ||
| 644 | + mq_close(receiver[i].testmq); | ||
| 645 | + sprintf(mqname, TESTMQ_NAME, i); | ||
| 646 | + mq_unlink(mqname); | ||
| 647 | + } | ||
| 648 | + | ||
| 649 | + if (strlen(jsonfile) != 0) { | ||
| 650 | + struct params_stats ps = { | ||
| 651 | + .receiver = receiver, | ||
| 652 | + .sender = sender, | ||
| 653 | + }; | ||
| 654 | + rt_write_json(jsonfile, 0, write_stats, &ps); | ||
| 655 | + } | ||
| 656 | + | ||
| 657 | +nomem: | ||
| 658 | + | ||
| 659 | + return 0; | ||
| 660 | +} | ||
| @@ -0,0 +1,72 @@ | |||
| 1 | +.TH "ptsematest" "8" "0.1" "" "" | ||
| 2 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | +.SH "NAME" | ||
| 4 | +.LP | ||
| 5 | +\fBptsematest\fR \- Start two threads and measure the latency of interprocess communication with POSIX mutex. | ||
| 6 | +.SH "SYNOPSIS" | ||
| 7 | +.LP | ||
| 8 | +ptsematest [-a|--affinity [PROC]] [-b|--breaktrace USEC] [-d|--distance DIST] [-D|--duration TIME] [-h|--help] [-i|--interval INTV] [--json FILENAME] [-l|--loops LOOPS] [-p|--prio PRIO] [-q|--quiet] [-S|--smp] [-t|--threads [NUM]] | ||
| 9 | +.br | ||
| 10 | +.SH "DESCRIPTION" | ||
| 11 | +.LP | ||
| 12 | +The program \fBptsematest\fR starts two threads that are synchronized via pthread_mutex_unlock()/pthread_mutex_lock() and measures the latency between releasing and getting the lock. | ||
| 13 | +.SH "OPTIONS" | ||
| 14 | +.TP | ||
| 15 | +.B \-a, \-\-affinity[=PROC] | ||
| 16 | +Run on processor number PROC. If PROC is not specified, run on current processor. | ||
| 17 | +.TP | ||
| 18 | +.B \-b, \-\-breaktrace=USEC | ||
| 19 | +Send break trace command when latency > USEC. This is a debugging option to control the latency tracer in the realtime preemption patch. | ||
| 20 | +It is useful to track down unexpected large latencies of a system. | ||
| 21 | +.TP | ||
| 22 | +.B \-d, \-\-distance=DIST | ||
| 23 | +Set the distance of thread intervals in microseconds (default is 500 us). When cyclictest is called with the -t option and more than one thread is created, then this distance value is added to the interval of the threads: Interval(thread N) = Interval(thread N-1) + DIST | ||
| 24 | +.TP | ||
| 25 | +.B \-D, \-\-duration=TIME | ||
| 26 | +Specify a length for the test run. | ||
| 27 | +.br | ||
| 28 | +Append 'm', 'h', or 'd' to specify minutes, hours or days. | ||
| 29 | +.TP | ||
| 30 | +.B \-h, \-\-help | ||
| 31 | +Print help message. | ||
| 32 | +.TP | ||
| 33 | +.B \-i, \-\-interval=INTV | ||
| 34 | +Set the base interval of the thread(s) in microseconds (default is 1000 us). This sets the interval of the first thread. See also -d. | ||
| 35 | +.TP | ||
| 36 | +.B \-\-json=FILENAME | ||
| 37 | +Write final results into FILENAME, JSON formatted. | ||
| 38 | +.TP | ||
| 39 | +.B \-l, \-\-loops=LOOPS | ||
| 40 | +Set the number of loops. The default is 0 (endless). This option is useful for automated tests with a given number of test cycles. ptsematest is stopped once the number of timer intervals has been reached. | ||
| 41 | +.TP | ||
| 42 | +.B \-p, \-\-prio=PRIO | ||
| 43 | +Set the priority of the process. | ||
| 44 | +.TP | ||
| 45 | +.B \-q, \-\-quiet | ||
| 46 | +Print a summary only on exit. Useful for automated tests, where only the summary output needs to be captured. | ||
| 47 | +.TP | ||
| 48 | +.B \-S, \-\-smp | ||
| 49 | +SMP testing: options -a -t and same priority | ||
| 50 | +.TP | ||
| 51 | +.B \-t, \-\-threads[=NUM] | ||
| 52 | +Set the number of test threads (default is 1, if this option is not given). If NUM is specified, create NUM test threads. If NUM is not specified, NUM is set to the number of available CPUs. | ||
| 53 | +.SH "EXAMPLES" | ||
| 54 | +The following example was running on a 4-way processor: | ||
| 55 | +.LP | ||
| 56 | +.nf | ||
| 57 | +# ptsematest -a -t -p99 -i100 -d25 -l1000000 | ||
| 58 | +#0: ID8672, P99, CPU0, I100; #1: ID8673, P99, CPU0, Cycles 1000000 | ||
| 59 | +#2: ID8674, P98, CPU1, I125; #3: ID8675, P98, CPU1, Cycles 811035 | ||
| 60 | +#4: ID8676, P97, CPU2, I150; #5: ID8677, P97, CPU2, Cycles 668130 | ||
| 61 | +#6: ID8678, P96, CPU3, I175; #7: ID8679, P96, CPU3, Cycles 589423 | ||
| 62 | +#1 -> #0, Min 1, Cur 1, Avg 2, Max 11 | ||
| 63 | +#3 -> #2, Min 1, Cur 2, Avg 2, Max 13 | ||
| 64 | +#5 -> #4, Min 1, Cur 4, Avg 3, Max 12 | ||
| 65 | +#7 -> #6, Min 1, Cur 4, Avg 2, Max 12 | ||
| 66 | +.fi | ||
| 67 | +.SH "AUTHORS" | ||
| 68 | +.LP | ||
| 69 | +Carsten Emde <C.Emde@osadl.org> | ||
| 70 | +.SH "SEE ALSO" | ||
| 71 | +.LP | ||
| 72 | +pthread_mutex_lock(3p), pthread_mutex_unlock(3p) | ||
| @@ -0,0 +1,523 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +/* | ||
| 3 | + * ptsematest.c | ||
| 4 | + * Copyright (C) 2009 Carsten Emde <C.Emde@osadl.org> | ||
| 5 | + */ | ||
| 6 | + | ||
| 7 | + | ||
| 8 | + | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | +enum { | ||
| 30 | + AFFINITY_UNSPECIFIED, | ||
| 31 | + AFFINITY_SPECIFIED, | ||
| 32 | + AFFINITY_USEALL | ||
| 33 | +}; | ||
| 34 | + | ||
| 35 | +static pthread_mutex_t *testmutex; | ||
| 36 | +static pthread_mutex_t *syncmutex; | ||
| 37 | + | ||
| 38 | +struct params { | ||
| 39 | + int num; | ||
| 40 | + int cpu; | ||
| 41 | + int priority; | ||
| 42 | + int affinity; | ||
| 43 | + int sender; | ||
| 44 | + int samples; | ||
| 45 | + int max_cycles; | ||
| 46 | + int tracelimit; | ||
| 47 | + int tid; | ||
| 48 | + int shutdown; | ||
| 49 | + int stopped; | ||
| 50 | + struct timespec delay; | ||
| 51 | + unsigned int mindiff, maxdiff; | ||
| 52 | + double sumdiff; | ||
| 53 | + struct timeval unblocked, received, diff; | ||
| 54 | + pthread_t threadid; | ||
| 55 | + struct params *neighbor; | ||
| 56 | +}; | ||
| 57 | + | ||
| 58 | +void *semathread(void *param) | ||
| 59 | +{ | ||
| 60 | + int mustgetcpu = 0; | ||
| 61 | + struct params *par = param; | ||
| 62 | + cpu_set_t mask; | ||
| 63 | + int policy = SCHED_FIFO; | ||
| 64 | + struct sched_param schedp; | ||
| 65 | + | ||
| 66 | + memset(&schedp, 0, sizeof(schedp)); | ||
| 67 | + schedp.sched_priority = par->priority; | ||
| 68 | + sched_setscheduler(0, policy, &schedp); | ||
| 69 | + | ||
| 70 | + if (par->cpu != -1) { | ||
| 71 | + CPU_ZERO(&mask); | ||
| 72 | + CPU_SET(par->cpu, &mask); | ||
| 73 | + if (sched_setaffinity(0, sizeof(mask), &mask) == -1) | ||
| 74 | + fprintf(stderr, "WARNING: Could not set CPU affinity " | ||
| 75 | + "to CPU #%d\n", par->cpu); | ||
| 76 | + } else | ||
| 77 | + mustgetcpu = 1; | ||
| 78 | + | ||
| 79 | + par->tid = gettid(); | ||
| 80 | + | ||
| 81 | + while (!par->shutdown) { | ||
| 82 | + if (par->sender) { | ||
| 83 | + pthread_mutex_lock(&syncmutex[par->num]); | ||
| 84 | + | ||
| 85 | + /* Release lock: Start of latency measurement ... */ | ||
| 86 | + gettimeofday(&par->unblocked, NULL); | ||
| 87 | + pthread_mutex_unlock(&testmutex[par->num]); | ||
| 88 | + par->samples++; | ||
| 89 | + if (par->max_cycles && par->samples >= par->max_cycles) | ||
| 90 | + par->shutdown = 1; | ||
| 91 | + if (mustgetcpu) | ||
| 92 | + par->cpu = get_cpu(); | ||
| 93 | + } else { | ||
| 94 | + pthread_mutex_lock(&testmutex[par->num]); | ||
| 95 | + | ||
| 96 | + /* ... Got the lock: End of latency measurement */ | ||
| 97 | + gettimeofday(&par->received, NULL); | ||
| 98 | + par->samples++; | ||
| 99 | + timersub(&par->received, &par->neighbor->unblocked, | ||
| 100 | + &par->diff); | ||
| 101 | + | ||
| 102 | + if (par->diff.tv_usec < par->mindiff) | ||
| 103 | + par->mindiff = par->diff.tv_usec; | ||
| 104 | + if (par->diff.tv_usec > par->maxdiff) | ||
| 105 | + par->maxdiff = par->diff.tv_usec; | ||
| 106 | + par->sumdiff += (double) par->diff.tv_usec; | ||
| 107 | + if (par->tracelimit && par->maxdiff > par->tracelimit) { | ||
| 108 | + char tracing_enabled_file[MAX_PATH]; | ||
| 109 | + | ||
| 110 | + strcpy(tracing_enabled_file, get_debugfileprefix()); | ||
| 111 | + strcat(tracing_enabled_file, "tracing_on"); | ||
| 112 | + int tracing_enabled = | ||
| 113 | + open(tracing_enabled_file, O_WRONLY); | ||
| 114 | + if (tracing_enabled >= 0) { | ||
| 115 | + write(tracing_enabled, "0", 1); | ||
| 116 | + close(tracing_enabled); | ||
| 117 | + } else | ||
| 118 | + fatal("Could not access %s\n", | ||
| 119 | + tracing_enabled_file); | ||
| 120 | + par->shutdown = 1; | ||
| 121 | + par->neighbor->shutdown = 1; | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + if (par->max_cycles && par->samples >= par->max_cycles) | ||
| 125 | + par->shutdown = 1; | ||
| 126 | + if (mustgetcpu) | ||
| 127 | + par->cpu = get_cpu(); | ||
| 128 | + nanosleep(&par->delay, NULL); | ||
| 129 | + pthread_mutex_unlock(&syncmutex[par->num]); | ||
| 130 | + } | ||
| 131 | + } | ||
| 132 | + par->stopped = 1; | ||
| 133 | + return NULL; | ||
| 134 | +} | ||
| 135 | + | ||
| 136 | + | ||
| 137 | +static void display_help(int error) | ||
| 138 | +{ | ||
| 139 | + printf("ptsematest V %1.2f\n", VERSION); | ||
| 140 | + printf("Usage:\n" | ||
| 141 | + "ptsematest <options>\n\n" | ||
| 142 | + "Function: test POSIX threads mutex latency\n\n" | ||
| 143 | + "Available options:\n" | ||
| 144 | + "-a [NUM] --affinity run thread #N on processor #N, if possible\n" | ||
| 145 | + " with NUM pin all threads to the processor NUM\n" | ||
| 146 | + "-b USEC --breaktrace=USEC send break trace command when latency > USEC\n" | ||
| 147 | + "-d DIST --distance=DIST distance of thread intervals in us default=500\n" | ||
| 148 | + "-D --duration=TIME specify a length for the test run.\n" | ||
| 149 | + " Append 'm', 'h', or 'd' to specify minutes, hours or\n" | ||
| 150 | + " days.\n" | ||
| 151 | + "-i INTV --interval=INTV base interval of thread in us default=1000\n" | ||
| 152 | + " --json=FILENAME write final results into FILENAME, JSON formatted\n" | ||
| 153 | + "-l LOOPS --loops=LOOPS number of loops: default=0(endless)\n" | ||
| 154 | + "-p PRIO --prio=PRIO priority\n" | ||
| 155 | + "-q --quiet print a summary only on exit\n" | ||
| 156 | + "-S --smp SMP testing: options -a -t and same priority\n" | ||
| 157 | + " of all threads\n" | ||
| 158 | + "-t --threads one thread per available processor\n" | ||
| 159 | + "-t [NUM] --threads=NUM number of threads:\n" | ||
| 160 | + " without NUM, threads = max_cpus\n" | ||
| 161 | + " without -t default = 1\n" | ||
| 162 | + ); | ||
| 163 | + exit(error); | ||
| 164 | +} | ||
| 165 | + | ||
| 166 | + | ||
| 167 | +static int setaffinity = AFFINITY_UNSPECIFIED; | ||
| 168 | +static int affinity; | ||
| 169 | +static int tracelimit; | ||
| 170 | +static int priority; | ||
| 171 | +static int num_threads = 1; | ||
| 172 | +static int max_cycles; | ||
| 173 | +static int duration; | ||
| 174 | +static int interval = 1000; | ||
| 175 | +static int distance = 500; | ||
| 176 | +static int smp; | ||
| 177 | +static int sameprio; | ||
| 178 | +static int quiet; | ||
| 179 | +static char jsonfile[MAX_PATH]; | ||
| 180 | + | ||
| 181 | +enum option_value { | ||
| 182 | + OPT_AFFINITY=1, OPT_BREAKTRACE, OPT_DISTANCE, OPT_DURATION, | ||
| 183 | + OPT_HELP, OPT_INTERVAL, OPT_JSON, OPT_LOOPS, OPT_PRIORITY, | ||
| 184 | + OPT_QUIET, OPT_SMP, OPT_THREADS | ||
| 185 | +}; | ||
| 186 | + | ||
| 187 | +static void process_options(int argc, char *argv[]) | ||
| 188 | +{ | ||
| 189 | + int error = 0; | ||
| 190 | + int max_cpus = sysconf(_SC_NPROCESSORS_CONF); | ||
| 191 | + | ||
| 192 | + for (;;) { | ||
| 193 | + int option_index = 0; | ||
| 194 | + /** Options for getopt */ | ||
| 195 | + static struct option long_options[] = { | ||
| 196 | + {"affinity", optional_argument, NULL, OPT_AFFINITY}, | ||
| 197 | + {"breaktrace", required_argument, NULL, OPT_BREAKTRACE}, | ||
| 198 | + {"distance", required_argument, NULL, OPT_DISTANCE}, | ||
| 199 | + {"duration", required_argument, NULL, OPT_DURATION}, | ||
| 200 | + {"help", no_argument, NULL, OPT_HELP}, | ||
| 201 | + {"interval", required_argument, NULL, OPT_INTERVAL}, | ||
| 202 | + {"json", required_argument, NULL, OPT_JSON }, | ||
| 203 | + {"loops", required_argument, NULL, OPT_LOOPS}, | ||
| 204 | + {"priority", required_argument, NULL, OPT_PRIORITY}, | ||
| 205 | + {"quiet", no_argument , NULL, OPT_QUIET}, | ||
| 206 | + {"smp", no_argument, NULL, OPT_SMP}, | ||
| 207 | + {"threads", optional_argument, NULL, OPT_THREADS}, | ||
| 208 | + {NULL, 0, NULL, 0} | ||
| 209 | + }; | ||
| 210 | + int c = getopt_long (argc, argv, "a::b:d:i:l:D:p:qSt::h", | ||
| 211 | + long_options, &option_index); | ||
| 212 | + if (c == -1) | ||
| 213 | + break; | ||
| 214 | + switch (c) { | ||
| 215 | + case OPT_AFFINITY: | ||
| 216 | + case 'a': | ||
| 217 | + if (smp) { | ||
| 218 | + warn("-a ignored due to --smp\n"); | ||
| 219 | + break; | ||
| 220 | + } | ||
| 221 | + if (optarg != NULL) { | ||
| 222 | + affinity = atoi(optarg); | ||
| 223 | + setaffinity = AFFINITY_SPECIFIED; | ||
| 224 | + } else if (optind < argc && atoi(argv[optind])) { | ||
| 225 | + affinity = atoi(argv[optind]); | ||
| 226 | + setaffinity = AFFINITY_SPECIFIED; | ||
| 227 | + } else { | ||
| 228 | + setaffinity = AFFINITY_USEALL; | ||
| 229 | + } | ||
| 230 | + break; | ||
| 231 | + case OPT_BREAKTRACE: | ||
| 232 | + case 'b': | ||
| 233 | + tracelimit = atoi(optarg); | ||
| 234 | + break; | ||
| 235 | + case OPT_DISTANCE: | ||
| 236 | + case 'd': | ||
| 237 | + distance = atoi(optarg); | ||
| 238 | + break; | ||
| 239 | + case OPT_DURATION: | ||
| 240 | + case 'D': | ||
| 241 | + duration = parse_time_string(optarg); | ||
| 242 | + break; | ||
| 243 | + case OPT_INTERVAL: | ||
| 244 | + case 'i': | ||
| 245 | + interval = atoi(optarg); | ||
| 246 | + break; | ||
| 247 | + case OPT_JSON: | ||
| 248 | + strncpy(jsonfile, optarg, strnlen(optarg, MAX_PATH-1)); | ||
| 249 | + break; | ||
| 250 | + case OPT_HELP: | ||
| 251 | + case '?': | ||
| 252 | + case 'h': | ||
| 253 | + display_help(0); | ||
| 254 | + break; | ||
| 255 | + case OPT_LOOPS: | ||
| 256 | + case 'l': | ||
| 257 | + max_cycles = atoi(optarg); | ||
| 258 | + break; | ||
| 259 | + case OPT_PRIORITY: | ||
| 260 | + case 'p': | ||
| 261 | + priority = atoi(optarg); | ||
| 262 | + break; | ||
| 263 | + case OPT_QUIET: | ||
| 264 | + case 'q': | ||
| 265 | + quiet = 1; | ||
| 266 | + break; | ||
| 267 | + case OPT_SMP: | ||
| 268 | + case 'S': | ||
| 269 | + smp = 1; | ||
| 270 | + num_threads = max_cpus; | ||
| 271 | + setaffinity = AFFINITY_USEALL; | ||
| 272 | + break; | ||
| 273 | + case OPT_THREADS: | ||
| 274 | + case 't': | ||
| 275 | + if (smp) { | ||
| 276 | + warn("-t ignored due to --smp\n"); | ||
| 277 | + break; | ||
| 278 | + } | ||
| 279 | + if (optarg != NULL) | ||
| 280 | + num_threads = atoi(optarg); | ||
| 281 | + else if (optind < argc && atoi(argv[optind])) | ||
| 282 | + num_threads = atoi(argv[optind]); | ||
| 283 | + else | ||
| 284 | + num_threads = max_cpus; | ||
| 285 | + break; | ||
| 286 | + default: | ||
| 287 | + display_help(1); | ||
| 288 | + break; | ||
| 289 | + } | ||
| 290 | + } | ||
| 291 | + | ||
| 292 | + if (setaffinity == AFFINITY_SPECIFIED) { | ||
| 293 | + if (affinity < 0) | ||
| 294 | + error = 1; | ||
| 295 | + if (affinity >= max_cpus) { | ||
| 296 | + fprintf(stderr, "ERROR: CPU #%d not found, only %d CPUs available\n", | ||
| 297 | + affinity, max_cpus); | ||
| 298 | + error = 1; | ||
| 299 | + } | ||
| 300 | + } | ||
| 301 | + | ||
| 302 | + if (num_threads < 0) | ||
| 303 | + error = 1; | ||
| 304 | + | ||
| 305 | + if (priority < 0 || priority > 99) | ||
| 306 | + error = 1; | ||
| 307 | + | ||
| 308 | + if (num_threads < 1) | ||
| 309 | + error = 1; | ||
| 310 | + | ||
| 311 | + if (duration < 0) | ||
| 312 | + error = 1; | ||
| 313 | + | ||
| 314 | + if (priority && smp) | ||
| 315 | + sameprio = 1; | ||
| 316 | + | ||
| 317 | + if (error) | ||
| 318 | + display_help(error); | ||
| 319 | +} | ||
| 320 | + | ||
| 321 | + | ||
| 322 | +static int volatile shutdown; | ||
| 323 | + | ||
| 324 | +static void sighand(int sig __attribute__ ((unused))) | ||
| 325 | +{ | ||
| 326 | + shutdown = 1; | ||
| 327 | +} | ||
| 328 | + | ||
| 329 | +static void print_stat(FILE *fp __attribute__ ((unused)), | ||
| 330 | + struct params *receiver, struct params *sender, | ||
| 331 | + int verbose __attribute__ ((unused)), int quiet) | ||
| 332 | +{ | ||
| 333 | + int i; | ||
| 334 | + | ||
| 335 | + if (quiet) | ||
| 336 | + return; | ||
| 337 | + | ||
| 338 | + for (i = 0; i < num_threads; i++) { | ||
| 339 | + printf("#%1d: ID%d, P%d, CPU%d, I%ld; #%1d: ID%d, P%d, CPU%d, Cycles %d\n", | ||
| 340 | + i*2, receiver[i].tid, receiver[i].priority, receiver[i].cpu, | ||
| 341 | + receiver[i].delay.tv_nsec / 1000, | ||
| 342 | + i*2+1, sender[i].tid, sender[i].priority, sender[i].cpu, | ||
| 343 | + sender[i].samples); | ||
| 344 | + } | ||
| 345 | + for (i = 0; i < num_threads; i++) { | ||
| 346 | + printf("#%d -> #%d, Min %4d, Cur %4d, Avg %4d, Max %4d\n", | ||
| 347 | + i*2+1, i*2, | ||
| 348 | + receiver[i].mindiff, (int) receiver[i].diff.tv_usec, | ||
| 349 | + (int) ((receiver[i].sumdiff / receiver[i].samples) + 0.5), | ||
| 350 | + receiver[i].maxdiff); | ||
| 351 | + } | ||
| 352 | +} | ||
| 353 | + | ||
| 354 | +struct params_stats { | ||
| 355 | + struct params *receiver; | ||
| 356 | + struct params *sender; | ||
| 357 | +}; | ||
| 358 | + | ||
| 359 | +static void write_stats(FILE *f, void *data) | ||
| 360 | +{ | ||
| 361 | + struct params_stats *ps = data; | ||
| 362 | + struct params *s, *r; | ||
| 363 | + int i; | ||
| 364 | + | ||
| 365 | + fprintf(f, " \"num_threads\": %d,\n", num_threads); | ||
| 366 | + fprintf(f, " \"thread\": {\n"); | ||
| 367 | + for (i = 0; i < num_threads; i++) { | ||
| 368 | + s = &ps->sender[i]; | ||
| 369 | + r = &ps->receiver[i]; | ||
| 370 | + fprintf(f, " \"%u\": {\n", i); | ||
| 371 | + fprintf(f, " \"sender\": {\n"); | ||
| 372 | + fprintf(f, " \"cpu\": %d,\n", s->cpu); | ||
| 373 | + fprintf(f, " \"priority\": %d,\n", s->priority); | ||
| 374 | + fprintf(f, " \"samples\": %d,\n", s->samples); | ||
| 375 | + fprintf(f, " \"interval\": %ld\n", r->delay.tv_nsec/1000); | ||
| 376 | + fprintf(f, " },\n"); | ||
| 377 | + fprintf(f, " \"receiver\": {\n"); | ||
| 378 | + fprintf(f, " \"cpu\": %d,\n", r->cpu); | ||
| 379 | + fprintf(f, " \"priority\": %d,\n", r->priority); | ||
| 380 | + fprintf(f, " \"min\": %d,\n", r->mindiff); | ||
| 381 | + fprintf(f, " \"avg\": %.2f,\n", r->sumdiff/r->samples); | ||
| 382 | + fprintf(f, " \"max\": %d\n", r->maxdiff); | ||
| 383 | + fprintf(f, " }\n"); | ||
| 384 | + fprintf(f, " }%s\n", i == num_threads - 1 ? "" : ","); | ||
| 385 | + } | ||
| 386 | + fprintf(f, " }\n"); | ||
| 387 | +} | ||
| 388 | + | ||
| 389 | +int main(int argc, char *argv[]) | ||
| 390 | +{ | ||
| 391 | + int i; | ||
| 392 | + int max_cpus = sysconf(_SC_NPROCESSORS_CONF); | ||
| 393 | + int oldsamples = 1; | ||
| 394 | + struct params *receiver = NULL; | ||
| 395 | + struct params *sender = NULL; | ||
| 396 | + sigset_t sigset; | ||
| 397 | + struct timespec maindelay; | ||
| 398 | + | ||
| 399 | + rt_init(argc, argv); | ||
| 400 | + process_options(argc, argv); | ||
| 401 | + | ||
| 402 | + if (check_privs()) | ||
| 403 | + return 1; | ||
| 404 | + | ||
| 405 | + if (mlockall(MCL_CURRENT|MCL_FUTURE) == -1) { | ||
| 406 | + perror("mlockall"); | ||
| 407 | + return 1; | ||
| 408 | + } | ||
| 409 | + | ||
| 410 | + signal(SIGINT, sighand); | ||
| 411 | + signal(SIGTERM, sighand); | ||
| 412 | + signal(SIGALRM, sighand); | ||
| 413 | + | ||
| 414 | + if (duration) | ||
| 415 | + alarm(duration); | ||
| 416 | + | ||
| 417 | + receiver = calloc(num_threads, sizeof(struct params)); | ||
| 418 | + sender = calloc(num_threads, sizeof(struct params)); | ||
| 419 | + if (receiver == NULL || sender == NULL) | ||
| 420 | + goto nomem; | ||
| 421 | + | ||
| 422 | + testmutex = (pthread_mutex_t *) calloc(num_threads, sizeof(pthread_mutex_t)); | ||
| 423 | + syncmutex = (pthread_mutex_t *) calloc(num_threads, sizeof(pthread_mutex_t)); | ||
| 424 | + if (testmutex == NULL || syncmutex == NULL) | ||
| 425 | + goto nomem; | ||
| 426 | + | ||
| 427 | + for (i = 0; i < num_threads; i++) { | ||
| 428 | + receiver[i].mindiff = UINT_MAX; | ||
| 429 | + receiver[i].maxdiff = 0; | ||
| 430 | + receiver[i].sumdiff = 0.0; | ||
| 431 | + | ||
| 432 | + | ||
| 433 | + pthread_mutex_init(&testmutex[i], NULL); | ||
| 434 | + pthread_mutex_init(&syncmutex[i], NULL); | ||
| 435 | + | ||
| 436 | + /* Wait on first attempt */ | ||
| 437 | + pthread_mutex_lock(&testmutex[i]); | ||
| 438 | + | ||
| 439 | + receiver[i].num = i; | ||
| 440 | + receiver[i].cpu = i; | ||
| 441 | + switch (setaffinity) { | ||
| 442 | + case AFFINITY_UNSPECIFIED: receiver[i].cpu = -1; break; | ||
| 443 | + case AFFINITY_SPECIFIED: receiver[i].cpu = affinity; break; | ||
| 444 | + case AFFINITY_USEALL: receiver[i].cpu = i % max_cpus; break; | ||
| 445 | + } | ||
| 446 | + receiver[i].priority = priority; | ||
| 447 | + receiver[i].tracelimit = tracelimit; | ||
| 448 | + if (priority > 1 && !sameprio) | ||
| 449 | + priority--; | ||
| 450 | + receiver[i].delay.tv_sec = interval / USEC_PER_SEC; | ||
| 451 | + receiver[i].delay.tv_nsec = (interval % USEC_PER_SEC) * 1000; | ||
| 452 | + interval += distance; | ||
| 453 | + receiver[i].max_cycles = max_cycles; | ||
| 454 | + receiver[i].sender = 0; | ||
| 455 | + receiver[i].neighbor = &sender[i]; | ||
| 456 | + pthread_create(&receiver[i].threadid, NULL, semathread, &receiver[i]); | ||
| 457 | + memcpy(&sender[i], &receiver[i], sizeof(receiver[0])); | ||
| 458 | + sender[i].sender = 1; | ||
| 459 | + sender[i].neighbor = &receiver[i]; | ||
| 460 | + pthread_create(&sender[i].threadid, NULL, semathread, &sender[i]); | ||
| 461 | + } | ||
| 462 | + | ||
| 463 | + maindelay.tv_sec = 0; | ||
| 464 | + maindelay.tv_nsec = 50000000; /* 50 ms */ | ||
| 465 | + | ||
| 466 | + while (!shutdown) { | ||
| 467 | + for (i = 0; i < num_threads; i++) | ||
| 468 | + shutdown |= receiver[i].shutdown | sender[i].shutdown; | ||
| 469 | + | ||
| 470 | + if (receiver[0].samples > oldsamples || shutdown) { | ||
| 471 | + print_stat(stdout, receiver, sender, 0, quiet); | ||
| 472 | + if (!quiet) | ||
| 473 | + printf("\033[%dA", num_threads*2); | ||
| 474 | + } | ||
| 475 | + | ||
| 476 | + sigemptyset(&sigset); | ||
| 477 | + sigaddset(&sigset, SIGTERM); | ||
| 478 | + sigaddset(&sigset, SIGINT); | ||
| 479 | + pthread_sigmask(SIG_SETMASK, &sigset, NULL); | ||
| 480 | + | ||
| 481 | + nanosleep(&maindelay, NULL); | ||
| 482 | + | ||
| 483 | + sigemptyset(&sigset); | ||
| 484 | + pthread_sigmask(SIG_SETMASK, &sigset, NULL); | ||
| 485 | + } | ||
| 486 | + | ||
| 487 | + if (!quiet) | ||
| 488 | + printf("\033[%dB", num_threads*2 + 2); | ||
| 489 | + else | ||
| 490 | + print_stat(stdout, receiver, sender, 0, 0); | ||
| 491 | + | ||
| 492 | + for (i = 0; i < num_threads; i++) { | ||
| 493 | + receiver[i].shutdown = 1; | ||
| 494 | + sender[i].shutdown = 1; | ||
| 495 | + pthread_mutex_unlock(&testmutex[i]); | ||
| 496 | + pthread_mutex_unlock(&syncmutex[i]); | ||
| 497 | + } | ||
| 498 | + nanosleep(&receiver[0].delay, NULL); | ||
| 499 | + | ||
| 500 | + for (i = 0; i < num_threads; i++) { | ||
| 501 | + if (!receiver[i].stopped) | ||
| 502 | + pthread_kill(receiver[i].threadid, SIGTERM); | ||
| 503 | + if (!sender[i].stopped) | ||
| 504 | + pthread_kill(sender[i].threadid, SIGTERM); | ||
| 505 | + } | ||
| 506 | + | ||
| 507 | + for (i = 0; i < num_threads; i++) { | ||
| 508 | + pthread_mutex_destroy(&testmutex[i]); | ||
| 509 | + pthread_mutex_destroy(&syncmutex[i]); | ||
| 510 | + } | ||
| 511 | + | ||
| 512 | + if (strlen(jsonfile) != 0) { | ||
| 513 | + struct params_stats ps = { | ||
| 514 | + .receiver = receiver, | ||
| 515 | + .sender = sender, | ||
| 516 | + }; | ||
| 517 | + rt_write_json(jsonfile, 0, write_stats, &ps); | ||
| 518 | + } | ||
| 519 | + | ||
| 520 | +nomem: | ||
| 521 | + | ||
| 522 | + return 0; | ||
| 523 | +} | ||
| @@ -0,0 +1,143 @@ | |||
| 1 | + | ||
| 2 | +cyclictest does not catch all cases where packet forwarding | ||
| 3 | +latency can exceed a given threshold. | ||
| 4 | + | ||
| 5 | +Example: | ||
| 6 | + | ||
| 7 | +# taskset -c 3 ./queuelat -m 20000 -c 100 -p 13 -f `sh ./get_cpuinfo_mhz.sh` | ||
| 8 | + | ||
| 9 | +# rmmod targeted-ipi; insmod ./targeted-ipi.ko ipidest=3 nripis=200 interval=2 delay=10 | ||
| 10 | + | ||
| 11 | + <...>-4566 [003] .....11 4474.559880: tracing_mark_write: memmove block queue_size=28 queue_dec=279 queue_inc=307 delta=23685 ns | ||
| 12 | + <...>-4566 [003] .....11 4474.559912: tracing_mark_write: memmove block queue_size=63 queue_dec=279 queue_inc=314 delta=24198 ns | ||
| 13 | + <...>-4566 [003] .....11 4474.559937: tracing_mark_write: memmove block queue_size=97 queue_dec=279 queue_inc=313 delta=24090 ns | ||
| 14 | + <...>-4566 [003] .....11 4474.559965: tracing_mark_write: memmove block queue_size=130 queue_dec=279 queue_inc=312 delta=24048 ns | ||
| 15 | + <...>-4566 [003] .....11 4474.559993: tracing_mark_write: memmove block queue_size=162 queue_dec=279 queue_inc=311 delta=23957 ns | ||
| 16 | + <...>-4566 [003] .....11 4474.560018: tracing_mark_write: memmove block queue_size=193 queue_dec=279 queue_inc=310 delta=23912 ns | ||
| 17 | + <...>-4566 [003] .....11 4474.560046: tracing_mark_write: memmove block queue_size=225 queue_dec=279 queue_inc=311 delta=23965 ns | ||
| 18 | + <...>-4566 [003] .....11 4474.560074: tracing_mark_write: memmove block queue_size=257 queue_dec=279 queue_inc=311 delta=23971 ns | ||
| 19 | + <...>-4566 [003] .....11 4474.560102: tracing_mark_write: memmove block queue_size=288 queue_dec=279 queue_inc=310 delta=23902 ns | ||
| 20 | + <...>-4566 [003] .....11 4474.560127: tracing_mark_write: memmove block queue_size=320 queue_dec=279 queue_inc=311 delta=23945 ns | ||
| 21 | + <...>-4566 [003] .....11 4474.560155: tracing_mark_write: memmove block queue_size=351 queue_dec=279 queue_inc=310 delta=23921 ns | ||
| 22 | + <...>-4566 [003] .....11 4474.560180: tracing_mark_write: memmove block queue_size=381 queue_dec=279 queue_inc=309 delta=23839 ns | ||
| 23 | + <...>-4566 [003] .....11 4474.560208: tracing_mark_write: memmove block queue_size=412 queue_dec=279 queue_inc=310 delta=23876 ns | ||
| 24 | + <...>-4566 [003] .....11 4474.560236: tracing_mark_write: memmove block queue_size=443 queue_dec=279 queue_inc=310 delta=23886 ns | ||
| 25 | + <...>-4566 [003] .....11 4474.560261: tracing_mark_write: memmove block queue_size=474 queue_dec=279 queue_inc=310 delta=23901 ns | ||
| 26 | + <...>-4566 [003] .....11 4474.560288: tracing_mark_write: memmove block queue_size=505 queue_dec=279 queue_inc=310 delta=23891 ns | ||
| 27 | + <...>-4566 [003] .....11 4474.560316: tracing_mark_write: memmove block queue_size=535 queue_dec=279 queue_inc=309 delta=23822 ns | ||
| 28 | + <...>-4566 [003] .....11 4474.560341: tracing_mark_write: memmove block queue_size=565 queue_dec=279 queue_inc=309 delta=23815 ns | ||
| 29 | + <...>-4566 [003] .....11 4474.560353: tracing_mark_write: queue length exceeded: queue_size=565 max_queue_len=559 | ||
| 30 | + | ||
| 31 | +# taskset -c 3 cyclictest -m -n -q -p95 -D 60m -h60 -i 200 | ||
| 32 | + | ||
| 33 | +# rmmod targeted-ipi; insmod ./targeted-ipi.ko ipidest=3 nripis=20000 interval=2 delay=10 | ||
| 34 | + | ||
| 35 | +Cyclictest results: | ||
| 36 | + | ||
| 37 | +# Total: 000068099 | ||
| 38 | +# Min Latencies: 00001 | ||
| 39 | +# Avg Latencies: 00002 | ||
| 40 | +# Max Latencies: 00008 | ||
| 41 | +# Histogram Overflows: 00000 | ||
| 42 | + | ||
| 43 | + | ||
| 44 | +----- queuelat basics: | ||
| 45 | + | ||
| 46 | +Queuelat simulates a DPDK queue. From queuelat.c: | ||
| 47 | + | ||
| 48 | +Program parameters: | ||
| 49 | +max_queue_len: maximum latency allowed, in nanoseconds (int). | ||
| 50 | +cycles_per_packet: number of cycles to process one packet (int). | ||
| 51 | +mpps(million-packet-per-sec): million packets per second (float). | ||
| 52 | +tsc_freq_mhz: TSC frequency in MHz, as measured by TSC PIT calibration | ||
| 53 | +(search for "Detected XXX MHz processor" in dmesg, and use the integer part). | ||
| 54 | +timeout: timeout (in seconds). | ||
| 55 | + | ||
| 56 | +How it works | ||
| 57 | +============ | ||
| 58 | + | ||
| 59 | + The program in essence does: | ||
| 60 | + | ||
| 61 | + b = rdtsc(); | ||
| 62 | + memmove(dest, src, n); | ||
| 63 | + a = rdtsc(); | ||
| 64 | + | ||
| 65 | + delay = convert_to_ns(a - b); | ||
| 66 | + | ||
| 67 | + queue_size += packets_queued_in(delay); | ||
| 68 | + queue_size -= packets_processed; | ||
| 69 | + | ||
| 70 | + if (queue_size > max_queue_len) | ||
| 71 | + FAIL(); | ||
| 72 | + | ||
| 73 | +packets_processed is fixed, and is estimated as follows: | ||
| 74 | +n is determined first, so that the stats bucket with highest count | ||
| 75 | +takes max_latency/2. | ||
| 76 | +for max_latency/2, we calculate how many packets can be drained | ||
| 77 | +in that time (using cycles_per_packet). | ||
| 78 | + | ||
| 79 | +Queuelat output | ||
| 80 | +=============== | ||
| 81 | + | ||
| 82 | +During calibration, queuelat outputs the following table: | ||
| 83 | + | ||
| 84 | +[9600 - 9699] = 0 packetfillrates=[67 - 67] | ||
| 85 | +[9700 - 9799] = 7907 packetfillrates=[67 - 68] | ||
| 86 | +[9800 - 9899] = 42085 packetfillrates=[68 - 69] | ||
| 87 | +[9900 - 9999] = 7 packetfillrates=[69 - 69] | ||
| 88 | +[10000 - 10099] = 1 packetfillrates=[70 - 70] | ||
| 89 | + | | | | | ||
| 90 | + | | | | | ||
| 91 | + | | | |_________ [min - max] number of | ||
| 92 | + | | | packets the queue will reach | ||
| 93 | + | | | with specified mpps in this | ||
| 94 | + | | | time (without draining) | ||
| 95 | + | | | | ||
| 96 | + | | |______________________________ number of hits for this | ||
| 97 | + | | bucket | ||
| 98 | + | | | ||
| 99 | + | |______________________________________ min amount of time (ns) | ||
| 100 | + | this bucket accepts | ||
| 101 | + | | ||
| 102 | + |______________________________________________ max amount of time (ns) | ||
| 103 | + this bucket accepts | ||
| 104 | + | ||
| 105 | + | ||
| 106 | +On success, queuelat outputs a table similar to cyclictest: | ||
| 107 | + | ||
| 108 | +[7000 - 7099] = 0 | ||
| 109 | +[7100 - 7199] = 2 | ||
| 110 | +[7200 - 7299] = 2457 | ||
| 111 | +[7300 - 7399] = 21058 | ||
| 112 | + | | | | ||
| 113 | + | | |___________ Number of processing loops that hit this | ||
| 114 | + | | bucket. | ||
| 115 | + | | | ||
| 116 | + | |____________________ Maximum number of nanoseconds of this bucket. | ||
| 117 | + | | ||
| 118 | + | | ||
| 119 | + |___________________________ Minimum number of nanoseconds in this bucket. | ||
| 120 | + | ||
| 121 | +That is a processing loop will account into a bucket if its duration | ||
| 122 | +is | ||
| 123 | + | ||
| 124 | + min_number_ns_in_bucket < duration < max_number_ns_in_bucket | ||
| 125 | + | ||
| 126 | + | ||
| 127 | +Automatic determination of Mpps | ||
| 128 | +=============================== | ||
| 129 | + | ||
| 130 | +There is a script called determine_maximum_mpps.sh, which should be edited | ||
| 131 | +to include the pinning and -RT priority configuration for your machine. | ||
| 132 | + | ||
| 133 | +PREAMBLE="taskset -c 2 chrt -f 1" | ||
| 134 | +MAXLAT="20000" | ||
| 135 | +CYCLES_PER_PACKET="300" | ||
| 136 | + | ||
| 137 | +This script will find the maximum mpps parameter which can sustain: | ||
| 138 | + | ||
| 139 | + 1) 10 consecutive 30 second runs. | ||
| 140 | + 2) 1 run of 10 minutes. | ||
| 141 | + | ||
| 142 | +Without violating the latency specified with $MAXLAT. | ||
| 143 | + | ||
| @@ -0,0 +1,65 @@ | |||
| 1 | +.\" Hey, EMACS: -*- nroff -*- | ||
| 2 | +.TH DETERMINE_MAXIMUM_MPPS 8 "Dec 4, 2020" | ||
| 3 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 4 | +.\" Please adjust this date whenever revising the manpage. | ||
| 5 | +.\" | ||
| 6 | +.\" Some roff macros, for reference: | ||
| 7 | +.\" .nh disable hyphenation | ||
| 8 | +.\" .hy enable hyphenation | ||
| 9 | +.\" .ad l left justify | ||
| 10 | +.\" .ad b justify to both left and right margins | ||
| 11 | +.\" .nf disable filling | ||
| 12 | +.\" .fi enable filling | ||
| 13 | +.\" .br insert line break | ||
| 14 | +.\" .sp <n> insert n+1 empty lines | ||
| 15 | +.\" for manpage-specific macros, see man(7) | ||
| 16 | +.SH NAME | ||
| 17 | +determine_maximum_mpps \- Determine the maximum mpps the machine can handle | ||
| 18 | +.SH SYNOPSIS | ||
| 19 | +.LP | ||
| 20 | +determine_maximum_mpps [-a cpulist] [-m maxlat] [-n cycles] [-f | -r] [-p priority] [-h] | ||
| 21 | +.SH DESCRIPTION | ||
| 22 | +determine_maximum_mpps will find the maximum mpps parameter which can sustain | ||
| 23 | +.PP | ||
| 24 | +.RS | ||
| 25 | +1. 10 consecutive 30 second runs. | ||
| 26 | +.br | ||
| 27 | +2. 1 run of 10 minutes. | ||
| 28 | +.PP | ||
| 29 | +.RE | ||
| 30 | +Without violating the latency specified with $MAXLAT (default 20000) | ||
| 31 | +.PP | ||
| 32 | +.SH TERMINOLOGY | ||
| 33 | +mpps : million-packets-per-second | ||
| 34 | +.br | ||
| 35 | +.SH OPTIONS | ||
| 36 | +.TP | ||
| 37 | +.B \-a cpulist | ||
| 38 | +List of processors to run on. The default is processor 0 | ||
| 39 | +.br | ||
| 40 | +Numbers are separated by commas and may include ranges. Eg. 0,3,7\-11 | ||
| 41 | +.TP | ||
| 42 | +.B \-m maxlat | ||
| 43 | +Maximum latency in nanoseconds. The default is 20000. If the maximum is exceeded, that run of queuelat quits. | ||
| 44 | +.TP | ||
| 45 | +.B \-n cycles | ||
| 46 | +Extimated number of cycles it takes to process one packet. The default is 300 | ||
| 47 | +.TP | ||
| 48 | +.B \-f | ||
| 49 | +Set the scheduling policy to SCHED_FIFO. This is the default if not specified. | ||
| 50 | +.TP | ||
| 51 | +.B \-r | ||
| 52 | +Set the scheduling policy to SCHED_RR. | ||
| 53 | +.TP | ||
| 54 | +.B \-p priority | ||
| 55 | +default priority = 1. Valid numbers are from 1 to 99 | ||
| 56 | +.TP | ||
| 57 | +.B \-h | ||
| 58 | +help | ||
| 59 | +.LP | ||
| 60 | +.SH AUTHOR | ||
| 61 | +determine_maximum_mpps was written by | ||
| 62 | +.br | ||
| 63 | +Marcelo Tosatti <mtosatti@redhat.com> | ||
| 64 | +.PP | ||
| 65 | +This man page was written by John Kacur <jkacur@redhat.com> | ||
| @@ -0,0 +1,212 @@ | |||
| 1 | +#!/bin/bash | ||
| 2 | + | ||
| 3 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 4 | +# Copyright (C) 2018 Marcelo Tosatti <mtosatti@redhat.com> | ||
| 5 | + | ||
| 6 | +# A script to determine the maximum mpps. Logic: | ||
| 7 | +# Increase mpps in 0.5 units | ||
| 8 | +# | ||
| 9 | +MAXLAT="20000" | ||
| 10 | +CYCLES_PER_PACKET="300" | ||
| 11 | +OUTFILE=/usr/tmp/outfile | ||
| 12 | +PRIO=1 | ||
| 13 | +CPULIST=0 | ||
| 14 | +SCHED="" | ||
| 15 | + | ||
| 16 | +usage() | ||
| 17 | +{ | ||
| 18 | + echo "Usage:" | ||
| 19 | + echo "$(basename $0) [OPTIONS]" | ||
| 20 | + echo | ||
| 21 | + echo "-a cpulist" | ||
| 22 | + echo " List of processors to run on. The default is processor 0" | ||
| 23 | + echo " Numbers are separated by commas and may include ranges. Eg. 0,3,7-11" | ||
| 24 | + echo "-m maxlat" | ||
| 25 | + echo " maximum latency in nanoseconds. The default is 20000" | ||
| 26 | + echo " if the maximum is exceeded, that run of queuelat quits" | ||
| 27 | + echo "-n cycles" | ||
| 28 | + echo " Estimated number of cycles it takes to process one packet" | ||
| 29 | + echo " The default is 300" | ||
| 30 | + echo "-f" | ||
| 31 | + echo " Set the scheduling policy to SCHED_FIFO." | ||
| 32 | + echo " This is the default if not specified" | ||
| 33 | + echo "-r" | ||
| 34 | + echo " Set the scheduling policy to SCHED_RR". | ||
| 35 | + echo "-p priority" | ||
| 36 | + echo " default priority = 1. Valid numbers are from 1 to 99" | ||
| 37 | + echo "-h" | ||
| 38 | + echo " help" | ||
| 39 | + echo " print this help message and exit" | ||
| 40 | + exit | ||
| 41 | +} | ||
| 42 | + | ||
| 43 | +get_cpuinfo_mhz() | ||
| 44 | +{ | ||
| 45 | + # Try to determine MHz from /proc/cpuinfo | ||
| 46 | + FREQ_MHZ=$(grep "cpu MHz" /proc/cpuinfo | cut -f 3 -d " " | sort -rn | head -n1) | ||
| 47 | + | ||
| 48 | + # Try to determine MHz from /sys/firmware/devicetree/base/cpus/timebase-frequency | ||
| 49 | + if [ -z $FREQ_MHZ ]; then | ||
| 50 | + FREQ_MHZ=$(($((16#$(hexdump -e '1/1 "%02X"' /sys/firmware/devicetree/base/cpus/timebase-frequency)))/1000000)) | ||
| 51 | + fi | ||
| 52 | + | ||
| 53 | + echo "$FREQ_MHZ" | ||
| 54 | +} | ||
| 55 | + | ||
| 56 | +# Check that the scheduling policy hasn't already been set | ||
| 57 | +# Exit with an error message if it has | ||
| 58 | +check_sched() | ||
| 59 | +{ | ||
| 60 | + if [ "${SCHED}" != "" ]; then | ||
| 61 | + echo "Specify -f or -r, but not both" | ||
| 62 | + usage | ||
| 63 | + fi | ||
| 64 | +} | ||
| 65 | + | ||
| 66 | +# Process command line options | ||
| 67 | +while getopts ":a:frp:m:n:h" opt; do | ||
| 68 | + case ${opt} in | ||
| 69 | + a ) CPULIST="${OPTARG}" ;; | ||
| 70 | + m ) MAXLAT="${OPTARG}" ;; | ||
| 71 | + n ) CYCLES_PER_PACKET="${OPTARG}" ;; | ||
| 72 | + f ) check_sched; SCHED="-f" ;; | ||
| 73 | + r ) check_sched; SCHED="-r" ;; | ||
| 74 | + p ) PRIO="${OPTARG}" ;; | ||
| 75 | + h ) usage ;; | ||
| 76 | + * ) echo "no such option"; usage ;; | ||
| 77 | + esac | ||
| 78 | +done | ||
| 79 | + | ||
| 80 | +shift $((OPTIND -1 )) | ||
| 81 | + | ||
| 82 | +# If the user hasn't specified a scheduling policy | ||
| 83 | +# then set it to the default SCHED_FIFO | ||
| 84 | +if [ "${SCHED}" == "" ]; then | ||
| 85 | + SCHED="-f" | ||
| 86 | +fi | ||
| 87 | + | ||
| 88 | +# Error checking that the user entered a priority between 1 and 99 | ||
| 89 | +if [[ "${PRIO}" -lt "1" ]] || [[ "${PRIO}" -gt "99" ]]; then | ||
| 90 | + echo "PRIO must be a number between 1 and 99" | ||
| 91 | + usage | ||
| 92 | +fi | ||
| 93 | + | ||
| 94 | +PREAMBLE="taskset -c ${CPULIST} chrt ${SCHED} ${PRIO}" | ||
| 95 | + | ||
| 96 | +echo "Determining maximum mpps the machine can handle" | ||
| 97 | +echo "Will take a few minutes to determine mpps value" | ||
| 98 | +echo "And 10 minutes run to confirm the final mpps value is stable" | ||
| 99 | + | ||
| 100 | +for mpps in $(seq 3 3 50); do | ||
| 101 | + echo testing "$mpps" Mpps | ||
| 102 | + | ||
| 103 | + OUTFILE=$(mktemp) | ||
| 104 | + $PREAMBLE queuelat -m $MAXLAT -c $CYCLES_PER_PACKET -f "$(get_cpuinfo_mhz)" -p "$mpps" -t 30 > "$OUTFILE" | ||
| 105 | + | ||
| 106 | + exceeded=$(grep exceeded "$OUTFILE") | ||
| 107 | + if [ ! -z "$exceeded" ]; then | ||
| 108 | + echo mpps failed: "$mpps" | ||
| 109 | + break; | ||
| 110 | + fi | ||
| 111 | + echo success | ||
| 112 | +done | ||
| 113 | +echo first loop mpps: "$mpps" | ||
| 114 | + | ||
| 115 | +first_mpps=$(($mpps - 1)) | ||
| 116 | +for mpps in $(seq $first_mpps -1 3); do | ||
| 117 | + echo testing "$mpps" Mpps | ||
| 118 | + | ||
| 119 | + OUTFILE=$(mktemp) | ||
| 120 | + $PREAMBLE queuelat -m $MAXLAT -c $CYCLES_PER_PACKET -f "$(get_cpuinfo_mhz)" -p "$mpps" -t 30 > "$OUTFILE" | ||
| 121 | + | ||
| 122 | + exceeded=$(grep exceeded "$OUTFILE") | ||
| 123 | + if [ -z "$exceeded" ]; then | ||
| 124 | + echo mpps success "$mpps" | ||
| 125 | + break; | ||
| 126 | + fi | ||
| 127 | + echo failure | ||
| 128 | +done | ||
| 129 | + | ||
| 130 | +second_mpps=$(echo "$mpps + 0.3" | bc) | ||
| 131 | +echo second loop mpps: "$mpps" | ||
| 132 | + | ||
| 133 | +for mpps in $(seq "$second_mpps" 0.3 $first_mpps); do | ||
| 134 | + echo testing "$mpps" Mpps | ||
| 135 | + | ||
| 136 | + OUTFILE=$(mktemp) | ||
| 137 | + $PREAMBLE queuelat -m $MAXLAT -c $CYCLES_PER_PACKET -f "$(get_cpuinfo_mhz)" -p "$mpps" -t 30 > "$OUTFILE" | ||
| 138 | + | ||
| 139 | + exceeded=$(grep exceeded "$OUTFILE") | ||
| 140 | + if [ ! -z "$exceeded" ]; then | ||
| 141 | + echo mpps failure "$mpps" | ||
| 142 | + break; | ||
| 143 | + fi | ||
| 144 | + echo success | ||
| 145 | +done | ||
| 146 | + | ||
| 147 | +echo third loop mpps: "$mpps" | ||
| 148 | +third_mpps=$(echo "$mpps -0.1" | bc) | ||
| 149 | + | ||
| 150 | +for mpps in $(seq "$third_mpps" -0.1 3); do | ||
| 151 | + echo testing "$mpps" Mpps | ||
| 152 | + | ||
| 153 | + OUTFILE=$(mktemp) | ||
| 154 | + $PREAMBLE queuelat -m $MAXLAT -c $CYCLES_PER_PACKET -f "$(get_cpuinfo_mhz)" -p "$mpps" -t 30 > "$OUTFILE" | ||
| 155 | + | ||
| 156 | + exceeded=$(grep exceeded "$OUTFILE") | ||
| 157 | + if [ -z "$exceeded" ]; then | ||
| 158 | + echo mpps success "$mpps" | ||
| 159 | + break; | ||
| 160 | + fi | ||
| 161 | + echo failure | ||
| 162 | +done | ||
| 163 | + | ||
| 164 | +export queuelat_failure=1 | ||
| 165 | +while [ $queuelat_failure == 1 ]; do | ||
| 166 | + | ||
| 167 | + export queuelat_failure=0 | ||
| 168 | + | ||
| 169 | + echo -n "Starting 10 runs of 30 seconds with " | ||
| 170 | + echo "$mpps Mpps" | ||
| 171 | + | ||
| 172 | + for i in $(seq 1 10); do | ||
| 173 | + $PREAMBLE queuelat -m $MAXLAT -c $CYCLES_PER_PACKET -f "$(get_cpuinfo_mhz)" -p "$mpps" -t 30 > "$OUTFILE" | ||
| 174 | + exceeded=$(grep exceeded "$OUTFILE") | ||
| 175 | + | ||
| 176 | + if [ ! -z "$exceeded" ]; then | ||
| 177 | + echo "mpps failure (run $i) $mpps" | ||
| 178 | + export queuelat_failure=1 | ||
| 179 | + mpps=$(echo "$mpps" - 0.1 | bc) | ||
| 180 | + export mpps | ||
| 181 | + break | ||
| 182 | + fi | ||
| 183 | + echo "run $i success" | ||
| 184 | + done | ||
| 185 | + | ||
| 186 | +done | ||
| 187 | + | ||
| 188 | +export queuelat_failure=1 | ||
| 189 | +while [ $queuelat_failure == 1 ]; do | ||
| 190 | + | ||
| 191 | + export queuelat_failure=0 | ||
| 192 | + | ||
| 193 | + echo -n "Starting 10 minutes run with " | ||
| 194 | + echo "$mpps Mpps" | ||
| 195 | + | ||
| 196 | + $PREAMBLE queuelat -m $MAXLAT -c $CYCLES_PER_PACKET -f "$(get_cpuinfo_mhz)" -p "$mpps" -t 600 > "$OUTFILE" | ||
| 197 | + exceeded=$(grep exceeded "$OUTFILE") | ||
| 198 | + | ||
| 199 | + if [ ! -z "$exceeded" ]; then | ||
| 200 | + echo "mpps failure (run $i) $mpps" | ||
| 201 | + export queuelat_failure=1 | ||
| 202 | + mpps=$(echo "$mpps" - 0.1 | bc) | ||
| 203 | + export mpps | ||
| 204 | + continue | ||
| 205 | + fi | ||
| 206 | + echo "run $i success" | ||
| 207 | +done | ||
| 208 | + | ||
| 209 | +echo Final mpps is: "$mpps" | ||
| 210 | + | ||
| 211 | +unset queuelat_failure | ||
| 212 | +unset mpps | ||
| @@ -0,0 +1,51 @@ | |||
| 1 | +.\" Hey, EMACS: -*- nroff -*- | ||
| 2 | +.TH QUEUELAT 8 "Sept 3, 2018" | ||
| 3 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 4 | +.\" Please adjust this date whenever revising the manpage. | ||
| 5 | +.\" | ||
| 6 | +.\" Some roff macros, for reference: | ||
| 7 | +.\" .nh disable hyphenation | ||
| 8 | +.\" .hy enable hyphenation | ||
| 9 | +.\" .ad l left justify | ||
| 10 | +.\" .ad b justify to both left and right margins | ||
| 11 | +.\" .nf disable filling | ||
| 12 | +.\" .fi enable filling | ||
| 13 | +.\" .br insert line break | ||
| 14 | +.\" .sp <n> insert n+1 empty lines | ||
| 15 | +.\" for manpage-specific macros, see man(7) | ||
| 16 | +.SH NAME | ||
| 17 | +queuelat \- Queue latency test program | ||
| 18 | +.SH SYNOPSIS | ||
| 19 | +.LP | ||
| 20 | +queuelat [-c|--cycles N] [-f|--freq F] [-h|--help] [-m|--max-len LEN] [-p|--packets F] [-q|--queue-len N] [-t|--timeout TIME] | ||
| 21 | +.SH DESCRIPTION | ||
| 22 | +queuelat simulates a network queue checking for latency | ||
| 23 | +violations in packet processing. | ||
| 24 | + | ||
| 25 | +.SH OPTIONS | ||
| 26 | +A summary of options is included below. | ||
| 27 | +.TP | ||
| 28 | +.B \-c, \-\-cycles=N | ||
| 29 | +Estimated number of cycles it takes to process one packet. This value should come from the envisioned packet forwarding application being simulated. | ||
| 30 | +.TP | ||
| 31 | +.B \-f, \-\-freq=F | ||
| 32 | +TSC frequency in MHz. | ||
| 33 | +.TP | ||
| 34 | +.B \-h, \-\-help | ||
| 35 | +Show help | ||
| 36 | +.TP | ||
| 37 | +.B \-m, \-\-max-len=N | ||
| 38 | +Maximum allowed latency, in nanoseconds. If latency to process any packet exceeds this value, the program quits, writing a message to the trace buffer. | ||
| 39 | +.TP | ||
| 40 | +.B \-p, \-\-packets=F | ||
| 41 | +Million packets per second that arrive for processing. | ||
| 42 | +.TP | ||
| 43 | +.B \-q, \-\-queue-len=N | ||
| 44 | +Minimum queue length to print in the trace | ||
| 45 | +.TP | ||
| 46 | +.B \-t, \-\-timeout=TIME | ||
| 47 | +Timeout in seconds to quit the program. | ||
| 48 | + | ||
| 49 | +.SH AUTHOR | ||
| 50 | +queuelat was written by Marcelo Tosatti <mtosatti@redhat.com> | ||
| 51 | +.br | ||
| @@ -0,0 +1,673 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | + | ||
| 3 | +/* | ||
| 4 | + * Copyright (C) 2018 Marcelo Tosatti <mtosatti@redhat.com> | ||
| 5 | + * Copyright (C) 2019 John Kacur <jkacur@redhat.com> | ||
| 6 | + * Copyright (C) 2019 Clark Williams <williams@redhat.com> | ||
| 7 | + */ | ||
| 8 | + | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | +/* Program parameters: | ||
| 24 | + * max_queue_len: maximum latency allowed, in nanoseconds (int). | ||
| 25 | + * cycles_per_packet: number of cycles to process one packet (int). | ||
| 26 | + * mpps(million-packet-per-sec): million packets per second (float). | ||
| 27 | + * tsc_freq_mhz: TSC frequency in MHz, as measured by TSC PIT calibration | ||
| 28 | + * (search for "Detected XXX MHz processor" in dmesg, and use the integer part). | ||
| 29 | + * | ||
| 30 | + * How it works | ||
| 31 | + * ============ | ||
| 32 | + * | ||
| 33 | + * The program in essence does: | ||
| 34 | + * | ||
| 35 | + * b = rdtsc(); | ||
| 36 | + * memmove(dest, src, n); | ||
| 37 | + * a = rdtsc(); | ||
| 38 | + * | ||
| 39 | + * delay = convert_to_ns(a - b); | ||
| 40 | + * | ||
| 41 | + * queue_size += packets_queued_in(delay); | ||
| 42 | + * queue_size -= packets_processed; | ||
| 43 | + * | ||
| 44 | + * if (queue_size > max_queue_len) | ||
| 45 | + * FAIL(); | ||
| 46 | + * | ||
| 47 | + * packets_processed is fixed, and is estimated as follows: | ||
| 48 | + * n is determined first, so that the stats bucket with highest count | ||
| 49 | + * takes max_latency/2. | ||
| 50 | + * for max_latency/2, we calculate how many packets can be drained | ||
| 51 | + * in that time (using cycles_per_packet). | ||
| 52 | + * | ||
| 53 | + */ | ||
| 54 | + | ||
| 55 | +int maxlatency; | ||
| 56 | +int cycles_per_packet; | ||
| 57 | +float mpps; | ||
| 58 | +int timeout_secs; | ||
| 59 | +int min_queue_size_to_print; | ||
| 60 | + | ||
| 61 | +/* Derived constants */ | ||
| 62 | + | ||
| 63 | +float cycles_to_ns; | ||
| 64 | +int max_queue_len; | ||
| 65 | + | ||
| 66 | +int default_n; | ||
| 67 | +int nr_packets_drain_per_block; | ||
| 68 | + | ||
| 69 | +/* | ||
| 70 | + * Parameters for the stats collection buckets | ||
| 71 | + */ | ||
| 72 | + | ||
| 73 | + | ||
| 74 | + | ||
| 75 | + | ||
| 76 | + | ||
| 77 | +unsigned long long buckets[NR_BUCKETS+1]; | ||
| 78 | +unsigned long long total_count; | ||
| 79 | + | ||
| 80 | + | ||
| 81 | + | ||
| 82 | +static int val_to_bucket(unsigned long long val) | ||
| 83 | +{ | ||
| 84 | + int bucket_nr = val / VALS_PER_BUCKET; | ||
| 85 | + if (bucket_nr >= NR_BUCKETS) | ||
| 86 | + return OUTLIER_BUCKET; | ||
| 87 | + return bucket_nr; | ||
| 88 | +} | ||
| 89 | + | ||
| 90 | +static void account(unsigned long long val) | ||
| 91 | +{ | ||
| 92 | + int bucket_nr = val_to_bucket(val); | ||
| 93 | + buckets[bucket_nr]++; | ||
| 94 | + total_count++; | ||
| 95 | +} | ||
| 96 | + | ||
| 97 | +static unsigned long long total_samples(void) | ||
| 98 | +{ | ||
| 99 | + int i; | ||
| 100 | + unsigned long long total = 0; | ||
| 101 | + | ||
| 102 | + for (i = 0; i <= OUTLIER_BUCKET; i++) | ||
| 103 | + total += buckets[i]; | ||
| 104 | + | ||
| 105 | + return total; | ||
| 106 | +} | ||
| 107 | + | ||
| 108 | +static void print_all_buckets(void) | ||
| 109 | +{ | ||
| 110 | + int i, print_dotdotdot = 0; | ||
| 111 | + | ||
| 112 | + for (i = 0; i <= OUTLIER_BUCKET; i++) { | ||
| 113 | + int bucket_nr; | ||
| 114 | + unsigned long long val = i*VALS_PER_BUCKET; | ||
| 115 | + | ||
| 116 | + bucket_nr = val_to_bucket(val); | ||
| 117 | + | ||
| 118 | + if (bucket_nr != OUTLIER_BUCKET) { | ||
| 119 | + int n_bucketnr = bucket_nr+1; | ||
| 120 | + if (buckets[bucket_nr] == buckets[n_bucketnr]) { | ||
| 121 | + print_dotdotdot = 1; | ||
| 122 | + continue; | ||
| 123 | + } | ||
| 124 | + if (print_dotdotdot) { | ||
| 125 | + printf("...\n"); | ||
| 126 | + print_dotdotdot = 0; | ||
| 127 | + } | ||
| 128 | + printf("[%lld - %lld] = %lld\n", val, | ||
| 129 | + val + VALS_PER_BUCKET-1, | ||
| 130 | + buckets[bucket_nr]); | ||
| 131 | + } else { | ||
| 132 | + if (print_dotdotdot) { | ||
| 133 | + printf("...\n"); | ||
| 134 | + print_dotdotdot = 0; | ||
| 135 | + } | ||
| 136 | + printf("[%lld - END] = %lld\n", val, | ||
| 137 | + buckets[bucket_nr]); | ||
| 138 | + } | ||
| 139 | + } | ||
| 140 | +} | ||
| 141 | + | ||
| 142 | +static void print_max_bucketsec(void) | ||
| 143 | +{ | ||
| 144 | + int i, bucket_nr; | ||
| 145 | + unsigned long long highest_val = 0; | ||
| 146 | + | ||
| 147 | + for (i = 0; i <= OUTLIER_BUCKET; i++) { | ||
| 148 | + unsigned long long val = i*VALS_PER_BUCKET; | ||
| 149 | + | ||
| 150 | + bucket_nr = val_to_bucket(val); | ||
| 151 | + | ||
| 152 | + if (buckets[bucket_nr] != 0) | ||
| 153 | + highest_val = val; | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + bucket_nr = val_to_bucket(highest_val); | ||
| 157 | + printf("Max loop processing time: [%lld - %lld] = %lld\n", highest_val, | ||
| 158 | + highest_val + VALS_PER_BUCKET-1, | ||
| 159 | + buckets[bucket_nr]); | ||
| 160 | + | ||
| 161 | + return; | ||
| 162 | +} | ||
| 163 | + | ||
| 164 | +static void print_min_bucketsec(void) | ||
| 165 | +{ | ||
| 166 | + int i, bucket_nr; | ||
| 167 | + unsigned long long min_val = 0; | ||
| 168 | + | ||
| 169 | + for (i = 0; i <= OUTLIER_BUCKET; i++) { | ||
| 170 | + unsigned long long val = i*VALS_PER_BUCKET; | ||
| 171 | + | ||
| 172 | + bucket_nr = val_to_bucket(val); | ||
| 173 | + | ||
| 174 | + if (buckets[bucket_nr] != 0) { | ||
| 175 | + min_val = val; | ||
| 176 | + break; | ||
| 177 | + } | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + bucket_nr = val_to_bucket(min_val); | ||
| 181 | + printf("Min loop processing time: [%lld - %lld] = %lld\n", min_val, | ||
| 182 | + min_val + VALS_PER_BUCKET-1, | ||
| 183 | + buckets[bucket_nr]); | ||
| 184 | + | ||
| 185 | + return; | ||
| 186 | +} | ||
| 187 | + | ||
| 188 | +static void print_avg_bucketsec(void) | ||
| 189 | +{ | ||
| 190 | + int i, bucket_nr; | ||
| 191 | + unsigned long long total_sum = 0; | ||
| 192 | + unsigned long long nr_hits = 0; | ||
| 193 | + | ||
| 194 | + for (i = 0; i <= OUTLIER_BUCKET; i++) { | ||
| 195 | + unsigned long long val = i*VALS_PER_BUCKET; | ||
| 196 | + unsigned long long maxtime; | ||
| 197 | + | ||
| 198 | + bucket_nr = val_to_bucket(val); | ||
| 199 | + | ||
| 200 | + maxtime = val + VALS_PER_BUCKET-1; | ||
| 201 | + total_sum = total_sum + maxtime*buckets[bucket_nr]; | ||
| 202 | + | ||
| 203 | + nr_hits = nr_hits + buckets[bucket_nr]; | ||
| 204 | + } | ||
| 205 | + | ||
| 206 | + printf("Avg loop processing time: %lld\n", total_sum / nr_hits); | ||
| 207 | +} | ||
| 208 | + | ||
| 209 | +static void print_all_buckets_drainlength(void) | ||
| 210 | +{ | ||
| 211 | + int i, print_dotdotdot = 0; | ||
| 212 | + | ||
| 213 | + for (i = 0; i <= OUTLIER_BUCKET; i++) { | ||
| 214 | + int bucket_nr; | ||
| 215 | + unsigned long long val = i*VALS_PER_BUCKET; | ||
| 216 | + | ||
| 217 | + bucket_nr = val_to_bucket(val); | ||
| 218 | + | ||
| 219 | + if (bucket_nr != OUTLIER_BUCKET) { | ||
| 220 | + unsigned long long mindelta, maxdelta; | ||
| 221 | + int nr_packets_minfill, nr_packets_maxfill; | ||
| 222 | + int n_bucketnr = bucket_nr+1; | ||
| 223 | + | ||
| 224 | + if (buckets[bucket_nr] == buckets[n_bucketnr]) { | ||
| 225 | + print_dotdotdot = 1; | ||
| 226 | + continue; | ||
| 227 | + } | ||
| 228 | + if (print_dotdotdot) { | ||
| 229 | + printf("...\n"); | ||
| 230 | + print_dotdotdot = 0; | ||
| 231 | + } | ||
| 232 | + | ||
| 233 | + mindelta = val; | ||
| 234 | + maxdelta = val + VALS_PER_BUCKET-1; | ||
| 235 | + | ||
| 236 | + nr_packets_minfill = mindelta * mpps * 1000000 / NSEC_PER_SEC; | ||
| 237 | + nr_packets_maxfill = maxdelta * mpps * 1000000 / NSEC_PER_SEC; | ||
| 238 | + | ||
| 239 | + printf("[%lld - %lld] = %lld packetfillrates=[%d - %d]\n", val, | ||
| 240 | + val + VALS_PER_BUCKET-1, | ||
| 241 | + buckets[bucket_nr], | ||
| 242 | + nr_packets_minfill, | ||
| 243 | + nr_packets_maxfill); | ||
| 244 | + } else { | ||
| 245 | + if (print_dotdotdot) { | ||
| 246 | + printf("...\n"); | ||
| 247 | + print_dotdotdot = 0; | ||
| 248 | + } | ||
| 249 | + printf("[%lld - END] = %lld\n", val, | ||
| 250 | + buckets[bucket_nr]); | ||
| 251 | + } | ||
| 252 | + } | ||
| 253 | +} | ||
| 254 | + | ||
| 255 | +typedef unsigned long long cycles_t; | ||
| 256 | +typedef unsigned long long usecs_t; | ||
| 257 | +typedef unsigned long long u64; | ||
| 258 | + | ||
| 259 | + | ||
| 260 | + | ||
| 261 | + | ||
| 262 | + | ||
| 263 | + | ||
| 264 | + | ||
| 265 | + | ||
| 266 | + | ||
| 267 | + | ||
| 268 | + | ||
| 269 | + | ||
| 270 | + | ||
| 271 | + | ||
| 272 | + | ||
| 273 | +static inline unsigned long long __rdtscll(void) | ||
| 274 | +{ | ||
| 275 | + DECLARE_ARGS(val, low, high); | ||
| 276 | + | ||
| 277 | + asm volatile("mfence; rdtsc" : EAX_EDX_RET(val, low, high)); | ||
| 278 | + | ||
| 279 | + return EAX_EDX_VAL(val, low, high); | ||
| 280 | +} | ||
| 281 | + | ||
| 282 | + | ||
| 283 | + | ||
| 284 | + | ||
| 285 | + | ||
| 286 | +static inline unsigned long long __clock_gettime(void) | ||
| 287 | +{ | ||
| 288 | + struct timespec now; | ||
| 289 | + int ret; | ||
| 290 | + | ||
| 291 | + ret = clock_gettime(CLOCK_MONOTONIC, &now); | ||
| 292 | + if (ret < 0) | ||
| 293 | + return 0; | ||
| 294 | + | ||
| 295 | + // Combine seconds and nanoseconds into a single value in nanoseconds | ||
| 296 | + return (unsigned long long)now.tv_sec * 1000000000ULL + now.tv_nsec; | ||
| 297 | +} | ||
| 298 | + | ||
| 299 | + | ||
| 300 | + | ||
| 301 | + | ||
| 302 | + | ||
| 303 | +static void init_buckets(void) | ||
| 304 | +{ | ||
| 305 | + int i; | ||
| 306 | + | ||
| 307 | + for (i=0; i <= NR_BUCKETS; i++) | ||
| 308 | + buckets[i] = 0; | ||
| 309 | + | ||
| 310 | + total_count = 0; | ||
| 311 | +} | ||
| 312 | + | ||
| 313 | +static int find_highest_count_bucket(void) | ||
| 314 | +{ | ||
| 315 | + int i; | ||
| 316 | + int max_bucket = 0; | ||
| 317 | + unsigned long long max_val = 0; | ||
| 318 | + | ||
| 319 | + for (i=0; i <= NR_BUCKETS; i++) { | ||
| 320 | + if (buckets[i] > max_val) { | ||
| 321 | + max_bucket = i; | ||
| 322 | + max_val = buckets[i]; | ||
| 323 | + } | ||
| 324 | + } | ||
| 325 | + | ||
| 326 | + return max_bucket; | ||
| 327 | +} | ||
| 328 | + | ||
| 329 | +int tracing_mark_fd; | ||
| 330 | +static void trace_open(void) | ||
| 331 | +{ | ||
| 332 | + int fd; | ||
| 333 | + | ||
| 334 | + fd = open("/sys/kernel/debug/tracing/trace_marker", O_RDWR); | ||
| 335 | + | ||
| 336 | + if (fd == -1) { | ||
| 337 | + perror("open"); | ||
| 338 | + exit(0); | ||
| 339 | + } | ||
| 340 | + tracing_mark_fd = fd; | ||
| 341 | +} | ||
| 342 | + | ||
| 343 | +static void trace_write(char *buf, int len) | ||
| 344 | +{ | ||
| 345 | + int ret; | ||
| 346 | + | ||
| 347 | + ret = write(tracing_mark_fd, buf, len); | ||
| 348 | + if (ret == -1) { | ||
| 349 | + perror("write"); | ||
| 350 | + exit(0); | ||
| 351 | + } | ||
| 352 | +} | ||
| 353 | + | ||
| 354 | +static void run_n(int n) | ||
| 355 | +{ | ||
| 356 | + u64 a, b, delta; | ||
| 357 | + void *dest, *src; | ||
| 358 | + int i, loops = 50000; | ||
| 359 | + | ||
| 360 | + init_buckets(); | ||
| 361 | + | ||
| 362 | + dest = malloc(n); | ||
| 363 | + if (dest == NULL) { | ||
| 364 | + printf("failure to allocate %d bytes " | ||
| 365 | + " for dest\n", n); | ||
| 366 | + exit(0); | ||
| 367 | + } | ||
| 368 | + src = malloc(n); | ||
| 369 | + if (src == NULL) { | ||
| 370 | + printf("failure to allocate %d bytes " | ||
| 371 | + " for src\n", n); | ||
| 372 | + exit(0); | ||
| 373 | + } | ||
| 374 | + | ||
| 375 | + memset(src, 0, n); | ||
| 376 | + | ||
| 377 | + memmove(dest, src, n); | ||
| 378 | + for (i = 0; i < loops; i++) { | ||
| 379 | + gettick(b); | ||
| 380 | + memmove(dest, src, n); | ||
| 381 | + gettick(a); | ||
| 382 | + delta = (a - b) * cycles_to_ns; | ||
| 383 | + account(delta); | ||
| 384 | + } | ||
| 385 | + | ||
| 386 | + free(dest); | ||
| 387 | + free(src); | ||
| 388 | + | ||
| 389 | + return; | ||
| 390 | +} | ||
| 391 | + | ||
| 392 | +/* | ||
| 393 | + * Find the size of n such that the stats for the | ||
| 394 | + * function call | ||
| 395 | + * | ||
| 396 | + * memmove(dest, src, n). | ||
| 397 | + * | ||
| 398 | + * Takes MaximumLat/2 in the bucket that has most | ||
| 399 | + * entries. | ||
| 400 | + * | ||
| 401 | + */ | ||
| 402 | +static int measure_n(void) | ||
| 403 | +{ | ||
| 404 | + int time, bucket_nr; | ||
| 405 | + int n = 100000, delta = 0; | ||
| 406 | + | ||
| 407 | + do { | ||
| 408 | + if (delta > 0) | ||
| 409 | + n = n+1000; | ||
| 410 | + else if (delta < 0) | ||
| 411 | + n = n-1000; | ||
| 412 | + | ||
| 413 | + run_n(n); | ||
| 414 | + bucket_nr = find_highest_count_bucket(); | ||
| 415 | + | ||
| 416 | + time = bucket_nr * VALS_PER_BUCKET; | ||
| 417 | + | ||
| 418 | + delta = maxlatency/2 - time; | ||
| 419 | + } while (abs(delta) > VALS_PER_BUCKET*2); | ||
| 420 | + | ||
| 421 | + return n; | ||
| 422 | +} | ||
| 423 | + | ||
| 424 | +static void convert_to_ghz(double tsc_freq_mhz) | ||
| 425 | +{ | ||
| 426 | + float tsc_freq_ghz = tsc_freq_mhz/1000; | ||
| 427 | + | ||
| 428 | + cycles_to_ns = 1/tsc_freq_ghz; | ||
| 429 | + | ||
| 430 | + printf("tsc_freq_ghz = %f, cycles_to_ns = %f\n", tsc_freq_ghz, | ||
| 431 | + cycles_to_ns); | ||
| 432 | +} | ||
| 433 | + | ||
| 434 | + | ||
| 435 | +static void print_exit_info(void) | ||
| 436 | +{ | ||
| 437 | + print_all_buckets(); | ||
| 438 | + printf("\n ---------------- \n"); | ||
| 439 | + print_min_bucketsec(); | ||
| 440 | + print_max_bucketsec(); | ||
| 441 | + print_avg_bucketsec(); | ||
| 442 | + | ||
| 443 | +} | ||
| 444 | + | ||
| 445 | +void main_loop(void) | ||
| 446 | +{ | ||
| 447 | + u64 a, b, delta; | ||
| 448 | + void *dest, *src; | ||
| 449 | + int queue_size = 0; | ||
| 450 | + | ||
| 451 | + trace_open(); | ||
| 452 | + | ||
| 453 | + init_buckets(); | ||
| 454 | + | ||
| 455 | + dest = malloc(default_n); | ||
| 456 | + if (dest == NULL) { | ||
| 457 | + printf("failure to allocate %d bytes " | ||
| 458 | + " for dest\n", default_n); | ||
| 459 | + exit(0); | ||
| 460 | + } | ||
| 461 | + src = malloc(default_n); | ||
| 462 | + if (src == NULL) { | ||
| 463 | + printf("failure to allocate %d bytes " | ||
| 464 | + " for src\n", default_n); | ||
| 465 | + exit(0); | ||
| 466 | + } | ||
| 467 | + | ||
| 468 | + memset(src, 0, default_n); | ||
| 469 | + memmove(dest, src, default_n); | ||
| 470 | + | ||
| 471 | + while (1) { | ||
| 472 | + char buf[500]; | ||
| 473 | + int ret; | ||
| 474 | + int nr_packets_fill; | ||
| 475 | + | ||
| 476 | + gettick(b); | ||
| 477 | + memmove(dest, src, default_n); | ||
| 478 | + gettick(a); | ||
| 479 | + delta = (a - b) * cycles_to_ns; | ||
| 480 | + account(delta); | ||
| 481 | + | ||
| 482 | + /* fill up the queue by the amount of | ||
| 483 | + * time that passed */ | ||
| 484 | + nr_packets_fill = delta * mpps * 1000000 / NSEC_PER_SEC; | ||
| 485 | + queue_size += nr_packets_fill; | ||
| 486 | + | ||
| 487 | + /* decrease the queue by the amount of packets | ||
| 488 | + * processed in maxlatency/2 nanoseconds of | ||
| 489 | + * full processing. | ||
| 490 | + */ | ||
| 491 | + | ||
| 492 | + queue_size -= nr_packets_drain_per_block; | ||
| 493 | + | ||
| 494 | + if (queue_size < 0) | ||
| 495 | + queue_size = 0; | ||
| 496 | + | ||
| 497 | + if (queue_size <= min_queue_size_to_print) | ||
| 498 | + continue; | ||
| 499 | + | ||
| 500 | + ret = sprintf(buf, "memmove block queue_size=%d queue_dec=%d" | ||
| 501 | + " queue_inc=%d delta=%llu ns\n", queue_size, | ||
| 502 | + nr_packets_drain_per_block, | ||
| 503 | + nr_packets_fill, delta); | ||
| 504 | + trace_write(buf, ret); | ||
| 505 | + | ||
| 506 | + if (queue_size > max_queue_len) { | ||
| 507 | + printf("queue length exceeded: " | ||
| 508 | + " queue_size=%d max_queue_len=%d\n", | ||
| 509 | + queue_size, max_queue_len); | ||
| 510 | + ret = sprintf(buf, "queue length exceeded: " | ||
| 511 | + "queue_size=%d max_queue_len=%d\n", | ||
| 512 | + queue_size, max_queue_len); | ||
| 513 | + trace_write(buf, ret); | ||
| 514 | + print_exit_info(); | ||
| 515 | + exit(0); | ||
| 516 | + } | ||
| 517 | + } | ||
| 518 | + | ||
| 519 | + free(dest); | ||
| 520 | + free(src); | ||
| 521 | +} | ||
| 522 | + | ||
| 523 | +void sig_handler(int sig __attribute__ ((unused))) | ||
| 524 | +{ | ||
| 525 | + print_exit_info(); | ||
| 526 | + exit(0); | ||
| 527 | +} | ||
| 528 | + | ||
| 529 | +static void install_signals(void) | ||
| 530 | +{ | ||
| 531 | + signal(SIGALRM, sig_handler); | ||
| 532 | + signal(SIGINT, sig_handler); | ||
| 533 | +} | ||
| 534 | + | ||
| 535 | +int calculate_nr_packets_drain_per_block(void) | ||
| 536 | +{ | ||
| 537 | + unsigned long long maxcount; | ||
| 538 | + int i, time; | ||
| 539 | + int found = 0; | ||
| 540 | + int bucket_nr = find_highest_count_bucket(); | ||
| 541 | + | ||
| 542 | + maxcount = total_samples() / 40; | ||
| 543 | + | ||
| 544 | + for (i = bucket_nr+1; i <= NR_BUCKETS; i++) { | ||
| 545 | + if (buckets[i] < maxcount) { | ||
| 546 | + found = 1; | ||
| 547 | + break; | ||
| 548 | + } | ||
| 549 | + } | ||
| 550 | + | ||
| 551 | + if (found == 0) { | ||
| 552 | + printf("error, did not find right bucket with < 10%% of total\n"); | ||
| 553 | + exit(0); | ||
| 554 | + } | ||
| 555 | + | ||
| 556 | + time = i*VALS_PER_BUCKET + VALS_PER_BUCKET-1; | ||
| 557 | + nr_packets_drain_per_block = time / (cycles_per_packet*cycles_to_ns); | ||
| 558 | + | ||
| 559 | + return nr_packets_drain_per_block; | ||
| 560 | +} | ||
| 561 | + | ||
| 562 | +static void print_help(int error) | ||
| 563 | +{ | ||
| 564 | + printf("queuelat V %1.2f\n", VERSION); | ||
| 565 | + printf("Usage:\n" | ||
| 566 | + "queuelat <options>\n\n" | ||
| 567 | + "-c N --cycles N number of cycles to process one packet (int)\n" | ||
| 568 | + "-f F --freq F TSC frequency in MHz (float)\n" | ||
| 569 | + "-h --help show this help menu\n" | ||
| 570 | + "-m LEN --max-len LEN maximum latency allowed, in nanoseconds (int)\n" | ||
| 571 | + "-p F --packets F million packets per second (float)\n" | ||
| 572 | + "-q N --queue-len N minimum queue len to print trace (int)\n" | ||
| 573 | + "-t TIME --timeout TIME timeout, in seconds (int)\n" | ||
| 574 | + ); | ||
| 575 | + exit(error); | ||
| 576 | +} | ||
| 577 | + | ||
| 578 | +int main(int argc, char **argv) | ||
| 579 | +{ | ||
| 580 | + double tsc_freq_mhz; | ||
| 581 | + float max_queue_len_f; | ||
| 582 | + char *mvalue = NULL; | ||
| 583 | + char *cvalue = NULL; | ||
| 584 | + char *pvalue = NULL; | ||
| 585 | + char *fvalue = NULL; | ||
| 586 | + char *tvalue = NULL; | ||
| 587 | + char *qvalue = NULL; | ||
| 588 | + | ||
| 589 | + opterr = 0; | ||
| 590 | + | ||
| 591 | + for (;;) { | ||
| 592 | + static struct option options[] = { | ||
| 593 | + {"cycles", required_argument, NULL, 'c'}, | ||
| 594 | + {"freq", required_argument, NULL, 'f'}, | ||
| 595 | + {"help", no_argument, NULL, 'h'}, | ||
| 596 | + {"max-len", required_argument, NULL, 'm'}, | ||
| 597 | + {"packets", required_argument, NULL, 'p'}, | ||
| 598 | + {"queue-len", required_argument, NULL, 'q'}, | ||
| 599 | + {"timeout", required_argument, NULL, 't'}, | ||
| 600 | + {NULL, 0, NULL, 0} | ||
| 601 | + }; | ||
| 602 | + int c = getopt_long(argc, argv, "c:f:hm:p:q:t:", options, NULL); | ||
| 603 | + if (c == -1) | ||
| 604 | + break; | ||
| 605 | + switch (c) { | ||
| 606 | + case 'c': | ||
| 607 | + cvalue = optarg; | ||
| 608 | + break; | ||
| 609 | + case 'f': | ||
| 610 | + fvalue = optarg; | ||
| 611 | + break; | ||
| 612 | + case '?': | ||
| 613 | + case 'h': | ||
| 614 | + print_help(0); | ||
| 615 | + break; | ||
| 616 | + case 'm': | ||
| 617 | + mvalue = optarg; | ||
| 618 | + break; | ||
| 619 | + case 'p': | ||
| 620 | + pvalue = optarg; | ||
| 621 | + break; | ||
| 622 | + case 'q': | ||
| 623 | + qvalue = optarg; | ||
| 624 | + break; | ||
| 625 | + case 't': | ||
| 626 | + tvalue = optarg; | ||
| 627 | + break; | ||
| 628 | + default: | ||
| 629 | + print_help(1); | ||
| 630 | + break; | ||
| 631 | + } | ||
| 632 | + } | ||
| 633 | + | ||
| 634 | + if (mvalue == NULL || cvalue == NULL || pvalue == NULL || fvalue == NULL) { | ||
| 635 | + printf("options -m, -c, -p and -f are required\n"); | ||
| 636 | + exit(1); | ||
| 637 | + } | ||
| 638 | + | ||
| 639 | + install_signals(); | ||
| 640 | + | ||
| 641 | + maxlatency = atoi(mvalue); | ||
| 642 | + cycles_per_packet = atoi(cvalue); | ||
| 643 | + mpps = atof(pvalue); | ||
| 644 | + tsc_freq_mhz = atof(fvalue); | ||
| 645 | + | ||
| 646 | + if (tvalue) { | ||
| 647 | + int alarm_secs; | ||
| 648 | + alarm_secs = atoi(tvalue); | ||
| 649 | + alarm(alarm_secs); | ||
| 650 | + } | ||
| 651 | + | ||
| 652 | + if (qvalue) | ||
| 653 | + min_queue_size_to_print = atoi(qvalue); | ||
| 654 | + | ||
| 655 | + convert_to_ghz(tsc_freq_mhz); | ||
| 656 | + | ||
| 657 | + max_queue_len_f = maxlatency / (cycles_per_packet*cycles_to_ns); | ||
| 658 | + max_queue_len = max_queue_len_f; | ||
| 659 | + | ||
| 660 | + printf("max_queue_len = %d\n", max_queue_len); | ||
| 661 | + default_n = measure_n(); | ||
| 662 | + | ||
| 663 | + nr_packets_drain_per_block = calculate_nr_packets_drain_per_block(); | ||
| 664 | + print_all_buckets_drainlength(); | ||
| 665 | + | ||
| 666 | + printf("default_n=%d nr_packets_drain_per_block=%d\n", default_n, | ||
| 667 | + nr_packets_drain_per_block); | ||
| 668 | + | ||
| 669 | + main_loop(); | ||
| 670 | + | ||
| 671 | + return 0; | ||
| 672 | +} | ||
| 673 | + | ||
| @@ -0,0 +1,3 @@ | |||
| 1 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +obj-m := targeted-ipi.o | ||
| 3 | + | ||
| @@ -0,0 +1,2 @@ | |||
| 1 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +obj-$(CONFIG_TARGETED_IPI) += targeted-ipi.o | ||
| @@ -0,0 +1,12 @@ | |||
| 1 | + | ||
| 2 | +To build: | ||
| 3 | + | ||
| 4 | +make -C <path_to_kernel_src> M=$PWD | ||
| 5 | + | ||
| 6 | +To build against the running kernel use: | ||
| 7 | + | ||
| 8 | +make -C /lib/modules/`uname -r`/build M=$PWD | ||
| 9 | + | ||
| 10 | + | ||
| 11 | +Read targeted-ipi.c for the parameters. | ||
| 12 | + | ||
| @@ -0,0 +1,58 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | +/* | ||
| 3 | + * Copyright (C) 2018 Marcelo Tosatti <mtosatti@redhat.com> | ||
| 4 | + * Copyright (C) 2019 John Kacur <jkacur@redhat.com> | ||
| 5 | + * Copyright (C) 2019 Clark Williams <williams@redhat.com> | ||
| 6 | + */ | ||
| 7 | + | ||
| 8 | + | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | +static int ipidest; | ||
| 16 | +module_param(ipidest, int, S_IRUGO); | ||
| 17 | + | ||
| 18 | +/* number of ipis */ | ||
| 19 | +static int nripis; | ||
| 20 | +module_param(nripis, int, S_IRUGO); | ||
| 21 | + | ||
| 22 | +/* interval between consecutive IPI calls */ | ||
| 23 | +static int interval; | ||
| 24 | +module_param(interval, int, S_IRUGO); | ||
| 25 | + | ||
| 26 | +/* how many microseconds to delay in IPI handler */ | ||
| 27 | +static int delay; | ||
| 28 | +module_param(delay, int, S_IRUGO); | ||
| 29 | + | ||
| 30 | +static void ipi_handler(void *info) | ||
| 31 | +{ | ||
| 32 | + udelay(interval); | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +static int targeted_ipi_init(void) | ||
| 36 | +{ | ||
| 37 | + int ret, i; | ||
| 38 | + | ||
| 39 | + for (i=0; i < nripis; i++) | ||
| 40 | + { | ||
| 41 | + ret = smp_call_function_single(ipidest, ipi_handler, NULL, 1); | ||
| 42 | + if (ret) { | ||
| 43 | + printk(KERN_ERR "i=%d smp_call_function_single ret=%d\n", i, ret); | ||
| 44 | + return 0; | ||
| 45 | + } | ||
| 46 | + udelay(interval); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + return 0; | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +static void targeted_ipi_exit(void) | ||
| 53 | +{ | ||
| 54 | +} | ||
| 55 | + | ||
| 56 | +module_init(targeted_ipi_init); | ||
| 57 | +module_exit(targeted_ipi_exit); | ||
| 58 | +MODULE_LICENSE("GPL"); | ||
| @@ -0,0 +1,65 @@ | |||
| 1 | +.\" | ||
| 2 | +.TH RT-MIGRATE-TEST 8 "September 18, 2020" | ||
| 3 | +# SPDX-License-Identifier: GPL-2.0-only | ||
| 4 | +.\" Please adjust this date whenever editing this manpage | ||
| 5 | +.SH NAME | ||
| 6 | +rt-migrate-test \- real-time task migration program | ||
| 7 | +.SH SYNOPSIS | ||
| 8 | +.LP | ||
| 9 | +rt-migrate-test [-c|--check] [-D|--duration TIME] [-e|--equal] [-h|--help] [--json FILENAME] [-l|--loops LOOPS] [-m|--maxerr TIME] [-p|--prio PRIO] [-r|--run-time TIME] [-s|--sleep-time TIME] [NR_TASKS] | ||
| 10 | +.SH DESCRIPTION | ||
| 11 | +Test real-time multiprocessor scheduling of tasks to ensure the highest priority tasks are running on all available CPUs | ||
| 12 | +.SH OPTIONS | ||
| 13 | +This program follows the usual GNU command line syntax, with long options starting with two dashes ('\-\-'). | ||
| 14 | +.br | ||
| 15 | +.TP | ||
| 16 | +In the summary of options, a value in brackets (), indicates a default value | ||
| 17 | +.br | ||
| 18 | +.TP | ||
| 19 | +.B \-c, \-\-check | ||
| 20 | +Stop if lower prio task is quicker than higher (off) | ||
| 21 | +.TP | ||
| 22 | +.B \-D, \-\-duration=TIME | ||
| 23 | +Specify a length for the test run. | ||
| 24 | +.br | ||
| 25 | +Append 'm', 'h', or 'd' to specify minutes, hours or days. | ||
| 26 | +.TP | ||
| 27 | +.B \-e, \-\-equal | ||
| 28 | +Use equal prio for #CPU-1 tasks (requires > 2 CPUS) | ||
| 29 | +.br | ||
| 30 | +.TP | ||
| 31 | +.B \-h, \-\-help | ||
| 32 | +Display usage | ||
| 33 | +.br | ||
| 34 | +.TP | ||
| 35 | +.B \-\-json=FILENAME | ||
| 36 | +Write final results into FILENAME, JSON formatted. | ||
| 37 | +.br | ||
| 38 | +.TP | ||
| 39 | +.B \-l \-\-loops=LOOPS | ||
| 40 | +Number of iterations to run (50) | ||
| 41 | +.br | ||
| 42 | +.TP | ||
| 43 | +.B \-m, \-\-maxerr=TIME | ||
| 44 | +Max allowed error (microsecs) | ||
| 45 | +.br | ||
| 46 | +.TP | ||
| 47 | +.B \-p, \-\-prio=PRIO | ||
| 48 | +base priority to start RT tasks with (2) | ||
| 49 | +.br | ||
| 50 | +.TP | ||
| 51 | +.B \-r, \-\-run\-time=TIME | ||
| 52 | +Run time (ms) to busy loop the threads (20) | ||
| 53 | +.br | ||
| 54 | +.TP | ||
| 55 | +.B \-s, \-\-sleep\-time=TIME | ||
| 56 | +Sleep time (ms) between intervals (100) | ||
| 57 | +.br | ||
| 58 | +.TP | ||
| 59 | +.B [NR_TASKS] | ||
| 60 | +number of tasks to run (number of cpus + 1) | ||
| 61 | +.br | ||
| 62 | +.SH AUTHOR | ||
| 63 | +rt-migrate-test was written by Steven Rostedt <srostedt@redhat.com> | ||
| 64 | +.TP | ||
| 65 | +This manual page was written by John Kacur <jkacur@redhat.com> | ||
| @@ -0,0 +1,685 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | +/* | ||
| 3 | + * rt-migrate-test.c | ||
| 4 | + * Copyright (C) 2007-2009 Steven Rostedt <srostedt@redhat.com> | ||
| 5 | + */ | ||
| 6 | + | ||
| 7 | + | ||
| 8 | + | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | +int nr_tasks; | ||
| 33 | +int lfd; | ||
| 34 | + | ||
| 35 | +static int mark_fd = -1; | ||
| 36 | +static __thread char buff[BUFSIZ+1]; | ||
| 37 | + | ||
| 38 | +static void setup_ftrace_marker(void) | ||
| 39 | +{ | ||
| 40 | + struct stat st; | ||
| 41 | + char *files[] = { | ||
| 42 | + "/sys/kernel/debug/tracing/trace_marker", | ||
| 43 | + "/debug/tracing/trace_marker", | ||
| 44 | + "/debugfs/tracing/trace_marker", | ||
| 45 | + }; | ||
| 46 | + int ret; | ||
| 47 | + unsigned int i; | ||
| 48 | + | ||
| 49 | + for (i = 0; i < (sizeof(files) / sizeof(char *)); i++) { | ||
| 50 | + ret = stat(files[i], &st); | ||
| 51 | + if (ret >= 0) | ||
| 52 | + goto found; | ||
| 53 | + } | ||
| 54 | + /* todo, check mounts system */ | ||
| 55 | + return; | ||
| 56 | +found: | ||
| 57 | + mark_fd = open(files[i], O_WRONLY); | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +static void ftrace_write(const char *fmt, ...) | ||
| 61 | +{ | ||
| 62 | + va_list ap; | ||
| 63 | + int n; | ||
| 64 | + | ||
| 65 | + if (mark_fd < 0) | ||
| 66 | + return; | ||
| 67 | + | ||
| 68 | + va_start(ap, fmt); | ||
| 69 | + n = vsnprintf(buff, BUFSIZ, fmt, ap); | ||
| 70 | + va_end(ap); | ||
| 71 | + | ||
| 72 | + write(mark_fd, buff, n); | ||
| 73 | +} | ||
| 74 | + | ||
| 75 | + | ||
| 76 | + | ||
| 77 | + | ||
| 78 | + | ||
| 79 | + | ||
| 80 | + | ||
| 81 | + | ||
| 82 | + | ||
| 83 | + | ||
| 84 | + | ||
| 85 | +/* 1 millisec off */ | ||
| 86 | + | ||
| 87 | + | ||
| 88 | + | ||
| 89 | + | ||
| 90 | +static unsigned long long interval = INTERVAL; | ||
| 91 | +static unsigned long long run_interval = RUN_INTERVAL; | ||
| 92 | +static unsigned long long max_err = MAX_ERR; | ||
| 93 | +static int nr_runs = NR_RUNS; | ||
| 94 | +static int prio_start = PRIO_START; | ||
| 95 | +static int check; | ||
| 96 | +static int stop; | ||
| 97 | +static int equal; | ||
| 98 | + | ||
| 99 | +static unsigned long long now; | ||
| 100 | + | ||
| 101 | +static int done; | ||
| 102 | +static int loop; | ||
| 103 | +static int duration; | ||
| 104 | +static int quiet; | ||
| 105 | +static char jsonfile[MAX_PATH]; | ||
| 106 | + | ||
| 107 | +static pthread_barrier_t start_barrier; | ||
| 108 | +static pthread_barrier_t end_barrier; | ||
| 109 | +static unsigned long long **intervals; | ||
| 110 | +static unsigned long long **intervals_length; | ||
| 111 | +static unsigned long **intervals_loops; | ||
| 112 | +static long *thread_pids; | ||
| 113 | + | ||
| 114 | +static char buffer[BUFSIZ]; | ||
| 115 | + | ||
| 116 | +static void perr(char *fmt, ...) | ||
| 117 | +{ | ||
| 118 | + va_list ap; | ||
| 119 | + | ||
| 120 | + va_start(ap, fmt); | ||
| 121 | + vsnprintf(buffer, BUFSIZ, fmt, ap); | ||
| 122 | + va_end(ap); | ||
| 123 | + | ||
| 124 | + perror(buffer); | ||
| 125 | + fflush(stderr); | ||
| 126 | + exit(-1); | ||
| 127 | +} | ||
| 128 | + | ||
| 129 | +static void print_progress_bar(int percent) | ||
| 130 | +{ | ||
| 131 | + int i; | ||
| 132 | + int p; | ||
| 133 | + | ||
| 134 | + if (percent > 100) | ||
| 135 | + percent = 100; | ||
| 136 | + | ||
| 137 | + /* Use stderr, so we don't capture it */ | ||
| 138 | + putc('\r', stderr); | ||
| 139 | + putc('|', stderr); | ||
| 140 | + for (i=0; i < PROGRESS_CHARS; i++) | ||
| 141 | + putc(' ', stderr); | ||
| 142 | + putc('|', stderr); | ||
| 143 | + putc('\r', stderr); | ||
| 144 | + putc('|', stderr); | ||
| 145 | + | ||
| 146 | + p = PROGRESS_CHARS * percent / 100; | ||
| 147 | + | ||
| 148 | + for (i=0; i < p; i++) | ||
| 149 | + putc('-', stderr); | ||
| 150 | + | ||
| 151 | + fflush(stderr); | ||
| 152 | +} | ||
| 153 | + | ||
| 154 | +static void usage(int error) | ||
| 155 | +{ | ||
| 156 | + printf("rt-migrate-test %1.2f\n", VERSION); | ||
| 157 | + printf("Usage:\n" | ||
| 158 | + "rt-migrate-test <options> [NR_TASKS]\n\n" | ||
| 159 | + "-c --check Stop if lower prio task is quicker than higher (off)\n" | ||
| 160 | + "-D TIME --duration=TIME Specify a length for the test run.\n" | ||
| 161 | + " Append 'm', 'h', or 'd' to specify minutes, hours or\n" | ||
| 162 | + " days.\n" | ||
| 163 | + "-e --equal Use equal prio for #CPU-1 tasks (requires > 2 CPUS)\n" | ||
| 164 | + "-h --help Print this help message\n" | ||
| 165 | + " --json=FILENAME write final results into FILENAME, JSON formatted\n" | ||
| 166 | + "-l LOOPS --loops=LOOPS Number of iterations to run (50)\n" | ||
| 167 | + "-m TIME --maxerr=TIME Max allowed error (microsecs)\n" | ||
| 168 | + "-p PRIO --prio=PRIO base priority to start RT tasks with (2)\n" | ||
| 169 | + "-q --quiet print a summary only on exit\n" | ||
| 170 | + "-r TIME --run-time=TIME Run time (ms) to busy loop the threads (20)\n" | ||
| 171 | + "-s TIME --sleep-time=TIME Sleep time (ms) between intervals (100)\n\n" | ||
| 172 | + " () above are defaults \n" | ||
| 173 | + ); | ||
| 174 | + exit(error); | ||
| 175 | +} | ||
| 176 | + | ||
| 177 | +enum option_value { | ||
| 178 | + OPT_CHECK=1, OPT_DURATION, OPT_EQUAL, OPT_HELP, OPT_JSON, | ||
| 179 | + OPT_LOOPS, OPT_MAXERR, OPT_PRIO, OPT_QUIET, OPT_RUN_TIME, | ||
| 180 | + OPT_SLEEP_TIME | ||
| 181 | +}; | ||
| 182 | + | ||
| 183 | +static void parse_options(int argc, char *argv[]) | ||
| 184 | +{ | ||
| 185 | + for (;;) { | ||
| 186 | + int option_index = 0; | ||
| 187 | + /** Options for getopt */ | ||
| 188 | + static struct option long_options[] = { | ||
| 189 | + {"check", no_argument, NULL, OPT_CHECK}, | ||
| 190 | + {"duration", required_argument, NULL, OPT_DURATION}, | ||
| 191 | + {"equal", no_argument, NULL, OPT_EQUAL}, | ||
| 192 | + {"help", no_argument, NULL, OPT_HELP}, | ||
| 193 | + {"json", required_argument, NULL, OPT_JSON}, | ||
| 194 | + {"loops", required_argument, NULL, OPT_LOOPS}, | ||
| 195 | + {"maxerr", required_argument, NULL, OPT_MAXERR}, | ||
| 196 | + {"prio", required_argument, NULL, OPT_PRIO}, | ||
| 197 | + {"quiet", no_argument, NULL, OPT_QUIET}, | ||
| 198 | + {"run-time", required_argument, NULL, OPT_RUN_TIME}, | ||
| 199 | + {"sleep-time", required_argument, NULL, OPT_SLEEP_TIME}, | ||
| 200 | + {NULL, 0, NULL, 0} | ||
| 201 | + }; | ||
| 202 | + int c = getopt_long(argc, argv, "cD:ehl:m:p:qr:s:", | ||
| 203 | + long_options, &option_index); | ||
| 204 | + if (c == -1) | ||
| 205 | + break; | ||
| 206 | + switch (c) { | ||
| 207 | + case OPT_CHECK: | ||
| 208 | + case 'c': | ||
| 209 | + check = 1; | ||
| 210 | + break; | ||
| 211 | + case OPT_DURATION: | ||
| 212 | + case 'D': | ||
| 213 | + duration = parse_time_string(optarg); | ||
| 214 | + break; | ||
| 215 | + case OPT_EQUAL: | ||
| 216 | + case 'e': | ||
| 217 | + equal = 1; | ||
| 218 | + break; | ||
| 219 | + case OPT_HELP: | ||
| 220 | + case '?': | ||
| 221 | + case 'h': | ||
| 222 | + usage(0); | ||
| 223 | + break; | ||
| 224 | + case OPT_JSON: | ||
| 225 | + strncpy(jsonfile, optarg, strnlen(optarg, MAX_PATH-1)); | ||
| 226 | + break; | ||
| 227 | + case OPT_LOOPS: | ||
| 228 | + case 'l': | ||
| 229 | + nr_runs = atoi(optarg); | ||
| 230 | + break; | ||
| 231 | + case OPT_MAXERR: | ||
| 232 | + case 'm': | ||
| 233 | + max_err = usec2nano(atoi(optarg)); | ||
| 234 | + break; | ||
| 235 | + case OPT_PRIO: | ||
| 236 | + case 'p': | ||
| 237 | + prio_start = atoi(optarg); | ||
| 238 | + break; | ||
| 239 | + case OPT_QUIET: | ||
| 240 | + case 'q': | ||
| 241 | + quiet = 1; | ||
| 242 | + break; | ||
| 243 | + case OPT_RUN_TIME: | ||
| 244 | + case 'r': | ||
| 245 | + run_interval = atoi(optarg); | ||
| 246 | + break; | ||
| 247 | + case OPT_SLEEP_TIME: | ||
| 248 | + case 's': | ||
| 249 | + interval = atoi(optarg); | ||
| 250 | + break; | ||
| 251 | + default: | ||
| 252 | + usage(1); | ||
| 253 | + } | ||
| 254 | + } | ||
| 255 | +} | ||
| 256 | + | ||
| 257 | +static unsigned long long get_time(void) | ||
| 258 | +{ | ||
| 259 | + struct timeval tv; | ||
| 260 | + unsigned long long time; | ||
| 261 | + | ||
| 262 | + gettimeofday(&tv, NULL); | ||
| 263 | + | ||
| 264 | + time = sec2nano(tv.tv_sec); | ||
| 265 | + time += usec2nano(tv.tv_usec); | ||
| 266 | + | ||
| 267 | + return time; | ||
| 268 | +} | ||
| 269 | + | ||
| 270 | +static void record_time(int id, unsigned long long time, unsigned long l) | ||
| 271 | +{ | ||
| 272 | + unsigned long long ltime; | ||
| 273 | + | ||
| 274 | + if (loop >= nr_runs) | ||
| 275 | + return; | ||
| 276 | + time -= now; | ||
| 277 | + ltime = get_time(); | ||
| 278 | + ltime -= now; | ||
| 279 | + intervals[loop][id] = time; | ||
| 280 | + intervals_length[loop][id] = ltime; | ||
| 281 | + intervals_loops[loop][id] = l; | ||
| 282 | +} | ||
| 283 | + | ||
| 284 | +static int calc_prio(int id) | ||
| 285 | +{ | ||
| 286 | + int prio = equal && id && (id < nr_tasks - 1) ? 1 : id; | ||
| 287 | + return prio + prio_start; | ||
| 288 | +} | ||
| 289 | + | ||
| 290 | +static void print_results(void) | ||
| 291 | +{ | ||
| 292 | + int i; | ||
| 293 | + int t; | ||
| 294 | + unsigned long long tasks_max[nr_tasks]; | ||
| 295 | + unsigned long long tasks_min[nr_tasks]; | ||
| 296 | + unsigned long long tasks_avg[nr_tasks]; | ||
| 297 | + | ||
| 298 | + memset(tasks_max, 0, sizeof(tasks_max[0])*nr_tasks); | ||
| 299 | + memset(tasks_min, 0xff, sizeof(tasks_min[0])*nr_tasks); | ||
| 300 | + memset(tasks_avg, 0, sizeof(tasks_avg[0])*nr_tasks); | ||
| 301 | + | ||
| 302 | + printf("Iter: "); | ||
| 303 | + for (t=0; t < nr_tasks; t++) | ||
| 304 | + printf("%6d ", t); | ||
| 305 | + printf("\n"); | ||
| 306 | + | ||
| 307 | + for (i=0; i < nr_runs; i++) { | ||
| 308 | + printf("%4d: ", i); | ||
| 309 | + for (t=0; t < nr_tasks; t++) { | ||
| 310 | + unsigned long long itv = intervals[i][t]; | ||
| 311 | + | ||
| 312 | + if (tasks_max[t] < itv) | ||
| 313 | + tasks_max[t] = itv; | ||
| 314 | + if (tasks_min[t] > itv) | ||
| 315 | + tasks_min[t] = itv; | ||
| 316 | + tasks_avg[t] += itv; | ||
| 317 | + printf("%6lld ", nano2usec(itv)); | ||
| 318 | + } | ||
| 319 | + printf("\n"); | ||
| 320 | + printf(" len: "); | ||
| 321 | + for (t=0; t < nr_tasks; t++) { | ||
| 322 | + unsigned long long len = intervals_length[i][t]; | ||
| 323 | + | ||
| 324 | + printf("%6lld ", nano2usec(len)); | ||
| 325 | + } | ||
| 326 | + printf("\n"); | ||
| 327 | + printf(" loops: "); | ||
| 328 | + for (t=0; t < nr_tasks; t++) { | ||
| 329 | + unsigned long loops = intervals_loops[i][t]; | ||
| 330 | + | ||
| 331 | + printf("%6ld ", loops); | ||
| 332 | + } | ||
| 333 | + printf("\n"); | ||
| 334 | + printf("\n"); | ||
| 335 | + } | ||
| 336 | + | ||
| 337 | + printf("Parent pid: %d\n", getpid()); | ||
| 338 | + | ||
| 339 | + for (t=0; t < nr_tasks; t++) { | ||
| 340 | + printf(" Task %d (prio %d) (pid %ld):\n", t, calc_prio(t), | ||
| 341 | + thread_pids[t]); | ||
| 342 | + printf(" Max: %lld us\n", nano2usec(tasks_max[t])); | ||
| 343 | + printf(" Min: %lld us\n", nano2usec(tasks_min[t])); | ||
| 344 | + printf(" Tot: %lld us\n", nano2usec(tasks_avg[t])); | ||
| 345 | + printf(" Avg: %lld us\n", nano2usec(tasks_avg[t] / nr_runs)); | ||
| 346 | + printf("\n"); | ||
| 347 | + } | ||
| 348 | + | ||
| 349 | + if (check) { | ||
| 350 | + if (check < 0) | ||
| 351 | + printf(" Failed!\n"); | ||
| 352 | + else | ||
| 353 | + printf(" Passed!\n"); | ||
| 354 | + } | ||
| 355 | +} | ||
| 356 | + | ||
| 357 | +static void write_stats(FILE *f, void *data __attribute__ ((unused))) | ||
| 358 | +{ | ||
| 359 | + int i; | ||
| 360 | + int t; | ||
| 361 | + unsigned long long tasks_max[nr_tasks]; | ||
| 362 | + unsigned long long tasks_min[nr_tasks]; | ||
| 363 | + unsigned long long tasks_avg[nr_tasks]; | ||
| 364 | + | ||
| 365 | + memset(tasks_max, 0, sizeof(tasks_max[0])*nr_tasks); | ||
| 366 | + memset(tasks_min, 0xff, sizeof(tasks_min[0])*nr_tasks); | ||
| 367 | + memset(tasks_avg, 0, sizeof(tasks_avg[0])*nr_tasks); | ||
| 368 | + | ||
| 369 | + for (i=0; i < nr_runs; i++) { | ||
| 370 | + for (t=0; t < nr_tasks; t++) { | ||
| 371 | + unsigned long long itv = intervals[i][t]; | ||
| 372 | + | ||
| 373 | + if (tasks_max[t] < itv) | ||
| 374 | + tasks_max[t] = itv; | ||
| 375 | + if (tasks_min[t] > itv) | ||
| 376 | + tasks_min[t] = itv; | ||
| 377 | + tasks_avg[t] += itv; | ||
| 378 | + } | ||
| 379 | + } | ||
| 380 | + | ||
| 381 | + fprintf(f, " \"num_threads\": %d,\n", nr_tasks); | ||
| 382 | + fprintf(f, " \"thread\": {\n"); | ||
| 383 | + for (i = 0; i < nr_tasks; i++) { | ||
| 384 | + fprintf(f, " \"%u\": {\n", i); | ||
| 385 | + fprintf(f, " \"prio\": %d,\n", calc_prio(i)); | ||
| 386 | + fprintf(f, " \"min\": %lld,\n", nano2usec(tasks_min[i])); | ||
| 387 | + fprintf(f, " \"avg\": %lld,\n", nano2usec(tasks_avg[i]) / nr_runs); | ||
| 388 | + fprintf(f, " \"max\": %lld,\n", nano2usec(tasks_max[i])); | ||
| 389 | + fprintf(f, " \"total\": %lld\n", nano2usec(tasks_avg[i])); | ||
| 390 | + fprintf(f, " }%s\n", i == nr_tasks - 1 ? "" : ","); | ||
| 391 | + } | ||
| 392 | + fprintf(f, " }\n"); | ||
| 393 | +} | ||
| 394 | + | ||
| 395 | +static unsigned long busy_loop(unsigned long long start_time) | ||
| 396 | +{ | ||
| 397 | + unsigned long long time; | ||
| 398 | + unsigned long l = 0; | ||
| 399 | + | ||
| 400 | + do { | ||
| 401 | + l++; | ||
| 402 | + time = get_time(); | ||
| 403 | + } while ((time - start_time) < RUN_INTERVAL); | ||
| 404 | + | ||
| 405 | + return l; | ||
| 406 | +} | ||
| 407 | + | ||
| 408 | +void *start_task(void *data) | ||
| 409 | +{ | ||
| 410 | + long id = (long)data; | ||
| 411 | + unsigned long long start_time; | ||
| 412 | + int prio = calc_prio(id); | ||
| 413 | + struct sched_param param = { | ||
| 414 | + .sched_priority = prio, | ||
| 415 | + }; | ||
| 416 | + int ret; | ||
| 417 | + int high = 0; | ||
| 418 | + cpu_set_t cpumask; | ||
| 419 | + cpu_set_t save_cpumask; | ||
| 420 | + int cpu = 0; | ||
| 421 | + unsigned long l; | ||
| 422 | + long pid; | ||
| 423 | + | ||
| 424 | + ret = sched_getaffinity(0, sizeof(save_cpumask), &save_cpumask); | ||
| 425 | + if (ret < 0) | ||
| 426 | + perr("getting affinity"); | ||
| 427 | + | ||
| 428 | + pid = gettid(); | ||
| 429 | + thread_pids[id] = pid; | ||
| 430 | + | ||
| 431 | + /* Check if we are the highest prio task */ | ||
| 432 | + if (id == nr_tasks-1) | ||
| 433 | + high = 1; | ||
| 434 | + | ||
| 435 | + ret = sched_setscheduler(0, SCHED_FIFO, ¶m); | ||
| 436 | + if (ret < 0 && !id) | ||
| 437 | + fprintf(stderr, "Warning, can't set priorities\n"); | ||
| 438 | + | ||
| 439 | + while (!done) { | ||
| 440 | + if (high) { | ||
| 441 | + /* rotate around the CPUS */ | ||
| 442 | + if (!CPU_ISSET(cpu, &save_cpumask)) | ||
| 443 | + cpu = 0; | ||
| 444 | + CPU_ZERO(&cpumask); | ||
| 445 | + CPU_SET(cpu, &cpumask); cpu++; | ||
| 446 | + sched_setaffinity(0, sizeof(cpumask), &cpumask); | ||
| 447 | + } | ||
| 448 | + pthread_barrier_wait(&start_barrier); | ||
| 449 | + start_time = get_time(); | ||
| 450 | + ftrace_write("Thread %d: started %lld diff %lld\n", | ||
| 451 | + pid, start_time, start_time - now); | ||
| 452 | + l = busy_loop(start_time); | ||
| 453 | + record_time(id, start_time, l); | ||
| 454 | + pthread_barrier_wait(&end_barrier); | ||
| 455 | + } | ||
| 456 | + | ||
| 457 | + return (void*)pid; | ||
| 458 | +} | ||
| 459 | + | ||
| 460 | +static int check_times(int l) | ||
| 461 | +{ | ||
| 462 | + int i; | ||
| 463 | + unsigned long long last; | ||
| 464 | + unsigned long long last_loops; | ||
| 465 | + unsigned long long last_length; | ||
| 466 | + | ||
| 467 | + for (i=0; i < nr_tasks; i++) { | ||
| 468 | + if (i && last < intervals[l][i] && | ||
| 469 | + ((intervals[l][i] - last) > max_err)) { | ||
| 470 | + /* | ||
| 471 | + * May be a false positive. | ||
| 472 | + * Make sure that we did more loops | ||
| 473 | + * our start is before the end | ||
| 474 | + * and the end should be tested. | ||
| 475 | + */ | ||
| 476 | + if (intervals_loops[l][i] < last_loops || | ||
| 477 | + intervals[l][i] > last_length || | ||
| 478 | + (intervals_length[l][i] > last_length && | ||
| 479 | + intervals_length[l][i] - last_length > max_err)) { | ||
| 480 | + ftrace_write("Task %d FAILED\n", thread_pids[i]); | ||
| 481 | + check = -1; | ||
| 482 | + return 1; | ||
| 483 | + } | ||
| 484 | + } | ||
| 485 | + last = intervals[l][i]; | ||
| 486 | + last_loops = intervals_loops[l][i]; | ||
| 487 | + last_length = intervals_length[l][i]; | ||
| 488 | + } | ||
| 489 | + return 0; | ||
| 490 | +} | ||
| 491 | + | ||
| 492 | +static void stop_log(int sig __attribute__ ((unused))) | ||
| 493 | +{ | ||
| 494 | + stop = 1; | ||
| 495 | +} | ||
| 496 | + | ||
| 497 | +static int count_cpus(void) | ||
| 498 | +{ | ||
| 499 | + FILE *fp; | ||
| 500 | + char buf[2048]; | ||
| 501 | + int cpus = 0; | ||
| 502 | + char *pbuf; | ||
| 503 | + size_t *pn; | ||
| 504 | + size_t n; | ||
| 505 | + int r; | ||
| 506 | + | ||
| 507 | + n = 2048; | ||
| 508 | + pn = &n; | ||
| 509 | + pbuf = buf; | ||
| 510 | + | ||
| 511 | + fp = fopen("/proc/cpuinfo", "r"); | ||
| 512 | + if (!fp) | ||
| 513 | + perr("Can not read cpuinfo"); | ||
| 514 | + | ||
| 515 | + while ((r = getline(&pbuf, pn, fp)) >= 0) { | ||
| 516 | + char *p; | ||
| 517 | + | ||
| 518 | + if (strncmp(buf, "processor", 9) != 0) | ||
| 519 | + continue; | ||
| 520 | + for (p = buf+9; isspace(*p); p++) | ||
| 521 | + ; | ||
| 522 | + if (*p == ':') | ||
| 523 | + cpus++; | ||
| 524 | + } | ||
| 525 | + fclose(fp); | ||
| 526 | + | ||
| 527 | + return cpus; | ||
| 528 | +} | ||
| 529 | + | ||
| 530 | +int main (int argc, char **argv) | ||
| 531 | +{ | ||
| 532 | + pthread_t *threads; | ||
| 533 | + long i; | ||
| 534 | + int ret; | ||
| 535 | + struct timespec intv; | ||
| 536 | + struct sched_param param; | ||
| 537 | + | ||
| 538 | + rt_init(argc, argv); | ||
| 539 | + parse_options(argc, argv); | ||
| 540 | + | ||
| 541 | + signal(SIGINT, stop_log); | ||
| 542 | + signal(SIGALRM, stop_log); | ||
| 543 | + | ||
| 544 | + if (duration) | ||
| 545 | + alarm(duration); | ||
| 546 | + | ||
| 547 | + if (argc >= (optind + 1)) | ||
| 548 | + nr_tasks = atoi(argv[optind]); | ||
| 549 | + else | ||
| 550 | + nr_tasks = count_cpus() + 1; | ||
| 551 | + | ||
| 552 | + threads = malloc(sizeof(*threads) * nr_tasks); | ||
| 553 | + if (!threads) | ||
| 554 | + perr("malloc"); | ||
| 555 | + memset(threads, 0, sizeof(*threads) * nr_tasks); | ||
| 556 | + | ||
| 557 | + ret = pthread_barrier_init(&start_barrier, NULL, nr_tasks + 1); | ||
| 558 | + ret = pthread_barrier_init(&end_barrier, NULL, nr_tasks + 1); | ||
| 559 | + if (ret < 0) | ||
| 560 | + perr("pthread_barrier_init"); | ||
| 561 | + | ||
| 562 | + intervals = malloc(sizeof(void*) * nr_runs); | ||
| 563 | + if (!intervals) | ||
| 564 | + perr("malloc intervals array"); | ||
| 565 | + | ||
| 566 | + intervals_length = malloc(sizeof(void*) * nr_runs); | ||
| 567 | + if (!intervals_length) | ||
| 568 | + perr("malloc intervals length array"); | ||
| 569 | + | ||
| 570 | + intervals_loops = malloc(sizeof(void*) * nr_runs); | ||
| 571 | + if (!intervals_loops) | ||
| 572 | + perr("malloc intervals loops array"); | ||
| 573 | + | ||
| 574 | + thread_pids = malloc(sizeof(long) * nr_tasks); | ||
| 575 | + if (!thread_pids) | ||
| 576 | + perr("malloc thread_pids"); | ||
| 577 | + | ||
| 578 | + for (i=0; i < nr_runs; i++) { | ||
| 579 | + intervals[i] = malloc(sizeof(unsigned long long)*nr_tasks); | ||
| 580 | + if (!intervals[i]) | ||
| 581 | + perr("malloc intervals"); | ||
| 582 | + memset(intervals[i], 0, sizeof(unsigned long long)*nr_tasks); | ||
| 583 | + | ||
| 584 | + intervals_length[i] = malloc(sizeof(unsigned long long)*nr_tasks); | ||
| 585 | + if (!intervals_length[i]) | ||
| 586 | + perr("malloc length intervals"); | ||
| 587 | + memset(intervals_length[i], 0, sizeof(unsigned long long)*nr_tasks); | ||
| 588 | + | ||
| 589 | + intervals_loops[i] = malloc(sizeof(unsigned long)*nr_tasks); | ||
| 590 | + if (!intervals_loops[i]) | ||
| 591 | + perr("malloc loops intervals"); | ||
| 592 | + memset(intervals_loops[i], 0, sizeof(unsigned long)*nr_tasks); | ||
| 593 | + } | ||
| 594 | + | ||
| 595 | + for (i=0; i < nr_tasks; i++) { | ||
| 596 | + if (pthread_create(&threads[i], NULL, start_task, (void *)i)) | ||
| 597 | + perr("pthread_create"); | ||
| 598 | + | ||
| 599 | + } | ||
| 600 | + | ||
| 601 | + /* | ||
| 602 | + * Progress bar uses stderr to let users see it when | ||
| 603 | + * redirecting output. So we convert stderr to use line | ||
| 604 | + * buffering so the progress bar doesn't flicker. | ||
| 605 | + */ | ||
| 606 | + setlinebuf(stderr); | ||
| 607 | + | ||
| 608 | + /* up our prio above all tasks */ | ||
| 609 | + memset(¶m, 0, sizeof(param)); | ||
| 610 | + param.sched_priority = nr_tasks + prio_start; | ||
| 611 | + if (sched_setscheduler(0, SCHED_FIFO, ¶m)) | ||
| 612 | + fprintf(stderr, "Warning, can't set priority of main thread!\n"); | ||
| 613 | + | ||
| 614 | + | ||
| 615 | + | ||
| 616 | + intv.tv_sec = nano2sec(INTERVAL); | ||
| 617 | + intv.tv_nsec = INTERVAL % sec2nano(1); | ||
| 618 | + | ||
| 619 | + if (!quiet) | ||
| 620 | + print_progress_bar(0); | ||
| 621 | + | ||
| 622 | + setup_ftrace_marker(); | ||
| 623 | + | ||
| 624 | + for (loop=0; loop < nr_runs; loop++) { | ||
| 625 | + unsigned long long end; | ||
| 626 | + | ||
| 627 | + /* Release the CPU so all can get to the next barrier */ | ||
| 628 | + nanosleep(&intv, NULL); | ||
| 629 | + | ||
| 630 | + now = get_time(); | ||
| 631 | + | ||
| 632 | + ftrace_write("Loop %d now=%lld\n", loop, now); | ||
| 633 | + | ||
| 634 | + pthread_barrier_wait(&start_barrier); | ||
| 635 | + | ||
| 636 | + ftrace_write("All running!!!\n"); | ||
| 637 | + | ||
| 638 | + nanosleep(&intv, NULL); | ||
| 639 | + | ||
| 640 | + if (!quiet) | ||
| 641 | + print_progress_bar((loop * 100)/nr_runs); | ||
| 642 | + | ||
| 643 | + end = get_time(); | ||
| 644 | + ftrace_write("Loop %d end now=%lld diff=%lld\n", loop, end, end - now); | ||
| 645 | + | ||
| 646 | + pthread_barrier_wait(&end_barrier); | ||
| 647 | + | ||
| 648 | + if (stop || (check && check_times(loop))) { | ||
| 649 | + loop++; | ||
| 650 | + nr_runs = loop; | ||
| 651 | + break; | ||
| 652 | + } | ||
| 653 | + } | ||
| 654 | + putc('\n', stderr); | ||
| 655 | + | ||
| 656 | + pthread_barrier_wait(&start_barrier); | ||
| 657 | + done = 1; | ||
| 658 | + pthread_barrier_wait(&end_barrier); | ||
| 659 | + | ||
| 660 | + for (i=0; i < nr_tasks; i++) | ||
| 661 | + pthread_join(threads[i], (void*)&thread_pids[i]); | ||
| 662 | + | ||
| 663 | + print_results(); | ||
| 664 | + | ||
| 665 | + if (strlen(jsonfile) != 0) | ||
| 666 | + rt_write_json(jsonfile, check < 0, write_stats, NULL); | ||
| 667 | + | ||
| 668 | + if (stop) { | ||
| 669 | + /* | ||
| 670 | + * We use this test in bash while loops | ||
| 671 | + * So if we hit Ctrl-C then let the while | ||
| 672 | + * loop know to break. | ||
| 673 | + */ | ||
| 674 | + if (check < 0) | ||
| 675 | + exit(-1); | ||
| 676 | + else | ||
| 677 | + exit(1); | ||
| 678 | + } | ||
| 679 | + if (check < 0) | ||
| 680 | + exit(-1); | ||
| 681 | + else | ||
| 682 | + exit(0); | ||
| 683 | + | ||
| 684 | + return 0; | ||
| 685 | +} | ||
| @@ -0,0 +1,64 @@ | |||
| 1 | +.\" Hey, EMACS: -*- nroff -*- | ||
| 2 | +.TH CYCLICDEADLINE 8 "January 16, 2020" | ||
| 3 | +# SPDX-License-Identifier: GPL-2.0-only | ||
| 4 | +.\" Please adjust this date whenever revising the manpage. | ||
| 5 | +.\" | ||
| 6 | +.\" Some roff macros, for reference: | ||
| 7 | +.\" .nh disable hyphenation | ||
| 8 | +.\" .hy enable hyphenation | ||
| 9 | +.\" .ad l left justify | ||
| 10 | +.\" .ad b justify to both left and right margins | ||
| 11 | +.\" .nf disable filling | ||
| 12 | +.\" .fi enable filling | ||
| 13 | +.\" .br insert line break | ||
| 14 | +.\" .sp <n> insert n+1 empty lines | ||
| 15 | +.\" for manpage-specific macros, see man(7) | ||
| 16 | +.SH NAME | ||
| 17 | +cyclicdeadline \- This program is used to test the deadline scheduler (SCHED_DEADLINE) using a cyclictest style program | ||
| 18 | +.PP | ||
| 19 | +.SH SYNOPSIS | ||
| 20 | +.B cyclicdeadline | ||
| 21 | +.RI "[-a [CPUSET]] [-D TIME] [-h] [-i INTV] [--json FILENAME] [-s STEP] [-t NUM] [-q]" | ||
| 22 | +.PP | ||
| 23 | +.SH DESCRIPTION | ||
| 24 | +.B cyclicdeadline | ||
| 25 | +is a cyclictest style program for testing the deadline scheduler | ||
| 26 | +.PP | ||
| 27 | +.SH OPTIONS | ||
| 28 | +.TP | ||
| 29 | +.B \-a \-\-affinity [CPUSET] | ||
| 30 | +Comma / hypen separated list of CPUs to run deadline tasks on | ||
| 31 | +.TP | ||
| 32 | +.B \-D \-\-duration TIME | ||
| 33 | +Specify a length for the test to run | ||
| 34 | +Append 'm', 'h', or 'd' to specify minutes, hours, or days | ||
| 35 | +.TP | ||
| 36 | +.B \-h \-\-help | ||
| 37 | +Show this help menu | ||
| 38 | +.TP | ||
| 39 | +.B \-i \-\-interval INTV | ||
| 40 | +The shortest deadline for the tasks in us. (default 1000us) | ||
| 41 | +.TP | ||
| 42 | +.B \-\-json=FILENAME | ||
| 43 | +Write final results into FILENAME, JSON formatted. | ||
| 44 | +.TP | ||
| 45 | +.B \-s \-\-step STEP | ||
| 46 | +The amount to increase the deadline for each task in us. (default 500us) | ||
| 47 | +.TP | ||
| 48 | +.B \-t \-\-threads NUM | ||
| 49 | +The number of threads to run as deadline (default 1) | ||
| 50 | +.TP | ||
| 51 | +.B \-q, \-\-quiet | ||
| 52 | +Print a summary only on exit. Useful for automated tests, where only | ||
| 53 | +the summary output needs to be captured. | ||
| 54 | +.TP | ||
| 55 | +.B \-b, \-\-breaktrace=USEC | ||
| 56 | +Send break trace command when latency > USEC | ||
| 57 | +.TP | ||
| 58 | +.B \-\-tracemark | ||
| 59 | +write a trace mark when \-b latency is exceeded. | ||
| 60 | +.br | ||
| 61 | +.SH AUTHOR | ||
| 62 | +cyclicdeadline was written by Steven Rostedt <rostedt@goodmis.org> | ||
| 63 | +.PP | ||
| 64 | +This manual page was written by John Kacur <jkacur@redhat.com> | ||
| @@ -0,0 +1,1439 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | + | ||
| 3 | +/* | ||
| 4 | + * Copyright (C) 2016 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> | ||
| 5 | + * Copyright (C) 2019 John Kacur <jkacur@redhat.com> | ||
| 6 | + * Copyright (C) 2019 Clark Williams <williams@redhat.com> | ||
| 7 | + */ | ||
| 8 | + | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + | ||
| 35 | + | ||
| 36 | + | ||
| 37 | + | ||
| 38 | + | ||
| 39 | + | ||
| 40 | + | ||
| 41 | + | ||
| 42 | + | ||
| 43 | + | ||
| 44 | + | ||
| 45 | + | ||
| 46 | + | ||
| 47 | + | ||
| 48 | + | ||
| 49 | +typedef unsigned long long u64; | ||
| 50 | +typedef unsigned int u32; | ||
| 51 | +typedef int s32; | ||
| 52 | + | ||
| 53 | +/* Struct for statistics */ | ||
| 54 | +struct thread_stat { | ||
| 55 | + unsigned long cycles; | ||
| 56 | + long min; | ||
| 57 | + long max; | ||
| 58 | + long act; | ||
| 59 | + double avg; | ||
| 60 | + long *values; | ||
| 61 | + struct histogram *hist; | ||
| 62 | + pthread_t thread; | ||
| 63 | + int threadstarted; | ||
| 64 | + int tid; | ||
| 65 | + long reduce; | ||
| 66 | +}; | ||
| 67 | + | ||
| 68 | +struct sched_data { | ||
| 69 | + u64 runtime_us; | ||
| 70 | + u64 deadline_us; | ||
| 71 | + | ||
| 72 | + int bufmsk; | ||
| 73 | + | ||
| 74 | + struct thread_stat stat; | ||
| 75 | + | ||
| 76 | + char buff[BUFSIZ+1]; | ||
| 77 | +}; | ||
| 78 | + | ||
| 79 | +static int shutdown; | ||
| 80 | +static int info_enable; | ||
| 81 | +static int debug_enable; | ||
| 82 | +static int tracelimit; | ||
| 83 | +static int trace_marker; | ||
| 84 | +static int histogram; | ||
| 85 | +static FILE *histfile; | ||
| 86 | +static pthread_mutex_t break_thread_id_lock = PTHREAD_MUTEX_INITIALIZER; | ||
| 87 | +static pid_t break_thread_id; | ||
| 88 | +static uint64_t break_thread_value; | ||
| 89 | +static pthread_barrier_t barrier; | ||
| 90 | + | ||
| 91 | +static int cpu_count; | ||
| 92 | +static int all_cpus; | ||
| 93 | +static int nr_threads; | ||
| 94 | +static int use_nsecs; | ||
| 95 | +static int mark_fd; | ||
| 96 | +static int quiet; | ||
| 97 | +static char jsonfile[MAX_PATH]; | ||
| 98 | + | ||
| 99 | +static struct histoset hset; | ||
| 100 | + | ||
| 101 | +static int find_mount(const char *mount, char *debugfs) | ||
| 102 | +{ | ||
| 103 | + char type[100]; | ||
| 104 | + FILE *fp; | ||
| 105 | + | ||
| 106 | + if ((fp = fopen("/proc/mounts", "r")) == NULL) | ||
| 107 | + return 0; | ||
| 108 | + | ||
| 109 | + while (fscanf(fp, "%*s %" | ||
| 110 | + STR(MAXPATH) | ||
| 111 | + "s %99s %*s %*d %*d\n", | ||
| 112 | + debugfs, type) == 2) { | ||
| 113 | + if (strcmp(type, mount) == 0) | ||
| 114 | + break; | ||
| 115 | + } | ||
| 116 | + fclose(fp); | ||
| 117 | + | ||
| 118 | + if (strcmp(type, mount) != 0) | ||
| 119 | + return 0; | ||
| 120 | + return 1; | ||
| 121 | +} | ||
| 122 | + | ||
| 123 | +static const char *find_debugfs(void) | ||
| 124 | +{ | ||
| 125 | + static int debugfs_found; | ||
| 126 | + static char debugfs[MAXPATH+1]; | ||
| 127 | + | ||
| 128 | + if (debugfs_found) | ||
| 129 | + return debugfs; | ||
| 130 | + | ||
| 131 | + if (!find_mount("debugfs", debugfs)) | ||
| 132 | + return ""; | ||
| 133 | + | ||
| 134 | + debugfs_found = 1; | ||
| 135 | + | ||
| 136 | + return debugfs; | ||
| 137 | +} | ||
| 138 | + | ||
| 139 | +static int my_vsprintf(char *buf, int size, const char *fmt, va_list ap) | ||
| 140 | +{ | ||
| 141 | + const char *p; | ||
| 142 | + char tmp[100]; | ||
| 143 | + char *s = buf; | ||
| 144 | + char *end = buf + size; | ||
| 145 | + char *str; | ||
| 146 | + long long lng; | ||
| 147 | + int l; | ||
| 148 | + int i; | ||
| 149 | + | ||
| 150 | + end[-1] = 0; | ||
| 151 | + | ||
| 152 | + for (p = fmt; *p && s < end; p++) { | ||
| 153 | + if (*p == '%') { | ||
| 154 | + l = 0; | ||
| 155 | + again: | ||
| 156 | + p++; | ||
| 157 | + switch (*p) { | ||
| 158 | + case 's': | ||
| 159 | + if (l) | ||
| 160 | + fatal("Illegal print format l used with %%s\n"); | ||
| 161 | + str = va_arg(ap, char *); | ||
| 162 | + l = strlen(str); | ||
| 163 | + strncpy(s, str, end - s); | ||
| 164 | + s += l; | ||
| 165 | + break; | ||
| 166 | + case 'l': | ||
| 167 | + l++; | ||
| 168 | + goto again; | ||
| 169 | + case 'd': | ||
| 170 | + if (l == 1) { | ||
| 171 | + if (sizeof(long) == 8) | ||
| 172 | + l = 2; | ||
| 173 | + } | ||
| 174 | + if (l == 2) | ||
| 175 | + lng = va_arg(ap, long long); | ||
| 176 | + else if (l > 2) | ||
| 177 | + fatal("Illegal print format l=%d\n", l); | ||
| 178 | + else | ||
| 179 | + lng = va_arg(ap, int); | ||
| 180 | + i = 0; | ||
| 181 | + while (lng > 0) { | ||
| 182 | + tmp[i++] = (lng % 10) + '0'; | ||
| 183 | + lng /= 10; | ||
| 184 | + } | ||
| 185 | + tmp[i] = 0; | ||
| 186 | + l = strlen(tmp); | ||
| 187 | + if (!l) { | ||
| 188 | + *s++ = '0'; | ||
| 189 | + } else { | ||
| 190 | + while (l) | ||
| 191 | + *s++ = tmp[--l]; | ||
| 192 | + } | ||
| 193 | + break; | ||
| 194 | + default: | ||
| 195 | + fatal("Illegal print format '%c'\n", *p); | ||
| 196 | + } | ||
| 197 | + continue; | ||
| 198 | + } | ||
| 199 | + *s++ = *p; | ||
| 200 | + } | ||
| 201 | + | ||
| 202 | + return s - buf; | ||
| 203 | +} | ||
| 204 | + | ||
| 205 | +static void ftrace_write(char *buf, const char *fmt, ...) | ||
| 206 | +{ | ||
| 207 | + va_list ap; | ||
| 208 | + int n; | ||
| 209 | + | ||
| 210 | + if (mark_fd < 0) | ||
| 211 | + return; | ||
| 212 | + | ||
| 213 | + va_start(ap, fmt); | ||
| 214 | + n = my_vsprintf(buf, BUFSIZ, fmt, ap); | ||
| 215 | + va_end(ap); | ||
| 216 | + | ||
| 217 | + write(mark_fd, buf, n); | ||
| 218 | +} | ||
| 219 | + | ||
| 220 | +static void setup_ftrace_marker(void) | ||
| 221 | +{ | ||
| 222 | + struct stat st; | ||
| 223 | + const char *debugfs = find_debugfs(); | ||
| 224 | + char files[strlen(debugfs) + strlen("/tracing/trace_marker") + 1]; | ||
| 225 | + int ret; | ||
| 226 | + | ||
| 227 | + if (strlen(debugfs) == 0) | ||
| 228 | + return; | ||
| 229 | + | ||
| 230 | + sprintf(files, "%s/tracing/trace_marker", debugfs); | ||
| 231 | + ret = stat(files, &st); | ||
| 232 | + if (ret >= 0) | ||
| 233 | + goto found; | ||
| 234 | + /* Do nothing if not mounted */ | ||
| 235 | + return; | ||
| 236 | +found: | ||
| 237 | + mark_fd = open(files, O_WRONLY); | ||
| 238 | +} | ||
| 239 | + | ||
| 240 | +/* | ||
| 241 | + * Return true if file exists | ||
| 242 | + */ | ||
| 243 | +static int check_file_exists(char *path) | ||
| 244 | +{ | ||
| 245 | + int ret; | ||
| 246 | + struct stat st; | ||
| 247 | + | ||
| 248 | + ret = !stat(path, &st); | ||
| 249 | + | ||
| 250 | + return ret; | ||
| 251 | + | ||
| 252 | +} | ||
| 253 | + | ||
| 254 | +/* | ||
| 255 | + * Return 0 on success | ||
| 256 | + */ | ||
| 257 | + | ||
| 258 | +static int fill_sched_features(char *path) | ||
| 259 | +{ | ||
| 260 | + int ret; | ||
| 261 | + const char *debugfs; | ||
| 262 | + | ||
| 263 | + debugfs = find_debugfs(); | ||
| 264 | + if (strlen(debugfs) == 0) | ||
| 265 | + return -1; | ||
| 266 | + | ||
| 267 | + snprintf(path, MAX_PATH, "%s/sched/features", debugfs); | ||
| 268 | + ret = check_file_exists(path); | ||
| 269 | + if (ret) | ||
| 270 | + return 0; | ||
| 271 | + | ||
| 272 | + snprintf(path, MAX_PATH, "%s/sched_features", debugfs); | ||
| 273 | + ret = check_file_exists(path); | ||
| 274 | + if (ret) | ||
| 275 | + return 0; | ||
| 276 | + | ||
| 277 | + memset(path, 0, MAX_PATH); | ||
| 278 | + | ||
| 279 | + return ret; | ||
| 280 | + | ||
| 281 | +} | ||
| 282 | + | ||
| 283 | +static int setup_hr_tick(void) | ||
| 284 | +{ | ||
| 285 | + char path[MAX_PATH]; | ||
| 286 | + char buf[500]; | ||
| 287 | + static int set = 0; | ||
| 288 | + int hrtick_dl = 0; | ||
| 289 | + char *p; | ||
| 290 | + int ret; | ||
| 291 | + int len; | ||
| 292 | + int fd; | ||
| 293 | + | ||
| 294 | + if (set) | ||
| 295 | + return 1; | ||
| 296 | + set = 1; | ||
| 297 | + | ||
| 298 | + ret = fill_sched_features(path); | ||
| 299 | + if (ret) | ||
| 300 | + return 0; | ||
| 301 | + | ||
| 302 | + fd = open(path, O_RDWR); | ||
| 303 | + if (fd < 0) { | ||
| 304 | + perror(path); | ||
| 305 | + return 0; | ||
| 306 | + } | ||
| 307 | + | ||
| 308 | + len = sizeof(buf); | ||
| 309 | + | ||
| 310 | + ret = read(fd, buf, len); | ||
| 311 | + if (ret < 0) { | ||
| 312 | + perror(path); | ||
| 313 | + close(fd); | ||
| 314 | + return 0; | ||
| 315 | + } | ||
| 316 | + if (ret >= len) | ||
| 317 | + ret = len - 1; | ||
| 318 | + buf[ret] = 0; | ||
| 319 | + | ||
| 320 | + ret = 1; | ||
| 321 | + | ||
| 322 | + p = strstr(buf, "HRTICK_DL"); | ||
| 323 | + if (p && p - 3 >= buf) { | ||
| 324 | + hrtick_dl = 1; | ||
| 325 | + p -= 3; | ||
| 326 | + if (strncmp(p, "NO_HRTICK_DL", 12) == 0) { | ||
| 327 | + ret = write(fd, "HRTICK_DL", 9); | ||
| 328 | + if (ret != 9) | ||
| 329 | + ret = 0; | ||
| 330 | + else | ||
| 331 | + ret = 1; | ||
| 332 | + } | ||
| 333 | + } | ||
| 334 | + | ||
| 335 | + /* Backwards compatibility for kernel that only have HRTICK */ | ||
| 336 | + if (!hrtick_dl) { | ||
| 337 | + p = strstr(buf, "HRTICK"); | ||
| 338 | + if (p && p - 3 >= buf) { | ||
| 339 | + p -=3; | ||
| 340 | + if (strncmp(p, "NO_HRTICK", 9) == 0) { | ||
| 341 | + ret = write(fd, "HRTICK", 6); | ||
| 342 | + if (ret != 6) | ||
| 343 | + ret = 0; | ||
| 344 | + else | ||
| 345 | + ret = 1; | ||
| 346 | + } | ||
| 347 | + } | ||
| 348 | + } | ||
| 349 | + | ||
| 350 | + close(fd); | ||
| 351 | + return ret; | ||
| 352 | +} | ||
| 353 | + | ||
| 354 | +static int mounted(const char *path, long magic) | ||
| 355 | +{ | ||
| 356 | + struct statfs st_fs; | ||
| 357 | + | ||
| 358 | + if (statfs(path, &st_fs) < 0) | ||
| 359 | + return -1; | ||
| 360 | + if ((long)st_fs.f_type != magic) | ||
| 361 | + return 0; | ||
| 362 | + return 1; | ||
| 363 | +} | ||
| 364 | + | ||
| 365 | + | ||
| 366 | + | ||
| 367 | + | ||
| 368 | +/** | ||
| 369 | + * cgroup_mounted - test if the path /sys/fs/cgroup exists | ||
| 370 | + * and is a supported type | ||
| 371 | + * | ||
| 372 | + * Returns -1 if the path does not exist | ||
| 373 | + * Returns 0 if the path exists but is not a cgroup type | ||
| 374 | + * Returns 1 if the path exists and supports cgroups | ||
| 375 | + */ | ||
| 376 | +static int cgroup_mounted(void) | ||
| 377 | +{ | ||
| 378 | + int ret; | ||
| 379 | + | ||
| 380 | + ret = mounted(CGROUP_PATH, TMPFS_MAGIC); | ||
| 381 | + if (ret == -1) | ||
| 382 | + return -1; /* path doesn't exist */ | ||
| 383 | + if (ret == 1) | ||
| 384 | + return 1; /* tmpfs */ | ||
| 385 | + ret = mounted(CGROUP_PATH, CGROUP_SUPER_MAGIC); | ||
| 386 | + if (ret == 1) | ||
| 387 | + return 1; /* cgroup v1 */ | ||
| 388 | + ret = mounted(CGROUP_PATH, CGROUP2_SUPER_MAGIC); | ||
| 389 | + if (ret == 1) | ||
| 390 | + return 1; /* cgroup v2 */ | ||
| 391 | + return 0; /* path exists but type is not recognized */ | ||
| 392 | +} | ||
| 393 | + | ||
| 394 | +static int open_cpuset(const char *path, const char *name) | ||
| 395 | +{ | ||
| 396 | + char buf[MAXPATH]; | ||
| 397 | + struct stat st; | ||
| 398 | + int ret; | ||
| 399 | + int fd; | ||
| 400 | + | ||
| 401 | + buf[MAXPATH - 1] = 0; | ||
| 402 | + snprintf(buf, MAXPATH - 1, "%s/%s", path, name); | ||
| 403 | + | ||
| 404 | + ret = stat(buf, &st); | ||
| 405 | + if (ret < 0) | ||
| 406 | + return ret; | ||
| 407 | + | ||
| 408 | + fd = open(buf, O_WRONLY); | ||
| 409 | + return fd; | ||
| 410 | +} | ||
| 411 | + | ||
| 412 | +static int mount_cpuset(void) | ||
| 413 | +{ | ||
| 414 | + struct stat st; | ||
| 415 | + int ret; | ||
| 416 | + int fd; | ||
| 417 | + | ||
| 418 | + /* Check if cgroups is already mounted. */ | ||
| 419 | + ret = cgroup_mounted(); | ||
| 420 | + if (ret < 0) /* /sys/fs/cgroup doesn't exist */ | ||
| 421 | + return ret; | ||
| 422 | + | ||
| 423 | + if (!ret) /* /sys/fs/cgroup exists, but we don't recognize the type */ | ||
| 424 | + return -1; | ||
| 425 | + | ||
| 426 | + ret = stat(CPUSET_PATH, &st); | ||
| 427 | + if (ret < 0) { | ||
| 428 | + ret = mkdir(CPUSET_PATH, 0755); | ||
| 429 | + if (ret < 0) | ||
| 430 | + return ret; | ||
| 431 | + } | ||
| 432 | + ret = mounted(CPUSET_PATH, CGROUP_SUPER_MAGIC); | ||
| 433 | + if (ret < 0) | ||
| 434 | + return ret; | ||
| 435 | + if (!ret) { | ||
| 436 | + ret = mount("cpuset", CPUSET_PATH, "cgroup", 0, "cpuset"); | ||
| 437 | + if (ret < 0) | ||
| 438 | + return ret; | ||
| 439 | + } | ||
| 440 | + | ||
| 441 | + fd = open_cpuset(CPUSET_PATH, "cpuset.cpu_exclusive"); | ||
| 442 | + if (fd < 0) | ||
| 443 | + return fd; | ||
| 444 | + ret = write(fd, "1", 2); | ||
| 445 | + close(fd); | ||
| 446 | + | ||
| 447 | + fd = open_cpuset(CPUSET_PATH, "cpuset.sched_load_balance"); | ||
| 448 | + if (fd < 0) | ||
| 449 | + return fd; | ||
| 450 | + ret = write(fd, "0", 2); | ||
| 451 | + close(fd); | ||
| 452 | + | ||
| 453 | + return 0; | ||
| 454 | +} | ||
| 455 | + | ||
| 456 | +enum { | ||
| 457 | + CPUSET_FL_CPU_EXCLUSIVE = (1 << 0), | ||
| 458 | + CPUSET_FL_MEM_EXCLUSIVE = (1 << 1), | ||
| 459 | + CPUSET_FL_ALL_TASKS = (1 << 2), | ||
| 460 | + CPUSET_FL_TASKS = (1 << 3), | ||
| 461 | + CPUSET_FL_CLEAR_LOADBALANCE = (1 << 4), | ||
| 462 | + CPUSET_FL_SET_LOADBALANCE = (1 << 5), | ||
| 463 | + CPUSET_FL_CLONE_CHILDREN = (1 << 6), | ||
| 464 | +}; | ||
| 465 | + | ||
| 466 | +static void make_cpuset(const char *name, const char *cpus, | ||
| 467 | + const char *mems, unsigned int flags, ...) | ||
| 468 | +{ | ||
| 469 | + struct stat st; | ||
| 470 | + char path[MAXPATH]; | ||
| 471 | + char buf[100]; | ||
| 472 | + va_list ap; | ||
| 473 | + int ret; | ||
| 474 | + int fd; | ||
| 475 | + | ||
| 476 | + printf("Creating cpuset '%s'\n", name); | ||
| 477 | + snprintf(path, MAXPATH - 1, "%s/%s", CPUSET_PATH, name); | ||
| 478 | + path[MAXPATH - 1] = 0; | ||
| 479 | + | ||
| 480 | + ret = mount_cpuset(); | ||
| 481 | + if (ret < 0) | ||
| 482 | + fatal("mount_cpuset"); | ||
| 483 | + | ||
| 484 | + ret = stat(path, &st); | ||
| 485 | + if (ret < 0) { | ||
| 486 | + ret = mkdir(path, 0755); | ||
| 487 | + if (ret < 0) | ||
| 488 | + fatal("mkdir"); | ||
| 489 | + } | ||
| 490 | + | ||
| 491 | + fd = open_cpuset(path, "cpuset.cpus"); | ||
| 492 | + if (fd < 0) | ||
| 493 | + fatal("cset"); | ||
| 494 | + ret = write(fd, cpus, strlen(cpus)); | ||
| 495 | + close(fd); | ||
| 496 | + if (ret < 0) | ||
| 497 | + fatal("write cpus"); | ||
| 498 | + | ||
| 499 | + if (mems) { | ||
| 500 | + fd = open_cpuset(path, "cpuset.mems"); | ||
| 501 | + if (fd < 0) | ||
| 502 | + fatal("open mems"); | ||
| 503 | + ret = write(fd, mems, strlen(mems)); | ||
| 504 | + close(fd); | ||
| 505 | + if (ret < 0) | ||
| 506 | + fatal("write mems"); | ||
| 507 | + } | ||
| 508 | + | ||
| 509 | + if (flags & CPUSET_FL_CPU_EXCLUSIVE) { | ||
| 510 | + fd = open_cpuset(path, "cpuset.cpu_exclusive"); | ||
| 511 | + if (fd < 0) | ||
| 512 | + fatal("open cpu_exclusive"); | ||
| 513 | + ret = write(fd, "1", 2); | ||
| 514 | + close(fd); | ||
| 515 | + if (ret < 0) | ||
| 516 | + fatal("write cpu_exclusive"); | ||
| 517 | + } | ||
| 518 | + | ||
| 519 | + if (flags & (CPUSET_FL_CLEAR_LOADBALANCE | CPUSET_FL_SET_LOADBALANCE)) { | ||
| 520 | + fd = open_cpuset(path, "cpuset.sched_load_balance"); | ||
| 521 | + if (fd < 0) | ||
| 522 | + fatal("open sched_load_balance"); | ||
| 523 | + if (flags & CPUSET_FL_SET_LOADBALANCE) | ||
| 524 | + ret = write(fd, "1", 2); | ||
| 525 | + else | ||
| 526 | + ret = write(fd, "0", 2); | ||
| 527 | + close(fd); | ||
| 528 | + if (ret < 0) | ||
| 529 | + fatal("write sched_load_balance"); | ||
| 530 | + } | ||
| 531 | + | ||
| 532 | + if (flags & CPUSET_FL_CLONE_CHILDREN) { | ||
| 533 | + fd = open_cpuset(path, "cgroup.clone_children"); | ||
| 534 | + if (fd < 0) | ||
| 535 | + fatal("open clone_children"); | ||
| 536 | + ret = write(fd, "1", 2); | ||
| 537 | + close(fd); | ||
| 538 | + if (ret < 0) | ||
| 539 | + fatal("write clone_children"); | ||
| 540 | + } | ||
| 541 | + | ||
| 542 | + | ||
| 543 | + if (flags & CPUSET_FL_TASKS) { | ||
| 544 | + int *pids; | ||
| 545 | + int i; | ||
| 546 | + | ||
| 547 | + va_start(ap, flags); | ||
| 548 | + | ||
| 549 | + fd = open_cpuset(path, "tasks"); | ||
| 550 | + if (fd < 0) | ||
| 551 | + fatal("open tasks"); | ||
| 552 | + | ||
| 553 | + ret = 0; | ||
| 554 | + pids = va_arg(ap, int *); | ||
| 555 | + for (i = 0; pids[i]; i++) { | ||
| 556 | + sprintf(buf, "%d ", pids[i]); | ||
| 557 | + ret = write(fd, buf, strlen(buf)); | ||
| 558 | + } | ||
| 559 | + va_end(ap); | ||
| 560 | + close(fd); | ||
| 561 | + if (ret < 0) | ||
| 562 | + fatal("Failed on task %d\n", pids[i]); | ||
| 563 | + } | ||
| 564 | + | ||
| 565 | + if (flags & CPUSET_FL_ALL_TASKS) { | ||
| 566 | + FILE *fp; | ||
| 567 | + int pid; | ||
| 568 | + | ||
| 569 | + fd = open_cpuset(path, "tasks"); | ||
| 570 | + | ||
| 571 | + snprintf(path, MAXPATH - 1, "%s/tasks", CPUSET_PATH); | ||
| 572 | + if ((fp = fopen(path, "r")) == NULL) { | ||
| 573 | + close(fd); | ||
| 574 | + fatal("opening cpuset tasks"); | ||
| 575 | + } | ||
| 576 | + | ||
| 577 | + while (fscanf(fp, "%d", &pid) == 1) { | ||
| 578 | + sprintf(buf, "%d", pid); | ||
| 579 | + ret = write(fd, buf, strlen(buf)); | ||
| 580 | + /* | ||
| 581 | + * Tasks can come and go, the only error we care | ||
| 582 | + * about is ENOSPC, as that means something went | ||
| 583 | + * wrong that we did not expect. | ||
| 584 | + */ | ||
| 585 | + if (ret < 0 && errno == ENOSPC) { | ||
| 586 | + fclose(fp); | ||
| 587 | + close(fd); | ||
| 588 | + fatal("Can not move tasks"); | ||
| 589 | + } | ||
| 590 | + } | ||
| 591 | + fclose(fp); | ||
| 592 | + close(fd); | ||
| 593 | + } | ||
| 594 | +} | ||
| 595 | + | ||
| 596 | +static void destroy_cpuset(const char *name, int print) | ||
| 597 | +{ | ||
| 598 | + struct stat st; | ||
| 599 | + char path[MAXPATH]; | ||
| 600 | + char buf[100]; | ||
| 601 | + FILE *fp; | ||
| 602 | + int pid; | ||
| 603 | + int ret; | ||
| 604 | + int fd; | ||
| 605 | + int retry = 0; | ||
| 606 | + | ||
| 607 | + printf("Removing %s\n", name); | ||
| 608 | + snprintf(path, MAXPATH - 1, "%s/%s", CPUSET_PATH, name); | ||
| 609 | + path[MAXPATH - 1] = 0; | ||
| 610 | + | ||
| 611 | + ret = stat(path, &st); | ||
| 612 | + if (ret < 0) | ||
| 613 | + return; | ||
| 614 | + | ||
| 615 | + again: | ||
| 616 | + strncat(path, "/tasks", MAXPATH - 1); | ||
| 617 | + if ((fp = fopen(path, "r")) == NULL) { | ||
| 618 | + fprintf(stderr, "Failed opening %s\n", path); | ||
| 619 | + perror("fopen"); | ||
| 620 | + return; | ||
| 621 | + } | ||
| 622 | + snprintf(path, MAXPATH - 1, "%s/tasks", CPUSET_PATH); | ||
| 623 | + path[MAXPATH - 1] = 0; | ||
| 624 | + | ||
| 625 | + fd = open(path, O_WRONLY); | ||
| 626 | + if (fd < 0) { | ||
| 627 | + fclose(fp); | ||
| 628 | + fprintf(stderr, "Failed opening %s\n", path); | ||
| 629 | + perror("open"); | ||
| 630 | + return; | ||
| 631 | + } | ||
| 632 | + | ||
| 633 | + while (fscanf(fp, "%d", &pid) == 1) { | ||
| 634 | + sprintf(buf, "%d", pid); | ||
| 635 | + if (print) | ||
| 636 | + printf("Moving %d out of %s\n", pid, name); | ||
| 637 | + write(fd, buf, strlen(buf)); | ||
| 638 | + } | ||
| 639 | + fclose(fp); | ||
| 640 | + close(fd); | ||
| 641 | + | ||
| 642 | + snprintf(path, MAXPATH - 1, "%s/%s", CPUSET_PATH, name); | ||
| 643 | + path[MAXPATH - 1] = 0; | ||
| 644 | + | ||
| 645 | + sleep(1); | ||
| 646 | + ret = rmdir(path); | ||
| 647 | + if (ret < 0) { | ||
| 648 | + if (retry++ < 5) { | ||
| 649 | + err_msg("Trying again\n"); | ||
| 650 | + goto again; | ||
| 651 | + } | ||
| 652 | + err_msg_n(errno, "Failed to remove %s\n", path); | ||
| 653 | + } | ||
| 654 | +} | ||
| 655 | + | ||
| 656 | +static void teardown(void) | ||
| 657 | +{ | ||
| 658 | + int fd; | ||
| 659 | + | ||
| 660 | + if (all_cpus) | ||
| 661 | + return; | ||
| 662 | + | ||
| 663 | + fd = open_cpuset(CPUSET_PATH, "cpuset.cpu_exclusive"); | ||
| 664 | + if (fd >= 0) { | ||
| 665 | + write(fd, "0", 2); | ||
| 666 | + close(fd); | ||
| 667 | + } | ||
| 668 | + | ||
| 669 | + fd = open_cpuset(CPUSET_PATH, "cpuset.sched_load_balance"); | ||
| 670 | + if (fd >= 0) { | ||
| 671 | + write(fd, "1", 2); | ||
| 672 | + close(fd); | ||
| 673 | + } | ||
| 674 | + | ||
| 675 | + destroy_cpuset(CPUSET_ALL, 0); | ||
| 676 | + destroy_cpuset(CPUSET_LOCAL, 1); | ||
| 677 | + | ||
| 678 | + /* close any tracer file descriptors */ | ||
| 679 | + disable_trace_mark(); | ||
| 680 | + | ||
| 681 | +} | ||
| 682 | + | ||
| 683 | +static void usage(int error) | ||
| 684 | +{ | ||
| 685 | + printf("cyclicdeadline V %1.2f\n", VERSION); | ||
| 686 | + printf("Usage:\n" | ||
| 687 | + "cyclicdeadline <options>\n\n" | ||
| 688 | + "-a [CPUSET] --affinity Comma/hyphen separated list of CPUs to run deadline\n" | ||
| 689 | + " tasks on. An empty CPUSET runs on all CPUs a deadline\n" | ||
| 690 | + " task.\n" | ||
| 691 | + "-D TIME --duration Specify a length for the test run.\n" | ||
| 692 | + " Append 'm', 'h', or 'd' to specify minutes, hours or\n" | ||
| 693 | + " days\n" | ||
| 694 | + "-h --help Show this help menu.\n" | ||
| 695 | + " --histogram=US dump a latency histogram to stdout after the run\n" | ||
| 696 | + " US is the max latency time to be tracked in microseconds\n" | ||
| 697 | + " This option runs all threads at the same priority.\n" | ||
| 698 | + " --histfile=<path> dump the latency histogram to <path> instead of stdout\n" | ||
| 699 | + "-i INTV --interval The shortest deadline for the tasks in us\n" | ||
| 700 | + " (default 1000us).\n" | ||
| 701 | + " --json=FILENAME write final results into FILENAME, JSON formatted\n" | ||
| 702 | + "-s STEP --step The amount to increase the deadline for each task in us\n" | ||
| 703 | + " (default 500us).\n" | ||
| 704 | + "-t NUM --threads The number of threads to run as deadline (default 1).\n" | ||
| 705 | + "-q --quiet print a summary only on exit\n" | ||
| 706 | + "-b USEC --breaktrace=USEC send break trace command when latency > USEC\n" | ||
| 707 | + " --tracemark write a trace mark when -b latency is exceeded\n" | ||
| 708 | + " --debug Print debugging info for cyclicdeadline\n" | ||
| 709 | + " --verbose Print useful information about the test\n" | ||
| 710 | + ); | ||
| 711 | + exit(error); | ||
| 712 | +} | ||
| 713 | + | ||
| 714 | +static u64 get_time_us(void) | ||
| 715 | +{ | ||
| 716 | + struct timespec ts; | ||
| 717 | + u64 time; | ||
| 718 | + | ||
| 719 | + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); | ||
| 720 | + time = ts.tv_sec * 1000000; | ||
| 721 | + time += ts.tv_nsec / 1000; | ||
| 722 | + | ||
| 723 | + return time; | ||
| 724 | +} | ||
| 725 | + | ||
| 726 | +static void print_hist(FILE *fp, struct sched_data *sd, int nthreads) | ||
| 727 | +{ | ||
| 728 | + int i; | ||
| 729 | + unsigned long maxmax, alloverflows; | ||
| 730 | + | ||
| 731 | + fprintf(fp, "# Histogram\n"); | ||
| 732 | + for (i = 0; i < histogram; i++) { | ||
| 733 | + unsigned long flags = 0; | ||
| 734 | + char buf[64]; | ||
| 735 | + | ||
| 736 | + snprintf(buf, sizeof(buf), "%06d ", i); | ||
| 737 | + hset_print_bucket(&hset, fp, buf, i, flags); | ||
| 738 | + } | ||
| 739 | + fprintf(fp, "# Min Latencies:"); | ||
| 740 | + for (i = 0; i < nthreads; i++) | ||
| 741 | + fprintf(fp, " %05lu", sd[i].stat.min); | ||
| 742 | + fprintf(fp, "\n"); | ||
| 743 | + fprintf(fp, "# Avg Latencies:"); | ||
| 744 | + for (i = 0; i < nthreads; i++) | ||
| 745 | + fprintf(fp, " %05lu", sd[i].stat.cycles ? | ||
| 746 | + (long)(sd[i].stat.avg/sd[i].stat.cycles) : 0); | ||
| 747 | + fprintf(fp, "\n"); | ||
| 748 | + fprintf(fp, "# Max Latencies:"); | ||
| 749 | + maxmax = 0; | ||
| 750 | + for (i = 0; i < nthreads; i++) { | ||
| 751 | + fprintf(fp, " %05lu", sd[i].stat.max); | ||
| 752 | + if (sd[i].stat.max > maxmax) | ||
| 753 | + maxmax = sd[i].stat.max; | ||
| 754 | + } | ||
| 755 | + fprintf(fp, "\n"); | ||
| 756 | + fprintf(fp, "# Histogram Overflows:"); | ||
| 757 | + alloverflows = 0; | ||
| 758 | + for (i = 0; i < nthreads; i++) { | ||
| 759 | + fprintf(fp, " %05lu", sd[i].stat.hist->oflow_count); | ||
| 760 | + alloverflows += sd[i].stat.hist->oflow_count; | ||
| 761 | + } | ||
| 762 | + fprintf(fp, "\n"); | ||
| 763 | + | ||
| 764 | + fprintf(fp, "# Histogram Overflow at cycle number:\n"); | ||
| 765 | + for (i = 0; i < nthreads; i++) { | ||
| 766 | + fprintf(fp, "# Thread %d: ", i); | ||
| 767 | + hist_print_oflows(sd[i].stat.hist, fp); | ||
| 768 | + fprintf(fp, "\n"); | ||
| 769 | + } | ||
| 770 | + | ||
| 771 | + fprintf(fp, "\n"); | ||
| 772 | +} | ||
| 773 | + | ||
| 774 | +static void print_stat(FILE *fp, struct sched_data *sd, int index, int quiet) | ||
| 775 | +{ | ||
| 776 | + struct thread_stat *stat = &sd->stat; | ||
| 777 | + char *fmt; | ||
| 778 | + | ||
| 779 | + if (quiet) | ||
| 780 | + return; | ||
| 781 | + | ||
| 782 | + if (use_nsecs) | ||
| 783 | + fmt = "T:%2d (%5d) I:%ld C:%7lu Min:%7ld Act:%8ld Avg:%8ld Max:%8ld\n"; | ||
| 784 | + else | ||
| 785 | + fmt = "T:%2d (%5d) I:%ld C:%7lu Min:%7ld Act:%5ld Avg:%5ld Max:%8ld\n"; | ||
| 786 | + | ||
| 787 | + fprintf(fp, fmt, index, stat->tid, | ||
| 788 | + sd->deadline_us, stat->cycles, stat->min, stat->act, | ||
| 789 | + stat->cycles ? | ||
| 790 | + (long)(stat->avg/stat->cycles) : 0, stat->max); | ||
| 791 | +} | ||
| 792 | + | ||
| 793 | +static u64 do_runtime(struct sched_data *sd, u64 period) | ||
| 794 | +{ | ||
| 795 | + struct thread_stat *stat = &sd->stat; | ||
| 796 | + u64 next_period = period + sd->deadline_us; | ||
| 797 | + u64 now = get_time_us(); | ||
| 798 | + u64 diff; | ||
| 799 | + | ||
| 800 | + if (now < period) { | ||
| 801 | + u64 delta = period - now; | ||
| 802 | + /* | ||
| 803 | + * The period could be off due to other deadline tasks | ||
| 804 | + * preempting us when we started. If that's the case then | ||
| 805 | + * adjust the current period. | ||
| 806 | + */ | ||
| 807 | + ftrace_write(sd->buff, | ||
| 808 | + "Adjusting period: now: %lld period: %lld delta:%lld%s\n", | ||
| 809 | + now, period, delta, delta > sd->deadline_us / 2 ? | ||
| 810 | + " HUGE ADJUSTMENT" : ""); | ||
| 811 | + period = now; | ||
| 812 | + next_period = period + sd->deadline_us; | ||
| 813 | + } | ||
| 814 | + | ||
| 815 | + ftrace_write(sd->buff, "start at %lld off=%lld (period=%lld next=%lld)\n", | ||
| 816 | + now, now - period, period, next_period); | ||
| 817 | + | ||
| 818 | + | ||
| 819 | + diff = now - period; | ||
| 820 | + if (diff > stat->max) | ||
| 821 | + stat->max = diff; | ||
| 822 | + if (!stat->min || diff < stat->min) | ||
| 823 | + stat->min = diff; | ||
| 824 | + stat->act = diff; | ||
| 825 | + stat->avg += (double) diff; | ||
| 826 | + | ||
| 827 | + if (histogram) | ||
| 828 | + hist_sample(stat->hist, diff); | ||
| 829 | + | ||
| 830 | + stat->cycles++; | ||
| 831 | + | ||
| 832 | + return next_period; | ||
| 833 | +} | ||
| 834 | + | ||
| 835 | +void *run_deadline(void *data) | ||
| 836 | +{ | ||
| 837 | + struct sched_data *sd = data; | ||
| 838 | + struct thread_stat *stat = &sd->stat; | ||
| 839 | + struct sched_attr attr; | ||
| 840 | + long tid = gettid(); | ||
| 841 | + u64 period; | ||
| 842 | + int ret; | ||
| 843 | + | ||
| 844 | + debug(debug_enable, "deadline thread %ld\n", tid); | ||
| 845 | + // set up for each measurment thread | ||
| 846 | + stat->tid = tid; | ||
| 847 | + | ||
| 848 | + ret = sched_getattr(0, &attr, sizeof(attr), 0); | ||
| 849 | + if (ret < 0) { | ||
| 850 | + err_msg_n(errno, "[%ld]", tid); | ||
| 851 | + shutdown = 1; | ||
| 852 | + pthread_barrier_wait(&barrier); | ||
| 853 | + pthread_exit("Failed sched_getattr"); | ||
| 854 | + return NULL; | ||
| 855 | + } | ||
| 856 | + | ||
| 857 | + attr.sched_policy = SCHED_DEADLINE; | ||
| 858 | + attr.sched_runtime = sd->runtime_us * 1000; | ||
| 859 | + attr.sched_deadline = sd->deadline_us * 1000; | ||
| 860 | + | ||
| 861 | + debug(debug_enable, "thread[%d] runtime=%lldus deadline=%lldus\n", | ||
| 862 | + gettid(), sd->runtime_us, sd->deadline_us); | ||
| 863 | + | ||
| 864 | + ret = sched_setattr(0, &attr, 0); | ||
| 865 | + if (ret < 0) { | ||
| 866 | + err_msg_n(errno, "[%ld]", tid); | ||
| 867 | + shutdown = 1; | ||
| 868 | + pthread_barrier_wait(&barrier); | ||
| 869 | + pthread_exit("Failed sched_setattr"); | ||
| 870 | + return NULL; | ||
| 871 | + } | ||
| 872 | + | ||
| 873 | + pthread_barrier_wait(&barrier); | ||
| 874 | + | ||
| 875 | + sched_yield(); | ||
| 876 | + period = get_time_us(); | ||
| 877 | + | ||
| 878 | + while (!shutdown) { | ||
| 879 | + period = do_runtime(sd, period); | ||
| 880 | + if (tracelimit && (stat->max > tracelimit)) { | ||
| 881 | + shutdown++; | ||
| 882 | + pthread_mutex_lock(&break_thread_id_lock); | ||
| 883 | + if (break_thread_id == 0) { | ||
| 884 | + break_thread_id = stat->tid; | ||
| 885 | + break_thread_value = stat->max; | ||
| 886 | + tracemark("hit latency threshold (%lld > %d)", | ||
| 887 | + (unsigned long long) stat->max, tracelimit); | ||
| 888 | + } | ||
| 889 | + pthread_mutex_unlock(&break_thread_id_lock); | ||
| 890 | + break; | ||
| 891 | + } | ||
| 892 | + sched_yield(); | ||
| 893 | + } | ||
| 894 | + ret = sched_getattr(0, &attr, sizeof(attr), 0); | ||
| 895 | + if (ret < 0) { | ||
| 896 | + err_msg_n(errno, "sched_getattr"); | ||
| 897 | + pthread_exit("Failed second sched_getattr"); | ||
| 898 | + } | ||
| 899 | + | ||
| 900 | + return NULL; | ||
| 901 | +} | ||
| 902 | + | ||
| 903 | +struct cpu_list { | ||
| 904 | + struct cpu_list *next; | ||
| 905 | + int start_cpu; | ||
| 906 | + int end_cpu; | ||
| 907 | +}; | ||
| 908 | + | ||
| 909 | +static void add_cpus(struct cpu_list **cpu_list, int start_cpu, int end_cpu) | ||
| 910 | +{ | ||
| 911 | + struct cpu_list *list; | ||
| 912 | + | ||
| 913 | + while (*cpu_list && (*cpu_list)->end_cpu + 1 < start_cpu) | ||
| 914 | + cpu_list = &(*cpu_list)->next; | ||
| 915 | + | ||
| 916 | + if (!*cpu_list) { | ||
| 917 | + *cpu_list = malloc(sizeof(struct cpu_list)); | ||
| 918 | + (*cpu_list)->start_cpu = start_cpu; | ||
| 919 | + (*cpu_list)->end_cpu = end_cpu; | ||
| 920 | + (*cpu_list)->next = NULL; | ||
| 921 | + return; | ||
| 922 | + } | ||
| 923 | + | ||
| 924 | + /* Look to concatinate */ | ||
| 925 | + if (end_cpu > (*cpu_list)->start_cpu && | ||
| 926 | + start_cpu <= (*cpu_list)->end_cpu + 1) { | ||
| 927 | + if (start_cpu < (*cpu_list)->start_cpu) | ||
| 928 | + (*cpu_list)->start_cpu = start_cpu; | ||
| 929 | + list = (*cpu_list)->next; | ||
| 930 | + while (list && list->start_cpu <= end_cpu + 1) { | ||
| 931 | + (*cpu_list)->end_cpu = list->end_cpu; | ||
| 932 | + (*cpu_list)->next = list->next; | ||
| 933 | + free(list); | ||
| 934 | + list = (*cpu_list)->next; | ||
| 935 | + } | ||
| 936 | + if ((*cpu_list)->end_cpu < end_cpu) | ||
| 937 | + (*cpu_list)->end_cpu = end_cpu; | ||
| 938 | + return; | ||
| 939 | + } | ||
| 940 | + | ||
| 941 | + /* Check for overlaps */ | ||
| 942 | + if (end_cpu >= (*cpu_list)->start_cpu - 1) { | ||
| 943 | + (*cpu_list)->start_cpu = start_cpu; | ||
| 944 | + return; | ||
| 945 | + } | ||
| 946 | + | ||
| 947 | + list = malloc(sizeof(struct cpu_list)); | ||
| 948 | + list->start_cpu = start_cpu; | ||
| 949 | + list->end_cpu = end_cpu; | ||
| 950 | + list->next = (*cpu_list)->next; | ||
| 951 | + (*cpu_list)->next = list; | ||
| 952 | +} | ||
| 953 | + | ||
| 954 | +static int count_cpus(struct cpu_list *cpu_list) | ||
| 955 | +{ | ||
| 956 | + struct cpu_list *list; | ||
| 957 | + int cpus = 0; | ||
| 958 | + int fail = 0; | ||
| 959 | + | ||
| 960 | + while (cpu_list) { | ||
| 961 | + list = cpu_list; | ||
| 962 | + cpus += (list->end_cpu - list->start_cpu) + 1; | ||
| 963 | + if (list->end_cpu >= cpu_count) | ||
| 964 | + fail = 1; | ||
| 965 | + cpu_list = list->next; | ||
| 966 | + free(list); | ||
| 967 | + } | ||
| 968 | + return fail ? -1 : cpus; | ||
| 969 | +} | ||
| 970 | + | ||
| 971 | +static char *append_cpus(char *buf, int start, int end, | ||
| 972 | + const char *comma, int *total) | ||
| 973 | +{ | ||
| 974 | + int len; | ||
| 975 | + | ||
| 976 | + if (start == end) { | ||
| 977 | + len = snprintf(NULL, 0, "%s%d", comma, start); | ||
| 978 | + buf = realloc(buf, *total + len + 1); | ||
| 979 | + buf[*total] = 0; | ||
| 980 | + snprintf(buf + *total, len + 1, "%s%d", comma, start); | ||
| 981 | + } else { | ||
| 982 | + len = snprintf(NULL, 0, "%s%d-%d", comma, start, end); | ||
| 983 | + buf = realloc(buf, *total + len + 1); | ||
| 984 | + buf[*total] = 0; | ||
| 985 | + snprintf(buf + *total, len + 1, "%s%d-%d", comma, | ||
| 986 | + start, end); | ||
| 987 | + } | ||
| 988 | + *total += len; | ||
| 989 | + return buf; | ||
| 990 | +} | ||
| 991 | + | ||
| 992 | +static void make_new_list(struct cpu_list *cpu_list, char **buf) | ||
| 993 | +{ | ||
| 994 | + char *comma = ""; | ||
| 995 | + int total = 0; | ||
| 996 | + | ||
| 997 | + while (cpu_list) { | ||
| 998 | + *buf = append_cpus(*buf, cpu_list->start_cpu, cpu_list->end_cpu, | ||
| 999 | + comma, &total); | ||
| 1000 | + comma = ","; | ||
| 1001 | + cpu_list = cpu_list->next; | ||
| 1002 | + } | ||
| 1003 | +} | ||
| 1004 | + | ||
| 1005 | +static void make_other_cpu_list(const char *setcpu, char **cpus) | ||
| 1006 | +{ | ||
| 1007 | + const char *p = setcpu; | ||
| 1008 | + const char *comma = ""; | ||
| 1009 | + int curr_cpu = 0; | ||
| 1010 | + int cpu; | ||
| 1011 | + int total = 0; | ||
| 1012 | + | ||
| 1013 | + while (*p && curr_cpu < cpu_count) { | ||
| 1014 | + cpu = atoi(p); | ||
| 1015 | + if (cpu > curr_cpu) { | ||
| 1016 | + *cpus = append_cpus(*cpus, curr_cpu, cpu - 1, | ||
| 1017 | + comma, &total); | ||
| 1018 | + comma = ","; | ||
| 1019 | + } | ||
| 1020 | + while (isdigit(*p)) | ||
| 1021 | + p++; | ||
| 1022 | + if (*p == '-') { | ||
| 1023 | + p++; | ||
| 1024 | + cpu = atoi(p); | ||
| 1025 | + while (isdigit(*p)) | ||
| 1026 | + p++; | ||
| 1027 | + } | ||
| 1028 | + curr_cpu = cpu + 1; | ||
| 1029 | + if (*p) | ||
| 1030 | + p++; | ||
| 1031 | + } | ||
| 1032 | + | ||
| 1033 | + if (curr_cpu < cpu_count) { | ||
| 1034 | + *cpus = append_cpus(*cpus, curr_cpu, cpu_count - 1, | ||
| 1035 | + comma, &total); | ||
| 1036 | + } | ||
| 1037 | +} | ||
| 1038 | + | ||
| 1039 | +static int calc_nr_cpus(const char *setcpu, char **buf) | ||
| 1040 | +{ | ||
| 1041 | + struct cpu_list *cpu_list = NULL; | ||
| 1042 | + const char *p; | ||
| 1043 | + int end_cpu; | ||
| 1044 | + int cpu; | ||
| 1045 | + | ||
| 1046 | + for (p = setcpu; *p; ) { | ||
| 1047 | + cpu = atoi(p); | ||
| 1048 | + if (cpu < 0 || (!cpu && *p != '0')) | ||
| 1049 | + goto err; | ||
| 1050 | + | ||
| 1051 | + while (isdigit(*p)) | ||
| 1052 | + p++; | ||
| 1053 | + if (*p == '-') { | ||
| 1054 | + p++; | ||
| 1055 | + end_cpu = atoi(p); | ||
| 1056 | + if (end_cpu < cpu || (!end_cpu && *p != '0')) | ||
| 1057 | + goto err; | ||
| 1058 | + while (isdigit(*p)) | ||
| 1059 | + p++; | ||
| 1060 | + } else | ||
| 1061 | + end_cpu = cpu; | ||
| 1062 | + | ||
| 1063 | + add_cpus(&cpu_list, cpu, end_cpu); | ||
| 1064 | + if (*p == ',') | ||
| 1065 | + p++; | ||
| 1066 | + } | ||
| 1067 | + | ||
| 1068 | + make_new_list(cpu_list, buf); | ||
| 1069 | + return count_cpus(cpu_list); | ||
| 1070 | + err: | ||
| 1071 | + /* Frees the list */ | ||
| 1072 | + count_cpus(cpu_list); | ||
| 1073 | + return -1; | ||
| 1074 | +} | ||
| 1075 | + | ||
| 1076 | +static void sighand(int sig __attribute__ ((unused))) | ||
| 1077 | +{ | ||
| 1078 | + shutdown = 1; | ||
| 1079 | +} | ||
| 1080 | + | ||
| 1081 | +static const char *join_thread(pthread_t *thread) | ||
| 1082 | +{ | ||
| 1083 | + void *result; | ||
| 1084 | + | ||
| 1085 | + pthread_join(*thread, &result); | ||
| 1086 | + return result; | ||
| 1087 | +} | ||
| 1088 | + | ||
| 1089 | +static void loop(struct sched_data *sched_data, int nr_threads) | ||
| 1090 | +{ | ||
| 1091 | + int i; | ||
| 1092 | + | ||
| 1093 | + while (!shutdown) { | ||
| 1094 | + for (i = 0; i < nr_threads; i++) | ||
| 1095 | + print_stat(stdout, &sched_data[i], i, quiet); | ||
| 1096 | + usleep(10000); | ||
| 1097 | + if (!quiet) | ||
| 1098 | + printf("\033[%dA", nr_threads); | ||
| 1099 | + } | ||
| 1100 | + usleep(10000); | ||
| 1101 | + if (!quiet) { | ||
| 1102 | + printf("\033[%dB", nr_threads + 2); | ||
| 1103 | + } else if (!histogram) { | ||
| 1104 | + for (i = 0; i < nr_threads; ++i) | ||
| 1105 | + print_stat(stdout, &sched_data[i], i, 0); | ||
| 1106 | + } | ||
| 1107 | + | ||
| 1108 | + if (histogram) { | ||
| 1109 | + FILE *out = histfile ? histfile : stdout; | ||
| 1110 | + | ||
| 1111 | + print_hist(out, sched_data, nr_threads); | ||
| 1112 | + } | ||
| 1113 | +} | ||
| 1114 | + | ||
| 1115 | +static void write_stats(FILE *f, void *data) | ||
| 1116 | +{ | ||
| 1117 | + struct sched_data *sd = data; | ||
| 1118 | + struct thread_stat *s; | ||
| 1119 | + int i; | ||
| 1120 | + | ||
| 1121 | + fprintf(f, " \"num_threads\": %d,\n", nr_threads); | ||
| 1122 | + fprintf(f, " \"resolution_in_ns\": %u,\n", use_nsecs); | ||
| 1123 | + fprintf(f, " \"thread\": {\n"); | ||
| 1124 | + for (i = 0; i < nr_threads; i++) { | ||
| 1125 | + s = &sd[i].stat; | ||
| 1126 | + fprintf(f, " \"%u\": {\n", i); | ||
| 1127 | + if (s->hist) { | ||
| 1128 | + fprintf(f, " \"histogram\": {"); | ||
| 1129 | + hist_print_json(s->hist, f); | ||
| 1130 | + fprintf(f, " },\n"); | ||
| 1131 | + } | ||
| 1132 | + fprintf(f, " \"cycles\": %ld,\n", s->cycles); | ||
| 1133 | + fprintf(f, " \"min\": %ld,\n", s->min); | ||
| 1134 | + fprintf(f, " \"max\": %ld,\n", s->max); | ||
| 1135 | + fprintf(f, " \"avg\": %.2f\n", s->avg/s->cycles); | ||
| 1136 | + fprintf(f, " }%s\n", i == nr_threads - 1 ? "" : ","); | ||
| 1137 | + } | ||
| 1138 | + fprintf(f, " }\n"); | ||
| 1139 | +} | ||
| 1140 | + | ||
| 1141 | +enum options_values { | ||
| 1142 | + OPT_AFFINITY=1, OPT_DURATION, OPT_HELP, OPT_INTERVAL, | ||
| 1143 | + OPT_JSON, OPT_STEP, OPT_THREADS, OPT_QUIET, | ||
| 1144 | + OPT_BREAKTRACE, OPT_TRACEMARK, OPT_INFO, OPT_DEBUG, | ||
| 1145 | + OPT_HISTOGRAM, OPT_HISTFILE | ||
| 1146 | +}; | ||
| 1147 | + | ||
| 1148 | +int main(int argc, char **argv) | ||
| 1149 | +{ | ||
| 1150 | + struct sched_data *sched_data; | ||
| 1151 | + struct sched_data *sd; | ||
| 1152 | + const char *res; | ||
| 1153 | + const char *setcpu = NULL; | ||
| 1154 | + char *setcpu_buf = NULL; | ||
| 1155 | + char *allcpu_buf = NULL; | ||
| 1156 | + pthread_t *thread; | ||
| 1157 | + unsigned int interval = 1000; | ||
| 1158 | + unsigned int step = 500; | ||
| 1159 | + int percent = 60; | ||
| 1160 | + int duration = 0; | ||
| 1161 | + u64 runtime; | ||
| 1162 | + u64 start_period; | ||
| 1163 | + u64 end_period; | ||
| 1164 | + int nr_cpus; | ||
| 1165 | + int i; | ||
| 1166 | + int c; | ||
| 1167 | + | ||
| 1168 | + rt_init(argc, argv); | ||
| 1169 | + | ||
| 1170 | + cpu_count = sysconf(_SC_NPROCESSORS_CONF); | ||
| 1171 | + if (cpu_count < 1) | ||
| 1172 | + err_quit("Can not calculate number of CPUS\n"); | ||
| 1173 | + | ||
| 1174 | + for (;;) { | ||
| 1175 | + static struct option options[] = { | ||
| 1176 | + { "affinity", optional_argument, NULL, OPT_AFFINITY }, | ||
| 1177 | + { "duration", required_argument, NULL, OPT_DURATION }, | ||
| 1178 | + { "help", no_argument, NULL, OPT_HELP }, | ||
| 1179 | + { "interval", required_argument, NULL, OPT_INTERVAL }, | ||
| 1180 | + { "json", required_argument, NULL, OPT_JSON }, | ||
| 1181 | + { "step", required_argument, NULL, OPT_STEP }, | ||
| 1182 | + { "threads", required_argument, NULL, OPT_THREADS }, | ||
| 1183 | + { "quiet", no_argument, NULL, OPT_QUIET }, | ||
| 1184 | + { "breaktrace", required_argument, NULL, OPT_BREAKTRACE }, | ||
| 1185 | + { "tracemark", no_argument, NULL, OPT_TRACEMARK }, | ||
| 1186 | + { "verbose", no_argument, NULL, OPT_INFO}, | ||
| 1187 | + { "debug", no_argument, NULL, OPT_DEBUG}, | ||
| 1188 | + { "histogram", required_argument, NULL, OPT_HISTOGRAM }, | ||
| 1189 | + { "histfile", required_argument, NULL, OPT_HISTFILE }, | ||
| 1190 | + { NULL, 0, NULL, 0 }, | ||
| 1191 | + }; | ||
| 1192 | + c = getopt_long(argc, argv, "a::c:D:hi:s:t:b:q", options, NULL); | ||
| 1193 | + if (c == -1) | ||
| 1194 | + break; | ||
| 1195 | + switch (c) { | ||
| 1196 | + case OPT_AFFINITY: | ||
| 1197 | + case 'a': | ||
| 1198 | + case 'c': | ||
| 1199 | + if (!nr_threads) | ||
| 1200 | + nr_threads = cpu_count; | ||
| 1201 | + if (optarg) | ||
| 1202 | + setcpu = optarg; | ||
| 1203 | + else if (optind < argc) | ||
| 1204 | + setcpu = argv[optind]; | ||
| 1205 | + else | ||
| 1206 | + all_cpus = 1; | ||
| 1207 | + break; | ||
| 1208 | + case OPT_INTERVAL: | ||
| 1209 | + case 'i': | ||
| 1210 | + interval = atoi(optarg); | ||
| 1211 | + break; | ||
| 1212 | + case OPT_JSON: | ||
| 1213 | + strncpy(jsonfile, optarg, strnlen(optarg, MAX_PATH-1)); | ||
| 1214 | + break; | ||
| 1215 | + case OPT_STEP: | ||
| 1216 | + case 's': | ||
| 1217 | + step = atoi(optarg); | ||
| 1218 | + break; | ||
| 1219 | + case OPT_THREADS: | ||
| 1220 | + case 't': | ||
| 1221 | + nr_threads = atoi(optarg); | ||
| 1222 | + break; | ||
| 1223 | + case OPT_DURATION: | ||
| 1224 | + case 'D': | ||
| 1225 | + duration = parse_time_string(optarg); | ||
| 1226 | + break; | ||
| 1227 | + case 'b': | ||
| 1228 | + case OPT_BREAKTRACE: | ||
| 1229 | + tracelimit = atoi(optarg); | ||
| 1230 | + break; | ||
| 1231 | + case OPT_QUIET: | ||
| 1232 | + case 'q': | ||
| 1233 | + quiet = 1; | ||
| 1234 | + break; | ||
| 1235 | + case OPT_HELP: | ||
| 1236 | + case 'h': | ||
| 1237 | + usage(0); | ||
| 1238 | + break; | ||
| 1239 | + case OPT_TRACEMARK: | ||
| 1240 | + trace_marker = 1; | ||
| 1241 | + break; | ||
| 1242 | + case OPT_INFO: | ||
| 1243 | + info_enable = 1; | ||
| 1244 | + break; | ||
| 1245 | + case OPT_DEBUG: | ||
| 1246 | + debug_enable = 1; | ||
| 1247 | + break; | ||
| 1248 | + case OPT_HISTOGRAM: | ||
| 1249 | + histogram = atoi(optarg); | ||
| 1250 | + if (histogram <= 0 || histogram > HIST_MAX) | ||
| 1251 | + usage(1); | ||
| 1252 | + break; | ||
| 1253 | + case OPT_HISTFILE: | ||
| 1254 | + histfile = fopen(optarg, "w"); | ||
| 1255 | + if (!histfile) | ||
| 1256 | + fatal("Couldn\'t open histfile %s: %s\n", | ||
| 1257 | + optarg, strerror(errno)); | ||
| 1258 | + break; | ||
| 1259 | + default: | ||
| 1260 | + usage(1); | ||
| 1261 | + } | ||
| 1262 | + } | ||
| 1263 | + | ||
| 1264 | + if (!nr_threads) | ||
| 1265 | + nr_threads = 1; | ||
| 1266 | + | ||
| 1267 | + if (setcpu) { | ||
| 1268 | + nr_cpus = calc_nr_cpus(setcpu, &setcpu_buf); | ||
| 1269 | + if (nr_cpus < 0 || nr_cpus > cpu_count) | ||
| 1270 | + fatal("Invalid cpu input '%s'\n", setcpu); | ||
| 1271 | + } else | ||
| 1272 | + nr_cpus = cpu_count; | ||
| 1273 | + | ||
| 1274 | + if (!all_cpus && cpu_count == nr_cpus) { | ||
| 1275 | + printf("Using all CPUS\n"); | ||
| 1276 | + all_cpus = 1; | ||
| 1277 | + } | ||
| 1278 | + | ||
| 1279 | + /* Default cpu to use is the last one */ | ||
| 1280 | + if (!all_cpus && !setcpu) { | ||
| 1281 | + setcpu_buf = malloc(12); | ||
| 1282 | + if (!setcpu_buf) | ||
| 1283 | + fatal("malloc"); | ||
| 1284 | + sprintf(setcpu_buf, "%d", cpu_count - 1); | ||
| 1285 | + } | ||
| 1286 | + | ||
| 1287 | + setcpu = setcpu_buf; | ||
| 1288 | + | ||
| 1289 | + if (setcpu) | ||
| 1290 | + make_other_cpu_list(setcpu, &allcpu_buf); | ||
| 1291 | + | ||
| 1292 | + if (mlockall(MCL_CURRENT|MCL_FUTURE) == -1) | ||
| 1293 | + warn("mlockall"); | ||
| 1294 | + | ||
| 1295 | + setup_ftrace_marker(); | ||
| 1296 | + if (tracelimit && trace_marker) | ||
| 1297 | + enable_trace_mark(); | ||
| 1298 | + | ||
| 1299 | + thread = calloc(nr_threads, sizeof(*thread)); | ||
| 1300 | + sched_data = calloc(nr_threads, sizeof(*sched_data)); | ||
| 1301 | + if (!thread || !sched_data) | ||
| 1302 | + fatal("allocating threads"); | ||
| 1303 | + | ||
| 1304 | + if (histogram && hset_init(&hset, nr_threads, 1, histogram, histogram)) | ||
| 1305 | + fatal("failed to allocate histogram of size %d for %d threads\n", | ||
| 1306 | + histogram, nr_threads); | ||
| 1307 | + | ||
| 1308 | + if (nr_threads > nr_cpus) { | ||
| 1309 | + /* | ||
| 1310 | + * More threads than CPUs, then have the total be | ||
| 1311 | + * no more than 80 percent. | ||
| 1312 | + */ | ||
| 1313 | + percent = nr_cpus * 80 / nr_threads; | ||
| 1314 | + } | ||
| 1315 | + | ||
| 1316 | + /* Set up the data while still in SCHED_FIFO */ | ||
| 1317 | + for (i = 0; i < nr_threads; i++) { | ||
| 1318 | + sd = &sched_data[i]; | ||
| 1319 | + /* | ||
| 1320 | + * Interval is the deadline/period | ||
| 1321 | + * The runtime is the percentage of that period. | ||
| 1322 | + */ | ||
| 1323 | + runtime = interval * percent / 100; | ||
| 1324 | + | ||
| 1325 | + if (runtime < 2000) { | ||
| 1326 | + /* | ||
| 1327 | + * If the runtime is less than 2ms, then we better | ||
| 1328 | + * have HRTICK enabled. | ||
| 1329 | + */ | ||
| 1330 | + if (!setup_hr_tick()) | ||
| 1331 | + fatal("For less than 2ms run times, you need to\n" | ||
| 1332 | + "have HRTICK enabled in debugfs/sched_features\n"); | ||
| 1333 | + } | ||
| 1334 | + sd->runtime_us = runtime; | ||
| 1335 | + sd->deadline_us = interval; | ||
| 1336 | + | ||
| 1337 | + if (histogram) | ||
| 1338 | + sd->stat.hist = &hset.histos[i]; | ||
| 1339 | + | ||
| 1340 | + info(info_enable, "interval: %lld:%lld\n", sd->runtime_us, sd->deadline_us); | ||
| 1341 | + | ||
| 1342 | + /* Make sure that we can make our deadlines */ | ||
| 1343 | + start_period = get_time_us(); | ||
| 1344 | + do_runtime(sd, start_period); | ||
| 1345 | + end_period = get_time_us(); | ||
| 1346 | + if (end_period - start_period > sd->runtime_us) | ||
| 1347 | + fatal("Failed to perform task within runtime: Missed by %lld us\n", | ||
| 1348 | + end_period - start_period - sd->runtime_us); | ||
| 1349 | + | ||
| 1350 | + info(info_enable, " Tested at %lldus of %lldus\n", | ||
| 1351 | + end_period - start_period, sd->runtime_us); | ||
| 1352 | + | ||
| 1353 | + interval += step; | ||
| 1354 | + } | ||
| 1355 | + | ||
| 1356 | + | ||
| 1357 | + pthread_barrier_init(&barrier, NULL, nr_threads + 1); | ||
| 1358 | + | ||
| 1359 | + for (i = 0; i < nr_threads; i++) { | ||
| 1360 | + sd = &sched_data[i]; | ||
| 1361 | + pthread_create(&thread[i], NULL, run_deadline, sd); | ||
| 1362 | + } | ||
| 1363 | + | ||
| 1364 | + atexit(teardown); | ||
| 1365 | + | ||
| 1366 | + if (shutdown) | ||
| 1367 | + fatal("failed to setup child threads at step 1\n"); | ||
| 1368 | + | ||
| 1369 | + if (!all_cpus) { | ||
| 1370 | + int *pids; | ||
| 1371 | + | ||
| 1372 | + make_cpuset(CPUSET_ALL, allcpu_buf, "0", | ||
| 1373 | + CPUSET_FL_SET_LOADBALANCE | | ||
| 1374 | + CPUSET_FL_CLONE_CHILDREN | | ||
| 1375 | + CPUSET_FL_ALL_TASKS); | ||
| 1376 | + | ||
| 1377 | + pids = calloc(nr_threads + 1, sizeof(int)); | ||
| 1378 | + if (!pids) | ||
| 1379 | + fatal("Allocating pids"); | ||
| 1380 | + | ||
| 1381 | + for (i = 0; i < nr_threads; i++) | ||
| 1382 | + pids[i] = sched_data[i].stat.tid; | ||
| 1383 | + | ||
| 1384 | + make_cpuset(CPUSET_LOCAL, setcpu, "0", | ||
| 1385 | + CPUSET_FL_CPU_EXCLUSIVE | | ||
| 1386 | + CPUSET_FL_SET_LOADBALANCE | | ||
| 1387 | + CPUSET_FL_CLONE_CHILDREN | | ||
| 1388 | + CPUSET_FL_TASKS, pids); | ||
| 1389 | + | ||
| 1390 | + system("cat /sys/fs/cgroup/cpuset/my_cpuset/tasks"); | ||
| 1391 | + } | ||
| 1392 | + | ||
| 1393 | + debug(debug_enable, "main thread %d\n", gettid()); | ||
| 1394 | + | ||
| 1395 | + if (shutdown) | ||
| 1396 | + fatal("failed to setup child threads at step 2"); | ||
| 1397 | + | ||
| 1398 | + pthread_barrier_wait(&barrier); | ||
| 1399 | + | ||
| 1400 | + signal(SIGINT, sighand); | ||
| 1401 | + signal(SIGTERM, sighand); | ||
| 1402 | + signal(SIGALRM, sighand); | ||
| 1403 | + | ||
| 1404 | + if (duration) | ||
| 1405 | + alarm(duration); | ||
| 1406 | + | ||
| 1407 | + loop(sched_data, nr_threads); | ||
| 1408 | + | ||
| 1409 | + if (tracelimit) { | ||
| 1410 | + if (break_thread_id) { | ||
| 1411 | + printf("# Break thread: %d\n", break_thread_id); | ||
| 1412 | + printf("# Break value: %llu\n", (unsigned long long)break_thread_value); | ||
| 1413 | + } | ||
| 1414 | + } | ||
| 1415 | + | ||
| 1416 | + for (i = 0; i < nr_threads; i++) { | ||
| 1417 | + | ||
| 1418 | + sd = &sched_data[i]; | ||
| 1419 | + | ||
| 1420 | + res = join_thread(&thread[i]); | ||
| 1421 | + if (res) { | ||
| 1422 | + warn("Thread %d failed: %s\n", i, res); | ||
| 1423 | + continue; | ||
| 1424 | + } | ||
| 1425 | + } | ||
| 1426 | + | ||
| 1427 | + if (strlen(jsonfile) != 0) | ||
| 1428 | + rt_write_json(jsonfile, 0, write_stats, sched_data); | ||
| 1429 | + | ||
| 1430 | + if (setcpu_buf) | ||
| 1431 | + free(setcpu_buf); | ||
| 1432 | + free(thread); | ||
| 1433 | + free(sched_data); | ||
| 1434 | + if (histfile) | ||
| 1435 | + fclose(histfile); | ||
| 1436 | + hset_destroy(&hset); | ||
| 1437 | + | ||
| 1438 | + return 0; | ||
| 1439 | +} | ||
| @@ -0,0 +1,65 @@ | |||
| 1 | +.\" Hey, EMACS: -*- nroff -*- | ||
| 2 | +.TH DEADLINE_TEST 8 "November 1, 2018" | ||
| 3 | +# SPDX-License-Identifier: GPL-2.0-only | ||
| 4 | +.\" Please adjust this date whenever revising the manpage. | ||
| 5 | +.\" | ||
| 6 | +.\" Some roff macros, for reference: | ||
| 7 | +.\" .nh disable hyphenation | ||
| 8 | +.\" .hy enable hyphenation | ||
| 9 | +.\" .ad l left justify | ||
| 10 | +.\" .ad b justify to both left and right margins | ||
| 11 | +.\" .nf disable filling | ||
| 12 | +.\" .fi enable filling | ||
| 13 | +.\" .br insert line break | ||
| 14 | +.\" .sp <n> insert n+1 empty lines | ||
| 15 | +.\" for manpage-specific macros, see man(7) | ||
| 16 | +.SH NAME | ||
| 17 | +deadline_test \- High resolution test program | ||
| 18 | +.SH DESCRIPTION | ||
| 19 | +.PP | ||
| 20 | +This program is used to test the deadline scheduler (SCHED_DEADLINE tasks) | ||
| 21 | +.SH SYNOPSIS | ||
| 22 | +.B deadline_test | ||
| 23 | +.RI "[ \-hb ] [ \-c CPUSET ] [ \-i INTV ] [ \-p PERCENT ] [ \-P PERCENT ] \ | ||
| 24 | +[ \-r PRIO ] [ \-s STEP ] [ \-t NUM ]" | ||
| 25 | +.SH OPTIONS | ||
| 26 | +.TP | ||
| 27 | +.B \-b | ||
| 28 | +Bind on the last cpu. (shortcut for -c <lastcpu>) | ||
| 29 | +.br | ||
| 30 | +.TP | ||
| 31 | +.B \-c CPUSET | ||
| 32 | +Comma/hyphen separated list of CPUs to run deadline tasks on | ||
| 33 | +.br | ||
| 34 | +.TP | ||
| 35 | +.B \-h | ||
| 36 | +Show this help menu | ||
| 37 | +.br | ||
| 38 | +.TP | ||
| 39 | +.B \-i INTV | ||
| 40 | +The shortest deadline for the tasks | ||
| 41 | +.br | ||
| 42 | +.TP | ||
| 43 | +.B \-p PERCENT | ||
| 44 | +The percent of bandwidth to use (1-90%) | ||
| 45 | +.br | ||
| 46 | +.TP | ||
| 47 | +.B \-P PERCENT | ||
| 48 | +The percent of runtime for execution completion (default 100%) | ||
| 49 | +.br | ||
| 50 | +.TP | ||
| 51 | +.B \-r PRIO | ||
| 52 | +Add an RT task with given prio to stress system | ||
| 53 | +.br | ||
| 54 | +.TP | ||
| 55 | +.B \-s STEP | ||
| 56 | +The amount to increase the deadline for each task in us (default 500us) | ||
| 57 | +.br | ||
| 58 | +.TP | ||
| 59 | +.B \-t NUM | ||
| 60 | +The number of threads to run as deadline (default 1) | ||
| 61 | +.br | ||
| 62 | +.SH AUTHOR | ||
| 63 | +Deadline test was written by Steven Rostedt <rostedt@goodmis.org> | ||
| 64 | +.PP | ||
| 65 | +This manual page was written by John Kacur <jkacur@redhat.com> | ||
| @@ -0,0 +1,76 @@ | |||
| 1 | +.\" | ||
| 2 | +.TH SIGNALTEST 8 "November 15, 2020" | ||
| 3 | +# SPDX-License-Identifier: GPL-2.0-only | ||
| 4 | +.\" Please adjust this date whenever updating this manpage | ||
| 5 | +.SH NAME | ||
| 6 | +signaltest \- signal roundtrip test software | ||
| 7 | +.SH SYNOPSIS | ||
| 8 | +.LP | ||
| 9 | +signaltest [ -a|--affinity NUM] [ -b|--backtrace USEC ] [-D|--duration TIME] [-h|--help] [--json FILENAME] [-l|--loops LOOPS ] [-p|--prio PRIO] [-q|--quiet] [-S|--smp] [-t|--threads NUM] [-m|--mlockall ] [-v|--verbose ] | ||
| 10 | +.SH OPTIONS | ||
| 11 | +These programs follow the usual GNU command line syntax, with long options | ||
| 12 | +starting with two dashes ('\-\-'). | ||
| 13 | +.TP | ||
| 14 | +.B \-a, \-\-affinity[=PROC-SET] | ||
| 15 | +Run threads on the set of processors given by PROC-SET. If PROC-SET is not | ||
| 16 | +specified, all processors will be used. Threads will be assigned to processors | ||
| 17 | +in the set in numeric order, in a round\-robin fashion. | ||
| 18 | +.br | ||
| 19 | +The set of processors can be specified as A,B,C, or A-C, or A-B,D-F, and so on*. | ||
| 20 | +The ! character can be used to negate a set. For example, !B-D means to use all | ||
| 21 | +available CPUs except B through D. The cpu numbers are the same as shown in the | ||
| 22 | +.I processor | ||
| 23 | +field in /proc/cpuinfo. See numa(3) for more information on specifying CPU sets. | ||
| 24 | +* Support for CPU sets requires libnuma version >= 2. For libnuma v1, PROC-SET, | ||
| 25 | +if specified, must be a single CPU number. | ||
| 26 | +.TP | ||
| 27 | +.B \-b, \-\-breaktrace=USEC | ||
| 28 | +Send break trace command when latency > USEC | ||
| 29 | +.TP | ||
| 30 | +.B \-D, \-\-duration=TIME | ||
| 31 | +Specify a length for the test run. | ||
| 32 | +.br | ||
| 33 | +Append 'm', 'h', or 'd' to specify minutes, hours or days. | ||
| 34 | +.TP | ||
| 35 | +.B \-\-help | ||
| 36 | +.br | ||
| 37 | +display usage information | ||
| 38 | +.TP | ||
| 39 | +.B \-\-json=FILENAME | ||
| 40 | +Write final results into FILENAME, JSON formatted. | ||
| 41 | +.TP | ||
| 42 | +.B \-l, \-\-loops=LOOPS | ||
| 43 | +Number of loops: default=0 (endless) | ||
| 44 | +.TP | ||
| 45 | +.B \-p, \-\-priority=PRIO | ||
| 46 | +Priority of highest priority thread | ||
| 47 | +.TP | ||
| 48 | +.B \-q, \-\-quiet | ||
| 49 | +print a summary only on exit | ||
| 50 | +.TP | ||
| 51 | +.B \\-S, \-\-smp | ||
| 52 | +Set options for standard testing on SMP systems. Equivalent to using | ||
| 53 | +the options: "\-t \-a" as well keeping any specified priority | ||
| 54 | +equal across all threads | ||
| 55 | +.TP | ||
| 56 | +.B \-t, \-\-threads=NUM | ||
| 57 | +number of threads: default=2 | ||
| 58 | +.TP | ||
| 59 | +.B \-m, \-\-mlockall | ||
| 60 | +lock current and future memory allocations | ||
| 61 | +.TP | ||
| 62 | +.B \-v, \-\-verbose | ||
| 63 | +output values on stdout for statistics | ||
| 64 | +.br | ||
| 65 | +format: n:c:v n=tasknum c=count v=value in us | ||
| 66 | +.SH AUTHOR | ||
| 67 | +signaltest was written by Thomas Gleixner <tglx@linuxtronix.de>. | ||
| 68 | +.PP | ||
| 69 | +This manual page was originally written by | ||
| 70 | +.br | ||
| 71 | +John Kacur <jkacur@redhat.com> | ||
| 72 | +.PP | ||
| 73 | +Copying and distribution of this file, with or without modification, | ||
| 74 | +are permitted in any medium without royalty provided the copyright | ||
| 75 | +notice and this notice are preserved. This file is offered as-is, | ||
| 76 | +without any warranty. | ||
| @@ -0,0 +1,569 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | + | ||
| 3 | +/* | ||
| 4 | + * RT signal roundtrip test software | ||
| 5 | + * | ||
| 6 | + * (C) 2007 Thomas Gleixner <tglx@linutronix.de> | ||
| 7 | + * | ||
| 8 | + */ | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + | ||
| 35 | +/* Must be power of 2 ! */ | ||
| 36 | + | ||
| 37 | + | ||
| 38 | +/* Struct to transfer parameters to the thread */ | ||
| 39 | +struct thread_param { | ||
| 40 | + int id; | ||
| 41 | + int prio; | ||
| 42 | + int signal; | ||
| 43 | + unsigned long max_cycles; | ||
| 44 | + struct thread_stat *stats; | ||
| 45 | + int bufmsk; | ||
| 46 | + int cpu; | ||
| 47 | +}; | ||
| 48 | + | ||
| 49 | +/* Struct for statistics */ | ||
| 50 | +struct thread_stat { | ||
| 51 | + unsigned long cycles; | ||
| 52 | + unsigned long cyclesread; | ||
| 53 | + long min; | ||
| 54 | + long max; | ||
| 55 | + long act; | ||
| 56 | + double avg; | ||
| 57 | + long *values; | ||
| 58 | + pthread_t thread; | ||
| 59 | + pthread_t tothread; | ||
| 60 | + int threadstarted; | ||
| 61 | + int tid; | ||
| 62 | + int interrupted; | ||
| 63 | +}; | ||
| 64 | + | ||
| 65 | +static int shutdown; | ||
| 66 | +static int tracelimit; | ||
| 67 | + | ||
| 68 | + | ||
| 69 | +/* | ||
| 70 | + * signal thread | ||
| 71 | + * | ||
| 72 | + */ | ||
| 73 | +void *signalthread(void *param) | ||
| 74 | +{ | ||
| 75 | + struct thread_param *par = param; | ||
| 76 | + struct sched_param schedp; | ||
| 77 | + sigset_t sigset; | ||
| 78 | + struct timespec before, after; | ||
| 79 | + struct thread_stat *stat = par->stats; | ||
| 80 | + int policy = par->prio ? SCHED_FIFO : SCHED_OTHER; | ||
| 81 | + int stopped = 0; | ||
| 82 | + int first = 1; | ||
| 83 | + pthread_t thread; | ||
| 84 | + cpu_set_t mask; | ||
| 85 | + | ||
| 86 | + stat->tid = gettid(); | ||
| 87 | + | ||
| 88 | + if (par->cpu != -1) { | ||
| 89 | + CPU_ZERO(&mask); | ||
| 90 | + CPU_SET(par->cpu, &mask); | ||
| 91 | + thread = pthread_self(); | ||
| 92 | + if (pthread_setaffinity_np(thread, sizeof(mask), &mask) != 0) | ||
| 93 | + warn("Could not set CPU affinity to CPU #%d\n", | ||
| 94 | + par->cpu); | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | + sigemptyset(&sigset); | ||
| 98 | + sigaddset(&sigset, par->signal); | ||
| 99 | + sigprocmask(SIG_BLOCK, &sigset, NULL); | ||
| 100 | + | ||
| 101 | + memset(&schedp, 0, sizeof(schedp)); | ||
| 102 | + schedp.sched_priority = par->prio; | ||
| 103 | + sched_setscheduler(0, policy, &schedp); | ||
| 104 | + | ||
| 105 | + stat->threadstarted++; | ||
| 106 | + | ||
| 107 | + clock_gettime(CLOCK_MONOTONIC, &before); | ||
| 108 | + | ||
| 109 | + while (!shutdown) { | ||
| 110 | + struct timespec now; | ||
| 111 | + long diff; | ||
| 112 | + int sigs; | ||
| 113 | + | ||
| 114 | + if (sigwait(&sigset, &sigs) < 0) | ||
| 115 | + goto out; | ||
| 116 | + | ||
| 117 | + clock_gettime(CLOCK_MONOTONIC, &after); | ||
| 118 | + | ||
| 119 | + /* | ||
| 120 | + * If it is the first thread, sleep after every 16 | ||
| 121 | + * round trips. | ||
| 122 | + */ | ||
| 123 | + if (!par->id && !(stat->cycles & 0x0F)) | ||
| 124 | + usleep(10000); | ||
| 125 | + | ||
| 126 | + /* Get current time */ | ||
| 127 | + clock_gettime(CLOCK_MONOTONIC, &now); | ||
| 128 | + pthread_kill(stat->tothread, SIGUSR1); | ||
| 129 | + | ||
| 130 | + /* Skip the first cycle */ | ||
| 131 | + if (first) { | ||
| 132 | + first = 0; | ||
| 133 | + before = now; | ||
| 134 | + continue; | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + diff = calcdiff(after, before); | ||
| 138 | + before = now; | ||
| 139 | + | ||
| 140 | + if (diff < stat->min) | ||
| 141 | + stat->min = diff; | ||
| 142 | + if (diff > stat->max) | ||
| 143 | + stat->max = diff; | ||
| 144 | + stat->avg += (double) diff; | ||
| 145 | + | ||
| 146 | + if (!stopped && tracelimit && !par->id && (diff > tracelimit)) { | ||
| 147 | + stat->act = diff; | ||
| 148 | + stat->interrupted = 1; | ||
| 149 | + stopped++; | ||
| 150 | + shutdown++; | ||
| 151 | + } | ||
| 152 | + stat->act = diff; | ||
| 153 | + stat->cycles++; | ||
| 154 | + | ||
| 155 | + if (par->bufmsk) | ||
| 156 | + stat->values[stat->cycles & par->bufmsk] = diff; | ||
| 157 | + | ||
| 158 | + if (par->max_cycles && par->max_cycles == stat->cycles) | ||
| 159 | + break; | ||
| 160 | + } | ||
| 161 | + | ||
| 162 | +out: | ||
| 163 | + /* switch to normal */ | ||
| 164 | + schedp.sched_priority = 0; | ||
| 165 | + sched_setscheduler(0, SCHED_OTHER, &schedp); | ||
| 166 | + | ||
| 167 | + stat->threadstarted = -1; | ||
| 168 | + | ||
| 169 | + return NULL; | ||
| 170 | +} | ||
| 171 | + | ||
| 172 | + | ||
| 173 | +/* Print usage information */ | ||
| 174 | +static void display_help(int error) | ||
| 175 | +{ | ||
| 176 | + printf("signaltest V %1.2f\n", VERSION); | ||
| 177 | + printf("Usage:\n" | ||
| 178 | + "signaltest <options>\n\n" | ||
| 179 | + "-a [NUM] --affinity run thread #N on processor #N, if possible\n" | ||
| 180 | + " with NUM pin all threads to the processor NUM\n" | ||
| 181 | + "-b USEC --breaktrace=USEC send break trace command when latency > USEC\n" | ||
| 182 | + "-D --duration=TIME specify a length for the test run.\n" | ||
| 183 | + " Append 'm', 'h', or 'd' to specify minutes, hours or\n" | ||
| 184 | + " days.\n" | ||
| 185 | + "-h --help display usage information\n" | ||
| 186 | + " --json=FILENAME write final results into FILENAME, JSON formatted\n" | ||
| 187 | + "-l LOOPS --loops=LOOPS number of loops: default=0(endless)\n" | ||
| 188 | + "-m --mlockall lock current and future memory allocations\n" | ||
| 189 | + "-p PRIO --prio=PRIO priority of highest prio thread\n" | ||
| 190 | + "-q --quiet print a summary only on exit\n" | ||
| 191 | + "-t NUM --threads=NUM number of threads: default=2\n" | ||
| 192 | + "-v --verbose output values on stdout for statistics\n" | ||
| 193 | + " format: n:c:v n=tasknum c=count v=value in us\n" | ||
| 194 | + ); | ||
| 195 | + exit(error); | ||
| 196 | +} | ||
| 197 | + | ||
| 198 | +static int priority; | ||
| 199 | +static int num_threads = 2; | ||
| 200 | +static int max_cycles; | ||
| 201 | +static int duration; | ||
| 202 | +static int verbose; | ||
| 203 | +static int quiet; | ||
| 204 | +static int lockall; | ||
| 205 | +static struct bitmask *affinity_mask = NULL; | ||
| 206 | +static int smp = 0; | ||
| 207 | +static int numa = 0; | ||
| 208 | +static int setaffinity = AFFINITY_UNSPECIFIED; | ||
| 209 | +static char jsonfile[MAX_PATH]; | ||
| 210 | + | ||
| 211 | +enum option_values { | ||
| 212 | + OPT_AFFINITY=1, OPT_BREAKTRACE, | ||
| 213 | + OPT_DURATION, OPT_HELP, OPT_JSON, | ||
| 214 | + OPT_LOOPS, OPT_MLOCKALL, OPT_PRIORITY, | ||
| 215 | + OPT_QUIET, OPT_SMP, OPT_THREADS, OPT_VERBOSE | ||
| 216 | +}; | ||
| 217 | + | ||
| 218 | +/* Process commandline options */ | ||
| 219 | +static void process_options(int argc, char *argv[], unsigned int max_cpus) | ||
| 220 | +{ | ||
| 221 | + int option_affinity = 0; | ||
| 222 | + int error = 0; | ||
| 223 | + | ||
| 224 | + for (;;) { | ||
| 225 | + int option_index = 0; | ||
| 226 | + /** Options for getopt */ | ||
| 227 | + static struct option long_options[] = { | ||
| 228 | + {"affinity", optional_argument, NULL, OPT_AFFINITY}, | ||
| 229 | + {"breaktrace", required_argument, NULL, OPT_BREAKTRACE}, | ||
| 230 | + {"duration", required_argument, NULL, OPT_DURATION}, | ||
| 231 | + {"help", no_argument, NULL, OPT_HELP}, | ||
| 232 | + {"json", required_argument, NULL, OPT_JSON}, | ||
| 233 | + {"loops", required_argument, NULL, OPT_LOOPS}, | ||
| 234 | + {"mlockall", no_argument, NULL, OPT_MLOCKALL}, | ||
| 235 | + {"priority", required_argument, NULL, OPT_PRIORITY}, | ||
| 236 | + {"quiet", no_argument, NULL, OPT_QUIET}, | ||
| 237 | + {"smp", no_argument, NULL, OPT_SMP}, | ||
| 238 | + {"threads", required_argument, NULL, OPT_THREADS}, | ||
| 239 | + {"verbose", no_argument, NULL, OPT_VERBOSE}, | ||
| 240 | + {NULL, 0, NULL, 0} | ||
| 241 | + }; | ||
| 242 | + int c = getopt_long(argc, argv, "a::b:D:hl:mp:qSt:v", | ||
| 243 | + long_options, &option_index); | ||
| 244 | + if (c == -1) | ||
| 245 | + break; | ||
| 246 | + switch (c) { | ||
| 247 | + case OPT_AFFINITY: | ||
| 248 | + case 'a': | ||
| 249 | + option_affinity = 1; | ||
| 250 | + /* smp sets AFFINITY_USEALL in OPT_SMP */ | ||
| 251 | + if (smp) | ||
| 252 | + break; | ||
| 253 | + numa = numa_initialize(); | ||
| 254 | + if (optarg) { | ||
| 255 | + parse_cpumask(optarg, max_cpus, &affinity_mask); | ||
| 256 | + setaffinity = AFFINITY_SPECIFIED; | ||
| 257 | + } else if (optind < argc && | ||
| 258 | + (atoi(argv[optind]) || | ||
| 259 | + argv[optind][0] == '0' || | ||
| 260 | + argv[optind][0] == '!' || | ||
| 261 | + argv[optind][0] == '+' || | ||
| 262 | + argv[optind][0] == 'a')) { | ||
| 263 | + parse_cpumask(argv[optind], max_cpus, &affinity_mask); | ||
| 264 | + setaffinity = AFFINITY_SPECIFIED; | ||
| 265 | + } else { | ||
| 266 | + setaffinity = AFFINITY_USEALL; | ||
| 267 | + } | ||
| 268 | + | ||
| 269 | + if (setaffinity == AFFINITY_SPECIFIED && !affinity_mask) | ||
| 270 | + display_help(1); | ||
| 271 | + if (verbose) | ||
| 272 | + printf("Using %u cpus.\n", | ||
| 273 | + numa_bitmask_weight(affinity_mask)); | ||
| 274 | + break; | ||
| 275 | + case OPT_BREAKTRACE: | ||
| 276 | + case 'b': | ||
| 277 | + tracelimit = atoi(optarg); | ||
| 278 | + break; | ||
| 279 | + case OPT_DURATION: | ||
| 280 | + case 'D': | ||
| 281 | + duration = parse_time_string(optarg); | ||
| 282 | + break; | ||
| 283 | + case OPT_HELP: | ||
| 284 | + case '?': | ||
| 285 | + case 'h': | ||
| 286 | + display_help(0); | ||
| 287 | + break; | ||
| 288 | + case OPT_JSON: | ||
| 289 | + strncpy(jsonfile, optarg, strnlen(optarg, MAX_PATH-1)); | ||
| 290 | + break; | ||
| 291 | + case OPT_LOOPS: | ||
| 292 | + case 'l': | ||
| 293 | + max_cycles = atoi(optarg); | ||
| 294 | + break; | ||
| 295 | + case OPT_MLOCKALL: | ||
| 296 | + case 'm': | ||
| 297 | + lockall = 1; | ||
| 298 | + break; | ||
| 299 | + case OPT_PRIORITY: | ||
| 300 | + case 'p': | ||
| 301 | + priority = atoi(optarg); | ||
| 302 | + break; | ||
| 303 | + case OPT_QUIET: | ||
| 304 | + case 'q': | ||
| 305 | + quiet = 1; | ||
| 306 | + break; | ||
| 307 | + case OPT_SMP: | ||
| 308 | + case 'S': | ||
| 309 | + if (numa) | ||
| 310 | + fatal("numa and smp options are mutually exclusive\n"); | ||
| 311 | + smp = 1; | ||
| 312 | + num_threads = -1; /* update after parsing */ | ||
| 313 | + setaffinity = AFFINITY_USEALL; | ||
| 314 | + break; | ||
| 315 | + case OPT_THREADS: | ||
| 316 | + case 't': | ||
| 317 | + num_threads = atoi(optarg); | ||
| 318 | + break; | ||
| 319 | + case OPT_VERBOSE: | ||
| 320 | + case 'v': verbose = 1; | ||
| 321 | + break; | ||
| 322 | + } | ||
| 323 | + } | ||
| 324 | + | ||
| 325 | + if (duration < 0) | ||
| 326 | + error = 1; | ||
| 327 | + | ||
| 328 | + if (priority < 0 || priority > 99) | ||
| 329 | + error = 1; | ||
| 330 | + | ||
| 331 | + if (num_threads == -1) | ||
| 332 | + num_threads = get_available_cpus(affinity_mask); | ||
| 333 | + | ||
| 334 | + if (num_threads < 2) | ||
| 335 | + error = 1; | ||
| 336 | + | ||
| 337 | + /* if smp wasn't requested, test for numa automatically */ | ||
| 338 | + if (!smp) { | ||
| 339 | + numa = numa_initialize(); | ||
| 340 | + if (setaffinity == AFFINITY_UNSPECIFIED) | ||
| 341 | + setaffinity = AFFINITY_USEALL; | ||
| 342 | + } | ||
| 343 | + | ||
| 344 | + if (option_affinity) { | ||
| 345 | + if (smp) | ||
| 346 | + warn("-a ignored due to smp mode\n"); | ||
| 347 | + } | ||
| 348 | + | ||
| 349 | + if (error) { | ||
| 350 | + if (affinity_mask) | ||
| 351 | + numa_bitmask_free(affinity_mask); | ||
| 352 | + display_help(error); | ||
| 353 | + } | ||
| 354 | +} | ||
| 355 | + | ||
| 356 | +static void sighand(int sig __attribute__ ((unused))) | ||
| 357 | +{ | ||
| 358 | + shutdown = 1; | ||
| 359 | +} | ||
| 360 | + | ||
| 361 | +static void print_stat(struct thread_param *par, int index, int verbose) | ||
| 362 | +{ | ||
| 363 | + struct thread_stat *stat = par->stats; | ||
| 364 | + | ||
| 365 | + if (!verbose) { | ||
| 366 | + if (quiet != 1) { | ||
| 367 | + printf("T:%2d (%5d) P:%2d C:%7lu " | ||
| 368 | + "Min:%7ld Act:%5ld Avg:%5ld Max:%8ld\n", | ||
| 369 | + index, stat->tid, par->prio, | ||
| 370 | + stat->cycles, stat->min, stat->act, | ||
| 371 | + stat->cycles ? | ||
| 372 | + (long)(stat->avg/stat->cycles) : 0, stat->max); | ||
| 373 | + } | ||
| 374 | + } else { | ||
| 375 | + while (stat->cycles != stat->cyclesread) { | ||
| 376 | + long diff = stat->values[stat->cyclesread & par->bufmsk]; | ||
| 377 | + printf("%8d:%8lu:%8ld\n", index, stat->cyclesread, diff); | ||
| 378 | + stat->cyclesread++; | ||
| 379 | + } | ||
| 380 | + } | ||
| 381 | +} | ||
| 382 | + | ||
| 383 | +static void write_stats(FILE *f, void *data) | ||
| 384 | +{ | ||
| 385 | + struct thread_param *par = data; | ||
| 386 | + struct thread_stat *s; | ||
| 387 | + int i; | ||
| 388 | + | ||
| 389 | + fprintf(f, " \"num_threads\": %d,\n", num_threads); | ||
| 390 | + fprintf(f, " \"thread\": {\n"); | ||
| 391 | + for (i = 0; i < num_threads; i++) { | ||
| 392 | + fprintf(f, " \"%u\": {\n", i); | ||
| 393 | + s = &par->stats[i]; | ||
| 394 | + fprintf(f, " \"cycles\": %ld,\n", s->cycles); | ||
| 395 | + fprintf(f, " \"min\": %ld,\n", s->min); | ||
| 396 | + fprintf(f, " \"max\": %ld,\n", s->max); | ||
| 397 | + fprintf(f, " \"avg\": %.2f,\n", s->avg/s->cycles); | ||
| 398 | + fprintf(f, " \"cpu\": %d\n", par->cpu); | ||
| 399 | + fprintf(f, " }%s\n", i == num_threads - 1 ? "" : ","); | ||
| 400 | + | ||
| 401 | + } | ||
| 402 | + fprintf(f, " }\n"); | ||
| 403 | +} | ||
| 404 | + | ||
| 405 | +int main(int argc, char **argv) | ||
| 406 | +{ | ||
| 407 | + sigset_t sigset; | ||
| 408 | + int signum = SIGUSR1; | ||
| 409 | + struct thread_param *par; | ||
| 410 | + struct thread_stat *stat; | ||
| 411 | + int i, ret = -1; | ||
| 412 | + int status, cpu; | ||
| 413 | + int max_cpus = sysconf(_SC_NPROCESSORS_ONLN); | ||
| 414 | + | ||
| 415 | + rt_init(argc, argv); | ||
| 416 | + process_options(argc, argv, max_cpus); | ||
| 417 | + | ||
| 418 | + if (check_privs()) | ||
| 419 | + exit(1); | ||
| 420 | + | ||
| 421 | + /* lock all memory (prevent paging) */ | ||
| 422 | + if (lockall) | ||
| 423 | + if (mlockall(MCL_CURRENT|MCL_FUTURE) == -1) { | ||
| 424 | + perror("mlockall"); | ||
| 425 | + goto out; | ||
| 426 | + } | ||
| 427 | + | ||
| 428 | + /* Restrict the main pid to the affinity specified by the user */ | ||
| 429 | + if (affinity_mask != NULL) { | ||
| 430 | + int res; | ||
| 431 | + | ||
| 432 | + errno = 0; | ||
| 433 | + res = numa_sched_setaffinity(getpid(), affinity_mask); | ||
| 434 | + if (res != 0) | ||
| 435 | + warn("Couldn't setaffinity in main thread: %s\n", strerror(errno)); | ||
| 436 | + } | ||
| 437 | + | ||
| 438 | + sigemptyset(&sigset); | ||
| 439 | + sigaddset(&sigset, signum); | ||
| 440 | + sigprocmask(SIG_BLOCK, &sigset, NULL); | ||
| 441 | + | ||
| 442 | + signal(SIGINT, sighand); | ||
| 443 | + signal(SIGTERM, sighand); | ||
| 444 | + signal(SIGALRM, sighand); | ||
| 445 | + | ||
| 446 | + if (duration) | ||
| 447 | + alarm(duration); | ||
| 448 | + | ||
| 449 | + par = calloc(num_threads, sizeof(struct thread_param)); | ||
| 450 | + if (!par) | ||
| 451 | + goto out; | ||
| 452 | + stat = calloc(num_threads, sizeof(struct thread_stat)); | ||
| 453 | + if (!stat) | ||
| 454 | + goto outpar; | ||
| 455 | + | ||
| 456 | + for (i = 0; i < num_threads; i++) { | ||
| 457 | + if (verbose) { | ||
| 458 | + stat[i].values = calloc(VALBUF_SIZE, sizeof(long)); | ||
| 459 | + if (!stat[i].values) | ||
| 460 | + goto outall; | ||
| 461 | + par[i].bufmsk = VALBUF_SIZE - 1; | ||
| 462 | + } | ||
| 463 | + | ||
| 464 | + switch (setaffinity) { | ||
| 465 | + case AFFINITY_UNSPECIFIED: | ||
| 466 | + cpu = -1; | ||
| 467 | + break; | ||
| 468 | + case AFFINITY_SPECIFIED: | ||
| 469 | + cpu = cpu_for_thread_sp(i, max_cpus, affinity_mask); | ||
| 470 | + if (verbose) | ||
| 471 | + printf("Thread %d using cpu %d.\n", i, cpu); | ||
| 472 | + break; | ||
| 473 | + case AFFINITY_USEALL: | ||
| 474 | + cpu = cpu_for_thread_ua(i, max_cpus); | ||
| 475 | + break; | ||
| 476 | + default: | ||
| 477 | + cpu = -1; | ||
| 478 | + } | ||
| 479 | + | ||
| 480 | + par[i].id = i; | ||
| 481 | + par[i].prio = priority; | ||
| 482 | + | ||
| 483 | + if (priority) | ||
| 484 | + priority--; | ||
| 485 | + | ||
| 486 | + par[i].signal = signum; | ||
| 487 | + par[i].max_cycles = max_cycles; | ||
| 488 | + par[i].stats = &stat[i]; | ||
| 489 | + par[i].cpu = cpu; | ||
| 490 | + stat[i].min = 1000000; | ||
| 491 | + stat[i].max = -1000000; | ||
| 492 | + stat[i].avg = 0.0; | ||
| 493 | + stat[i].threadstarted = 1; | ||
| 494 | + status = pthread_create(&stat[i].thread, NULL, signalthread, | ||
| 495 | + &par[i]); | ||
| 496 | + if (status) | ||
| 497 | + fatal("failed to create thread %d: %s\n", i, | ||
| 498 | + strerror(status)); | ||
| 499 | + } | ||
| 500 | + | ||
| 501 | + while (!shutdown) { | ||
| 502 | + int allstarted = 1; | ||
| 503 | + | ||
| 504 | + for (i = 0; i < num_threads; i++) { | ||
| 505 | + if (stat[i].threadstarted != 2) | ||
| 506 | + allstarted = 0; | ||
| 507 | + } | ||
| 508 | + if (!allstarted) | ||
| 509 | + continue; | ||
| 510 | + | ||
| 511 | + for (i = 0; i < num_threads - 1; i++) | ||
| 512 | + stat[i].tothread = stat[i+1].thread; | ||
| 513 | + stat[i].tothread = stat[0].thread; | ||
| 514 | + break; | ||
| 515 | + } | ||
| 516 | + pthread_kill(stat[0].thread, signum); | ||
| 517 | + | ||
| 518 | + while (!shutdown) { | ||
| 519 | + char lavg[256]; | ||
| 520 | + int fd, len, allstopped = 0; | ||
| 521 | + | ||
| 522 | + if (!verbose && !quiet) { | ||
| 523 | + fd = open("/proc/loadavg", O_RDONLY, 0666); | ||
| 524 | + len = read(fd, &lavg, 255); | ||
| 525 | + close(fd); | ||
| 526 | + lavg[len-1] = 0x0; | ||
| 527 | + printf("%s \n\n", lavg); | ||
| 528 | + } | ||
| 529 | + | ||
| 530 | + print_stat(&par[0], 0, verbose); | ||
| 531 | + if (max_cycles && stat[0].cycles >= max_cycles) | ||
| 532 | + allstopped++; | ||
| 533 | + | ||
| 534 | + usleep(10000); | ||
| 535 | + if (shutdown || allstopped) | ||
| 536 | + break; | ||
| 537 | + if (!verbose && !quiet) | ||
| 538 | + printf("\033[%dA", 3); | ||
| 539 | + } | ||
| 540 | + ret = 0; | ||
| 541 | + outall: | ||
| 542 | + shutdown = 1; | ||
| 543 | + usleep(50000); | ||
| 544 | + if (quiet) | ||
| 545 | + quiet = 2; | ||
| 546 | + for (i = 0; i < num_threads; i++) { | ||
| 547 | + if (stat[i].threadstarted > 0) | ||
| 548 | + pthread_kill(stat[i].thread, SIGUSR1); | ||
| 549 | + if (stat[i].interrupted) | ||
| 550 | + printf("Thread %d exceeded trace limit.\n", i); | ||
| 551 | + if (stat[i].threadstarted) { | ||
| 552 | + pthread_join(stat[i].thread, NULL); | ||
| 553 | + print_stat(&par[i], i, 0); | ||
| 554 | + } | ||
| 555 | + if (stat[i].values) | ||
| 556 | + free(stat[i].values); | ||
| 557 | + } | ||
| 558 | + if (strlen(jsonfile) != 0) | ||
| 559 | + rt_write_json(jsonfile, ret, write_stats, par); | ||
| 560 | + | ||
| 561 | + free(stat); | ||
| 562 | + outpar: | ||
| 563 | + free(par); | ||
| 564 | + out: | ||
| 565 | + if (lockall) | ||
| 566 | + munlockall(); | ||
| 567 | + | ||
| 568 | + exit(ret); | ||
| 569 | +} | ||
| @@ -0,0 +1,66 @@ | |||
| 1 | +.TH "sigwaittest" "8" "0.1" "" "" | ||
| 2 | +# SPDX-License-Identifier: GPL-2.0-only | ||
| 3 | +.SH "NAME" | ||
| 4 | +.LP | ||
| 5 | +\fBsigwaittest\fR \- Start two threads or fork two processes and measure the latency between sending and receiving a signal | ||
| 6 | +.SH "SYNTAX" | ||
| 7 | +.LP | ||
| 8 | +sigwaittest [-a|--affinity PROC] [-b|--breaktrace USEC] [-d|--distance DIST] [-D|--duration TIME] [-f|--fork [OPT]] [-i|--interval INTV] [--json FILENAME] [-l|--loops LOOPS] [-p|--prio PRIO] [-t|--threads [NUM]] | ||
| 9 | +.br | ||
| 10 | +.SH "DESCRIPTION" | ||
| 11 | +.LP | ||
| 12 | +The program \fBsigwaittest\fR starts two threads or, optionally, forks two processes that are synchronized via signals and measures the latency between sending a signal and returning from sigwait(). | ||
| 13 | +.SH "OPTIONS" | ||
| 14 | +.TP | ||
| 15 | +.B \-a, \-\-affinity[=PROC] | ||
| 16 | +Run on processor number PROC. If PROC is not specified, run on current processor. | ||
| 17 | +.TP | ||
| 18 | +.B \-b, \-\-breaktrace=USEC | ||
| 19 | +Send break trace command when latency > USEC. This is a debugging option to control the latency tracer in the realtime preemption patch. | ||
| 20 | +It is useful to track down unexpected large latencies of a system. | ||
| 21 | +.TP | ||
| 22 | +.B \-d, \-\-distance=DIST | ||
| 23 | +Set the distance of thread intervals in microseconds (default is 500 us). When cyclictest is called with the -t option and more than one thread is created, then this distance value is added to the interval of the threads: Interval(thread N) = Interval(thread N-1) + DIST | ||
| 24 | +.TP | ||
| 25 | +.B \-D, \-\-duration=TIME | ||
| 26 | +Specify a length for the test run. | ||
| 27 | +.br | ||
| 28 | +Append 'm', 'h', or 'd' to specify minutes, hours or days. | ||
| 29 | +.TP | ||
| 30 | +.B \-f, \-\-fork[=OPT] | ||
| 31 | +Instead of creating threads (which is the default), fork new processes | ||
| 32 | +.TP | ||
| 33 | +.B \-i, \-\-interval=INTV | ||
| 34 | +Set the base interval of the thread(s) in microseconds (default is 1000 us). This sets the interval of the first thread. See also -d. | ||
| 35 | +.TP | ||
| 36 | +.B \-\-json=FILENAME | ||
| 37 | +Write final results into FILENAME, JSON formatted. | ||
| 38 | +.TP | ||
| 39 | +.B \-l, \-\-loops=LOOPS | ||
| 40 | +Set the number of loops. The default is 0 (endless). This option is useful for automated tests with a given number of test cycles. sigwaittest is stopped once the number of timer intervals has been reached. | ||
| 41 | +.TP | ||
| 42 | +.B \-p, \-\-prio=PRIO | ||
| 43 | +Set the priority of the process. | ||
| 44 | +.TP | ||
| 45 | +.B \-t, \-\-threads[=NUM] | ||
| 46 | +Set the number of test threads (default is 1, if this option is not given). If NUM is specified, create NUM test threads. If NUM is not specified, NUM is set to the number of available CPUs. | ||
| 47 | +.SH "EXAMPLES" | ||
| 48 | +The following example was running on a 4-way CPU: | ||
| 49 | +.LP | ||
| 50 | +.nf | ||
| 51 | +# sigwaittest -a -t -p99 -i100 -d25 -l1000000 | ||
| 52 | +#0: ID11510, P99, CPU0, I100; #1: ID11511, P99, CPU0, Cycles 1000000 | ||
| 53 | +#2: ID11512, P98, CPU1, I125; #3: ID11513, P98, CPU1, Cycles 817484 | ||
| 54 | +#4: ID11514, P97, CPU2, I150; #5: ID11515, P97, CPU2, Cycles 668213 | ||
| 55 | +#6: ID11516, P96, CPU3, I175; #7: ID11517, P96, CPU3, Cycles 597344 | ||
| 56 | +#1 -> #0, Min 1, Cur 2, Avg 3, Max 30 | ||
| 57 | +#3 -> #2, Min 1, Cur 26, Avg 3, Max 42 | ||
| 58 | +#5 -> #4, Min 1, Cur 46, Avg 4, Max 67 | ||
| 59 | +#7 -> #6, Min 1, Cur 2, Avg 3, Max 74 | ||
| 60 | +.fi | ||
| 61 | +.SH "AUTHORS" | ||
| 62 | +.LP | ||
| 63 | +Carsten Emde <C.Emde@osadl.org> | ||
| 64 | +.SH "SEE ALSO" | ||
| 65 | +.LP | ||
| 66 | +kill(2), sigwait(3) | ||
| @@ -0,0 +1,705 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | + | ||
| 3 | +/* | ||
| 4 | + * sigwaittest.c | ||
| 5 | + * | ||
| 6 | + * Copyright (C) 2009 Carsten Emde <C.Emde@osadl.org> | ||
| 7 | + * | ||
| 8 | + */ | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | +enum { | ||
| 35 | + AFFINITY_UNSPECIFIED, | ||
| 36 | + AFFINITY_SPECIFIED, | ||
| 37 | + AFFINITY_USEALL | ||
| 38 | +}; | ||
| 39 | + | ||
| 40 | +struct params { | ||
| 41 | + int num; | ||
| 42 | + int num_threads; | ||
| 43 | + int cpu; | ||
| 44 | + int priority; | ||
| 45 | + int affinity; | ||
| 46 | + int sender; | ||
| 47 | + int samples; | ||
| 48 | + int max_cycles; | ||
| 49 | + int tracelimit; | ||
| 50 | + int tid; | ||
| 51 | + pid_t pid; | ||
| 52 | + int shutdown; | ||
| 53 | + int stopped; | ||
| 54 | + struct timespec delay; | ||
| 55 | + unsigned int mindiff, maxdiff; | ||
| 56 | + double sumdiff; | ||
| 57 | + struct timeval unblocked, received, diff; | ||
| 58 | + pthread_t threadid; | ||
| 59 | + struct params *neighbor; | ||
| 60 | +}; | ||
| 61 | + | ||
| 62 | +static int mustfork; | ||
| 63 | +static int wasforked; | ||
| 64 | +static int wasforked_sender = -1; | ||
| 65 | +static int wasforked_threadno = -1; | ||
| 66 | +static int tracelimit; | ||
| 67 | + | ||
| 68 | +void *semathread(void *param) | ||
| 69 | +{ | ||
| 70 | + int mustgetcpu = 0; | ||
| 71 | + struct params *par = param; | ||
| 72 | + cpu_set_t mask; | ||
| 73 | + int policy = SCHED_FIFO; | ||
| 74 | + struct sched_param schedp; | ||
| 75 | + | ||
| 76 | + memset(&schedp, 0, sizeof(schedp)); | ||
| 77 | + schedp.sched_priority = par->priority; | ||
| 78 | + sched_setscheduler(0, policy, &schedp); | ||
| 79 | + | ||
| 80 | + if (par->cpu != -1) { | ||
| 81 | + CPU_ZERO(&mask); | ||
| 82 | + CPU_SET(par->cpu, &mask); | ||
| 83 | + if (sched_setaffinity(0, sizeof(mask), &mask) == -1) | ||
| 84 | + fprintf(stderr, "WARNING: Could not set CPU affinity " | ||
| 85 | + "to CPU #%d\n", par->cpu); | ||
| 86 | + } else { | ||
| 87 | + int max_cpus = sysconf(_SC_NPROCESSORS_CONF); | ||
| 88 | + | ||
| 89 | + if (max_cpus > 1) | ||
| 90 | + mustgetcpu = 1; | ||
| 91 | + else | ||
| 92 | + par->cpu = 0; | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + if (!wasforked) | ||
| 96 | + par->tid = gettid(); | ||
| 97 | + | ||
| 98 | + while (!par->shutdown) { | ||
| 99 | + int sig; | ||
| 100 | + int first = 1; | ||
| 101 | + sigset_t sigset; | ||
| 102 | + struct params *neighbor = NULL; | ||
| 103 | + | ||
| 104 | + if (par->sender) { | ||
| 105 | + if (wasforked) | ||
| 106 | + neighbor = par - par->num_threads; | ||
| 107 | + else | ||
| 108 | + neighbor = par->neighbor; | ||
| 109 | + if (first) { | ||
| 110 | + sigemptyset(&sigset); | ||
| 111 | + sigaddset(&sigset, SIGUSR1); | ||
| 112 | + pthread_sigmask(SIG_SETMASK, &sigset, NULL); | ||
| 113 | + first = 0; | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + /* Sending signal: Start of latency measurement ... */ | ||
| 117 | + gettimeofday(&par->unblocked, NULL); | ||
| 118 | + if (wasforked) | ||
| 119 | + kill(neighbor->pid, SIGUSR2); | ||
| 120 | + else | ||
| 121 | + pthread_kill(neighbor->threadid, SIGUSR2); | ||
| 122 | + par->samples++; | ||
| 123 | + if (par->max_cycles && par->samples >= par->max_cycles) | ||
| 124 | + par->shutdown = 1; | ||
| 125 | + | ||
| 126 | + if (mustgetcpu) | ||
| 127 | + par->cpu = get_cpu(); | ||
| 128 | + sigwait(&sigset, &sig); | ||
| 129 | + } else { | ||
| 130 | + /* Receiver */ | ||
| 131 | + if (wasforked) | ||
| 132 | + neighbor = par + par->num_threads; | ||
| 133 | + else | ||
| 134 | + neighbor = par->neighbor; | ||
| 135 | + if (first) { | ||
| 136 | + sigemptyset(&sigset); | ||
| 137 | + sigaddset(&sigset, SIGUSR2); | ||
| 138 | + pthread_sigmask(SIG_SETMASK, &sigset, NULL); | ||
| 139 | + first = 0; | ||
| 140 | + } | ||
| 141 | + sigwait(&sigset, &sig); | ||
| 142 | + | ||
| 143 | + /* ... Signal received: End of latency measurement */ | ||
| 144 | + gettimeofday(&par->received, NULL); | ||
| 145 | + par->samples++; | ||
| 146 | + if (par->max_cycles && par->samples >= par->max_cycles) | ||
| 147 | + par->shutdown = 1; | ||
| 148 | + | ||
| 149 | + if (mustgetcpu) | ||
| 150 | + par->cpu = get_cpu(); | ||
| 151 | + /* | ||
| 152 | + * Latency is the time spent between sending and | ||
| 153 | + * receiving the signal. | ||
| 154 | + */ | ||
| 155 | + timersub(&par->received, &neighbor->unblocked, | ||
| 156 | + &par->diff); | ||
| 157 | + | ||
| 158 | + if (par->diff.tv_usec < par->mindiff) | ||
| 159 | + par->mindiff = par->diff.tv_usec; | ||
| 160 | + if (par->diff.tv_usec > par->maxdiff) | ||
| 161 | + par->maxdiff = par->diff.tv_usec; | ||
| 162 | + par->sumdiff += (double) par->diff.tv_usec; | ||
| 163 | + if (par->tracelimit && par->maxdiff > par->tracelimit) { | ||
| 164 | + char tracing_enabled_file[MAX_PATH]; | ||
| 165 | + | ||
| 166 | + strcpy(tracing_enabled_file, get_debugfileprefix()); | ||
| 167 | + strcat(tracing_enabled_file, "tracing_on"); | ||
| 168 | + int tracing_enabled = | ||
| 169 | + open(tracing_enabled_file, O_WRONLY); | ||
| 170 | + if (tracing_enabled >= 0) { | ||
| 171 | + write(tracing_enabled, "0", 1); | ||
| 172 | + close(tracing_enabled); | ||
| 173 | + } else | ||
| 174 | + fatal("Could not access %s\n", | ||
| 175 | + tracing_enabled_file); | ||
| 176 | + par->shutdown = 1; | ||
| 177 | + neighbor->shutdown = 1; | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + nanosleep(&par->delay, NULL); | ||
| 181 | + | ||
| 182 | + if (wasforked) | ||
| 183 | + kill(neighbor->pid, SIGUSR1); | ||
| 184 | + else | ||
| 185 | + pthread_kill(neighbor->threadid, SIGUSR1); | ||
| 186 | + } | ||
| 187 | + } | ||
| 188 | + par->stopped = 1; | ||
| 189 | + return NULL; | ||
| 190 | +} | ||
| 191 | + | ||
| 192 | + | ||
| 193 | +static void display_help(int error) | ||
| 194 | +{ | ||
| 195 | + printf("sigwaittest V %1.2f\n", VERSION); | ||
| 196 | + printf("Usage:\n" | ||
| 197 | + "sigwaittest <options>\n\n" | ||
| 198 | + "Function: test sigwait() latency\n\n" | ||
| 199 | + "Available options:\n" | ||
| 200 | + "-a [NUM] --affinity run thread #N on processor #N, if possible\n" | ||
| 201 | + " with NUM pin all threads to the processor NUM\n" | ||
| 202 | + "-b USEC --breaktrace=USEC send break trace command when latency > USEC\n" | ||
| 203 | + "-d DIST --distance=DIST distance of thread intervals in us default=500\n" | ||
| 204 | + "-D --duration=TIME specify a length for the test run.\n" | ||
| 205 | + " Append 'm', 'h', or 'd' to specify minutes, hours or\n" | ||
| 206 | + " days.\n" | ||
| 207 | + "-f [OPT] --fork[=OPT] fork new processes instead of creating threads\n" | ||
| 208 | + "-i INTV --interval=INTV base interval of thread in us default=1000\n" | ||
| 209 | + " --json=FILENAME write final results into FILENAME, JSON formatted\n" | ||
| 210 | + "-l LOOPS --loops=LOOPS number of loops: default=0(endless)\n" | ||
| 211 | + "-p PRIO --prio=PRIO priority\n" | ||
| 212 | + "-q --quiet print a summary only on exit\n" | ||
| 213 | + "-t --threads one thread per available processor\n" | ||
| 214 | + "-t [NUM] --threads=NUM number of threads:\n" | ||
| 215 | + " without NUM, threads = max_cpus\n" | ||
| 216 | + " without -t default = 1\n" | ||
| 217 | + ); | ||
| 218 | + exit(error); | ||
| 219 | +} | ||
| 220 | + | ||
| 221 | + | ||
| 222 | +static int setaffinity = AFFINITY_UNSPECIFIED; | ||
| 223 | +static int affinity; | ||
| 224 | +static int priority; | ||
| 225 | +static int num_threads = 1; | ||
| 226 | +static int max_cycles; | ||
| 227 | +static int duration; | ||
| 228 | +static int interval = 1000; | ||
| 229 | +static int distance = 500; | ||
| 230 | +static int quiet; | ||
| 231 | +static char jsonfile[MAX_PATH]; | ||
| 232 | + | ||
| 233 | +enum option_value { | ||
| 234 | + OPT_AFFINITY=1, OPT_BREAKTRACE, OPT_DISTANCE, OPT_DURATION, | ||
| 235 | + OPT_FORK, OPT_HELP, OPT_INTERVAL, OPT_JSON, OPT_LOOPS, | ||
| 236 | + OPT_PRIORITY, OPT_QUIET, OPT_THREADS | ||
| 237 | +}; | ||
| 238 | + | ||
| 239 | +static void process_options(int argc, char *argv[]) | ||
| 240 | +{ | ||
| 241 | + int error = 0; | ||
| 242 | + int max_cpus = sysconf(_SC_NPROCESSORS_CONF); | ||
| 243 | + int thistracelimit = 0; | ||
| 244 | + | ||
| 245 | + for (;;) { | ||
| 246 | + int option_index = 0; | ||
| 247 | + /** Options for getopt */ | ||
| 248 | + static struct option long_options[] = { | ||
| 249 | + {"affinity", optional_argument, NULL, OPT_AFFINITY}, | ||
| 250 | + {"breaktrace", required_argument, NULL, OPT_BREAKTRACE}, | ||
| 251 | + {"distance", required_argument, NULL, OPT_DISTANCE}, | ||
| 252 | + {"duration", required_argument, NULL, OPT_DURATION}, | ||
| 253 | + {"fork", optional_argument, NULL, OPT_FORK}, | ||
| 254 | + {"help", no_argument, NULL, OPT_HELP}, | ||
| 255 | + {"interval", required_argument, NULL, OPT_INTERVAL}, | ||
| 256 | + {"json", required_argument, NULL, OPT_JSON}, | ||
| 257 | + {"loops", required_argument, NULL, OPT_LOOPS}, | ||
| 258 | + {"priority", required_argument, NULL, OPT_PRIORITY}, | ||
| 259 | + {"quiet", no_argument, NULL, OPT_QUIET}, | ||
| 260 | + {"threads", optional_argument, NULL, OPT_THREADS}, | ||
| 261 | + {NULL, 0, NULL, 0} | ||
| 262 | + }; | ||
| 263 | + int c = getopt_long (argc, argv, "a::b:d:D:f::hi:l:p:qt::", | ||
| 264 | + long_options, &option_index); | ||
| 265 | + if (c == -1) | ||
| 266 | + break; | ||
| 267 | + switch (c) { | ||
| 268 | + case OPT_AFFINITY: | ||
| 269 | + case 'a': | ||
| 270 | + if (optarg != NULL) { | ||
| 271 | + affinity = atoi(optarg); | ||
| 272 | + setaffinity = AFFINITY_SPECIFIED; | ||
| 273 | + } else if (optind < argc && atoi(argv[optind])) { | ||
| 274 | + affinity = atoi(argv[optind]); | ||
| 275 | + setaffinity = AFFINITY_SPECIFIED; | ||
| 276 | + } else { | ||
| 277 | + setaffinity = AFFINITY_USEALL; | ||
| 278 | + } | ||
| 279 | + break; | ||
| 280 | + case OPT_BREAKTRACE: | ||
| 281 | + case 'b': | ||
| 282 | + thistracelimit = atoi(optarg); | ||
| 283 | + break; | ||
| 284 | + case OPT_DISTANCE: | ||
| 285 | + case 'd': | ||
| 286 | + distance = atoi(optarg); | ||
| 287 | + break; | ||
| 288 | + case OPT_DURATION: | ||
| 289 | + case 'D': | ||
| 290 | + duration = parse_time_string(optarg); | ||
| 291 | + break; | ||
| 292 | + case OPT_FORK: | ||
| 293 | + case 'f': | ||
| 294 | + if (optarg != NULL) { | ||
| 295 | + wasforked = 1; | ||
| 296 | + if (optarg[0] == 's') | ||
| 297 | + wasforked_sender = 1; | ||
| 298 | + else if (optarg[0] == 'r') | ||
| 299 | + wasforked_sender = 0; | ||
| 300 | + wasforked_threadno = atoi(optarg+1); | ||
| 301 | + } else | ||
| 302 | + mustfork = 1; | ||
| 303 | + break; | ||
| 304 | + case OPT_HELP: | ||
| 305 | + case '?': | ||
| 306 | + case 'h': | ||
| 307 | + display_help(0); | ||
| 308 | + break; | ||
| 309 | + case OPT_INTERVAL: | ||
| 310 | + case 'i': | ||
| 311 | + interval = atoi(optarg); | ||
| 312 | + break; | ||
| 313 | + case OPT_JSON: | ||
| 314 | + strncpy(jsonfile, optarg, strnlen(optarg, MAX_PATH-1)); | ||
| 315 | + break; | ||
| 316 | + case OPT_LOOPS: | ||
| 317 | + case 'l': | ||
| 318 | + max_cycles = atoi(optarg); | ||
| 319 | + break; | ||
| 320 | + case OPT_PRIORITY: | ||
| 321 | + case 'p': | ||
| 322 | + priority = atoi(optarg); | ||
| 323 | + break; | ||
| 324 | + case OPT_QUIET: | ||
| 325 | + case 'q': | ||
| 326 | + quiet = 1; | ||
| 327 | + break; | ||
| 328 | + case OPT_THREADS: | ||
| 329 | + case 't': | ||
| 330 | + if (optarg != NULL) | ||
| 331 | + num_threads = atoi(optarg); | ||
| 332 | + else if (optind < argc && atoi(argv[optind])) | ||
| 333 | + num_threads = atoi(argv[optind]); | ||
| 334 | + else | ||
| 335 | + num_threads = max_cpus; | ||
| 336 | + break; | ||
| 337 | + default: | ||
| 338 | + display_help(1); | ||
| 339 | + break; | ||
| 340 | + } | ||
| 341 | + } | ||
| 342 | + | ||
| 343 | + if (!wasforked) { | ||
| 344 | + if (setaffinity == AFFINITY_SPECIFIED) { | ||
| 345 | + if (affinity < 0) | ||
| 346 | + error = 1; | ||
| 347 | + if (affinity >= max_cpus) { | ||
| 348 | + fprintf(stderr, "ERROR: CPU #%d not found, " | ||
| 349 | + "only %d CPUs available\n", | ||
| 350 | + affinity, max_cpus); | ||
| 351 | + error = 1; | ||
| 352 | + } | ||
| 353 | + } | ||
| 354 | + | ||
| 355 | + if (duration < 0) | ||
| 356 | + error = 1; | ||
| 357 | + | ||
| 358 | + if (num_threads < 1) | ||
| 359 | + error = 1; | ||
| 360 | + | ||
| 361 | + if (priority < 0 || priority > 99) | ||
| 362 | + error = 1; | ||
| 363 | + | ||
| 364 | + tracelimit = thistracelimit; | ||
| 365 | + } | ||
| 366 | + if (error) | ||
| 367 | + display_help(error); | ||
| 368 | +} | ||
| 369 | + | ||
| 370 | + | ||
| 371 | +static int volatile mustshutdown; | ||
| 372 | + | ||
| 373 | +static void sighand(int sig __attribute__ ((unused))) | ||
| 374 | +{ | ||
| 375 | + mustshutdown = 1; | ||
| 376 | +} | ||
| 377 | + | ||
| 378 | +static void print_stat(struct params *receiver, struct params *sender, | ||
| 379 | + int verbose __attribute__ ((unused)), int quiet) | ||
| 380 | +{ | ||
| 381 | + int i; | ||
| 382 | + | ||
| 383 | + if (quiet) | ||
| 384 | + return; | ||
| 385 | + | ||
| 386 | + for (i = 0; i < num_threads; i++) { | ||
| 387 | + int receiver_pid, sender_pid; | ||
| 388 | + if (mustfork) { | ||
| 389 | + receiver_pid = receiver[i].pid; | ||
| 390 | + sender_pid = sender[i].pid; | ||
| 391 | + } else { | ||
| 392 | + receiver_pid = receiver[i].tid; | ||
| 393 | + sender_pid = sender[i].tid; | ||
| 394 | + } | ||
| 395 | + printf("#%1d: ID%d, P%d, CPU%d, I%ld; #%1d: ID%d, P%d, CPU%d, Cycles %d\n", | ||
| 396 | + i*2, receiver_pid, receiver[i].priority, | ||
| 397 | + receiver[i].cpu, receiver[i].delay.tv_nsec / | ||
| 398 | + 1000, i*2+1, sender_pid, sender[i].priority, | ||
| 399 | + sender[i].cpu, sender[i].samples); | ||
| 400 | + } | ||
| 401 | + | ||
| 402 | + for (i = 0; i < num_threads; i++) { | ||
| 403 | + if (receiver[i].mindiff == -1) | ||
| 404 | + printf("#%d -> #%d (not yet ready)\n", i*2+1, i*2); | ||
| 405 | + else | ||
| 406 | + printf("#%d -> #%d, Min %4d, Cur %4d, Avg %4d, Max %4d\n", | ||
| 407 | + i*2+1, i*2, receiver[i].mindiff, | ||
| 408 | + (int) receiver[i].diff.tv_usec, | ||
| 409 | + (int) ((receiver[i].sumdiff / | ||
| 410 | + receiver[i].samples) + 0.5), | ||
| 411 | + receiver[i].maxdiff); | ||
| 412 | + } | ||
| 413 | +} | ||
| 414 | + | ||
| 415 | +struct params_stats { | ||
| 416 | + struct params *receiver; | ||
| 417 | + struct params *sender; | ||
| 418 | +}; | ||
| 419 | + | ||
| 420 | +static void write_stats(FILE *f, void *data) | ||
| 421 | +{ | ||
| 422 | + struct params_stats *ps = data; | ||
| 423 | + struct params *s, *r; | ||
| 424 | + int i; | ||
| 425 | + | ||
| 426 | + fprintf(f, " \"num_threads\": %d,\n", num_threads); | ||
| 427 | + fprintf(f, " \"thread\": {\n"); | ||
| 428 | + for (i = 0; i < num_threads; i++) { | ||
| 429 | + s = &ps->sender[i]; | ||
| 430 | + r = &ps->receiver[i]; | ||
| 431 | + fprintf(f, " \"%u\": {\n", i); | ||
| 432 | + fprintf(f, " \"sender\": {\n"); | ||
| 433 | + fprintf(f, " \"cpu\": %d,\n", s->cpu); | ||
| 434 | + fprintf(f, " \"priority\": %d,\n", s->priority); | ||
| 435 | + fprintf(f, " \"samples\": %d,\n", s->samples); | ||
| 436 | + fprintf(f, " \"interval\": %ld\n", r->delay.tv_nsec/1000); | ||
| 437 | + fprintf(f, " },\n"); | ||
| 438 | + fprintf(f, " \"receiver\": {\n"); | ||
| 439 | + fprintf(f, " \"cpu\": %d,\n", r->cpu); | ||
| 440 | + fprintf(f, " \"priority\": %d,\n", r->priority); | ||
| 441 | + fprintf(f, " \"min\": %d,\n", r->mindiff); | ||
| 442 | + fprintf(f, " \"avg\": %.2f,\n", r->sumdiff/r->samples); | ||
| 443 | + fprintf(f, " \"max\": %d\n", r->maxdiff); | ||
| 444 | + fprintf(f, " }\n"); | ||
| 445 | + fprintf(f, " }%s\n", i == num_threads - 1 ? "" : ","); | ||
| 446 | + } | ||
| 447 | + fprintf(f, " }\n"); | ||
| 448 | +} | ||
| 449 | + | ||
| 450 | +int main(int argc, char *argv[]) | ||
| 451 | +{ | ||
| 452 | + int i, totalsize = 0; | ||
| 453 | + int max_cpus = sysconf(_SC_NPROCESSORS_CONF); | ||
| 454 | + int oldsamples = 1; | ||
| 455 | + struct params *receiver = NULL; | ||
| 456 | + struct params *sender = NULL; | ||
| 457 | + sigset_t sigset; | ||
| 458 | + void *param = NULL; | ||
| 459 | + char f_opt[14]; | ||
| 460 | + struct timespec launchdelay, maindelay; | ||
| 461 | + | ||
| 462 | + rt_init(argc, argv); | ||
| 463 | + process_options(argc, argv); | ||
| 464 | + | ||
| 465 | + if (check_privs()) | ||
| 466 | + return 1; | ||
| 467 | + | ||
| 468 | + if (mlockall(MCL_CURRENT|MCL_FUTURE) == -1) { | ||
| 469 | + perror("mlockall"); | ||
| 470 | + return 1; | ||
| 471 | + } | ||
| 472 | + | ||
| 473 | + get_cpu_setup(); /* init get_cpu() */ | ||
| 474 | + | ||
| 475 | + if (mustfork) { | ||
| 476 | + int shmem; | ||
| 477 | + | ||
| 478 | + /* | ||
| 479 | + * In fork mode (-f), the shared memory contains two | ||
| 480 | + * subsequent arrays, receiver[num_threads] and | ||
| 481 | + * sender[num_threads]. | ||
| 482 | + */ | ||
| 483 | + totalsize = num_threads * sizeof(struct params) * 2; | ||
| 484 | + | ||
| 485 | + shm_unlink("/sigwaittest"); | ||
| 486 | + shmem = shm_open("/sigwaittest", O_CREAT|O_EXCL|O_RDWR, | ||
| 487 | + S_IRUSR|S_IWUSR); | ||
| 488 | + if (shmem < 0) { | ||
| 489 | + fprintf(stderr, "Could not create shared memory\n"); | ||
| 490 | + return 1; | ||
| 491 | + } | ||
| 492 | + ftruncate(shmem, totalsize); | ||
| 493 | + param = mmap(0, totalsize, PROT_READ|PROT_WRITE, MAP_SHARED, | ||
| 494 | + shmem, 0); | ||
| 495 | + if (param == MAP_FAILED) { | ||
| 496 | + fprintf(stderr, "Could not map shared memory\n"); | ||
| 497 | + close(shmem); | ||
| 498 | + return 1; | ||
| 499 | + } | ||
| 500 | + | ||
| 501 | + receiver = (struct params *) param; | ||
| 502 | + sender = receiver + num_threads; | ||
| 503 | + } else if (wasforked) { | ||
| 504 | + struct stat buf; | ||
| 505 | + int shmem, totalsize, expect_totalsize; | ||
| 506 | + | ||
| 507 | + if (wasforked_threadno == -1 || wasforked_sender == -1) { | ||
| 508 | + fprintf(stderr, "Invalid fork option\n"); | ||
| 509 | + return 1; | ||
| 510 | + } | ||
| 511 | + shmem = shm_open("/sigwaittest", O_RDWR, S_IRUSR|S_IWUSR); | ||
| 512 | + if (fstat(shmem, &buf)) { | ||
| 513 | + fprintf(stderr, | ||
| 514 | + "Could not determine shared memory size\n"); | ||
| 515 | + close(shmem); | ||
| 516 | + return 1; | ||
| 517 | + } | ||
| 518 | + totalsize = buf.st_size; | ||
| 519 | + param = mmap(0, totalsize, PROT_READ|PROT_WRITE, MAP_SHARED, | ||
| 520 | + shmem, 0); | ||
| 521 | + close(shmem); | ||
| 522 | + if (param == MAP_FAILED) { | ||
| 523 | + fprintf(stderr, "Could not map shared memory\n"); | ||
| 524 | + return 1; | ||
| 525 | + } | ||
| 526 | + | ||
| 527 | + receiver = (struct params *) param; | ||
| 528 | + expect_totalsize = receiver->num_threads * | ||
| 529 | + sizeof(struct params) * 2; | ||
| 530 | + if (totalsize != expect_totalsize) { | ||
| 531 | + fprintf(stderr, "Memory size problem (expected %d, " | ||
| 532 | + "found %d\n", expect_totalsize, totalsize); | ||
| 533 | + munmap(param, totalsize); | ||
| 534 | + return 1; | ||
| 535 | + } | ||
| 536 | + sender = receiver + receiver->num_threads; | ||
| 537 | + if (wasforked_sender) | ||
| 538 | + semathread(sender + wasforked_threadno); | ||
| 539 | + else | ||
| 540 | + semathread(receiver + wasforked_threadno); | ||
| 541 | + munmap(param, totalsize); | ||
| 542 | + return 0; | ||
| 543 | + } | ||
| 544 | + | ||
| 545 | + signal(SIGINT, sighand); | ||
| 546 | + signal(SIGTERM, sighand); | ||
| 547 | + signal(SIGALRM, sighand); | ||
| 548 | + sigemptyset(&sigset); | ||
| 549 | + pthread_sigmask(SIG_SETMASK, &sigset, NULL); | ||
| 550 | + | ||
| 551 | + if (duration) | ||
| 552 | + alarm(duration); | ||
| 553 | + | ||
| 554 | + if (!mustfork && !wasforked) { | ||
| 555 | + receiver = calloc(num_threads, sizeof(struct params)); | ||
| 556 | + sender = calloc(num_threads, sizeof(struct params)); | ||
| 557 | + if (receiver == NULL || sender == NULL) | ||
| 558 | + goto nomem; | ||
| 559 | + } | ||
| 560 | + | ||
| 561 | + launchdelay.tv_sec = 0; | ||
| 562 | + launchdelay.tv_nsec = 10000000; /* 10 ms */ | ||
| 563 | + | ||
| 564 | + maindelay.tv_sec = 0; | ||
| 565 | + maindelay.tv_nsec = 50000000; /* 50 ms */ | ||
| 566 | + | ||
| 567 | + for (i = 0; i < num_threads; i++) { | ||
| 568 | + receiver[i].mindiff = UINT_MAX; | ||
| 569 | + receiver[i].maxdiff = 0; | ||
| 570 | + receiver[i].sumdiff = 0.0; | ||
| 571 | + | ||
| 572 | + receiver[i].num = i; | ||
| 573 | + receiver[i].cpu = i; | ||
| 574 | + receiver[i].priority = priority; | ||
| 575 | + receiver[i].tracelimit = tracelimit; | ||
| 576 | + if (priority > 0) | ||
| 577 | + priority--; | ||
| 578 | + switch (setaffinity) { | ||
| 579 | + case AFFINITY_UNSPECIFIED: receiver[i].cpu = -1; break; | ||
| 580 | + case AFFINITY_SPECIFIED: receiver[i].cpu = affinity; break; | ||
| 581 | + case AFFINITY_USEALL: receiver[i].cpu = i % max_cpus; break; | ||
| 582 | + } | ||
| 583 | + receiver[i].delay.tv_sec = interval / USEC_PER_SEC; | ||
| 584 | + receiver[i].delay.tv_nsec = (interval % USEC_PER_SEC) * 1000; | ||
| 585 | + interval += distance; | ||
| 586 | + receiver[i].max_cycles = max_cycles; | ||
| 587 | + receiver[i].sender = 0; | ||
| 588 | + receiver[i].neighbor = &sender[i]; | ||
| 589 | + if (mustfork) { | ||
| 590 | + pid_t pid = fork(); | ||
| 591 | + if (pid == -1) { | ||
| 592 | + fprintf(stderr, "Could not fork\n"); | ||
| 593 | + return 1; | ||
| 594 | + } else if (pid == 0) { | ||
| 595 | + char *args[3]; | ||
| 596 | + | ||
| 597 | + receiver[i].num_threads = num_threads; | ||
| 598 | + receiver[i].pid = getpid(); | ||
| 599 | + sprintf(f_opt, "-fr%d", i); | ||
| 600 | + args[0] = argv[0]; | ||
| 601 | + args[1] = f_opt; | ||
| 602 | + args[2] = NULL; | ||
| 603 | + execvp(args[0], args); | ||
| 604 | + fprintf(stderr, | ||
| 605 | + "Could not execute receiver child process " | ||
| 606 | + "#%d\n", i); | ||
| 607 | + } | ||
| 608 | + } else | ||
| 609 | + pthread_create(&receiver[i].threadid, NULL, | ||
| 610 | + semathread, &receiver[i]); | ||
| 611 | + | ||
| 612 | + nanosleep(&launchdelay, NULL); | ||
| 613 | + | ||
| 614 | + memcpy(&sender[i], &receiver[i], sizeof(receiver[0])); | ||
| 615 | + sender[i].sender = 1; | ||
| 616 | + sender[i].neighbor = &receiver[i]; | ||
| 617 | + if (mustfork) { | ||
| 618 | + pid_t pid = fork(); | ||
| 619 | + if (pid == -1) { | ||
| 620 | + fprintf(stderr, "Could not fork\n"); | ||
| 621 | + return 1; | ||
| 622 | + } else if (pid == 0) { | ||
| 623 | + char *args[3]; | ||
| 624 | + | ||
| 625 | + sender[i].num_threads = num_threads; | ||
| 626 | + sender[i].pid = getpid(); | ||
| 627 | + sprintf(f_opt, "-fs%d", i); | ||
| 628 | + args[0] = argv[0]; | ||
| 629 | + args[1] = f_opt; | ||
| 630 | + args[2] = NULL; | ||
| 631 | + execvp(args[0], args); | ||
| 632 | + fprintf(stderr, | ||
| 633 | + "Could not execute sender child process " | ||
| 634 | + "#%d\n", i); | ||
| 635 | + } | ||
| 636 | + } else | ||
| 637 | + pthread_create(&sender[i].threadid, NULL, semathread, | ||
| 638 | + &sender[i]); | ||
| 639 | + } | ||
| 640 | + | ||
| 641 | + while (!mustshutdown) { | ||
| 642 | + for (i = 0; i < num_threads; i++) | ||
| 643 | + mustshutdown |= receiver[i].shutdown | | ||
| 644 | + sender[i].shutdown; | ||
| 645 | + | ||
| 646 | + if (receiver[0].samples > oldsamples || mustshutdown) { | ||
| 647 | + print_stat(receiver, sender, 0, quiet); | ||
| 648 | + if (!quiet) | ||
| 649 | + printf("\033[%dA", num_threads*2); | ||
| 650 | + } | ||
| 651 | + | ||
| 652 | + sigemptyset(&sigset); | ||
| 653 | + sigaddset(&sigset, SIGTERM); | ||
| 654 | + sigaddset(&sigset, SIGINT); | ||
| 655 | + sigaddset(&sigset, SIGALRM); | ||
| 656 | + pthread_sigmask(SIG_SETMASK, &sigset, NULL); | ||
| 657 | + | ||
| 658 | + nanosleep(&maindelay, NULL); | ||
| 659 | + | ||
| 660 | + sigemptyset(&sigset); | ||
| 661 | + pthread_sigmask(SIG_SETMASK, &sigset, NULL); | ||
| 662 | + } | ||
| 663 | + | ||
| 664 | + if (!quiet) | ||
| 665 | + printf("\033[%dB", num_threads*2 + 2); | ||
| 666 | + else | ||
| 667 | + print_stat(receiver, sender, 0, 0); | ||
| 668 | + | ||
| 669 | + for (i = 0; i < num_threads; i++) { | ||
| 670 | + receiver[i].shutdown = 1; | ||
| 671 | + sender[i].shutdown = 1; | ||
| 672 | + } | ||
| 673 | + nanosleep(&receiver[0].delay, NULL); | ||
| 674 | + | ||
| 675 | + for (i = 0; i < num_threads; i++) { | ||
| 676 | + if (!receiver[i].stopped) { | ||
| 677 | + if (mustfork) | ||
| 678 | + kill(receiver[i].pid, SIGTERM); | ||
| 679 | + else | ||
| 680 | + pthread_kill(receiver[i].threadid, SIGTERM); | ||
| 681 | + } | ||
| 682 | + if (!sender[i].stopped) { | ||
| 683 | + if (mustfork) | ||
| 684 | + kill(sender[i].pid, SIGTERM); | ||
| 685 | + else | ||
| 686 | + pthread_kill(sender[i].threadid, SIGTERM); | ||
| 687 | + } | ||
| 688 | + } | ||
| 689 | + | ||
| 690 | + if (strlen(jsonfile) != 0) { | ||
| 691 | + struct params_stats ps = { | ||
| 692 | + .receiver = receiver, | ||
| 693 | + .sender = sender, | ||
| 694 | + }; | ||
| 695 | + rt_write_json(jsonfile, 0, write_stats, &ps); | ||
| 696 | + } | ||
| 697 | + | ||
| 698 | +nomem: | ||
| 699 | + if (mustfork) { | ||
| 700 | + munmap(param, totalsize); | ||
| 701 | + shm_unlink("/sigwaittest"); | ||
| 702 | + } | ||
| 703 | + | ||
| 704 | + return 0; | ||
| 705 | +} | ||
| @@ -0,0 +1,38 @@ | |||
| 1 | +.TH SSDD 8 "September 19, 2020" | ||
| 2 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | +.SH NAME | ||
| 4 | +ssdd \- have a tracer do a bunch of PTRACE_SINGLESTEPs | ||
| 5 | +.SH SYNOPSIS | ||
| 6 | +.LP | ||
| 7 | +ssdd [-f|--forks NUM] [-h|--help] [-i|--iters NUM] [--json FILENAME] | ||
| 8 | +.SH DESCRIPTION | ||
| 9 | +Have a tracer do a bunch of PTRACE_SINGLESTEPs against a tracee as | ||
| 10 | +fast as possible. Create several of these tracer/tracee pairs and | ||
| 11 | +see if they can be made to interfere with each other. The tracer | ||
| 12 | +waits on each PTRACE_SINGLESTEP with a waitpid(2) and checks that | ||
| 13 | +waitpid's return values for correctness. | ||
| 14 | +.SH OPTIONS | ||
| 15 | +.TP | ||
| 16 | +.B \-f, \-\-forks=NUM | ||
| 17 | +number of tracer/tracee pairs to fork off. | ||
| 18 | +.br | ||
| 19 | +Default is 10. | ||
| 20 | +.TP | ||
| 21 | +.B \-h, \-\-help | ||
| 22 | +Display usage | ||
| 23 | +.TP | ||
| 24 | +.B \-i, \-\-iters=NUM | ||
| 25 | +number of PTRACE_SINGLESTEP iterations to do before declaring | ||
| 26 | +success, for each tracer tracee pair set up. | ||
| 27 | +.br | ||
| 28 | +Default is 10,000. | ||
| 29 | +.br | ||
| 30 | +NUM must be at least 1 | ||
| 31 | +.TP | ||
| 32 | +.B \-\-json=FILENAME | ||
| 33 | +Write final results into FILENAME, JSON formatted. | ||
| 34 | +.SH AUTHOR | ||
| 35 | +ssdd was written by Joe Korty <joe.korty@concurrent-rt.com> | ||
| 36 | +.PP | ||
| 37 | +This manual page was written by John Kacur <jkacur@redhat.com> | ||
| 38 | + | ||
| @@ -0,0 +1,381 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | + | ||
| 3 | +/* | ||
| 4 | + * Copyright (C) 2019 Joe Korty <joe.korty@concurrent-rt.com> | ||
| 5 | + */ | ||
| 6 | + | ||
| 7 | +/* | ||
| 8 | + * Have a tracer do a bunch of PTRACE_SINGLESTEPs against | ||
| 9 | + * a tracee as fast as possible. Create several of these | ||
| 10 | + * tracer/tracee pairs and see if they can be made to | ||
| 11 | + * interfere with each other. | ||
| 12 | + * | ||
| 13 | + * Usage: | ||
| 14 | + * ssdd nforks niters | ||
| 15 | + * Where: | ||
| 16 | + * nforks - number of tracer/tracee pairs to fork off. | ||
| 17 | + * default 10. | ||
| 18 | + * niters - number of PTRACE_SINGLESTEP iterations to | ||
| 19 | + * do before declaring success, for each tracer/ | ||
| 20 | + * tracee pair set up. Default 10,000. | ||
| 21 | + * | ||
| 22 | + * The tracer waits on each PTRACE_SINGLESTEP with a waitpid(2) | ||
| 23 | + * and checks that waitpid's return values for correctness. | ||
| 24 | + * | ||
| 25 | + */ | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + | ||
| 35 | + | ||
| 36 | + | ||
| 37 | + | ||
| 38 | + | ||
| 39 | + | ||
| 40 | + | ||
| 41 | +/* do_wait return values */ | ||
| 42 | + | ||
| 43 | + | ||
| 44 | + | ||
| 45 | + | ||
| 46 | + | ||
| 47 | + | ||
| 48 | + | ||
| 49 | + | ||
| 50 | +static char *state_name[] = { | ||
| 51 | + [STATE_EXITED] = "STATE_EXITED", | ||
| 52 | + [STATE_STOPPED] = "STATE_STOPPED", | ||
| 53 | + [STATE_SIGNALED] = "STATE_SIGNALED", | ||
| 54 | + [STATE_UNKNOWN] = "STATE_UNKNOWN", | ||
| 55 | + [STATE_ECHILD] = "STATE_ECHILD", | ||
| 56 | + [STATE_EXITED_TSIG] = "STATE_EXITED_TSIG", | ||
| 57 | + [STATE_EXITED_ERRSTAT] = "STATE_EXITED_ERRSTAT" | ||
| 58 | +}; | ||
| 59 | + | ||
| 60 | +static const char *get_state_name(int state) | ||
| 61 | +{ | ||
| 62 | + if (state < STATE_EXITED || state > STATE_EXITED_ERRSTAT) | ||
| 63 | + return "?"; | ||
| 64 | + return state_name[state]; | ||
| 65 | +} | ||
| 66 | + | ||
| 67 | +static int quiet; | ||
| 68 | +static char jsonfile[MAX_PATH]; | ||
| 69 | + | ||
| 70 | +static int got_sigchld; | ||
| 71 | + | ||
| 72 | +enum option_value { OPT_NFORKS=1, OPT_NITERS, OPT_HELP, OPT_JSON, OPT_QUIET }; | ||
| 73 | + | ||
| 74 | +static void usage(int error) | ||
| 75 | +{ | ||
| 76 | + printf("ssdd V %1.2f\n", VERSION); | ||
| 77 | + printf("Usage:\n" | ||
| 78 | + "ssdd <options>\n\n" | ||
| 79 | + "-f --forks=NUM number of forks\n" | ||
| 80 | + "-h --help print this message\n" | ||
| 81 | + " --json=FILENAME write final results into FILENAME, JSON formatted\n" | ||
| 82 | + "-q --quiet suppress running output\n" | ||
| 83 | + "-i --iters=NUM number of iterations, NUM must be at least 1\n" | ||
| 84 | + ); | ||
| 85 | + exit(error); | ||
| 86 | +} | ||
| 87 | + | ||
| 88 | +static int do_wait(pid_t *wait_pid, int *ret_sig) | ||
| 89 | +{ | ||
| 90 | + int status, child_status; | ||
| 91 | + | ||
| 92 | + *ret_sig = -1; /* initially mark 'nothing returned' */ | ||
| 93 | + | ||
| 94 | + while (1) { | ||
| 95 | + status = waitpid(-1, &child_status, WUNTRACED | __WALL); | ||
| 96 | + if (status == -1) { | ||
| 97 | + if (errno == EINTR) | ||
| 98 | + continue; | ||
| 99 | + if (errno == ECHILD) { | ||
| 100 | + *wait_pid = (pid_t)0; | ||
| 101 | + return STATE_ECHILD; | ||
| 102 | + } | ||
| 103 | + printf("do_wait/%d: EXITING, ERROR: " | ||
| 104 | + "waitpid() returned errno %d\n", | ||
| 105 | + getpid(), errno); | ||
| 106 | + exit(1); | ||
| 107 | + } | ||
| 108 | + break; | ||
| 109 | + } | ||
| 110 | + *wait_pid = (pid_t)status; | ||
| 111 | + | ||
| 112 | + if (WIFEXITED(child_status)) { | ||
| 113 | + if (WIFSIGNALED(child_status)) | ||
| 114 | + return STATE_EXITED_TSIG; | ||
| 115 | + if (WEXITSTATUS(child_status)) | ||
| 116 | + return STATE_EXITED_ERRSTAT; | ||
| 117 | + return STATE_EXITED; | ||
| 118 | + } | ||
| 119 | + if (WIFSTOPPED(child_status)) { | ||
| 120 | + *ret_sig = WSTOPSIG(child_status); | ||
| 121 | + return STATE_STOPPED; | ||
| 122 | + } | ||
| 123 | + if (WIFSIGNALED(child_status)) { | ||
| 124 | + *ret_sig = WTERMSIG(child_status); | ||
| 125 | + return STATE_SIGNALED; | ||
| 126 | + } | ||
| 127 | + return STATE_UNKNOWN; | ||
| 128 | +} | ||
| 129 | + | ||
| 130 | +static int check_sigchld(void) | ||
| 131 | +{ | ||
| 132 | + int i; | ||
| 133 | + /* | ||
| 134 | + * The signal is asynchronous so give it some | ||
| 135 | + * time to arrive. | ||
| 136 | + */ | ||
| 137 | + for (i = 0; i < 10 && !got_sigchld; i++) | ||
| 138 | + usleep(1000); /* 10 msecs */ | ||
| 139 | + for (i = 0; i < 10 && !got_sigchld; i++) | ||
| 140 | + usleep(2000); /* 20 + 10 = 30 msecs */ | ||
| 141 | + for (i = 0; i < 10 && !got_sigchld; i++) | ||
| 142 | + usleep(4000); /* 40 + 30 = 70 msecs */ | ||
| 143 | + for (i = 0; i < 10 && !got_sigchld; i++) | ||
| 144 | + usleep(8000); /* 80 + 70 = 150 msecs */ | ||
| 145 | + for (i = 0; i < 10 && !got_sigchld; i++) | ||
| 146 | + usleep(16000); /* 160 + 150 = 310 msecs */ | ||
| 147 | + | ||
| 148 | + return got_sigchld; | ||
| 149 | +} | ||
| 150 | + | ||
| 151 | +static pid_t parent; | ||
| 152 | +static int nforks = 10; | ||
| 153 | +static int nsteps = 10000; | ||
| 154 | + | ||
| 155 | +static void sigchld(int sig __attribute__ ((unused)), siginfo_t *info __attribute__ ((unused)), void *arg __attribute__ ((unused))) | ||
| 156 | +{ | ||
| 157 | + got_sigchld = 1; | ||
| 158 | +} | ||
| 159 | + | ||
| 160 | +static void child_process(void) | ||
| 161 | +{ | ||
| 162 | + while (1) | ||
| 163 | + ; | ||
| 164 | +} | ||
| 165 | + | ||
| 166 | +static int forktests(int testid) | ||
| 167 | +{ | ||
| 168 | + int i, status, ret_sig; | ||
| 169 | + long pstatus; | ||
| 170 | + pid_t child, wait_pid; | ||
| 171 | + struct sigaction act, oact; | ||
| 172 | + | ||
| 173 | + parent = getpid(); | ||
| 174 | + | ||
| 175 | + child = fork(); | ||
| 176 | + if (child == -1) { | ||
| 177 | + printf("forktest#%d/%d: EXITING, ERROR: " | ||
| 178 | + "fork returned errno %d\n", testid, parent, errno); | ||
| 179 | + exit(1); | ||
| 180 | + } | ||
| 181 | + if (!child) | ||
| 182 | + child_process(); | ||
| 183 | + | ||
| 184 | + if (!quiet) | ||
| 185 | + printf("forktest#%d/%d/%d: STARTING\n", testid, parent, child); | ||
| 186 | + | ||
| 187 | + act.sa_sigaction = sigchld; | ||
| 188 | + sigemptyset(&act.sa_mask); | ||
| 189 | + act.sa_flags = SA_SIGINFO; | ||
| 190 | + status = sigaction(SIGCHLD, &act, &oact); | ||
| 191 | + if (status) { | ||
| 192 | + printf("forktest#%d/%d: EXITING, ERROR: " | ||
| 193 | + "sigaction returned %d, errno %d\n", | ||
| 194 | + testid, parent, status, errno); | ||
| 195 | + exit(1); | ||
| 196 | + } | ||
| 197 | + | ||
| 198 | + /* | ||
| 199 | + * Attach to the child. | ||
| 200 | + */ | ||
| 201 | + pstatus = ptrace(PTRACE_ATTACH, child, NULL, NULL); | ||
| 202 | + if (pstatus == ~0l) { | ||
| 203 | + printf("forktest#%d/%d: EXITING, ERROR: " | ||
| 204 | + "attach failed. errno %d\n", | ||
| 205 | + testid, getpid(), errno); | ||
| 206 | + exit(1); | ||
| 207 | + } | ||
| 208 | + | ||
| 209 | + /* | ||
| 210 | + * The attach should cause the child to receive a signal. | ||
| 211 | + */ | ||
| 212 | + status = do_wait(&wait_pid, &ret_sig); | ||
| 213 | + if (wait_pid != child) { | ||
| 214 | + printf("forktest#%d/%d: EXITING, ERROR: " | ||
| 215 | + "attach: Unexpected wait pid %d\n", | ||
| 216 | + testid, getpid(), wait_pid); | ||
| 217 | + exit(1); | ||
| 218 | + } | ||
| 219 | + if (status != STATE_STOPPED) { | ||
| 220 | + printf("forktest#%d/%d: EXITING, ERROR: " | ||
| 221 | + "attach: wait on PTRACE_ATTACH returned %d " | ||
| 222 | + "[%s, wanted STATE_STOPPED], signo %d\n", | ||
| 223 | + testid, getpid(), status, get_state_name(status), | ||
| 224 | + ret_sig); | ||
| 225 | + exit(1); | ||
| 226 | + } | ||
| 227 | + if (!check_sigchld()) { | ||
| 228 | + printf("forktest#%d/%d: EXITING, ERROR: " | ||
| 229 | + "wait on PTRACE_ATTACH saw a SIGCHLD count of %d, should be 1\n", | ||
| 230 | + testid, getpid(), got_sigchld); | ||
| 231 | + exit(1); | ||
| 232 | + } | ||
| 233 | + got_sigchld = 0; | ||
| 234 | + | ||
| 235 | + | ||
| 236 | + /* | ||
| 237 | + * Generate 'nsteps' PTRACE_SINGLESTEPs, make sure they all actually | ||
| 238 | + * step the tracee. | ||
| 239 | + */ | ||
| 240 | + for (i = 0; i < nsteps; i++) { | ||
| 241 | + pstatus = ptrace(PTRACE_SINGLESTEP, child, NULL, NULL); | ||
| 242 | + | ||
| 243 | + if (pstatus) { | ||
| 244 | + printf("forktest#%d/%d: EXITING, ERROR: " | ||
| 245 | + "PTRACE_SINGLESTEP #%d: returned status %ld, " | ||
| 246 | + "errno %d, signo %d\n", | ||
| 247 | + testid, getpid(), i, pstatus, errno, ret_sig); | ||
| 248 | + exit(1); | ||
| 249 | + } | ||
| 250 | + | ||
| 251 | + status = do_wait(&wait_pid, &ret_sig); | ||
| 252 | + if (wait_pid != child) { | ||
| 253 | + printf("forktest#%d/%d: EXITING, ERROR: " | ||
| 254 | + "wait on PTRACE_SINGLESTEP #%d: returned wrong pid %d, " | ||
| 255 | + "expected %d\n", | ||
| 256 | + testid, getpid(), i, wait_pid, child); | ||
| 257 | + exit(1); | ||
| 258 | + } | ||
| 259 | + if (status != STATE_STOPPED) { | ||
| 260 | + printf("forktest#%d/%d: EXITING, ERROR: " | ||
| 261 | + "wait on PTRACE_SINGLESTEP #%d: wanted STATE_STOPPED, " | ||
| 262 | + "saw %s instead (and saw signo %d too)\n", | ||
| 263 | + testid, getpid(), i, | ||
| 264 | + get_state_name(status), ret_sig); | ||
| 265 | + exit(1); | ||
| 266 | + } | ||
| 267 | + if (ret_sig != SIGTRAP) { | ||
| 268 | + printf("forktest#%d/%d: EXITING, ERROR: " | ||
| 269 | + "wait on PTRACE_SINGLESTEP #%d: returned signal %d, " | ||
| 270 | + "wanted SIGTRAP\n", | ||
| 271 | + testid, getpid(), i, ret_sig); | ||
| 272 | + exit(1); | ||
| 273 | + } | ||
| 274 | + if (!check_sigchld()) { | ||
| 275 | + printf("forktest#%d/%d: EXITING, ERROR: " | ||
| 276 | + "wait on PTRACE_SINGLESTEP #%d: no SIGCHLD seen " | ||
| 277 | + "(signal count == 0), signo %d\n", | ||
| 278 | + testid, getpid(), i, ret_sig); | ||
| 279 | + exit(1); | ||
| 280 | + } | ||
| 281 | + got_sigchld = 0; | ||
| 282 | + } | ||
| 283 | + | ||
| 284 | + /* There is no need for the tracer to kill the tracee. It will | ||
| 285 | + * automatically exit when its owner, ie, us, exits. | ||
| 286 | + */ | ||
| 287 | + if (!quiet) | ||
| 288 | + printf("forktest#%d/%d: EXITING, no error\n", testid, parent); | ||
| 289 | + exit(0); | ||
| 290 | +} | ||
| 291 | + | ||
| 292 | +int main(int argc, char **argv) | ||
| 293 | +{ | ||
| 294 | + int i, ret_sig, status; | ||
| 295 | + pid_t child = 0, wait_pid; | ||
| 296 | + int error = 0; | ||
| 297 | + | ||
| 298 | + setbuf(stdout, NULL); | ||
| 299 | + | ||
| 300 | + rt_init(argc, argv); | ||
| 301 | + for (;;) { | ||
| 302 | + int option_index = 0; | ||
| 303 | + | ||
| 304 | + static struct option long_options[] = { | ||
| 305 | + {"forks", required_argument, NULL, OPT_NFORKS}, | ||
| 306 | + {"help", no_argument, NULL, OPT_HELP}, | ||
| 307 | + {"json", required_argument, NULL, OPT_JSON}, | ||
| 308 | + {"quiet", no_argument, NULL, OPT_QUIET}, | ||
| 309 | + {"iters", required_argument, NULL, OPT_NITERS}, | ||
| 310 | + {NULL, 0, NULL, 0}, | ||
| 311 | + }; | ||
| 312 | + int c = getopt_long(argc, argv, "f:hqi:", long_options, &option_index); | ||
| 313 | + if (c == -1) | ||
| 314 | + break; | ||
| 315 | + switch(c) { | ||
| 316 | + case 'f': | ||
| 317 | + case OPT_NFORKS: | ||
| 318 | + nforks = atoi(optarg); | ||
| 319 | + break; | ||
| 320 | + case 'h': | ||
| 321 | + case OPT_HELP: | ||
| 322 | + usage(0); | ||
| 323 | + break; | ||
| 324 | + case OPT_JSON: | ||
| 325 | + strncpy(jsonfile, optarg, strnlen(optarg, MAX_PATH-1)); | ||
| 326 | + break; | ||
| 327 | + case OPT_QUIET: | ||
| 328 | + case 'q': | ||
| 329 | + quiet = 1; | ||
| 330 | + break; | ||
| 331 | + case 'i': | ||
| 332 | + case OPT_NITERS: | ||
| 333 | + nsteps = atoi(optarg); | ||
| 334 | + if (nsteps < 1) { | ||
| 335 | + usage(0); | ||
| 336 | + } | ||
| 337 | + break; | ||
| 338 | + default: | ||
| 339 | + usage(1); | ||
| 340 | + break; | ||
| 341 | + } | ||
| 342 | + } | ||
| 343 | + | ||
| 344 | + if (!quiet) { | ||
| 345 | + printf("#main : %d\n", getpid()); | ||
| 346 | + printf("#forks: %d\n", nforks); | ||
| 347 | + printf("#steps: %d\n", nsteps); | ||
| 348 | + printf("\n"); | ||
| 349 | + } | ||
| 350 | + | ||
| 351 | + for (i = 0; i < nforks; i++) { | ||
| 352 | + child = fork(); | ||
| 353 | + if (child == -1) { | ||
| 354 | + printf("main: fork returned errno %d\n", errno); | ||
| 355 | + exit(1); | ||
| 356 | + } | ||
| 357 | + if (!child) | ||
| 358 | + forktests(i); | ||
| 359 | + } | ||
| 360 | + | ||
| 361 | + for (i = 0; i < nforks; i++) { | ||
| 362 | + status = do_wait(&wait_pid, &ret_sig); | ||
| 363 | + if (status != STATE_EXITED) { | ||
| 364 | + if (0) printf("main/%d: ERROR: " | ||
| 365 | + "forktest#%d unexpected do_wait status %d " | ||
| 366 | + "[%s, wanted STATE_EXITED]\n", | ||
| 367 | + getpid(), wait_pid, status, | ||
| 368 | + get_state_name(status)); | ||
| 369 | + error = 1; | ||
| 370 | + } | ||
| 371 | + } | ||
| 372 | + | ||
| 373 | + printf("%s.\n", error ? | ||
| 374 | + "One or more tests FAILED" : | ||
| 375 | + "All tests PASSED"); | ||
| 376 | + | ||
| 377 | + if (strlen(jsonfile) != 0) | ||
| 378 | + rt_write_json(jsonfile, error, NULL, NULL); | ||
| 379 | + | ||
| 380 | + exit(error); | ||
| 381 | +} | ||
| @@ -0,0 +1,72 @@ | |||
| 1 | +.TH "svsematest" "8" "0.1" "" "" | ||
| 2 | +# SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | +.SH "NAME" | ||
| 4 | +.LP | ||
| 5 | +\fBsvsematest\fR \- Start two threads or fork two processes and measure the latency of SYSV semaphores | ||
| 6 | +.SH "SYNTAX" | ||
| 7 | +.LP | ||
| 8 | +svsematest [-a|--affinity NUM] [-b|--breaktrace USEC] [-d|--distance DIST] [-D|--duration TIME] [-f|--fork [OPT]] [-i|--interval INTV] [--json FILENAME] [-l|--loops LOOPS] [-p|--prio PRIO] [-q|--quiet] [-S|--smp] [-t|--threads [NUM]] | ||
| 9 | +.br | ||
| 10 | +.SH "DESCRIPTION" | ||
| 11 | +.LP | ||
| 12 | +The program \fBsvsematest\fR starts two threads or, optionally, forks two processes that are synchronized via SYSV semaphores and measures the latency between releasing a semaphore on one side and getting it on the other side. | ||
| 13 | +.SH "OPTIONS" | ||
| 14 | +.TP | ||
| 15 | +.B \-a, \-\-affinity[=NUM] | ||
| 16 | +Run on processor number NUM. If PROC is not specified, run on current processor. | ||
| 17 | +.TP | ||
| 18 | +.B \-b, \-\-breaktrace=USEC | ||
| 19 | +Send break trace command when latency > USEC. This is a debugging option to control the latency tracer in the realtime preemption patch. | ||
| 20 | +It is useful to track down unexpected large latencies of a system. | ||
| 21 | +.TP | ||
| 22 | +.B \-d, \-\-distance=DIST | ||
| 23 | +Set the distance of thread intervals in microseconds (default is 500 us). When cyclictest is called with the -t option and more than one thread is created, then this distance value is added to the interval of the threads: Interval(thread N) = Interval(thread N-1) + DIST | ||
| 24 | +.TP | ||
| 25 | +.B \-D, \-\-duration=TIME | ||
| 26 | +Specify a length for the test run. | ||
| 27 | +.br | ||
| 28 | +Append 'm', 'h', or 'd' to specify minutes, hours or days. | ||
| 29 | +.TP | ||
| 30 | +.B \-f, \-\-fork | ||
| 31 | +Instead of creating threads (which is the default), fork new processes | ||
| 32 | +.TP | ||
| 33 | +.B \-i, \-\-interval=INTV | ||
| 34 | +Set the base interval of the thread(s) in microseconds (default is 1000 us). This sets the interval of the first thread. See also -d. | ||
| 35 | +.TP | ||
| 36 | +.B \-\-json=FILENAME | ||
| 37 | +Write final results into FILENAME, JSON formatted. | ||
| 38 | +.TP | ||
| 39 | +.B \-l, \-\-loops=LOOPS | ||
| 40 | +Set the number of loops. The default is 0 (endless). This option is useful for automated tests with a given number of test cycles. svsematest is stopped once the number of timer intervals has been reached. | ||
| 41 | +.TP | ||
| 42 | +.B \-p, \-\-prio=PRIO | ||
| 43 | +Set the priority of the process. | ||
| 44 | +.TP | ||
| 45 | +.B \-q, \-\-quiet | ||
| 46 | +Print a summary only on exit. Useful for automated tests, where only the summary output needs to be captured. | ||
| 47 | +.TP | ||
| 48 | +.B \-S, \-\-smp | ||
| 49 | +SMP testing: options -a -t and same priority of all threads | ||
| 50 | +.TP | ||
| 51 | +.B \-t, \-\-threads[=NUM] | ||
| 52 | +Set the number of test threads (default is 1, if this option is not given). If NUM is specified, create NUM test threads. If NUM is not specified, NUM is set to the number of available CPUs. | ||
| 53 | +.SH "EXAMPLES" | ||
| 54 | +The following example was running on a 4-way CPU: | ||
| 55 | +.LP | ||
| 56 | +.nf | ||
| 57 | +# svsematest -a -t -p99 -i100 -d25 -l1000000 | ||
| 58 | +#0: ID13110, P99, CPU0, I100; #1: ID13111, P99, CPU0, Cycles 1000000 | ||
| 59 | +#2: ID13112, P98, CPU1, I125; #3: ID13113, P98, CPU1, Cycles 813573 | ||
| 60 | +#4: ID13114, P97, CPU2, I150; #5: ID13115, P97, CPU2, Cycles 667285 | ||
| 61 | +#6: ID13116, P96, CPU3, I175; #7: ID13117, P96, CPU3, Cycles 591403 | ||
| 62 | +#1 -> #0, Min 1, Cur 2, Avg 2, Max 12 | ||
| 63 | +#3 -> #2, Min 1, Cur 3, Avg 2, Max 12 | ||
| 64 | +#5 -> #4, Min 1, Cur 3, Avg 3, Max 12 | ||
| 65 | +#7 -> #6, Min 1, Cur 2, Avg 3, Max 11 | ||
| 66 | +.fi | ||
| 67 | +.SH "AUTHORS" | ||
| 68 | +.LP | ||
| 69 | +Carsten Emde <C.Emde@osadl.org> | ||
| 70 | +.SH "SEE ALSO" | ||
| 71 | +semop(2) | ||
| 72 | +.LP | ||
| @@ -0,0 +1,794 @@ | |||
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | + | ||
| 3 | +/* | ||
| 4 | + * svsematest.c | ||
| 5 | + * | ||
| 6 | + * Copyright (C) 2009 Carsten Emde <C.Emde@osadl.org> | ||
| 7 | + * | ||
| 8 | + */ | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + | ||
| 35 | + | ||
| 36 | + | ||
| 37 | + | ||
| 38 | + | ||
| 39 | + | ||
| 40 | + | ||
| 41 | + | ||
| 42 | + | ||
| 43 | +enum { | ||
| 44 | + AFFINITY_UNSPECIFIED, | ||
| 45 | + AFFINITY_SPECIFIED, | ||
| 46 | + AFFINITY_USEALL | ||
| 47 | +}; | ||
| 48 | + | ||
| 49 | +struct params { | ||
| 50 | + int num; | ||
| 51 | + int num_threads; | ||
| 52 | + int cpu; | ||
| 53 | + int priority; | ||
| 54 | + int affinity; | ||
| 55 | + int semid; | ||
| 56 | + int sender; | ||
| 57 | + int samples; | ||
| 58 | + int max_cycles; | ||
| 59 | + int tracelimit; | ||
| 60 | + int tid; | ||
| 61 | + pid_t pid; | ||
| 62 | + int shutdown; | ||
| 63 | + int stopped; | ||
| 64 | + struct timespec delay; | ||
| 65 | + unsigned int mindiff, maxdiff; | ||
| 66 | + double sumdiff; | ||
| 67 | + struct timeval unblocked, received, diff; | ||
| 68 | + pthread_t threadid; | ||
| 69 | + struct params *neighbor; | ||
| 70 | +}; | ||
| 71 | + | ||
| 72 | +static int mustfork; | ||
| 73 | +static int wasforked; | ||
| 74 | +static int wasforked_sender = -1; | ||
| 75 | +static int wasforked_threadno = -1; | ||
| 76 | +static int tracelimit; | ||
| 77 | + | ||
| 78 | +void *semathread(void *param) | ||
| 79 | +{ | ||
| 80 | + int mustgetcpu = 0; | ||
| 81 | + struct params *par = param; | ||
| 82 | + cpu_set_t mask; | ||
| 83 | + int policy = SCHED_FIFO; | ||
| 84 | + struct sched_param schedp; | ||
| 85 | + struct sembuf sb = { 0, 0, 0}; | ||
| 86 | + sigset_t sigset; | ||
| 87 | + | ||
| 88 | + sigemptyset(&sigset); | ||
| 89 | + pthread_sigmask(SIG_SETMASK, &sigset, NULL); | ||
| 90 | + | ||
| 91 | + memset(&schedp, 0, sizeof(schedp)); | ||
| 92 | + schedp.sched_priority = par->priority; | ||
| 93 | + sched_setscheduler(0, policy, &schedp); | ||
| 94 | + | ||
| 95 | + if (par->cpu != -1) { | ||
| 96 | + CPU_ZERO(&mask); | ||
| 97 | + CPU_SET(par->cpu, &mask); | ||
| 98 | + if (sched_setaffinity(0, sizeof(mask), &mask) == -1) | ||
| 99 | + fatal("Could not set CPU affinity " | ||
| 100 | + "to CPU #%d\n", par->cpu); | ||
| 101 | + } else { | ||
| 102 | + int max_cpus = sysconf(_SC_NPROCESSORS_CONF); | ||
| 103 | + | ||
| 104 | + if (max_cpus > 1) | ||
| 105 | + mustgetcpu = 1; | ||
| 106 | + else | ||
| 107 | + par->cpu = 0; | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + if (!wasforked) | ||
| 111 | + par->tid = gettid(); | ||
| 112 | + | ||
| 113 | + while (!par->shutdown) { | ||
| 114 | + if (par->sender) { | ||
| 115 | + sb.sem_num = SEM_WAIT_FOR_SENDER; | ||
| 116 | + sb.sem_op = SEM_UNLOCK; | ||
| 117 | + /* | ||
| 118 | + * Unlocking the semaphore: | ||
| 119 | + * Start of latency measurement ... | ||
| 120 | + */ | ||
| 121 | + gettimeofday(&par->unblocked, NULL); | ||
| 122 | + semop(par->semid, &sb, 1); | ||
| 123 | + par->samples++; | ||
| 124 | + if (par->max_cycles && par->samples >= par->max_cycles) | ||
| 125 | + par->shutdown = 1; | ||
| 126 | + | ||
| 127 | + if (mustgetcpu) | ||
| 128 | + par->cpu = get_cpu(); | ||
| 129 | + | ||
| 130 | + sb.sem_num = SEM_WAIT_FOR_RECEIVER; | ||
| 131 | + sb.sem_op = SEM_LOCK; | ||
| 132 | + semop(par->semid, &sb, 1); | ||
| 133 | + | ||
| 134 | + sb.sem_num = SEM_WAIT_FOR_SENDER; | ||
| 135 | + sb.sem_op = SEM_LOCK; | ||
| 136 | + semop(par->semid, &sb, 1); | ||
| 137 | + } else { | ||
| 138 | + /* Receiver */ | ||
| 139 | + struct params *neighbor; | ||
| 140 | + | ||
| 141 | + if (wasforked) | ||
| 142 | + neighbor = par + par->num_threads; | ||
| 143 | + else | ||
| 144 | + neighbor = par->neighbor; | ||
| 145 | + | ||
| 146 | + sb.sem_num = SEM_WAIT_FOR_SENDER; | ||
| 147 | + sb.sem_op = SEM_LOCK; | ||
| 148 | + semop(par->semid, &sb, 1); | ||
| 149 | + | ||
| 150 | + /* | ||
| 151 | + * ... We got the lock: | ||
| 152 | + * End of latency measurement | ||
| 153 | + */ | ||
| 154 | + gettimeofday(&par->received, NULL); | ||
| 155 | + par->samples++; | ||
| 156 | + if (par->max_cycles && par->samples >= par->max_cycles) | ||
| 157 | + par->shutdown = 1; | ||
| 158 | + | ||
| 159 | + if (mustgetcpu) | ||
| 160 | + par->cpu = get_cpu(); | ||
| 161 | + | ||
| 162 | + timersub(&par->received, &neighbor->unblocked, | ||
| 163 | + &par->diff); | ||
| 164 | + | ||
| 165 | + if (par->diff.tv_usec < par->mindiff) | ||
| 166 | + par->mindiff = par->diff.tv_usec; | ||
| 167 | + if (par->diff.tv_usec > par->maxdiff) | ||
| 168 | + par->maxdiff = par->diff.tv_usec; | ||
| 169 | + par->sumdiff += (double) par->diff.tv_usec; | ||
| 170 | + if (par->tracelimit && par->maxdiff > par->tracelimit) { | ||
| 171 | + char tracing_enabled_file[MAX_PATH]; | ||
| 172 | + | ||
| 173 | + strcpy(tracing_enabled_file, get_debugfileprefix()); | ||
| 174 | + strcat(tracing_enabled_file, "tracing_on"); | ||
| 175 | + int tracing_enabled = | ||
| 176 | + open(tracing_enabled_file, O_WRONLY); | ||
| 177 | + if (tracing_enabled >= 0) { | ||
| 178 | + write(tracing_enabled, "0", 1); | ||
| 179 | + close(tracing_enabled); | ||
| 180 | + } else | ||
| 181 | + fatal("Could not access %s\n", | ||
| 182 | + tracing_enabled_file); | ||
| 183 | + par->shutdown = 1; | ||
| 184 | + neighbor->shutdown = 1; | ||
| 185 | + } | ||
| 186 | + | ||
| 187 | + sb.sem_num = SEM_WAIT_FOR_RECEIVER; | ||
| 188 | + sb.sem_op = SEM_UNLOCK; | ||
| 189 | + semop(par->semid, &sb, 1); | ||
| 190 | + | ||
| 191 | + nanosleep(&par->delay, NULL); | ||
| 192 | + | ||
| 193 | + sb.sem_num = SEM_WAIT_FOR_SENDER; | ||
| 194 | + sb.sem_op = SEM_UNLOCK; | ||
| 195 | + semop(par->semid, &sb, 1); | ||
| 196 | + } | ||
| 197 | + } | ||
| 198 | + if (par->sender) { | ||
| 199 | + sb.sem_num = SEM_WAIT_FOR_SENDER; | ||
| 200 | + sb.sem_op = SEM_UNLOCK; | ||
| 201 | + semop(par->semid, &sb, 1); | ||
| 202 | + | ||
| 203 | + sb.sem_num = SEM_WAIT_FOR_RECEIVER; | ||
| 204 | + sb.sem_op = SEM_UNLOCK; | ||
| 205 | + semop(par->semid, &sb, 1); | ||
| 206 | + } | ||
| 207 | + par->stopped = 1; | ||
| 208 | + return NULL; | ||
| 209 | +} | ||
| 210 | + | ||
| 211 | + | ||
| 212 | +union semun { | ||
| 213 | + int val; /* Value for SETVAL */ | ||
| 214 | + struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ | ||
| 215 | + unsigned short *array; /* Array for GETALL, SETALL */ | ||
| 216 | + struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */ | ||
| 217 | +}; | ||
| 218 | + | ||
| 219 | + | ||
| 220 | +static void display_help(int error) | ||
| 221 | +{ | ||
| 222 | + printf("svsematest V %1.2f\n", VERSION); | ||
| 223 | + printf("Usage:\n" | ||
| 224 | + "svsematest <options>\n\n" | ||
| 225 | + "Function: test SYSV semaphore latency\n\n" | ||
| 226 | + "Avaiable options:\n" | ||
| 227 | + "-a [NUM] --affinity run thread #N on processor #N, if possible\n" | ||
| 228 | + " with NUM pin all threads to the processor NUM\n" | ||
| 229 | + "-b USEC --breaktrace=USEC send break trace command when latency > USEC\n" | ||
| 230 | + "-d DIST --distance=DIST distance of thread intervals in us default=500\n" | ||
| 231 | + "-D --duration=TIME specify a length for the test run.\n" | ||
| 232 | + " Append 'm', 'h', or 'd' to specify minutes, hours or\n" | ||
| 233 | + " days.\n" | ||
| 234 | + "-f [OPT] --fork[=OPT] fork new processes instead of creating threads\n" | ||
| 235 | + "-i INTV --interval=INTV base interval of thread in us default=1000\n" | ||
| 236 | + " --json=FILENAME write final results into FILENAME, JSON formatted\n" | ||
| 237 | + "-l LOOPS --loops=LOOPS number of loops: default=0(endless)\n" | ||
| 238 | + "-p PRIO --prio=PRIO priority\n" | ||
| 239 | + "-S --smp SMP testing: options -a -t and same priority\n" | ||
| 240 | + " of all threads\n" | ||
| 241 | + "-t --threads one thread per available processor\n" | ||
| 242 | + "-t [NUM] --threads[=NUM] number of threads:\n" | ||
| 243 | + " without NUM, threads = max_cpus\n" | ||
| 244 | + " without -t default = 1\n" | ||
| 245 | + ); | ||
| 246 | + exit(error); | ||
| 247 | +} | ||
| 248 | + | ||
| 249 | +static int setaffinity = AFFINITY_UNSPECIFIED; | ||
| 250 | +static int affinity; | ||
| 251 | +static int priority; | ||
| 252 | +static int num_threads = 1; | ||
| 253 | +static int max_cycles; | ||
| 254 | +static int duration; | ||
| 255 | +static int interval = 1000; | ||
| 256 | +static int distance = 500; | ||
| 257 | +static int smp; | ||
| 258 | +static int sameprio; | ||
| 259 | +static int quiet; | ||
| 260 | +static char jsonfile[MAX_PATH]; | ||
| 261 | + | ||
| 262 | +enum option_value { | ||
| 263 | + OPT_AFFINITY=1, OPT_BREAKTRACE, OPT_DISTANCE, OPT_DURATION, | ||
| 264 | + OPT_FORK, OPT_HELP, OPT_INTERVAL, OPT_JSON, OPT_LOOPS, | ||
| 265 | + OPT_PRIORITY, OPT_QUIET, OPT_SMP, OPT_THREADS | ||
| 266 | +}; | ||
| 267 | + | ||
| 268 | +static void process_options(int argc, char *argv[]) | ||
| 269 | +{ | ||
| 270 | + int error = 0; | ||
| 271 | + int max_cpus = sysconf(_SC_NPROCESSORS_CONF); | ||
| 272 | + int thistracelimit = 0; | ||
| 273 | + | ||
| 274 | + for (;;) { | ||
| 275 | + int option_index = 0; | ||
| 276 | + /** Options for getopt */ | ||
| 277 | + static struct option long_options[] = { | ||
| 278 | + {"affinity", optional_argument, NULL, OPT_AFFINITY}, | ||
| 279 | + {"breaktrace", required_argument, NULL, OPT_BREAKTRACE}, | ||
| 280 | + {"distance", required_argument, NULL, OPT_DISTANCE}, | ||
| 281 | + {"duration", required_argument, NULL, OPT_DURATION}, | ||
| 282 | + {"fork", optional_argument, NULL, OPT_FORK}, | ||
| 283 | + {"help", no_argument, NULL, OPT_HELP}, | ||
| 284 | + {"interval", required_argument, NULL, OPT_INTERVAL}, | ||
| 285 | + {"json", required_argument, NULL, OPT_JSON}, | ||
| 286 | + {"loops", required_argument, NULL, OPT_LOOPS}, | ||
| 287 | + {"priority", required_argument, NULL, OPT_PRIORITY}, | ||
| 288 | + {"quiet", no_argument, NULL, OPT_QUIET}, | ||
| 289 | + {"smp", no_argument, NULL, OPT_SMP}, | ||
| 290 | + {"threads", optional_argument, NULL, OPT_THREADS}, | ||
| 291 | + {NULL, 0, NULL, 0} | ||
| 292 | + }; | ||
| 293 | + int c = getopt_long (argc, argv, "a::b:d:D:f::hi:l:p:qSt::", | ||
| 294 | + long_options, &option_index); | ||
| 295 | + if (c == -1) | ||
| 296 | + break; | ||
| 297 | + switch (c) { | ||
| 298 | + case OPT_AFFINITY: | ||
| 299 | + case 'a': | ||
| 300 | + if (smp) { | ||
| 301 | + warn("-a ignored due to --smp\n"); | ||
| 302 | + break; | ||
| 303 | + } | ||
| 304 | + if (optarg != NULL) { | ||
| 305 | + affinity = atoi(optarg); | ||
| 306 | + setaffinity = AFFINITY_SPECIFIED; | ||
| 307 | + } else if (optind < argc && atoi(argv[optind])) { | ||
| 308 | + affinity = atoi(argv[optind]); | ||
| 309 | + setaffinity = AFFINITY_SPECIFIED; | ||
| 310 | + } else { | ||
| 311 | + setaffinity = AFFINITY_USEALL; | ||
| 312 | + } | ||
| 313 | + break; | ||
| 314 | + case OPT_BREAKTRACE: | ||
| 315 | + case 'b': | ||
| 316 | + thistracelimit = atoi(optarg); | ||
| 317 | + break; | ||
| 318 | + case OPT_DISTANCE: | ||
| 319 | + case 'd': | ||
| 320 | + distance = atoi(optarg); | ||
| 321 | + break; | ||
| 322 | + case OPT_DURATION: | ||
| 323 | + case 'D': | ||
| 324 | + duration = parse_time_string(optarg); | ||
| 325 | + break; | ||
| 326 | + case OPT_FORK: | ||
| 327 | + case 'f': | ||
| 328 | + if (optarg != NULL) { | ||
| 329 | + wasforked = 1; | ||
| 330 | + if (optarg[0] == 's') | ||
| 331 | + wasforked_sender = 1; | ||
| 332 | + else if (optarg[0] == 'r') | ||
| 333 | + wasforked_sender = 0; | ||
| 334 | + wasforked_threadno = atoi(optarg+1); | ||
| 335 | + } else | ||
| 336 | + mustfork = 1; | ||
| 337 | + break; | ||
| 338 | + case OPT_HELP: | ||
| 339 | + case 'h': | ||
| 340 | + display_help(0); | ||
| 341 | + break; | ||
| 342 | + case OPT_INTERVAL: | ||
| 343 | + case 'i': | ||
| 344 | + interval = atoi(optarg); | ||
| 345 | + break; | ||
| 346 | + case OPT_JSON: | ||
| 347 | + strncpy(jsonfile, optarg, strnlen(optarg, MAX_PATH-1)); | ||
| 348 | + break; | ||
| 349 | + case OPT_LOOPS: | ||
| 350 | + case 'l': | ||
| 351 | + max_cycles = atoi(optarg); | ||
| 352 | + break; | ||
| 353 | + case OPT_PRIORITY: | ||
| 354 | + case 'p': | ||
| 355 | + priority = atoi(optarg); | ||
| 356 | + break; | ||
| 357 | + case OPT_QUIET: | ||
| 358 | + case 'q': | ||
| 359 | + quiet = 1; | ||
| 360 | + break; | ||
| 361 | + case OPT_SMP: | ||
| 362 | + case 'S': | ||
| 363 | + smp = 1; | ||
| 364 | + num_threads = max_cpus; | ||
| 365 | + setaffinity = AFFINITY_USEALL; | ||
| 366 | + break; | ||
| 367 | + case OPT_THREADS: | ||
| 368 | + case 't': | ||
| 369 | + if (smp) { | ||
| 370 | + warn("-t ignored due to --smp\n"); | ||
| 371 | + break; | ||
| 372 | + } | ||
| 373 | + if (optarg != NULL) | ||
| 374 | + num_threads = atoi(optarg); | ||
| 375 | + else if (optind < argc && atoi(argv[optind])) | ||
| 376 | + num_threads = atoi(argv[optind]); | ||
| 377 | + else | ||
| 378 | + num_threads = max_cpus; | ||
| 379 | + break; | ||
| 380 | + default: | ||
| 381 | + display_help(1); | ||
| 382 | + break; | ||
| 383 | + } | ||
| 384 | + } | ||
| 385 | + | ||
| 386 | + if (!wasforked) { | ||
| 387 | + if (setaffinity == AFFINITY_SPECIFIED) { | ||
| 388 | + if (affinity < 0) | ||
| 389 | + error = 1; | ||
| 390 | + if (affinity >= max_cpus) { | ||
| 391 | + fprintf(stderr, "ERROR: CPU #%d not found, " | ||
| 392 | + "only %d CPUs available\n", | ||
| 393 | + affinity, max_cpus); | ||
| 394 | + error = 1; | ||
| 395 | + } | ||
| 396 | + } | ||
| 397 | + | ||
| 398 | + if (duration < 0) | ||
| 399 | + error = 0; | ||
| 400 | + | ||
| 401 | + if (num_threads < 1) | ||
| 402 | + error = 1; | ||
| 403 | + | ||
| 404 | + if (priority < 0 || priority > 99) | ||
| 405 | + error = 1; | ||
| 406 | + | ||
| 407 | + if (priority && smp) | ||
| 408 | + sameprio = 1; | ||
| 409 | + | ||
| 410 | + tracelimit = thistracelimit; | ||
| 411 | + } | ||
| 412 | + if (error) | ||
| 413 | + display_help(error); | ||
| 414 | +} | ||
| 415 | + | ||
| 416 | + | ||
| 417 | +static int volatile mustshutdown; | ||
| 418 | + | ||
| 419 | +static void sighand(int sig __attribute__ ((unused))) | ||
| 420 | +{ | ||
| 421 | + mustshutdown = 1; | ||
| 422 | +} | ||
| 423 | + | ||
| 424 | +struct params_stats { | ||
| 425 | + struct params *receiver; | ||
| 426 | + struct params *sender; | ||
| 427 | +}; | ||
| 428 | + | ||
| 429 | +static void write_stats(FILE *f, void *data) | ||
| 430 | +{ | ||
| 431 | + struct params_stats *ps = data; | ||
| 432 | + struct params *s, *r; | ||
| 433 | + int i; | ||
| 434 | + | ||
| 435 | + fprintf(f, " \"num_threads\": %d,\n", num_threads); | ||
| 436 | + fprintf(f, " \"thread\": {\n"); | ||
| 437 | + for (i = 0; i < num_threads; i++) { | ||
| 438 | + s = &ps->sender[i]; | ||
| 439 | + r = &ps->receiver[i]; | ||
| 440 | + fprintf(f, " \"%u\": {\n", i); | ||
| 441 | + fprintf(f, " \"sender\": {\n"); | ||
| 442 | + fprintf(f, " \"cpu\": %d,\n", s->cpu); | ||
| 443 | + fprintf(f, " \"priority\": %d,\n", s->priority); | ||
| 444 | + fprintf(f, " \"samples\": %d,\n", s->samples); | ||
| 445 | + fprintf(f, " \"interval\": %ld\n", r->delay.tv_nsec/1000); | ||
| 446 | + fprintf(f, " },\n"); | ||
| 447 | + fprintf(f, " \"receiver\": {\n"); | ||
| 448 | + fprintf(f, " \"cpu\": %d,\n", r->cpu); | ||
| 449 | + fprintf(f, " \"priority\": %d,\n", r->priority); | ||
| 450 | + fprintf(f, " \"min\": %d,\n", r->mindiff); | ||
| 451 | + fprintf(f, " \"avg\": %.2f,\n", r->sumdiff/r->samples); | ||
| 452 | + fprintf(f, " \"max\": %d\n", r->maxdiff); | ||
| 453 | + fprintf(f, " }\n"); | ||
| 454 | + fprintf(f, " }%s\n", i == num_threads - 1 ? "" : ","); | ||
| 455 | + } | ||
| 456 | + fprintf(f, " }\n"); | ||
| 457 | +} | ||
| 458 | + | ||
| 459 | +static void print_stat(FILE *fp __attribute__ ((unused)), struct params *receiver, struct params *sender, int verbose __attribute__ ((unused)), int quiet) | ||
| 460 | +{ | ||
| 461 | + int i; | ||
| 462 | + | ||
| 463 | + if (quiet) | ||
| 464 | + return; | ||
| 465 | + | ||
| 466 | + for (i = 0; i < num_threads; i++) { | ||
| 467 | + int receiver_pid, sender_pid; | ||
| 468 | + | ||
| 469 | + if (mustfork) { | ||
| 470 | + receiver_pid = receiver[i].pid; | ||
| 471 | + sender_pid = sender[i].pid; | ||
| 472 | + } else { | ||
| 473 | + receiver_pid = receiver[i].tid; | ||
| 474 | + sender_pid = sender[i].tid; | ||
| 475 | + } | ||
| 476 | + printf("#%1d: ID%d, P%d, CPU%d, I%ld; #%1d: ID%d, P%d, CPU%d, Cycles %d\n", | ||
| 477 | + i*2, receiver_pid, receiver[i].priority, | ||
| 478 | + receiver[i].cpu, receiver[i].delay.tv_nsec / | ||
| 479 | + 1000, i*2+1, sender_pid, sender[i].priority, | ||
| 480 | + sender[i].cpu, sender[i].samples); | ||
| 481 | + } | ||
| 482 | + | ||
| 483 | + for (i = 0; i < num_threads; i++) { | ||
| 484 | + if (receiver[i].mindiff == -1) | ||
| 485 | + printf("#%d -> #%d (not yet ready)\n", | ||
| 486 | + i*2+1, i*2); | ||
| 487 | + else | ||
| 488 | + printf("#%d -> #%d, Min %4d, Cur %4d, Avg %4d, Max %4d\n", | ||
| 489 | + i*2+1, i*2, receiver[i].mindiff, | ||
| 490 | + (int) receiver[i].diff.tv_usec, | ||
| 491 | + (int) ((receiver[i].sumdiff / | ||
| 492 | + receiver[i].samples) + 0.5), | ||
| 493 | + receiver[i].maxdiff); | ||
| 494 | + } | ||
| 495 | +} | ||
| 496 | + | ||
| 497 | +int main(int argc, char *argv[]) | ||
| 498 | +{ | ||
| 499 | + char *myfile; | ||
| 500 | + int i, totalsize = 0; | ||
| 501 | + int max_cpus = sysconf(_SC_NPROCESSORS_CONF); | ||
| 502 | + int oldsamples = 1; | ||
| 503 | + key_t key; | ||
| 504 | + union semun args; | ||
| 505 | + struct params *receiver = NULL; | ||
| 506 | + struct params *sender = NULL; | ||
| 507 | + sigset_t sigset; | ||
| 508 | + void *param = NULL; | ||
| 509 | + char f_opt[14]; | ||
| 510 | + struct timespec launchdelay, maindelay; | ||
| 511 | + | ||
| 512 | + myfile = getenv("_"); | ||
| 513 | + if (myfile == NULL) | ||
| 514 | + myfile = argv[0]; | ||
| 515 | + | ||
| 516 | + rt_init(argc, argv); | ||
| 517 | + process_options(argc, argv); | ||
| 518 | + | ||
| 519 | + if (check_privs()) | ||
| 520 | + return 1; | ||
| 521 | + | ||
| 522 | + if (mlockall(MCL_CURRENT|MCL_FUTURE) == -1) { | ||
| 523 | + perror("mlockall"); | ||
| 524 | + return 1; | ||
| 525 | + } | ||
| 526 | + | ||
| 527 | + get_cpu_setup(); | ||
| 528 | + | ||
| 529 | + if (mustfork) { | ||
| 530 | + int shmem; | ||
| 531 | + | ||
| 532 | + /* | ||
| 533 | + * In fork mode (-f), the shared memory contains two | ||
| 534 | + * subsequent arrays, receiver[num_threads] and | ||
| 535 | + * sender[num_threads]. | ||
| 536 | + */ | ||
| 537 | + totalsize = num_threads * sizeof(struct params) * 2; | ||
| 538 | + | ||
| 539 | + shm_unlink("/sigwaittest"); | ||
| 540 | + shmem = shm_open("/sigwaittest", O_CREAT|O_EXCL|O_RDWR, | ||
| 541 | + S_IRUSR|S_IWUSR); | ||
| 542 | + if (shmem < 0) { | ||
| 543 | + fprintf(stderr, "Could not create shared memory\n"); | ||
| 544 | + return 1; | ||
| 545 | + } | ||
| 546 | + ftruncate(shmem, totalsize); | ||
| 547 | + param = mmap(0, totalsize, PROT_READ|PROT_WRITE, MAP_SHARED, | ||
| 548 | + shmem, 0); | ||
| 549 | + if (param == MAP_FAILED) { | ||
| 550 | + fprintf(stderr, "Could not map shared memory\n"); | ||
| 551 | + close(shmem); | ||
| 552 | + return 1; | ||
| 553 | + } | ||
| 554 | + | ||
| 555 | + receiver = (struct params *) param; | ||
| 556 | + sender = receiver + num_threads; | ||
| 557 | + } else if (wasforked) { | ||
| 558 | + struct stat buf; | ||
| 559 | + int shmem, totalsize, expect_totalsize; | ||
| 560 | + | ||
| 561 | + if (wasforked_threadno == -1 || wasforked_sender == -1) { | ||
| 562 | + fprintf(stderr, "Invalid fork option\n"); | ||
| 563 | + return 1; | ||
| 564 | + } | ||
| 565 | + shmem = shm_open("/sigwaittest", O_RDWR, S_IRUSR|S_IWUSR); | ||
| 566 | + if (fstat(shmem, &buf)) { | ||
| 567 | + fprintf(stderr, | ||
| 568 | + "Could not determine shared memory size\n"); | ||
| 569 | + close(shmem); | ||
| 570 | + return 1; | ||
| 571 | + } | ||
| 572 | + totalsize = buf.st_size; | ||
| 573 | + param = mmap(0, totalsize, PROT_READ|PROT_WRITE, MAP_SHARED, | ||
| 574 | + shmem, 0); | ||
| 575 | + close(shmem); | ||
| 576 | + if (param == MAP_FAILED) { | ||
| 577 | + fprintf(stderr, "Could not map shared memory\n"); | ||
| 578 | + return 1; | ||
| 579 | + } | ||
| 580 | + | ||
| 581 | + receiver = (struct params *) param; | ||
| 582 | + expect_totalsize = receiver->num_threads * | ||
| 583 | + sizeof(struct params) * 2; | ||
| 584 | + if (totalsize != expect_totalsize) { | ||
| 585 | + fprintf(stderr, "Memory size problem (expected %d, " | ||
| 586 | + "found %d\n", expect_totalsize, totalsize); | ||
| 587 | + munmap(param, totalsize); | ||
| 588 | + return 1; | ||
| 589 | + } | ||
| 590 | + sender = receiver + receiver->num_threads; | ||
| 591 | + if (wasforked_sender) | ||
| 592 | + semathread(sender + wasforked_threadno); | ||
| 593 | + else | ||
| 594 | + semathread(receiver + wasforked_threadno); | ||
| 595 | + munmap(param, totalsize); | ||
| 596 | + return 0; | ||
| 597 | + } | ||
| 598 | + | ||
| 599 | + signal(SIGINT, sighand); | ||
| 600 | + signal(SIGTERM, sighand); | ||
| 601 | + signal(SIGALRM, sighand); | ||
| 602 | + | ||
| 603 | + sigemptyset(&sigset); | ||
| 604 | + pthread_sigmask(SIG_SETMASK, &sigset, NULL); | ||
| 605 | + | ||
| 606 | + if (duration) | ||
| 607 | + alarm(duration); | ||
| 608 | + | ||
| 609 | + if (!mustfork && !wasforked) { | ||
| 610 | + receiver = calloc(num_threads, sizeof(struct params)); | ||
| 611 | + sender = calloc(num_threads, sizeof(struct params)); | ||
| 612 | + if (receiver == NULL || sender == NULL) | ||
| 613 | + goto nomem; | ||
| 614 | + } | ||
| 615 | + | ||
| 616 | + launchdelay.tv_sec = 0; | ||
| 617 | + launchdelay.tv_nsec = 10000000; /* 10 ms */ | ||
| 618 | + | ||
| 619 | + maindelay.tv_sec = 0; | ||
| 620 | + maindelay.tv_nsec = 50000000; /* 50 ms */ | ||
| 621 | + | ||
| 622 | + for (i = 0; i < num_threads; i++) { | ||
| 623 | + struct sembuf sb = { 0, 0, 0}; | ||
| 624 | + | ||
| 625 | + receiver[i].mindiff = UINT_MAX; | ||
| 626 | + receiver[i].maxdiff = 0; | ||
| 627 | + receiver[i].sumdiff = 0.0; | ||
| 628 | + | ||
| 629 | + if ((key = ftok(myfile, i)) == -1) { | ||
| 630 | + perror("ftok"); | ||
| 631 | + goto nosem; | ||
| 632 | + } | ||
| 633 | + | ||
| 634 | + if ((receiver[i].semid = semget(key, 2, 0666 | IPC_CREAT)) == -1) { | ||
| 635 | + perror("semget"); | ||
| 636 | + goto nosem; | ||
| 637 | + } | ||
| 638 | + | ||
| 639 | + args.val = 1; | ||
| 640 | + if (semctl(receiver[i].semid, SEM_WAIT_FOR_RECEIVER, SETVAL, args) == -1) { | ||
| 641 | + perror("semctl sema #0"); | ||
| 642 | + goto nosem; | ||
| 643 | + } | ||
| 644 | + | ||
| 645 | + if (semctl(receiver[i].semid, SEM_WAIT_FOR_SENDER, SETVAL, args) == -1) { | ||
| 646 | + perror("semctl sema #1"); | ||
| 647 | + goto nosem; | ||
| 648 | + } | ||
| 649 | + | ||
| 650 | + sb.sem_num = SEM_WAIT_FOR_RECEIVER; | ||
| 651 | + sb.sem_op = SEM_LOCK; | ||
| 652 | + semop(receiver[i].semid, &sb, 1); | ||
| 653 | + | ||
| 654 | + sb.sem_num = SEM_WAIT_FOR_SENDER; | ||
| 655 | + sb.sem_op = SEM_LOCK; | ||
| 656 | + semop(receiver[i].semid, &sb, 1); | ||
| 657 | + | ||
| 658 | + receiver[i].cpu = i; | ||
| 659 | + switch (setaffinity) { | ||
| 660 | + case AFFINITY_UNSPECIFIED: receiver[i].cpu = -1; break; | ||
| 661 | + case AFFINITY_SPECIFIED: receiver[i].cpu = affinity; break; | ||
| 662 | + case AFFINITY_USEALL: receiver[i].cpu = i % max_cpus; break; | ||
| 663 | + } | ||
| 664 | + receiver[i].priority = priority; | ||
| 665 | + receiver[i].tracelimit = tracelimit; | ||
| 666 | + if (priority > 1 && !sameprio) | ||
| 667 | + priority--; | ||
| 668 | + receiver[i].delay.tv_sec = interval / USEC_PER_SEC; | ||
| 669 | + receiver[i].delay.tv_nsec = (interval % USEC_PER_SEC) * 1000; | ||
| 670 | + interval += distance; | ||
| 671 | + receiver[i].max_cycles = max_cycles; | ||
| 672 | + receiver[i].sender = 0; | ||
| 673 | + receiver[i].neighbor = &sender[i]; | ||
| 674 | + if (mustfork) { | ||
| 675 | + pid_t pid = fork(); | ||
| 676 | + if (pid == -1) { | ||
| 677 | + fprintf(stderr, "Could not fork\n"); | ||
| 678 | + return 1; | ||
| 679 | + } else if (pid == 0) { | ||
| 680 | + char *args[3]; | ||
| 681 | + | ||
| 682 | + receiver[i].num_threads = num_threads; | ||
| 683 | + receiver[i].pid = getpid(); | ||
| 684 | + sprintf(f_opt, "-fr%d", i); | ||
| 685 | + args[0] = argv[0]; | ||
| 686 | + args[1] = f_opt; | ||
| 687 | + args[2] = NULL; | ||
| 688 | + execvp(args[0], args); | ||
| 689 | + fprintf(stderr, | ||
| 690 | + "Could not execute receiver child process " | ||
| 691 | + "#%d\n", i); | ||
| 692 | + } | ||
| 693 | + } else | ||
| 694 | + pthread_create(&receiver[i].threadid, NULL, | ||
| 695 | + semathread, &receiver[i]); | ||
| 696 | + | ||
| 697 | + nanosleep(&launchdelay, NULL); | ||
| 698 | + | ||
| 699 | + memcpy(&sender[i], &receiver[i], sizeof(receiver[0])); | ||
| 700 | + sender[i].sender = 1; | ||
| 701 | + sender[i].neighbor = &receiver[i]; | ||
| 702 | + if (mustfork) { | ||
| 703 | + pid_t pid = fork(); | ||
| 704 | + if (pid == -1) { | ||
| 705 | + fprintf(stderr, "Could not fork\n"); | ||
| 706 | + return 1; | ||
| 707 | + } else if (pid == 0) { | ||
| 708 | + char *args[3]; | ||
| 709 | + | ||
| 710 | + sender[i].num_threads = num_threads; | ||
| 711 | + sender[i].pid = getpid(); | ||
| 712 | + sprintf(f_opt, "-fs%d", i); | ||
| 713 | + args[0] = argv[0]; | ||
| 714 | + args[1] = f_opt; | ||
| 715 | + args[2] = NULL; | ||
| 716 | + execvp(args[0], args); | ||
| 717 | + fprintf(stderr, | ||
| 718 | + "Could not execute sender child process " | ||
| 719 | + "#%d\n", i); | ||
| 720 | + } | ||
| 721 | + } else | ||
| 722 | + pthread_create(&sender[i].threadid, NULL, semathread, | ||
| 723 | + &sender[i]); | ||
| 724 | + } | ||
| 725 | + | ||
| 726 | + while (!mustshutdown) { | ||
| 727 | + for (i = 0; i < num_threads; i++) | ||
| 728 | + mustshutdown |= receiver[i].shutdown | | ||
| 729 | + sender[i].shutdown; | ||
| 730 | + | ||
| 731 | + if (receiver[0].samples > oldsamples || mustshutdown) { | ||
| 732 | + print_stat(stdout, receiver, sender, 0, quiet); | ||
| 733 | + if (!quiet) | ||
| 734 | + printf("\033[%dA", num_threads*2); | ||
| 735 | + } | ||
| 736 | + | ||
| 737 | + sigemptyset(&sigset); | ||
| 738 | + sigaddset(&sigset, SIGTERM); | ||
| 739 | + sigaddset(&sigset, SIGINT); | ||
| 740 | + sigaddset(&sigset, SIGALRM); | ||
| 741 | + pthread_sigmask(SIG_SETMASK, &sigset, NULL); | ||
| 742 | + | ||
| 743 | + nanosleep(&maindelay, NULL); | ||
| 744 | + | ||
| 745 | + sigemptyset(&sigset); | ||
| 746 | + pthread_sigmask(SIG_SETMASK, &sigset, NULL); | ||
| 747 | + } | ||
| 748 | + | ||
| 749 | + if (!quiet) | ||
| 750 | + printf("\033[%dB", num_threads*2 + 2); | ||
| 751 | + else | ||
| 752 | + print_stat(stdout, receiver, sender, 0, 0); | ||
| 753 | + | ||
| 754 | + for (i = 0; i < num_threads; i++) { | ||
| 755 | + receiver[i].shutdown = 1; | ||
| 756 | + sender[i].shutdown = 1; | ||
| 757 | + } | ||
| 758 | + nanosleep(&receiver[0].delay, NULL); | ||
| 759 | + | ||
| 760 | + for (i = 0; i < num_threads; i++) { | ||
| 761 | + if (!receiver[i].stopped) { | ||
| 762 | + if (mustfork) | ||
| 763 | + kill(receiver[i].pid, SIGTERM); | ||
| 764 | + else | ||
| 765 | + pthread_kill(receiver[i].threadid, SIGTERM); | ||
| 766 | + } | ||
| 767 | + if (!sender[i].stopped) { | ||
| 768 | + if (mustfork) | ||
| 769 | + kill(sender[i].pid, SIGTERM); | ||
| 770 | + else | ||
| 771 | + pthread_kill(sender[i].threadid, SIGTERM); | ||
| 772 | + } | ||
| 773 | + } | ||
| 774 | + | ||
| 775 | + if (strlen(jsonfile) != 0) { | ||
| 776 | + struct params_stats ps = { | ||
| 777 | + .receiver = receiver, | ||
| 778 | + .sender = sender, | ||
| 779 | + }; | ||
| 780 | + rt_write_json(jsonfile, 0, write_stats, &ps); | ||
| 781 | + } | ||
| 782 | + | ||
| 783 | +nosem: | ||
| 784 | + for (i = 0; i < num_threads; i++) | ||
| 785 | + semctl(receiver[i].semid, -1, IPC_RMID); | ||
| 786 | + | ||
| 787 | +nomem: | ||
| 788 | + if (mustfork) { | ||
| 789 | + munmap(param, totalsize); | ||
| 790 | + shm_unlink("/sigwaittest"); | ||
| 791 | + } | ||
| 792 | + | ||
| 793 | + return 0; | ||
| 794 | +} | ||


这里应该要刷新我们oe的镜像?