pax_global_header00006660000000000000000000000064115466453400014522gustar00rootroot0000000000000052 comment=cb909497c955f5ea6d403e1b0529b514cb292a4c poelzi-ulatencyd-55515a9/000077500000000000000000000000001154664534000152725ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/.gitignore000066400000000000000000000005031154664534000172600ustar00rootroot00000000000000config.h *.patch *.[oa] *.so .kdbgrc.* src/ulatencyd src/ulatencyd_cleanup.lua tests/test_xwatch *~ Makefile # globals GPATH GRTAGS GSYMS GTAGS tests/memleak docs/api docs/html docs/txt docs/wiki .nfs* # cmake cmake_install.cmake CMakeFiles CMakeCache.txt CTestTestfile.cmake Testing install_manifest.txt tests/forkbomb poelzi-ulatencyd-55515a9/AUTHORS000066400000000000000000000000461154664534000163420ustar00rootroot00000000000000Daniel Poelzleithner poelzi-ulatencyd-55515a9/CMakeLists.txt000066400000000000000000000136161154664534000200410ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8) project(ulatency C) INCLUDE(FindPkgConfig) INCLUDE(FindGettext) INCLUDE (CheckIncludeFiles) option(CONFIG_PREFIX "config prefix" "/etc") option(DEVELOP_MODE "build for development" FALSE) option(DEVELOP_DBUS_SESSION "export to session dbus" FALSE) option(LIBCGROUPS "add libcgroups support (BROKEN)" FALSE) option(LUA_JIT "enable luajit support when available" FALSE) option(ENABLE_DBUS "enable dbus" TRUE) SET(INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) if(LIBCGROUPS) pkg_check_modules(LIBCGROUP libcgroup OPTIONAL) endif(LIBCGROUPS) pkg_check_modules(GLIB2 glib-2.0 REQUIRED) #pkg_check_modules(GMODULE gmodule-2.0 REQUIRED) if(ENABLE_DBUS) pkg_check_modules(DBUS dbus-glib-1 REQUIRED) if(DBUS_FOUND) set(ENABLE_DBUS 1) pkg_check_modules(GTHREAD gthread-2.0 REQUIRED) pkg_check_modules(POLKIT polkit-gobject-1) endif(DBUS_FOUND) endif(ENABLE_DBUS) pkg_check_modules(GIO gio-2.0 REQUIRED) # FIXME: libproc should export more symbols # find libproc #FIND_PATH(LIBPROC_INCLUDE_DIR readproc.h /usr/include/proc /usr/local/include/proc) #FIND_LIBRARY(LIBPROC_LIBRARY NAMES proc PATH /usr/lib /usr/local/lib) #IF (LIBPROC_INCLUDE_DIR AND LIBPROC_LIBRARY) # SET(LIBPROC_FOUND TRUE) #ENDIF (LIBPROC_INCLUDE_DIR AND LIBPROC_LIBRARY) #IF (NOT LIBPROC_FOUND) # MESSAGE(FATAL_ERROR "Could not find libproc") #ENDIF (NOT LIBPROC_FOUND) # use own libproc copy SET(CONFIG_PREFIX "/etc" CACHE STRING "Config prefix" FORCE) SET(SYSTEMD_DIR "/lib/systemd/system" CACHE STRING "systemd service file directory") set(LIBPROC_INCLUDE_DIRS src) #set(LIBPROC_LIBRARY src/proc/libproc.a) if(DEVELOP_MODE) message("don't forget to copy conf/org.quamquam.ulatencyd.conf to /etc/dbus-1/system.d and reload dbus") SET(CONFIG_DIR "${CMAKE_CURRENT_BINARY_DIR}/conf") else(DEVELOP_MODE) SET(CONFIG_DIR "${CONFIG_PREFIX}/ulatencyd") endif(DEVELOP_MODE) IF(LUA_JIT) pkg_check_modules(LUAJIT luajit) ENDIF(LUA_JIT) IF(NOT LUAJIT_FOUND) #find_package(lua51) INCLUDE(FindLua51) IF(NOT LUA51_FOUND) message(FATAL_ERROR "lua 5.1 not found") ENDIF(NOT LUA51_FOUND) ENDIF(NOT LUAJIT_FOUND) # config.h checks #include(ConfigureChecks.cmake) #MACRO(INSERT_INTO_MAP _TYPE _KEY _VALUE) # SET("SYS_${_TYPE}_${_KEY}" "${_VALUE}") #ENDMACRO(INSERT_INTO_MAP) #FIXME check this CHECK_INCLUDE_FILES (locale.h HAVE_LOCALE_H) if(GETTEXT_FOUND) set(ENABLE_NLS 1) endif(GETTEXT_FOUND) configure_file(src/config.h.tmpl ${CMAKE_CURRENT_BINARY_DIR}/src/config.h) include_directories (${CMAKE_CURRENT_BINARY_DIR}/src ${LIBCGROUP_INCLUDE_DIRS} ${LIBPROC_INCLUDE_DIRS} ${GLIB2_INCLUDE_DIRS} ${DBUS_INCLUDE_DIRS} ${GIO_INCLUDE_DIRS} ${POLKIT_INCLUDE_DIRS}) IF(LUA_JIT AND LUAJIT_FOUND) include_directories (${LUAJIT_INCLUDE_DIRS}) set(MY_LUA_LIBRARIES ${LUAJIT_LIBRARIES}) ELSE(LUA_JIT AND LUAJIT_FOUND) include_directories (${LUA_INCLUDE_DIR}) set(MY_LUA_LIBRARIES ${LUA_LIBRARIES}) ENDIF(LUA_JIT AND LUAJIT_FOUND) if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") set(ADD_COMPILE_FLAGS "-g -pg -Wall") endif("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") add_subdirectory(src/bc) add_subdirectory(src/proc) add_subdirectory(src) add_subdirectory(modules) add_subdirectory(tests) add_subdirectory(docs) #SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall") #SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -pg -Wall") #SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-Wall") SET_PROPERTY(GLOBAL APPEND PROPERTY CMAKE_C_FLAGS "-Wall -uiaeiaue") # FIXME make distclean recrusive set_property(GLOBAL PROPERTY ALLOW_DUPLICATE_CUSTOM_TARGETS TRUE) ADD_CUSTOM_TARGET (distclean @echo cleaning for source distribution) SET(DISTCLEANED cmake.depends cmake.check_depends CMakeCache.txt cmake.check_cache *.cmake Makefile core core.* gmon.out *~ GPATH GRTAGS GSYMS GTAGS ) ADD_CUSTOM_COMMAND( DEPENDS src/distclean clean COMMENT "distribution clean" COMMAND rm ARGS -Rf CMakeTmp ${DISTCLEANED} TARGET distclean ) install(FILES conf/ulatencyd.conf DESTINATION ${CONFIG_PREFIX}/ulatencyd) install(FILES conf/cgroups.conf DESTINATION ${CONFIG_PREFIX}/ulatencyd) install(FILES conf/simple.conf DESTINATION ${CONFIG_PREFIX}/ulatencyd) install(DIRECTORY rules DESTINATION ${CONFIG_PREFIX}/ulatencyd FILES_MATCHING PATTERN "*.lua" PATTERN "test.lua" EXCLUDE) install(DIRECTORY conf/simple.d DESTINATION ${CONFIG_PREFIX}/ulatencyd PATTERN "*~" EXCLUDE) install(DIRECTORY conf/scheduler DESTINATION ${CONFIG_PREFIX}/ulatencyd PATTERN "*~" EXCLUDE) # install client files FILE(GLOB cl RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "client/ulatency*") SET(client "${cl}" "${client}") FILE(GLOB cl RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "client/run-*") SET(client "${cl}" "${client}") install(PROGRAMS ${client} DESTINATION bin) if(NOT "${SYSTEMD_DIR}" STREQUAL "") install(FILES conf/ulatencyd.service DESTINATION ${SYSTEMD_DIR}) endif (NOT "${SYSTEMD_DIR}" STREQUAL "") # FIXME how to find out where to put it ??? IF(DBUS_FOUND AND ENABLE_DBUS) install(FILES conf/org.quamquam.ulatencyd.conf DESTINATION /etc/dbus-1/system.d) ENDIF(DBUS_FOUND AND ENABLE_DBUS) if(POLKIT_FOUND) install(FILES conf/org.quamquam.ulatencyd.policy DESTINATION ${POLKIT_PREFIX}/share/polkit-1/actions) endif(POLKIT_FOUND) #install(TARGETS mySharedLib DESTINATION /some/full/path) # # # add_custom_target(dist-clean # COMMAND rm -rf CMakeCache.txt CMakeFiles/ CTestTestfile.cmake cmake_install.cmake Makefile # WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) enable_testing() set(CMAKE_CTEST_COMMAND "ctest -V") add_test(lua_tests src/ulatencyd -r tests --rule-pattern test.lua -v -v -v) # dbus dtd is outdated :-( #add_test(dbus_config CONFIGURATIONS Debug # COMMAND xmllint --loaddtd --valid conf/org.quamquam.ulatencyd.conf) poelzi-ulatencyd-55515a9/COPYING000066400000000000000000001045131154664534000163310ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . poelzi-ulatencyd-55515a9/ChangeLog000066400000000000000000000117501154664534000170500ustar00rootroot00000000000000 0.5.0 / 2011-04-05 ================== * bump version * fix single task config * hide videoplayback for now * test if subsystem is loaded * Merge branch 'master' into develop * fix missing labels on game and single_task config * add video_playback config * cache available cgroup names * add xfce & lxde configs * list configs with no info object as well * better error reporting and handling * better error messages on connecting errors * retry process lookup in remote api calls * install all man pages * fix typo schduler -> scheduler in ulatency * add manpage for run-single-task * add manpage for run-game * add ulatency-gui to ulatency.1 manpage * fix goption warning for G_OPTION_FLAG_NO_ARG 0.5.0-rc3 / 2011-03-24 ================== * fix client install rules 0.5.0-rc2 / 2011-03-24 ================== * also run instant filters on processes * do mixture of read/write on mem pages * don't put active processes into rt io * implement stress mode in memleak * implement io prio settings for processes * add banshee player * Merge commit 'dbdcb918d051d71c913e63ed6bea605088b457ed' * more robust version of terminal size detection * fix io bottleneck detector * catch errors caused in terminal detection * Merge commit 'f6715991672ccc8c7fd239b8139aae74dac3a3f5' * work without python-dbus-qt4 * add set config menu in window 0.5.0-rc1 / 2011-03-23 ================== * Merge commit 'd8b2bd5c248c06b39c7d7da56a3961cad1d36cc9' * update readme to reflect recent depencies * let gui survive daemon restart * update gui to use policykit * install doc files, bump version * Merge commit '4d65ec602b0673876732fe100506f68fbc8e08b0' * updates to documentation * optimize doxygen for project * Updated Start Hacking (markdown) * don't start ulatencyd more then once * optional build docs * Merge commit 'bd23966e9e27132cedceb775d01c8fe91e5bb9f3' * add make docs target * Updated Home (markdown) * rename file * Merge commit '76d54cf31eacbb060bcad7f5d41b8e6d78976bb0' * start changing documentation * add api doc generation trough doxygen * add manpages. thanks Alessandro * add script for update external repositories * Add 'docs/wiki/' from commit '978fb5626ac4f2c61c6704dba636c7562b3ce09e' * add script to run ulatencyd under alleyoop * Merge commit '0a9ff7e9373c6d7a9f202f1ebd2252291e65e86a' * adjust cmake rules * implement policykit support * better run command code * use glob matching in simplerules * cleanup exe readlink * add recrusive flags api to dbus interface * add rules for boinc client * use full path on exe matches, not substring * adjust dbus interface to recrusive list flags changes * add u_proc_list_flags function, inherit changes * ensure notifiy release agent is set * add extra executable for gui/close window change * print help config on run command * add support for execute process in switches scheduler config * use u_task struct for task storage * add /usr/local/games to game path * add single_task & game config * bug fix: check data if none * bug fix:function prnt doesnt worked under python3 * move scheduler configs into own path * implement hard limits on memory usage * reduce io prio on poison targets * bump version * fix install rules * Updated 1   FAQ (rest) * Updated Home (markdown) * Updated Flag system (markdown) * Updated Flag system (markdown) * Updated Flag system (markdown) * Updated Home (markdown) * Created flags.md (markdown) * Updated Home (markdown) * Updated 1   FAQ (rest) * Write a small message here explaining this change. (Optional) * Updated Writing Rules (markdown) * Updated Adjusting default scheduler (markdown) * Updated 1   FAQ (rest) * Updated How does it work (markdown) * Updated How does it work (markdown) * Updated 1   FAQ (rest) * Updated 1   FAQ (rest) * Updated Start Hacking (markdown) * Created Start-hacking (markdown) * Updated Home (markdown) * Updated 1   FAQ (rest) * Updated Home (markdown) * Updated Home (markdown) * Updated Home (markdown) * Updated Home (markdown) * Updated Home (markdown) * Updated Home (markdown) * Updated Home (markdown) * Created Adjusting default scheduler (markdown) * Updated Home (markdown) * Updated Home (markdown) * started writing rules howto * Created How does it work (markdown) * Updated Home (markdown) * Updated 1   FAQ (rest) * Updated 1   FAQ (rest) * Updated 1   FAQ (rest) * Updated 1   FAQ (rest) * Write a small message here explaining this change. (Optional) * Updated 1   FAQ (rest) * Updated Home (markdown) * Updated Home (markdown) * Updated Home (markdown) * Updated 1   Does ulatencyd support my cgroups mount points (rest) * Created Faq (rest) * Updated Home (markdown) * start dbus specs * Updated Home (markdown) * Updated Home (markdown) * Created Ideas to test (markdown) * Created Architecture (markdown) * add arch image * Initial Commit poelzi-ulatencyd-55515a9/NEWS000066400000000000000000000020101154664534000157620ustar00rootroot000000000000000.5.0 ===== - Policykit support - Configs for single task & games - XFCE & LXDE support - updated documentation - GUI/CLI updates 0.4.9 ===== - Bugfixes 0.4.8 ===== - cli/gui client called ulatency (requires python2.6+ and qt bindings for gui) - simplerules module for easy to handle rules - clang support - instant rules that are executed without delay. - enhancements to the dbus interface - better realtime support - different bugfixes 0.4.5 ===== - Systemd compatibilty - Per subsystem layout and mountpoints - Bugfixes & rules updates 0.4.1 ===== - IO bottleneck detector - Bugfixes 0.4.0 ===== Features since 0.3.0 - DBUS interface to query and control ulatencyd - Much improved desktop config. It now uses the task group as primary grouping value - IO optimization when available - Workarounds for GNOME & KDE which do not properly set task groups - More robust startup on missing system features - Tons of updates and bugfixes 0.3.0 ===== - First public release poelzi-ulatencyd-55515a9/README000066400000000000000000000062561154664534000161630ustar00rootroot00000000000000WARNING: this program is under development and only suited for testing. == What is ulatency == Ulatency is a daemon that controls how the Linux kernel will spend it's resources on the running processes. It uses dynamic cgroups to give the kernel hints and limitations on processes. It strongly supports the lua scripting language for writing rules and the scheduler code. == What tries it to fix == The Linux scheduler does a pretty good job to give the available resources to all processes, but this may not be the best user experience in the desktop case. ulatencyd monitors the system and categorizes the running processes into cgroups. Processes that run wild to slow down the system by causing massive swaping will be isolated. == Isn't CONFIG_SCHED_DESKTOP enough ? == There is a patch for 2.6.38 in pipeline, see http://thread.gmane.org/gmane.linux.kernel/1050575 I think that this mimimal aproach is good for some circumstances, but does not provide enough flexibility required for a true low latency desktop. Perfect desktop scheduling needs a lot of heuristics, that don't belong in the kernel. For example, the patch won't protect you from swap of death, fork bombs, can't detect which process you are actually using and give more cpu shares to them, can't give realtime priorities to processes like jackd, etc... ulatencyd is designed for fixing exactly that. == Building == Build Requirements: libglib2.0-dev libdbus-glib-1-dev liblua5.1-0-dev | libluajit-5.1-dev liblua5.1-posix1 (sometimes called luaposix) Documentation: doxygen libmoose-perl pandoc CLI: python-dbus python2.5+ - python3.2+ GUI: python-qt4 python-qt4-dbus Compiling: # cmake . # make DEBUG=1 Configure options (optional): # ccmake . Building Documentation: # make docs Install: # sudo make install Running: # sudo /usr/local/sbin/ulatencyd -v 2 == Links == Website - https://github.com/poelzi/ulatencyd Infos - https://github.com/poelzi/ulatencyd/wiki FAQ - https://github.com/poelzi/ulatencyd/wiki/Faq Reporting Bugs - https://github.com/poelzi/ulatencyd/issues == Architecture == See docs/architecture.svg for a general overview. The core of the daemon is written in c, embedding a lua interpreter. Most of the rules are written in lua scripts, as heuristics for system behavior can best be written in a script language. The daemon exports system informations into lua script. There are two ways implementing a heuristic behavior: - using a timeout callback - using a filter class The timeout callback is called until it returns True. The filter class is the preferred way. The filter gets executed on processes and can categorize the process. Depending on the return value of the call the future behaviour may vary. The return value consists of a flag section and i timeout section. The filter is only executed after the timeout seconds. It may also cause the filter to not be called on any child progress of the filter. The processes are traveld in tree order. This means that the progress tree is mapped into the data structure and traveled from top (id = 1, which is the init) and then trough all the children. For more informations see docs/RULES_HOWTO.txt poelzi-ulatencyd-55515a9/TODO000066400000000000000000000031301154664534000157570ustar00rootroot00000000000000TODO: - add process affinity api (maybe better done with cgroups cpuset) - use the lua bignumber interface to export the long long variables of the proc_t structure to lua - upstart plugin - dbus interface - interface for notification of rough processes, when processes or groups of them where killed and why - interface for user wishes pinning to different parameters (cli) - rules rules rules ;-) - often a process is spawned and dies quite quickly. if it is spawned from a already scheduled subprocess, the rules and scheduler code gets applied. this can cause quite a load on ulatencyd, when a lot of processes get spawned and die quickly. having an aging stack for new processes that have to exist for at least n ms before they get run through the pipe should decrease the load on the daemon. the processes will exist in the parent group as long as they are not run yet - optimize src/proc. it is a fork of the libproc that could need some optimizations. currently we use quite many read flags that do unnessery mallocs and reading of values that are not used that much. running in the main loop only a minimal set should be used and in case of new processes a full update should be run. - use signals in xwatch to detect changes in sessions - implement rtkit interface and combine it with cgroups. add filter for detecting abusive rt processes. - optionally drop priviliges UNLIKLY: - maybe: using audit linux system to watch process creation/deletion auditctl or more interessting syscalls - having a copy of libproc is very ugly, but currently it does not export enough symbols poelzi-ulatencyd-55515a9/client/000077500000000000000000000000001154664534000165505ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/client/COPYING000066400000000000000000000772461154664534000176230ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. poelzi-ulatencyd-55515a9/client/README.md000066400000000000000000000004341154664534000200300ustar00rootroot00000000000000WARNING: this program is under development and only suited for testing. # ulatencyd client it's meant to be a usable client (cli and gui) for the [ulatencyd](https://github.com/poelzi/ulatencyd) deamon. ## Dependency * python-dbus * python 2.5> * ulatencyd * python-qt4 (optional)poelzi-ulatencyd-55515a9/client/run-game000077700000000000000000000000001154664534000217502ulatencyustar00rootroot00000000000000poelzi-ulatencyd-55515a9/client/run-single-task000077700000000000000000000000001154664534000232602ulatencyustar00rootroot00000000000000poelzi-ulatencyd-55515a9/client/ulatency000077500000000000000000001061771154664534000203360ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import sys try: sys.setdefaultencoding('utf-8') except AttributeError: pass PY3 = sys.version_info[0] != 2 import os import re try: import dbus except: sys.stderr.write("warning: dbus not available\n") import subprocess from optparse import OptionParser from signal import signal, SIGINT, SIG_DFL from functools import partial signal(SIGINT, SIG_DFL) VERSION = '0.2' DEFAULT_TREE = "cpu" CGROUP_PATHS = {} CGROUP_SUBSYS = [] CONFIG_SINGLE_TASK = "single_task" def prnt(arg): sys.stdout.write(PY3 and str(arg) or unicode(arg).encode("utf-8")) sys.stdout.write("\n") if PY3: u = str else: def tounicode(x): return unicode(x, "unicode_escape") u = tounicode fp = open("/proc/cgroups", "r") for line in fp: if line[0] == "#": continue chunks = line.split() CGROUP_SUBSYS.append(chunks[0]) fp = open("/proc/mounts", "r") for line in fp: chunks = line.split() if chunks[2] == "cgroup": opts = chunks[3].split(",") for opt in opts: if opt in CGROUP_SUBSYS: CGROUP_PATHS[opt] = chunks[1] class drug(): def __init__(self, **kwargs): for k in kwargs: setattr(self, k, kwargs[k]) style = drug(**dict( default = "\033[m", # styles bold = "\033[1m", underline = "\033[4m", blink = "\033[5m", reverse = "\033[7m", concealed = "\033[8m", # font colors black = "\033[30m", red = "\033[31m", green = "\033[32m", yellow = "\033[33m", blue = "\033[34m", magenta = "\033[35m", cyan = "\033[36m", white = "\033[37m", # background colors on_black = "\033[40m", on_red = "\033[41m", on_green = "\033[42m", on_yellow = "\033[43m", on_blue = "\033[44m", on_magenta = "\033[45m", on_cyan = "\033[46m", on_white = "\033[47m")) def terminal_size(): def ioctl_GWINSZ(fd): try: import fcntl, termios, struct, os cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) except: return None return cr cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) if not cr: try: fd = os.open(os.ctermid(), os.O_RDONLY) cr = ioctl_GWINSZ(fd) os.close(fd) except: pass if not cr: try: cr = (os.environment['LINES'], os.environment['COLUMNS']) except: cr = (25, 80) return int(cr[1]), int(cr[0]) TERM_WIDTH = terminal_size()[0] def cap_line(line, width, utf): if len(line) < width: return line else: if utf: return line[:width-1] + u("\u2026") else: return line[:width-3] + u("...") def list_processes(): rv = [] for line in os.listdir("/proc"): if re.match("\d+", line): rv.append(line) return rv def get_process_data(pid): try: path = os.path.join("/proc", pid, "status") if not os.path.exists(path): return None fp = open(path) except OSError: return None rv = {} for line in fp.readlines(): chunks = line.split(":\t") rv[chunks[0].strip().lower()] = chunks[1].strip() fp.close() try: path = os.path.join("/proc", pid, "cmdline") if not os.path.exists(path): return None fp = open(path) rv["cmdline"] = fp.read().split("\0")[:-1] fp.close() except OSError: return None return rv def flags_to_string(flags): out = "" for flag in flags: if isinstance(flag, (tuple, list)): flag = flag[1] out += "[%s" %flag["name"] for k,v in flag.iteritems(): if k == "name": continue if isinstance(v, (int, long)): if v != 0: out += " %s=%s" %(unicode(k), unicode(v)) elif isinstance(v, (str, unicode)): if v != "": out += " %s=%s" %(unicode(k), unicode(v)) else: out += " %s=%s" %(unicode(k), unicode(v)) out += "]" return out def print_process(pid, flags=None): data = get_process_data(pid) out = "%6s %s" %(data["pid"], data["name"].ljust(25)) if flags: out += flags_to_string(flags) prnt(out) class Session: def __init__(self, dbus_loop=None, silent=True): self.dbus_loop = dbus_loop self.bus = dbus.SystemBus(mainloop=self.dbus_loop) self.silent = silent self.sets = { 'config':self.set_config, 'active':self.set_active, } self.gets = { 'config':self.get_config, 'desc':self.get_desc, 'systemflags':self.get_systemflags, 'activelistlength':self.get_activelistlength, } def init_system(self): if hasattr(self, 'system'): return True try: self.system = self.bus.get_object( 'org.quamquam.ulatencyd', '/org/quamquam/ulatencyd/System', follow_name_owner_changes=bool(self.dbus_loop)) except dbus.exceptions.DBusException as e: if self.silent: return False raise return True def test_connection(self): try: self.system = self.bus.get_object( 'org.quamquam.ulatencyd', '/org/quamquam/ulatencyd/System') return True except dbus.exceptions.DBusException as e: print(e) def init_user(self): if hasattr(self, 'user'): return True try: self.user = self.bus.get_object( 'org.quamquam.ulatencyd', '/org/quamquam/ulatencyd/User', follow_name_owner_changes=bool(self.dbus_loop)) except dbus.exceptions.DBusException as e: if self.silent: return False raise return True def config_list(self): if not self.init_system(): return [] system = dbus.Interface(self.system, 'org.quamquam.ulatencyd.System') try: return list(map(unicode, system.listSchedulerConfigs())) except dbus.exceptions.DBusException as e: prnt(e) if self.silent: return None raise def set(self, key, *value): if key not in self.sets: prnt("error: key '{0}' not available".format(key)) sys.exit(1) return self.sets[key](*value) def set_config(self, config=None, timeout=600): if not self.init_system(): return False configs = self.config_list() if config is None: return ",".join(configs) system = dbus.Interface(self.system, 'org.quamquam.ulatencyd.System') try: return system.setSchedulerConfig(config, timeout=timeout) except dbus.exceptions.DBusException as e: prnt(e) if self.silent: return None raise def set_active(self, pid=None): if pid is None: prnt("error: no pid given") sys.exit(1) if not self.init_user(): return False user = dbus.Interface(self.user, 'org.quamquam.ulatencyd.User') try: return user.setActive(pid) except dbus.exceptions.DBusException as e: prnt(e) if self.silent: return None raise def get(self, key, *value): if key not in self.gets: prnt("error: key '{0}' not available".format(key)) sys.exit(1) return self.gets[key](*value) def get_config(self): if not self.init_system(): return "" properties=dbus.Interface(self.system,'org.freedesktop.DBus.Properties') try: return unicode(properties.Get( 'org.quamquam.ulatencyd.System', 'config')) except dbus.exceptions.DBusException as e: prnt(e) if self.silent: return None raise def get_desc(self, config=None): if not self.init_system(): return None configs = self.config_list() if config is None: return ",".join(configs) if config not in configs: prnt("error: config '{0}' not available".format(config)) sys.exit(1) system = dbus.Interface(self.system, 'org.quamquam.ulatencyd.System') try: return system.getSchedulerConfigDescription(config) except dbus.exceptions.DBusException as e: prnt(e) if self.silent: return None raise def get_systemflags(self): if not self.init_system(): return [] system = dbus.Interface(self.system, 'org.quamquam.ulatencyd.System') try: return list(map(unicode, system.listSystemFlags())) except dbus.exceptions.DBusException as e: prnt(e) if self.silent: return None raise def get_processflags(self, pid=None, recrusive=True): if not self.init_system(): return [] if pid is None: prnt("error: no pid given") sys.exit(1) pid = int(pid) system = dbus.Interface(self.system, 'org.quamquam.ulatencyd.System') try: rv = [] data = system.listFlags(pid, recrusive) for flag in data: rv.append([flag[0],dict((unicode(x),v) for x,v in flag[1].iteritems())]) return rv except dbus.exceptions.DBusException as e: if self.silent: return [] raise def get_activelistlength(self): if not self.init_user(): return None properties = dbus.Interface(self.user,'org.freedesktop.DBus.Properties') try: return unicode(properties.Get( 'org.quamquam.ulatencyd.User', 'activeListLength')) except dbus.exceptions.DBusException as e: prnt(e) if self.silent: return [] raise def add_flag(self, pid, **values): if not self.init_system(): return "" system = dbus.Interface(self.system, 'org.quamquam.ulatencyd.System') try: rv = [] data = system.addFlag(pid, 0, values["name"], unicode(values.get("reason", "")), long(values.get("timeout", 0)), int(values.get("priority", 0)), long(values.get("value", 0)), long(values.get("treshold", 0)), bool(values.get("inherit", False))) except dbus.exceptions.DBusException as e: prnt(e) if self.silent: return False raise e class Tree(object): """Displays a cgroup tree""" boxes = { True: drug( start = u("\u2500\u252c\u00bb"), branch = u("\u251c"), last = u("\u2514"), end = u("\u00ab"), subtree = u("\u2502"), file = u(" ") ), False: drug( start = u("# "), branch = u("+-"), last = u("+-"), end = u(" #"), subtree = u("|"), file = u(" ") ), } def __init__(self, tree=DEFAULT_TREE, processes=True, show_all=True, utf = True, color = False, flags = False, cmdline = False): self.tree = tree self.processes = processes self.show_all = show_all self.utf = utf self.flags = flags self.cmdline = cmdline if flags: self.session = Session() if color: self.color = drug( process = u("{0.yellow}{1}{0.default} {2}"), subtree = u("{0.bold}{0.black}{1}{0.default}"), tree = u("{0.bold}{0.black}{1}{2}{3}{0.default}{0.green}") + u("{5}{0.bold}{0.black}{4}{0.default}"), file = u("{0.bold}{0.black}{1}{2}{3}{0.default}{4}{5}{0.magenta}{6}{0.default}") ) else: self.color = drug( process = u("{1} {2}"), subtree = u("{1}"), tree = u("{1}{2}{3}{5}{4}"), file = u("{1}{2}{3}{4}{5}{6}") ) path = self.get_path() if path: self.cache = drug(children = [], path = path, state = None, name = os.path.basename(os.path.abspath(path))) else: prnt("error: tree does not exist") return None def processes_(self, b): self.processes = b def show_all_(self, b): self.show_all = b def get_process(self, pid): data = get_process_data(pid) if not data: return None if not self.show_all and data and data["pid"] != data["tgid"]: return None if self.cmdline: cmdline = u(" ").join(data.get("cmdline", [])) return data["pid"], cmdline or data.get("name", "") return data["pid"], data.get("name", "") def generate(self, dir): tree = drug(children = [], path = dir, state = "", name = os.path.basename(os.path.abspath(dir))) files = [] if self.processes and os.path.exists(os.path.join(dir, "tasks")): try: fp = open(os.path.join(dir, "tasks"), "r") for line in fp.readlines(): proc = self.get_process(line.strip()) if proc: files.append(proc) fp.close() except IOError: pass for x in os.listdir(dir): if os.path.isdir(dir + os.sep + x): files.append((None, x)) for pid, fname in files: path = os.path.join(dir, fname) if os.path.isdir(path): tree.children.append(self.generate(path)) else: tree.children.append(drug( state = "", pid = pid, name = fname, path = path)) return tree def get_path(self): path = CGROUP_PATHS.get(self.tree, None) if not path or not os.path.exists(path): return None return path def update(self): path = self.get_path() if not path: return tree = self.generate(path) def rec(old, new): if old.state is None: old.state = "new" else: old.state = "" if new.name != old.name: old.name = new.name if not old.state: old.state = "update" if hasattr(old, 'children'): children = dict([(child.name, child) for child in new.children]) doomed = [] for n, child in enumerate(old.children): if child.state == "delete": doomed.append(n) continue if child.name in children: rec(child, children[child.name]) del children[child.name] else: child.state = "delete" for n in sorted(doomed, reverse = True): del old.children[n] for child in children.values(): old.children.append(child) child.state = "new" rec(self.cache, tree) return self.cache def display(self): path = self.get_path() if not path: return None prnt(path) if not os.path.exists(path): prnt("error: tree does not exist") sys.exit(1) def rec(branch, padding, islast=None): if hasattr(branch, 'pid'): if self.flags: fpad = " "*(max(28-len(branch.name)-len(str(branch.pid)), 3)) flags = flags_to_string(self.session.get_processflags(branch.pid)) else: flags = "" fpad = "" process = self.color.process.format( style, branch.pid, cap_line(branch.name, TERM_WIDTH - len(padding) - 13 - len(flags), self.utf)) prnt(self.color.file.format(style, padding, islast or self.boxes[self.utf].branch, self.boxes[self.utf].file, process, fpad, flags)) return prnt(self.color.tree.format(style, padding, islast or self.boxes[self.utf].branch, self.boxes[self.utf].start, self.boxes[self.utf].end, branch.name)) count = 0 for child in branch.children: count += 1 last = None p = padding if count == len(branch.children): last = self.boxes[self.utf].last if os.path.isdir(branch.path): if islast: p = p + u(" ") else: p = p + self.boxes[self.utf].subtree p = p + u(" ") else: if islast: p = p + u(" ") else: p = p + self.boxes[self.utf].subtree if os.path.isdir(branch.path): p = p + u(" ") rec(child, p, last) rec(self.generate(path), u(""), self.boxes[self.utf].last) def clear(self): """clears a cgroup mountpoint so it can be removed""" path = self.get_path() if not path: prnt("subsystem not mounted") return try: rootfp = open(os.path.join(path, "tasks"), "w") except IOError: prnt("need to run as root") sys.exit(1) def move(pid): rootfp.write("%s\n" %pid) try: rootfp.flush() except IOError: pass def clear(root): prnt("clear %s" %root) try: fp = open(os.path.join(root, "tasks"), "r") for line in fp: move(line.strip()) except IOError: pass for root, dirs, files in os.walk(path, topdown=False): if root == path: continue for retry in xrange(5): clear(root) try: os.rmdir(root) break except OSError: pass else: prnt("could not clear %s" %root) prnt("cleared %s" %path) class Gui: def __init__(self): pass def run(self): return 0 class QtGui(Gui): def __init__(self): try: from PyQt4.Qt import Qt, QApplication, QSystemTrayIcon, \ QIcon, QPixmap, QMenu, QMainWindow, \ QTreeWidget, QTreeWidgetItem, QTimer,\ QColor, QMenuBar, QKeySequence, QSettings, \ QActionGroup except ImportError: prnt("python qt4 bindings missing.") sys.exit(1) if dbus: # if DBusQtMainLoop is not available, we simply stop to work # correctly when ulatencyd is restarted try: from dbus.mainloop.qt import DBusQtMainLoop self.dbus_loop = DBusQtMainLoop() except ImportError as e: self.dbus_loop = None class MainWindow(QMainWindow): def __init__(self, app): self.app = app super(MainWindow, self).__init__() def closeEvent(self, event): self.app.toggle_window(event) event.ignore() self.QColor = QColor self.QTreeWidgetItem = QTreeWidgetItem self.app = app = QApplication(sys.argv) self.session = Session(dbus_loop=self.dbus_loop) self.settings = QSettings("ulatencyd", "qtgui") self.first_fill = True self.tree = Tree(show_all = False) icon = QIcon(QPixmap(self.get_logo())) app.trayicon = QSystemTrayIcon(icon, app) app.trayicon.show() self.window = win = MainWindow(self) mb = QMenuBar(win) win.setMenuBar(mb) menu = mb.addMenu("client") c = menu.addMenu("set config") c.aboutToShow.connect(partial(self.update_active, c)) menu.addSeparator() quit = menu.addAction("quit") quit.setShortcut(QKeySequence("Ctrl+Q")) quit.triggered.connect(app.quit) menu = mb.addMenu("settings") tree_menu = menu.addMenu("tree") self.config_action = {} self.config_tree = {} def connect(con): return lambda: self.switch_tree(con) tree_group = QActionGroup(tree_menu) for subsys in CGROUP_PATHS: prnt(subsys) self.config_tree[subsys] = c = tree_menu.addAction(subsys) c.setCheckable(True) c.setActionGroup(tree_group) c.setChecked(self.tree.tree == subsys) c.triggered.connect(connect(subsys)) processes = menu.addAction("show processes") processes.setCheckable(True) processes.setChecked(self.tree.processes) processes.triggered.connect(lambda: self.tree.processes_(processes.isChecked()) or self.update_tree()) show_all = menu.addAction("show threads") show_all.setCheckable(True) show_all.setChecked(self.tree.show_all) show_all.triggered.connect(lambda: self.tree.show_all_(show_all.isChecked()) or self.update_tree()) win.ui = ui = drug( treeview = QTreeWidget(win), timer = QTimer(), ) win.setCentralWidget(ui.treeview) ui.treeview.setHeaderLabels(["process", "pid"]) ui.treeview.setAlternatingRowColors(True) ui.treeview.indexOfChild = ui.treeview.indexOfTopLevelItem ui.treeview.child = ui.treeview.topLevelItem ui.timer.setInterval(5000) ui.timer.timeout.connect(self.update_tree) self.cur = self.session.get_config() self.app.menu = QMenu() self.config_menu = menu = self.app.menu.addMenu("set config") menu.aboutToShow.connect(partial(self.update_active, menu)) menu = self.app.menu menu.addSeparator() quit = menu.addAction("Quit") self.doomed_entries = [] self.update_tree() app.trayicon.activated.connect(self.toggle_window) app.trayicon.setContextMenu(menu) quit.triggered.connect(self.quit) prnt("gui started.") def update_active(self, menu): self.cur = self.session.get_config() configs = self.session.config_list() self.config_action = {} menu.clear() if not configs: return def connect(con): return lambda: self.switch_config(con) for config in configs: self.config_action[config] = c = menu.addAction(config) c.setCheckable(True) c.setChecked(self.cur == config) c.setToolTip(unicode(self.session.get_desc(config))) c.triggered.connect(connect(config)) def switch_tree(self, tree): self.tree = Tree(tree=tree, show_all = self.tree.show_all) self.doomed_entries = [] self.window.ui.treeview.clear() self.update_tree() def quit(self): if self.window.isVisible(): self.settings.setValue("main_window_geometry", self.window.saveGeometry()); self.settings.setValue("main_window_state", self.window.saveState()); self.settings.sync() self.app.quit() def run(self): return self.app.exec_() def toggle_window(self, reason): if reason != self.app.trayicon.Context: b = not self.window.isVisible() if b: self.update_tree() self.window.restoreGeometry(self.settings.value("main_window_geometry").toByteArray()); self.window.restoreState(self.settings.value("main_window_state").toByteArray()); self.window.ui.timer.start() else: self.settings.setValue("main_window_geometry", self.window.saveGeometry()); self.settings.setValue("main_window_state", self.window.saveState()); self.window.ui.timer.stop() self.window.setVisible(b) def update_tree(self): QTreeWidgetItem = self.QTreeWidgetItem tree = self.tree.update() if not tree: return for parent, child in self.doomed_entries: i = parent.indexOfChild(child) parent.takeChild(i) self.doomed_entries = [] def add(branch, parent): background = self.QColor(0, 200, 0, 100) pid = u("") if hasattr(branch, 'pid'): pid = branch.pid branch.item = item = QTreeWidgetItem(parent, [branch.name, pid]) item.setBackgroundColor(0, background) item.setBackgroundColor(1, background) item.setExpanded(True) if hasattr(branch, 'children'): for child in branch.children: if hasattr(child, 'children'): add(child, item) for child in branch.children: if not hasattr(child, 'children'): add(child, item) def update(branch, parent): background = self.QColor("transparent") dead = self.QColor(200, 0, 0, 100) if branch.state == "update": i = parent.indexOfChild(branch.item) if i == -1: branch.state = "delete" # FIXME prnt("FIXME") else: parent.child(i).setText(0, branch.name) branch.item.setBackgroundColor(0, background) branch.item.setBackgroundColor(1, background) if hasattr(branch, 'children'): for child in branch.children: if child.state == "delete": self.doomed_entries.append((branch.item, child.item)) child.item.setBackgroundColor(0, dead) child.item.setBackgroundColor(1, dead) elif child.state == "new": add(child, branch.item) else: update(child, branch.item) if tree.state == "new": add(tree, self.window.ui.treeview) else: update(tree, self.window.ui.treeview) if self.first_fill: self.window.ui.treeview.resizeColumnToContents(0) self.first_fill = False def switch_config(self, new): if self.session.set_config(new, timeout=60*60*24): self.config_action[self.cur].setChecked(False) self.config_action[new].setChecked(True) def get_logo(self): header = ["16 16 5 1"] chars = ["#","x","o","."] color = ["black", "gray25", "gray50", "white"] background = [ " ", " xxxx ", " xx####xx ", " x##xxxx##x ", " x#xx xx#x ", " x#x x#x ", " x#x x#x ", " x#x x#x ", " x#x x#x ", " x#x x#x ", " x#x x#x ", " x#xx xx#x ", " x##xxxx##x ", " xx####xx ", " xxxx ", " "] foreground = [ "o o", "o o", "o. .o", "o....o", "oooo.o", "o.o ", "ooo "] color = [" c None"] + [char+" c "+col for char,col in zip(chars,color)] for y, line in enumerate(foreground): for x, char in enumerate(line): if char != " ": s = background[y+4] background[y+4] = s[:x+5] + char + s[x+6:] return header + color + background def command(args, config=CONFIG_SINGLE_TASK, as_root=False): pid = os.getpid() uid = None if os.getuid() == 0: uid = (not as_root and int(os.environ.get("SUDO_UID", 0))) or os.getuid() #prnt("you need to run: sudo comand") #sys.exit(1) session = Session() old_config = session.get_config() if not old_config: prnt("ulatency: could not contact dbus daemon") session.test_connection() sys.exit(1) prnt("ulatency: save old config: %s" %old_config) session.add_flag(pid, name="cmd.config.%s" %config, inherit=True) if not session.set_config(config, timeout=60*60*24): prnt("ulatency: abort by user") sys.exit(2) prnt("ulatency: execute '%s'" %" ".join(args)) def int_handler(ignum, frame): prnt("-" * 30) prnt("received interrupt signal") prnt("ulatency: restore config:%s" %old_config) session.set_config(old_config, timeout=60*60*24) sys.exit(1) import signal signal.signal(signal.SIGINT, int_handler) if uid is not None: prnt("Execute as id: %s" %uid) old_uid = os.getuid() os.seteuid(uid) try: prnt("-"*30) retcode = subprocess.call(args) prnt("-"*30) if retcode < 0: print >>sys.stderr, "Child was terminated by signal", -retcode else: print >>sys.stderr, "Child returned", retcode except OSError, e: print >>sys.stderr, "Execution failed:", e except KeyboardInterrupt, e: print "kbi" if uid is not None: os.seteuid(old_uid) prnt("ulatency: restore config %s" %old_config) session.set_config(old_config, timeout=60*60*24) EPILOG="""commands: tree [cgroup] show a cgroup tree get [key] get daemon setting set [key value] set daemon value flags [process] list flags of a process or all sysflags list system flags mounts lists all mounted cgroups clear cgroup|ALL clears a cgroup run [options] -- [COMMAND] run task under different scheduler config run-CONFIG [COMMAND] run task under scheduler CONFIG """ def main(): parser = OptionParser( usage = "usage: %prog [options] [command] [key [value]]", version="%prog " + VERSION, epilog="commands for a list of commands" ) parser.add_option("--config-list", dest="list", action="store_true", default=False, help="list system configs") parser.add_option("-s", "--set", dest="set", action="store_true", default=False, help="set key value pair") parser.add_option("-g", "--get", dest="get", action="store_true", default=False, help="get value by key") parser.add_option("--gui", dest="gui", action="store_true", default=False, help="enable gui") parser.add_option("--no-utf", dest="utf", action="store_false", default=True, help="disables utf8 for tree view") parser.add_option("--no-color", dest="color", action="store_false", default=True, help="disables color") parser.add_option("--all", dest="all", action="store_true", default=False, help="show complete tree") parser.add_option("--flags", dest="flags", action="store_true", default=False, help="show flags on tree output") parser.add_option("--cmd", dest="cmdline", action="store_true", default=False, help="show cmdline") parser.add_option("--as-root", dest="as_root", action="store_true", default=False, help="run command as root") parser.add_option("--no-processes", dest="processes", action="store_false", default=True, help="hide all processes") parser.add_option("--scheduler", dest="scheduler", action="store", default=CONFIG_SINGLE_TASK, help="scheduler config to use") prog = os.path.basename(sys.argv[0]) if prog[:4] in ("run-", "run_"): if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help"): prnt("Usage: %s COMMAND" %prog) prnt("executes program under scheduler config: %s" %prog[4:].replace("-", "_").upper()) sys.exit(1) command(sys.argv[1:], config=prog[4:].replace("-", "_")) return options, args = parser.parse_args() # if we run without any arguments and it doesn't look like we are in a # terminal, we start the gui if prog == "ulatency-gui": options.gui = True if options.gui: sys.exit(QtGui().run()) if options.list: prnt("\n".join(Session().config_list())) sys.exit(0) if options.set or args and args[0] == 'set': if len(args[1:]): rv = Session().set(*args[1:]) prnt(rv) if rv: sys.exit(0) sys.exit(2) else: prnt(",".join(Session().sets.keys())) return if options.get or args and args[0] == 'get': if len(args[1:]): rv = Session().get(*args[1:]) prnt(rv) if rv: sys.exit(0) sys.exit(2) else: prnt(",".join(Session().gets.keys())) return if args and args[0] == 'run': command(args[1:], options.scheduler, options.as_root) return if args and args[0][:4] == "run-": command(args[1:], config=args[0][4:].replace("-", "_")) return if not args or args[0] == 'tree': kwargs = dict(utf = options.utf, color = options.color, show_all = options.all, processes = options.processes, flags = options.flags, cmdline = options.cmdline) if len(args) > 1: tree = Tree(tree=args[1], **kwargs) else: tree = Tree(**kwargs) tree.display() return if args[0] == 'commands': parser.print_usage() prnt(EPILOG) return if args[0] == 'mounts': for v,i in CGROUP_PATHS.iteritems(): prnt("%s %s" %(v.ljust(10, " "), i)) return if args[0] == 'clear': if not len(args) == 2: prnt("not enough arguments") sys.exit(1) if args[1] == "ALL": for tree in CGROUP_PATHS.iterkeys(): ct = Tree(tree=tree) ct.clear() else: ct = Tree(tree=args[1]) ct.clear() return if args[0] == 'flags': session = Session() if len(args) > 1: print_process(args[1], flags=session.get_processflags(args[1])) else: for proc in list_processes(): print_process(proc, flags=session.get_processflags(proc)) return if args[0] == 'sysflags': session = Session() prnt(flags_to_string(session.get_systemflags())) return parser.print_usage() prnt(EPILOG) if __name__ == "__main__": main() poelzi-ulatencyd-55515a9/client/ulatency-gui000077700000000000000000000000001154664534000226432ulatencyustar00rootroot00000000000000poelzi-ulatencyd-55515a9/conf/000077500000000000000000000000001154664534000162175ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/conf/cgroups.conf000066400000000000000000000014221154664534000205470ustar00rootroot00000000000000-- this is a lua file CGROUP_ROOT = "/sys/fs/cgroup" -- /sys/fs/cgroup is not available on older kernels, we need to change that -- in those cases fp = io.open(CGROUP_ROOT, "r") if not fp then CGROUP_ROOT = "/dev/cgroup" else fp:close() end -- edit the below only when you know what you are doing -- describes which subsystems are mounted under -- which toplevel path CGROUP_MOUNTPOINTS = { cpu={"cpu"}, memory={"memory"}, blkio={"blkio"}, cpuset={"cpuset"} } -- FIXME we need some better solution for that :-/ -- cpuset, very powerfull, but can't create a group with unset cpus or mems CGROUP_DEFAULT = { cpu={["notify_on_release"] = "1",}, memory={["notify_on_release"] = "1",}, io={["notify_on_release"] = "1",}, cpuset={["notify_on_release"] = "1",}, } poelzi-ulatencyd-55515a9/conf/org.quamquam.ulatencyd.conf000066400000000000000000000017311154664534000234740ustar00rootroot00000000000000 poelzi-ulatencyd-55515a9/conf/org.quamquam.ulatencyd.policy000066400000000000000000000013471154664534000240510ustar00rootroot00000000000000 ulatency http://ulatencyd.quamquam.org/ Set scheduler config Authentication is required to set scheduler config auth_admin_keep auth_admin_keep auth_admin_keep poelzi-ulatencyd-55515a9/conf/scheduler/000077500000000000000000000000001154664534000201755ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/conf/scheduler/00-common.lua000066400000000000000000000020301154664534000224000ustar00rootroot00000000000000--! @brief penelizes function save_io_prio(proc, prio, class) if not proc.data.io_prio then proc.data.io_prio = {proc:get_ioprio()} end proc:set_ioprio(prio, class) end function restore_io_prio(proc) if proc.data.io_prio then local pr = proc.data.io_prio proc:set_ioprio(pr[1], pr[2]) end end function merge_config(template, new_values) rv = {} if new_values.pre then for k,entry in ipairs(new_values.pre) do rv[#rv+1] = entry end end if new_values.replace then for k,entry in pairs(template) do rv[k] = entry for nkey,nvalue in pairs(new_values.replace) do if nvalue.name == entry.name then rv[k] = nvalue end end end end if new_values.post then for k,entry in ipairs(new_values.post) do rv[#rv+1] = entry end end return rv end -- fallback mappings SCHEDULER_MAPPING_DEFAULT = {} SCHEDULER_MAPPING_DEFAULT["cpuset"] = { { name = "", cgroups_name = "", check = function(proc) return true end, }, } poelzi-ulatencyd-55515a9/conf/scheduler/20-desktop.lua000066400000000000000000000241061154664534000225730ustar00rootroot00000000000000--[[ Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. License: GNU General Public License 3 or later ]]-- SCHEDULER_MAPPING_DESKTOP = { info = { description = "a good default desktop configuration" } } -- cpu & memory configuration SCHEDULER_MAPPING_DESKTOP["cpu"] = { { name = "rt_tasks", cgroups_name = "rt_tasks", param = { ["cpu.shares"]="3048", ["?cpu.rt_runtime_us"] = "949500" }, check = function(proc) local rv = proc.received_rt or check_label({"sched.rt"}, proc) or proc.vm_size == 0 return rv end, }, { name = "system_essential", cgroups_name = "sys_essential", param = { ["cpu.shares"]="3048" }, label = { "system.essential" } }, { name = "user", cgroups_name = "usr_${euid}", check = function(proc) return ( proc.euid > 999 ) end, param = { ["cpu.shares"]="3048", ["?cpu.rt_runtime_us"] = "100" }, children = { { name = "poison", param = { ["cpu.shares"]="10" }, label = { "user.poison" }, cgroups_name = "psn_${pid}", }, { name = "poison_group", param = { ["cpu.shares"]="300" }, cgroups_name = "pgr_${pgrp}", check = function(proc) local rv = ulatency.find_flag(ulatency.list_flags(), {name = "user.poison.group", value = proc.pgrp}) return rv ~= nil end, }, { name = "bg_high", param = { ["cpu.shares"]="1000", ["?cpu.rt_runtime_us"] = "1"}, label = { "user.bg_high" }, }, { name = "media", param = { ["cpu.shares"]="2600", ["?cpu.rt_runtime_us"] = "1"}, label = { "user.media" }, }, { name = "ui", param = { ["cpu.shares"]="2000", ["?cpu.rt_runtime_us"] = "1"}, label = { "user.ui" } }, { name = "active", param = { ["cpu.shares"]="1500", ["?cpu.rt_runtime_us"] = "1"}, check = function(proc) return proc.is_active end }, { name = "idle", param = { ["cpu.shares"]="200"}, label = { "user.idle" }, }, { name = "group", param = { ["cpu.shares"]="600", ["?cpu.rt_runtime_us"] = "1"}, cgroups_name = "grp_${pgrp}", check = function(proc) return true end, }, }, }, { name = "system", cgroups_name = "sys_idle", label = { "daemon.idle" }, param = { ["cpu.shares"]="1"}, }, { name = "system", cgroups_name = "sys_bg", label = { "daemon.bg" }, param = { ["cpu.shares"]="600"}, }, { name = "system", cgroups_name = "sys_daemon", check = function(proc) -- don't put kernel threads into a cgroup return (proc.ppid ~= 0 or proc.pid == 1) end, param = { ["cpu.shares"]="800", ["?cpu.rt_runtime_us"] = "1"}, } } SCHEDULER_MAPPING_DESKTOP["memory"] = { { name = "system_essential", cgroups_name = "sys_essential", param = { ["?memory.swappiness"] = "0" }, label = { "system.essential" } }, { name = "user", cgroups_name = "usr_${euid}", check = function(proc) return ( proc.euid > 999 ) end, children = { { name = "poison", label = { "user.poison" }, cgroups_name = "psn_${pid}", adjust_new = function(cgroup, proc) cgroup:add_task(proc.pid) cgroup:commit() bytes = cgroup:get_value("memory.usage_in_bytes") if not bytes then ulatency.log_warning("can't access memory subsystem") return end bytes = math.floor(bytes*(tonumber(ulatency.get_config("memory", "process_downsize")) or 0.95)) cgroup:set_value("memory.soft_limit_in_bytes", bytes) -- we use soft limit, but without limit we can't set the memsw limit local max_rss = math.floor(num_or_percent(ulatency.get_config("memory", "max_rss"), Scheduler.meminfo.kb_main_total, false) * 1024) local total_limit = math.max(math.floor(num_or_percent(ulatency.get_config("memory", "total_limit"), Scheduler.meminfo.kb_main_total + Scheduler.meminfo.kb_swap_total) * 1024), max_rss) ulatency.log_info("memory container created: ".. cgroup.name .. " max_rss:" .. tostring(max_rss) .. " max_total:" .. tostring(total_limit) .. " soft_limit:".. tostring(bytes)) cgroup:set_value("memory.limit_in_bytes", max_rss) cgroup:set_value("memory.memsw.limit_in_bytes", total_limit, max_rss) cgroup:commit() end }, { name = "poison_group", cgroups_name = "pgr_${pgrp}", check = function(proc) local rv = ulatency.find_flag(ulatency.list_flags(), {name = "user.poison.group", value = proc.pgrp}) return rv ~= nil end, adjust_new = function(cgroup, proc) local flag = ulatency.find_flag(ulatency.list_flags(), { name = "user.poison.group", value = proc.pgrp }) cgroup:add_task(proc.pid) cgroup:set_value("memory.soft_limit_in_bytes", math.ceil(flag.threshold*(tonumber(ulatency.get_config("memory", "group_downsize") or 0.95)))) -- we use soft limit, but without limit we can't set the memsw limit local max_rss = math.floor(num_or_percent(ulatency.get_config("memory", "max_rss"), Scheduler.meminfo.kb_main_total, false) * 1024) local total_limit = math.max(math.floor(num_or_percent(ulatency.get_config("memory", "total_limit"), Scheduler.meminfo.kb_main_total + Scheduler.meminfo.kb_swap_total) * 1024), max_rss) ulatency.log_info("memory container created: ".. cgroup.name .. " max_rss:" .. tostring(max_rss) .. " max_total:" .. tostring(total_limit) .. " soft_limit:".. tostring(bytes)) cgroup:set_value("memory.limit_in_bytes", max_rss) cgroup:set_value("memory.memsw.limit_in_bytes", total_limit, max_rss) cgroup:commit() end }, { name = "bg_high", param = { ["?memory.swappiness"] = "20" }, label = { "user.bg_high" }, }, { name = "media", param = { ["?memory.swappiness"] = "40" }, label = { "user.media" }, }, { name = "ui", param = { ["?memory.swappiness"] = "0" }, label = { "user.ui" } }, { name = "active", param = { ["?memory.swappiness"] = "0" }, check = function(proc) return proc.is_active end }, { name = "idle", param = { ["?memory.swappiness"] = "100" }, }, { name = "group", param = {["?memory.swappiness"] = "60" }, cgroups_name = "default", check = function(proc) return true end, }, }, }, { name = "system", cgroups_name = "sys_idle", label = { "daemon.idle" }, param = { ["?memory.swappiness"] = "100" }, }, { name = "system", cgroups_name = "sys_bg", label = { "daemon.bg" }, param = { ["?memory.swappiness"] = "100" }, }, { name = "system", cgroups_name = "sys_daemon", check = function(proc) -- don't put kernel threads into a cgroup return (proc.ppid ~= 0 or proc.pid == 1) end, param = { ["?memory.swappiness"] = "70" }, }, { name = "kernel", cgroups_name = "", check = function(proc) return (proc.vm_size == 0) end }, } -- io configuration. blkio does not support hirarchies SCHEDULER_MAPPING_DESKTOP["blkio"] = { { name = "poison", label = { "user.poison", "user.poison.group" }, cgroups_name = "psn_${pgrp}", param = { ["blkio.weight"]="1" }, adjust = function(cgroup, proc) save_io_prio(proc, 7, ulatency.IOPRIO_CLASS_IDLE) end, }, { name = "active", cgroups_name = "usr_${euid}_active", param = { ["blkio.weight"]="1000" }, check = function(proc) return proc.is_active end, adjust = function(cgroup, proc) save_io_prio(proc, 3, ulatency.IOPRIO_CLASS_BE) end, }, { name = "ui", label = { "user.ui" }, adjust = function(cgroup, proc) save_io_prio(proc, 2, ulatency.IOPRIO_CLASS_BE) end, }, { name = "idle", param = { ["blkio.weight"]="1" }, label = { "daemon.idle", "user.idle" }, adjust = function(cgroup, proc) save_io_prio(proc, 5, ulatency.IOPRIO_CLASS_IDLE) end, }, { name = "media", param = { ["blkio.weight"]="300" }, cgroups_name = "grp_${pgrp}", label = { "user.media"}, adjust = function(cgroup, proc) save_io_prio(proc, 7, ulatency.IOPRIO_CLASS_RT) end, }, { name = "group", param = { ["blkio.weight"]="300" }, cgroups_name = "grp_${pgrp}", check = function(proc) return proc.pgrp > 0 end, adjust = function(cgroup, proc) restore_io_prio(proc) end, }, { name = "kernel", cgroups_name = "", check = function(proc) return (proc.vm_size == 0) end }, } poelzi-ulatencyd-55515a9/conf/scheduler/30-game.lua000066400000000000000000000111551154664534000220340ustar00rootroot00000000000000--[[ Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. License: GNU General Public License 3 or later ]]-- SCHEDULER_MAPPING_GAME = { info = { description = "scheduler for games", }, } -- cpu & memory configuration SCHEDULER_MAPPING_GAME["cpu"] = { { name = "rt_tasks", cgroups_name = "rt_tasks", param = { ["cpu.shares"]="3048", ["?cpu.rt_runtime_us"] = "949500" }, check = function(proc) local rv = proc.received_rt or check_label({"sched.rt"}, proc) or proc.vm_size == 0 return rv end, }, { name = "system_essential", cgroups_name = "sys_essential", param = { ["cpu.shares"]="3048" }, label = { "system.essential" } }, { name = "user", cgroups_name = "usr_${euid}", check = function(proc) return ( proc.euid > 999 ) end, param = { ["cpu.shares"]="3048", ["?cpu.rt_runtime_us"] = "100" }, children = { { name = "ui", param = { ["cpu.shares"]="200", ["?cpu.rt_runtime_us"] = "1"}, label = { "user.ui" } }, { name = "media", param = { ["cpu.shares"]="1000", ["?cpu.rt_runtime_us"] = "1"}, label = { "user.media" }, }, { name = "game", param = { ["cpu.shares"]="3048", ["?cpu.rt_runtime_us"] = "1"}, check = function(proc) return proc.active_pos == 1 or check_label({ "cmd.config.game", "user.game" }, proc) end }, { name = "group", param = { ["cpu.shares"]="5", ["?cpu.rt_runtime_us"] = "1"}, cgroups_name = "grp_${pgrp}", check = function(proc) return true end, }, }, }, { name = "system", cgroups_name = "sys_idle", label = { "daemon.idle" }, param = { ["cpu.shares"]="1"}, }, { name = "system", cgroups_name = "sys", param = { ["cpu.shares"]="100", ["?cpu.rt_runtime_us"] = "100" }, check = function(proc) return true end, children = { { name = "system_group", cgroups_name = "pgr_${pgrp}", param = { ["cpu.shares"]="30", ["?cpu.rt_runtime_us"] = "1"}, check = function(proc) return true end, }, } }, } SCHEDULER_MAPPING_GAME["memory"] = { { name = "system_essential", cgroups_name = "sys_essential", param = { ["?memory.swappiness"] = "0" }, label = { "system.essential" } }, { name = "user", cgroups_name = "usr_${euid}", check = function(proc) return ( proc.euid > 999 ) end, children = { { name = "game", param = { ["?memory.swappiness"] = "20" }, label = { "user.game", "cmd.config.single_task" }, adjust_new = function(cgroup, proc) local max_rss = Scheduler.meminfo.kb_main_total * 0.80 * 1024 cgroup:set_value("memory.limit_in_bytes", max_rss) end }, { name = "group", param = {["?memory.swappiness"] = "100" }, cgroups_name = "default", check = function(proc) return true end, adjust_new = function(cgroup, proc) local max_rss = Scheduler.meminfo.kb_main_total * 0.10 * 1024 cgroup:set_value("memory.limit_in_bytes", max_rss) end }, }, }, { name = "system", cgroups_name = "sys_idle", label = { "daemon.idle" }, param = { ["?memory.swappiness"] = "100" }, }, { name = "system", cgroups_name = "sys_bg", label = { "daemon.bg" }, param = { ["?memory.swappiness"] = "100" }, }, { name = "system", cgroups_name = "sys_daemon", check = function(proc) -- don't put kernel threads into a cgroup return (proc.ppid ~= 0 or proc.pid == 1) end, param = { ["?memory.swappiness"] = "70" }, }, { name = "kernel", cgroups_name = "", check = function(proc) return (proc.vm_size == 0) end }, } -- io configuration. blkio does not support hirarchies SCHEDULER_MAPPING_GAME["blkio"] = { { name = "task", cgroups_name = "usr_${euid}_game", param = { ["blkio.weight"]="1000" }, label = { "user.game", "cmd.config.game"}, }, { name = "group", param = { ["blkio.weight"]="1" }, cgroups_name = "grp_${pgrp}", check = function(proc) return proc.pgrp > 0 end, }, { name = "kernel", cgroups_name = "", check = function(proc) return (proc.vm_size == 0) end }, } poelzi-ulatencyd-55515a9/conf/scheduler/30-single-task.lua000066400000000000000000000121221154664534000233370ustar00rootroot00000000000000--[[ Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. License: GNU General Public License 3 or later ]]-- SCHEDULER_MAPPING_SINGLE_TASK = { info = { description = "a good default desktop configuration", hidden = true }, } -- cpu & memory configuration SCHEDULER_MAPPING_SINGLE_TASK["cpu"] = { { name = "rt_tasks", cgroups_name = "rt_tasks", param = { ["cpu.shares"]="3048", ["?cpu.rt_runtime_us"] = "949500" }, check = function(proc) local rv = proc.received_rt or check_label({"sched.rt"}, proc) or proc.vm_size == 0 return rv end, }, { name = "system_essential", cgroups_name = "sys_essential", param = { ["cpu.shares"]="100" }, label = { "system.essential" } }, { name = "user", cgroups_name = "usr_${euid}", check = function(proc) return ( proc.euid > 999 ) end, param = { ["cpu.shares"]="3048", ["?cpu.rt_runtime_us"] = "100" }, children = { { name = "task", param = { ["cpu.shares"]="3000", ["?cpu.rt_runtime_us"] = "1"}, check = function(proc) return proc.active_pos == 1 or check_label({"cmd.config.single_task"}, proc) end }, { name = "active", param = { ["cpu.shares"]="30", ["?cpu.rt_runtime_us"] = "1"}, check = function(proc) return proc.active_pos == 1 end }, { name = "group", param = { ["cpu.shares"]="5", ["?cpu.rt_runtime_us"] = "1"}, cgroups_name = "grp_${pgrp}", check = function(proc) return true end, }, }, }, { name = "task", param = { ["cpu.shares"]="3000", ["?cpu.rt_runtime_us"] = "1"}, check = function(proc) return proc.active_pos == 1 or check_label({"cmd.config.single_task"}, proc) end }, { name = "system", cgroups_name = "sys_idle", label = { "daemon.idle" }, param = { ["cpu.shares"]="1"}, }, { name = "system", cgroups_name = "sys", check = function(proc) return ( proc.euid < 1000 ) end, param = { ["cpu.shares"]="100", ["?cpu.rt_runtime_us"] = "100" }, children = { { name = "system_group", cgroups_name = "pgr_${pgrp}", check = function(proc) -- don't put kernel threads into a cgroup return (proc.ppid ~= 0 or proc.pid == 1) end, param = { ["cpu.shares"]="30", ["?cpu.rt_runtime_us"] = "1"}, }, { name = "kernel", cgroups_name = "", check = function(proc) return (proc.vm_size == 0) end }, } }, } SCHEDULER_MAPPING_SINGLE_TASK["memory"] = { { name = "system_essential", cgroups_name = "sys_essential", param = { ["?memory.swappiness"] = "0" }, label = { "system.essential" } }, { name = "user", cgroups_name = "usr_${euid}", check = function(proc) return ( proc.euid > 999 ) end, children = { { name = "task", param = { ["?memory.swappiness"] = "20" }, label = { "user.single_task", "cmd.config.single_task" }, adjust_new = function(cgroup, proc) local max_rss = Scheduler.meminfo.kb_main_total * 0.90 * 1024 cgroup:set_value("memory.limit_in_bytes", max_rss) end }, { name = "group", param = {["?memory.swappiness"] = "100" }, cgroups_name = "default", check = function(proc) return true end, adjust_new = function(cgroup, proc) local max_rss = Scheduler.meminfo.kb_main_total * 0.10 * 1024 cgroup:set_value("memory.limit_in_bytes", max_rss) end }, }, }, { name = "system", cgroups_name = "sys_idle", label = { "daemon.idle" }, param = { ["?memory.swappiness"] = "100" }, }, { name = "system", cgroups_name = "sys_bg", label = { "daemon.bg" }, param = { ["?memory.swappiness"] = "100" }, }, { name = "system", cgroups_name = "sys_daemon", check = function(proc) -- don't put kernel threads into a cgroup return (proc.ppid ~= 0 or proc.pid == 1) end, param = { ["?memory.swappiness"] = "70" }, }, { name = "kernel", cgroups_name = "", check = function(proc) return (proc.vm_size == 0) end }, } -- io configuration. blkio does not support hirarchies SCHEDULER_MAPPING_SINGLE_TASK["blkio"] = { { name = "task", cgroups_name = "usr_${euid}_single_task", param = { ["blkio.weight"]="1000" }, label = { "user.single_task", "cmd.config.single_task" }, }, { name = "group", param = { ["blkio.weight"]="1" }, cgroups_name = "grp_${pgrp}", check = function(proc) return proc.pgrp > 0 end, }, { name = "kernel", cgroups_name = "", check = function(proc) return (proc.vm_size == 0) end }, } poelzi-ulatencyd-55515a9/conf/scheduler/50-video_playback.lua000066400000000000000000000114631154664534000241030ustar00rootroot00000000000000--[[ Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. License: GNU General Public License 3 or later ]]-- SCHEDULER_MAPPING_VIDEO_PLAYBACK = { info = { description = "a scheduler for video playback. EXPERIMENTAL", hidden = true } } -- cpu & memory configuration SCHEDULER_MAPPING_VIDEO_PLAYBACK["cpu"] = { { name = "rt_tasks", cgroups_name = "rt_tasks", param = { ["cpu.shares"]="3048", ["?cpu.rt_runtime_us"] = "949500" }, check = function(proc) local rv = proc.received_rt or check_label({"sched.rt"}, proc) or proc.vm_size == 0 return rv end, }, { name = "system_essential", cgroups_name = "sys_essential", param = { ["cpu.shares"]="3048" }, label = { "system.essential" } }, { name = "user", cgroups_name = "usr_${euid}", check = function(proc) return ( proc.euid > 999 ) end, param = { ["cpu.shares"]="3048", ["?cpu.rt_runtime_us"] = "100" }, children = { { name = "poison", param = { ["cpu.shares"]="10" }, label = { "user.poison" }, cgroups_name = "psn_${pid}", }, { name = "poison_group", param = { ["cpu.shares"]="300" }, cgroups_name = "pgr_${pgrp}", check = function(proc) local rv = ulatency.find_flag(ulatency.list_flags(), {name = "user.poison.group", value = proc.pgrp}) return rv ~= nil end, }, { name = "media", param = { ["cpu.shares"]="3000", ["?cpu.rt_runtime_us"] = "1"}, label = { "user.media" }, }, { name = "bg_high", param = { ["cpu.shares"]="500", ["?cpu.rt_runtime_us"] = "1"}, label = { "user.bg_high" }, }, { name = "ui", param = { ["cpu.shares"]="1000", ["?cpu.rt_runtime_us"] = "1"}, label = { "user.ui" } }, { name = "idle", param = { ["cpu.shares"]="1"}, label = { "user.idle" }, }, { name = "group", param = { ["cpu.shares"]="50", ["?cpu.rt_runtime_us"] = "1"}, cgroups_name = "grp_${pgrp}", check = function(proc) return true end, }, }, }, { name = "system", cgroups_name = "sys_idle", label = { "daemon.idle" }, param = { ["cpu.shares"]="1"}, }, { name = "system", cgroups_name = "sys_bg", label = { "daemon.bg" }, param = { ["cpu.shares"]="600"}, }, { name = "system", cgroups_name = "sys_daemon", check = function(proc) -- don't put kernel threads into a cgroup return (proc.ppid ~= 0 or proc.pid == 1) end, param = { ["cpu.shares"]="800", ["?cpu.rt_runtime_us"] = "1"}, } } if ulatency.smp_num_cpus > 1 then local all_cpus = "0-"..tostring((ulatency.smp_num_cpus-1 or 0)) local rest_cpu = "0" local media_label = { "user.media" } -- use special cases for low number of processors if ulatency.smp_num_cpus == 2 then essential_cpu = "0" other_cpu = "0" media_cpu = "1" media_exc = "1" else other_cpu = "0" media_label = { "user.media", "system.essential"} media_cpu = "1-"..tostring((ulatency.smp_num_cpus-1)) media_exc = "1" end SCHEDULER_MAPPING_VIDEO_PLAYBACK["cpuset"] = { { name = "media", param = { ["?cpuset.mems"] = "0", ["?cpuset.cpus"] = media_cpu, ["?cpuset.cpu_exclusive"] = media_exc,}, label = media_label, }, { name = "essential", param = { ["?cpuset.mems"] = "0", ["?cpuset.cpus"] = essential_cpu, ["?cpuset.cpu_exclusive"] = essential_exc,}, label = { "system.essential" }, }, { name = "other", param = { ["?cpuset.mems"] = "0", ["?cpuset.cpus"] = other_cpu, ["?cpuset.cpu_exclusive"] = other_exc,}, check = function(proc) return true end, }, } end SCHEDULER_MAPPING_VIDEO_PLAYBACK["memory"] = merge_config(SCHEDULER_MAPPING_DESKTOP["memory"], { replace = { { name = "media", param = { ["?memory.swappiness"] = "0" }, label = { "user.media" }, }, } } ) -- io configuration. blkio does not support hirarchies SCHEDULER_MAPPING_VIDEO_PLAYBACK["blkio"] = merge_config(SCHEDULER_MAPPING_DESKTOP["blkio"], { replace = { { name = "media", param = { ["blkio.weight"]="1000" }, cgroups_name = "grp_${pgrp}", label = { "user.media"}, adjust = function(cgroup, proc) save_io_prio(proc, 7, ulatency.IOPRIO_CLASS_RT) end, }, }, } ) poelzi-ulatencyd-55515a9/conf/simple.conf000066400000000000000000000031761154664534000203660ustar00rootroot00000000000000# This are simple rule files that allow you to add flags to processes # Each rule is a line # # [match] [flagename] [[attribute]=[value]|...] # # match: # if not regular expressions is specified, a glob style pattern is used. # The g_pattern_match* functions match a string against a pattern containing # '*' and '?' wildcards with similar semantics as the standard glob() function: # '*' matches an arbitrary, possibly empty, string, # '?' matches an arbitrary character. # # Note that in contrast to glob(), the '/' character can be matched by the # wildcards, there are no '[...]' character ranges and '*' and '?' # can not be escaped to include them literally in a pattern. # # see: # http://library.gnome.org/devel/glib/2.26/glib-Glob-style-pattern-matching.html # # /full/path path starting with / are absolute exe paths # basename matches the last part of the command # cmd:[globmatch] matches the cmdline with glob pattern # re_exe:[regexp] regular expression used on the path of the executable # re_cmd:[regexp] regular expression on full command line # re_base:[regexp] regular expression on basename # # Example: # /path/to/my/exe user.media # python test.useless value=1 threshold=10212 # # there is one speciality: the timeout value is an offset to the current # time. So, writing timeout=20 means that the flag will disappear 20 seconds # after the flag gets added # # See https://github.com/poelzi/ulatencyd/wiki/flags.md for the convention # on the flag names # # It is highly recomanded to add your personal rules to a file in simple.d # so it does not get overwritten by an update poelzi-ulatencyd-55515a9/conf/simple.d/000077500000000000000000000000001154664534000177325ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/conf/simple.d/audio.conf000066400000000000000000000004651154664534000217070ustar00rootroot00000000000000# audio players amarok user.media clementine user.media cmd:python*exaile.py* user.media listen user.media mpd user.media parole user.media rhythmbox user.media xmms2d user.media banshee* user.media poelzi-ulatencyd-55515a9/conf/simple.d/games.conf000066400000000000000000000002121154664534000216700ustar00rootroot00000000000000/usr/games/* user.game inherit=1 /usr/local/games/* user.game inherit=1 /opt/games/* user.game inherit=1 poelzi-ulatencyd-55515a9/conf/simple.d/idle.conf000066400000000000000000000001121154664534000215100ustar00rootroot00000000000000boinc daemon.idle inherit=1 boinc daemon.idle inherit=1 poelzi-ulatencyd-55515a9/conf/simple.d/lxde.conf000066400000000000000000000001251154664534000215330ustar00rootroot00000000000000# lxde openbox user.ui lxpanel user.ui poelzi-ulatencyd-55515a9/conf/simple.d/media.conf000066400000000000000000000002131154664534000216540ustar00rootroot00000000000000# mixing processes and other special programs /usr/bin/jackd sched.rt instant=1 /usr/bin/pulseaudio sched.rt instant=1 poelzi-ulatencyd-55515a9/conf/simple.d/video.conf000066400000000000000000000006541154664534000217140ustar00rootroot00000000000000# video players ?mplayer* user.media mplayer* user.media xine user.media ?vlc user.media vlc user.media dragon user.media totem user.media kplayer user.media kmplayer user.media kaffeine user.media poelzi-ulatencyd-55515a9/conf/simple.d/xfce.conf000066400000000000000000000001721154664534000215260ustar00rootroot00000000000000# xfce xfwm4 user.ui xfce4-panel user.ui xfdesktop user.ui poelzi-ulatencyd-55515a9/conf/ulatencyd.conf000066400000000000000000000042271154664534000210630ustar00rootroot00000000000000[core] # interval in seconds to run the filters interval=10 disabled_rules=test disabled_modules= # monitor netlink for process events. highly suggested netlink=true # milli secs before a new process is scheduled delay_new_pid=1000 #delay_new_pid=0 # you can change the cgroup mount point in cgroups.conf [scheduler] # scheduler configuration to use. available: # desktop mapping=desktop # full reschedule every n intervals (fixes seldom cases of wrong groups) full_run=12 # allow to change the mapping at runtime via dbus allow_reconfigure=true [memory] # maximum physical size of memory a single process may have so it is considered # target for isolation target_max_rss=30% # maximum size of physical memory given to a target. This is a hard limit, while # downsize values are soft limits max_rss=70% # total amount of ram+swap usage for poisoness processes/groups # percent or kb total_limit=70% # in case memory pressure occures # minimum number of pressure process targets min_add_targets=2 # minimum number of pressure group targets min_add_groups=2 # downsize the top groups/process memory eater by percent (physical ram) group_downsize=0.95 process_downsize=0.95 # in the case the list of targets does not have enough entries to reduce mem # pressure, take the top targets top_targets=3 # free + cache is counted, so it shouldn't be to low min_free_ram=10% # if you have more then 4gb ram, you could try 5% or less #min_free_ram=5% # timeout of memory pressure. to low values will cause laggy system as the # 500 secs is a good value timeout = 500 [logging] disable_cgroup=false [TestFilter] something=bla [user] # how many processes should be in the users active list default_active_list=4 [io] # window in seconds in which the threshold must be reached window=10 # weighted number of milliseconds spent doing I/Os # see http://www.kernel.org/doc/Documentation/iostats.txt field 11 threshold=500 # percent of on the window the threshold must be reached to trigger enforced # group io percent=50 # only cfq supports cgroups currently scheduler=cfq [xwatch] # poll xserver very n milliseconds poll_interval=500 [simplerules] # enables debug logging for simplerules debug=false poelzi-ulatencyd-55515a9/conf/ulatencyd.service000066400000000000000000000004261154664534000215730ustar00rootroot00000000000000[Unit] Description=Latency reducing daemon for linux # due to Type=dbus this service automatically gets a dependency on dbus.target [Service] Type=dbus BusName=org.quamquam.ulatencyd ExecStart=/usr/sbin/ulatencyd Restart=always UMask=022 [Install] WantedBy=multi-user.target poelzi-ulatencyd-55515a9/docs/000077500000000000000000000000001154664534000162225ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/docs/CMakeLists.txt000066400000000000000000000056001154664534000207630ustar00rootroot00000000000000FILE(GLOB man1 RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.1") FILE(GLOB man8 RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.8") install(FILES ${man1} DESTINATION ${CMAKE_INSTALL_PREFIX}/man/man1) install(FILES ${man8} DESTINATION ${CMAKE_INSTALL_PREFIX}/man/man8) FILE(GLOB docfiles RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "wiki/*.md") MESSAGE(STATUS "Process file: ${docfiles}") macro(CONVDOC SUFFIX TARGET_FORMAT TARGET_DIR) ADD_CUSTOM_COMMAND(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_DIR} COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_DIR} ) #FILE(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${TARGET_DIR}) FOREACH(infileName ${docfiles}) #MESSAGE(STATUS "Process file: ${infileName}") # Generate output file name STRING(REPLACE ".md" "${SUFFIX}" outfileName "${infileName}") STRING(REPLACE "wiki/" "" outfileName "${outfileName}") SET(outfile "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_DIR}/${outfileName}") #MESSAGE(STATUS "Output file: ${outfile}") # Generate input file name SET(infile "${CMAKE_CURRENT_SOURCE_DIR}/${infileName}") # Custom command to do the processing ADD_CUSTOM_COMMAND(OUTPUT "${outfile}" COMMAND pandoc "${infile}" -o "${outfile}" -t ${TARGET_FORMAT} DEPENDS "${infile}" "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_DIR}" # depends on the 'processor' COMMENT "convent ${infile} => ${outfile}") # Finally remember the output file for dependencies SET(${TARGET_DIR}Files ${${TARGET_DIR}Files} "${outfile}") ENDFOREACH(infileName) endmacro(CONVDOC) CONVDOC(.html html html) CONVDOC("" plain txt) # Setup a target to drive the conversion ADD_CUSTOM_TARGET(docs-html DEPENDS ${htmlFiles}) ADD_CUSTOM_TARGET(docs-txt DEPENDS ${txtFiles}) ADD_CUSTOM_COMMAND(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/api COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/api ) add_custom_target(docs-api doxygen ${CMAKE_SOURCE_DIR}/docs/doxygen.conf WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/api) install(DIRECTORY api DESTINATION doc/ulatencyd OPTIONAL) install(DIRECTORY html DESTINATION doc/ulatencyd OPTIONAL) install(DIRECTORY txt DESTINATION doc/ulatencyd OPTIONAL) add_custom_target(docs) add_dependencies(docs docs-txt docs-html docs-api) # macro(CONVDOC GLOB SUFFIX TARGET_FORMAT) # file(GLOB DOCS RELATIVE wiki ${GLOB}) # message(${DOCS}) # file(MAKE_DIRECTORY ${TARGET_FORMAT}) # foreach(cfile in DOCS) # message(string(REPLACE ".md" "${SUFFIX}")) # #set(ofile, string(REPLACE ".md" "${SUFFIX}")) # #execute_process(pandoc -o ${TARGET_FORMAT}/${ofile} -t {TARGET_FORMAT} -f markdown ${cfile}) # endforeach(cfile in DOCS) # endmacro(CONVDOC) # # # add_custom_target(docs-html # COMMAND )poelzi-ulatencyd-55515a9/docs/architecture.dia000066400000000000000000000054141154664534000213670ustar00rootroot00000000000000][o~ϯuf|dO?~Zw~|0?¿QO"EVOqUM?<<<@i2C_ t|E *v->HFY<:xuTlؙ8oy·չYsV+eO6\41=fi^&pJ4:9J8)}*[|_ey.(ɶA`ƽ$тa}9^pyvU'6d?SgsԪqAB{Z߸8-whλvFC)ɰ>z]ҟ<~I?>ɪt7N˟l %bp͒-hf44^v]'5).2(om0Yj_gqt3wGAM| H3G0ALNK1ء%<4F}ʉFLJ%0{Xk֥gwnS5&*YqooI f.Hi9RQ-ɂ{mHv9+ᝓkdCs3Lgb:6N)R1FbZb(20q`MLLM89S 3iR0RF.BO/ |,a]`gLY|%ɜ5 f|s{ @I߻+ r5 AlM֘ą1pI| 8_?%ʧU2I[ʻ[.,{e#}G yv?A("@+uNfDuw8S%H556@NcѩZg(n'D!KH`* 6`cC xZ\֩{&Q,HU`F` 1+чQd9J|MC#az!F +3$+Ӈ˗rrxRq|K%ti<Y0v:DӰbpH ٧[JaΤu_6LZH-eX1&Z 15~И:EPI=%,N HXy@1PZgnGI&%33HhtXPR3Q0Q]"QL @0d䟁 ǁbN׆@" D0NWgakzVSAz #Ȓ|N&t{l4|GJۜx%O F#2"BJytm30nF Ҹk \yy%5U' s?q'fGBo}Z}u 3Xaa#3 a-H*V>n~=uáu(%r΂``9׾wQfj ^ ̠[î2Ydn2γ+mV\rD͢'1.Ոp7`~utkpaPC q,9r[B$ՃKIfBz7C = >ÍKr4+|ܐFdLDQ%6pq !D 44~|ѥs0.$\eHH"0;BT0# 6Rj~cE:C]aH4 V0#ԋR/ 9:Jjg[F5lql_GCRx.jI5j c;䫍fzt|_ck7?N'[|QO>_L poelzi-ulatencyd-55515a9/docs/architecture.png000066400000000000000000001624121154664534000214200ustar00rootroot00000000000000PNG  IHDR54bKGD IDATxy\?3l& `!,AThPVQuC BՏ FqZ֭(q$ K~Qy=1s̙ w( -am^deeyxxh; vh;N :q+W\x#h<==@S o>{l}}zm. -i]S[<}455ȑ#>$ 'q!-.Pv***RSS:ZQQA MMM/]Dt[_h)Z~ -Д|k֬% 0;[ggggz7|/NrƍEO0ȑ#zzz...|ɓٜHmmٳLLL04Eڎ$ׯ?~ӧeees]r… !vBn6ڵkRtSLiHHHHIIyq\\ɓ'򬬬7|… xO?tQBܹs !wf[ػw/!=СCKKK_Ui/Rիn:Xm6D?[XX(WzǷ~BBWzZڙGׯS[Jb}݌Wߟѣnݺ+//3p@BHUU ̈#Ҏ?+_W.UTTܿɓ'ٳgݳ#[gϞetٽ{'OBv?cƌAi:ロ?>;Ν@Dž"NII i2"J8h2ݺu#ܾ}[믿N7ҲgϞ%;p83f̨D8 ˇ Fs"˗/Bh"Fyٳ5kc)^y!sai4CdIII^^^?Idd[y<^s=z4M0@Dž@ׯtT… H333BHVVs7 !/ !NR=\vСC\.wҤIBrss."? ~hh]XXX~~P(LLL{nBBBϞ=[О!-Fx B֭[={&\]]}3f4`iiIq[rJ##+Wݻwo ,>>|'gϞ٣| !d̙ ƍ355e-ZgbXg5rr9skӦM?سgO^^^hhh׮]h"md޽#GڵihNA7.66<(((&&Q˗[XXۗRu77Çs!C>9 4oƄ x<ܫG eeeiӦ)_߽{w jKTW84epF%HKrrg>ӧOgee622j=J4-ee:??eIIA\ajTm޼yȐ!Ϟ=[tÇL(999K.=vXCC wttlP(;w Wu L&ŵYNE]vnpxPlٲ_UPЄHDDv #^{͛7IFڧK.UUUђA544FGG;99i7Bh~wm? d2]@NwBM2eo֗_~T]]mhh8cƌ2 "a[n…aaalyuuuZZܹs.ڋ:D!;v8x !o߾a;vxŋB@ohԮ3pvݝηv!CBVXfq@tH@;"fϞAӧٳgccc]FIINN FFF|2->}ڵkƟ{buuuPP=q͚5l~ ;wSS#F\~]CZڋ .xzzn߾#rrr|MBT*>ŋ999bFFFrE.D믿N>]*ٳgu###?fimm- !"wLʝ;wrOOÇ+ԙ3g>xmBvl))))'N޽;!$!!/$m-!nj#Jnܸ6'BXnk3[:ujxxT*H$aaa?""Ç> Wn뵵 ZLP444ܺuW^ ݛ H$} iЂWzxxٳ޽{|>Qv;;SN}l|Ŋ掎:thZb i'Nhllw70={ܻw'|RYYi`` 衲2666EEEаZN"I͛7@AZZRRҠAnܸѿ@: %O>=~8[DP4hpv-bETk:ٳ8qD:ށڿW:ݻ{{{w֭d!!!5CBBo߾]WWO @#˃rG}t̙}j;`҈ &h;f͚};v|8 ]r'Nmbbe˖ mGP(ի\.7n˵Gz{!$''-3gNzzzэZg{@CZBFFɓ%ˡC vF0ZBXzD"8qbNNN͉AZZQUUԩS EBBO?dbb h^z׿@ox%onTo 2ի'r"0+V"Ǝ+J7ZX]]w֭Ǐ?vP E}W ØL6iҤ#G'''oȉFߊk׮SBnnnk*}s@;={BV\IsRwIMM;{ #@ {=rwQvСCm EBN{EggӧOWר[XX8n8SSS3j(D~mȑ}իիWGFFr|֬Y|>rZ^SS3}tccckkk>7HV}}}tt婩322 ;wlEt҅"~7n:uJ(j;46vذ0D"]]]U<{>>>&M$H233 !eee'NdĔ߻w/66JRHtʕ?_x1''G,s\6lٲʌs5A݄@;~c/Iٴi;sݷz?v\/ڵk>>>\.ǟZw߱III͏6%%eݻw755MHHi9}T*upp@oBZ@'<7߱hѢ-[(OƐBBB!6mtVsssBHUUUCCx1o 0>Ti o߾W*NNNtI,=z͏w 0 cmm9pɓ' гg#G4A݄@+yIu֥KOOϟ~U/DfQFtDMi32m}_FFϧ&&&ڍdggkc`` vYY[>eʔy744TTTtZK. CX@$B+++mmm͖Mɲ)**RMDzzz>|X*n޼988h=Æ 1bD~~'Olٲ~m\.NJ!#׭['Ɋi'p8H6P^^7dȐ&UlH$PZ>upT*Hž$+$$$88uuu ^~VP5%]H9s" b{hZ@jkk---ׯ_O|AVmQ$99Y( 8˪j a7 lݺɩK.;}]zE[ͥg]A5ZvFN4u:EuѣGg7HLLvPmCO4QBȎ;<󽼼'OMII 7666ljS񇷷%WZ%mmmW\IWXannؿC5Ɋ>|8 Ǐ8qqdd޽{_"Tݢ?&:-@PRRPDDѣbqii￿pBZξ%ٍ˗9O<̙C{/// kt*>}l2OOOsO"'Nw^UUժULMM'OѹMW\\lkk˶nݺѦ.۲2L& #ѲM\]m}ل w޼@ntDPMYYY6IL@ӝsqa @k֭T*U{Hz4-4Z@,BxNNNp…h~x͡ieMѾձ4L 0Nt)t\-: iQHBZt"(E@G!- ìYF Ϙ1-߸q!D.Ϛ5`\Nֆ[ZZ_^k!-"H,/Y-?|NNNCC!$&& ??޽{N\\ܵk.]$sP(aٙRPPs}Z^\\lkkK٥B}||h>--W^ڻxa!?`UKv,1 ݹS @E? {nlN"趓X,<`a @kQCQHD7ܹ#lB+++mccSXXE#<<\*J &H$Ihh?- O?~`6SAZ@;' u|ruVZ%mmmW\I˗.]ڧOwww'''6SZ0xurrU`BAxQsCw:.(E#)E@G!-;[&2v @dt*)S/@CoW=H@wn@ǡL`&99Y( 8˴>::ʊWVVgFo[AŋwԺ7С`UHOO?sLyyopp0-xbNNX,r䟯=R uQF}vCj})++ d2>_[[K 'Npuu%bwwR2AalAٖTN}yzzfggkeeeyxxh;VG:rAanŽpQ:MaBx<^]]...ݻj ͡ABȆ P@ky+mllΞ=kggׂa_Æ 377_hQ 6 j[qx.t,hW0@;|\ZnffrmvJII[.Riv$**{</ ח/\(MOO߳gϊ+Z.XhvWOO/:::::Qŋ/^XꆦrkkׯLzBZt"vO0Ez 5ǏOHHhkt&HhAUUUbbuZYMknذe/!-?oܲͲ6ʏkȐ!jttHh#G|}}~~~n+HhAvv!Cqƙr8QFI$Zި[SS3}tccckkk6|/\j7!!-666رc$X,vu$$0 IDATu oXT*\4:J( vL !޾30vHP׮]\.7>>^(6}w}neeEIJJruumH:3h-?To 0>l=zm''s})@h=8w;eʔy744TTTd2]VVַDthڹs|͗ i-;v?VWWs8#rwwud␐|ԩRT"off\?;nCZ@ &Mt̙[nݔpccaÆyyyvqA>ÖXѱCUm…6^&'i?<ך0aZ@>hFJQԣS7 VЎ6V$_: rI&u֭K.?#mBZtEAAaFɓ-[߿_A6!-"...22rΜ9ȠA"r|֬Y|>r,a6nhooGx5!-"--O{*=|NNNCC!$>>ŋ999bFFFE:]QVVfiiо}Mۧ|tÆ )))7o޽iBB?VJCV]ѭ[7TjcczH,;99m'''X|֖...ݻ73z>|?@ DtJS#666EEEБ5sH,!-y %$$l߾ѣG555.\!??0D"HBCC55|\9 -btKZZ{ٵkyIUV gggggg[[ە+Wjj$**{</ ׷h; ø)OP(틿ѭEQfS\nnJӧ$,.zѯ ==x<ѣGǎKw:dllݐZzgώ9rݺuȉ@g^]zrr|֬Y|>rZ^SS3}tccckkkײu3QuR__mee+++iyjjj~BΝ;["D30Bh/_nTAywΜ9_5[W_͙3Gmĉ@4֧-oܸ^OOxhEL4I"dffB&N))))((Ͽw^ll,-J"ʕ+G/ŜXrL``e*++322Ν;r_H3gΔ7Q3))i׮]?!߽{MT8p`̙#hsrru t NOOo…HBB¢E6Ծ}MG˿;<))KIIټysMMMn\.RE5 V;[0LYY!D&Zճ]X|h|@sDEE֪ H$$44]zԩRT"׭['ɊCBBT/|\_^[[P(K@Z"6mڷ~u)STWW=}ܸq1cƮ]-@kS]h3Yj@ pvvvvv]r%-_bc߱c|jQQQÇx|'N466ܻwo[QBPV.]9sfnn]dd'|P(g͚E+oܸ177jt9W5*,,,""b۶m+))quuSnCCtmn4Bafff```jjjdddllp\"C?-N=*:uAVZ:W~}}}h\yU+-BHkcfׯ֭[ lذaÆ rO?mڴB蠀={DDDBcbbRRR>}:nܸ;vBRSS-Zokk c̻r` itQѩ 6lX}}'Ojkk>}ZSS#ɪr9 WG#<K.&&&|>_OO\OOvZi>Em喊 ҵkܴBHFFFnn.!dK,ˈV:$Ĕ^~k׮%]vҥK&&&˖-'˟GFF~嗄1cƈ˗ BrWDr`Z z@/in4_M+++[ijFFF'BjjjLLLfΜgmmDMim 8;;B |||߿OTV:KOOwqq!Bzܦ> &tޝQ4]Eܡ9[:zTtfBZ:-J3.O>m8_=բ\v{i8;ǏB-Z"-1 SWWOr(a``PSSZ@.7ghhd.bŊL>8vXM"lb(MT̈́t:@A4/r\PVzY ^bB&SP(RSSgΜMͧZ:MCHhYZ^:"jjjjyфYJ"G_͛;sL%LxxxJJ !djeBCC̓>Ӕ:ƍ?˟oڴƍVڿ?!dԩ ʡPY^^Jӵ4@+AZjTK'\ji~…N˾.v}ѢE/_={vRRR\\ɓ[v {ɓ/_ΪU͛G{LĖѣK8pɓ ٳ#GZ"j@oi+WBLLLfΜ`GGGmemm-O"H[~@@ d2#===>P(RSSgΜָe-\2"V\yDDeZH$ Ç5@ԩSåRD" S>D˥R hud2YqqqHHr;ׯ_U(uuumzÇi.Z"emm};w,^B6Rsc:;;oڴ)))EO/**󳷷={60Zbc|ۻ_~B[n˗/;v8x a+?~ĉƑ{m[h?fϞmgg_h;߿wt ZhTR?<<|u<==<<<^V3,ipKKKssh+++HMMׯP(d?-T3$-),,7n)5j;Ԣ:((z͚5l}MBh/_nDPvww9Ndddqq]^4'BٳcǮ^:y䊊KFEEUTTFk7nXSS3mڴD:E=Dbxɒ%lsrr!111%%%݋u]v%HTTTD /^#\ndd$- \lYeeeFFƹs(d 0vذ0D"]]]iylllEEŝ;wrssO>Ͷ\Yzz3g}}}"(#Gx{{9̙3fffK.{nBB])~8{ß>}z={Žd%7oN2O>{Q(|󍛛CD#@A4azEAAiyqq-fggBI{{^z)) O8A bwwwVg}6a„ݻ6PL& H322znrqqgi*geNa#|~mmm'EhÆ ? !]taj͕w҅./edddll\k׮{egg?||E J~#Gٳa//3f 0La ix?E0 SWWG?|q\h000Qf`` gzCCC-]NVXǎQʑdffFFF^tJFҳ4+E=?M}iWSSaaa#mnn^^^\4S```@(q\ΘwFS]ϯ ݻwi;w(G^DrԩgKKO>$$$K.5ry4E={XO <::gefƍ XZZ*Ϻ#@P^^sUgf|>?88X.֬Y#g̘QSSC [˳cO)((ݻwoUUUYZZ*bwm@OɓcǎEFF+"pwws̙G-,s@{C@ 6mZBBܹs8pȑ#w޴kkk:{vss;p!䧟~z뭷yn#b. Mqrr=zsffombb0Ç~‚nxXLMMGp'''7]r%>>O>믿^t @G@c0 [,CtΈyƍnݢJ7of}MiDIJJZp L D"]XXheebܹærZ<)S̛7C IDATB̀uDgϞ}СyK*nݺuС ,8s_I iז QtMUUմiӾ[__߭[N2.82uׯl۷om{ɖ-[LLLB*S{H$PSåRT*]`A@@-l񘫫9DP|7o˻qF\\\޽ܳgϨk׮i;LVuZ ZhTR<|}T,i7Z͡}vvwÆ ӧOW(suu500pss;~8=Z__hhhد_rjǎzzz'N`K.K&͘1tƌ2=etqjZ1/NNNt&ZZrJ4sVP>Wh˗/)lO o_]UU:b? Z-xh=S}JB1 :Ko4OO쬬,9@( .|{ !̚5IYADa5kcc3f԰7n#Yf|>,ipKKKsshee%-OMMׯP(ܹsg"ܺuS.]w]vhZaaqLMM9ΨQ9 INN ˗_2Eu,ڋj3q^FMA40 JNN.))III2dHEE֭[x m۶a\"к222rssE"X,^d [~:bLLLIIIAAA~~{bccik׮]tI$/b.I-[VYYqܹ& 򀀀ѣG;v,--Ǔ'Of&;v,@,9sWy.ϥ kz$snfffnn/^tR5: x9xhS}Ju%ݻl5[[|gggGwרMGGǛ7oR@@7o|}j EC]UUh@*KKK2D)sPh@jO2j  s(@3uGz׮]pf͚uu:fCoxU?eѣGii)[nkknb'''$rVqqq޽'8pȑ#M6ҭ[7Kcdff&&& ?|=B~s^jjiz$p8AAA/_NKK;vgv6f̘4mG\M0v3͵=*Ɲ;w:VXXheeEmll U)**b/JB<==>,J7o̎Q[L2o޼ↆ M7W@&WU>|͛sx# rqm|-+<<\*J G'ѐH$<((O?-**z haHHHpp۷rssSN~zmmB`;D-| DMhz׭['ɊCBBQ33<^F6e˖篽ڹsFu mzm=jG͚5K__~j)cƌibT{ۻ_~B[n˗/W[gժUvʕ|ҥ}qwwwrrrppQQQÇx|'N466ܻwo/!%%%<>>Ϳ… =<}uzDOOOF>cAB4@ (++ 4hЅ Z-:cbbbmmM?effAN΂ -Ҧ 0ŋ={b ';;{Ȑ!fzbĴin߾pߧOGi;.BiczW\iYvvv;v,]hΝB0..r+Z#x!ݛ?~]]݆ *++:EKi򸸸{̘1UUU˖-߿ɓ'[+PCہ@k%%%={;jƮ,%lrr(#uT7-KMivKmw,QJ)d f~o)3qd>ܼ8,,v.i(46,rݺ?m۶,BH.^xv=|ذaӧOPZ:nqPGz ?uꔾ~JJL_hE ;Z-Əٳ~۷K.?nQ}HM ƌ~eeWVV"e֭[799/_)lY$33?|2!DKKݺuDZafʔ){YYwϞ=iтHcp8<F(8p@__˖-'))iႊ O@UU:::=߿{YY\ *PG"o޼qttɑu˥@? e ~t3EEE=zu֭dL###zꘘCCϟ_? 9E(ݻ7!$66)ꄐw 6ɓ'nRVVpݻqŭY#66v(hP]v:t(**S444ڴisȑ+Whkkڵ"Ȱ"p8^^^]ty왙6AYBn߾]g͚3ѣG;88N:J )Dѻw︸WTTx{{2Ό e:~,2aee/Nn߾]SSΝ;XdU9R1t@@@XXFxxѵkhe:زHDDDݗ޽{Yl\@dnnqY[[aVh@(СUXXPv 2w܊ GGǏ?6T<>ch-[<~M$$$6oljjJ;4(Phj积+V4D.0͛7544bccMLL\B;4(PPe99JHH]6$$!#ssaÆo~~~5lY$<}Z<<}ԀN2eԩeeecƌ)))iV׮]CBBtuu  ډ@ء,BaYYٝ;w;v3))iҤI<aNQQQkZZڠA?N;5E賴$ ="%%uVZ]xqڵ 8RSS 0`@-P]^ ޲/-Z3&;;ݻBY6x#Fr'OlǧLRTTdeey@Y>iii333."W\iaa;vϟ? !$..wiӦ[ZZ mhP ئOLLСC111-DI\\|׮]pAYD(^vM@i7o>wzB옑ӧNBe`hhئMdu1hР+WxS&%% !$&&{ӧ~Сqqq@YD(h׋رcmmma&((}xذaϞ="bԨQpazJNN3fLee6˳|D@"ZJJ*:::??_pHKK;wN]]֭[X~D3g222,,,޾}K;P߿UUՕ+Wڑɓ'6nܸg jz }R'++{̙.]xb>}hBYDB.^(cBȜ9s޽+ʍ7ttt"##Mh'jtmm-[IJJ ;WW;vm@0 ChpA6z~Ϟ=igTЎt`ڵo߾m޼yo߾7n=s挔lB;Ёpiy4 #GGGG{xx4BBl xxx?vhKttt~m{ 4lÆ (4LAK CvvICI4|˗/ ӧ8Ш0ZDGMM-==ɓ'c޽O:%..>Ç7Ncٲeƍ+++;vlNN8ШP.bbb֤Ѱׯ_f̘qΝFԍ5bbb711Ȱ"B1sss3gNyyȑ#SRRkdddΝ;׮]0wwwq,"t*%%u޽wӦM666#G|]cv @ɓ'm۶w^q,"tZhamm]UUuW\\ݻwOJJŖ jLLL6nH={hƀ03f !ԩSo-.]1uT3&??v8ETXXXnnn#wq iicǎ[{nݺuKOOwqqea0tЪ7~۷o7Q#++{%%ӧO޽v,E;᳷x<ٳgdCbfܹ?e!egg'))RPP@%Ӳe˪&ME%-Çwuu-//8qǏiAAYDH)**8<ٳ?~8jԨgϞъ@u떘h"Y@PP^tѰG]PP`iiI#Gdddmvq@ P^Gp87n(,,A\\СCfffÇZ9;;gggӎ eպukss󊊊/R!##s.]y[YYa0!HIIIYzٳwNcBCCuttUTTƏϟZQQ驢~zSvء-))iddCdUUŋ۴i#++koo_RRžӴi֭[o5D^Rٸqc5JJJ˖-#,\PF:MɓoܸN;˷ݻֶxĈ4_;v\dɮ]o>k֬VZNh255urrz222¢ 윜ؓ~~~<ɑb/]4///--ѣG!!!uVSwqܟgh:tHHHENxФL0K;Hm>}4rHBBTT8|KrPҚnnn޽cj׮]rrB Rkii%%%٪챆FJJ {Bz⠦ !kgϞ"X"r$hii? E?o>aӐɓ'njS\\leeiHNcJ̟?~޽_'?y򤬬ڵkǏgzOQVVfdee+++Ν;crss:::7[IM B.@ &Nhdd}vY, :C^%%%O81cƌ>XYY;wv"m۶B֯__M־}{~0;w oֶm:>m۶zjjjiii1pも7CLLlՄ??bqǠ,0 3eBȞ={hg11 ϟ??ԩS@+))y葌c8ߚ5kh11ŋoܸv4lٲcǎuEJJ_~F IDAT'N`ҥKuuu555kolȐ!'Ncϯ\RIIIKKpwu9SS׽A6>>>7@`:tSdmm}yR'vy]kٿ~QQQ*77]dagff׮]."/:4^z޿]377\n݂ hgh&{nAk۶m<o̙@Ð8p`UUպu3fx'O矄H4rHRWfb{xxю G\\hŋWUU[YYb<dmmݫW_Pi$%%!/,?͍]꯿rwwPsf_~_۰a닊_>`lZŋ !k׮3,P'(4U3f =zi-riiiѣGNկ_/0 ynݺ=|8!!J6Ffkkkhh͛uHSehh8hР&kggw-[;wn`\ihhܼywYYY<aa[ݰaG4 (4ag&lٲɽ4($$M6C ˣBEE%$$:''gΜ@&N[n߇H6jԨv%&&FFFÌbcckډ@ .^8wܲcz{{7J.4'[ءC;200/ 8Μ9s!6me&̜9m6Y~nDDD=LLL@s%..y?mڴ(@nܸaiiyYY`ϟk\@̙3edd._sY;Piڜ$%%Ϝ9E;PSSqF޽߾}kaaCډ@PO<)++~sss#GlѢunn7~ /Zߟ3g*(((((899 Ӵi֭[ݐ|UUU/nӦ}IIwou<oΝw,ҴUTT4ݭ[n2l0v+WN2f̘H^zEDDNPwwܜN:yzz~}ϟ###Ccƌͽs!$,,`G,Yׯ_/]=tҼG=߃rrrddd`sB[ZZJ; ԊM\xx8!D]]ϴJa|||hf+%%%nrQ#k/(ko^SZZ>}Ν;G񬬬7s899YCC=HII__SHVRR{u**={$ܿv۬9!dǎ@m0Z377֭[VVօ hgy6mbٹv(VZ]reѢEs;vlqq1Ppsssyyya󿾦4,GG=zSLPNN.{ggg=mfffΝaFMM)?Иf͚Eٱc PEWWWd^ԩS222AAAEz.'..iEE3g3>>^P.((?~9s233\nqq1[(^7oޔ)Sܤ?iӆ=VSSm۶?|q7ܘ1c>|M; e` O(is%"Y;p! l0Z044|}YI]]=<<ں`Ȑ!Cƍc++QF9;;5Xn}a#!d,|7o\QQA;KÐt钏ϟOln %B]]ʕ+[l ڵ˗(8@|_~ĉnݺYPi>u떙yIY 0˗/?v올lPP @i%9s>|hjj9bĈiӦa4iʿ=t, (4+ƏٷoDډ?W!FFFqqq^ruuOeHOO;wn=رcxx ddd߮]8pkv@5ydBѣGio@YY4iZ\\۷igi`=z􈉉ӧOjji'x;lq? g1BYY9!!紳PiV\]] !6lmSLოi'׏طojDDi14@YquuxbRR, OJJ]l!ϴC1 3uTqqM6ikkVUUюFM9s %E)Sx͛7" ;vLJJ*((hC`._ɓ'Ç/,,twwӧݻwiCl2...55v(4CbbbϧEPƏӽ{k׮N5bw?==K.9rDCCsnJJJjĈgefcǎeee;wEzqaÆ{ۛ ~ E1 uV=== PG Pi؝znGYHYYk֬aԨQEEEC4L5JJJvvviiiC?WnCm+//?bĈ+VmݺAÇdeeHdaaѯ_젠 Ya// .(**ɓ'C4<q_~eر_~%888"",77wܹ;vܱcǧOhGYY!Cp`YPi!Wˈ#"##;v옒bffvډaڴitD;%%%>|ȞӴi֭[WڟGGGGk׮_ʕ]JJJjkk޽z<33K.իWzzz[lic!\zv/E-CCÌ ݻ7rb[[[ooJڡ]GGC_j/:4^z޿gϞܹsrrrdd),@F4o&LShgi$ x{{t!**k׮Ǐ w޹ϛ7k!DVV?D+;;_`ku{Ϳׯ_ѣG.^XKfalll߿M >l޼Y__„ QQQuo$ܸqvE3qq Boq\\܄ ޿ooo?e2PJJJ^^^wܩjjjmkuzW^.\۲e M rqBN8affֳg`MV‚, LPi544?~|%YU˖-;sN))xv(8 SNu3///77ݽϟ???????ӳzO>x?l'^|pB%%8::jii-Z())4JJJ޽{ ` E9)))ŪUhgΝ;zzzqqqch'hJQ)i+WTRR2448p`{\r{~ԨQG:tЏވڵk322va``nݺ.]ٳGi-Z۷oUUUhh(,@\:::yyy?(((:ujpp00~~~CA37/))iᩩ rtt޽{O8{BQ~́劉 xZ"r?jŊ>>>7n0ZD͝;G; Zpºu$$$֯_obbBã0;;{o߾vX^^~ё#Gm9$$M>|P9߿?!V@""a֬Y7oY`Ç{ri/ikkwQ___QQqŊHNN144ׯ766~]PPА!CTUUO\^^^K EEE?|޽'[BBѣGeee"UVfxԥKh// ooo++,ڡ啔>|X^^z􌍍MJJ)((طoM6mƎgϞoW(**{xxTTT8;;7BThdeew^QQq}YexbEEsݽ{v֬Ys5uu7ov… CPԩtҮ]>}zƌڵ322:rKoذЊ MQ߾} !wܡPnnn˗BСC=z4jԨQFM2v(FFF+VHHHHMMݶmۈ#ddd>| %%e``0eʔDvŋ^ ,M;`'QR\\ݻpsssqx6mZxO LLLhf43oߞ;wnJJJZٳgϞ=mVTTD@-"r?!==][[uֹ @ "eʕ˖-:t7hg=4iRbb… /_.%%E;4y(4WEEEO|aaa@DА.++@ta{n߾}eeeSRRڶmK;NFݻۍihx<މ'-Zk E-YDZZv.ӧOtB; hQקO#GҔ뇄:tHMM޽{}={vQQ\M[bb_~ݷo߸UV&XRjj* e gO>)afҤIIII#l߾SN,Ç n:(((22k׮s!$--vхN:M:jɒ%4= Ϟ=͝:ujϞ=5Y.\n``V@@0>>>^9s~Ke Bٳghgi^gϞ֭[řϟ?v.adii;SSӘ˗H0:EB]]] !K.bf\.wÆ ;vܷoRooݻߺuKMMmFFFs4*mmmBHzzz#umMMMa/Qdh?޽{WXXxqXwwwv荱q``)P¢2((777̙3g|D۷o޾}[Ϧ;zdɒ#Gֳ݄\0Qx!Yf<};v옗כ7o0a&\]zu"ߦ;v ***8ΧO=@FF]8hw'R00---00v&a/_nڴe˖ǎsss+.. AY[[?}C.\^3 1k"M"_6m"^"!!斐PYYy]9rv4Ɠ?gΜ޽{˯\ǘ jӦ !$//0 cmmmIII##e=(//9sSyy97nl߾=0󏮮T׮]###ߎ;-'$$JMM9rd-ssszZz:׷Y=aUUŋ۴i#++kooSEEEIIiɚ.fݻw***ՋS޽SUU-,,As969r$22W^&M֭ŋiϟ?oذA__۶m<oƌr IDATϟ?_d,hBEEҰ¢ 윜!t,Y$+++%%_ݻwܼyۅ'N>|˗oݺUTT4n8eB{nnnNNNN<==Zz:ת'{A\\\NN{222ؓ5]LQVV;vlPPLPPĉywЬ׋/ݻG;KsSUUo߾vڱ?} {.PrO>ϾYXX<~v(adkkK9}t=!jqii)s899YCCMfff6M}%JKKUTT_4R=VRR{k.99t1ٳg<B[[;==wMF:t0w\.B JLLlԩϟ?WRR}Ʉ ^xA;@C 1113fLJJ/rׯ ~hjLYY=]]]XWW7''z+[jo/-߹s\^^a99w033sDMMm|-^ܹӧ !Nׯa49(7,[LMMΝ;'OYh˗/.\(%%u yB)u<2dȽ{455ckkkڹ[m߷٩jZZ{ʮoƏ?gΜL.[\\x {۶m322ܟmۦb>wwwv A|C-VXAY`AYY8͓ڵk߼y0̖-[U 4Q>ٳwŋiӦӎ Ԕ!EEE 2rssǏҪ/,]\\^|YYYoqܹEEE_gii[~wl,yfÆ 4g[^f̓lll m'*++ťW^7oޔ_lٳgϜ $@]S>~ؘ^ZUUUOOOOOO]]}ժUimϞ=rrr 255_`AϞ=Ӏ{{{ 2DVVvĉvvvy.]t]WW Sչ-\w!@P7nXZZh"%%EUUv/22rő/^? F[l)++tqq믿9@۷oSNݷo,EFF*))Yh,",""ڵk&&&o޼5kΝ;?L;/^_^^'''&"@ "PGAAnݢETXZZFGG믯_vqqر]***hG |ooo5k|޽{֦ I2PڴmvҥWWWvјF{CCt''N:ٳ]A^^[ðaâ^jllL;@&##C0ZE;ܺw@;haÇǏҥKZZڌ3:vsNyyykk+WЎa ](wp8m۶1 z꯷A?~|BB∋K'4o.ZHGGgڵlA$&&(4!DsmMMͯw)j~wԼ,gjj:mڴ?Ξ=v%..>y'O8qGyJMMuuuYn]iiقH>}hGhV؝?X@.\uVm`* Bt9??ٳ4zwB͛7o?0 ŋiFVVޞ?5a7o^LLg9EֹS***<==UTT֯__{_7^^^>sL''t_|yʕ]JJJjkk޽[5==DxAbbw"|ԒFq,u%&&sN~ǏӎaffvʕL8Q\\ܹsӧω'haW\\~z===VZzjA *,,srr"s:x<r߃rrrdddO{n\\Y666999:ud_|ybbb|||ZZZFFwg}%KRRR?K~?e__ߒ𘘘oF !seKo6lwKPKF Aϟ߲eK[GGgӦM߿ ы/͛עE C[[{Æ %%%sXBq=!J qii)VRR{ʿ,33fPZZk.99 jjy}t_ݻwiu줉>}Գ-oR8vXQQ-..?"5_S)))qrr7TpJKK/r.]RSS=O-8{x{>wܠA/eK-ߓ/FE?ֶŅvqpp 5j=v옙}KX(└-ZˣGݛv:QWYYItG/]\\^|YYY`oo>~(-----_/8w܌"k„ %nnnuӽ{M>}`a?wÆ }:ymر#G3giGݻ}ܹ3%%pF:dȐ/2N<9~cǞЎ?LYYyɡ'Nzرc۷os;x<^hhɓ۶m;{VZ?ׯ_b#@B-@"P/-222JOO_d ,8pÇ3337mԵkלkvyРAƶ5B(##c>|ϟ-,,;Ɏ e*Wtt0111={@ttݻ?^ZZJQTT7nξдϟ?ٳڵkUUUMMiӦM6MKKv:...;wo1 ޽C?ø;ܓP".vIjUm_Fmvmmk[Rm rK"r1.1?w;?!x=q3sf44Z}Mؖɇ#";t萙Y||qk{.$$̙3O'M}Ҏ$@`%%%=<|ޞ8\nTTTppŋ+**4iҜ9s\\\dee RL_BΚ5kC6t-wPM}ҎE0ƈ#ZZZϟO"F;ĉEEE.]6mFy󦧧 ȹH>|f6lHIIQVV^xqxx8ܹs'j" ~}vxshǏKJJ>gi4Yn:F߿_GGH3''gԩ%%%d&555ee} B}}ŋ}||x3!\HII144cfۋMukhh?l߾}ko w_~@JJ4**ٳFFFd;o655_?>>Aߐs\W;vHLLLJJb222՟mͶqR&ŋ;^^^SLuփ*++y ?ϝ;w˖-Ցqqqܼyѣ~~~d&f7m^RF'(뤤HKKh[nQzNaaClmmy 7|{.&wѣG󾲐}h[[[[PPtRSTZZj*//Iw[iq>| >}S۷oF}||着*tL0ARR {z8tЗo?!ٱc\ʕ+⺺=秢? Bmm ,XP[[qT~Ϟ=%%%\.fϟw|hl{:N=~uuusΕQWW߽{7g{gh᧞ccc:>dȐ;w|s[(**4h֭[;/))it:]OOرcܵkyooﺺIw[oHBvd---Ǐ :^zƍ,+"" !`X[[;99M4r力W_}uM^(@KLLijjqW_}EuJՏ?~Qxxg8.))9bkkkMMMjskiiyullltttdddNN)yy1cٍ93g+Wܿ?Yz/E'ܹsݺu/^۷/q@UVVFFF>z(**U3f1c3bcc⪪xO)++ّ0'SN9r?Yz/E'899=|pɷn€s*555 111o߾=%''giiiiiiaaaaaahh_< q8nfa IDATO6B044=zQ@92!!!**j̘1TgPRPP`nn^^^~+VPDMKK#/MKKSTT1bſP%USSS^xQSS{`XZZb=Z]]¨{455755UUUQBYz9ԓ'Oͩ""!!ӧO>MHH`XϒUaÆ 2DJJRSS={FB^|Y__{VLLzذa:z^rr1P?yB'U߲2gtСCutt )===%%իW)))L&7//At:Ą.DPܹsޞ!!!TgPݻ#Fdgg/[ѣTǁ+77711իW^zevv6h TTTjlllddddddllܿ OMMMfffVVVZZYIOOojj#++;h 333bff`0 ЦUV8p`׮]k׮: @W^YYY֞9sf޼yT OMMMNNNIIyW Z'K$FFF8p8%{,4 }Ç|(DsaՕ\xmYYYqq1Y)--` :::ח1O^h a1PCZZ… .\_lՉ>UIS齿p֭[E@m߾7jMM qQll޽{W^Mu^X@,ӯB/EYn:&h舉?/))9|ϟ[M/VTTTTT񩯯k ,hhh Ax7~)))SSӨgْ?Ϗlə:usIII; |g8 (ee .0#GP .&L@ر#111))f:?y$))|]QQ1c  6ffffddmܸTdddrr2dٛ6m"zy#""***Lr֭TVVpuIuʕ%%%l6߿v3 \p&L[.Avy///qq[n999Qh4:k׿{1Al6ܼ\PP[\EE ZEEŦW^KK+<<-##b333 tppp#4LUU #++_vmDD|`#mmAٳgSet?壢̨5h4ӧ+WFJHH477!WhtzCC8A222EsssE:=55uaaazzzdKLLL@@gȹ>z lDIIɇ p u-Zzԩl68]fgΜ>}ɖ~X,e&Kf:V`2rNN:)^{nn'e773gj"AxxxTUU}AxD^A3i$D " ;6nܸ7o޸@S^vmgϞ%bҥ>>>ɞOZ}̙t%%%~~~KKKKKKWZEKRzz'Mnݺ1c7IKKKKK3L Aiۃ)WZ/^$ݝ " $$$.]4`ܘDرc>|?ٳ'00ё`xyy͘16Io߾]CCPSS=ekkkjjuVq͚5_^\7o^;ќ>}_VVvvAxf#""dddNJuh ]xuUUՎ;֭[Guф8ǿiӦ]z,F4hPhh(NBCC)!!!A~!sUVȄ58Eׯ,//_TT$++Kuh E@Ȭ\rҥuuuӧOgXT5t-rbuD0a„(kkP MMM:::l6;::Ɔ8#%%u##Yfq8l6{РA,E@(Ç:::׮]7o=:s Aͣ: р{رc+++7mڴeOQQ.FР: E@^|YJJj֭G:'NDM@,mgΜh~~~W^:@hjjWSSSVV޷oXWW-++۷oݻwh4F߿_GGGLL ŋ+*****ݻwkhh.Xl}G b8Α#GAYެYnpbbb𥂂RRR={d2yw޸qcUUUnnnrrrTT'O$%%aÆ̌7~twL&foڴl;w-[###D͛AM2,-"ѣ}􉊊266:yvddAYYY$߽i4ZAA&MKK+<<|Addd888hLCCC 233 ]vӵ{D̘1cbbb;"e믿v횁ALL .ǻ7a:^__O[5644,!!koU7E?m۶(**8pյO芏RUUc0T"Ν377N>|_~999544޼yC.涷Y &I.䨫t:\.//_?77WO9rKKK>)GÇ X`j"e nӋvww':˗/gXV"===W^]VVVVV޺3g\reIIIII'nnnwڂK_ZZZZZj*///q֬YMMM\.{@4]pNR>e)?q͛ dnnn``K6n۶MNNNWWwȐ!Ghs۷khhjjjdS^8fUlmmMMMUUUnJ6N6MVV6 88ێ@8q񫯾AE@ۗgϞ%o\ 2ҦL6՜#޽{_QQ9vX"!Cܾ}[^^?:@XjUEEEqqիONuhÁ***P(h9rիW9».@ 8PII rAQ: çA]voinn޷o_T|~iƍ{YPwܹyqԩ RDSuuu n xpp~[YYF?~\___RRrϟ?絓 MMMjjj#9κu guu55z۷WVVN<ޞ,P^DLL̙3syݤI>}Ju"GGGWTT̘1ǧճAAA)))Ϟ=c2,lܱcGbbbRR͖ QVVֱcvMuz߆())j@4\EE ZEEEr<ޔ:::<022_K__ݻAlssb*P$===--| ?@۬YΟ??w,90Zzqq?c̙NNNIIIT'ވ6%k"A0V}8BAA ;}tyfh%66644TFFfǎTgτFdedڴiU^::}ƺcXͶtEL͛\Y3,ą \]]KKKSRRNY˗/gXV".]㓝ܜImHwڵ0UU 6P>"{IJJ?~ܸq%%%L&DyA꒍ kƌԆm߿%bϞ=}:|>L ]MMǏuuu{6LЭ^z99`(&&&v? W\:.}tpTTT,[ m۶ߟ8ePhF;t… g͚u ֬YSTTdggb @WBYmbbbNAo[__xbEEEEEEz^ǏKJJ>VljjWSSSVV޷op֭[`0<=={؝;w~799gϊ3HK]4رc;wp8s :.6l(,,۸q#芊3fZ1(((%%ٳgL&b;vHLLLJJb222=w$¬?A7n3k>_hQsst+ZZZ$"##qhr jkkWyf޽kllL677/..&&&iiio8 ͙3?5jTLL8qѩ <==9RQQq Cp]Amfl6Y!`477Z"OAAɇ'O?/DDiӦݾ}[AA?1cFmm-Չ@qA &I.䨫wrbz/^qP"5nܸ?Ǐ///:3g\ӳ+z{{/_bUVVZl\tOvvvsssrrr;zyyϝ;w̙T'0`@||o2K}v CCCCCCMM+n޼yРAdc``#1cF&&&ǎ: t#L ޼y3qČ ##wSWÌ 󄄄xyyIKK:8Ѝ0ZEEEYXXdddھxDeVXA֭[Qy(|55G;ݻT'.PWWVVV6}5kP"I^^s̩ꫯ9Bu""\.wΜ9=@o瓔o<pBP^O>/((r Չzׯ_O8r{:P enz޽|AAA\.PQjj}iiYΝ;'&Dzov11-[ ) IDAT5 '降@O a0NNN,DEnn &Mt% @@YGGFFjkkGGG[ZZFFFR@YXXJJJR"=ŋ&Mbj[::: 2Ν;T'TTTn߾sN.e˖ӧWUUQ @ۿxbwӧՉ@,@ p5%%ׯ[YY~P"%++#GҢ:EojjneeuW^;6//޽{***T'AQ]]_P @xѱ!,,LIID P\hh't]NZYYIu(acGGǒ;;WR"ڵk󏵵gϨN |.]4q)Sܾ}[^^D P ÇOKK9~8ՉIPPG}}}@@͛7 Չ@С, X 7owM:۷Tt͋/޲e F;pΝ;i4ա@, ptzPPիWUTTnܸ1|XCo_t钟Չ@h, \]]?nccgggk..Ku(f'O|5%%[n͘1D LP\:::'00pT QQQƍ:EN߹s'yAׇ Gu(pԩ1cưX,!CP"B`ԩyyy+ՉظdƀwӇP PFFFqqq>>>uuu/5;l'O2;wtCBY@hHKK8q"$$DII)44ÇTQ/_ׯ߽{fϞMu"n(OOϴ)SO0aɒ%555Tv\.ȑ#VVVYYYVVV cƌ:=E͛7O8!##sIKKDC#F`04Fuб"''˗766n޼9**JSSP PJ4'>><--mAAAO^hѳg:nح[@Fu[%QGGpqqo^6%"33\p555jjj䲶vzzzHuu5k$$$־uՉhe``#a#ׯ_:Tkw!׷U;A111[n?a/!&&p[8y A{ 6`7n&&&M}]B&ou ߿?o\;vF-++#ۋ Ze  .ݻ,Z˗T^eQF|||^xaggWTT4m4/lE$̌N)))_onn.A#G@ΫW[^|K.9륥!bx%3js]w{ rss544e__߂*ޟ~r'pyرcgΜYPP0f̘SN)++S zED߀߿yf /57gr8_dijj4hZt… bcc.\l2Yf655qf*>>>ɞmnu /-----]jXWW'-----d2k(˗/gXV ,޿~#GFEE)++=z4""ܜ\,+HHH=y,++kܸq>>>_>lˑ_N }}} >}JaT㣨dɒ ~ӦMsss &mmm ׌3rakkkjjuVǏ3f ͛ dnnn`` 9bhhO?S/[L\\hK>{;{n۶o߾4˧q?߁:ڵkcgg:^7ii醆Z&abbFN- F8$8/.aÆl vmeeEm*]$$$֭[V\\11:%%eƍÇ՝={vDD͛ ?ZHdmm&//uŋ&{ ,YJ/^gCt_2J^ wݾ}Ǐ PPP]rZ<ӧɓ'GqCr7nܰW\8p~]xW8d2׮]+//Ou:CY11s禧XСC&&&Ν:ﻺرl„ ׯ_/**c0T,E ***.((vtt|5չb%%%?aÜn޼)..>s谰0WWW11|!-h׈#wuu{}[ss͛7ܴW^ 6?ƦtE#4mܹ ,p8}jDFZZڈ#\\\"""듔d555WW+WHHH|aaa۶m]e8uuӧOXZZfff:;;c;;LUUVfgg޽{'O4hݻϝ;7a\/eYVVVO<9v옢?cff[VVFu.dv?~|iiԩS?njjJzj֭Æ 344 x䉒ҢEbbbRSS}Rk,@LL G :tJccŋ9N@@˗ F\\MLLy/^(++ϛ7͛l6ԩST4.KuJyyy6lrZZZ6mZp8չ111IOOOKK366: ia_utt4طo̝;w***ӦMswwwtt4;@O@Yȓ'OV^MĈ#~g{{{C@@_ IIIӦMcX 'Ovuuutt63@OSǏ/]4n8R Ν;fͪTPPxݻwdddƍ7yɓ'Q[Fsww? ##sM33oR  ._RYYIĻw رco߾k.___D7E4Е?EIIi͚5~~~rrrTQh:{pȈ렪jkk;n8;;;sssL "2226lp%.|իWS D"hMG"""rssy/_1c)tm۶]x 55իW\RJJ\ jPG_ yyyd}$***::ZUUP e^сQQQA_@\5f̘Ǐ :͛7K,133x"j@9E'L0!!!Ǐ:j(@ںuRDD>|Hu4P%''qFu~EFF:::6 @CY( ++痙IG^x0E2dq$++/_077Gqz"@1痖cǎ>}$''{xxܹsh P //n:&sN558ggg .p8hBY\@@@NNΞ={455fΜibbrɆAYܚ5k_niidmmr@YӧO?~RVVe===??|(@Y7\\\jkk:4`s禦R " ƌsƍof͊:+E@XZZ^x .ϟ=ztHHHSS@Ƞ,_e䉗V`` CY_vv'LPZZk.CCC@YyÚ$qq/3ܹs:Ç8q"--mڵʉރ޷o_YY@,"EWWw׮]ˠA֬Y={H`AYDҥKSSS|||t_eoobee'N(((8q℩)ڵk׀<<<߿rTBYDϋ/n޼W_577_xѣUUUTj,W_}u͜khh$''jjjΝ;Ç---Tg:zzz۷o upp? 6oޜ]LZp jkxZ`Ѧql˲zdmbY͚lK͖nM%͖uKnN]EUT__*80<~/>~ܻw\VcY_mtԩSo߶X,<}H$jiibXkbRTN… aqq~z+++G uX觟~gϞ>dj/tݙbՕ9ΞV388J$_L@:4ܹs>d2Lw}tvכ2Y x"4 A\.׏?LN>-H/^8332) x:w~W~g߿KX^+..6}<#otuuMNN;w)Htuu B^W_ݽ{7e(///++;ulN$`@ UG]^^t M$tRgggr‘cǎF@3[!&gX4 S( B.+ʪ*xʑ.6TM@zEE ׮]|r#GF( 3[!洼,&&&Ri4ZYYRd\wެ,bf+W:;;F*Z]]m4=j2[$rϟ8q  l6bjfp80Xbl4FF.tѦV\|رcdҒf#ҥK 7 @zE2&vvv^rr4ÇtΤDQ3???>>ٍ7Fԑ#G:::0H@zE6yy1''H4ͮ]2[$x<=ܿB?&^vpĉ?CNN>pX`sF}}}ݷn݊d;zɈdT*5u#p8p#3˫[o}x0H@zE6?Hv{z^o0 j( ]~+dhh(IAA`hllllld$///\xwމb_| b.l ǓlgX:NzFC>S9 [D"A3 NРRR>B144z…X,+¨ \ !z޽k2_n6S8jAi4hD~2 ]wj O>9s BRgΜ裏Cbqvvt"[^ZZB,"[ۃf3ܹs'9 BZVR1 ;͛7oܸOD,K:NR=Ia6z}VV7|sUȺvX`-E龾>d2+P(o>FjZR^NbX,P(FUUU:tСCO{x<.Ϟ=ҲVg`gTkA,=---q0=VkIDATݞǁBh4JT*zqO Ç{lZcv.K"66Q*r\P(c#خ777gvȈj{|Ҳ0cEQt:].x<vIRE"QEET*r\ dlXؘf#QV,|@(D"P(6dnnn||?H]?ÑJ2,HRV!u)Q霝]%dJ"#D"QQQ/**|NNN>xNLLkabqiiD"H$RllvE_ ɔd||vONNnɎ4/d0Ɉx⢢"Wf ;uǃÇ`F0 _}>ߊ%`V|> >RdeemleE |>LLLx<z^Ox6d2L&b6yyyvb24JlUT*b=yH$P(D~q D"H8 BHdnnnvvvffBOv|> ⒒'|>rOxXbؼfggɈ$|>~盚 p8ӕ>5#ɟ$.KdR?K[5rIENDB`poelzi-ulatencyd-55515a9/docs/architecture.svg000066400000000000000000000260021154664534000214250ustar00rootroot00000000000000 Linux Kernel Collect informations: - proc - netlink - (audit) Core: - process tree - process tagging - process manipulation -... Scheduler Lua / Module Rules Lua / Modules Lua Bindings Kernel optimization: - cgroups - nice - ionice Userspace Lua Bindings ulatencyd Modules - dbus - xserver observation -... Effect - information receival - interface for user wishes - interaction with other daemons like upstart poelzi-ulatencyd-55515a9/docs/doxygen.conf000066400000000000000000002046341154664534000205570ustar00rootroot00000000000000# Doxyfile 1.7.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = ulatencyd # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = docs/api # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, # Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English # messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, # Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, # Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = YES # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = YES # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = YES # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given extension. # Doxygen has a built-in mapping, but you can override or extend it using this # tag. The format is ext=language, where ext is a file extension, and language # is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, # C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make # doxygen treat .inc files as Fortran files (default is PHP), and .f files as C # (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions # you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. EXTENSION_MAPPING = # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate getter # and setter methods for a property. Setting this option to YES (the default) # will make doxygen to replace the get and set methods by a property in the # documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO # The SYMBOL_CACHE_SIZE determines the size of the internal cache use to # determine which symbols to keep in memory and which to flush to disk. # When the cache is full, less often used symbols will be written to disk. # For small to medium size projects (<1000 input files) the default value is # probably good enough. For larger projects a too small cache size can cause # doxygen to be busy swapping symbols to and from disk most of the time # causing a significant performance penality. # If the system has enough physical memory increasing the cache will improve the # performance by keeping more symbols in memory. Note that the value works on # a logarithmic scale so increasing the size by one will rougly double the # memory usage. The cache size is given by this formula: # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols SYMBOL_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespace are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen # will list include files with double quotes in the documentation # rather than with sharp brackets. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen # will sort the (brief and detailed) documentation of class members so that # constructors and destructors are listed first. If set to NO (the default) # the constructors will appear in the respective orders defined by # SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. # This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO # and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. # This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. The create the layout file # that represents doxygen's defaults, run doxygen with the -l option. # You can optionally specify a file name after the option, if omitted # DoxygenLayout.xml will be used as the name of the layout file. LAYOUT_FILE = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = src src/proc rules modules client # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 FILE_PATTERNS = *.lua *.h *.c ulatency # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. # If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. # Doxygen will compare the file name with each pattern and apply the # filter if there is a match. # The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = *.lua=scripts/lua2dox/lua2dox # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. # Otherwise they will link to the documentation. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = YES # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 1 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. # Doxygen will adjust the colors in the stylesheet and background images # according to this color. Hue is specified as an angle on a colorwheel, # see http://en.wikipedia.org/wiki/Hue for more information. # For instance the value 0 represents red, 60 is yellow, 120 is green, # 180 is cyan, 240 is blue, 300 purple, and 360 is red again. # The allowed range is 0 to 359. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of # the colors in the HTML output. For a value of 0 the output will use # grayscales only. A value of 255 will produce the most vivid colors. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to # the luminance component of the colors in the HTML output. Values below # 100 gradually make the output lighter, whereas values above 100 make # the output darker. The value divided by 100 is the actual gamma applied, # so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, # and 100 does not change the gamma. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting # this to NO can help when comparing the output of multiple runs. HTML_TIMESTAMP = YES # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. For this to work a browser that supports # JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox # Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). HTML_DYNAMIC_SECTIONS = NO # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. # See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.quamquam.ulatencyd.docs # When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. DOCSET_PUBLISHER_ID = org.quamquam.ulatencyd # The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. DOCSET_PUBLISHER_NAME = ulatencyd # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated # that can be used as input for Qt's qhelpgenerator to generate a # Qt Compressed Help (.qch) of the generated HTML documentation. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can # be used to specify the file name of the resulting .qch file. # The path specified is relative to the HTML output folder. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#namespace QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc # If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to # add. For more information please see # http://doc.trolltech.com/qthelpproject.html#custom-filters QHP_CUST_FILTER_NAME = # The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see # # Qt Help Project / Custom Filters. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's # filter section matches. # # Qt Help Project / Filter Attributes. QHP_SECT_FILTER_ATTRS = # If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can # be used to specify the location of Qt's qhelpgenerator. # If non-empty doxygen will try to run qhelpgenerator on the generated # .qhp file. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files # will be generated, which together with the HTML files, form an Eclipse help # plugin. To install this plugin and make it available under the help contents # menu in Eclipse, the contents of the directory containing the HTML and XML # files needs to be copied into the plugins directory of eclipse. The name of # the directory within the plugins directory should be the same as # the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before # the help appears. GENERATE_ECLIPSEHELP = NO # A unique identifier for the eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have # this name. ECLIPSE_DOC_ID = org.doxygen.Project # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to YES, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). # Windows users are probably better off using the HTML help feature. GENERATE_TREEVIEW = NO # By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, # and Class Hierarchy pages using a tree view instead of an ordered list. USE_INLINE_TREES = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open # links to external symbols imported via tag files in a separate window. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are # not supported properly for IE 6.0, but are supported on all modern browsers. # Note that when changing this option you need to delete any form_*.png files # in the HTML output before the changes have effect. FORMULA_TRANSPARENT = YES # When the SEARCHENGINE tag is enabled doxygen will generate a search box # for the HTML output. The underlying search engine uses javascript # and DHTML and should work on any modern browser. Note that when using # HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets # (GENERATE_DOCSET) there is already a search function so this one should # typically be disabled. For large projects the javascript based search engine # can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a PHP enabled web server instead of at the web client # using Javascript. Doxygen will generate the search PHP script and index # file to put on the web server. The advantage of the server # based approach is that it scales better to large projects and allows # full text search. The disadvances is that it is more difficult to setup # and does not have live searching capabilities. SERVER_BASED_SEARCH = NO #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = YES # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. # Note that when enabling USE_PDFLATEX this option is only used for # generating bitmaps for formulas in the HTML output, but not in the # Makefile that is written to the output directory. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = YES # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO # If LATEX_SOURCE_CODE is set to YES then doxygen will include # source code with syntax highlighting in the LaTeX output. # Note that which sources are shown also depends on other settings # such as SOURCE_BROWSER. LATEX_SOURCE_CODE = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. # This is useful # if you want to understand what is going on. # On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = NO # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is # allowed to run in parallel. When set to 0 (the default) doxygen will # base this on the number of processors available in the system. You can set it # explicitly to a value larger than 0 to get control over the balance # between CPU load and processing speed. DOT_NUM_THREADS = 0 # By default doxygen will write a font called FreeSans.ttf to the output # directory and reference it in all dot files that doxygen generates. This # font does not include all possible unicode characters however, so when you need # these (or just want a differently looking font) you can specify the font name # using DOT_FONTNAME. You need need to make sure dot is able to find the font, # which can be done by putting it in a standard location or by setting the # DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory # containing the font. DOT_FONTNAME = FreeSans.ttf # The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. # The default size is 10pt. DOT_FONTSIZE = 10 # By default doxygen will tell dot to use the output directory to look for the # FreeSans.ttf font (which doxygen will put there itself). If you specify a # different font using DOT_FONTNAME you can set the path where dot # can find it using this tag. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = NO # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, because dot on Windows does not # seem to support this out of the box. Warning: Depending on the platform used, # enabling this option may lead to badly anti-aliased labels on the edges of # a graph (i.e. they become hard to read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = YES # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES poelzi-ulatencyd-55515a9/docs/run-game.1000066400000000000000000000010621154664534000200160ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .TH ULATENCYD 1 "March 24, 2011" .SH NAME run-game \- executes program under GAME scheduler config .SH SYNOPSIS .B run-game .RI [ COMMAND ] .SH DESCRIPTION \fBrun-game\fP is a simple shortcut to start a program using the GAME scheduler configuration of the ulatencyd daemon. .PP \fBrun-game\fP is a shortcut for .B ulatency run-GAME COMMAND .SH AUTHOR ulatencyd was written by Daniel Poelzleithner . .PP This manual page was written by Alessandro Ghedini . poelzi-ulatencyd-55515a9/docs/run-single-task.1000066400000000000000000000011371154664534000213310ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .TH ULATENCYD 1 "March 24, 2011" .SH NAME run-single-task \- executes program under SINGLE_TASK scheduler config .SH SYNOPSIS .B run-single-task .RI COMMAND .SH DESCRIPTION \fBrun-single-task\fP is a simple shortcut to start a program using the SINGLE_TASK scheduler configuration of the ulatencyd daemon. .PP \fBrun-single-task\fP is a shortcut for .B ulatency run-SINGLE_TASK COMMAND .SH AUTHOR ulatencyd was written by Daniel Poelzleithner . .PP This manual page was written by Alessandro Ghedini . poelzi-ulatencyd-55515a9/docs/ulatency-gui.1000077700000000000000000000000001154664534000226132ulatency.1ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/docs/ulatency.1000066400000000000000000000021141154664534000201260ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .TH ULATENCYD 1 "March 02, 2011" .SH NAME ulatency, ulatency-gui \- client for the ulatencyd daemon .SH SYNOPSIS .B ulatency .RI [ OPTIONS ] .RI [ COMMAND ] .RI [ KEY [ VALUE ] ] .SH DESCRIPTION \fBulatency\fP is a simple command-line\/qt4 client for the ulatencyd daemon. It can be used to show and tune the ulatencyd status. .SH OPTIONS .TP .B \-\-version show program's version number and exit .TP .B \-h, \-\-help show this help message and exit .TP .B \-\-config\-list list system configs .TP .B \-s, \-\-set set key value pair .TP .B \-g, \-\-get get value by key .TP .B \-\-gui enable gui .TP .B \-\-no\-utf disables utf8 for tree view .TP .B \-\-no\-color disables color .TP .B \-\-all show complete tree .TP .B \-\-flags show flags on tree output .TP .B \-\-cmd show cmdline .TP .B \-\-no-processes hide all processes .SH AUTHOR ulatencyd was written by Daniel Poelzleithner . .PP This manual page was written by Alessandro Ghedini , for the Debian project (and may be used by others). poelzi-ulatencyd-55515a9/docs/ulatencyd.8000066400000000000000000000017761154664534000203160ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .TH ULATENCYD 8 "January 28, 2011" .SH NAME ulatencyd \- latency optimizing daemon .SH SYNOPSIS .B ulatencyd .RI [ OPTIONS ] .SH DESCRIPTION \fBulatencyd\fP is a daemon that controls how the Linux kernel will spend it's resources on the running processes. It uses dynamic cgroups to give the kernel hints and limitations on processes. .SH OPTIONS .TP .B \-h, \-\-help Show help options .TP .B \-c, \-\-config Use config file .TP .B \-r, \-\-rules\-directory Path with .TP .B \-\-rule\-pattern Load only rules matching the pattern .TP .B \-v, \-\-verbose More verbose. Can be passed multiple times .TP .B \-q, \-\-quiet More quiet. Can be passed multiple times .TP .B \-f, \-\-log\-file Log to file .TP .B \-d, \-\-daemonize Run daemon in background .SH AUTHOR ulatencyd was written by Daniel Poelzleithner . .PP This manual page was written by Alessandro Ghedini , for the Debian project (and may be used by others). poelzi-ulatencyd-55515a9/docs/wiki/000077500000000000000000000000001154664534000171655ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/docs/wiki/Adjusting-default-scheduler.md000066400000000000000000000070341154664534000250410ustar00rootroot00000000000000The default scheduler (`rules/scheduler.lua`) uses a decision tree. You can have different configurations that are set in the `ulatencyd.conf`, but only one can be active at any time. The scheduler finds it's decision by traveling down the rules top to bottom and checking each entry. The first rule that matches will be used. If the rule contains children rules, these are checked in the same manner. Lets look at the [default config](https://github.com/poelzi/ulatencyd/blob/master/rules/scheduler_desktop.lua): SCHEDULER_MAPPING_DESKTOP["cm"] = { { name = "system_essential", cgroups_name = "sys_essential", param = { ["cpu.shares"]="3048" }, label = { "system.essential" } }, { name = "user", cgroups_name = "usr_${euid}", check = function(proc) return ( proc.euid > 999 ) end, param = { ["cpu.shares"]="3048" }, children = { { ... `cm` is the cgroups toplevel tree configured in `cgroups.conf`. If no cgroups subsystem is available from this group, the tree is skipped. The scheduler will start looking at `system_essential` if it matches, it will use it, if not, `user` is checked and so on. # Entries of a map Lets look at at one entry: { name = "sometest", cgroups_name = "test_${euid}", label = { "testflag" } check = function(proc) return ( proc.euid == 1020 ) end, param = { ["cpu.shares"]="1001" }, } * `name` is a human readable entry, not necessary but good to have. * `cgroups_name` is the name to use for creating the subdirectory in cgroups. If it is a string, values written with `${NAME}` will be substituted by the values of the process object. If it is a function, it gets the process as first argument and must return a string. * `label` is a table of strings that are checked against the flags of the process. If any flags name matches any entry of the label list, the rule matches. If label and a check function exists, the check function is only called when the label matches. * `check(proc)` is a function that will be called with the process. If it returns `true`, the rule matches. * `param` are the default parameters that will be used for this cgroup. They represent the cgroups subsystem values set for the subsystems mounted. `cm` for example mounts the cpu, cpuset and memory subsystem. Names starting with "?" will be silently ignored on writing errors. `cpu.shares` represent the amount of of cpu power this group will get. For values see the cgroups documentation below. * `adjust(cgroup, proc)` is a function that is called with the cgroup and proc object. It can adjust the cgroup parameter for better fitting. Important: this function is called everytime a process runs through. * `adjust_new(cgroup, proc)` this function is only called once a new cgroup is created. When a cgroup does not have any processes anymore, it is deleted and the adjust_new function will be called again. # If this does not fit If the default mapping does not suite you, write another. It would be great if you share it by adding it as a ticket. If this rule based approach does not fit you, you can replace it with something different by editing `rules/scheduler.lua` # cgroups documentation * [cpu scheduler](http://www.kernel.org/doc/Documentation/scheduler/sched-design-CFS.txt) * [cgroups docs](http://www.kernel.org/doc/Documentation/cgroups/) * [blockio docs](http://www.mjmwired.net/kernel/Documentation/cgroups/blkio-controller.txt) poelzi-ulatencyd-55515a9/docs/wiki/Architecture.md000066400000000000000000000000641154664534000221310ustar00rootroot00000000000000# Architecture [[/docs/architecture.png]] ## FIXMEpoelzi-ulatencyd-55515a9/docs/wiki/Faq.rest000066400000000000000000000117601154664534000206000ustar00rootroot00000000000000FAQ === .. sectnum:: .. contents:: Table of Contents :depth: 2 How can I see if it's working ? ------------------------------- With ulatencyd 0.4.8+ you can type in your console :: ulatency tree And should get a nice tree like structure like this: :: /sys/fs/cgroup/cpu └─┬»cpu« ├ 2 kthreadd ├─┬»s_ul« │ └ 4975 ulatencyd ├─┬»sys_bg« │ └ 1928 cron ├─┬»sys_idle« │ └ 3036 preload Alternatively type: :: ps xaf -eo pid,session,args,cgroup You should get an output like this: :: 8127 2824 /usr/bin/python /usr/lib/ub 3:memory,cpu,cpuset:/u_1000/g_2824?2:blkio:/ps_2824 8131 3171 /usr/bin/konsole 3:memory,cpu,cpuset:/u_1000/active?2:blkio:/active 8133 8133 \_ /usr/bin/fish \_ 3:memory,cpu,cpuset:/u_1000/g_8133?2:blkio:/ps_8133 9843 9843 \_ /usr/bin/fish \_ 3:memory,cpu,cpuset:/u_1000/g_9843?2:blkio:/ps_9843 As you can see in the last column, these are the groups the process was placed in. To test if the X11 plugin (xwatch) is working, then there should be processes in the active group. If your ps does not support cgroup output, you can type: :: cat /proc/self/cgroup And should get an output like this: :: 2:memory,cpu,cpuset:/usr_1000/grp_26561 1:blkio:/ps_26561 Are you trying to do scheduling in userspace ? ---------------------------------------------- NO. That would be nuts. You can't write a scheduler in userspace. ulatencyd just uses the interfaces the Linux kernel already provides to adjust the parameters of the scheduler and other subsystems. Adjustment is something that should be done in userspace and does not need to be done very often, so the resources required by ulatencyd are quite low. What causes conflicts ? ----------------------- You will not be able to run a different cgroups agent in parallel. * cgrulesengd (from libcgroup/cgroup-bin) Does it work with systemd ------------------------- Since Version 0.4.5 ulatencyd works with systemd under two conditions: * DefaultControllers should be unset. In `/etc/systemd/system.conf` set `DefaultControllers=` * The PAM-systemd module should be disabled, or set "controllers=" Both options would cause constant moving of processes as the layout of systemd and ulatencyd (in the default configuration) differs. What kernel options are required ? ---------------------------------- At least those are very useful: :: PROC_EVENTS=y CONFIG_CGROUPS=y CONFIG_CGROUP_FREEZER=y CONFIG_CGROUP_DEVICE=y CONFIG_CGROUP_MEM_RES_CTLR=y CONFIG_CGROUP_MEM_RES_CTLR_SWAP=y CONFIG_CGROUP_MEM_RES_CTLR_SWAP_ENABLED=y CONFIG_CGROUP_SCHED=y CONFIG_FAIR_GROUP_SCHED=y CONFIG_RT_GROUP_SCHED=y CONFIG_BLK_CGROUP=y CONFIG_CFQ_GROUP_IOSCHED=y I get WARNING: binding sk_nl error: Bad file descriptor ------------------------------------------------------- You are missing the `PROC_EVENTS` option in your Kernel. Enable it here: `Device Drivers -> Connector ... -> Report process events to userspace` I don't have a swap ------------------- Having no swap device is not a problem, but the memory protector which runs in a 1 second interval may not have the chance to detect memory pressure in time and therefore the OOM will kick in or the system will freeze. Due the new dynamic swapiness of groups, having a swap is now a very good idea and will not cause a laggy behaviour after long periods of idle. Does ulatencyd support my cgroups mount points ---------------------------------------------- The problem with the custom mountpoints is, that not each subsystem supports hierarchies. For example, the blkio subsystem only allows flag groups, while the cpu and memory do. As cpu and memory are the most important systems and using different mount points would be possible but not that useful, ulatenyd uses a default schema: */dev/cgroup/[MOUNTPOINT]/* Where the current subsys are: +------------+--------------------+ | Mountpoint | Subsystems | +============+====================+ | cm | cpu and memory | +------------+--------------------+ | io | block io | +------------+--------------------+ Changing the layout is possible but requires adjustments to the scheduler config in rules/scheduler_[MAPPING].lua and is not advised. When you only want to change the mount point you can do that in ulatencyd.conf Building a Debian package without upstart ----------------------------------------- You can simply delete the upstart file before building the Debian package. :: rm debian/ulatencyd.upstart dpkg-buildpackage Doesn't build on Mandriva ------------------------- I looks like the lua package of Mandriva is heavily broken. Install lua5.1 from hand and try again. poelzi-ulatencyd-55515a9/docs/wiki/Flags.md000066400000000000000000000037361154664534000205540ustar00rootroot00000000000000# Flag system Processes are usually scheduled according their attached flags. Flags must contain a name and can optionally have some other values attached to it. The name can be any asciinumeric string, please don't use non ascii based strings as lua does not have the knowledge of unicode. The name is based on convention, having a top level category and subcategories separated by `.`, like `user.ui`, `user.media`,... A flags once added stays until the process dies, flag gets removed by a rule/module or the optional timeout hits. Possible values are: char *name; // label name char *reason; // why the flag was set. This makes most sense with emergency flags time_t timeout; // timeout when the flag will disapear int32_t priority; // custom data: priority int64_t value; // custom data: value int64_t threshold; // custom data: threshold uint32_t inherit : 1; // will apply to all children ## user. Indicates a typical user process - **user.ui** - graphical user interface - **user.media** - media players - **user.bg_high** - very important background process for the user - **user.idle** - process that is not necessary and will only get spare resources. - **user.poison** - process that causes trouble to the user & system. Depending on _reason_ - _reason="memory"_ - using a lot of ram in memory pressure situation - **user.poison.group** - belongs to a group of process that cause trouble - _reason="memory"_ - using a lot of ram in memory pressure situation ## system. System important processes * **system.essential** - very essential system processes like X, wayland,... ## daemon. System daemon processes, that run in background - **daemon.idle** - absolutely lowest priority of all - **daemon.bg** - a little bit more important then idle, but no important processes. ## sched. Special scheduling required - **sched.rt** - process must be in realtime group poelzi-ulatencyd-55515a9/docs/wiki/Home.md000066400000000000000000000012411154664534000203750ustar00rootroot00000000000000Welcome to the ulatencyd wiki! * [How does it work](wiki/How-does-it-work) * [FAQ (before you ask)](wiki/Faq) * [Mailinglist](http://groups.google.com/group/ulatencyd) # Package Repositories * [Ubuntu](https://launchpad.net/~ulatencyd/+archive/stable) **NEW LOCATION !!!** * [Arch](http://aur.archlinux.org/packages.php?O=0&K=ulatencyd&do_Search=Go) # For Admins * [Adjusting the default scheduler](wiki/Adjusting-default-scheduler) * [Flags](wiki/Flags) # Developers Corner * [Start Hacking](wiki/Start-Hacking) * [Writing Rules](wiki/Writing-Rules) * Writing Modules * [Ideas to test](wiki/Ideas-to-test) # Specifications * [Generic DBUS interface](wiki/specs/dbus)poelzi-ulatencyd-55515a9/docs/wiki/How-does-it-work.md000066400000000000000000000015711154664534000225720ustar00rootroot00000000000000ulatencyd has 3 different parts: * core, which does process parsing, building a process tree, etc * rules, which categorize the processes, analyze the system etc * the scheduler, which uses the information collected by the core and rules to make decisions on the processes Some settings are adjustable in `/etc/ulatencyd/ulatencyd.conf` and the cgroups that will be used can be changed in `/etc/ulatencyd/cgroups.conf` The core listens on the kernel when new processes are spawned or exit and runs the rules an scheduler on them. Additionally, a full iteration is run every 10 seconds on all processes. This is required for example when flags, set on a process expire and the scheduler will make another decision. The rules and the scheduler can be adjusted by the user to his wishes. * [Writing rules](Writing-Rules) * [Adjusting the default scheduler](Adjusting-default-scheduler)poelzi-ulatencyd-55515a9/docs/wiki/Ideas-to-test.md000066400000000000000000000004411154664534000221300ustar00rootroot00000000000000There are a lot of knobs we can turn: * irq masking. http://www.kernel.org/doc/Documentation/filesystems/proc.txt there is a way to remove irq from processors. most system got more then one, so what would happen if the current active program is always run on a irq free processor ? poelzi-ulatencyd-55515a9/docs/wiki/Start-hacking.md000066400000000000000000000010661154664534000222110ustar00rootroot00000000000000# Start Hacking * Checkout trunk * `cmake .` * `ccmake .` and change DEVELOP_MODE to True. This allows you to develop in your checkout without having to install it all the time. This changes the behavoir slightly: * ulatencyd will not register a system dbus name, but will use the session bus. He still connects to the system bus, too. * make sure if you start in develop mode and wan't to run as root to start with sudo or set SUDO_UID first * run `make docs` to generate Documentation. You find it in `docs/` * run `make DEBUG=1` or `make DEBUG=1 VERBOSE=1`poelzi-ulatencyd-55515a9/docs/wiki/Writing-Rules.md000066400000000000000000000176031154664534000222310ustar00rootroot00000000000000Rules are written in the [Lua](http://www.lua.org/) programming language. You can find good Tutorials and the Documentation on the [Lua website](http://www.lua.org/docs.html). Lua is a small, very fast functional programming language that fits perfect to do this job. ## Two types of invocation Rules contain usually one or more Filters. These filters are called on a process which it can manipulate. Normally these manipulations tagging the the process with a flag that is later used by the scheduler. The second method you have is registering a timeout function that is called independent from the iteration interval. This is useful for example to check the system for specific conditions to apply. The `rules/protector.lua` script for example uses this for detecting memory pressure. ### Filters: MyExample = { name = "MyExample", re_basename = "my_windowmanage", check = function(self, proc) local flag = ulatency.new_flag{name="user.ui"} proc:add_flag(flag) return ulatency.filter_rv(ulatency.FILTER_STOP) end } ulatency.register_filter(KdeUI) This is an simple example filter. Lets take a look what it does: Each filter is a lua table, which is created by `{}` MyExample = { just creates a new variable with name `MyExample`. The `name` is not required, but strongly suggested. re_basename = "my_windowmanage", This is a prefilter for the check function. If any prefilter exist, they must apply to the process for the check function to get called. If you can't create a regular expression to match only on the processes you want, you can still do more filtering on the actual check function. These prefilters exist to reduce the calls into lua, as they are compiled regular expressions that are executed very fast in the C core. If no prefilter exist, the check function is always called. check = function(self, proc) This is the real heart of the filter. It gets passed an process instance which it should analyze and manipulate with it's decisions. The most common case of maipulation is the adding of flags. local flag = ulatency.new_flag{name="user.ui"} proc:add_flag(flag) You can create a new flag with `ulatency.new_flag{parameter=value, parameter2=value2,...}`. This instances can be added to as many processes you like. As they are references, changing a value of a flag will change them on all processes. The return value of the check filter is used to notify the core how the filter is used on future runs on a per process bases. return ulatency.filter_rv(ulatency.FILTER_STOP) This means that this filter will never be run again on **this process**. If you mark a process with some flags that will never change, that will never time out, you don't need to run the filter again, ever. If you don't do this, the filter gets executed every iteration causing to get more and more flags if you don't delete them first. The return value is composed of those values: `ulatency.filter_rv(FLAGS, TIMEOUT)` Flags can be * ulatency.FILTER_STOP Will stop the filter on this process * ulatency.FILTER_SKIP_CHILD Will skip all child processes of the current process Timeout is a integer in seconds, that the filter will not be run again. If you want for example skip this filter for the next 5 minutes on this process you can return: `ulatency.filter_rv(0, 5*60)` If you simply return `0`, the filter is executed on the next iteration again. ## Full Filter definition MyFilter = { name = "MyFilter", -- human readable name used in reporting re_basename = , -- perl regular expression to match against the executable name re_cmdline = -- perl regular expression to match against the command line used min_percent = -- min percent of load the system must have precheck = function(self) -- executed before any process. if exits must return -- true for filter to get run end check = function(self, proc) -- check one process. all processes are checked in process tree order form the top (init) -- descenting all children end postcheck = function(self) -- run after all processes are processed. You can make final manipulations to processes here. end exit = function(self, proc) -- only called when the process is removed from the process list. most functions calls on the -- process will fail. Useful for cleanup data in filters end } ` ## Full Flag definition flag = ulatency.new_flag{ name=[string], -- name of the flag. the convention is to use a hierarchy seperated by . inherit=[boolean], -- will apply to all children timeout=[unix timestemp] -- timeout when the flag will disappear. create it with -- ulatency.get_time(seconds) reason=[string], -- why the flag was set. this makes most sense with emergency flags, but -- can be set to any string value priority=[integer], -- custom data: priority value=[integer] -- custom data: value threshold=[integer] -- custom data: threshold } Prefilters: min_percent = - min percent of of cpu utilization. means load/number_of_cores precheck() The pre filters are checked first, and if they exist and apply, filter is run. If no pre filters exist, the filter is run. Per process prefilters: re_basename = - Regular expression must match the basename of process re_cmdline = - Must match the command line **Warning**: The prefilters can't currently be changed after registering the filter. basename is limited to 15 chars. Prefilters reduce the amount of filter calling by using fast c checks before the check function is called. The check function returns an integer that contains informs the core about two things. An timeout value of 16 bits in seconds when the filter should be run again, and flags about the transversel. FILTER_STOP -- stops the filter on this process, so it is never run again FILTER_SKIP_CHILD -- skips all child process of the current process FILTER_STOP is very important. If you have a static filter that marks the process with a flag and you will never change that label on this process, you simple return FILTER_STOP. The flag will stay on the process for it's lifetime. the return value is calculated with: ulatency.filter_rv(ulatency.FILTER_A [+ulatency.FILTER_B ...] , [timeout]) **warning**: as lua does not have OR on integer values and the flags are binary flags, you have to make sure not to generate invalid flags if you calculate them through addition. ## Important to know: Process objects are shared between all parts of ulatencyd. If you save a reference you do not save the data, but only a reference to it. Accessing values will always be the most recent version of it. If you want create a history of data, you have to store them as copies. If you save references they may be to a dead process, but still there. To prevent memory leaks you should check them periodically if they are still valid. You can check if the process of your reference is still alive with `proc.is_valid`. Best practice is to store your data attached to the process, so it gets collected when the process dies. Use `proc.data` for a table shared between all lua scripts. You should use `proc.data[SOMEUNIQUESTRING]` to prevent clashes between rules. ## !!! Don't do's !!! Try not to fork or execute external programs from rules. This is especially important when you try to detect something like a fork bomb or extreme load... If you store data, cleanup your memory. You can easliy register a cleanup timeout function for that.poelzi-ulatencyd-55515a9/docs/wiki/docs/000077500000000000000000000000001154664534000201155ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/docs/wiki/docs/architecture.png000066400000000000000000001624121154664534000233130ustar00rootroot00000000000000PNG  IHDR54bKGD IDATxy\?3l& `!,AThPVQuC BՏ FqZ֭(q$ K~Qy=1s̙ w( -am^deeyxxh; vh;N :q+W\x#h<==@S o>{l}}zm. -i]S[<}455ȑ#>$ 'q!-.Pv***RSS:ZQQA MMM/]Dt[_h)Z~ -Д|k֬% 0;[ggggz7|/NrƍEO0ȑ#zzz...|ɓٜHmmٳLLL04Eڎ$ׯ?~ӧeees]r… !vBn6ڵkRtSLiHHHHIIyq\\ɓ'򬬬7|… xO?tQBܹs !wf[ػw/!=СCKKK_Ui/Rիn:Xm6D?[XX(WzǷ~BBWzZڙGׯS[Jb}݌Wߟѣnݺ+//3p@BHUU ̈#Ҏ?+_W.UTTܿɓ'ٳgݳ#[gϞetٽ{'OBv?cƌAi:ロ?>;Ν@Dž"NII i2"J8h2ݺu#ܾ}[믿N7ҲgϞ%;p83f̨D8 ˇ Fs"˗/Bh"Fyٳ5kc)^y!sai4CdIII^^^?Idd[y<^s=z4M0@Dž@ׯtT… H333BHVVs7 !/ !NR=\vСC\.wҤIBrss."? ~hh]XXX~~P(LLL{nBBBϞ=[О!-Fx B֭[={&\]]}3f4`iiIq[rJ##+Wݻwo ,>>|'gϞ٣| !d̙ ƍ355e-ZgbXg5rr9skӦM?سgO^^^hhh׮]h"md޽#GڵihNA7.66<(((&&Q˗[XXۗRu77Çs!C>9 4oƄ x<ܫG eeeiӦ)_߽{w jKTW84epF%HKrrg>ӧOgee622j=J4-ee:??eIIA\ajTm޼yȐ!Ϟ=[tÇL(999K.=vXCC wttlP(;w Wu L&ŵYNE]vnpxPlٲ_UPЄHDDv #^{͛7IFڧK.UUUђA544FGG;99i7Bh~wm? d2]@NwBM2eo֗_~T]]mhh8cƌ2 "a[n…aaalyuuuZZܹs.ڋ:D!;v8x !o߾a;vxŋB@ohԮ3pvݝηv!CBVXfq@tH@;"fϞAӧٳgccc]FIINN FFF|2->}ڵkƟ{buuuPP=q͚5l~ ;wSS#F\~]CZڋ .xzzn߾#rrr|MBT*>ŋ999bFFFrE.D믿N>]*ٳgu###?fimm- !"wLʝ;wrOOÇ+ԙ3g>xmBvl))))'N޽;!$!!/$m-!nj#Jnܸ6'BXnk3[:ujxxT*H$aaa?""Ç> Wn뵵 ZLP444ܺuW^ ݛ H$} iЂWzxxٳ޽{|>Qv;;SN}l|Ŋ掎:thZb i'Nhllw70={ܻw'|RYYi`` 衲2666EEEаZN"I͛7@AZZRRҠAnܸѿ@: %O>=~8[DP4hpv-bETk:ٳ8qD:ށڿW:ݻ{{{w֭d!!!5CBBo߾]WWO @#˃rG}t̙}j;`҈ &h;f͚};v|8 ]r'Nmbbe˖ mGP(ի\.7n˵Gz{!$''-3gNzzzэZg{@CZBFFɓ%ˡC vF0ZBXzD"8qbNNN͉AZZQUUԩS EBBO?dbb h^z׿@ox%onTo 2ի'r"0+V"Ǝ+J7ZX]]w֭Ǐ?vP E}W ØL6iҤ#G'''oȉFߊk׮SBnnnk*}s@;={BV\IsRwIMM;{ #@ {=rwQvСCm EBN{EggӧOWר[XX8n8SSS3j(D~mȑ}իիWGFFr|֬Y|>rZ^SS3}tccckkk>7HV}}}tt婩322 ;wlEt҅"~7n:uJ(j;46vذ0D"]]]U<{>>>&M$H233 !eee'NdĔ߻w/66JRHtʕ?_x1''G,s\6lٲʌs5A݄@;~c/Iٴi;sݷz?v\/ڵk>>>\.ǟZw߱III͏6%%eݻw755MHHi9}T*upp@oBZ@'<7߱hѢ-[(OƐBBB!6mtVsssBHUUUCCx1o 0>Ti o߾W*NNNtI,=z͏w 0 cmm9pɓ' гg#G4A݄@+yIu֥KOOϟ~U/DfQFtDMi32m}_FFϧ&&&ڍdggkc`` vYY[>eʔy744TTTtZK. CX@$B+++mmm͖Mɲ)**RMDzzz>|X*n޼988h=Æ 1bD~~'Olٲ~m\.NJ!#׭['Ɋi'p8H6P^^7dȐ&UlH$PZ>upT*Hž$+$$$88uuu ^~VP5%]H9s" b{hZ@jkk---ׯ_O|AVmQ$99Y( 8˪j a7 lݺɩK.;}]zE[ͥg]A5ZvFN4u:EuѣGg7HLLvPmCO4QBȎ;<󽼼'OMII 7666ljS񇷷%WZ%mmmW\IWXannؿC5Ɋ>|8 Ǐ8qqdd޽{_"Tݢ?&:-@PRRPDDѣbqii￿pBZξ%ٍ˗9O<̙C{/// kt*>}l2OOOsO"'Nw^UUժULMM'OѹMW\\lkk˶nݺѦ.۲2L& #ѲM\]m}ل w޼@ntDPMYYY6IL@ӝsqa @k֭T*U{Hz4-4Z@,BxNNNp…h~x͡ieMѾձ4L 0Nt)t\-: iQHBZt"(E@G!- ìYF Ϙ1-߸q!D.Ϛ5`\Nֆ[ZZ_^k!-"H,/Y-?|NNNCC!$&& ??޽{N\\ܵk.]$sP(aٙRPPs}Z^\\lkkK٥B}||h>--W^ڻxa!?`UKv,1 ݹS @E? {nlN"趓X,<`a @kQCQHD7ܹ#lB+++mccSXXE#<<\*J &H$Ihh?- O?~`6SAZ@;' u|ruVZ%mmmW\I˗.]ڧOwww'''6SZ0xurrU`BAxQsCw:.(E#)E@G!-;[&2v @dt*)S/@CoW=H@wn@ǡL`&99Y( 8˴>::ʊWVVgFo[AŋwԺ7С`UHOO?sLyyopp0-xbNNX,r䟯=R uQF}vCj})++ d2>_[[K 'Npuu%bwwR2AalAٖTN}yzzfggkeeeyxxh;VG:rAanŽpQ:MaBx<^]]...ݻj ͡ABȆ P@ky+mllΞ=kggׂa_Æ 377_hQ 6 j[qx.t,hW0@;|\ZnffrmvJII[.Riv$**{</ ח/\(MOO߳gϊ+Z.XhvWOO/:::::Qŋ/^XꆦrkkׯLzBZt"vO0Ez 5ǏOHHhkt&HhAUUUbbuZYMknذe/!-?oܲͲ6ʏkȐ!jttHh#G|}}~~~n+HhAvv!Cqƙr8QFI$Zި[SS3}tccckkk6|/\j7!!-666رc$X,vu$$0 IDATu oXT*\4:J( vL !޾30vHP׮]\.7>>^(6}w}neeEIJJruumH:3h-?To 0>l=zm''s})@h=8w;eʔy744TTTd2]VVַDthڹs|͗ i-;v?VWWs8#rwwud␐|ԩRT"off\?;nCZ@ &Mt̙[nݔpccaÆyyyvqA>ÖXѱCUm…6^&'i?<ך0aZ@>hFJQԣS7 VЎ6V$_: rI&u֭K.?#mBZtEAAaFɓ-[߿_A6!-"...22rΜ9ȠA"r|֬Y|>r,a6nhooGx5!-"--O{*=|NNNCC!$>>ŋ999bFFFE:]QVVfiiо}Mۧ|tÆ )))7o޽iBB?VJCV]ѭ[7TjcczH,;99m'''X|֖...ݻ73z>|?@ DtJS#666EEEБ5sH,!-y %$$l߾ѣG555.\!??0D"HBCC55|\9 -btKZZ{ٵkyIUV gggggg[[ە+Wjj$**{</ ׷h; ø)OP(틿ѭEQfS\nnJӧ$,.zѯ ==x<ѣGǎKw:dllݐZzgώ9rݺuȉ@g^]zrr|֬Y|>rZ^SS3}tccckkkײu3QuR__mee+++iyjjj~BΝ;["D30Bh/_nTAywΜ9_5[W_͙3Gmĉ@4֧-oܸ^OOxhEL4I"dffB&N))))((Ͽw^ll,-J"ʕ+G/ŜXrL``e*++322Ν;r_H3gΔ7Q3))i׮]?!߽{MT8p`̙#hsrru t NOOo…HBB¢E6Ծ}MG˿;<))KIIټysMMMn\.RE5 V;[0LYY!D&Zճ]X|h|@sDEE֪ H$$44]zԩRT"׭['ɊCBBT/|\_^[[P(K@Z"6mڷ~u)STWW=}ܸq1cƮ]-@kS]h3Yj@ pvvvvv]r%-_bc߱c|jQQQÇx|'N466ܻwo[QBPV.]9sfnn]dd'|P(g͚E+oܸ177jt9W5*,,,""b۶m+))quuSnCCtmn4Bafff```jjjdddllp\"C?-N=*:uAVZ:W~}}}h\yU+-BHkcfׯ֭[ lذaÆ rO?mڴB蠀={DDDBcbbRRR>}:nܸ;vBRSS-Zokk c̻r` itQѩ 6lX}}'Ojkk>}ZSS#ɪr9 WG#<K.&&&|>_OO\OOvZi>Em喊 ҵkܴBHFFFnn.!dK,ˈV:$Ĕ^~k׮%]vҥK&&&˖-'˟GFF~嗄1cƈ˗ BrWDr`Z z@/in4_M+++[ijFFF'BjjjLLLfΜgmmDMim 8;;B |||߿OTV:KOOwqq!Bzܦ> &tޝQ4]Eܡ9[:zTtfBZ:-J3.O>m8_=բ\v{i8;ǏB-Z"-1 SWWOr(a``PSSZ@.7ghhd.bŊL>8vXM"lb(MT̈́t:@A4/r\PVzY ^bB&SP(RSSgΜMͧZ:MCHhYZ^:"jjjjyфYJ"G_͛;sL%LxxxJJ !djeBCC̓>Ӕ:ƍ?˟oڴƍVڿ?!dԩ ʡPY^^Jӵ4@+AZjTK'\ji~…N˾.v}ѢE/_={vRRR\\ɓ[v {ɓ/_ΪU͛G{LĖѣK8pɓ ٳ#GZ"j@oi+WBLLLfΜ`GGGmemm-O"H[~@@ d2#===>P(RSSgΜָe-\2"V\yDDeZH$ Ç5@ԩSåRD" S>D˥R hud2YqqqHHr;ׯ_U(uuumzÇi.Z"emm};w,^B6Rsc:;;oڴ)))EO/**󳷷={60Zbc|ۻ_~B[n˗/;v8x a+?~ĉƑ{m[h?fϞmgg_h;߿wt ZhTR?<<|u<==<<<^V3,ipKKKssh+++HMMׯP(d?-T3$-),,7n)5j;Ԣ:((z͚5l}MBh/_nDPvww9Ndddqq]^4'BٳcǮ^:y䊊KFEEUTTFk7nXSS3mڴD:E=Dbxɒ%lsrr!111%%%݋u]v%HTTTD /^#\ndd$- \lYeeeFFƹs(d 0vذ0D"]]]iylllEEŝ;wrssO>Ͷ\Yzz3g}}}"(#Gx{{9̙3fffK.{nBB])~8{ß>}z={Žd%7oN2O>{Q(|󍛛CD#@A4azEAAiyqq-fggBI{{^z)) O8A bwwwVg}6a„ݻ6PL& H322znrqqgi*geNa#|~mmm'EhÆ ? !]taj͕w҅./edddll\k׮{egg?||E J~#Gٳa//3f 0La ix?E0 SWWG?|q\h000Qf`` gzCCC-]NVXǎQʑdffFFF^tJFҳ4+E=?M}iWSSaaa#mnn^^^\4S```@(q\ΘwFS]ϯ ݻwi;w(G^DrԩgKKO>$$$K.5ry4E={XO <::gefƍ XZZ*Ϻ#@P^^sUgf|>?88X.֬Y#g̘QSSC [˳cO)((ݻwoUUUYZZ*bwm@OɓcǎEFF+"pwws̙G-,s@{C@ 6mZBBܹs8pȑ#w޴kkk:{vss;p!䧟~z뭷yn#b. Mqrr=zsffombb0Ç~‚nxXLMMGp'''7]r%>>O>믿^t @G@c0 [,CtΈyƍnݢJ7of}MiDIJJZp L D"]XXheebܹærZ<)S̛7C IDATB̀uDgϞ}СyK*nݺuС ,8s_I iז QtMUUմiӾ[__߭[N2.82uׯl۷om{ɖ-[LLLB*S{H$PSåRT*]`A@@-l񘫫9DP|7o˻qF\\\޽ܳgϨk׮i;LVuZ ZhTR<|}T,i7Z͡}vvwÆ ӧOW(suu500pss;~8=Z__hhhد_rjǎzzz'N`K.K&͘1tƌ2=etqjZ1/NNNt&ZZrJ4sVP>Wh˗/)lO o_]UU:b? Z-xh=S}JB1 :Ko4OO쬬,9@( .|{ !̚5IYADa5kcc3f԰7n#Yf|>,ipKKKsshee%-OMMׯP(ܹsg"ܺuS.]w]vhZaaqLMM9ΨQ9 INN ˗_2Eu,ڋj3q^FMA40 JNN.))III2dHEE֭[x m۶a\"к222rssE"X,^d [~:bLLLIIIAAA~~{bccik׮]tI$/b.I-[VYYqܹ& 򀀀ѣG;v,--Ǔ'Of&;v,@,9sWy.ϥ kz$snfffnn/^tR5: x9xhS}Ju%ݻl5[[|gggGwרMGGǛ7oR@@7o|}j EC]UUh@*KKK2D)sPh@jO2j  s(@3uGz׮]pf͚uu:fCoxU?eѣGii)[nkknb'''$rVqqq޽'8pȑ#M6ҭ[7Kcdff&&& ?|=B~s^jjiz$p8AAA/_NKK;vgv6f̘4mG\M0v3͵=*Ɲ;w:VXXheeEmll U)**b/JB<==>,J7o̎Q[L2o޼ↆ M7W@&WU>|͛sx# rqm|-+<<\*J G'ѐH$<((O?-**z haHHHpp۷rssSN~zmmB`;D-| DMhz׭['ɊCBBQ33<^F6e˖篽ڹsFu mzm=jG͚5K__~j)cƌibT{ۻ_~B[n˗/W[gժUvʕ|ҥ}qwwwrrrppQQQÇx|'N466ܻwo/!%%%<>>Ϳ… =<}uzDOOOF>cAB4@ (++ 4hЅ Z-:cbbbmmM?effAN΂ -Ҧ 0ŋ={b ';;{Ȑ!fzbĴin߾pߧOGi;.BiczW\iYvvv;v,]hΝB0..r+Z#x!ݛ?~]]݆ *++:EKi򸸸{̘1UUU˖-߿ɓ'[+PCہ@k%%%={;jƮ,%lrr(#uT7-KMivKmw,QJ)d f~o)3qd>ܼ8,,v.i(46,rݺ?m۶,BH.^xv=|ذaӧOPZ:nqPGz ?uꔾ~JJL_hE ;Z-Əٳ~۷K.?nQ}HM ƌ~eeWVV"e֭[799/_)lY$33?|2!DKKݺuDZafʔ){YYwϞ=iтHcp8<F(8p@__˖-'))iႊ O@UU:::=߿{YY\ *PG"o޼qttɑu˥@? e ~t3EEE=zu֭dL###zꘘCCϟ_? 9E(ݻ7!$66)ꄐw 6ɓ'nRVVpݻqŭY#66v(hP]v:t(**S444ڴisȑ+Whkkڵ"Ȱ"p8^^^]ty왙6AYBn߾]g͚3ѣG;88N:J )Dѻw︸WTTx{{2Ό e:~,2aee/Nn߾]SSΝ;XdU9R1t@@@XXFxxѵkhe:زHDDDݗ޽{Yl\@dnnqY[[aVh@(СUXXPv 2w܊ GGǏ?6T<>ch-[<~M$$$6oljjJ;4(Phj积+V4D.0͛7544bccMLL\B;4(PPe99JHH]6$$!#ssaÆo~~~5lY$<}Z<<}ԀN2eԩeeecƌ)))iV׮]CBBtuu  ډ@ء,BaYYٝ;w;v3))iҤI<aNQQQkZZڠA?N;5E賴$ ="%%uVZ]xqڵ 8RSS 0`@-P]^ ޲/-Z3&;;ݻBY6x#Fr'OlǧLRTTdeey@Y>iii333."W\iaa;vϟ? !$..wiӦ[ZZ mhP ئOLLСC111-DI\\|׮]pAYD(^vM@i7o>wzB옑ӧNBe`hhئMdu1hР+WxS&%% !$&&{ӧ~Сqqq@YD(h׋رcmmma&((}xذaϞ="bԨQpazJNN3fLee6˳|D@"ZJJ*:::??_pHKK;wN]]֭[X~D3g222,,,޾}K;P߿UUՕ+Wڑɓ'6nܸg jz }R'++{̙.]xb>}hBYDB.^(cBȜ9s޽+ʍ7ttt"##Mh'jtmm-[IJJ ;WW;vm@0 ChpA6z~Ϟ=igTЎt`ڵo߾m޼yo߾7n=s挔lB;Ёpiy4 #GGGG{xx4BBl xxx?vhKttt~m{ 4lÆ (4LAK CvvICI4|˗/ ӧ8Ш0ZDGMM-==ɓ'c޽O:%..>Ç7Ncٲeƍ+++;vlNN8ШP.bbb֤Ѱׯ_f̘qΝFԍ5bbb711Ȱ"B1sss3gNyyȑ#SRRkdddΝ;׮]0wwwq,"t*%%u޽wӦM666#G|]cv @ɓ'm۶w^q,"tZhamm]UUuW\\ݻwOJJŖ jLLL6nH={hƀ03f !ԩSo-.]1uT3&??v8ETXXXnnn#wq iicǎ[{nݺuKOOwqqea0tЪ7~۷o7Q#++{%%ӧO޽v,E;᳷x<ٳgdCbfܹ?e!egg'))RPP@%Ӳe˪&ME%-Çwuu-//8qǏiAAYDH)**8<ٳ?~8jԨgϞъ@u떘h"Y@PP^tѰG]PP`iiI#Gdddmvq@ P^Gp87n(,,A\\СCfffÇZ9;;gggӎ eպukss󊊊/R!##s.]y[YYa0!HIIIYzٳwNcBCCuttUTTƏϟZQQ驢~zSvء-))iddCdUUŋ۴i#++koo_RRžӴi֭[o5D^Rٸqc5JJJ˖-#,\PF:MɓoܸN;˷ݻֶxĈ4_;v\dɮ]o>k֬VZNh255urrz222¢ 윜ؓ~~~<ɑb/]4///--ѣG!!!uVSwqܟgh:tHHHENxФL0K;Hm>}4rHBBTT8|KrPҚnnn޽cj׮]rrB Rkii%%%٪챆FJJ {Bz⠦ !kgϞ"X"r$hii? E?o>aӐɓ'njS\\leeiHNcJ̟?~޽_'?y򤬬ڵkǏgzOQVVfdee+++Ν;crss:::7[IM B.@ &Nhdd}vY, :C^%%%O81cƌ>XYY;wv"m۶B֯__M־}{~0;w oֶm:>m۶zjjjiii1pも7CLLlՄ??bqǠ,0 3eBȞ={hg11 ϟ??ԩS@+))y葌c8ߚ5kh11ŋoܸv4lٲcǎuEJJ_~F IDAT'N`ҥKuuu555kolȐ!'Ncϯ\RIIIKKpwu9SS׽A6>>>7@`:tSdmm}yR'vy]kٿ~QQQ*77]dagff׮]."/:4^z޿]377\n݂ hgh&{nAk۶m<o̙@Ð8p`UUպu3fx'O矄H4rHRWfb{xxю G\\hŋWUU[YYb<dmmݫW_Pi$%%!/,?͍]꯿rwwPsf_~_۰a닊_>`lZŋ !k׮3,P'(4U3f =zi-riiiѣGNկ_/0 ynݺ=|8!!J6Ffkkkhh͛uHSehh8hР&kggw-[;wn`\ihhܼywYYY<aa[ݰaG4 (4ag&lٲɽ4($$M6C ˣBEE%$$:''gΜ@&N[n߇H6jԨv%&&FFFÌbcckډ@ .^8wܲcz{{7J.4'[ءC;200/ 8Μ9s!6me&̜9m6Y~nDDD=LLL@s%..y?mڴ(@nܸaiiyYY`ϟk\@̙3edd._sY;Piڜ$%%Ϝ9E;PSSqF޽߾}kaaCډ@PO<)++~sss#GlѢunn7~ /Zߟ3g*(((((899 Ӵi֭[ݐ|UUU/nӦ}IIwou<oΝw,ҴUTT4ݭ[n2l0v+WN2f̘H^zEDDNPwwܜN:yzz~}ϟ###Ccƌͽs!$,,`G,Yׯ_/]=tҼG=߃rrrddd`sB[ZZJ; ԊM\xx8!D]]ϴJa|||hf+%%%nrQ#k/(ko^SZZ>}Ν;G񬬬7s899YCC=HII__SHVRR{u**={$ܿv۬9!dǎ@m0Z377֭[VVօ hgy6mbٹv(VZ]reѢEs;vlqq1Ppsssyyya󿾦4,GG=zSLPNN.{ggg=mfffΝaFMM)?Иf͚Eٱc PEWWWd^ԩS222AAAEz.'..iEE3g3>>^P.((?~9s233\nqq1[(^7oޔ)Sܤ?iӆ=VSSm۶?|q7ܘ1c>|M; e` O(is%"Y;p! l0Z044|}YI]]=<<ں`Ȑ!Cƍc++QF9;;5Xn}a#!d,|7o\QQA;KÐt钏ϟOln %B]]ʕ+[l ڵ˗(8@|_~ĉnݺYPi>u떙yIY 0˗/?v올lPP @i%9s>|hjj9bĈiӦa4iʿ=t, (4+ƏٷoDډ?W!FFFqqq^ruuOeHOO;wn=رcxx ddd߮]8pkv@5ydBѣGio@YY4iZ\\۷igi`=z􈉉ӧOjji'x;lq? g1BYY9!!紳PiV\]] !6lmSLოi'׏طojDDi14@YquuxbRR, OJJ]l!ϴC1 3uTqqM6ikkVUUюFM9s %E)Sx͛7" ;vLJJ*((hC`._ɓ'Ç/,,twwӧݻwiCl2...55v(4CbbbϧEPƏӽ{k׮N5bw?==K.9rDCCsnJJJjĈgefcǎeee;wEzqaÆ{ۛ ~ E1 uV=== PG Pi؝znGYHYYk֬aԨQEEEC4L5JJJvvviiiC?WnCm+//?bĈ+VmݺAÇdeeHdaaѯ_젠 Ya// .(**ɓ'C4<q_~eر_~%888"",77wܹ;vܱcǧOhGYY!Cp`YPi!Wˈ#"##;v옒bffvډaڴitD;%%%>|ȞӴi֭[WڟGGGGk׮_ʕ]JJJjkk޽z<33K.իWzzz[lic!\zv/E-CCÌ ݻ7rb[[[ooJڡ]GGC_j/:4^z޿gϞܹsrrrdd),@F4o&LShgi$ x{{t!**k׮Ǐ w޹ϛ7k!DVV?D+;;_`ku{Ϳׯ_ѣG.^XKfalll߿M >l޼Y__„ QQQuo$ܸqvE3qq Boq\\܄ ޿ooo?e2PJJJ^^^wܩjjjmkuzW^.\۲e M rqBN8affֳg`MV‚, LPi544?~|%YU˖-;sN))xv(8 SNu3///77ݽϟ???????ӳzO>x?l'^|pB%%8::jii-Z())4JJJ޽{ ` E9)))ŪUhgΝ;zzzqqqch'hJQ)i+WTRR2448p`{\r{~ԨQG:tЏވڵk322va``nݺ.]ٳGi-Z۷oUUUhh(,@\:::yyy?(((:ujpp00~~~CA37/))iᩩ rtt޽{O8{BQ~́劉 xZ"r?jŊ>>>7n0ZD͝;G; Zpºu$$$֯_obbBã0;;{o߾vX^^~ё#Gm9$$M>|P9߿?!V@""a֬Y7oY`Ç{ri/ikkwQ___QQqŊHNN144ׯ766~]PPА!CTUUO\^^^K EEE?|޽'[BBѣGeee"UVfxԥKh// ooo++,ڡ啔>|X^^z􌍍MJJ)((طoM6mƎgϞoW(**{xxTTT8;;7BThdeew^QQq}YexbEEsݽ{v֬Ys5uu7ov… CPԩtҮ]>}zƌڵ322:rKoذЊ MQ߾} !wܡPnnn˗BСC=z4jԨQFM2v(FFF+VHHHHMMݶmۈ#ddd>| %%e``0eʔDvŋ^ ,M;`'QR\\ݻpsssqx6mZxO LLLhf43oߞ;wnJJJZٳgϞ=mVTTD@-"r?!==][[uֹ @ "eʕ˖-:t7hg=4iRbb… /_.%%E;4y(4WEEEO|aaa@DА.++@ta{n߾}eeeSRRڶmK;NFݻۍihx<މ'-Zk E-YDZZv.ӧOtB; hQקO#GҔ뇄:tHMM޽{}={vQQ\M[bb_~ݷo߸UV&XRjj* e gO>)afҤIIII#l߾SN,Ç n:(((22k׮s!$--vхN:M:jɒ%4= Ϟ=͝:ujϞ=5Y.\n``V@@0>>>^9s~Ke Bٳghgi^gϞ֭[řϟ?v.adii;SSӘ˗H0:EB]]] !K.bf\.wÆ ;vܷoRooݻߺuKMMmFFFs4*mmmBHzzz#umMMMa/Qdh?޽{WXXxqXwwwv荱q``)P¢2((777̙3g|D۷o޾}[Ϧ;zdɒ#Gֳ݄\0Qx!Yf<};v옗כ7o0a&\]zu"ߦ;v ***8ΧO=@FF]8hw'R00---00v&a/_nڴe˖ǎsss+.. AY[[?}C.\^3 1k"M"_6m"^"!!斐PYYy]9rv4Ɠ?gΜ޽{˯\ǘ jӦ !$//0 cmmmIII##e=(//9sSyy97nl߾=0󏮮T׮]###ߎ;-'$$JMM9rd-ssszZz:׷Y=aUUŋ۴i#++kooSEEEIIiɚ.fݻw***ՋS޽SUU-,,As969r$22W^&M֭ŋiϟ?oذA__۶m<oƌr IDATϟ?_d,hBEEҰ¢ 윜!t,Y$+++%%_ݻwܼyۅ'N>|˗oݺUTT4n8eB{nnnNNNN<==Zz:ת'{A\\\NN{222ؓ5]LQVV;vlPPLPPĉywЬ׋/ݻG;KsSUUo߾vڱ?} {.PrO>ϾYXX<~v(adkkK9}t=!jqii)s899YCCMfff6M}%JKKUTT_4R=VRR{k.99t1ٳg<B[[;==wMF:t0w\.B JLLlԩϟ?WRR}Ʉ ^xA;@C 1113fLJJ/rׯ ~hjLYY=]]]XWW7''z+[jo/-߹s\^^a99w033sDMMm|-^ܹӧ !Nׯa49(7,[LMMΝ;'OYh˗/.\(%%u yB)u<2dȽ{455ckkkڹ[m߷٩jZZ{ʮoƏ?gΜL.[\\x {۶m322ܟmۦb>wwwv A|C-VXAY`AYY8͓ڵk߼y0̖-[U 4Q>ٳwŋiӦӎ Ԕ!EEE 2rssǏҪ/,]\\^|YYYoqܹEEE_gii[~wl,yfÆ 4g[^f̓lll m'*++ťW^7oޔ_lٳgϜ $@]S>~ؘ^ZUUUOOOOOO]]}ժUimϞ=rrr 255_`AϞ=Ӏ{{{ 2DVVvĉvvvy.]t]WW Sչ-\w!@P7nXZZh"%%EUUv/22rő/^? F[l)++tqq믿9@۷oSNݷo,EFF*))Yh,",""ڵk&&&o޼5kΝ;?L;/^_^^'''&"@ "PGAAnݢETXZZFGG믯_vqqر]***hG |ooo5k|޽{֦ I2PڴmvҥWWWvјF{CCt''N:ٳ]A^^[ðaâ^jllL;@&##C0ZE;ܺw@;haÇǏҥKZZڌ3:vsNyyykk+WЎa ](wp8m۶1 z꯷A?~|BB∋K'4o.ZHGGgڵlA$&&(4!DsmMMͯw)j~wԼ,gjj:mڴ?Ξ=v%..>y'O8qGyJMMuuuYn]iiقH>}hGhV؝?X@.\uVm`* Bt9??ٳ4zwB͛7o?0 ŋiFVVޞ?5a7o^LLg9EֹS***<==UTT֯__{_7^^^>sL''t_|yʕ]JJJjkk޽[5==DxAbbw"|ԒFq,u%&&sN~ǏӎaffvʕL8Q\\ܹsӧω'haW\\~z===VZzjA *,,srr"s:x<r߃rrrdddO{n\\Y666999:ud_|ybbb|||ZZZFFwg}%KRRR?K~?e__ߒ𘘘oF !seKo6lwKPKF Aϟ߲eK[GGgӦM߿ ы/͛עE C[[{Æ %%%sXBq=!J qii)VRR{ʿ,33fPZZk.99 jjy}t_ݻwiu줉>}Գ-oR8vXQQ-..?"5_S)))qrr7TpJKK/r.]RSS=O-8{x{>wܠA/eK-ߓ/FE?ֶŅvqpp 5j=v옙}KX(└-ZˣGݛv:QWYYItG/]\\^|YYY`oo>~(-----_/8w܌"k„ %nnnuӽ{M>}`a?wÆ }:ymر#G3giGݻ}ܹ3%%pF:dȐ/2N<9~cǞЎ?LYYyɡ'Nzرc۷os;x<^hhɓ۶m;{VZ?ׯ_b#@B-@"P/-222JOO_d ,8pÇ3337mԵkלkvyРAƶ5B(##c>|ϟ-,,;Ɏ e*Wtt0111={@ttݻ?^ZZJQTT7nξдϟ?ٳڵkUUUMMiӦM6MKKv:...;wo1 ޽C?ø;ܓP".vIjUm_Fmvmmk[Rm rK"r1.1?w;?!x=q3sf44Z}Mؖɇ#";t萙Y||qk{.$$̙3O'M}Ҏ$@`%%%=<|ޞ8\nTTTppŋ+**4iҜ9s\\\dee RL_BΚ5kC6t-wPM}ҎE0ƈ#ZZZϟO"F;ĉEEE.]6mFy󦧧 ȹH>|f6lHIIQVV^xqxx8ܹs'j" ~}vxshǏKJJ>gi4Yn:F߿_GGH3''gԩ%%%d&555ee} B}}ŋ}||x3!\HII144cfۋMukhh?l߾}ko w_~@JJ4**ٳFFFd;o655_?>>Aߐs\W;vHLLLJJb222՟mͶqR&ŋ;^^^SLuփ*++y ?ϝ;w˖-Ցqqqܼyѣ~~~d&f7m^RF'(뤤HKKh[nQzNaaClmmy 7|{.&wѣG󾲐}h[[[[PPtRSTZZj*//Iw[iq>| >}S۷oF}||着*tL0ARR {z8tЗo?!ٱc\ʕ+⺺=秢? Bmm ,XP[[qT~Ϟ=%%%\.fϟw|hl{:N=~uuusΕQWW߽{7g{gh᧞ccc:>dȐ;w|s[(**4h֭[;/))it:]OOرcܵkyooﺺIw[oHBvd---Ǐ :^zƍ,+"" !`X[[;99M4r力W_}uM^(@KLLijjqW_}EuJՏ?~Qxxg8.))9bkkkMMMjskiiyullltttdddNN)yy1cٍ93g+Wܿ?Yz/E'ܹsݺu/^۷/q@UVVFFF>z(**U3f1c3bcc⪪xO)++ّ0'SN9r?Yz/E'899=|pɷn€s*555 111o߾=%''giiiiiiaaaaaahh_< q8nfa IDATO6B044=zQ@92!!!**j̘1TgPRPP`nn^^^~+VPDMKK#/MKKSTT1bſP%USSS^xQSS{`XZZb=Z]]¨{455755UUUQBYz9ԓ'Oͩ""!!ӧO>MHH`XϒUaÆ 2DJJRSS={FB^|Y__{VLLzذa:z^rr1P?yB'U߲2gtСCutt )===%%իW)))L&7//At:Ą.DPܹsޞ!!!TgPݻ#Fdgg/[ѣTǁ+77711իW^zevv6h TTTjlllddddddllܿ OMMMfffVVVZZYIOOojj#++;h 333bff`0 ЦUV8p`׮]k׮: @W^YYY֞9sf޼yT OMMMNNNIIyW Z'K$FFF8p8%{,4 }Ç|(DsaՕ\xmYYYqq1Y)--` :::ח1O^h a1PCZZ… .\_lՉ>UIS齿p֭[E@m߾7jMM qQll޽{W^Mu^X@,ӯB/EYn:&h舉?/))9|ϟ[M/VTTTTT񩯯k ,hhh Ax7~)))SSӨgْ?Ϗlə:usIII; |g8 (ee .0#GP .&L@ر#111))f:?y$))|]QQ1c  6ffffddmܸTdddrr2dٛ6m"zy#""***Lr֭TVVpuIuʕ%%%l6߿v3 \p&L[.Avy///qq[n999Qh4:k׿{1Al6ܼ\PP[\EE ZEEŦW^KK+<<-##b333 tppp#4LUU #++_vmDD|`#mmAٳgSet?壢̨5h4ӧ+WFJHH477!WhtzCC8A222EsssE:=55uaaazzzdKLLL@@gȹ>z lDIIɇ p u-Zzԩl68]fgΜ>}ɖ~X,e&Kf:V`2rNN:)^{nn'e773gj"AxxxTUU}AxD^A3i$D " ;6nܸ7o޸@S^vmgϞ%bҥ>>>ɞOZ}̙t%%%~~~KKKKKKWZEKRzz'Mnݺ1c7IKKKKK3L Aiۃ)WZ/^$ݝ " $$$.]4`ܘDرc>|?ٳ'00ё`xyy͘16Io߾]CCPSS=ekkkjjuVq͚5_^\7o^;ќ>}_VVvvAxf#""dddNJuh ]xuUUՎ;֭[Guф8ǿiӦ]z,F4hPhh(NBCC)!!!A~!sUVȄ58Eׯ,//_TT$++Kuh E@Ȭ\rҥuuuӧOgXT5t-rbuD0a„(kkP MMM:::l6;::Ɔ8#%%u##Yfq8l6{РA,E@(Ç:::׮]7o=:s Aͣ: р{رc+++7mڴeOQQ.FР: E@^|YJJj֭G:'NDM@,mgΜh~~~W^:@hjjWSSSVV޷oXWW-++۷oݻwh4F߿_GGGLL ŋ+*****ݻwkhh.Xl}G b8Α#GAYެYnpbbb𥂂RRR={d2yw޸qcUUUnnnrrrTT'O$%%aÆ̌7~twL&foڴl;w-[###D͛AM2,-"ѣ}􉊊266:yvddAYYY$߽i4ZAA&MKK+<<|Addd888hLCCC 233 ]vӵ{D̘1cbbb;"e믿v횁ALL .ǻ7a:^__O[5644,!!koU7E?m۶(**8pյO芏RUUc0T"Ν377N>|_~999544޼yC.涷Y &I.䨫t:\.//_?77WO9rKKK>)GÇ X`j"e nӋvww':˗/gXV"===W^]VVVVV޺3g\reIIIII'nnnwڂK_ZZZZZj*///q֬YMMM\.{@4]pNR>e)?q͛ dnnn``K6n۶MNNNWWwȐ!Ghs۷khhjjjdS^8fUlmmMMMUUUnJ6N6MVV6 88ێ@8q񫯾AE@ۗgϞ%o\ 2ҦL6՜#޽{_QQ9vX"!Cܾ}[^^?:@XjUEEEqqիONuhÁ***P(h9rիW9».@ 8PII rAQ: çA]voinn޷o_T|~iƍ{YPwܹyqԩ RDSuuu n xpp~[YYF?~\___RRrϟ?絓 MMMjjj#9κu guu55z۷WVVN<ޞ,P^DLL̙3syݤI>}Ju"GGGWTT̘1ǧճAAA)))Ϟ=c2,lܱcGbbbRR͖ QVVֱcvMuz߆())j@4\EE ZEEEr<ޔ:::<022_K__ݻAlssb*P$===--| ?@۬YΟ??w,90Zzqq?c̙NNNIIIT'ވ6%k"A0V}8BAA ;}tyfh%66644TFFfǎTgτFdedڴiU^::}ƺcXͶtEL͛\Y3,ą \]]KKKSRRNY˗/gXV".]㓝ܜImHwڵ0UU 6P>"{IJJ?~ܸq%%%L&DyA꒍ kƌԆm߿%bϞ=}:|>L ]MMǏuuu{6LЭ^z99`(&&&v? W\:.}tpTTT,[ m۶ߟ8ePhF;t… g͚u ֬YSTTdggb @WBYmbbbNAo[__xbEEEEEEz^ǏKJJ>VljjWSSSVV޷op֭[`0<=={؝;w~799gϊ3HK]4رc;wp8s :.6l(,,۸q#芊3fZ1(((%%ٳgL&b;vHLLLJJb222=w$¬?A7n3k>_hQsst+ZZZ$"##qhr jkkWyf޽kllL677/..&&&iiio8 ͙3?5jTLL8qѩ <==9RQQq Cp]Amfl6Y!`477Z"OAAɇ'O?/DDiӦݾ}[AA?1cFmm-Չ@qA &I.䨫wrbz/^qP"5nܸ?Ǐ///:3g\ӳ+z{{/_bUVVZl\tOvvvsssrrr;zyyϝ;w̙T'0`@||o2K}v CCCCCCMM+n޼yРAdc``#1cF&&&ǎ: t#L ޼y3qČ ##wSWÌ 󄄄xyyIKK:8Ѝ0ZEEEYXXdddھxDeVXA֭[Qy(|55G;ݻT'.PWWVVV6}5kP"I^^s̩ꫯ9Bu""\.wΜ9=@o瓔o<pBP^O>/((r Չzׯ_O8r{:P enz޽|AAA\.PQjj}iiYΝ;'&Dzov11-[ ) IDAT5 '降@O a0NNN,DEnn &Mt% @@YGGFFjkkGGG[ZZFFFR@YXXJJJR"=ŋ&Mbj[::: 2Ν;T'TTTn߾sN.e˖ӧWUUQ @ۿxbwӧՉ@,@ p5%%ׯ[YY~P"%++#GҢ:EojjneeuW^;6//޽{***T'AQ]]_P @xѱ!,,LIID P\hh't]NZYYIu(acGGǒ;;WR"ڵk󏵵gϨN |.]4q)Sܾ}[^^D P ÇOKK9~8ՉIPPG}}}@@͛7 Չ@С, X 7owM:۷Tt͋/޲e F;pΝ;i4ա@, ptzPPիWUTTnܸ1|XCo_t钟Չ@h, \]]?nccgggk..Ku(f'O|5%%[n͘1D LP\:::'00pT QQQƍ:EN߹s'yAׇ Gu(pԩ1cưX,!CP"B`ԩyyy+ՉظdƀwӇP PFFFqqq>>>uuu/5;l'O2;wtCBY@hHKK8q"$$DII)44ÇTQ/_ׯ߽{fϞMu"n(OOϴ)SO0aɒ%555Tv\.ȑ#VVVYYYVVV cƌ:=E͛7O8!##sIKKDC#F`04Fuб"''˗766n޼9**JSSP PJ4'>><--mAAAO^hѳg:nح[@Fu[%QGGpqqo^6%"33\p555jjj䲶vzzzHuu5k$$$־uՉhe``#a#ׯ_:Tkw!׷U;A111[n?a/!&&p[8y A{ 6`7n&&&M}]B&ou ߿?o\;vF-++#ۋ Ze  .ݻ,Z˗T^eQF|||^xaggWTT4m4/lE$̌N)))_onn.A#G@ΫW[^|K.9륥!bx%3js]w{ rss544e__߂*ޟ~r'pyرcgΜYPP0f̘SN)++S zED߀߿yf /57gr8_dijj4hZt… bcc.\l2Yf655qf*>>>ɞmnu /-----]jXWW'-----d2k(˗/gXV ,޿~#GFEE)++=z4""ܜ\,+HHH=y,++kܸq>>>_>lˑ_N }}} >}JaT㣨dɒ ~ӦMsss &mmm ׌3rakkkjjuVǏ3f ͛ dnnn`` 9bhhO?S/[L\\hK>{;{n۶o߾4˧q?߁:ڵkcgg:^7ii醆Z&abbFN- F8$8/.aÆl vmeeEm*]$$$֭[V\\11:%%eƍÇ՝={vDD͛ ?ZHdmm&//uŋ&{ ,YJ/^gCt_2J^ wݾ}Ǐ PPP]rZ<ӧɓ'GqCr7nܰW\8p~]xW8d2׮]+//Ou:CY11s禧XСC&&&Ν:ﻺرl„ ׯ_/**c0T,E ***.((vtt|5չb%%%?aÜn޼)..>s谰0WWW11|!-h׈#wuu{}[ss͛7ܴW^ 6?ƦtE#4mܹ ,p8}jDFZZڈ#\\\"""듔d555WW+WHHH|aaa۶m]e8uuӧOXZZfff:;;c;;LUUVfgg޽{'O4hݻϝ;7a\/eYVVVO<9v옢?cff[VVFu.dv?~|iiԩS?njjJzj֭Æ 344 x䉒ҢEbbbRSS}Rk,@LL G :tJccŋ9N@@˗ F\\MLLy/^(++ϛ7͛l6ԩST4.KuJyyy6lrZZZ6mZp8չ111IOOOKK366: ia_utt4طo̝;w***ӦMswwwtt4;@O@Yȓ'OV^MĈ#~g{{{C@@_ IIIӦMcX 'Ovuuutt63@OSǏ/]4n8R Ν;fͪTPPxݻwdddƍ7yɓ'Q[Fsww? ##sM33oR  ._RYYIĻw رco߾k.___D7E4Е?EIIi͚5~~~rrrTQh:{pȈ렪jkk;n8;;;sssL "2226lp%.|իWS D"hMG"""rssy/_1c)tm۶]x 55իW\RJJ\ jPG_ yyyd}$***::ZUUP e^сQQQA_@\5f̘Ǐ :͛7K,133x"j@9E'L0!!!Ǐ:j(@ںuRDD>|Hu4P%''qFu~EFF:::6 @CY( ++痙IG^x0E2dq$++/_077Gqz"@1痖cǎ>}$''{xxܹsh P //n:&sN558ggg .p8hBY\@@@NNΞ={455fΜibbrɆAYܚ5k_niidmmr@YӧO?~RVVe===??|(@Y7\\\jkk:4`s禦R " ƌsƍof͊:+E@XZZ^x .ϟ=ztHHHSS@Ƞ,_e䉗V`` CY_vv'LPZZk.CCC@YyÚ$qq/3ܹs:Ç8q"--mڵʉރ޷o_YY@,"EWWw׮]ˠA֬Y={H`AYDҥKSSS|||t_eoobee'N(((8q℩)ڵk׀<<<߿rTBYDϋ/n޼W_577_xѣUUUTj,W_}u͜khh$''jjjΝ;Ç---Tg:zzz۷o upp? 6oޜ]LZp jkxZ`Ѧql˲zdmbY͚lK͖nM%͖uKnN]EUT__*80<~/>~ܻw\VcY_mtԩSo߶X,<}H$jiibXkbRTN… aqq~z+++G uX觟~gϞ>dj/tݙbՕ9ΞV388J$_L@:4ܹs>d2Lw}tvכ2Y x"4 A\.׏?LN>-H/^8332) x:w~W~g߿KX^+..6}<#otuuMNN;w)Htuu B^W_ݽ{7e(///++;ulN$`@ UG]^^t M$tRgggr‘cǎF@3[!&gX4 S( B.+ʪ*xʑ.6TM@zEE ׮]|r#GF( 3[!洼,&&&Ri4ZYYRd\wެ,bf+W:;;F*Z]]m4=j2[$rϟ8q  l6bjfp80Xbl4FF.tѦV\|رcdҒf#ҥK 7 @zE2&vvv^rr4ÇtΤDQ3???>>ٍ7Fԑ#G:::0H@zE6yy1''H4ͮ]2[$x<=ܿB?&^vpĉ?CNN>pX`sF}}}ݷn݊d;zɈdT*5u#p8p#3˫[o}x0H@zE6?Hv{z^o0 j( ]~+dhh(IAA`hllllld$///\xwމb_| b.l ǓlgX:NzFC>S9 [D"A3 NРRR>B144z…X,+¨ \ !z޽k2_n6S8jAi4hD~2 ]wj O>9s BRgΜ裏Cbqvvt"[^ZZB,"[ۃf3ܹs'9 BZVR1 ;͛7oܸOD,K:NR=Ia6z}VV7|sUȺvX`-E龾>d2+P(o>FjZR^NbX,P(FUUU:tСCO{x<.Ϟ=ҲVg`gTkA,=---q0=VkIDATݞǁBh4JT*zqO Ç{lZcv.K"66Q*r\P(c#خ777gvȈj{|Ҳ0cEQt:].x<vIRE"QEET*r\ dlXؘf#QV,|@(D"P(6dnnn||?H]?ÑJ2,HRV!u)Q霝]%dJ"#D"QQQ/**|NNN>xNLLkabqiiD"H$RllvE_ ɔd||vONNnɎ4/d0Ɉx⢢"Wf ;uǃÇ`F0 _}>ߊ%`V|> >RdeemleE |>LLLx<z^Ox6d2L&b6yyyvb24JlUT*b=yH$P(D~q D"H8 BHdnnnvvvffBOv|> ⒒'|>rOxXbؼfggɈ$|>~盚 p8ӕ>5#ɟ$.KdR?K[5rIENDB`poelzi-ulatencyd-55515a9/docs/wiki/specs/000077500000000000000000000000001154664534000203025ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/docs/wiki/specs/dbus000066400000000000000000000017541154664534000211710ustar00rootroot00000000000000We need a generic interface between user UI and a scheduling daemon. This should later run under an generic namespace of freedesktop.org This is work in progress. If you can think of anything that may be usefull and generic enough, please add. Suggested systembus name: optimizer.freedesktop.org # API /optimizer - listModes in: out: as desc: list of all user activatable modes - getMode in: out: s desc: returns the current mode set /user1/ Methodes - enableTracking in: b out: desc: tells the optimizer that a user driven program is controlling the active list - setActivePid in: i out: desc: adds the given pid to the users active list - getActives in: out: a[i,i] desc: returns a list of tuples. First entry is a pid, second the timestamp when last added - setActiveList in: a[i,i] out: desc: sets the active list Properties: - activeLength type: i desc: maximum number of entries in the actives list poelzi-ulatencyd-55515a9/modules/000077500000000000000000000000001154664534000167425ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/modules/CMakeLists.txt000066400000000000000000000021761154664534000215100ustar00rootroot00000000000000# some macros macro(add_module LNAME) add_library(${LNAME} MODULE ${ARGV}) set_target_properties(${LNAME} PROPERTIES PREFIX "") if(NOT "${LNAME}" STREQUAL "test") install(TARGETS ${LNAME} LIBRARY DESTINATION lib/ulatencyd/modules) endif(NOT "${LNAME}" STREQUAL "test") SET_TARGET_PROPERTIES(${LNAME} PROPERTIES COMPILE_FLAGS "${ADD_COMPILE_FLAGS}") endmacro(add_module) add_module(simplerules simplerules.c) pkg_check_modules(XCB xcb) pkg_check_modules(XAU xau) option(DEBUG_XWATCH "debug xwatch module" FALSE) if(XCB_FOUND AND XAU_FOUND AND DBUS_FOUND AND ENABLE_DBUS) add_module(xwatch xwatch.c) include_directories(${XCB_INCLUDE_DIRS} ${XAU_INCLUDE_DIRS} ${DBUS_INCLUDE_DIRS}) target_link_libraries (xwatch ${GLIB2_LIBRARIES} ${XCB_LIBRARIES} ${XAU_LIBRARIES} ${DBUS_LIBRARIES}) if(DEBUG_XWATCH) SET_TARGET_PROPERTIES(xwatch PROPERTIES COMPILE_FLAGS "${ADD_COMPILE_FLAGS} -DDEBUG_XWATCH") endif(DEBUG_XWATCH) else(XCB_FOUND AND XAU_FOUND AND DBUS_FOUND AND ENABLE_DBUS) message("xcb, xau or dbus headers missing. disable xwatch module") endif(XCB_FOUND AND XAU_FOUND AND DBUS_FOUND AND ENABLE_DBUS) poelzi-ulatencyd-55515a9/modules/simplerules.c000066400000000000000000000261761154664534000214660ustar00rootroot00000000000000/* Copyright 2011 Daniel Poelzleithner This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. */ #define _GNU_SOURCE #ifndef G_LOG_DOMAIN #define G_LOG_DOMAIN "simplerules" #endif #include "config.h" #include "ulatency.h" #include #include #include #include #include #include #include #include #include int simplerules_id; int simplerules_debug; struct simple_rule { gid_t gid; uid_t uid; /* char *cmdline; char *exe; char *basename; */ char *pattern; GPatternSpec *glob_exe; GPatternSpec *glob_basename; GPatternSpec *glob_cmd; GRegex *re_exe; GRegex *re_cmd; GRegex *re_basename; u_flag *template; }; struct filter_data { GList *rules; }; struct filter_data FILTERS[] = { {NULL}, {NULL}, {NULL}, }; enum { LIST_FAST, LIST_NORMAL, LIST_END }; #define simple_debug(...) \ if(simplerules_debug) g_debug(__VA_ARGS__); int parse_line(char *line, int lineno) { char **chunks = NULL; GError *error = NULL; gint chunk_len; struct simple_rule *rule = NULL; int i, instant=0; char *value, *key; int tmp; if(line[0] == '#') return TRUE; if(strlen(line) == 0) return TRUE; g_shell_parse_argv(line, &chunk_len, &chunks, &error); if(error) { g_warning("can't parse line %d: %s", lineno, error->message); goto error; } if(chunk_len && chunk_len < 2) { g_warning("not enough arguments in line %d: %s", lineno, line); goto error; } rule = g_slice_new0(struct simple_rule); if(chunks[0][0] == '/') { rule->glob_exe = g_pattern_spec_new(chunks[0]); } else if(!strncmp(chunks[0], "cmd:", 4)) { rule->glob_cmd = g_pattern_spec_new(chunks[0]+4); } else if(!strncmp(chunks[0], "re_exe:", 7)) { rule->re_exe = g_regex_new(chunks[0] + 7, G_REGEX_OPTIMIZE, 0, &error); if(error && error->code) { g_warning("Error compiling regular expression in %s: %s", chunks[0], error->message); goto error; } } else if(!strncmp(chunks[0], "re_cmd:", 7)) { rule->re_cmd = g_regex_new(chunks[0] + 7, G_REGEX_OPTIMIZE, 0, &error); if(error && error->code) { g_warning("Error compiling regular expression in %s: %s", chunks[0], error->message); goto error; } } else if(!strncmp(chunks[0], "re_base:", 8)) { rule->re_cmd = g_regex_new(chunks[0] + 7, G_REGEX_OPTIMIZE, 0, &error); if(error && error->code) { g_warning("Error compiling regular expression in %s: %s", chunks[0], error->message); goto error; } } else { rule->glob_basename = g_pattern_spec_new(chunks[0]); } rule->pattern = g_strdup(chunks[0]); rule->template = g_slice_new0(u_flag); rule->template->name = g_strdup(chunks[1]); for(i = 2; chunks[i]; i++) { key = chunks[i]; value = strstr(chunks[i], "="); if(!value) { g_error("invalid argument in line %d: '=' missing", lineno); goto error; } // split by replacing = with null byte *value = 0; value++; if(strcmp(key, "reason") == 0) { rule->template->reason = g_strdup(value); } else if(strcmp(key, "timeout") == 0) { rule->template->timeout = (time_t)atoll(value); } else if(strcmp(key, "priority") == 0) { rule->template->priority = (int32_t)atoi(value); } else if(strcmp(key, "value") == 0) { rule->template->value = (int64_t)atoll(value); } else if(strcmp(key, "threshold") == 0) { rule->template->threshold = (int64_t)atoll(value); } else if(strcmp(key, "inherit") == 0) { tmp = atoi(value); rule->template->inherit = tmp; } else if(strcmp(key, "instant") == 0) { instant = !strcmp(value, "true") || atoi(value); } } if(instant) FILTERS[LIST_FAST].rules = g_list_append(FILTERS[LIST_FAST].rules, rule); else FILTERS[LIST_NORMAL].rules = g_list_append(FILTERS[LIST_NORMAL].rules, rule); g_strfreev(chunks); return TRUE; error: g_strfreev(chunks); g_slice_free(struct simple_rule, rule); g_error_free(error); return FALSE; } int load_simple_file(const char *path) { char *content, **lines, *line; gsize length; int i; GError *error = NULL; if(!g_file_get_contents(path, &content, &length, &error)) { g_warning("can't load simple rule file %s: %s", path, error->message); return FALSE; } g_debug("load simple rule file: %s", path); lines = g_strsplit_set(content, "\n", -1); for(i = 0; lines[i]; i++) { line = lines[i]; parse_line(line, i+1); } g_strfreev(lines); g_free(content); return TRUE; } int load_simple_directory(char *path) { char rpath[PATH_MAX+1]; gsize disabled_len; int i, j; char **disabled; char *rule_name = NULL; struct stat sb; disabled = g_key_file_get_string_list(config_data, "simplerules", "disabled_rules", &disabled_len, NULL); g_message("load simple rules directory: %s", path); struct dirent **namelist; int n; n = scandir(path, &namelist, 0, versionsort); if (n < 0) { g_warning("cant't load directory %s", path); return FALSE; } else { for(i = 0; i < n; i++) { if(fnmatch("*.conf", namelist[i]->d_name, 0)) continue; rule_name = g_strndup(namelist[i]->d_name,strlen(namelist[i]->d_name)-4); for(j = 0; j < disabled_len; j++) { if(!g_strcasecmp(disabled[j], rule_name)) goto skip; } snprintf(rpath, PATH_MAX, "%s/%s", path, namelist[i]->d_name); if (stat(rpath, &sb) == -1) goto skip; if((sb.st_mode & S_IFMT) != S_IFREG) goto next; load_simple_file(rpath); next: g_free(rule_name); rule_name = NULL; free(namelist[i]); continue; skip: g_debug("skip rule: %s", namelist[i]->d_name); g_free(rule_name); rule_name = NULL; free(namelist[i]); } free(namelist); } return TRUE; } void read_rules(void) { load_simple_directory(QUOTEME(CONFIG_PATH)"/simple.d"); load_simple_file(QUOTEME(CONFIG_PATH)"/simple.conf"); return; } int rule_applies(u_proc *proc, struct simple_rule *rule) { // u_proc_ensure(proc, EXE, TRUE); // printf("add proc %d to %s\n", proc->pid, proc->exe); gboolean match = FALSE; if(rule->glob_cmd) { if(u_proc_ensure(proc, CMDLINE, FALSE) && proc->cmdline_match) { match = g_pattern_match_string(rule->glob_cmd, proc->cmdline_match); simple_debug("match pid:%d cmdline glob:'%s' cmdline:'%s' = %d", proc->pid, rule->pattern, proc->cmdline_match, match) if(match) return TRUE; } } if(rule->glob_basename) { if(u_proc_ensure(proc, CMDLINE, FALSE) && proc->cmdfile) { match = g_pattern_match_string(rule->glob_basename, proc->cmdfile); simple_debug("match pid:%d basename glob:'%s' basename:'%s' = %d", proc->pid, rule->pattern, proc->cmdfile, match) if(match) return TRUE; } } if(rule->glob_exe) { if(u_proc_ensure(proc, EXE, FALSE) && proc->exe) { match = g_pattern_match_string(rule->glob_exe, proc->exe); simple_debug("match pid:%d exe glob:'%s' exe:'%s' = %d", proc->pid, rule->pattern, proc->exe, match) if(match) return TRUE; } } if(rule->re_exe) { if(u_proc_ensure(proc, EXE, FALSE) && proc->exe) { match = g_regex_match(rule->re_exe, proc->exe, 0, NULL); simple_debug("match pid:%d cmdline re:'%s' exe:'%s' = %d", proc->pid, rule->pattern, proc->cmdline_match, match) if(match) return TRUE; } } if(rule->re_cmd) { if(u_proc_ensure(proc, CMDLINE, FALSE) && proc->cmdline) { match = g_regex_match(rule->re_cmd, proc->cmdline_match, 0, NULL); simple_debug("match pid:%d cmdline re:'%s' cmdline:'%s' = %d", proc->pid, rule->pattern, proc->cmdline_match, match) if(match) return TRUE; } } if(rule->re_basename) { if(u_proc_ensure(proc, CMDLINE, FALSE) && proc->cmdfile) { match = g_regex_match(rule->re_basename, proc->cmdfile, 0, NULL); simple_debug("match pid:%d cmdline re:'%s' basename:'%s' = %d", proc->pid, rule->pattern, proc->cmdline_match, match) if(match) return TRUE; } } return FALSE; } void simple_add_flag(u_filter *filter, u_proc *proc, struct simple_rule *rule) { u_flag *t = rule->template; u_flag *nf = u_flag_new(filter, t->name); if(t->reason) nf->reason = g_strdup(t->reason); if(t->timeout) nf->timeout = time(NULL) + t->timeout; nf->priority = t->priority; nf->value = t->value; nf->threshold = t->threshold; nf->inherit = t->inherit; u_trace("add flag %s to %d", nf->name, proc->pid); u_flag_add(proc, nf); } int simplerules_run_proc(u_proc *proc, u_filter *filter) { GList *cur = ((struct filter_data *)filter->data)->rules; struct simple_rule *rule; while(cur) { rule = cur->data; if(rule_applies(proc, rule)) { simple_add_flag(filter, proc, rule); } cur = g_list_next(cur); } return FILTER_MIX(FILTER_RERUN_EXEC | FILTER_STOP, 0); } int simplerules_init() { int i = 0; simplerules_id = get_plugin_id(); u_filter *filter; simplerules_debug = g_key_file_get_boolean(config_data, "simplerules", "debug", NULL); // target_rules = NULL; read_rules(); // if(target_rules) { for(i=0; i < LIST_END; i++) { if(FILTERS[i].rules) { filter = filter_new(); filter->type = FILTER_C; filter->name = g_strdup("simplerules"); filter->callback = simplerules_run_proc; filter->data = &FILTERS[i]; filter_register(filter, i == LIST_FAST); } } // } return 0; } poelzi-ulatencyd-55515a9/modules/test.c000066400000000000000000000015101154664534000200620ustar00rootroot00000000000000/* Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. */ #include #include int test_init() { printf("test module loaded\n"); return 0; }poelzi-ulatencyd-55515a9/modules/xwatch.c000066400000000000000000000317551154664534000204170ustar00rootroot00000000000000/* Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. */ #ifndef G_LOG_DOMAIN #define G_LOG_DOMAIN "xwatch" #endif #include "config.h" #include "ulatency.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DEBUG_XWATCH #define dprint(...) printf(__VA_ARGS__) #else #define dprint(...) #endif #define DEFAULT_INTERVAL 1000 #define RETRY_TIMEOUT 30 struct x_server { char *name; // unique name for identification time_t last_try; uid_t uid; char *display; xcb_connection_t *connection; xcb_screen_t *screen; xcb_atom_t atom_active; xcb_atom_t atom_pid; xcb_atom_t atom_client; xcb_atom_t window_atom; xcb_atom_t cardinal_atom; xcb_atom_t string_atom; }; static void free_x_server(struct x_server *xs) { g_debug("remove x_server display: %s", xs->display); if(xs->connection) xcb_disconnect (xs->connection); g_free(xs->name); g_free(xs->display); } static int xwatch_id; // unique plugin id static GList *server_list = NULL; // list of x_server objects static char *localhost; // char of localhost static xcb_atom_t get_atom (xcb_connection_t *conn, xcb_intern_atom_cookie_t ck) { xcb_intern_atom_reply_t *reply; xcb_atom_t atom; reply = xcb_intern_atom_reply (conn, ck, NULL); if (reply == NULL) return 0; atom = reply->atom; free (reply); return atom; } static inline xcb_intern_atom_cookie_t intern_string (xcb_connection_t *c, const char *s) { return xcb_intern_atom (c, 0, strlen (s), s); } static char * get_localhost() { char *buf = 0; size_t buf_len = 0; int myerror = 0; do { errno = 0; if (buf) { buf_len += buf_len; if ((buf = realloc (buf, buf_len)) == NULL) { g_warning("malloc failed"); return NULL; } } else { buf_len = 128; /* Initial guess */ buf = malloc(buf_len); if (!buf) { g_warning("malloc failed"); return NULL; } } } while (((myerror = gethostname(buf, buf_len)) == 0 && !memchr (buf, '\0', buf_len)) || errno == ENAMETOOLONG); /* gethostname failed, abort. */ if (myerror) { g_warning("can't get hostname"); return NULL; } return buf; } int create_connection(struct x_server *xs) { int screenNum, i, dsp, parsed = 0; char *host; char dispbuf[40]; /* big enough to hold more than 2^64 base 10 */ int dispbuflen; xcb_screen_iterator_t iter; const xcb_setup_t *setup; struct passwd *pw; GPtrArray *xauthptr; char *save_home, *save_xauth = NULL; xs->last_try = time(NULL); g_debug("create x-watch connection: '%s'", xs->display); parsed = xcb_parse_display(xs->display, &host, &dsp, &screenNum); if(!parsed) { g_warning("can't parse display: '%s'", xs->display); return FALSE; } dispbuflen = snprintf(dispbuf, sizeof(dispbuf), "%d", dsp); if(dispbuflen < 0) { printf("cant put display buf\n"); return FALSE; } pw = getpwuid(xs->uid); save_home = g_strdup(getenv("HOME")); save_xauth = g_strdup(getenv("XAUTHORITY")); xauthptr = search_user_env(xs->uid, "XAUTHORITY", TRUE); setenv("HOME", pw->pw_dir, 1); unsetenv("XAUTHORITY"); i = -1; if(seteuid(xs->uid)) { g_warning("can't seteuid to %d", xs->uid); goto error; } do { xs->connection = xcb_connect(xs->display, &screenNum); if(xs->connection) { setup = xcb_get_setup(xs->connection); if(setup) { g_debug("connected to X11 %s", xs->display); break; } } i++; if(!xauthptr) goto error; if(i >= xauthptr->len) goto error; setenv("XAUTHORITY", g_ptr_array_index(xauthptr, i), 1); } while(TRUE); if((getuid() == 0) && seteuid(0)) { g_error("can't switch back to root"); } g_ptr_array_unref(xauthptr); if(save_home) setenv("HOME", save_home, 1); else unsetenv("HOME"); if(save_xauth) setenv("XAUTHORITY", save_xauth, 1); else unsetenv("XAUTHORITY"); g_free(save_xauth); g_free(save_home); iter = xcb_setup_roots_iterator(setup); // we want the screen at index screenNum of the iterator for (i = 0; i < screenNum; ++i) { xcb_screen_next (&iter); } xs->screen = iter.data; g_message("connected to X11 host: %s display: %d screen: %d", localhost, dsp, screenNum); // fillup the x server atoms xcb_intern_atom_cookie_t net_active_ck = intern_string (xs->connection, "_NET_ACTIVE_WINDOW"); xcb_intern_atom_cookie_t net_pid_ck = intern_string (xs->connection, "_NET_WM_PID"); xcb_intern_atom_cookie_t net_client_ck = intern_string (xs->connection, "WM_CLIENT_MACHINE"); xs->atom_active = get_atom (xs->connection, net_active_ck); xs->atom_pid = get_atom (xs->connection, net_pid_ck); xs->atom_client = get_atom (xs->connection, net_client_ck); xcb_intern_atom_cookie_t window_ck = intern_string (xs->connection, "WINDOW"); xcb_intern_atom_cookie_t cardinal_ck = intern_string (xs->connection, "CARDINAL"); xcb_intern_atom_cookie_t string_ck = intern_string (xs->connection, "STRING"); xs->window_atom = get_atom (xs->connection, window_ck); xs->cardinal_atom = get_atom (xs->connection, cardinal_ck); xs->string_atom = get_atom (xs->connection, string_ck); return TRUE; error: seteuid(0); g_message("could not connect to display %s \n", xs->display); // restore env if(save_home) setenv("HOME", save_home, 1); else unsetenv("HOME"); if(save_xauth) setenv("XAUTHORITY", save_xauth, 1); else unsetenv("XAUTHORITY"); g_free(save_xauth); g_free(save_home); return FALSE; } // test if connection is alive, initiate new connection if lost etc int test_connection(struct x_server *xs) { if(xs->connection) { if(xcb_connection_has_error(xs->connection)) { xcb_disconnect(xs->connection); xs->connection = NULL; xs->screen = NULL; g_debug("got connection problems. disconnectd %s", xs->display); } else { return TRUE; } } if(!xs->connection) { if(xs->last_try && xs->last_try + RETRY_TIMEOUT > time(NULL)) return FALSE; return create_connection(xs); } return FALSE; } gint match_display(gconstpointer a, gconstpointer b) { const struct x_server *xa = a; return strcmp(xa->display, (const char *)b); } void del_connection(struct x_server *rm) { free_x_server(rm); server_list = g_list_remove(server_list, rm); g_free(rm); } struct x_server *add_connection(const char *name, uid_t uid, const char *display) { struct x_server *nc; GList *cur; uid_t myid = getuid(); // test if we are root. we will not be able to connect to other users // if we are not root, so skip them if(myid && myid != uid) return NULL; while(TRUE) { cur = g_list_find_custom(server_list, display, match_display); if(!cur) break; free_x_server(cur->data); server_list = g_list_remove(server_list, cur->data); } nc = g_malloc0(sizeof(struct x_server)); nc->name = g_strdup(name); nc->display = g_strdup(display); nc->uid = uid; create_connection(nc); server_list = g_list_append(server_list, nc); return nc; } pid_t read_pid(struct x_server *conn, int *err) { xcb_generic_error_t *error; *err = 0; pid_t rv = 0; dprint("dsp: %s xs: %p conn: %p\n", conn->display, conn, conn->connection); xcb_get_property_cookie_t naw = xcb_get_property (conn->connection, 0, conn->screen->root, conn->atom_active, conn->window_atom, 0, 1); xcb_get_property_reply_t *rep = xcb_get_property_reply (conn->connection, naw, &error); if(!rep || !xcb_get_property_value_length(rep)) return 0; dprint("len: %d ", xcb_get_property_value_length (rep)); uint32_t *win = xcb_get_property_value(rep); dprint("win: 0x%x\n", *win); g_free(rep); xcb_get_property_cookie_t caw = xcb_get_property (conn->connection, 0, *win, conn->atom_pid, conn->cardinal_atom, 0, 1); xcb_get_property_reply_t *rep2 = xcb_get_property_reply (conn->connection, caw, &error); if((error && error->response_type == 0) || !rep2 || !xcb_get_property_value_length(rep2)) { g_free(rep2); goto error; } dprint("len: %d ", xcb_get_property_value_length (rep2)); uint32_t *pid = xcb_get_property_value(rep2); dprint("pid: %d\n", *pid); g_free(rep2); xcb_get_property_cookie_t ccaw = xcb_get_property (conn->connection, 0, *win, conn->atom_client, conn->string_atom, 0, strlen(localhost)); xcb_get_property_reply_t *rep3 = xcb_get_property_reply (conn->connection, ccaw, &error); if((error && error->response_type == 0) || !rep3 || !xcb_get_property_value_length(rep3)) { g_free(rep3); goto error; } char *client = xcb_get_property_value(rep3); #ifdef DEBUG_XWATCH char *tmp = g_strndup(xcb_get_property_value(rep3), xcb_get_property_value_length(rep3)); dprint("client: %d %s\n", xcb_get_property_value_length(rep3), tmp); g_free(tmp); #endif if(client && !strncmp(client, localhost, xcb_get_property_value_length(rep3))) { rv = *pid; } g_free(rep3); return rv; error: // error in connection. free x_server connection if(error && error->response_type == 0 && error->error_code == 3) return 0; *err = 1; if(error) g_debug("xcb error: %d %d\n", error->response_type, error->error_code); return 0; } #ifndef TEST_XWATCH static gboolean update_all_server(gpointer data) { GList *cur; pid_t pid; int i; u_session *sess; GList *csess; struct x_server *xs; // check the session list for new/changed servers // remove dead servers for(i = 0; i < g_list_length(server_list);) { int found = FALSE; cur = g_list_nth(server_list, i); xs = cur->data; csess = g_list_first(U_session_list); while(csess) { sess = csess->data; if(!strcmp(xs->name, sess->name)) { found = TRUE; break; } csess = g_list_next(csess); } if(!found) { del_connection(xs); } else { i++; } } csess = g_list_first(U_session_list); while(csess) { sess = csess->data; int found = FALSE; GList *xcur = g_list_first(server_list); while(xcur) { xs = xcur->data; if(!strcmp(xs->name, sess->name)) { found = TRUE; break; } xcur = g_list_next(xcur); } if(!found && sess->X11Display && strcmp(sess->X11Display, "")) { add_connection(sess->name, sess->uid, sess->X11Display); } csess = g_list_next(csess); } int error = 0; cur = server_list; while(cur) { struct x_server *xs = cur->data; struct user_active *ua = get_userlist(xs->uid, TRUE); // we take over the active pid if noone is doing it if(ua->active_agent == USER_ACTIVE_AGENT_NONE) ua->active_agent = xwatch_id; // test if another agent is doing the active pid if(ua->active_agent != xwatch_id) { cur = g_list_next(cur); continue; } // if we can't connect, skip if(!test_connection(xs)) { cur = g_list_next(cur); continue; } pid = read_pid(xs, &error); if(pid && error == 0) { //printf ("current uid: %d pid: %d\n", xs->uid, pid); set_active_pid(xs->uid, pid); } cur = g_list_next(cur); } return TRUE; } #endif int xwatch_init() { localhost = get_localhost(); if(!localhost) { g_warning("can't find localhost name\n"); return 0; } xwatch_id = get_plugin_id(); #ifndef TEST_XWATCH GError *error = NULL; int interval = g_key_file_get_integer(config_data, "xwatch", "poll_interval", &error); if(error && error->code) interval = DEFAULT_INTERVAL; g_timeout_add(interval, update_all_server, NULL); g_message("x server observation active. poll interval: %d", interval); #endif return 0; } poelzi-ulatencyd-55515a9/rules/000077500000000000000000000000001154664534000164245ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/rules/00-fixers.lua000066400000000000000000000056621154664534000206550ustar00rootroot00000000000000--[[ Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. License: GNU General Public License 3 or later common code for fixing bad behaviour of desktop uis ]]-- -- search if name is in table lst function in_list(name, lst) for i,v in ipairs(lst) do if name == v then return true end end return false end --[[ UIRunnerFix class this allows easy defination of bad behaving starters that are supposed to change the process group of it's children ]]-- RunnerFix = {} function RunnerFix:check(proc) -- remove old blacklist items if in_list(proc.cmdfile, self.bad_starters) and proc.pgrp == proc.pid then -- we save two blacklists, the pgrp blacklist and the pid blacklist -- so we can lookup very fast if we should change a pgrp self.blacklist_pgrp[proc.pgrp] = proc.pid self.blacklist_pid[proc.pid] = proc.pgrp end parent = proc:get_parent() if parent then -- change direct children if in_list(parent.cmdfile, self.bad_starters) then proc:set_pgid(proc.pid) -- change init children elseif ( parent.pid == 1 and self.blacklist_pgrp[proc.pgrp] ) or ( in_list(parent.cmdfile, self.bad_starters) and self.blacklist_pgrp[proc.pgrp] ) then proc:set_pgid(proc.pid) end end return ulatency.filter_rv(ulatency.FILTER_STOP) end function RunnerFix:exit(proc) if self.blacklist_pid[proc.pid] then self.blacklist_pgrp[self.blacklist_pid[proc.pid]] = nil self.blacklist_pid[proc.pid] = nil end end local function RunnerFix_tostring(data) return "" end local RunnerFixMeta = { __index = RunnerFix, __tostring = RunnerFix_tostring} --re_basename = "metacity", function RunnerFix.new(name, bad_starters) return setmetatable({ name=name, bad_starters=bad_starters, blacklist_pgrp={}, blacklist_pid={}, }, RunnerFixMeta) end -- on start we have to fix all processes that have descented from a group -- of programs function cleanup_desktop_mess(bad_starters) local procs = ulatency.list_processes() local init = ulatency.get_pid(1) local remap = {} -- we search for bad starters first and save their pgrp for i,proc in ipairs(procs) do if in_list(proc.cmdfile, bad_starters) then remap[#remap+1] = proc.pgrp -- fix all children of these bad starters for i,child in ipairs(proc:get_children()) do child:set_pgid(child.pid) end end end -- we now search for detached processes which may now belong to init -- only change the top level entries. the fix will travel down as long as -- needed. this may take some iterations, thou for i,proc in ipairs(init:get_children()) do for i,map in ipairs(remap) do if not in_list(proc.cmdfile, bad_starters) then if proc.pgrp == map then proc:set_pgid(proc.pid) end end end end return false end poelzi-ulatencyd-55515a9/rules/desktop.lua000066400000000000000000000010571154664534000206030ustar00rootroot00000000000000--[[ Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. License: GNU General Public License 3 or later ]]-- DesktopEssential = { name = "DesktopEssential", re_cmdline = "/usr/bin/X", check = function(self, proc) local flag = ulatency.new_flag{name="system.essential"} proc:add_flag(flag) -- adjust the oom score adjust so x server will more likely survive proc:set_oom_score(-400) rv = ulatency.filter_rv(ulatency.FILTER_STOP) return rv end } ulatency.register_filter(DesktopEssential) poelzi-ulatencyd-55515a9/rules/gnome.lua000066400000000000000000000027011154664534000202340ustar00rootroot00000000000000--[[ Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. License: GNU General Public License 3 or later ]]-- GnomeUI = { name = "GnomeUI", re_basename = "metacity|compiz|gnome-panel|gtk-window-decorator|nautilus", --re_basename = "metacity", check = function(self, proc) local flag = ulatency.new_flag("user.ui") proc:add_flag(flag) proc:set_oom_score(-300) rv = ulatency.filter_rv(ulatency.FILTER_STOP) return rv end } GnomeCore = { name = "GnomeCore", re_basename = "x-session-manager", -- adjust the oom score adjust so x server will more likely survive check = function(self, proc) proc:set_oom_score(-300) return ulatency.filter_rv(ulatency.FILTER_STOP) end } -- gnome does a very bad job in setting grpid's, causing the complete -- desktop to be run under one group. we fix this problem here, ugly -- but working -- filter that instantly sets a fake group on newly spawned processes from -- gnome-panel or x-session-manager GnomeFix = RunnerFix.new("GnomeFix", {"gnome-panel", "x-session-manager", "gnome-session"}) -- on start we have to fix all processes that have descented from kde local function cleanup_gnome_mess() cleanup_desktop_mess({"x-session-manager", "gnome-session", "gnome-panel"}) return false end ulatency.add_timeout(cleanup_gnome_mess, 1000) ulatency.register_filter(GnomeCore) ulatency.register_filter(GnomeUI) ulatency.register_filter(GnomeFix) poelzi-ulatencyd-55515a9/rules/io.lua000066400000000000000000000064671154664534000175530ustar00rootroot00000000000000--[[ IO rules these are optimizers for IO ]]-- posix = require("posix") BottleNeck = { -- detects high loads on discs and enables the cgroup group_isolation -- when the treshold is over a limited time -- group_isolation is only good on heavy io load last_data = {}, history = {}, -- list of entries to be ignored (partitions) ignored = {}, first_run = true, window = tonumber(ulatency.get_config("io", "window") or 10), threshold = tonumber(ulatency.get_config("io", "threshold") or 100000), percent = tonumber(ulatency.get_config("io", "percent") or 50) , last_set = {}, calc_history = function(self, old, new) -- calculates if the threshold was reached and puts the result into the history function check(old, new) if new < old then -- overflow, better be safe :-) return true end if new >= old + self.threshold then return true end return false end result = check(tonumber(old[14]), tonumber(new[14])) local h = self.history[old[3]] table.insert(h, 1, result) h[self.window+1] = nil end, add_entry = function(self, chunks) local dev = chunks[3] if self.ignored[dev] then return end if self.first_run == true then self:set_scheduler(dev, ulatency.get_config("io", "scheduler") or "cfq") end if not self.history[dev] then if posix.access(ulatency.mountpoints["sysfs"] .. "/block/"..dev) == 0 then self.history[dev] = {} else self.ignored[dev] = true end end if self.last_data[dev] then self:calc_history(self.last_data[dev], chunks) end self.last_data[dev] = chunks end, set_scheduler = function(self, dev, scheduler) local path = ulatency.mountpoints["sysfs"] .. "/block/" .. dev .. "/queue/scheduler" local fp = io.open(path, "w") if not fp then return end fp:write(tostring(scheduler)) fp:close() end, set_isolation = function(self, dev, value) if self.last_set[dev] == value then return end ulatency.log_debug("IO: set group isolation on dev "..dev.." to "..tostring(value)) self.last_set[dev] = value local path = ulatency.mountpoints["sysfs"] .. "/block/" .. dev .. "/queue/iosched/group_isolation" local fp = io.open(path, "w") if not fp then return end fp:write(tostring(value)) fp:close() end, parse_data = function(self) fp = io.open("/proc/diskstats", "r") if not fp then return end for line in fp:lines() do local chunks = string.split(line, " ") self:add_entry(chunks) end self.first_run = false end, decide = function(self) for dev, history in pairs(self.history) do if #history == self.window then local yes = 0 for n, x in ipairs(history) do if x then yes = yes +1 end end if (yes * 100) >= (#history * self.percent) then self:set_isolation(dev, 1) else self:set_isolation(dev, 0) end end end end, iterate = function(self) -- called from timeout function self:parse_data() self:decide() end } local function iterate() -- called from timeout function BottleNeck:iterate() return true end if ulatency.tree_loaded("blkio") then ulatency.add_timeout(iterate, 1000) end poelzi-ulatencyd-55515a9/rules/kde.lua000066400000000000000000000031651154664534000176770ustar00rootroot00000000000000--[[ Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. License: GNU General Public License 3 or later ]]-- local Kde_Ui_Tab = { "kuiserver", "kwalletmanager", "knotify4", "kmix", "kded4", "kwin", "plasma-desktop" } KdeUI = { name = "KdeUI", re_basename = re_from_table(Kde_Ui_Tab), --re_basename = "metacity", check = function(self, proc) local flag = ulatency.new_flag{name="user.ui"} proc:add_flag(flag) rv = ulatency.filter_rv(ulatency.FILTER_STOP) return rv end } KdeCore = { name = "KdeCore", re_basename = "startkde|kdeinit4|plasma-desktop", check = function(self, proc) if proc.cmd_file == "plasma-desktop" then -- plasma requires a lot of ram and is buggy sometimes, better we -- do not set it's oom adj to low proc:set_oom_score(-130) else proc:set_oom_score(-300) end return ulatency.filter_rv(ulatency.FILTER_STOP) end } -- kde does a very bad job in setting grpid's, causing the complete -- desktop to be run under one group. we fix this problem here, ugly -- but working -- filter that instantly sets a fake group on newly spawned processes from -- krunner und kdeinit4 KdeRunnerFix = RunnerFix.new("KdeRunnerFix", {"kdeinit4: kdeinit4 Running...", "krunner"}) -- on start we have to fix all processes that have descented from kde local function cleanup_kde_mess() cleanup_desktop_mess({"kdeinit4: kdeinit4 Running...", "krunner"}) return false end ulatency.add_timeout(cleanup_kde_mess, 1000) ulatency.register_filter(KdeCore) ulatency.register_filter(KdeUI) ulatency.register_filter(KdeRunnerFix) poelzi-ulatencyd-55515a9/rules/lxde.lua000066400000000000000000000007141154664534000200650ustar00rootroot00000000000000--[[ Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. License: GNU General Public License 3 or later ]]-- LxdeRunnerFix = RunnerFix.new("LxdeRunnerFix", {"lxsession"}) -- on start we have to fix all processes that have descented from kde local function cleanup_lxde_mess() cleanup_desktop_mess({"lxsession"}) return false end ulatency.add_timeout(cleanup_lxde_mess, 1000) ulatency.register_filter(LxdeRunnerFix) poelzi-ulatencyd-55515a9/rules/protectors.lua000066400000000000000000000114541154664534000213400ustar00rootroot00000000000000--[[ Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. License: GNU General Public License 3 or later ]]-- local physical_ram = false local memory_pressure = false local pressure_timeout = 500 -- tracker of swapout local vminfo = ulatency.get_vminfo() local meminfo = ulatency.get_meminfo() local swapout_stats_last = vminfo.vm_pswpout local swapout_stats = {} --pprint(meminfo) --pprint(vminfo) function update_caches() local new_memory_pressure = false vminfo = ulatency.get_vminfo() meminfo = ulatency.get_meminfo() table.insert(swapout_stats, 1, vminfo.vm_pswpout - swapout_stats_last) swapout_stats_last = vminfo.vm_pswpout swapout_stats[20] = nil local swap_memory_pressure = true for i,j in ipairs(swapout_stats) do if j == 0 then swap_memory_pressure = false end end local min_free = num_or_percent(ulatency.get_config("memory", "min_free_ram"), meminfo.kb_main_total) --print("min free", min_free, tonumber(meminfo.kb_main_cached) + tonumber(meminfo.kb_main_free)) if (tonumber(meminfo.kb_main_cached) + tonumber(meminfo.kb_main_free)) <= min_free then new_memory_pressure = true end --print("pressure", new_memory_pressure, memory_pressure) new_memory_pressure = swap_memory_pressure or new_memory_pressure if(memory_pressure ~= new_memory_pressure and new_memory_pressure) then ulatency.log_warning("memory pressure detected !") ulatency.run_iteration() elseif (memory_pressure ~= new_memory_pressure and not new_memory_pressure) then ulatency.log_info("memory pressure ended") end memory_pressure = new_memory_pressure return true end update_caches() --ulatency.quit_daemon() local max_targets = ulatency.get_config("memory", "top_targets") or 0 if max_targets then max_targets = tonumber(max_targets) end local target_max_rss = num_or_percent(ulatency.get_config("memory", "target_max_rss"), meminfo.kb_main_total, false) ProtectorMemory = { name = "ProtectorMemory", targets = {}, sure_targets = {}, poison_groups = {}, precheck = function(self) self.targets = {} self.sure_targets = {} self.poison_groups = {} local flag = nil if not memory_pressure then return false end for i, flg in ipairs(ulatency.list_flags()) do if flg.is_source and flg.name == "pressure" and flg.reason == "memory" then flg.timeout = ulatency.get_time(pressure_timeout) flag = flg end end if not flag then flag = ulatency.new_flag{name="pressure", reason="memory", timeout=ulatency.get_time(pressure_timeout)} ulatency.add_flag(flag) end return true end, check = function(self, proc) self.poison_groups[proc.pgrp] = (self.poison_groups[proc.pgrp] or 0) + proc.vm_rss self.targets[#self.targets+1] = proc table.sort(self.targets, function(a, b) if a.is_invalid or b.is_invalid then return false end return a.rss > b.rss end) self.targets[max_targets+1] = nil if target_max_rss then if proc.rss >= target_max_rss then self.sure_targets[#self.sure_targets+1] = proc end end return 0 end, postcheck = function(self) --pprint(self.targets) --pprint(self.sure_targets) --pprint(self.poison_groups) local top_targets = {} for sess,size in pairs(self.poison_groups) do top_targets[#top_targets+1] = {sess, size} end table.sort(top_targets, function(a,b) return a[2]>b[2] end) for v = 1, tonumber(ulatency.get_config("memory", "min_add_groups")) do local flag, added = ulatency.add_adjust_flag( ulatency.list_flags(), {name="user.poison.group", reason="memory", value=top_targets[v][1]}, {timeout=ulatency.get_time(pressure_timeout)} ) if not added then ulatency.add_flag(flag) flag.threshold = top_targets[v][2] end end local flag = ulatency.new_flag{name="user.poison", reason="memory", timeout=ulatency.get_time(pressure_timeout)} local added = 0 local min_add = tonumber(ulatency.get_config("memory", "min_add_targets") or 0) for i,proc in ipairs(self.sure_targets) do if proc.is_valid then proc:clear_flag_source() proc:add_flag(flag) added = added + 1 end end for i,proc in ipairs(self.targets) do if added >= min_add then break end if proc.is_valid then proc:clear_flag_source() proc:add_flag(flag) end end rv = ulatency.filter_rv(ulatency.FILTER_STOP) end, } ulatency.register_filter(ProtectorMemory) ulatency.add_timeout(update_caches, 1000)poelzi-ulatencyd-55515a9/rules/scheduler.lua000066400000000000000000000161641154664534000211150ustar00rootroot00000000000000--[[ Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. License: GNU General Public License 3 or later ]]-- require("posix") u_groups = {} local UL_PID = posix.getpid()["pid"] ul_group_cpu = CGroup.new("s_ul", { ["cpu.shares"]="500"}, "cpu") ul_group_cpu:add_task(UL_PID) ul_group_cpu:commit() ul_group_mem = CGroup.new("s_ul", { ["?memory.swappiness"]="0"}, "memory") ul_group_cpu:add_task(UL_PID) ul_group_cpu:commit() -- WARNING: don't use non alpha numeric characters in name -- FIXME: build validator function check_label(labels, proc) for j, flag in pairs(proc:list_flags(true)) do for k, slabel in pairs(labels) do if flag.name == slabel then return true end end end end local function check(proc, rule) assert(proc) if rule.label then if check_label(rule.label, proc) then if rule.check then return rule.check(proc) and rule or nil else return rule end end elseif rule.check then if rule.check(proc) then return rule end end return nil end local function run_list(proc, lst) local rv = {} for key, rule in ipairs(lst) do match = check(proc, rule) if match then rv[#rv+1] = match if match.children then best_subs = run_list(proc, match.children) --print("got bestsubs", type(best_subs), best_subs, #best_subs) if best_subs and #best_subs > 0 then for i,sub in ipairs(best_subs) do rv[#rv+1] = sub end end end break end end return rv end local function format_name(proc, map) -- generates the final path for the process for the map if map.cgroups_name then if type(map.cgroups_name) == "function" then return map.cgroups_name(proc) end function get(name) return tostring(proc[name]) end return string.gsub(map.cgroups_name, "%$\{(%w+)\}", get) end return map.name end local function build_path_parts(proc, res) -- build a array for local rv = {} for i,k in ipairs(res) do local cname = format_name(proc, k) rv[#rv+1] = cname end return rv end local function create_group(proc, prefix, mapping, subsys) name = format_name(proc, mapping) if #prefix > 0 then path = prefix .. "/" .. name else path = name end rv = CGroup.new(path, mapping.param, subsys) if mapping.adjust then rv.adjust[#rv.adjust+1] = mapping.adjust end if mapping.adjust_new then mapping.adjust_new(rv, proc) end rv:commit() return rv end local function map_to_group(proc, parts, subsys) local chain = build_path_parts(proc, parts) local path = subsys .."/".. table.concat(chain, "/") local cgr = CGroup.get_group(path) if cgr then cgr:run_adjust(proc) return cgr end local prefix = "" for i, parrule in ipairs(parts) do --local parent = create_group(proc, prefix, cgr = create_group(proc, prefix, parrule, subsys) prefix = cgr.name end --print("final prefix", prefix) --CGroup.new(mapping.name, )n-ar return cgr end Scheduler = {C_FILTER = false, ITERATION = 1} function Scheduler:all() local group if ulatency.get_flags_changed() then self.C_FILTER = false end for j, flag in pairs(ulatency.list_flags()) do if flag.name == "pressure" or flag.name == "emergency" then self.C_FILTER = false end end if self.ITERATION > (tonumber(ulatency.get_config("scheduler", "full_run") or 15)) then self.C_FILTER = false self.ITERATION = 1 end -- list only changed processes self:update_caches() ulatency.log_debug("scheduler filter:".. tostring(self.C_FILTER)) for k,proc in ipairs(ulatency.list_processes(self.C_FILTER)) do --print("sched", proc, proc.cmdline) self:one(proc, false) end self.C_FILTER = true self.ITERATION = self.ITERATION + 1 return true end function Scheduler:load_config(name) if not name then name = ulatency.get_config("scheduler", "mapping") if not name then ulatency.log_error("no default scheduler config specified in config file") end end ulatency.log_info("Scheduler use mapping: "..name) local mapping_name = "SCHEDULER_MAPPING_"..string.upper(name) MAPPING = getfenv()[mapping_name] if not MAPPING then if not self.MAPPING then ulatency.log_error("invalid mapping: "..mapping_name) else ulatency.log_warning("invalid mapping: "..mapping_name) end return false end ulatency.log_debug("use schduler map \n" .. to_string(MAPPING)) self.MAPPING = MAPPING self.CONFIG_NAME = name return true end function Scheduler:update_caches() Scheduler.meminfo = ulatency.get_meminfo() Scheduler.vminfo = ulatency.get_vminfo() end function Scheduler:one(proc) return self:_one(proc, true) end function Scheduler:_one(proc, single) if not self.MAPPING then self:load_config() end if single then self:update_caches() end if proc.block_scheduler == 0 then -- we shall not touch us if proc.pid == UL_PID then proc:clear_changed() return true end for x,subsys in ipairs(ulatency.get_cgroup_subsystems()) do map = self.MAPPING[subsys] or SCHEDULER_MAPPING_DEFAULT[subsys] if map and ulatency.tree_loaded(subsys) then local mappings = run_list(proc, map) --pprint(mappings) group = map_to_group(proc, mappings, subsys) --print(tostring(group)) --pprint(mappings) --print(tostring(proc.pid) .. " : ".. tostring(group)) if group then if group:is_dirty() then group:commit() end --print("add task", proc.pid, group) -- get_current_tasks can fail if the process is already dead local tasks = proc:get_current_task_pids(true) if tasks then group:add_task_list(proc.pid, tasks) group:commit() end else ulatency.log_debug("no group found for: "..tostring(proc).." subsystem:"..tostring(subsys)) end end end proc:clear_changed() --pprint(build_path_parts(proc, res)) end return true end function Scheduler:list_configs() rv = {} for k,v in pairs(getfenv()) do if string.sub(k, 1, 18 ) == "SCHEDULER_MAPPING_" then name = string.lower(string.sub(k, 19)) if v.info then if not v.info.hidden then rv[#rv+1] = name end else rv[#rv+1] = name end end end return rv end function Scheduler:get_config_description(name) name = string.upper(name) local mapping = getfenv()["SCHEDULER_MAPPING_" .. name] if mapping and mapping.info then return mapping.info.description end end function Scheduler:set_config(config) if ulatency.get_config("scheduler", "allow_reconfigure") ~= 'true' then ulatencyd.log_info("requested scheduler reconfiguration denied") return false end local rv = self:load_config(config) if rv then self.C_FILTER = false ulatency.run_iteration() end return rv end function Scheduler:get_config() if self.CONFIG_NAME then return string.lower(self.CONFIG_NAME) end return nil end -- register scheduler ulatency.scheduler = Scheduler ulatency.load_rule_directory("scheduler/")poelzi-ulatencyd-55515a9/rules/system.lua000066400000000000000000000015501154664534000204540ustar00rootroot00000000000000--[[ Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. License: GNU General Public License 3 or later ]]-- SystemIdle = { name = "SystemIdle", --re_basename = "preload", re_basename = "preload", check = function(self, proc) local flag = ulatency.new_flag{name="daemon.idle", inherit=true} proc:add_flag(flag) proc:set_ioprio(0, ulatency.IOPRIO_CLASS_IDLE) rv = ulatency.filter_rv(ulatency.FILTER_STOP) return rv end } SystemBg = { name = "SystemBg", re_basename = "cron|anacron", check = function(self, proc) local flag = ulatency.new_flag{name="daemon.bg", inherit=true} proc:add_flag(flag) proc:set_ioprio(7, ulatency.IOPRIO_CLASS_BE) rv = ulatency.filter_rv(ulatency.FILTER_STOP) return rv end } ulatency.register_filter(SystemIdle) ulatency.register_filter(SystemBg) poelzi-ulatencyd-55515a9/rules/test.lua000066400000000000000000000023511154664534000201070ustar00rootroot00000000000000flag = ulatency.new_flag{name="test", reason="dbus"} ulatency.add_flag(flag) flag = ulatency.new_flag{name="test", reason="need more data", value=32, threshold=666} ulatency.add_flag(flag) print("-------") pprint(ulatency.get_sessions()) print("-------") act, idle = ulatency.get_uid_stats(1000) print("act:", act, "idle:", idle) PrintProcTest = { name = "Test", --re_basename = "preload", check = function(self, proc) if proc.ppid > 0 then --pprint(proc.cmdline) --print("cf", proc.cmdfile) print("cmd",proc, proc.sched, proc.rtprio, proc.cmd) for i,v in ipairs(proc:get_tasks()) do print(v.tid, v.rtprio, v.sched) end print(proc.received_rt) --print(proc:get_tasks()[1].tid) --print("exe", proc.exe) --print("tasks") --pprint(proc:get_tasks(true)) --pprint(proc.cmdline_match) --pprint(proc.environ) --if proc.environ then -- print("HOME", proc.environ.HOME) --end --print("groups:") --pprint(proc.groups) end rv = ulatency.filter_rv(ulatency.FILTER_STOP) return rv end, postcheck = function() pprint(ulatency.search_uid_env(1000, "DBUS_SESSION_BUS_ADDRESS")) end } ulatency.register_filter(PrintProcTest) poelzi-ulatencyd-55515a9/rules/zz_fixers.lua000066400000000000000000000006651154664534000211610ustar00rootroot00000000000000-- last rules to execute, they can depend on flags set by previous rules MediaIO = { name = "MediaIO", check = function(self, proc) -- we give processes marked with media flags good io prio if ulatency.find_flag(proc:list_flags(), {name="user.media"}) then proc:set_ioprio(7, ulatency.IOPRIO_CLASS_BE) end rv = ulatency.filter_rv(ulatency.FILTER_STOP) return rv end } ulatency.register_filter(MediaIO) poelzi-ulatencyd-55515a9/scripts/000077500000000000000000000000001154664534000167615ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/scripts/lua2dox/000077500000000000000000000000001154664534000203375ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/scripts/lua2dox/Lua.pm000066400000000000000000000070341154664534000214220ustar00rootroot00000000000000package Doxygen::Lua; use warnings; use strict; use Moose; =head1 NAME Doxygen::Lua - Make Doxygen support Lua =head1 VERSION Version 0.03 =cut our $VERSION = '0.03'; has 'mark' => ( is => 'rw', isa => 'Str', default => '--!' ); =head1 SYNOPSIS use Doxygen::Lua; my $p = Doxygen::Lua->new; print $p->parse($input); =head1 DESCRIPTION A script named "lua2dox" will be installed. Then modify your Doxyfile as below: FILTER_PATTERNS = *.lua=../bin/lua2dox That's all! =head1 SUBROUTINES/METHODS =head2 new This function will create a Doxygen::Lua object. =cut =head2 parse This function will parse the given input file and return the result. =cut sub parse { my $self = shift; my $input = shift; my $in_block = 0; my $in_function = 0; my $block_name = q{}; my $result = q{}; my $mark = $self->mark; open FH, "<$input" or die "Can't open $input for reading: $!"; foreach my $line () { chomp $line; # skip normal comments next if $line =~ /^\s*--[^!]/; # remove end of line comments $line =~ s/--[^!].*//; # skip comparison next if $line =~ /==/; # translate to doxygen mark $line =~ s{$mark}{///}; if ($line =~ m{^\s*///}) { $result .= "$line\n"; } # function start elsif ($line =~ /^function/) { $in_function = 1; $line .= q{;}; $line =~ s/:/-/; $result .= "$line\n"; } # function end elsif ($in_function == 1 && $line =~ /^end/) { $in_function = 0; } # block start elsif ($in_function == 0 && $line =~ /^(\S+)\s*=\s*{/ && $line !~ /}/) { $block_name = $1; $in_block = 1; } # block end elsif ($in_function == 0 && $line =~ /^\s*}/ && $in_block == 1) { $block_name = q{}; $in_block = 0; } # variables elsif ($in_function == 0 && $line =~ /=/) { $line =~ s/(?=\S)/$block_name./ if $block_name; $line =~ s{,?(\s*)(?=///|$)}{;$1}; $result .= "$line\n"; } } close FH; return $result; } =head2 mark This function will set the mark style. The default value is "--!". =cut =head1 AUTHOR Alec Chen, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc Doxygen::Lua You can also look for information at: =over 4 =item * RT: CPAN's request tracker L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 ACKNOWLEDGEMENTS =head1 REPOSITORY See http://github.com/alecchen/doxygen-lua =head1 LICENSE AND COPYRIGHT Copyright 2010 Alec Chen. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information. =cut 1; # End of Doxygen::Lua poelzi-ulatencyd-55515a9/scripts/lua2dox/lua2dox000077500000000000000000000003061154664534000216420ustar00rootroot00000000000000#!/usr/bin/env perl use Cwd 'abs_path'; use File::Basename; use lib dirname(__FILE__); use strict; use warnings; use Lua; my $input = $ARGV[0]; my $p = Doxygen::Lua->new; print $p->parse($input); poelzi-ulatencyd-55515a9/scripts/root_alleyoop.sh000077500000000000000000000001551154664534000222100ustar00rootroot00000000000000#!/bin/sh sudo su -c 'env G_SLICE=always-malloc dbus-launch --exit-with-session alleyoop src/ulatencyd -v 3' poelzi-ulatencyd-55515a9/scripts/update_externals.sh000077500000000000000000000002331154664534000226650ustar00rootroot00000000000000#!/bin/sh git subtree pull -P client git://github.com/dodo/ulatency.git HEAD git subtree pull -P docs/wiki git://github.com/poelzi/ulatencyd.wiki.git HEAD poelzi-ulatencyd-55515a9/src/000077500000000000000000000000001154664534000160615ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/src/CMakeLists.txt000066400000000000000000000032301154664534000206170ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8) #add_library(efence STATIC IMPORTED) #set_property(TARGET efence PROPERTY # IMPORTED_LOCATION /usr/lib/libefence.a) add_definitions(-DHAVE_INLINE) #add_definitions(-DINSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DCONFIG_PREFIX=${CONFIG_PREFIX}) #set_directory_properties(PROPERTIES COMPILE_DEFINITIONS_DEBUG BLUBB=1) #set_directory_properties(PROPERTIES COMPILE_FLAGS "-pg -Wall") #set_directory_properties(APPEND PROPERTY COMPILE_FLAGS -Wall) IF(LIBCGROUPS) list(APPEND EXTRA_C "lua_cgroups.c") ENDIF(LIBCGROUPS) IF(DBUS_FOUND AND ENABLE_DBUS) list(APPEND EXTRA_C "dbus.c") IF(POLKIT_FOUND) list(APPEND EXTRA_C "polkit.c") ENDIF(POLKIT_FOUND) ENDIF(DBUS_FOUND AND ENABLE_DBUS) add_executable(ulatencyd core.c ulatencyd.c group.c sysinfo.c sysctl.c coreutils/readutmp.c coreutils/xalloc-die.c linux_netlink.c ${EXTRA_C} lua_binding.c tools.c) target_link_libraries (ulatencyd proc lbc dl ${MY_LUA_LIBRARIES} ${LIBCGROUP_LIBRARIES} ${DBUS_LIBRARIES} ${GLIB2_LIBRARIES} ${GIO_LIBRARIES} ${GTHREAD_LIBRARIES} ${POLKIT_LIBRARIES}) SET_TARGET_PROPERTIES(ulatencyd PROPERTIES COMPILE_FLAGS "${ADD_COMPILE_FLAGS}") configure_file(ulatencyd_cleanup.lua.tmpl ulatencyd_cleanup.lua) install(TARGETS ulatencyd RUNTIME DESTINATION sbin) install(FILES core.lua DESTINATION lib/ulatencyd) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ulatencyd_cleanup.lua PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE DESTINATION lib/ulatencyd) poelzi-ulatencyd-55515a9/src/bc/000077500000000000000000000000001154664534000164455ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/src/bc/CMakeLists.txt000066400000000000000000000001451154664534000212050ustar00rootroot00000000000000include_directories (${CMAKE_CURRENT_SOURCE_DIR}) add_library(lbc STATIC number.c lbc.c) poelzi-ulatencyd-55515a9/src/bc/README000066400000000000000000000024041154664534000173250ustar00rootroot00000000000000This is a big-number library for Lua 5.1. It is based on the arbitrary precision library number.c written by Philip A. Nelson for GNU bc-1.06: http://www.gnu.org/software/bc/ To try the library, just edit Makefile to reflect your installation of Lua and then run make. This will build the library and run a simple test. For detailed installation instructions, see http://www.tecgraf.puc-rio.br/~lhf/ftp/lua/install.html There is no manual but the library is simple and intuitive; see the summary below. Read also test.lua, which shows the library in action. This code is hereby placed in the public domain, except number.c and number.h, which are subject to GPL. Please send comments, suggestions, and bug reports to lhf@tecgraf.puc-rio.br . ------------------------------------------------------------------------------- bc library: __add(x,y) __pow(x,y) digits([n]) mul(x,y) sub(x,y) __div(x,y) __sub(x,y) div(x,y) neg(x) tonumber(x) __eq(x,y) __tostring(x) divmod(x,y) number(x) tostring(x) __lt(x,y) __unm(x) isneg(x) pow(x,y) trunc(x,[n]) __mod(x,y) add(x,y) iszero(x) powmod(x,y,m) version __mul(x,y) compare(x,y) mod(x,y) sqrt(x) ------------------------------------------------------------------------------- poelzi-ulatencyd-55515a9/src/bc/bconfig.h000066400000000000000000000011531154664534000202250ustar00rootroot00000000000000/* * config.h * number.c from GNU bc-1.06 exports some symbols without the bc_ prefix. * This header file fixes this without touching either number.c or number.h * (luckily, number.c already wants to include a config.h). * Clients of number.c should include config.h before number.h. */ #include #include #define NDEBUG #define _zero_ bc_zero #define _one_ bc_one #define _two_ bc_two #define num2str bc_num2str #define mul_base_digits bc_mul_base_digits #define bc_rt_warn bc_error #define bc_rt_error bc_error #define bc_out_of_memory() bc_error(NULL) void bc_error(const char *mesg); poelzi-ulatencyd-55515a9/src/bc/lbc.c000066400000000000000000000137561154664534000173650ustar00rootroot00000000000000/* * lbc.c * big-number library for Lua 5.1 based on GNU bc-1.06 core library * Luiz Henrique de Figueiredo * 04 Apr 2010 22:40:22 * This code is hereby placed in the public domain. */ #include #include #include #include "config.h" #include "bconfig.h" #include "number.h" #include #include #include #define lua_boxpointer(L,u) \ (*(void **)(lua_newuserdata(L, sizeof(void *))) = (u)) #define MYNAME "bc" #define MYVERSION MYNAME " library for " LUA_VERSION " / Apr 2010 / "\ "based on GNU bc-1.06" #define MYTYPE MYNAME " bignumber" static int DIGITS=0; static lua_State *LL=NULL; void bc_error(const char *mesg) { luaL_error(LL,"(bc) %s",mesg ? mesg : "not enough memory"); } void Bnew(lua_State *L, bc_num x) { lua_boxpointer(L,x); luaL_getmetatable(L,MYTYPE); lua_setmetatable(L,-2); } bc_num Bget(lua_State *L, int i) { LL=L; switch (lua_type(L,i)) { case LUA_TNUMBER: case LUA_TSTRING: { bc_num x=NULL; const char *s=lua_tostring(L,i); for (; isspace(*s); s++); /* bc_str2num chokes on spaces */ bc_str2num(&x,(char*)s,DIGITS); if (bc_is_zero(x)) /* bc_str2num chokes on sci notation */ { char *t=strchr(s,'e'); if (t==NULL) t=strchr(s,'E'); if (t!=NULL) { bc_num y=NULL,n=NULL; int c=*t; *t=0; /* harmless const violation! */ bc_str2num(&x,(char*)s,DIGITS); *t=c; bc_int2num(&y,10); bc_int2num(&n,atoi(t+1)); bc_raise(y,n,&y,DIGITS); bc_multiply(x,y,&x,DIGITS); bc_free_num(&y); bc_free_num(&n); } } Bnew(L,x); lua_replace(L,i); return x; } default: return *((void**)luaL_checkudata(L,i,MYTYPE)); } return NULL; } static int Bdo1(lua_State *L, void (*f)(bc_num a, bc_num b, bc_num *c, int n)) { bc_num a=Bget(L,1); bc_num b=Bget(L,2); bc_num c=NULL; f(a,b,&c,DIGITS); Bnew(L,c); return 1; } static int Bdigits(lua_State *L) /** digits([n]) */ { lua_pushinteger(L,DIGITS); DIGITS=luaL_optint(L,1,DIGITS); return 1; } static int Btostring(lua_State *L) /** tostring(x) */ { bc_num a=Bget(L,1); #if 0 if (lua_toboolean(L,2)) { lua_pushlstring(L,a->n_value,a->n_len+a->n_scale); lua_pushinteger(L,a->n_len); return 2; } else #endif { char *s=bc_num2str(a); lua_pushstring(L,s); free(s); return 1; } } static int Btonumber(lua_State *L) /** tonumber(x) */ { Btostring(L); lua_pushnumber(L,lua_tonumber(L,-1)); return 1; } static int Biszero(lua_State *L) /** iszero(x) */ { bc_num a=Bget(L,1); lua_pushboolean(L,bc_is_zero(a)); return 1; } static int Bisneg(lua_State *L) /** isneg(x) */ { bc_num a=Bget(L,1); lua_pushboolean(L,bc_is_neg(a)); return 1; } static int Bnumber(lua_State *L) /** number(x) */ { Bget(L,1); lua_settop(L,1); return 1; } static int Bcompare(lua_State *L) /** compare(x,y) */ { bc_num a=Bget(L,1); bc_num b=Bget(L,2); lua_pushinteger(L,bc_compare(a,b)); return 1; } static int Beq(lua_State *L) { bc_num a=Bget(L,1); bc_num b=Bget(L,2); lua_pushboolean(L,bc_compare(a,b)==0); return 1; } static int Blt(lua_State *L) { bc_num a=Bget(L,1); bc_num b=Bget(L,2); lua_pushboolean(L,bc_compare(a,b)<0); return 1; } static int Badd(lua_State *L) /** add(x,y) */ { return Bdo1(L,bc_add); } static int Bsub(lua_State *L) /** sub(x,y) */ { return Bdo1(L,bc_sub); } static int Bmul(lua_State *L) /** mul(x,y) */ { return Bdo1(L,bc_multiply); } static int Bpow(lua_State *L) /** pow(x,y) */ { return Bdo1(L,bc_raise); } static int Bdiv(lua_State *L) /** div(x,y) */ { bc_num a=Bget(L,1); bc_num b=Bget(L,2); bc_num c=NULL; if (bc_divide(a,b,&c,DIGITS)!=0) return 0; Bnew(L,c); return 1; } static int Bmod(lua_State *L) /** mod(x,y) */ { bc_num a=Bget(L,1); bc_num b=Bget(L,2); bc_num c=NULL; if (bc_modulo(a,b,&c,0)!=0) return 0; Bnew(L,c); return 1; } static int Bdivmod(lua_State *L) /** divmod(x,y) */ { bc_num a=Bget(L,1); bc_num b=Bget(L,2); bc_num q=NULL; bc_num r=NULL; if (bc_divmod(a,b,&q,&r,0)!=0) return 0; Bnew(L,q); Bnew(L,r); return 2; } static int Bgc(lua_State *L) { bc_num x=Bget(L,1); bc_free_num(&x); lua_pushnil(L); lua_setmetatable(L,1); return 0; } static int Bneg(lua_State *L) /** neg(x) */ { bc_num a=bc_zero; bc_num b=Bget(L,1); bc_num c=NULL; bc_sub(a,b,&c,DIGITS); Bnew(L,c); return 1; } static int Btrunc(lua_State *L) /** trunc(x,[n]) */ { bc_num a=Bget(L,1); bc_num c=NULL; bc_divide(a,bc_one,&c,luaL_optint(L,2,0)); Bnew(L,c); return 1; } static int Bpowmod(lua_State *L) /** powmod(x,y,m) */ { bc_num a=Bget(L,1); bc_num k=Bget(L,2); bc_num m=Bget(L,3); bc_num c=NULL; if (bc_raisemod(a,k,m,&c,0)!=0) return 0; Bnew(L,c); return 1; } static int Bsqrt(lua_State *L) /** sqrt(x) */ { bc_num a=Bget(L,1); bc_num b=bc_zero; bc_num c=NULL; bc_add(a,b,&c,DIGITS); /* bc_sqrt works inplace! */ if (bc_sqrt(&c,DIGITS)==0) return 0; Bnew(L,c); return 1; } static const luaL_Reg R[] = { { "__add", Badd }, /** __add(x,y) */ { "__div", Bdiv }, /** __div(x,y) */ { "__eq", Beq }, /** __eq(x,y) */ { "__gc", Bgc }, { "__lt", Blt }, /** __lt(x,y) */ { "__mod", Bmod }, /** __mod(x,y) */ { "__mul", Bmul }, /** __mul(x,y) */ { "__pow", Bpow }, /** __pow(x,y) */ { "__sub", Bsub }, /** __sub(x,y) */ { "__tostring", Btostring}, /** __tostring(x) */ { "__unm", Bneg }, /** __unm(x) */ { "add", Badd }, { "compare", Bcompare}, { "digits", Bdigits }, { "div", Bdiv }, { "divmod", Bdivmod }, { "isneg", Bisneg }, { "iszero", Biszero }, { "mod", Bmod }, { "mul", Bmul }, { "neg", Bneg }, { "number", Bnumber }, { "pow", Bpow }, { "powmod", Bpowmod }, { "sqrt", Bsqrt }, { "sub", Bsub }, { "tonumber", Btonumber}, { "tostring", Btostring}, { "trunc", Btrunc }, { NULL, NULL } }; LUALIB_API int luaopen_bc(lua_State *L) { bc_init_numbers(); luaL_newmetatable(L,MYTYPE); lua_setglobal(L,MYNAME); luaL_register(L,MYNAME,R); lua_pushliteral(L,"version"); /** version */ lua_pushliteral(L,MYVERSION); lua_settable(L,-3); lua_pushliteral(L,"__index"); lua_pushvalue(L,-2); lua_settable(L,-3); return 1; } poelzi-ulatencyd-55515a9/src/bc/lbc.h000066400000000000000000000003111154664534000173510ustar00rootroot00000000000000/* This file was automatically generated. Do not edit! */ LUALIB_API int luaopen_bc(lua_State *L); bc_num Bget(lua_State *L,int i); void Bnew(lua_State *L,bc_num x); void bc_error(const char *mesg); poelzi-ulatencyd-55515a9/src/bc/number.c000066400000000000000000001073041154664534000201060ustar00rootroot00000000000000/* * this file is originally from GNU bc-1.06. it was trimmed down by lhf to fix * a memory leak in bc_raisemod and to remove the free list, as in php bcmath. */ /* number.c: Implements arbitrary precision numbers. */ /* Copyright (C) 1991, 1992, 1993, 1994, 1997, 2000 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License , or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to: The Free Software Foundation, Inc. 59 Temple Place, Suite 330 Boston, MA 02111-1307 USA. You may contact the author by: e-mail: philnelson@acm.org us-mail: Philip A. Nelson Computer Science Department, 9062 Western Washington University Bellingham, WA 98226-9062 *************************************************************************/ #include #include "config.h" #include "bconfig.h" #include #include #include #include /* Prototypes needed for external utility routines. */ /* Storage used for special numbers. */ bc_num _zero_; bc_num _one_; bc_num _two_; /* new_num allocates a number and sets fields to known values. */ bc_num bc_new_num (length, scale) int length, scale; { bc_num temp; temp = (bc_num) malloc (sizeof(bc_struct)); if (temp == NULL) bc_out_of_memory (); temp->n_sign = PLUS; temp->n_len = length; temp->n_scale = scale; temp->n_refs = 1; temp->n_ptr = (char *) malloc (length+scale); if (temp->n_ptr == NULL) bc_out_of_memory(); temp->n_value = temp->n_ptr; memset (temp->n_ptr, 0, length+scale); return temp; } /* "Frees" a bc_num NUM. Actually decreases reference count and only frees the storage if reference count is zero. */ void bc_free_num (num) bc_num *num; { if (*num == NULL) return; (*num)->n_refs--; if ((*num)->n_refs == 0) { if ((*num)->n_ptr) free ((*num)->n_ptr); free (*num); } *num = NULL; } /* Intitialize the number package! */ void bc_init_numbers () { _zero_ = bc_new_num (1,0); _one_ = bc_new_num (1,0); _one_->n_value[0] = 1; _two_ = bc_new_num (1,0); _two_->n_value[0] = 2; } /* Make a copy of a number! Just increments the reference count! */ bc_num bc_copy_num (num) bc_num num; { num->n_refs++; return num; } /* Initialize a number NUM by making it a copy of zero. */ void bc_init_num (num) bc_num *num; { *num = bc_copy_num (_zero_); } /* For many things, we may have leading zeros in a number NUM. _bc_rm_leading_zeros just moves the data "value" pointer to the correct place and adjusts the length. */ static void _bc_rm_leading_zeros (num) bc_num num; { /* We can move n_value to point to the first non zero digit! */ while (*num->n_value == 0 && num->n_len > 1) { num->n_value++; num->n_len--; } } /* Compare two bc numbers. Return value is 0 if equal, -1 if N1 is less than N2 and +1 if N1 is greater than N2. If USE_SIGN is false, just compare the magnitudes. */ static int _bc_do_compare (n1, n2, use_sign, ignore_last) bc_num n1, n2; int use_sign; int ignore_last; { char *n1ptr, *n2ptr; int count; /* First, compare signs. */ if (use_sign && n1->n_sign != n2->n_sign) { if (n1->n_sign == PLUS) return (1); /* Positive N1 > Negative N2 */ else return (-1); /* Negative N1 < Positive N1 */ } /* Now compare the magnitude. */ if (n1->n_len != n2->n_len) { if (n1->n_len > n2->n_len) { /* Magnitude of n1 > n2. */ if (!use_sign || n1->n_sign == PLUS) return (1); else return (-1); } else { /* Magnitude of n1 < n2. */ if (!use_sign || n1->n_sign == PLUS) return (-1); else return (1); } } /* If we get here, they have the same number of integer digits. check the integer part and the equal length part of the fraction. */ count = n1->n_len + MIN (n1->n_scale, n2->n_scale); n1ptr = n1->n_value; n2ptr = n2->n_value; while ((count > 0) && (*n1ptr == *n2ptr)) { n1ptr++; n2ptr++; count--; } if (ignore_last && count == 1 && n1->n_scale == n2->n_scale) return (0); if (count != 0) { if (*n1ptr > *n2ptr) { /* Magnitude of n1 > n2. */ if (!use_sign || n1->n_sign == PLUS) return (1); else return (-1); } else { /* Magnitude of n1 < n2. */ if (!use_sign || n1->n_sign == PLUS) return (-1); else return (1); } } /* They are equal up to the last part of the equal part of the fraction. */ if (n1->n_scale != n2->n_scale) { if (n1->n_scale > n2->n_scale) { for (count = n1->n_scale-n2->n_scale; count>0; count--) if (*n1ptr++ != 0) { /* Magnitude of n1 > n2. */ if (!use_sign || n1->n_sign == PLUS) return (1); else return (-1); } } else { for (count = n2->n_scale-n1->n_scale; count>0; count--) if (*n2ptr++ != 0) { /* Magnitude of n1 < n2. */ if (!use_sign || n1->n_sign == PLUS) return (-1); else return (1); } } } /* They must be equal! */ return (0); } /* This is the "user callable" routine to compare numbers N1 and N2. */ int bc_compare (n1, n2) bc_num n1, n2; { return _bc_do_compare (n1, n2, TRUE, FALSE); } /* In some places we need to check if the number is negative. */ char bc_is_neg (num) bc_num num; { return num->n_sign == MINUS; } /* In some places we need to check if the number NUM is zero. */ char bc_is_zero (num) bc_num num; { int count; char *nptr; /* Quick check. */ if (num == _zero_) return TRUE; /* Initialize */ count = num->n_len + num->n_scale; nptr = num->n_value; /* The check */ while ((count > 0) && (*nptr++ == 0)) count--; if (count != 0) return FALSE; else return TRUE; } /* In some places we need to check if the number NUM is almost zero. Specifically, all but the last digit is 0 and the last digit is 1. Last digit is defined by scale. */ char bc_is_near_zero (num, scale) bc_num num; int scale; { int count; char *nptr; /* Error checking */ if (scale > num->n_scale) scale = num->n_scale; /* Initialize */ count = num->n_len + scale; nptr = num->n_value; /* The check */ while ((count > 0) && (*nptr++ == 0)) count--; if (count != 0 && (count != 1 || *--nptr != 1)) return FALSE; else return TRUE; } /* Perform addition: N1 is added to N2 and the value is returned. The signs of N1 and N2 are ignored. SCALE_MIN is to set the minimum scale of the result. */ static bc_num _bc_do_add (n1, n2, scale_min) bc_num n1, n2; int scale_min; { bc_num sum; int sum_scale, sum_digits; char *n1ptr, *n2ptr, *sumptr; int carry, n1bytes, n2bytes; int count; /* Prepare sum. */ sum_scale = MAX (n1->n_scale, n2->n_scale); sum_digits = MAX (n1->n_len, n2->n_len) + 1; sum = bc_new_num (sum_digits, MAX(sum_scale, scale_min)); /* Zero extra digits made by scale_min. */ if (scale_min > sum_scale) { sumptr = (char *) (sum->n_value + sum_scale + sum_digits); for (count = scale_min - sum_scale; count > 0; count--) *sumptr++ = 0; } /* Start with the fraction part. Initialize the pointers. */ n1bytes = n1->n_scale; n2bytes = n2->n_scale; n1ptr = (char *) (n1->n_value + n1->n_len + n1bytes - 1); n2ptr = (char *) (n2->n_value + n2->n_len + n2bytes - 1); sumptr = (char *) (sum->n_value + sum_scale + sum_digits - 1); /* Add the fraction part. First copy the longer fraction.*/ if (n1bytes != n2bytes) { if (n1bytes > n2bytes) while (n1bytes>n2bytes) { *sumptr-- = *n1ptr--; n1bytes--;} else while (n2bytes>n1bytes) { *sumptr-- = *n2ptr--; n2bytes--;} } /* Now add the remaining fraction part and equal size integer parts. */ n1bytes += n1->n_len; n2bytes += n2->n_len; carry = 0; while ((n1bytes > 0) && (n2bytes > 0)) { *sumptr = *n1ptr-- + *n2ptr-- + carry; if (*sumptr > (BASE-1)) { carry = 1; *sumptr -= BASE; } else carry = 0; sumptr--; n1bytes--; n2bytes--; } /* Now add carry the longer integer part. */ if (n1bytes == 0) { n1bytes = n2bytes; n1ptr = n2ptr; } while (n1bytes-- > 0) { *sumptr = *n1ptr-- + carry; if (*sumptr > (BASE-1)) { carry = 1; *sumptr -= BASE; } else carry = 0; sumptr--; } /* Set final carry. */ if (carry == 1) *sumptr += 1; /* Adjust sum and return. */ _bc_rm_leading_zeros (sum); return sum; } /* Perform subtraction: N2 is subtracted from N1 and the value is returned. The signs of N1 and N2 are ignored. Also, N1 is assumed to be larger than N2. SCALE_MIN is the minimum scale of the result. */ static bc_num _bc_do_sub (n1, n2, scale_min) bc_num n1, n2; int scale_min; { bc_num diff; int diff_scale, diff_len; int min_scale, min_len; char *n1ptr, *n2ptr, *diffptr; int borrow, count, val; /* Allocate temporary storage. */ diff_len = MAX (n1->n_len, n2->n_len); diff_scale = MAX (n1->n_scale, n2->n_scale); min_len = MIN (n1->n_len, n2->n_len); min_scale = MIN (n1->n_scale, n2->n_scale); diff = bc_new_num (diff_len, MAX(diff_scale, scale_min)); /* Zero extra digits made by scale_min. */ if (scale_min > diff_scale) { diffptr = (char *) (diff->n_value + diff_len + diff_scale); for (count = scale_min - diff_scale; count > 0; count--) *diffptr++ = 0; } /* Initialize the subtract. */ n1ptr = (char *) (n1->n_value + n1->n_len + n1->n_scale -1); n2ptr = (char *) (n2->n_value + n2->n_len + n2->n_scale -1); diffptr = (char *) (diff->n_value + diff_len + diff_scale -1); /* Subtract the numbers. */ borrow = 0; /* Take care of the longer scaled number. */ if (n1->n_scale != min_scale) { /* n1 has the longer scale */ for (count = n1->n_scale - min_scale; count > 0; count--) *diffptr-- = *n1ptr--; } else { /* n2 has the longer scale */ for (count = n2->n_scale - min_scale; count > 0; count--) { val = - *n2ptr-- - borrow; if (val < 0) { val += BASE; borrow = 1; } else borrow = 0; *diffptr-- = val; } } /* Now do the equal length scale and integer parts. */ for (count = 0; count < min_len + min_scale; count++) { val = *n1ptr-- - *n2ptr-- - borrow; if (val < 0) { val += BASE; borrow = 1; } else borrow = 0; *diffptr-- = val; } /* If n1 has more digits then n2, we now do that subtract. */ if (diff_len != min_len) { for (count = diff_len - min_len; count > 0; count--) { val = *n1ptr-- - borrow; if (val < 0) { val += BASE; borrow = 1; } else borrow = 0; *diffptr-- = val; } } /* Clean up and return. */ _bc_rm_leading_zeros (diff); return diff; } /* Here is the full subtract routine that takes care of negative numbers. N2 is subtracted from N1 and the result placed in RESULT. SCALE_MIN is the minimum scale for the result. */ void bc_sub (n1, n2, result, scale_min) bc_num n1, n2, *result; int scale_min; { bc_num diff = NULL; int cmp_res; int res_scale; if (n1->n_sign != n2->n_sign) { diff = _bc_do_add (n1, n2, scale_min); diff->n_sign = n1->n_sign; } else { /* subtraction must be done. */ /* Compare magnitudes. */ cmp_res = _bc_do_compare (n1, n2, FALSE, FALSE); switch (cmp_res) { case -1: /* n1 is less than n2, subtract n1 from n2. */ diff = _bc_do_sub (n2, n1, scale_min); diff->n_sign = (n2->n_sign == PLUS ? MINUS : PLUS); break; case 0: /* They are equal! return zero! */ res_scale = MAX (scale_min, MAX(n1->n_scale, n2->n_scale)); diff = bc_new_num (1, res_scale); memset (diff->n_value, 0, res_scale+1); break; case 1: /* n2 is less than n1, subtract n2 from n1. */ diff = _bc_do_sub (n1, n2, scale_min); diff->n_sign = n1->n_sign; break; } } /* Clean up and return. */ bc_free_num (result); *result = diff; } /* Here is the full add routine that takes care of negative numbers. N1 is added to N2 and the result placed into RESULT. SCALE_MIN is the minimum scale for the result. */ void bc_add (n1, n2, result, scale_min) bc_num n1, n2, *result; int scale_min; { bc_num sum = NULL; int cmp_res; int res_scale; if (n1->n_sign == n2->n_sign) { sum = _bc_do_add (n1, n2, scale_min); sum->n_sign = n1->n_sign; } else { /* subtraction must be done. */ cmp_res = _bc_do_compare (n1, n2, FALSE, FALSE); /* Compare magnitudes. */ switch (cmp_res) { case -1: /* n1 is less than n2, subtract n1 from n2. */ sum = _bc_do_sub (n2, n1, scale_min); sum->n_sign = n2->n_sign; break; case 0: /* They are equal! return zero with the correct scale! */ res_scale = MAX (scale_min, MAX(n1->n_scale, n2->n_scale)); sum = bc_new_num (1, res_scale); memset (sum->n_value, 0, res_scale+1); break; case 1: /* n2 is less than n1, subtract n2 from n1. */ sum = _bc_do_sub (n1, n2, scale_min); sum->n_sign = n1->n_sign; } } /* Clean up and return. */ bc_free_num (result); *result = sum; } /* Recursive vs non-recursive multiply crossover ranges. */ #if defined(MULDIGITS) #include "muldigits.h" #else #define MUL_BASE_DIGITS 80 #endif int mul_base_digits = MUL_BASE_DIGITS; #define MUL_SMALL_DIGITS mul_base_digits/4 /* Multiply utility routines */ static bc_num new_sub_num (length, scale, value) int length, scale; char *value; { bc_num temp; temp = (bc_num) malloc (sizeof(bc_struct)); if (temp == NULL) bc_out_of_memory (); temp->n_sign = PLUS; temp->n_len = length; temp->n_scale = scale; temp->n_refs = 1; temp->n_ptr = NULL; temp->n_value = value; return temp; } static void _bc_simp_mul (bc_num n1, int n1len, bc_num n2, int n2len, bc_num *prod, int full_scale) { char *n1ptr, *n2ptr, *pvptr; char *n1end, *n2end; /* To the end of n1 and n2. */ int indx, sum, prodlen; prodlen = n1len+n2len+1; *prod = bc_new_num (prodlen, 0); n1end = (char *) (n1->n_value + n1len - 1); n2end = (char *) (n2->n_value + n2len - 1); pvptr = (char *) ((*prod)->n_value + prodlen - 1); sum = 0; /* Here is the loop... */ for (indx = 0; indx < prodlen-1; indx++) { n1ptr = (char *) (n1end - MAX(0, indx-n2len+1)); n2ptr = (char *) (n2end - MIN(indx, n2len-1)); while ((n1ptr >= n1->n_value) && (n2ptr <= n2end)) sum += *n1ptr-- * *n2ptr++; *pvptr-- = sum % BASE; sum = sum / BASE; } *pvptr = sum; } /* A special adder/subtractor for the recursive divide and conquer multiply algorithm. Note: if sub is called, accum must be larger that what is being subtracted. Also, accum and val must have n_scale = 0. (e.g. they must look like integers. *) */ static void _bc_shift_addsub (bc_num accum, bc_num val, int shift, int sub) { signed char *accp, *valp; int count, carry; count = val->n_len; if (val->n_value[0] == 0) count--; assert (accum->n_len+accum->n_scale >= shift+count); /* Set up pointers and others */ accp = (signed char *)(accum->n_value + accum->n_len + accum->n_scale - shift - 1); valp = (signed char *)(val->n_value + val->n_len - 1); carry = 0; if (sub) { /* Subtraction, carry is really borrow. */ while (count--) { *accp -= *valp-- + carry; if (*accp < 0) { carry = 1; *accp-- += BASE; } else { carry = 0; accp--; } } while (carry) { *accp -= carry; if (*accp < 0) *accp-- += BASE; else carry = 0; } } else { /* Addition */ while (count--) { *accp += *valp-- + carry; if (*accp > (BASE-1)) { carry = 1; *accp-- -= BASE; } else { carry = 0; accp--; } } while (carry) { *accp += carry; if (*accp > (BASE-1)) *accp-- -= BASE; else carry = 0; } } } /* Recursive divide and conquer multiply algorithm. Based on Let u = u0 + u1*(b^n) Let v = v0 + v1*(b^n) Then uv = (B^2n+B^n)*u1*v1 + B^n*(u1-u0)*(v0-v1) + (B^n+1)*u0*v0 B is the base of storage, number of digits in u1,u0 close to equal. */ static void _bc_rec_mul (bc_num u, int ulen, bc_num v, int vlen, bc_num *prod, int full_scale) { bc_num u0, u1, v0, v1; int u0len, v0len; bc_num m1, m2, m3, d1, d2; int n, prodlen, m1zero; int d1len, d2len; /* Base case? */ if ((ulen+vlen) < mul_base_digits || ulen < MUL_SMALL_DIGITS || vlen < MUL_SMALL_DIGITS ) { _bc_simp_mul (u, ulen, v, vlen, prod, full_scale); return; } /* Calculate n -- the u and v split point in digits. */ n = (MAX(ulen, vlen)+1) / 2; /* Split u and v. */ if (ulen < n) { u1 = bc_copy_num (_zero_); u0 = new_sub_num (ulen,0, u->n_value); } else { u1 = new_sub_num (ulen-n, 0, u->n_value); u0 = new_sub_num (n, 0, u->n_value+ulen-n); } if (vlen < n) { v1 = bc_copy_num (_zero_); v0 = new_sub_num (vlen,0, v->n_value); } else { v1 = new_sub_num (vlen-n, 0, v->n_value); v0 = new_sub_num (n, 0, v->n_value+vlen-n); } _bc_rm_leading_zeros (u1); _bc_rm_leading_zeros (u0); u0len = u0->n_len; _bc_rm_leading_zeros (v1); _bc_rm_leading_zeros (v0); v0len = v0->n_len; m1zero = bc_is_zero(u1) || bc_is_zero(v1); /* Calculate sub results ... */ bc_init_num(&d1); bc_init_num(&d2); bc_sub (u1, u0, &d1, 0); d1len = d1->n_len; bc_sub (v0, v1, &d2, 0); d2len = d2->n_len; /* Do recursive multiplies and shifted adds. */ if (m1zero) m1 = bc_copy_num (_zero_); else _bc_rec_mul (u1, u1->n_len, v1, v1->n_len, &m1, 0); if (bc_is_zero(d1) || bc_is_zero(d2)) m2 = bc_copy_num (_zero_); else _bc_rec_mul (d1, d1len, d2, d2len, &m2, 0); if (bc_is_zero(u0) || bc_is_zero(v0)) m3 = bc_copy_num (_zero_); else _bc_rec_mul (u0, u0->n_len, v0, v0->n_len, &m3, 0); /* Initialize product */ prodlen = ulen+vlen+1; *prod = bc_new_num(prodlen, 0); if (!m1zero) { _bc_shift_addsub (*prod, m1, 2*n, 0); _bc_shift_addsub (*prod, m1, n, 0); } _bc_shift_addsub (*prod, m3, n, 0); _bc_shift_addsub (*prod, m3, 0, 0); _bc_shift_addsub (*prod, m2, n, d1->n_sign != d2->n_sign); /* Now clean up! */ bc_free_num (&u1); bc_free_num (&u0); bc_free_num (&v1); bc_free_num (&m1); bc_free_num (&v0); bc_free_num (&m2); bc_free_num (&m3); bc_free_num (&d1); bc_free_num (&d2); } /* The multiply routine. N2 times N1 is put int PROD with the scale of the result being MIN(N2 scale+N1 scale, MAX (SCALE, N2 scale, N1 scale)). */ void bc_multiply (n1, n2, prod, scale) bc_num n1, n2, *prod; int scale; { bc_num pval; int len1, len2; int full_scale, prod_scale; /* Initialize things. */ len1 = n1->n_len + n1->n_scale; len2 = n2->n_len + n2->n_scale; full_scale = n1->n_scale + n2->n_scale; prod_scale = MIN(full_scale,MAX(scale,MAX(n1->n_scale,n2->n_scale))); /* Do the multiply */ _bc_rec_mul (n1, len1, n2, len2, &pval, full_scale); /* Assign to prod and clean up the number. */ pval->n_sign = ( n1->n_sign == n2->n_sign ? PLUS : MINUS ); pval->n_value = pval->n_ptr; pval->n_len = len2 + len1 + 1 - full_scale; pval->n_scale = prod_scale; _bc_rm_leading_zeros (pval); if (bc_is_zero (pval)) pval->n_sign = PLUS; bc_free_num (prod); *prod = pval; } /* Some utility routines for the divide: First a one digit multiply. NUM (with SIZE digits) is multiplied by DIGIT and the result is placed into RESULT. It is written so that NUM and RESULT can be the same pointers. */ static void _one_mult (num, size, digit, result) unsigned char *num; int size, digit; unsigned char *result; { int carry, value; unsigned char *nptr, *rptr; if (digit == 0) memset (result, 0, size); else { if (digit == 1) memcpy (result, num, size); else { /* Initialize */ nptr = (unsigned char *) (num+size-1); rptr = (unsigned char *) (result+size-1); carry = 0; while (size-- > 0) { value = *nptr-- * digit + carry; *rptr-- = value % BASE; carry = value / BASE; } if (carry != 0) *rptr = carry; } } } /* The full division routine. This computes N1 / N2. It returns 0 if the division is ok and the result is in QUOT. The number of digits after the decimal point is SCALE. It returns -1 if division by zero is tried. The algorithm is found in Knuth Vol 2. p237. */ int bc_divide (n1, n2, quot, scale) bc_num n1, n2, *quot; int scale; { bc_num qval; unsigned char *num1, *num2; unsigned char *ptr1, *ptr2, *n2ptr, *qptr; int scale1, val; unsigned int len1, len2, scale2, qdigits, extra, count; unsigned int qdig, qguess, borrow, carry; unsigned char *mval; char zero; unsigned int norm; /* Test for divide by zero. */ if (bc_is_zero (n2)) return -1; /* Test for divide by 1. If it is we must truncate. */ if (n2->n_scale == 0) { if (n2->n_len == 1 && *n2->n_value == 1) { qval = bc_new_num (n1->n_len, scale); qval->n_sign = (n1->n_sign == n2->n_sign ? PLUS : MINUS); memset (&qval->n_value[n1->n_len],0,scale); memcpy (qval->n_value, n1->n_value, n1->n_len + MIN(n1->n_scale,scale)); bc_free_num (quot); *quot = qval; } } /* Set up the divide. Move the decimal point on n1 by n2's scale. Remember, zeros on the end of num2 are wasted effort for dividing. */ scale2 = n2->n_scale; n2ptr = (unsigned char *) n2->n_value+n2->n_len+scale2-1; while ((scale2 > 0) && (*n2ptr-- == 0)) scale2--; len1 = n1->n_len + scale2; scale1 = n1->n_scale - scale2; if (scale1 < scale) extra = scale - scale1; else extra = 0; num1 = (unsigned char *) malloc (n1->n_len+n1->n_scale+extra+2); if (num1 == NULL) bc_out_of_memory(); memset (num1, 0, n1->n_len+n1->n_scale+extra+2); memcpy (num1+1, n1->n_value, n1->n_len+n1->n_scale); len2 = n2->n_len + scale2; num2 = (unsigned char *) malloc (len2+1); if (num2 == NULL) bc_out_of_memory(); memcpy (num2, n2->n_value, len2); *(num2+len2) = 0; n2ptr = num2; while (*n2ptr == 0) { n2ptr++; len2--; } /* Calculate the number of quotient digits. */ if (len2 > len1+scale) { qdigits = scale+1; zero = TRUE; } else { zero = FALSE; if (len2>len1) qdigits = scale+1; /* One for the zero integer part. */ else qdigits = len1-len2+scale+1; } /* Allocate and zero the storage for the quotient. */ qval = bc_new_num (qdigits-scale,scale); memset (qval->n_value, 0, qdigits); /* Allocate storage for the temporary storage mval. */ mval = (unsigned char *) malloc (len2+1); if (mval == NULL) bc_out_of_memory (); /* Now for the full divide algorithm. */ if (!zero) { /* Normalize */ norm = 10 / ((int)*n2ptr + 1); if (norm != 1) { _one_mult (num1, len1+scale1+extra+1, norm, num1); _one_mult (n2ptr, len2, norm, n2ptr); } /* Initialize divide loop. */ qdig = 0; if (len2 > len1) qptr = (unsigned char *) qval->n_value+len2-len1; else qptr = (unsigned char *) qval->n_value; /* Loop */ while (qdig <= len1+scale-len2) { /* Calculate the quotient digit guess. */ if (*n2ptr == num1[qdig]) qguess = 9; else qguess = (num1[qdig]*10 + num1[qdig+1]) / *n2ptr; /* Test qguess. */ if (n2ptr[1]*qguess > (num1[qdig]*10 + num1[qdig+1] - *n2ptr*qguess)*10 + num1[qdig+2]) { qguess--; /* And again. */ if (n2ptr[1]*qguess > (num1[qdig]*10 + num1[qdig+1] - *n2ptr*qguess)*10 + num1[qdig+2]) qguess--; } /* Multiply and subtract. */ borrow = 0; if (qguess != 0) { *mval = 0; _one_mult (n2ptr, len2, qguess, mval+1); ptr1 = (unsigned char *) num1+qdig+len2; ptr2 = (unsigned char *) mval+len2; for (count = 0; count < len2+1; count++) { val = (int) *ptr1 - (int) *ptr2-- - borrow; if (val < 0) { val += 10; borrow = 1; } else borrow = 0; *ptr1-- = val; } } /* Test for negative result. */ if (borrow == 1) { qguess--; ptr1 = (unsigned char *) num1+qdig+len2; ptr2 = (unsigned char *) n2ptr+len2-1; carry = 0; for (count = 0; count < len2; count++) { val = (int) *ptr1 + (int) *ptr2-- + carry; if (val > 9) { val -= 10; carry = 1; } else carry = 0; *ptr1-- = val; } if (carry == 1) *ptr1 = (*ptr1 + 1) % 10; } /* We now know the quotient digit. */ *qptr++ = qguess; qdig++; } } /* Clean up and return the number. */ qval->n_sign = ( n1->n_sign == n2->n_sign ? PLUS : MINUS ); if (bc_is_zero (qval)) qval->n_sign = PLUS; _bc_rm_leading_zeros (qval); bc_free_num (quot); *quot = qval; /* Clean up temporary storage. */ free (mval); free (num1); free (num2); return 0; /* Everything is OK. */ } /* Division *and* modulo for numbers. This computes both NUM1 / NUM2 and NUM1 % NUM2 and puts the results in QUOT and REM, except that if QUOT is NULL then that store will be omitted. */ int bc_divmod (num1, num2, quot, rem, scale) bc_num num1, num2, *quot, *rem; int scale; { bc_num quotient = NULL; bc_num temp; int rscale; /* Check for correct numbers. */ if (bc_is_zero (num2)) return -1; /* Calculate final scale. */ rscale = MAX (num1->n_scale, num2->n_scale+scale); bc_init_num(&temp); /* Calculate it. */ bc_divide (num1, num2, &temp, scale); if (quot) quotient = bc_copy_num (temp); bc_multiply (temp, num2, &temp, rscale); bc_sub (num1, temp, rem, rscale); bc_free_num (&temp); if (quot) { bc_free_num (quot); *quot = quotient; } return 0; /* Everything is OK. */ } /* Modulo for numbers. This computes NUM1 % NUM2 and puts the result in RESULT. */ int bc_modulo (num1, num2, result, scale) bc_num num1, num2, *result; int scale; { return bc_divmod (num1, num2, NULL, result, scale); } /* Raise BASE to the EXPO power, reduced modulo MOD. The result is placed in RESULT. If a EXPO is not an integer, only the integer part is used. */ int bc_raisemod (base, expo, mod, result, scale) bc_num base, expo, mod, *result; int scale; { bc_num power, exponent, parity, temp; int rscale; /* Check for correct numbers. */ if (bc_is_zero(mod)) return -1; if (bc_is_neg(expo)) return -1; /* Set initial values. */ power = bc_copy_num (base); exponent = bc_copy_num (expo); temp = bc_copy_num (_one_); bc_init_num(&parity); /* Check the base for scale digits. */ if (base->n_scale != 0) bc_rt_warn ("non-zero scale in base"); /* Check the exponent for scale digits. */ if (exponent->n_scale != 0) { bc_rt_warn ("non-zero scale in exponent"); bc_divide (exponent, _one_, &exponent, 0); /*truncate */ } /* Check the modulus for scale digits. */ if (mod->n_scale != 0) bc_rt_warn ("non-zero scale in modulus"); /* Do the calculation. */ rscale = MAX(scale, base->n_scale); while ( !bc_is_zero(exponent) ) { (void) bc_divmod (exponent, _two_, &exponent, &parity, 0); if ( !bc_is_zero(parity) ) { bc_multiply (temp, power, &temp, rscale); (void) bc_modulo (temp, mod, &temp, scale); } bc_multiply (power, power, &power, rscale); (void) bc_modulo (power, mod, &power, scale); } /* Assign the value. */ bc_free_num (&power); bc_free_num (&exponent); bc_free_num (result); bc_free_num (&parity); *result = temp; return 0; /* Everything is OK. */ } /* Raise NUM1 to the NUM2 power. The result is placed in RESULT. Maximum exponent is LONG_MAX. If a NUM2 is not an integer, only the integer part is used. */ void bc_raise (num1, num2, result, scale) bc_num num1, num2, *result; int scale; { bc_num temp, power; long exponent; int rscale; int pwrscale; int calcscale; char neg; /* Check the exponent for scale digits and convert to a long. */ if (num2->n_scale != 0) bc_rt_warn ("non-zero scale in exponent"); exponent = bc_num2long (num2); if (exponent == 0 && (num2->n_len > 1 || num2->n_value[0] != 0)) bc_rt_error ("exponent too large in raise"); /* Special case if exponent is a zero. */ if (exponent == 0) { bc_free_num (result); *result = bc_copy_num (_one_); return; } /* Other initializations. */ if (exponent < 0) { neg = TRUE; exponent = -exponent; rscale = scale; } else { neg = FALSE; rscale = MIN (num1->n_scale*exponent, MAX(scale, num1->n_scale)); } /* Set initial value of temp. */ power = bc_copy_num (num1); pwrscale = num1->n_scale; while ((exponent & 1) == 0) { pwrscale = 2*pwrscale; bc_multiply (power, power, &power, pwrscale); exponent = exponent >> 1; } temp = bc_copy_num (power); calcscale = pwrscale; exponent = exponent >> 1; /* Do the calculation. */ while (exponent > 0) { pwrscale = 2*pwrscale; bc_multiply (power, power, &power, pwrscale); if ((exponent & 1) == 1) { calcscale = pwrscale + calcscale; bc_multiply (temp, power, &temp, calcscale); } exponent = exponent >> 1; } /* Assign the value. */ if (neg) { bc_divide (_one_, temp, result, rscale); bc_free_num (&temp); } else { bc_free_num (result); *result = temp; if ((*result)->n_scale > rscale) (*result)->n_scale = rscale; } bc_free_num (&power); } /* Take the square root NUM and return it in NUM with SCALE digits after the decimal place. */ int bc_sqrt (num, scale) bc_num *num; int scale; { int rscale, cmp_res, done; int cscale; bc_num guess, guess1, point5, diff; /* Initial checks. */ cmp_res = bc_compare (*num, _zero_); if (cmp_res < 0) return 0; /* error */ else { if (cmp_res == 0) { bc_free_num (num); *num = bc_copy_num (_zero_); return 1; } } cmp_res = bc_compare (*num, _one_); if (cmp_res == 0) { bc_free_num (num); *num = bc_copy_num (_one_); return 1; } /* Initialize the variables. */ rscale = MAX (scale, (*num)->n_scale); bc_init_num(&guess); bc_init_num(&guess1); bc_init_num(&diff); point5 = bc_new_num (1,1); point5->n_value[1] = 5; /* Calculate the initial guess. */ if (cmp_res < 0) { /* The number is between 0 and 1. Guess should start at 1. */ guess = bc_copy_num (_one_); cscale = (*num)->n_scale; } else { /* The number is greater than 1. Guess should start at 10^(exp/2). */ bc_int2num (&guess,10); bc_int2num (&guess1,(*num)->n_len); bc_multiply (guess1, point5, &guess1, 0); guess1->n_scale = 0; bc_raise (guess, guess1, &guess, 0); bc_free_num (&guess1); cscale = 3; } /* Find the square root using Newton's algorithm. */ done = FALSE; while (!done) { bc_free_num (&guess1); guess1 = bc_copy_num (guess); bc_divide (*num, guess, &guess, cscale); bc_add (guess, guess1, &guess, 0); bc_multiply (guess, point5, &guess, cscale); bc_sub (guess, guess1, &diff, cscale+1); if (bc_is_near_zero (diff, cscale)) { if (cscale < rscale+1) cscale = MIN (cscale*3, rscale+1); else done = TRUE; } } /* Assign the number and clean up. */ bc_free_num (num); bc_divide (guess,_one_,num,rscale); bc_free_num (&guess); bc_free_num (&guess1); bc_free_num (&point5); bc_free_num (&diff); return 1; } /* Convert a number NUM to a long. The function returns only the integer part of the number. For numbers that are too large to represent as a long, this function returns a zero. This can be detected by checking the NUM for zero after having a zero returned. */ long bc_num2long (num) bc_num num; { long val; char *nptr; int index; /* Extract the int value, ignore the fraction. */ val = 0; nptr = num->n_value; for (index=num->n_len; (index>0) && (val<=(LONG_MAX/BASE)); index--) val = val*BASE + *nptr++; /* Check for overflow. If overflow, return zero. */ if (index>0) val = 0; if (val < 0) val = 0; /* Return the value. */ if (num->n_sign == PLUS) return (val); else return (-val); } /* Convert an integer VAL to a bc number NUM. */ void bc_int2num (num, val) bc_num *num; int val; { char buffer[30]; char *bptr, *vptr; int ix = 1; char neg = 0; /* Sign. */ if (val < 0) { neg = 1; val = -val; } /* Get things going. */ bptr = buffer; *bptr++ = val % BASE; val = val / BASE; /* Extract remaining digits. */ while (val != 0) { *bptr++ = val % BASE; val = val / BASE; ix++; /* Count the digits. */ } /* Make the number. */ bc_free_num (num); *num = bc_new_num (ix, 0); if (neg) (*num)->n_sign = MINUS; /* Assign the digits. */ vptr = (*num)->n_value; while (ix-- > 0) *vptr++ = *--bptr; } /* Convert a numbers to a string. Base 10 only.*/ char *num2str (num) bc_num num; { char *str, *sptr; char *nptr; int index, signch; /* Allocate the string memory. */ signch = ( num->n_sign == PLUS ? 0 : 1 ); /* Number of sign chars. */ if (num->n_scale > 0) str = (char *) malloc (num->n_len + num->n_scale + 2 + signch); else str = (char *) malloc (num->n_len + 1 + signch); if (str == NULL) bc_out_of_memory(); /* The negative sign if needed. */ sptr = str; if (signch) *sptr++ = '-'; /* Load the whole number. */ nptr = num->n_value; for (index=num->n_len; index>0; index--) *sptr++ = BCD_CHAR(*nptr++); /* Now the fraction. */ if (num->n_scale > 0) { *sptr++ = '.'; for (index=0; indexn_scale; index++) *sptr++ = BCD_CHAR(*nptr++); } /* Terminate the string and return it! */ *sptr = '\0'; return (str); } /* Convert strings to bc numbers. Base 10 only.*/ void bc_str2num (num, str, scale) bc_num *num; char *str; int scale; { int digits, strscale; char *ptr, *nptr; char zero_int; /* Prepare num. */ bc_free_num (num); /* Check for valid number and count digits. */ ptr = str; digits = 0; strscale = 0; zero_int = FALSE; if ( (*ptr == '+') || (*ptr == '-')) ptr++; /* Sign */ while (*ptr == '0') ptr++; /* Skip leading zeros. */ while (isdigit((int)*ptr)) ptr++, digits++; /* digits */ if (*ptr == '.') ptr++; /* decimal point */ while (isdigit((int)*ptr)) ptr++, strscale++; /* digits */ if ((*ptr != '\0') || (digits+strscale == 0)) { *num = bc_copy_num (_zero_); return; } /* Adjust numbers and allocate storage and initialize fields. */ strscale = MIN(strscale, scale); if (digits == 0) { zero_int = TRUE; digits = 1; } *num = bc_new_num (digits, strscale); /* Build the whole number. */ ptr = str; if (*ptr == '-') { (*num)->n_sign = MINUS; ptr++; } else { (*num)->n_sign = PLUS; if (*ptr == '+') ptr++; } while (*ptr == '0') ptr++; /* Skip leading zeros. */ nptr = (*num)->n_value; if (zero_int) { *nptr++ = 0; digits = 0; } for (;digits > 0; digits--) *nptr++ = CH_VAL(*ptr++); /* Build the fractional part. */ if (strscale > 0) { ptr++; /* skip the decimal point! */ for (;strscale > 0; strscale--) *nptr++ = CH_VAL(*ptr++); } } poelzi-ulatencyd-55515a9/src/bc/number.h000066400000000000000000000103261154664534000201100ustar00rootroot00000000000000/* number.h: Arbitrary precision numbers header file. */ /* Copyright (C) 1991, 1992, 1993, 1994, 1997, 2000 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License , or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to: The Free Software Foundation, Inc. 59 Temple Place, Suite 330 Boston, MA 02111-1307 USA. You may contact the author by: e-mail: philnelson@acm.org us-mail: Philip A. Nelson Computer Science Department, 9062 Western Washington University Bellingham, WA 98226-9062 *************************************************************************/ #ifndef _NUMBER_H_ #define _NUMBER_H_ typedef enum {PLUS, MINUS} sign; typedef struct bc_struct *bc_num; typedef struct bc_struct { sign n_sign; int n_len; /* The number of digits before the decimal point. */ int n_scale; /* The number of digits after the decimal point. */ int n_refs; /* The number of pointers to this number. */ bc_num n_next; /* Linked list for available list. */ char *n_ptr; /* The pointer to the actual storage. If NULL, n_value points to the inside of another number (bc_multiply...) and should not be "freed." */ char *n_value; /* The number. Not zero char terminated. May not point to the same place as n_ptr as in the case of leading zeros generated. */ } bc_struct; /* The base used in storing the numbers in n_value above. Currently this MUST be 10. */ #define BASE 10 /* Some useful macros and constants. */ #define CH_VAL(c) (c - '0') #define BCD_CHAR(d) (d + '0') #ifdef MIN #undef MIN #undef MAX #endif #define MAX(a,b) ((a)>(b)?(a):(b)) #define MIN(a,b) ((a)>(b)?(b):(a)) #define ODD(a) ((a)&1) #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif #ifndef LONG_MAX #define LONG_MAX 0x7ffffff #endif /* Global numbers. */ extern bc_num _zero_; extern bc_num _one_; extern bc_num _two_; /* Function Prototypes */ /* Define the _PROTOTYPE macro if it is needed. */ #ifndef _PROTOTYPE #ifdef __STDC__ #define _PROTOTYPE(func, args) func args #else #define _PROTOTYPE(func, args) func() #endif #endif _PROTOTYPE(void bc_init_numbers, (void)); _PROTOTYPE(bc_num bc_new_num, (int length, int scale)); _PROTOTYPE(void bc_free_num, (bc_num *num)); _PROTOTYPE(bc_num bc_copy_num, (bc_num num)); _PROTOTYPE(void bc_init_num, (bc_num *num)); _PROTOTYPE(void bc_str2num, (bc_num *num, char *str, int scale)); _PROTOTYPE(char *bc_num2str, (bc_num num)); _PROTOTYPE(void bc_int2num, (bc_num *num, int val)); _PROTOTYPE(long bc_num2long, (bc_num num)); _PROTOTYPE(int bc_compare, (bc_num n1, bc_num n2)); _PROTOTYPE(char bc_is_zero, (bc_num num)); _PROTOTYPE(char bc_is_near_zero, (bc_num num, int scale)); _PROTOTYPE(char bc_is_neg, (bc_num num)); _PROTOTYPE(void bc_add, (bc_num n1, bc_num n2, bc_num *result, int scale_min)); _PROTOTYPE(void bc_sub, (bc_num n1, bc_num n2, bc_num *result, int scale_min)); _PROTOTYPE(void bc_multiply, (bc_num n1, bc_num n2, bc_num *prod, int scale)); _PROTOTYPE(int bc_divide, (bc_num n1, bc_num n2, bc_num *quot, int scale)); _PROTOTYPE(int bc_modulo, (bc_num num1, bc_num num2, bc_num *result, int scale)); _PROTOTYPE(int bc_divmod, (bc_num num1, bc_num num2, bc_num *quot, bc_num *rem, int scale)); _PROTOTYPE(int bc_raisemod, (bc_num base, bc_num expo, bc_num mod, bc_num *result, int scale)); _PROTOTYPE(void bc_raise, (bc_num num1, bc_num num2, bc_num *result, int scale)); _PROTOTYPE(int bc_sqrt, (bc_num *num, int scale)); _PROTOTYPE(void bc_out_num, (bc_num num, int o_base, void (* out_char)(int), int leading_zero)); #endif poelzi-ulatencyd-55515a9/src/config.h.tmpl000066400000000000000000000100351154664534000204510ustar00rootroot00000000000000/* Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. */ #ifndef __CONFIG_H #define __CONFIG_H // ioprio #cmakedefine ENABLE_NLS #cmakedefine LIBCGROUPS #cmakedefine CONFIG_PREFIX @CONFIG_PREFIX@ #cmakedefine INSTALL_PREFIX @INSTALL_PREFIX@ #cmakedefine ENABLE_DBUS #cmakedefine DEVELOP_DBUS_SESSION #cmakedefine POLKIT_FOUND #cmakedefine DEVELOP_MODE #ifdef DEVELOP_MODE #define RELEASE_AGENT ${CMAKE_CURRENT_BINARY_DIR}/src/ulatencyd_cleanup.lua #define CONFIG_PATH conf #define RULES_DIRECTORY rules #define MODULES_DIRECTORY modules #define LUA_CORE_FILE src/core.lua #else #define RELEASE_AGENT ${INSTALL_PREFIX}/lib/ulatencyd/ulatencyd_cleanup.lua #define CONFIG_PATH ${CONFIG_PREFIX}/ulatencyd #define RULES_DIRECTORY ${CONFIG_PREFIX}/ulatencyd/rules #define MODULES_DIRECTORY ${INSTALL_PREFIX}/lib/ulatencyd/modules #define LUA_CORE_FILE ${INSTALL_PREFIX}/lib/ulatencyd/core.lua #endif #define QUOTEME_(x) #x #define QUOTEME(x) QUOTEME_(x) #cmakedefine HAVE_LOCALE_H // FIXME need detection /* Define to 1 if `ut_exit' is a member of `struct utmpx'. */ #define HAVE_STRUCT_UTMPX_UT_EXIT 1 /* Define to 1 if `ut_exit.e_exit' is a member of `struct utmpx'. */ #define HAVE_STRUCT_UTMPX_UT_EXIT_E_EXIT 1 /* Define to 1 if `ut_exit.e_termination' is a member of `struct utmpx'. */ #define HAVE_STRUCT_UTMPX_UT_EXIT_E_TERMINATION 1 /* Define to 1 if `ut_exit.ut_exit' is a member of `struct utmpx'. */ /* #undef HAVE_STRUCT_UTMPX_UT_EXIT_UT_EXIT */ /* Define to 1 if `ut_exit.ut_termination' is a member of `struct utmpx'. */ /* #undef HAVE_STRUCT_UTMPX_UT_EXIT_UT_TERMINATION */ /* Define to 1 if `ut_id' is a member of `struct utmpx'. */ #define HAVE_STRUCT_UTMPX_UT_ID 1 /* Define to 1 if `ut_name' is a member of `struct utmpx'. */ #define HAVE_STRUCT_UTMPX_UT_NAME 1 /* Define to 1 if `ut_pid' is a member of `struct utmpx'. */ #define HAVE_STRUCT_UTMPX_UT_PID 1 /* Define to 1 if `ut_type' is a member of `struct utmpx'. */ #define HAVE_STRUCT_UTMPX_UT_TYPE 1 /* Define to 1 if `ut_user' is a member of `struct utmpx'. */ #define HAVE_STRUCT_UTMPX_UT_USER 1 /* Define to 1 if `ut_exit' is a member of `struct utmp'. */ #define HAVE_STRUCT_UTMP_UT_EXIT 1 /* Define to 1 if `ut_exit.e_exit' is a member of `struct utmp'. */ #define HAVE_STRUCT_UTMP_UT_EXIT_E_EXIT 1 /* Define to 1 if `ut_exit.e_termination' is a member of `struct utmp'. */ #define HAVE_STRUCT_UTMP_UT_EXIT_E_TERMINATION 1 /* Define to 1 if `ut_exit.ut_exit' is a member of `struct utmp'. */ /* #undef HAVE_STRUCT_UTMP_UT_EXIT_UT_EXIT */ /* Define to 1 if `ut_exit.ut_termination' is a member of `struct utmp'. */ /* #undef HAVE_STRUCT_UTMP_UT_EXIT_UT_TERMINATION */ /* Define to 1 if `ut_id' is a member of `struct utmp'. */ #define HAVE_STRUCT_UTMP_UT_ID 1 /* Define to 1 if `ut_name' is a member of `struct utmp'. */ #define HAVE_STRUCT_UTMP_UT_NAME 1 /* Define to 1 if `ut_pid' is a member of `struct utmp'. */ #define HAVE_STRUCT_UTMP_UT_PID 1 /* Define to 1 if `ut_type' is a member of `struct utmp'. */ #define HAVE_STRUCT_UTMP_UT_TYPE 1 /* Define to 1 if `ut_user' is a member of `struct utmp'. */ #define HAVE_STRUCT_UTMP_UT_USER 1 /* Define to 1 if you have the `utmpname' function. */ #define HAVE_UTMPNAME 1 /* Define to 1 if you have the `utmpxname' function. */ #define HAVE_UTMPXNAME 1 /* Define to 1 if you have the header file. */ #define HAVE_UTMPX_H 1 /* Define to 1 if you have the header file. */ #define HAVE_UTMP_H 1 #endifpoelzi-ulatencyd-55515a9/src/core.c000066400000000000000000001372621154664534000171700ustar00rootroot00000000000000/* Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. */ #define _GNU_SOURCE #include "config.h" #include "ulatency.h" #include "proc/procps.h" #include "proc/sysinfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_DBUS #include DBusGConnection *U_dbus_connection; #endif lua_State *lua_main_state; GList *filter_list; GList *filter_fast_list; GNode *processes_tree; GHashTable *processes; u_scheduler scheduler = {NULL}; static int iteration; static double _last_load; static double _last_percent; // flag list of system wide flags GList *system_flags; int system_flags_changed; // delay rules execution static long int delay; static GPtrArray *delay_stack; // profiling timers struct u_timer timer_filter; struct u_timer timer_scheduler; struct u_timer timer_parse; // delay new processes struct delay_proc { struct timespec when; u_proc *proc; }; double get_last_load() { return _last_load; } double get_last_percent() { return _last_percent; } guint get_plugin_id() { static guint last = USER_ACTIVE_AGENT_MODULE; return ++last; } /************************************************************* * u_proc code ************************************************************/ void filter_block_free(gpointer fb) { free(fb); } void u_head_free(gpointer fb) { DEC_REF(fb); } /** * remove all child nodes * @arg proc a #u_proc * * Unlinks all child nodes from a #u_proc node. Moving them to the parent * on @proc and unlinks the node. Makes sure the node is save to remove. * * @return none */ static void u_proc_remove_child_nodes(u_proc *proc) { GNode *nparent, *cur; u_proc *proc_tmp; if(g_node_n_children(proc->node)) { // the process which dies has some children. we have to move children // to a new parent. Try the parent of the dead process first if(proc->node->parent) { nparent = proc->node->parent; } else { proc_tmp = proc_by_pid(1); if(proc_tmp && proc_tmp->node) { nparent = proc_tmp->node; } else { // this should not happen, but we have to attach the node somewhere // this could happen if the netlink messages arrive befor a fill update g_warning("attach child from dead process to root tree"); nparent = processes_tree; } } g_node_unlink(proc->node); g_assert(nparent != proc->node); while((cur = g_node_first_child(proc->node)) != NULL) { g_node_unlink(cur); g_node_append(nparent, cur); } } else { g_node_unlink(proc->node); } } /** * remove pid from delay stack * @arg pid #pid_t pid * * removes process from the delay stack * * @return none */ static void remove_proc_from_delay_stack(pid_t pid) { int i = 0; struct delay_proc *cur; for(i = 0; i < delay_stack->len;) { cur = g_ptr_array_index(delay_stack, i); if(cur->proc->pid == pid) { u_trace("remove delay %d %d:%d", pid, i, delay_stack->len); g_ptr_array_remove_index_fast(delay_stack, i); } else { i++; } } } /** * test if pid is in delay stack * @arg pid #pid_t pid * * @return boolean */ static int pid_in_delay_stack(pid_t pid) { int i = 0; struct delay_proc *cur; for(i = 0; i < delay_stack->len; i++) { cur = g_ptr_array_index(delay_stack, i); if(cur->proc->pid == pid) return TRUE; } return FALSE; } /** * free u_proc instance * @arg ptr pointer to #u_proc * * free's all memory of a u_proc. This function should never be called directly. * It as called automaticly when the ref counter drops 0 * * @return none */ void u_proc_free(void *ptr) { u_proc *proc = ptr; g_assert(proc->ref == 0); g_free(proc->cmdfile); g_free(proc->exe); g_free(proc->cmdline_match); g_strfreev(proc->cgroup_origin); if(proc->environ) g_hash_table_unref(proc->environ); if(proc->cmdline) g_ptr_array_unref(proc->cmdline); if(proc->lua_data) { luaL_unref(lua_main_state, LUA_REGISTRYINDEX, proc->lua_data); } g_hash_table_destroy (proc->skip_filter); //if(proc->tasks) g_ptr_array_free(proc->tasks, TRUE); u_proc_remove_child_nodes(proc); g_assert(g_node_n_children(proc->node) == 0); g_node_destroy(proc->node); freesupgrp(&(proc->proc)); freeproc_light(&(proc->proc)); g_slice_free(u_proc, proc); } void u_proc_free_task(void *ptr) { u_task *task = ptr; // the task group owner has the same pointers, so we shall not free them // when the task is removed if(task->task.nsupgid > 0 && task->task.supgid && task->task.supgid != task->proc->proc.supgid) { free(task->task.supgid); } //g_free(proc->supgid); g_slice_free(u_task, task); } /** * allocate new #u_proc * @arg proc pointer to #proc_t datastructure * * Allocates a new #u_proc. It can be prefiled with a proc_t datastructure. * If \c proc is NULL, the resulting u_proc will have the state UPROC_NEW, otherwise * it is UPROC_ALIVE * * @return newly allocated #u_proc reference */ u_proc* u_proc_new(proc_t *proc) { u_proc *rv; rv = g_slice_new0(u_proc); rv->free_fnk = u_proc_free; rv->ref = 1; rv->skip_filter = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, filter_block_free); //rv->tasks = g_array_new(FALSE, TRUE, sizeof(proc_t)); rv->tasks = g_ptr_array_new_with_free_func(u_proc_free_task); rv->flags = NULL; rv->changed = TRUE; rv->node = g_node_new(rv); if(proc) { rv->pid = proc->tid; U_PROC_SET_STATE(rv,UPROC_ALIVE); memcpy(&(rv->proc), proc, sizeof(proc_t)); } else { U_PROC_SET_STATE(rv,UPROC_NEW); } return rv; } /** * list all flags from #u_proc * @arg proc a #u_proc * @arg recrusive boolean if recrusive flags should be returned, too * * Returns a new allocated GList of all flags. Don't forgett to DECREF the * result items and release the list * * @return @glist */ GList *u_proc_list_flags (u_proc *proc, gboolean recrusive) { int i = 1; u_flag *fl; GList *cur, *rv = NULL; do { cur = g_list_first(proc->flags); while(cur) { fl = cur->data; if(recrusive == 2 && !fl->inherit) { cur = g_list_next (cur); continue; } INC_REF(fl); rv = g_list_append(rv, fl); i++; cur = g_list_next (cur); } if(recrusive) { if(!proc->node || !proc->node->parent || proc->node->parent == processes_tree) { proc = NULL; break; } proc = (u_proc *)(proc->node->parent->data); if(recrusive == 1) recrusive = 2; } } while (recrusive && proc); return rv; } /** * ensures fields on #u_proc * @arg proc a #u_proc * @arg what set of varibles to fill from #ENSURE_WHAT * @arg update force update * * Ensures a set of varibles is filled. * If update is true, the variable are updated even if they already exist. * * @return @success */ int u_proc_ensure(u_proc *proc, enum ENSURE_WHAT what, int update) { if(what == BASIC) { // make sure process has basic values parsed if(U_PROC_HAS_STATE(proc,UPROC_BASIC) && !update) return TRUE; else return process_update_pid(proc->pid); } else if(what == TASKS) { // FIXME return TRUE; } else if(what == ENVIRONMENT) { if(update && proc->environ) { g_hash_table_unref(proc->environ); proc->environ = NULL; } if(!proc->environ) proc->environ = u_read_env_hash (proc->pid); return (proc->environ != NULL); } else if(what == CMDLINE) { if(update && proc->cmdline) { g_ptr_array_unref(proc->cmdline); proc->cmdline = NULL; } if(!proc->cmdline) { int i; gchar *tmp, *tmp2; g_free(proc->cmdline_match); proc->cmdline_match = NULL; GString *match = g_string_new(""); proc->cmdline = u_read_0file (proc->pid, "cmdline"); // update cmd if(proc->cmdline) { for(i = 0; i < proc->cmdline->len; i++) { if(i) match = g_string_append_c(match, ' '); match = g_string_append(match, g_ptr_array_index(proc->cmdline, i)); } proc->cmdline_match = g_string_free(match, FALSE); // empty command line, for kernel threads for example if(!proc->cmdline->len) return FALSE; if(proc->cmdfile) { g_free(proc->cmdfile); proc->cmdfile = NULL; } tmp = g_ptr_array_index(proc->cmdline, 0); if(tmp) { tmp2 = g_strrstr_len(tmp, -1, "/"); if(tmp2 == NULL) { proc->cmdfile = g_strdup(tmp); } else if((tmp2+1-tmp) < strlen(tmp)) { proc->cmdfile = g_strdup(tmp2+1); } } return TRUE; } else { return FALSE; } } return (proc->cmdline != NULL); } else if(what == EXE) { char buf[PATH_MAX+1]; ssize_t out; char *path; if(update && proc->exe) { g_free(proc->exe); proc->exe = NULL; } if(!proc->exe) { path = g_strdup_printf ("/proc/%u/exe", (guint)proc->pid); out = readlink(path, (char *)&buf, PATH_MAX); buf[out] = 0; if(out > 0) { // strip out the ' (deleted)' suffix if(out > 10 && !strncmp((char *)&buf[out-10], " (deleted)", 10)) { buf[out-10] = 0; out -= 10; } proc->exe = g_strndup((char *)&buf, out); } else { g_free(path); return FALSE; } g_free(path); } return TRUE; } return FALSE; } /** * up to date list process tasks * @arg proc #u_proc to get tasks from * * Returns a GArray of #pid_t of all tasks from given #u_proc process * * @return none */ GArray *u_proc_get_current_task_pids(u_proc *proc) { if(!U_PROC_SET_STATE(proc, UPROC_ALIVE)) return FALSE; GArray *rv = g_array_new(TRUE, TRUE, sizeof(pid_t)); DIR *dip; struct dirent *dit; pid_t tpid; char *path = g_strdup_printf("/proc/%d/task", proc->pid); dip = opendir(path); if(!dip) goto out; while ((dit = readdir(dip)) != NULL) { if(!strcmp(dit->d_name, ".") || !strcmp(dit->d_name, "..")) continue; tpid = (pid_t)atol(dit->d_name); g_array_append_val(rv, tpid); } closedir(dip); g_free(path); return rv; out: g_free(path); g_array_unref(rv); return NULL; } /** * free process * @arg data a #u_proc pointer * * INTERNAL: Called when the process is removed from the process_list * * @return none */ static void processes_free_value(gpointer data) { // called when a process is freed from the process list // this means that the process is not valid anymore and is // marked as such u_proc *proc = data; u_filter *flt; U_PROC_UNSET_STATE(proc, UPROC_ALIVE); // run exit hooks GList *cur = g_list_first(filter_list); while(cur) { flt = cur->data; if(flt->exit) flt->exit(proc, flt); cur = cur->next; } U_PROC_SET_STATE(proc, UPROC_INVALID); u_proc_remove_child_nodes(proc); // remove it from the delay stack remove_proc_from_delay_stack(proc->pid); DEC_REF(proc); } static int find_parent_caller_stack(GArray *array, pid_t pid) { int i; for(i = 0; i < array->len; i++) { if(g_array_index(array, pid_t, i) == pid) return TRUE; } return FALSE; } static int remove_parent_caller_stack(GArray *array, pid_t pid) { int i; for(i = 0; i < array->len; i++) { if(g_array_index(array, pid_t, i) == pid) { g_array_remove_index(array, i); return TRUE; } } return FALSE; } /** * returns the parent of process * @arg parent_pid #pid_t of parent * @arg child #u_proc of child * * INTERNAL: lookup the parent #u_proc of a child. Prints warning when missing. * * @return #u_proc of parent */ static inline u_proc *parent_proc_by_pid(pid_t parent_pid, u_proc *child) { pid_t update_pid; static GArray *updates = NULL; if(!updates) updates = g_array_new(FALSE, FALSE, sizeof(pid_t)); u_proc *parent = proc_by_pid(parent_pid); // this should't happen, but under fork stress init may not have // collected so the parent does not exist, or the parent just died. we try updating // the process first and try again. if(!parent) { g_debug("parent missing: %d, force update", parent_pid); if(!find_parent_caller_stack(updates, child->pid)) { update_pid = child->pid; g_array_append_val(updates, update_pid); process_update_pid(update_pid); remove_parent_caller_stack(updates, update_pid); } else if(!find_parent_caller_stack(updates, child->proc.ppid)) { // we try to get the parent as last resort update_pid = child->proc.ppid; g_array_append_val(updates, update_pid); process_update_pid(update_pid); remove_parent_caller_stack(updates, update_pid); } parent = proc_by_pid(child->proc.ppid); if(!parent) { g_debug("parent missing, second try: %d parent %d", child->pid, child->proc.ppid); process_update_pid(child->proc.ppid); parent = proc_by_pid(child->proc.ppid); } } if(!parent) { g_warning("pid: %d parent %d missing. attaching to pid 1", child->pid, parent_pid); return proc_by_pid(1); } return parent; } /** * rebuilds the process tree * * INTERNAL: completly rebuild the process tree. used when a desync is detected * on update_processes. * * @return none */ static void rebuild_tree() { GHashTableIter iter; GList *keys, *cur; gpointer key, value; u_proc *proc, *parent; // clear root node g_node_destroy(processes_tree); processes_tree = g_node_new(NULL); // create nodes first g_hash_table_iter_init (&iter, processes); while (g_hash_table_iter_next (&iter, &key, &value)) { proc = (u_proc *)value; proc->node = g_node_new(proc); g_node_append(processes_tree, proc->node); } // now we can lookup the parents and attach the node to the parent //g_hash_table_iter_init (&iter, processes); keys = g_hash_table_get_keys(processes); cur = g_list_first(keys); while(cur) { proc = (u_proc *)g_hash_table_lookup(processes,cur->data); g_assert(proc->proc.ppid != proc->pid); if(proc->proc.ppid) { // get a parent, hopfully the real one parent = parent_proc_by_pid(proc->proc.ppid, proc); U_PROC_SET_STATE(proc, UPROC_HAS_PARENT); g_assert(parent != proc); g_assert(parent && parent->node); g_node_unlink(proc->node); g_node_append(parent->node, proc->node); } else { g_node_unlink(proc->node); g_node_append(processes_tree, proc->node); U_PROC_UNSET_STATE(proc, UPROC_HAS_PARENT); } cur = cur->next; } g_list_free(keys); } /** * detect changes of process * @arg old *#proc_t of old values * @arg new *#proc_t of new values * * INTERNAL: detect if the changed values of a u_proc.proc structure are sufficient * enough for the #u_proc.changed flag to be set. When the changed flag is set, * the scheduler will run again. * * @return boolean if a major change detected */ static int detect_changed(proc_t *old, proc_t *new) { // detects changes of main paramenters if(old->euid != new->euid || old->session != new->session || old->egid != new->egid || old->pgrp != new->pgrp || old->sched != new->sched || old->rtprio != new->rtprio) return 1; return 0; } /** * test if process has changed * @arg key unused * @arg value #u_proc pointer * @arg user_data pointer to int * * INTERNAL: detect if the process was changed in the last full update run. * if not, the process is removed from the process_list * * @return boolean TRUE if not changed */ static gboolean processes_is_last_changed(gpointer key, gpointer value, gpointer user_data) { u_proc *proc = (u_proc *)value; int last_changed = *(int *)user_data; return (proc->last_update != last_changed); } /** * remove process * @arg proc #u_proc to remove * * tells the core that a process is not active anymore * * @return boolean if the process got removed */ int process_remove(u_proc *proc) { return g_hash_table_remove(processes, GUINT_TO_POINTER(proc->pid)); } /** * remove process by pid * @arg proc #pid_t to remove * * same as process_remove execpt with pid * * @return boolean if the process got removed */ int process_remove_by_pid(pid_t pid) { return g_hash_table_remove(processes, GUINT_TO_POINTER(pid)); } /** * clear all changed flags * * INTERNAL: unset the changed flag. called after a full run. * * @return none */ static void clear_process_changed() { GHashTableIter iter; gpointer ikey, value; u_proc *proc; g_hash_table_iter_init (&iter, processes); while (g_hash_table_iter_next (&iter, &ikey, &value)) { proc = (u_proc *)value; proc->changed = FALSE; } return; } // helper for process_clear_filter_block static gboolean _clear_skip_filters_types(gpointer key, gpointer value, gpointer user_data) { struct filter_block *fb = value; int *block_type = user_data; return !(fb->flags & *block_type); } /** * clears given skip filters * * @arg proc #u_proc to change * @arg block_types remove the matching block types * * clears all filter blocks of given types * * @return none */ void clear_process_skip_filters(u_proc *proc, int block_types) { g_hash_table_foreach_remove(proc->skip_filter, _clear_skip_filters_types, &block_types); } // copy the fake value of a parent pid to the child until the real value // of the child changes from the parent #define fake_var_fix(FAKE, ORG) \ if(proc->FAKE && ((proc-> FAKE##_old != proc->proc.ORG) || (proc->FAKE == proc->proc.ORG))) { \ /* when real value was set, the fake value disapears. */ \ /*printf("unset fake: %d %d %d %d\n", proc->pid, proc->proc.ORG, proc->FAKE##_old, proc-> FAKE);*/ \ proc-> FAKE = 0; \ proc->FAKE##_old = 0; \ proc->changed = 1; \ } else if(parent-> FAKE && !proc->FAKE && \ parent->proc.ORG == proc->proc.ORG && \ parent-> FAKE != proc->FAKE) { \ proc-> FAKE = parent->FAKE; \ proc->FAKE##_old = proc->proc.ORG; \ proc->changed = 1; \ /*printf("set fake: pid:%d ppid:%d fake:%d fake_old:%d\n", proc->pid, parent->pid, proc->FAKE, proc->FAKE##_old);*/ \ } /** * process workarrounds * @arg proc #u_proc proc * @arg parent #u_proc parent * * INTERNAL: do workarounds for process parameters that can't be changed in the * system but need to for nice grouping. * * @return boolean if the process got removed */ static void process_workarrounds(u_proc *proc, u_proc *parent) { // do various workaround jobs here... fake_var_fix(fake_pgrp, pgrp); fake_var_fix(fake_session, session); } #undef fake_var_fix /** * updates processes * @arg proctab #PROCTAB * @arg full boolean indicates that a full run is done * * parses the /proc filesystem and updates the internal node structure acordingly. * This low level function is usually called from wrapper that fill the @proctab * accordingly. * * @return int number of parsed records */ int update_processes_run(PROCTAB *proctab, int full) { proc_t buf; proc_t buf_task; u_proc *proc; u_proc *parent; time_t timeout = time(NULL); gboolean full_update = FALSE; static int run = 0; int rrt; int rv = 0; int i; GList *updated = NULL; if(full) run++; if(!proctab) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, "can't open /proc"); return 1; } memset(&buf, 0, sizeof(proc_t)); while(readproc(proctab, &buf)){ proc = proc_by_pid(buf.tid); if(proc) { // we need to clear the task array first to detect which dynamic mallocs // need to be freed as readproc likes to reuse pointers on some dynamic // allocations if(proc->tasks->len) g_ptr_array_remove_range(proc->tasks, 0, proc->tasks->len); // free all changable allocated buffers freesupgrp(&(proc->proc)); freeproc_light(&(proc->proc)); } else { proc = u_proc_new(&buf); g_hash_table_insert(processes, GUINT_TO_POINTER(proc->pid), proc); // we save the origin of cgroups for scheduler constrains } // must still have the process allocated // detect change of important parameters that will cause a reschedule proc->changed = proc->changed | detect_changed(&(proc->proc), &buf); // remove it from delay stack remove_proc_from_delay_stack(proc->pid); if(full) proc->last_update = run; //save rt received flag rrt = proc->received_rt; memcpy(&(proc->proc), &buf, sizeof(proc_t)); proc->received_rt |= (proc->proc.sched == SCHED_FIFO || proc->proc.sched == SCHED_RR); while(readtask(proctab,&buf,&buf_task)) { u_task *task = g_slice_new0(u_task); task->proc = proc; memcpy(&(task->task), &buf_task, sizeof(proc_t)); g_ptr_array_add(proc->tasks, task); proc->received_rt |= (buf_task.sched == SCHED_FIFO || buf_task.sched == SCHED_RR); } if(rrt != proc->received_rt) proc->changed = 1; if(!proc->cgroup_origin) proc->cgroup_origin = g_strdupv(proc->proc.cgroup); U_PROC_UNSET_STATE(proc, UPROC_NEW); U_PROC_SET_STATE(proc, UPROC_ALIVE); if((proctab->flags & OPENPROC_FLAGS) == OPENPROC_FLAGS) { U_PROC_SET_STATE(proc, UPROC_BASIC); } else U_PROC_UNSET_STATE(proc, UPROC_BASIC); u_flag_clear_timeout(proc, timeout); updated = g_list_append(updated, proc); rv++; memset(&buf, 0, sizeof(proc_t)); //g_list_foreach(filter_list, filter_run_for_proc, &buf); //freesupgrp(&buf); } // we update the parent links after all processes are updated for(i = 0; i < rv; i++) { proc = g_list_nth_data(updated, i); if(proc->proc.ppid && proc->proc.ppid != proc->pid) { parent = g_hash_table_lookup(processes, GUINT_TO_POINTER(proc->proc.ppid)); // the parent should exist. in case it is missing we have to run a full // tree rebuild then if(parent && parent->node) { // current parent is not what it should be if(proc->node->parent != parent->node) { g_node_unlink(proc->node); g_node_append(parent->node, proc->node); } process_workarrounds(proc, parent); } else { full_update = TRUE; } } else { // this is kinda bad. it is ok for kernel processes and init if(proc->node->parent != processes_tree) { if(!G_NODE_IS_ROOT(proc->node)) g_node_unlink(proc->node); g_node_append(processes_tree, proc->node); } } } // remove old processes g_list_free(updated); if(full) { g_hash_table_foreach_remove(processes, processes_is_last_changed, &run); // we can completly clean the delay stack as all processes are now processed // missing so will cause scheduling for dead processes if(delay_stack->len) g_ptr_array_remove_range(delay_stack, 0, delay_stack->len); } if(full_update) { rebuild_tree(); } return rv; } /** * updates all processes * * updates all process of the system * * @return number of process updated */ int process_update_all() { int rv; PROCTAB *proctab; proctab = openproc(OPENPROC_FLAGS); rv = update_processes_run(proctab, TRUE); closeproc(proctab); return rv; } // calculated the difference between two timespec values static struct timespec diff(struct timespec start, struct timespec end) { struct timespec temp; if ((end.tv_nsec-start.tv_nsec)<0) { temp.tv_sec = end.tv_sec-start.tv_sec-1; temp.tv_nsec = 1000000000+end.tv_nsec-start.tv_nsec; } else { temp.tv_sec = end.tv_sec-start.tv_sec; temp.tv_nsec = end.tv_nsec-start.tv_nsec; } return temp; } /** * runs process from delay stack * * called by timeout to check if processes from the delay stack are old enough * to be run through the filters and scheduler * * @return number of process updated */ static int run_new_pid(gpointer ign) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); struct delay_proc *cur; struct timespec td; int i; GArray *targets = NULL; if(!delay_stack->len) return TRUE; targets = g_array_new(TRUE, FALSE, sizeof(pid_t)); for(i = 0; i < delay_stack->len;i++) { cur = g_ptr_array_index(delay_stack, i); td = diff(cur->when, now); //printf("test %d %ld >= %ld\n", cur->proc->pid, (td.tv_sec * 1000000000 + td.tv_nsec), delay); if((td.tv_sec * 1000000000 + td.tv_nsec) >= delay) { u_trace("run filter for %d", cur->proc->pid); g_array_append_val(targets, cur->proc->pid); // enforce the scheduler on run when moved from the delay queue cur->proc->changed = TRUE; } } process_new_list(targets, TRUE, FALSE); // process_new_list removes the entries it processes from the delay stack // buf it the process is dead already, they stay here in the list. we make // sure they are removed. for(i=0; ilen; i++) { remove_proc_from_delay_stack(g_array_index(targets, pid_t, i)); } g_array_unref(targets); return TRUE; } /** * adds a new process via delay stack * @arg pid new pid to create * @arg parent pid of parent process * * this function creates a delay process. This means that a #u_proc instance is * created and linked into the process tree, but the process is not parsed and * the rules and scheduler run on it. If the delay process sticks in the system * for the configured "delay" or is updated by the full update in between, the * process will be parsed fully and run through the rules and scheduler. * This is the prefered way to notifiy the core of new processes as it allows to * save cpu time for processes that die very quickly. * Passing a parent helps to skip reading basic data from /proc * * @return boolean. TRUE if process could be created. */ gboolean process_new_delay(pid_t pid, pid_t parent) { u_proc *proc, *proc_parent; struct delay_proc *lp; g_assert(pid != 0); if(!delay) { return process_new(pid, TRUE); } proc = proc_by_pid(pid); if(!proc) { if(parent) { proc = u_proc_new(NULL); proc->pid = pid; proc->proc.tid = pid; proc->proc.ppid = parent; U_PROC_SET_STATE(proc, UPROC_HAS_PARENT); // put it into the lists proc_parent = parent_proc_by_pid(parent, proc); g_node_append(proc_parent->node, proc->node); g_hash_table_insert(processes, GUINT_TO_POINTER(pid), proc); } else { if(!process_update_pid(pid)) return FALSE; proc = proc_by_pid(pid); if(!proc) return FALSE; } lp = g_malloc(sizeof(struct delay_proc)); lp->proc = proc; clock_gettime(CLOCK_MONOTONIC, &(lp->when)); g_ptr_array_add(delay_stack, lp); proc->changed = FALSE; filter_for_proc(proc, filter_fast_list); if(proc->changed) scheduler_run_one(proc); proc->changed = TRUE; } else { // we got a exec event, so the process changed daramaticly. // clear all filter blocks that requested rerun on exec clear_process_skip_filters(proc, FILTER_RERUN_EXEC); // force update on basic data, they will be invalid anyway int old_changed = proc->changed; u_proc_ensure(proc, CMDLINE, TRUE); u_proc_ensure(proc, BASIC, TRUE); u_proc_ensure(proc, EXE, TRUE); // if a process is in the new stack, his changed settings will be true // for sure, but we only want to schedule him, if the instant filters // change something. // if the process is old, we run the filters and let the scheduler decide if(pid_in_delay_stack(proc->pid)) { proc->changed = FALSE; filter_for_proc(proc, filter_fast_list); if(proc->changed) scheduler_run_one(proc); proc->changed = old_changed; } else { filter_for_proc(proc, filter_fast_list); scheduler_run_one(proc); } } return TRUE; } /** * updates list of pids * @arg pids #pid_t array * * Updates a list of processes. The @pids list must be terminated with 0. * * @return int. number of processes updated */ int process_update_pids(pid_t pids[]) { int rv; PROCTAB *proctab; u_timer_start(&timer_parse); proctab = openproc(OPENPROC_FLAGS | PROC_PID, pids); rv = update_processes_run(proctab, FALSE); u_timer_stop(&timer_parse); closeproc(proctab); return rv; } /** * updates a single pid * @arg pid #pid_t to update * * Updates a single pid. If you have a list of processes to update, better use * process_update_pids * * @return int. number of processes updated */ int process_update_pid(pid_t pid) { pid_t pids [2] = { pid, 0 }; return process_update_pids(pids); } /** * instant add new process * @arg pid #pid_t to update * @arg noupdate skip if process already exists * * Indicates a new process and runs the rules and scheduler on it. * * @return boolean. Sucess */ int process_new(int pid, int noupdate) { u_proc *proc; // if the process is already dead we can exit if(noupdate && proc_by_pid(pid)) return FALSE; if(!process_update_pid(pid)) return FALSE; proc = proc_by_pid(pid); if(!proc) return FALSE; filter_for_proc(proc, filter_fast_list); filter_for_proc(proc, filter_list); scheduler_run_one(proc); return TRUE; } /** * updates list of processes * @arg list array of #pid_t * @arg update update even if existing * @instant boolean if instant filters should be run first * * Indicates a list of new processes and runs the rules and scheduler on it. * * @return boolean. Sucess */ int process_new_list(GArray *list, int update, int instant) { u_proc *proc; int i, j = 0; pid_t *pids = (pid_t *)malloc((list->len+1)*sizeof(pid_t)); //int pid_t = malloc(sizeof(pid_t)*(list->len+1)); for(i = 0; i < list->len; i++) { if(update || !proc_by_pid(g_array_index(list,pid_t,i))) { pids[j] = g_array_index(list,pid_t,i); j++; } } pids[j] = 0; // if the process is already dead we can exit process_update_pids(pids); for(i=0; i < list->len; i++) { proc = proc_by_pid(g_array_index(list,pid_t,i)); if(!proc) continue; if(!u_proc_ensure(proc, BASIC, FALSE)) continue; if(instant) filter_for_proc(proc, filter_fast_list); filter_for_proc(proc, filter_list); scheduler_run_one(proc); } free(pids); return TRUE; } /** * run filters and scheduler on one process * @arg proc #u_proc to run * @update: update process before run * * Run the filters and scheduler on the process * * @return boolean. Sucess */ int process_run_one(u_proc *proc, int update, int instant) { if(update) process_update_pid(proc->pid); filter_for_proc(proc, instant ? filter_fast_list : filter_list); scheduler_run_one(proc); return TRUE; } /** * free flags * @ptr: #u_flag pointer * * INTERNAL: free a u_flag structure. It is called when the ref count drops 0. * * @return none */ void u_flag_free(void *ptr) { u_flag *flag = ptr; g_assert(flag->ref == 0); if(flag->name) free(flag->name); g_slice_free(u_flag, flag); } /** * u_flag_new: * @arg source pointer to identify the source * @arg name char * name of flag * * Allocates a new u_flag * * @return #u_flag pointer */ u_flag *u_flag_new(u_filter *source, const char *name) { u_flag *rv; rv = g_slice_new0(u_flag); rv->free_fnk = u_flag_free; rv->ref = 1; rv->source = source; if(name) { rv->name = g_strdup(name); } return rv; } /** * add flag to process * @arg proc #u_proc to add the flag to, or NULL for system flags * @arg flag #u_flag to add * * Adds a new flag to the u_proc or system flag list. * * @return boolean. TRUE on success. */ int u_flag_add(u_proc *proc, u_flag *flag) { if(proc) { if(!g_list_find(proc->flags, flag)) { proc->flags = g_list_insert(proc->flags, flag, 0); INC_REF(flag); } proc->changed = 1; } else { if(!g_list_find(system_flags, flag)) { system_flags = g_list_insert(system_flags, flag, 0); INC_REF(flag); } } return TRUE; } /** * delete flag from process * @arg proc #u_proc to remove the flag from, or NULL for system flags * @arg flag #u_flag to remove * * Removes a flag from a process or system flags. * * @return boolean. TRUE on success. */ int u_flag_del(u_proc *proc, u_flag *flag) { if(proc) { if(g_list_index(proc->flags, flag) != -1) { DEC_REF(flag); } proc->flags = g_list_remove(proc->flags, flag); proc->changed = 1; return TRUE; } else { if(g_list_index(system_flags, flag) != -1) { DEC_REF(flag); } system_flags = g_list_remove(system_flags, flag); return TRUE; } return FALSE; } static gint u_flag_match_source(gconstpointer a, gconstpointer match) { u_flag *flg = (u_flag *)a; if(flg->source == match) return 0; return -1; } static gint u_flag_match_flag(gconstpointer a, gconstpointer match) { u_flag *flg = (u_flag *)a; if(flg == match) return 0; return -1; } static int u_flag_match_name(gconstpointer a, gconstpointer name) { u_flag *flg = (u_flag *)a; return strcmp(flg->name, (char *)name); } static int u_flag_match_timeout(gconstpointer a, gconstpointer time) { u_flag *flg = (u_flag *)a; time_t t = GPOINTER_TO_UINT(time); if(flg->timeout == 0) return TRUE; return (flg->timeout > t); } #define CLEAR_BUILD(NAME, ARG, CMP) \ int NAME (u_proc *proc, ARG ) { \ GList *item; \ int rv = 0; \ while((item = CMP ) != NULL) { \ if(proc) { \ proc->flags = g_list_remove_link (proc->flags, item); \ DEC_REF(item->data); \ item->data = NULL; \ proc->changed = 1; \ rv++; \ g_list_free(item); \ } else { \ system_flags = g_list_remove_link (system_flags, item); \ DEC_REF(item->data); \ item->data = NULL; \ system_flags_changed = 1; \ rv ++; \ g_list_free(item); \ } \ } \ return rv; \ } CLEAR_BUILD(u_flag_clear_source, const void *var, g_list_find_custom(proc ? proc->flags : system_flags, var, u_flag_match_source)) CLEAR_BUILD(u_flag_clear_name, const char *name, g_list_find_custom(proc ? proc->flags : system_flags, name, u_flag_match_name)) CLEAR_BUILD(u_flag_clear_flag, const void *var, g_list_find_custom(proc ? proc->flags : system_flags, var, u_flag_match_flag)) CLEAR_BUILD(u_flag_clear_timeout, time_t tm, g_list_find_custom(proc ? proc->flags : system_flags, GUINT_TO_POINTER(tm), u_flag_match_timeout)) int u_flag_clear_all(u_proc *proc) { GList *item; int rv = 0; if(proc) { while((item = g_list_first(proc->flags)) != NULL) { proc->flags = g_list_remove_link (proc->flags, item); DEC_REF(item->data); item->data = NULL; proc->changed = 1; rv++; g_list_free(item); } g_list_free(proc->flags); } else { while((item = g_list_first(system_flags)) != NULL) { system_flags = g_list_remove_link(system_flags, item); DEC_REF(item->data); item->data = NULL; g_list_free(item); rv++; system_flags_changed = 1; } g_list_free(system_flags); } return rv; } /************************************************************* * filter code ************************************************************/ void u_filter_free(void *ptr) { // FIXME } u_filter* filter_new() { u_filter *rv = malloc(sizeof(u_filter)); memset(rv, 0, sizeof(u_filter)); rv->free_fnk = u_filter_free; return rv; } void filter_register(u_filter *filter, int instant) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "register new filter:%s instant:%d", filter->name ? filter->name : "unknown", instant); if(instant) { filter_fast_list = g_list_append(filter_fast_list, filter); } else { filter_list = g_list_append(filter_list, filter); } } int filter_run_for_proc(gpointer data, gpointer user_data) { u_proc *proc = data; u_filter *flt = user_data; struct filter_block *flt_block =NULL; int rv = 0; time_t ttime = 0; int timeout, flags; //printf("filter for proc %p\n", flt); g_assert(data); flt_block = (struct filter_block *)g_hash_table_lookup(proc->skip_filter, GUINT_TO_POINTER(flt)); //g_hash_table_lookup if(flt_block) { if(flt_block->flags & FILTER_STOP) return 0; time (&ttime); if(flt_block->timeout > ttime) return 0; } if(flt->check) { // if return 0 the real callback will be skipped if(!flt->check(proc, flt)) return 0; } rv = flt->callback(proc, flt); if(rv == 0) return rv; if(!flt_block) { flt_block = malloc(sizeof(struct filter_block)); memset(flt_block, 0, sizeof(struct filter_block)); g_hash_table_insert(proc->skip_filter, GUINT_TO_POINTER(flt), flt_block); } timeout = FILTER_TIMEOUT(rv); flags = FILTER_FLAGS(rv); if(timeout) { if(!ttime) time (&ttime); flt_block->timeout = ttime + abs(timeout); } flt_block->flags = flags; return rv; } static GNode *blocked_parent; gboolean filter_run_for_node(GNode *node, gpointer data) { GNode *tmp; int rv; //u_filter *uf = data; //printf("rfn %s ;", uf->name); //printf("run for node\n"); if(node == processes_tree) return FALSE; if(blocked_parent) { do { tmp = node->parent; if(!tmp) break; if(tmp == blocked_parent) { // we don't run filters on nodes those parent set the skip child flag return FALSE; } else if (tmp == processes_tree) { // we can unset the block, as we must have left all childs blocked_parent = NULL; break; } } while(TRUE); } rv = filter_run_for_proc(node->data, data); if(FILTER_FLAGS(rv) & FILTER_SKIP_CHILD) { blocked_parent = node; } return FALSE; } int scheduler_run() { // FIXME make scheduler more flexible if(scheduler.all) { return scheduler.all(); } else { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "no scheduler.all set"); } return 1; } int scheduler_run_one(u_proc *proc) { // FIXME make scheduler more flexible int rv; if(scheduler.one) { u_timer_start(&timer_scheduler); rv = scheduler.one(proc); u_timer_stop(&timer_scheduler); return rv; } g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "no scheduler.one set"); return 1; } void filter_for_proc(u_proc *proc, GList *list) { /* run all filters on one proc */ u_timer_start(&timer_filter); GList *cur = g_list_first(list); while(cur) { filter_run_for_proc(proc, cur->data); cur = g_list_next(cur); } u_timer_stop(&timer_filter); } void filter_run() { u_filter *flt; int i = 0; //printf("run filter %p, %d\n", filter_list, g_list_length(filter_list)); GList *cur; if(filter_fast_list) { cur = g_list_first(filter_fast_list); } else { cur = g_list_first(filter_list); i=1; } while(cur) { flt = cur->data; blocked_parent = NULL; if(flt->precheck) if(!flt->precheck(flt)) { cur = g_list_next(cur); continue; } g_debug("run filter: %s", flt->name); //printf("children %d %d\n", g_node_n_children(processes_tree), g_node_n_nodes (processes_tree,G_TRAVERSE_ALL )); g_node_traverse(processes_tree, G_PRE_ORDER,G_TRAVERSE_ALL, -1, filter_run_for_node, flt); if(flt->postcheck) { flt->postcheck(flt); } cur = g_list_next(cur); if(!cur && i == 0) { cur = g_list_first(filter_list); i++; } } blocked_parent = NULL; } static void update_caches() { double a, b; loadavg(&_last_load, &a, &b); _last_percent = (_last_load / (double)smp_num_cpus); } int iterate(gpointer rv) { time_t timeout = time(NULL); GTimer *timer = g_timer_new(); gdouble last, current, tparse, tfilter, tscheduler; gulong dump; tparse = g_timer_elapsed(timer_parse.timer, &dump); tfilter = g_timer_elapsed(timer_filter.timer, &dump); tscheduler = g_timer_elapsed(timer_scheduler.timer, &dump); g_debug("spend between iterations: update=%0.2F filter=%0.2F scheduler=%0.2F total=%0.2F", tparse, tfilter, tscheduler, (tparse + tfilter + tscheduler)); g_timer_start(timer); u_flag_clear_timeout(NULL, timeout); iteration += 1; g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "start iteration %d:", iteration); update_caches(); g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "update processes:"); last = g_timer_elapsed(timer, &dump); process_update_all(); current = g_timer_elapsed(timer, &dump); g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "took %0.2F. run filter:", (current - last)); last = current; filter_run(); current = g_timer_elapsed(timer, &dump); g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "took %0.2F. schedule:", (current - last)); last = current; scheduler_run(); g_timer_stop(timer); current = g_timer_elapsed(timer, &dump); g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "took %0.2F. complete run %d took %0.2F", (current - last), iteration, current); clear_process_changed(); system_flags_changed = 0; // g_timer_reset causes strange effects... g_timer_destroy(timer); u_timer_stop_clear(&timer_parse); u_timer_stop_clear(&timer_filter); u_timer_stop_clear(&timer_scheduler); // try the make current memory non swapalbe if(mlockall(MCL_CURRENT) && getuid() == 0) g_debug("can't mlock memory"); return GPOINTER_TO_INT(rv); } /*************************************************************************** * scheduler stuff **************************************************************************/ int scheduler_set(u_scheduler *sched) { if(sched) { memcpy(&scheduler, sched, sizeof(u_scheduler)); } else { memset(&scheduler, 0, sizeof(u_scheduler)); g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "unset scheduler"); } return 0; } u_scheduler *scheduler_get() { return &scheduler; } /*************************************************************************** * rules and modules handling **************************************************************************/ int load_rule_directory(const char *path, const char *load_pattern, int fatal) { char rpath[PATH_MAX+1]; gsize disabled_len; int i, j; char **disabled; char *rule_name = NULL; struct stat sb; disabled = g_key_file_get_string_list(config_data, CONFIG_CORE, "disabled_rules", &disabled_len, NULL); g_message("load rule directory: %s", path); struct dirent **namelist; int n; n = scandir(path, &namelist, 0, versionsort); if (n < 0) { perror("scandir"); } else { for(i = 0; i < n; i++) { if(fnmatch("*.lua", namelist[i]->d_name, 0)) continue; rule_name = g_strndup(namelist[i]->d_name,strlen(namelist[i]->d_name)-4); if(load_pattern && (fnmatch(load_pattern, namelist[i]->d_name, 0) != 0)) goto skip; for(j = 0; j < disabled_len; j++) { if(!g_strcasecmp(disabled[j], rule_name)) goto skip; } snprintf(rpath, PATH_MAX, "%s/%s", path, namelist[i]->d_name); if (stat(rpath, &sb) == -1) goto skip; if((sb.st_mode & S_IFMT) != S_IFREG) goto next; if(load_lua_rule_file(lua_main_state, rpath) && fatal) abort(); next: g_free(rule_name); rule_name = NULL; free(namelist[i]); continue; skip: g_debug("skip rule: %s", namelist[i]->d_name); g_free(rule_name); rule_name = NULL; free(namelist[i]); } free(namelist); } return 0; } int load_modules(char *modules_directory) { DIR *dip; struct dirent *dit; char rpath[PATH_MAX+1]; char *minit_name, *module_name, *error; char **disabled; gsize disabled_len, i; gboolean skip; void *handle; int (*minit)(void); if ((dip = opendir(modules_directory)) == NULL) { perror("opendir"); return 0; } disabled = g_key_file_get_string_list(config_data, CONFIG_CORE, "disabled_modules", &disabled_len, NULL); while ((dit = readdir(dip)) != NULL) { skip = FALSE; if(fnmatch("*.so", dit->d_name, 0)) continue; module_name = g_strndup(dit->d_name,strlen(dit->d_name)-3); for(i = 0; i < disabled_len; i++) { if(!g_strcasecmp(disabled[i], module_name)) { skip = TRUE; break; } } if(!skip) { snprintf(rpath, PATH_MAX, "%s/%s", modules_directory, dit->d_name); g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "load module %s", dit->d_name); handle = dlopen(rpath, RTLD_LAZY); if (!handle) { //fprintf(stderr, "%s\n", dlerror()); g_log(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, "can't load module %s", rpath); } dlerror(); minit_name = g_strconcat(module_name, "_init", NULL); *(void **) (&minit) = dlsym(handle, minit_name); if ((error = dlerror()) != NULL) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, "can't load module %s: %s", module_name, error); } if(minit()) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "module %s returned error", module_name); free(minit_name); } else g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "skip module %s", module_name); free(module_name); } g_strfreev(disabled); closedir(dip); return 1; } int luaopen_ulatency(lua_State *L); int luaopen_bc(lua_State *L); int u_dbus_setup(); int core_init() { // load config iteration = 1; filter_list = NULL; smp_num_cpus = sysconf(_SC_NPROCESSORS_ONLN); // initialize profiling timer timer_filter.timer = g_timer_new(); timer_filter.count = 0; g_timer_stop(timer_filter.timer); timer_scheduler.timer = g_timer_new(); timer_scheduler.count = 0; g_timer_stop(timer_scheduler.timer); timer_parse.timer = g_timer_new(); timer_parse.count = 0; g_timer_stop(timer_parse.timer); #ifdef ENABLE_DBUS if(!u_dbus_setup()) g_warning("failed to setup dbus"); #endif #ifdef POLKIT_FOUND U_polkit_authority = polkit_authority_get_sync (NULL, NULL); #endif // delay stack delay_stack = g_ptr_array_new_with_free_func(free); delay = g_key_file_get_integer(config_data, CONFIG_CORE, "delay_new_pid", NULL); processes_tree = g_node_new(NULL); processes = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, processes_free_value); // configure lua lua_main_state = luaL_newstate(); luaL_openlibs(lua_main_state); luaopen_bc(lua_main_state); luaopen_ulatency(lua_main_state); #ifdef LIBCGROUP luaopen_cgroup(lua_main_state); #endif // FIXME make it configurable scheduler_set(&LUA_SCHEDULER); if(load_lua_rule_file(lua_main_state, QUOTEME(LUA_CORE_FILE))) g_log(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, "can't load core library"); g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "core initialized"); if(delay) g_timeout_add((int)(delay / 3), run_new_pid, NULL); // we save delay as ns for easier comparison delay = delay * 1000000; return 1; } void core_unload() { lua_gc (lua_main_state, LUA_GCCOLLECT, 0); } poelzi-ulatencyd-55515a9/src/core.lua000066400000000000000000000376431154664534000175310ustar00rootroot00000000000000--[[ Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. ]]-- --------------------------------- --! @file --! @brief ulatencyd core lua library --------------------------------- posix = require("posix") -- monkey patching lua core --! @brief split string with seperator sep --! @param sep seperator --! @return new table with chunks function string:split(sep) local sep, fields = sep or ":", {} local pattern = string.format("([^%s]+)", sep) self:gsub(pattern, function(c) fields[#fields+1] = c end) return fields end --! @brief copies tables --! @param t table --! @return new table with shallow copy function table.copy(t) local t2 = {} for k,v in pairs(t) do t2[k] = v end return t2 end --! @brief merge two tables --! @param t table of source 1 --! @param t2 table of source 2 --! @return table t function table.merge(t, t2) for k,v in pairs(t2) do t[k] = v end return t end -- logging shortcuts --! @brief log with level trace --! @param msg message --! @return nil function ulatency.log_trace(msg) ulatency.log(ulatency.LOG_LEVEL_TRACE, msg) end function ulatency.log_sched(msg) ulatency.log(ulatency.LOG_LEVEL_SCHED, msg) end function ulatency.log_debug(msg) ulatency.log(ulatency.LOG_LEVEL_DEBUG, msg) end function ulatency.log_info(msg) ulatency.log(ulatency.LOG_LEVEL_INFO, msg) end function ulatency.log_warning(msg) ulatency.log(ulatency.LOG_LEVEL_WARNING, msg) end function ulatency.log_error(msg) ulatency.log(ulatency.LOG_LEVEL_ERROR, msg) end function ulatency.log_critical(msg) ulatency.log(ulatency.LOG_LEVEL_CRITICAL, msg) end function re_from_table(tab) return table.concat(tab, "|") end function ulatency.list_processes_group(key) procs = ulatency.list_processes() rv = {} for i, proc in ipairs(procs) do c = rv[proc[key]] if not c then rv[proc[key]] = { proc } else c[#c+1] = proc end end return rv end function ulatency.add_adjust_flag(lst, match, adj) for i, flag in ipairs(lst) do for k, v in pairs(match) do if flag[k] ~= v then flag = nil break end end if flag then for k, v in pairs(adj) do flag[k] = v end return flag, true end end local flag = ulatency.new_flag(match) for k, v in pairs(adj) do flag[k] = v end return flag, false end function ulatency.find_flag(lst, match) for i, flag in ipairs(lst) do for k, v in pairs(match) do if flag[k] ~= v then flag = nil break end end if flag then return flag end end end -- load defaults.conf if(not ulatency.load_rule("../cgroups.conf")) then if(not ulatency.load_rule("../conf/cgroups.conf")) then ulatency.log_error("can't load defaults.conf") end end -- build a list of usefull mountpoints ulatency.mountpoints = {} local function load_mountpoints() local fp = io.open("/proc/mounts") local good = { sysfs=true, debugfs=true } if not fp then ulatency.log_error("/proc/mounts could not be opened") end for line in fp:lines() do local chunks = string.split(line, " ") if good[chunks[3]] then ulatency.mountpoints[chunks[3]] = chunks[2] end end fp:close() end load_mountpoints() if not ulatency.mountpoints["sysfs"] then ulatency.log_error("sysfs is not mounted") end local __CGROUP_HAS = false local __CGROUP_AVAIL = false local __CGROUP_LOADED = {} function ulatency.tree_loaded(name) return __CGROUP_LOADED[name] end --! @brief returns boolean if name is available --! @param name name of subsystem to test function ulatency.has_cgroup_subsystem(name) if not __CGROUP_HAS then ulatency.get_cgroup_subsystems() end return (__CGROUP_HAS[name] == true) end --!@brief returns a table of available cgroup subsystems function ulatency.get_cgroup_subsystems() if __CGROUP_AVAIL then return __CGROUP_AVAIL end __CGROUP_AVAIL = {} __CGROUP_HAS = {} for line in io.lines("/proc/cgroups") do if string.sub(line, 1, 2) ~= "#" then local var = string.gmatch(line, "(%w+)%s+.+")() __CGROUP_AVAIL[#__CGROUP_AVAIL+1] = var __CGROUP_HAS[var] = true end end return __CGROUP_AVAIL end -- reading / writing to /proc/sys function ulatency.get_sysctl(name) local pname = string.gsub(name, "%.", "/") local fp = io.open("/proc/sys/" .. pname) if not fp then return nil end local data = fp:read("*a") fp:close() return data end function ulatency.set_sysctl(name, value) local pname = string.gsub(name, "%.", "/") local fp = io.open("/proc/sys/" .. pname, "w") if not fp then return false end fp:write(value) fp:close() return true end -- CGroups interface if ulatency.get_uid() > 0 or ulatency.get_config("logging", "disable_cgroup") == "true" then ulatency.log_info("disable cgroups error logging. not running as root") function cg_log(...) end else cg_log = ulatency.log_warning end local function mkdirp(path) if posix.access(path) ~= 0 then local parts = path:split("/") for i,v in ipairs(parts) do name = "/" .. table.concat(parts, "/", 1, i) if posix.access(name, posix.R_OK) ~= 0 then if posix.mkdir(name) ~= 0 then cg_log("can't create "..name) return false end end end end return true end local _CGroup_Cache = {} CGroup = {} function CGroup_tostring(data, key) return "" end function CGroup_index(data, key) print("index", data, key) end if string.sub(CGROUP_ROOT, -1) ~= "/" then CGROUP_ROOT = CGROUP_ROOT .. "/" end -- test if a cgroups is mounted local function is_mounted(mnt_pnt) if string.sub(mnt_pnt, #mnt_pnt) == "/" then mnt_pnt = string.sub(mnt_pnt, 1, #mnt_pnt-1) end for line in io.lines("/proc/mounts") do if string.find(line, mnt_pnt) then return true end end return false end -- try mounting the mountpoints if not is_mounted(CGROUP_ROOT) then -- try mounting a tmpfs there mkdirp(CGROUP_ROOT) local prog = "/bin/mount -n -t tmpfs none "..CGROUP_ROOT.."/" ulatency.log_info("mount cgroups root: "..prog) fd = io.popen(prog, "r") print(fd:read("*a")) if not is_mounted(CGROUP_ROOT) then ulatency.log_error("can't mount: "..CGROUP_ROOT) end end -- disable the autogrouping local fp = io.open("/proc/sys/kernel/sched_autogroup_enabled", "w") if fp then ulatency.log_info("disable sched_autogroup in linux kernel") fp:write("0") fp:close() end ulatency.log_info("available cgroup subsystems: "..table.concat(ulatency.get_cgroup_subsystems(), ", ")) local __found_one_group = false for n,v in pairs(CGROUP_MOUNTPOINTS) do local path = CGROUP_ROOT..n local mnt_opts = false for i, subsys in ipairs(v) do if ulatency.has_cgroup_subsystem(subsys) then if mnt_opts then mnt_opts = mnt_opts .. ","..subsys else mnt_opts = subsys end end end if mnt_opts then if is_mounted(path) then ulatency.log_info("mount point "..path.." is already mounted") __CGROUP_LOADED[n] = true __found_one_group = true else mkdirp(path) local prog = "/bin/mount -n -t cgroup -o "..mnt_opts.." none "..path.."/" ulatency.log_info("mount cgroups: "..prog) fd = io.popen(prog, "r") print(fd:read("*a")) if not is_mounted(path) then ulatency.log_error("can't mount: "..path) else __CGROUP_LOADED[n] = true __found_one_group = true end end local fp = io.open(path.."/release_agent", "r") local ragent = fp:read("*a") fp:close() -- we only write a release agent if not already one. update if it looks like -- a ulatencyd release agent if ragent == "" or ragent == "\n" or string.sub(ragent, -22) == '/ulatencyd_cleanup.lua' then local fp = io.open(path.."/release_agent", "w") if fp then fp:write(ulatency.release_agent) fp:close() end end local fp = io.open(path.."/notify_on_release", "w") if fp then fp:write("1") fp:close() end else ulatency.log_info("no cgroups subsystem found for group "..n..". disable group") end end if not __found_one_group then ulatency.log_error("could not found one cgroup to mount.") end __found_one_group = nil CGroupMeta = { __index = CGroup, __tostring = CGroup_tostring} local function cgroups_cleanup() local to_remove = {} for n, c in pairs(_CGroup_Cache) do if c:can_be_removed() then to_remove[#to_remove + 1] = n end end for i,group in ipairs(to_remove) do local needed = false for i, test in ipairs(to_remove) do if string.sub(test, 1, #group) == group then needed = true break end end if not needed then _CGroup_Cache[group]:remove() _CGroup_Cache[group] = nil end end return true end ulatency.add_timeout(cgroups_cleanup, 120000) function CGroup.new(name, init, tree) tree = tree or "cpu" rv = _CGroup_Cache[tree..'/'..name] if rv then return rv end if CGROUP_DEFAULT[tree] then cinit = table.copy(CGROUP_DEFAULT[tree]) else cinit = {} end uncommited=table.merge(cinit, init or {}) rv = setmetatable( {name=name, uncommited=uncommited, new_tasks={}, tree=tree, adjust={}, used=false}, CGroupMeta) _CGroup_Cache[tree..'/'..name] = rv return rv end function CGroup.get_groups() return _CGroup_Cache end function CGroup.get_group(name) return _CGroup_Cache[name] end function CGroup:path(file) if file then return CGROUP_ROOT .. self.tree .. "/".. self.name .. "/" .. tostring(file) else return CGROUP_ROOT .. self.tree .. "/" .. self.name end end function CGroup:path_parts() return self.name:split("/") end function CGroup:parent() parts = self.name:split("/") name = table.concat(parts, "/", 1, #parts-1) if _CGroup_Cache[name] then return _CGroup_Cache[name] end return CGroup.new(name) end function CGroup:get_value(key, raw) uncommited = rawget(self, "uncommited") if uncommited[key] and not raw then return uncommited[key] end local path = self:path(key) if posix.access(path) == 0 then local fp = io.open(path, "r") return fp:read("*a") end end function CGroup:set_value(key, value) uncommited = rawget(self, "uncommited") uncommited[key] = value end function CGroup:get_tasks() local t_file = self:path("tasks") if posix.access(t_file, posix.R_OK) ~= 0 then return {} end rv = {} for line in io.lines(t_file) do rv[#rv+1] = tonumber(line) end return rv end function CGroup:has_tasks() local rv = false local t_file = self:path("tasks") if posix.access(t_file, posix.R_OK) ~= 0 then return false end for line in io.lines(t_file) do rv = true break end return rv end function CGroup:run_adjust(proc) adjust = rawget(self, "adjust") for i,v in ipairs(adjust) do v(self, proc) end end --function CGroup:adjust() -- return rawget(self, "adjust") --end function CGroup:add_task_list(pid, tasks, instant) local nt = rawget(self, "new_tasks") if not nt then nt = {} rawset(self, "new_tasks", nt) end for i,v in ipairs(tasks) do nt[#nt+1] = v end if instant then local t_file = self:path("tasks") fp = io.open(t_file, "w") if fp then for i,v in tasks do fp:write(tostring(v)..'\n') fp:flush() end ulatency.log_sched("Move "..pid.." to "..tostring(self).." tasks: "..table.concat(tasks, ",")) fp:close() else cg_log("can't attach "..pid.." to group "..t_file) end end end function CGroup:add_task(pid, instant) if instant then return self:add_task_list(pid, {pid}, instant) else local nt = rawget(self, "new_tasks") if not nt then nt = {} rawset(self, "new_tasks", nt) end nt[#nt+1] = pid return end end function CGroup:is_dirty() if #rawget(self, "uncommited") > 0 or #rawget(self, "new_tasks") > 0 or posix.access(self:path()) ~= 0 then return true end return false end function CGroup:exists() if posix.access(self:path()) == 0 then return true end if #rawget(self, "uncommited") > 0 or #rawget(self, "new_tasks") > 0 then return true end return false end function CGroup:can_be_removed() if self:has_tasks() or #rawget(self, "new_tasks") > 0 then return false end return true end function CGroup:remove() posix.rmdir(self:path()) end function CGroup:commit() mkdirp(self:path()) local uncommited = rawget(self, "uncommited") for k, v in pairs(uncommited) do local par = string.sub(k, 1, 1) if par == '?' then k = string.sub(k, 2) else par = nil end local path = self:path(k) local fp = io.open(path, "w") if fp then --print("write"..path) fp:write(v) fp:close() uncommited[k] = nil else if par ~= '?' then cg_log("can't write into :"..tostring(path)) end end end local t_file = self:path("tasks") local fp = io.open(t_file, "w") if fp then local pids = rawget(self, "new_tasks") if pids then for i, pid in ipairs(pids) do fp:write(tostring(pid)..'\n') fp:flush() end ulatency.log_sched("Move to "..tostring(self).." tasks: "..table.concat(pids, ",")) rawset(self, "new_tasks", {}) end fp:close() end end function CGroup:add_children(proc, fnc) function add_childs(list) for i,v in pairs(list) do self:add_task(v.pid) if fnc then fnc(v) end end for i,v in pairs(list) do add_childs(v:get_children()) end end add_childs(proc:get_children()) end function CGroup.create_isolation_group(proc, children, fnc) ng = CGroup.new("iso_"..tostring(pid)) ng:commit() ng:add_task(proc.pid) proc:set_block_scheduler(1) if children then ng:add_children(proc, fnc) end return ng end function CGroup:starve(what) if what == "memory" then nv = self:get_value("memory.usage_in_bytes", true) if nv then self:set_value("memory.limit_in_bytes", nv) end end end -- helper classes function to_string(data, indent) local str = "" if(indent == nil) then indent = 0 end -- Check the type if(type(data) == "string") then str = str .. (" "):rep(indent) .. data .. "\n" elseif(type(data) == "number") then str = str .. (" "):rep(indent) .. data .. "\n" elseif(type(data) == "boolean") then if(data == true) then str = str .. "true" else str = str .. "false" end elseif(type(data) == "table") then local i, v for i, v in pairs(data) do -- Check for a table in a table if(type(v) == "table") then str = str .. (" "):rep(indent) .. i .. ":\n" str = str .. to_string(v, indent + 2) else str = str .. (" "):rep(indent) .. i .. ": " .. to_string(v, 0) end end else return tostring(data).."\n" end return str end function pprint(data) print(to_string(data)) end function num_or_percent(conf, value, default) local rv = false if not conf and default then conf = default end if not conf then conf = "100%" end for w in string.gmatch(conf, "(%d+)%%") do return ((value)/100)*tonumber(w) end if not conf then return value end return conf end poelzi-ulatencyd-55515a9/src/coreutils/000077500000000000000000000000001154664534000200725ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/src/coreutils/readutmp.c000066400000000000000000000101271154664534000220600ustar00rootroot00000000000000/* GNU's read utmp module. Copyright (C) 1992-2001, 2003-2006, 2009-2010 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* Written by jla; revised by djm */ #include "config.h" #include "xalloc.h" #include "readutmp.h" #include #include #include #include #include #include #include #include #include #include #if USE_UNLOCKED_IO # include "unlocked-io.h" #endif /* Copy UT->ut_name into storage obtained from malloc. Then remove any trailing spaces from the copy, NUL terminate it, and return the copy. */ char * extract_trimmed_name (const STRUCT_UTMP *ut) { char *p, *trimmed_name; trimmed_name = malloc (sizeof (UT_USER (ut)) + 1); strncpy (trimmed_name, UT_USER (ut), sizeof (UT_USER (ut))); /* Append a trailing NUL. Some systems pad names shorter than the maximum with spaces, others pad with NULs. Remove any trailing spaces. */ trimmed_name[sizeof (UT_USER (ut))] = '\0'; for (p = trimmed_name + strlen (trimmed_name); trimmed_name < p && p[-1] == ' '; *--p = '\0') continue; return trimmed_name; } /* Is the utmp entry U desired by the user who asked for OPTIONS? */ static inline bool desirable_utmp_entry (STRUCT_UTMP const *u, int options) { bool user_proc = IS_USER_PROCESS (u); if ((options & READ_UTMP_USER_PROCESS) && !user_proc) return false; if ((options & READ_UTMP_CHECK_PIDS) && user_proc && (UT_PID (u) <= 0 || (kill (UT_PID (u), 0) < 0 && errno == ESRCH))) return false; return true; } /* Read the utmp entries corresponding to file FILE into freshly- malloc'd storage, set *UTMP_BUF to that pointer, set *N_ENTRIES to the number of entries, and return zero. If there is any error, return -1, setting errno, and don't modify the parameters. If OPTIONS & READ_UTMP_CHECK_PIDS is nonzero, omit entries whose process-IDs do not currently exist. */ #ifdef UTMP_NAME_FUNCTION int read_utmp (char const *file, size_t *n_entries, STRUCT_UTMP **utmp_buf, int options) { size_t n_read = 0; size_t n_alloc = 0; STRUCT_UTMP *utmp = NULL; STRUCT_UTMP *u; /* Ignore the return value for now. Solaris' utmpname returns 1 upon success -- which is contrary to what the GNU libc version does. In addition, older GNU libc versions are actually void. */ UTMP_NAME_FUNCTION (file); SET_UTMP_ENT (); while ((u = GET_UTMP_ENT ()) != NULL) if (desirable_utmp_entry (u, options)) { if (n_read == n_alloc) utmp = x2nrealloc (utmp, &n_alloc, sizeof *utmp); utmp[n_read++] = *u; } END_UTMP_ENT (); *n_entries = n_read; *utmp_buf = utmp; return 0; } #else int read_utmp (char const *file, size_t *n_entries, STRUCT_UTMP **utmp_buf, int options) { size_t n_read = 0; size_t n_alloc = 0; STRUCT_UTMP *utmp = NULL; int saved_errno; FILE *f = fopen (file, "r"); if (! f) return -1; for (;;) { if (n_read == n_alloc) utmp = x2nrealloc (utmp, &n_alloc, sizeof *utmp); if (fread (&utmp[n_read], sizeof utmp[n_read], 1, f) == 0) break; n_read += desirable_utmp_entry (&utmp[n_read], options); } saved_errno = ferror (f) ? errno : 0; if (fclose (f) != 0) saved_errno = errno; if (saved_errno != 0) { free (utmp); errno = saved_errno; return -1; } *n_entries = n_read; *utmp_buf = utmp; return 0; } #endif poelzi-ulatencyd-55515a9/src/coreutils/readutmp.h000066400000000000000000000133451154664534000220720ustar00rootroot00000000000000/* Declarations for GNU's read utmp module. Copyright (C) 1992-2007, 2009-2010 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* Written by jla; revised by djm */ #ifndef __READUTMP_H__ # define __READUTMP_H__ # include /* AIX 4.3.3 has both utmp.h and utmpx.h, but only struct utmp has the ut_exit member. */ # if (HAVE_UTMPX_H && HAVE_UTMP_H && HAVE_STRUCT_UTMP_UT_EXIT \ && ! HAVE_STRUCT_UTMPX_UT_EXIT) # undef HAVE_UTMPX_H # endif # if HAVE_UTMPX_H # if HAVE_UTMP_H /* HPUX 10.20 needs utmp.h, for the definition of e.g., UTMP_FILE. */ # include # endif # if defined _THREAD_SAFE && defined UTMP_DATA_INIT /* When including both utmp.h and utmpx.h on AIX 4.3, with _THREAD_SAFE defined, work around the duplicate struct utmp_data declaration. */ # define utmp_data gl_aix_4_3_workaround_utmp_data # endif # include # define UTMP_STRUCT_NAME utmpx # define UT_TIME_MEMBER(UT_PTR) ((UT_PTR)->ut_tv.tv_sec) # define SET_UTMP_ENT setutxent # define GET_UTMP_ENT getutxent # define END_UTMP_ENT endutxent # ifdef HAVE_UTMPXNAME # define UTMP_NAME_FUNCTION utmpxname # endif # if HAVE_STRUCT_UTMPX_UT_EXIT_E_TERMINATION # define UT_EXIT_E_TERMINATION(U) ((U)->ut_exit.e_termination) # else # if HAVE_STRUCT_UTMPX_UT_EXIT_UT_TERMINATION # define UT_EXIT_E_TERMINATION(U) ((U)->ut_exit.ut_termination) # else # define UT_EXIT_E_TERMINATION(U) 0 # endif # endif # if HAVE_STRUCT_UTMPX_UT_EXIT_E_EXIT # define UT_EXIT_E_EXIT(U) ((U)->ut_exit.e_exit) # else # if HAVE_STRUCT_UTMPX_UT_EXIT_UT_EXIT # define UT_EXIT_E_EXIT(U) ((U)->ut_exit.ut_exit) # else # define UT_EXIT_E_EXIT(U) 0 # endif # endif # elif HAVE_UTMP_H # include # if !HAVE_DECL_GETUTENT struct utmp *getutent (void); # endif # define UTMP_STRUCT_NAME utmp # define UT_TIME_MEMBER(UT_PTR) ((UT_PTR)->ut_time) # define SET_UTMP_ENT setutent # define GET_UTMP_ENT getutent # define END_UTMP_ENT endutent # ifdef HAVE_UTMPNAME # define UTMP_NAME_FUNCTION utmpname # endif # if HAVE_STRUCT_UTMP_UT_EXIT_E_TERMINATION # define UT_EXIT_E_TERMINATION(U) ((U)->ut_exit.e_termination) # else # if HAVE_STRUCT_UTMP_UT_EXIT_UT_TERMINATION # define UT_EXIT_E_TERMINATION(U) ((U)->ut_exit.ut_termination) # else # define UT_EXIT_E_TERMINATION(U) 0 # endif # endif # if HAVE_STRUCT_UTMP_UT_EXIT_E_EXIT # define UT_EXIT_E_EXIT(U) ((U)->ut_exit.e_exit) # else # if HAVE_STRUCT_UTMP_UT_EXIT_UT_EXIT # define UT_EXIT_E_EXIT(U) ((U)->ut_exit.ut_exit) # else # define UT_EXIT_E_EXIT(U) 0 # endif # endif # endif /* Accessor macro for the member named ut_user or ut_name. */ # if HAVE_UTMPX_H # if HAVE_STRUCT_UTMPX_UT_USER # define UT_USER(Utmp) ((Utmp)->ut_user) # endif # if HAVE_STRUCT_UTMPX_UT_NAME # undef UT_USER # define UT_USER(Utmp) ((Utmp)->ut_name) # endif # elif HAVE_UTMP_H # if HAVE_STRUCT_UTMP_UT_USER # define UT_USER(Utmp) ((Utmp)->ut_user) # endif # if HAVE_STRUCT_UTMP_UT_NAME # undef UT_USER # define UT_USER(Utmp) ((Utmp)->ut_name) # endif # endif # define HAVE_STRUCT_XTMP_UT_EXIT \ (HAVE_STRUCT_UTMP_UT_EXIT \ || HAVE_STRUCT_UTMPX_UT_EXIT) # define HAVE_STRUCT_XTMP_UT_ID \ (HAVE_STRUCT_UTMP_UT_ID \ || HAVE_STRUCT_UTMPX_UT_ID) # define HAVE_STRUCT_XTMP_UT_PID \ (HAVE_STRUCT_UTMP_UT_PID \ || HAVE_STRUCT_UTMPX_UT_PID) typedef struct UTMP_STRUCT_NAME STRUCT_UTMP; enum { UT_USER_SIZE = sizeof UT_USER ((STRUCT_UTMP *) 0) }; # if !defined UTMP_FILE && defined _PATH_UTMP # define UTMP_FILE _PATH_UTMP # endif # if !defined WTMP_FILE && defined _PATH_WTMP # define WTMP_FILE _PATH_WTMP # endif # ifdef UTMPX_FILE /* Solaris, SysVr4 */ # undef UTMP_FILE # define UTMP_FILE UTMPX_FILE # endif # ifdef WTMPX_FILE /* Solaris, SysVr4 */ # undef WTMP_FILE # define WTMP_FILE WTMPX_FILE # endif # ifndef UTMP_FILE # define UTMP_FILE "/etc/utmp" # endif # ifndef WTMP_FILE # define WTMP_FILE "/etc/wtmp" # endif # if HAVE_STRUCT_XTMP_UT_PID # define UT_PID(U) ((U)->ut_pid) # else # define UT_PID(U) 0 # endif # if HAVE_STRUCT_UTMP_UT_TYPE || HAVE_STRUCT_UTMPX_UT_TYPE # define UT_TYPE_EQ(U, V) ((U)->ut_type == (V)) # define UT_TYPE_NOT_DEFINED 0 # else # define UT_TYPE_EQ(U, V) 0 # define UT_TYPE_NOT_DEFINED 1 # endif # ifdef BOOT_TIME # define UT_TYPE_BOOT_TIME(U) UT_TYPE_EQ (U, BOOT_TIME) # else # define UT_TYPE_BOOT_TIME(U) 0 # endif # ifdef USER_PROCESS # define UT_TYPE_USER_PROCESS(U) UT_TYPE_EQ (U, USER_PROCESS) # else # define UT_TYPE_USER_PROCESS(U) 0 # endif # define IS_USER_PROCESS(U) \ (UT_USER (U)[0] \ && (UT_TYPE_USER_PROCESS (U) \ || (UT_TYPE_NOT_DEFINED && UT_TIME_MEMBER (U) != 0))) /* Options for read_utmp. */ enum { READ_UTMP_CHECK_PIDS = 1, READ_UTMP_USER_PROCESS = 2 }; char *extract_trimmed_name (const STRUCT_UTMP *ut); int read_utmp (char const *file, size_t *n_entries, STRUCT_UTMP **utmp_buf, int options); #ifdef UTMP_NAME_FUNCTION int UTMP_NAME_FUNCTION(char const *file); #endif #endif /* __READUTMP_H__ */ poelzi-ulatencyd-55515a9/src/coreutils/xalloc-die.c000066400000000000000000000024741154664534000222660ustar00rootroot00000000000000/* Report a memory allocation failure and exit. Copyright (C) 1997, 1998, 1999, 2000, 2002, 2003, 2004, 2006, 2009, 2010 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "xalloc.h" #include #include "error.h" //#include "exitfail.h" //#include "gettext.h" #define _(msgid) gettext (msgid) void xalloc_die (void) { //error (exit_failure, 0, "%s", _("memory exhausted")); /* The `noreturn' cannot be given to error, since it may return if its first argument is 0. To help compilers understand the xalloc_die does not return, call abort. Also, the abort is a safety feature if exit_failure is 0 (which shouldn't happen). */ abort (); } poelzi-ulatencyd-55515a9/src/coreutils/xalloc.h000066400000000000000000000201651154664534000215310ustar00rootroot00000000000000/* xalloc.h -- malloc with out-of-memory checking Copyright (C) 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2003, 2004, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef XALLOC_H_ # define XALLOC_H_ # include # ifdef __cplusplus extern "C" { # endif # ifndef __attribute__ # if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) # define __attribute__(x) # endif # endif # ifndef ATTRIBUTE_NORETURN # define ATTRIBUTE_NORETURN __attribute__ ((__noreturn__)) # endif # ifndef ATTRIBUTE_MALLOC # if __GNUC__ >= 3 # define ATTRIBUTE_MALLOC __attribute__ ((__malloc__)) # else # define ATTRIBUTE_MALLOC # endif # endif /* This function is always triggered when memory is exhausted. It must be defined by the application, either explicitly or by using gnulib's xalloc-die module. This is the function to call when one wants the program to die because of a memory allocation failure. */ extern void xalloc_die (void) ATTRIBUTE_NORETURN; void *xmalloc (size_t s) ATTRIBUTE_MALLOC; void *xzalloc (size_t s) ATTRIBUTE_MALLOC; void *xcalloc (size_t n, size_t s) ATTRIBUTE_MALLOC; void *xrealloc (void *p, size_t s); void *x2realloc (void *p, size_t *pn); void *xmemdup (void const *p, size_t s) ATTRIBUTE_MALLOC; char *xstrdup (char const *str) ATTRIBUTE_MALLOC; /* Return 1 if an array of N objects, each of size S, cannot exist due to size arithmetic overflow. S must be positive and N must be nonnegative. This is a macro, not an inline function, so that it works correctly even when SIZE_MAX < N. By gnulib convention, SIZE_MAX represents overflow in size calculations, so the conservative dividend to use here is SIZE_MAX - 1, since SIZE_MAX might represent an overflowed value. However, malloc (SIZE_MAX) fails on all known hosts where sizeof (ptrdiff_t) <= sizeof (size_t), so do not bother to test for exactly-SIZE_MAX allocations on such hosts; this avoids a test and branch when S is known to be 1. */ # define xalloc_oversized(n, s) \ ((size_t) (sizeof (ptrdiff_t) <= sizeof (size_t) ? -1 : -2) / (s) < (n)) /* In the following macros, T must be an elementary or structure/union or typedef'ed type, or a pointer to such a type. To apply one of the following macros to a function pointer or array type, you need to typedef it first and use the typedef name. */ /* Allocate an object of type T dynamically, with error checking. */ /* extern t *XMALLOC (typename t); */ # define XMALLOC(t) ((t *) xmalloc (sizeof (t))) /* Allocate memory for N elements of type T, with error checking. */ /* extern t *XNMALLOC (size_t n, typename t); */ # define XNMALLOC(n, t) \ ((t *) (sizeof (t) == 1 ? xmalloc (n) : xnmalloc (n, sizeof (t)))) /* Allocate an object of type T dynamically, with error checking, and zero it. */ /* extern t *XZALLOC (typename t); */ # define XZALLOC(t) ((t *) xzalloc (sizeof (t))) /* Allocate memory for N elements of type T, with error checking, and zero it. */ /* extern t *XCALLOC (size_t n, typename t); */ # define XCALLOC(n, t) \ ((t *) (sizeof (t) == 1 ? xzalloc (n) : xcalloc (n, sizeof (t)))) # if HAVE_INLINE # define static_inline static inline # else void *xnmalloc (size_t n, size_t s) ATTRIBUTE_MALLOC; void *xnrealloc (void *p, size_t n, size_t s); void *x2nrealloc (void *p, size_t *pn, size_t s); char *xcharalloc (size_t n) ATTRIBUTE_MALLOC; # endif # ifdef static_inline /* Allocate an array of N objects, each with S bytes of memory, dynamically, with error checking. S must be nonzero. */ static_inline void *xnmalloc (size_t n, size_t s) ATTRIBUTE_MALLOC; static_inline void * xnmalloc (size_t n, size_t s) { if (xalloc_oversized (n, s)) xalloc_die (); return xmalloc (n * s); } /* Change the size of an allocated block of memory P to an array of N objects each of S bytes, with error checking. S must be nonzero. */ static_inline void * xnrealloc (void *p, size_t n, size_t s) { if (xalloc_oversized (n, s)) xalloc_die (); return xrealloc (p, n * s); } /* If P is null, allocate a block of at least *PN such objects; otherwise, reallocate P so that it contains more than *PN objects each of S bytes. *PN must be nonzero unless P is null, and S must be nonzero. Set *PN to the new number of objects, and return the pointer to the new block. *PN is never set to zero, and the returned pointer is never null. Repeated reallocations are guaranteed to make progress, either by allocating an initial block with a nonzero size, or by allocating a larger block. In the following implementation, nonzero sizes are increased by a factor of approximately 1.5 so that repeated reallocations have O(N) overall cost rather than O(N**2) cost, but the specification for this function does not guarantee that rate. Here is an example of use: int *p = NULL; size_t used = 0; size_t allocated = 0; void append_int (int value) { if (used == allocated) p = x2nrealloc (p, &allocated, sizeof *p); p[used++] = value; } This causes x2nrealloc to allocate a block of some nonzero size the first time it is called. To have finer-grained control over the initial size, set *PN to a nonzero value before calling this function with P == NULL. For example: int *p = NULL; size_t used = 0; size_t allocated = 0; size_t allocated1 = 1000; void append_int (int value) { if (used == allocated) { p = x2nrealloc (p, &allocated1, sizeof *p); allocated = allocated1; } p[used++] = value; } */ static_inline void * x2nrealloc (void *p, size_t *pn, size_t s) { size_t n = *pn; if (! p) { if (! n) { /* The approximate size to use for initial small allocation requests, when the invoking code specifies an old size of zero. 64 bytes is the largest "small" request for the GNU C library malloc. */ enum { DEFAULT_MXFAST = 64 }; n = DEFAULT_MXFAST / s; n += !n; } } else { /* Set N = ceil (1.5 * N) so that progress is made if N == 1. Check for overflow, so that N * S stays in size_t range. The check is slightly conservative, but an exact check isn't worth the trouble. */ if ((size_t) -1 / 3 * 2 / s <= n) xalloc_die (); n += (n + 1) / 2; } *pn = n; return xrealloc (p, n * s); } /* Return a pointer to a new buffer of N bytes. This is like xmalloc, except it returns char *. */ static_inline char *xcharalloc (size_t n) ATTRIBUTE_MALLOC; static_inline char * xcharalloc (size_t n) { return XNMALLOC (n, char); } # endif # ifdef __cplusplus } /* C++ does not allow conversions from void * to other pointer types without a cast. Use templates to work around the problem when possible. */ template inline T * xrealloc (T *p, size_t s) { return (T *) xrealloc ((void *) p, s); } template inline T * xnrealloc (T *p, size_t n, size_t s) { return (T *) xnrealloc ((void *) p, n, s); } template inline T * x2realloc (T *p, size_t *pn) { return (T *) x2realloc ((void *) p, pn); } template inline T * x2nrealloc (T *p, size_t *pn, size_t s) { return (T *) x2nrealloc ((void *) p, pn, s); } template inline T * xmemdup (T const *p, size_t s) { return (T *) xmemdup ((void const *) p, s); } # endif #endif /* !XALLOC_H_ */ poelzi-ulatencyd-55515a9/src/coreutils/xmalloc.c000066400000000000000000000063221154664534000217000ustar00rootroot00000000000000/* xmalloc.c -- malloc with out of memory checking Copyright (C) 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2002, 2003, 2004, 2005, 2006, 2008, 2009, 2010 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #if ! HAVE_INLINE # define static_inline #endif #include "xalloc.h" #undef static_inline #include #include /* 1 if calloc is known to be compatible with GNU calloc. This matters if we are not also using the calloc module, which defines HAVE_CALLOC and supports the GNU API even on non-GNU platforms. */ #if defined HAVE_CALLOC || defined __GLIBC__ enum { HAVE_GNU_CALLOC = 1 }; #else enum { HAVE_GNU_CALLOC = 0 }; #endif /* Allocate N bytes of memory dynamically, with error checking. */ void * xmalloc (size_t n) { void *p = malloc (n); if (!p && n != 0) xalloc_die (); return p; } /* Change the size of an allocated block of memory P to N bytes, with error checking. */ void * xrealloc (void *p, size_t n) { p = realloc (p, n); if (!p && n != 0) xalloc_die (); return p; } /* If P is null, allocate a block of at least *PN bytes; otherwise, reallocate P so that it contains more than *PN bytes. *PN must be nonzero unless P is null. Set *PN to the new block's size, and return the pointer to the new block. *PN is never set to zero, and the returned pointer is never null. */ void * x2realloc (void *p, size_t *pn) { return x2nrealloc (p, pn, 1); } /* Allocate S bytes of zeroed memory dynamically, with error checking. There's no need for xnzalloc (N, S), since it would be equivalent to xcalloc (N, S). */ void * xzalloc (size_t s) { return memset (xmalloc (s), 0, s); } /* Allocate zeroed memory for N elements of S bytes, with error checking. S must be nonzero. */ void * xcalloc (size_t n, size_t s) { void *p; /* Test for overflow, since some calloc implementations don't have proper overflow checks. But omit overflow and size-zero tests if HAVE_GNU_CALLOC, since GNU calloc catches overflow and never returns NULL if successful. */ if ((! HAVE_GNU_CALLOC && xalloc_oversized (n, s)) || (! (p = calloc (n, s)) && (HAVE_GNU_CALLOC || n != 0))) xalloc_die (); return p; } /* Clone an object P of size S, with error checking. There's no need for xnmemdup (P, N, S), since xmemdup (P, N * S) works without any need for an arithmetic overflow check. */ void * xmemdup (void const *p, size_t s) { return memcpy (xmalloc (s), p, s); } /* Clone STRING. */ char * xstrdup (char const *string) { return xmemdup (string, strlen (string) + 1); } poelzi-ulatencyd-55515a9/src/dbus.c000066400000000000000000000662461154664534000172000ustar00rootroot00000000000000/* Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. */ #include "config.h" #include "ulatency.h" #include #include #include #include #include #include #include //char INTRO[] = { //#include "myfile.txt" //} #define INTROSPECT \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" const char *INTROSPECT_XML_USER = \ "\n" DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE "\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" //" \n" //" \n" //" \n" //" \n" " \n" " \n" INTROSPECT "\n"; const char *INTROSPECT_XML_SYSTEM = \ "\n" DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE "\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" INTROSPECT "\n"; // pointer for flags as source const void *U_DBUS_POINTER = &INTROSPECT_XML_SYSTEM; #define U_DBUS_ERROR_NO_PID "org.quamquam.ulatency.DBus.Error.PidNotFound" #define GET_CALLER() \ if ((caller = dbus_bus_get_unix_user(c, dbus_message_get_sender(m), &error)) == (unsigned long) -1) { \ g_warning("dbus_message_get_unix_user() failed: %s\n", error.message); \ ret = dbus_message_new_error(m, DBUS_ERROR_ACCESS_DENIED, "not a local connection"); \ goto finish; \ } #define PUSH_ERROR(ID, MSG) \ { if(error.name) { \ ret = dbus_message_new_error(m, error.name, error.message); \ } else { \ ret = dbus_message_new_error(m, ID, #MSG); } \ goto finish; \ } static DBusHandlerResult dbus_user_handler(DBusConnection *c, DBusMessage *m, void *userdata) { DBusError error; DBusMessage *ret = NULL; uid_t caller; int is2 = 0; dbus_error_init(&error); if(dbus_message_is_method_call(m, U_DBUS_USER_INTERFACE, "setActive") || (is2 = dbus_message_is_method_call(m, U_DBUS_USER_INTERFACE, "setActiveWithUser"))) { uid_t uid; pid_t pid; uint64_t tmpu; uint64_t tmpp; GET_CALLER() if(is2) { if(!dbus_message_get_args(m, &error, DBUS_TYPE_UINT64, &tmpu, DBUS_TYPE_UINT64, &tmpp, DBUS_TYPE_INVALID)) PUSH_ERROR(DBUS_ERROR_INVALID_ARGS, "wrong arguments") uid = (uid_t)tmpu; } else { if(!dbus_message_get_args(m, &error, DBUS_TYPE_UINT64, &tmpp, DBUS_TYPE_INVALID)) PUSH_ERROR(DBUS_ERROR_INVALID_ARGS, "wrong arguments") uid = (pid_t)caller; } pid = (pid_t)tmpp; if(caller != 0 && caller != uid) { ret = dbus_message_new_error(m, DBUS_ERROR_ACCESS_DENIED, "not allowed to set aktive pids of foreign users"); goto finish; } set_active_pid(uid, pid); ret = dbus_message_new_method_return(m); goto finish; } else if (dbus_message_is_method_call(m, U_DBUS_USER_INTERFACE, "setActiveControl")) { gboolean enable; struct user_active *ua; if(!dbus_message_get_args(m, &error, DBUS_TYPE_BOOLEAN, &enable, DBUS_TYPE_INVALID)) PUSH_ERROR(DBUS_ERROR_INVALID_ARGS, "wrong arguments") GET_CALLER() ua = get_userlist(caller, TRUE); if(enable) ua->active_agent = USER_ACTIVE_AGENT_DBUS; else ua->active_agent = USER_ACTIVE_AGENT_NONE; ret = dbus_message_new_method_return(m); goto finish; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Get")) { const char *interface, *property; struct user_active *ua; if (!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) { g_warning("Failed to parse property get call: %s\n", error.message); ret = dbus_message_new_error(m, error.name, error.message); goto finish; } if (g_strcmp0(interface, U_DBUS_USER_INTERFACE) == 0) { GET_CALLER() ua = get_userlist(caller, TRUE); ret = dbus_message_new_method_return(m); if(g_strcmp0(property, "activeListLength") == 0) { dbus_message_append_args (ret, DBUS_TYPE_UINT32, &ua->max_processes, DBUS_TYPE_INVALID); goto finish; } dbus_message_unref(ret); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } else { return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Set")) { const char *interface, *property; struct user_active *ua; DBusMessageIter imsg; if (!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID) || !dbus_message_iter_init (m, &imsg)) { g_warning("Failed to parse property set call: %s\n", error.message); ret = dbus_message_new_error(m, error.name, error.message); goto finish; } dbus_message_iter_next(&imsg); dbus_message_iter_next(&imsg); if (g_strcmp0(interface, U_DBUS_USER_INTERFACE) == 0) { GET_CALLER() ua = get_userlist(caller, TRUE); ret = dbus_message_new_method_return(m); if(g_strcmp0(property, "activeList") == 0) { if(!dbus_message_iter_get_arg_type(&imsg) == DBUS_TYPE_UINT32) goto error; dbus_message_iter_get_basic (&imsg, &ua->max_processes); goto finish; } dbus_message_unref(ret); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } else { return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) { ret = dbus_message_new_method_return(m); dbus_message_append_args(ret, DBUS_TYPE_STRING, &INTROSPECT_XML_USER, DBUS_TYPE_INVALID); goto finish; } else return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; error: if(ret) dbus_message_unref(ret); ret = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS , "wrong arguments"); finish: if (ret) { dbus_connection_send(c, ret, NULL); dbus_message_unref(ret); } dbus_error_free(&error); return DBUS_HANDLER_RESULT_HANDLED; }; static void push_flag(DBusMessage *ret, u_proc *proc, int recrusive) { GList *cur, *lst; u_flag *fl; char *name = NULL; uint32_t tu32; DBusMessageIter imsg, array, strukt, dict, entry, value; dbus_message_iter_init_append(ret, &imsg); if(proc) { lst = u_proc_list_flags(proc, recrusive); cur = lst; dbus_message_iter_open_container(&imsg, DBUS_TYPE_ARRAY, "(ta{sv})" , &array); } else { cur = g_list_first(system_flags); dbus_message_iter_open_container(&imsg, DBUS_TYPE_ARRAY, "a{sv}" , &array); } #define PUSH_VARIANT(NAME, VAR, TYPE) {\ name = #NAME ; \ dbus_message_iter_open_container(&entry, DBUS_TYPE_DICT_ENTRY, NULL, &dict); \ dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &name); \ dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, TYPE##_AS_STRING, &value); \ dbus_message_iter_append_basic(&value, TYPE, VAR); \ dbus_message_iter_close_container(&dict, &value); \ dbus_message_iter_close_container(&entry, &dict); } while(cur) { fl = cur->data; uint64_t flp = (uint64_t)fl; uint64_t tid = (uint64_t)fl->tid; if(proc) { dbus_message_iter_open_container(&array, DBUS_TYPE_STRUCT, NULL, &strukt); dbus_message_iter_append_basic(&strukt, DBUS_TYPE_UINT64, &flp); dbus_message_iter_open_container(&strukt, DBUS_TYPE_ARRAY, "{sv}", &entry); } else { dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY, "{sv}", &entry); } PUSH_VARIANT(name, &fl->name, DBUS_TYPE_STRING); PUSH_VARIANT(tid, &tid, DBUS_TYPE_UINT64); if(fl->reason) PUSH_VARIANT(reason, &fl->reason, DBUS_TYPE_STRING); tu32 = (uint32_t)fl->timeout; PUSH_VARIANT(timeout, &tu32, DBUS_TYPE_UINT32); PUSH_VARIANT(priority, &fl->priority, DBUS_TYPE_INT32); PUSH_VARIANT(value, &fl->value, DBUS_TYPE_INT64); PUSH_VARIANT(threshold, &fl->threshold, DBUS_TYPE_INT64); tu32 = (uint32_t)fl->inherit; PUSH_VARIANT(inherit, &tu32, DBUS_TYPE_BOOLEAN); if(proc) { dbus_message_iter_close_container(&strukt, &entry); dbus_message_iter_close_container(&array, &strukt); DEC_REF(fl); } else { dbus_message_iter_close_container(&array, &entry); } cur = g_list_next(cur); } #undef PUSH_VARIANT dbus_message_iter_close_container(&imsg, &array); if(proc) g_list_free(lst); return; } static void set_config_callback(struct callback_data *data) { DBusMessage *ret = dbus_message_new_method_return(data->message); u_scheduler *sched = scheduler_get(); g_message("DBUS: setSchedulerConfig(\"%s\") executed", (char *)data->user_data); dbus_bool_t rv = (dbus_bool_t)sched->set_config((char *)data->user_data); dbus_message_append_args (ret, DBUS_TYPE_BOOLEAN, &rv, DBUS_TYPE_INVALID); dbus_connection_send(data->connection, ret, NULL); dbus_message_unref(ret); } static DBusHandlerResult dbus_system_handler(DBusConnection *c, DBusMessage *m, void *userdata) { DBusError error; DBusMessage *ret = NULL; DBusMessageIter imsg; uid_t caller; uint64_t tu64; int is2 = 0; dbus_error_init(&error); if(dbus_message_is_method_call(m, U_DBUS_SYSTEM_INTERFACE, "listSystemFlags")) { ret = dbus_message_new_method_return(m); push_flag(ret, NULL, FALSE); goto finish; } else if(dbus_message_is_method_call(m, U_DBUS_SYSTEM_INTERFACE, "listFlags")) { u_proc *proc; pid_t pid; uint32_t recrusive; if (!dbus_message_get_args(m, &error, DBUS_TYPE_UINT64, &tu64, DBUS_TYPE_BOOLEAN,&recrusive, DBUS_TYPE_INVALID) || !dbus_message_iter_init (m, &imsg)) PUSH_ERROR(DBUS_ERROR_INVALID_ARGS, "wrong arguments") pid = (pid_t)tu64; proc = proc_by_pid_with_retry(pid); if(!proc) PUSH_ERROR(DBUS_ERROR_INVALID_ARGS, "wrong arguments") ret = dbus_message_new_method_return(m); push_flag(ret, proc, recrusive); goto finish; } else if(dbus_message_is_method_call(m, U_DBUS_SYSTEM_INTERFACE, "addFlag")) { u_proc *proc; uint64_t tpid, ttid, timeout; int64_t priority, value, threshold; uint32_t inherit; u_flag *flag; char *name, *reason; if (!dbus_message_get_args(m, &error, DBUS_TYPE_UINT64, &tpid, DBUS_TYPE_UINT64, &ttid, DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &reason, DBUS_TYPE_UINT64, &timeout, DBUS_TYPE_INT32, &priority, DBUS_TYPE_INT64, &value, DBUS_TYPE_INT64, &threshold, DBUS_TYPE_BOOLEAN,&inherit, DBUS_TYPE_INVALID) || !dbus_message_iter_init (m, &imsg)) PUSH_ERROR(DBUS_ERROR_INVALID_ARGS, "wrong arguments") pid_t pid = (pid_t)tpid; pid_t tid = (pid_t)ttid; proc = proc_by_pid_with_retry(pid); GET_CALLER() if(!proc) PUSH_ERROR(U_DBUS_ERROR_NO_PID, "wrong arguments") if(caller != 0 && caller != proc->proc.euid) PUSH_ERROR(DBUS_ERROR_ACCESS_DENIED, "access denied") flag = u_flag_new((void *)U_DBUS_POINTER, name); flag->reason = reason; flag->tid = tid; flag->timeout = timeout; flag->priority = priority; flag->value = value; flag->threshold = threshold; flag->inherit = inherit; u_flag_add(proc, flag); ret = dbus_message_new_method_return(m); goto finish; } else if(dbus_message_is_method_call(m, U_DBUS_SYSTEM_INTERFACE, "clearFlags") || (is2 = dbus_message_is_method_call(m, U_DBUS_SYSTEM_INTERFACE, "delFlag"))) { u_proc *proc; uint64_t tpid; uint64_t id; if (is2) { if (!dbus_message_get_args(m, &error, DBUS_TYPE_UINT64, &tpid, DBUS_TYPE_UINT64, &id, DBUS_TYPE_INVALID) || !dbus_message_iter_init (m, &imsg)) PUSH_ERROR(DBUS_ERROR_INVALID_ARGS, "wrong arguments") } else if(!dbus_message_get_args(m, &error, DBUS_TYPE_UINT64, &tpid, DBUS_TYPE_INVALID) || !dbus_message_iter_init (m, &imsg)) PUSH_ERROR(DBUS_ERROR_INVALID_ARGS, "wrong arguments") pid_t pid = (pid_t)tpid; proc = proc_by_pid_with_retry(pid); GET_CALLER() if(!proc) PUSH_ERROR(U_DBUS_ERROR_NO_PID, "wrong arguments") if(caller != 0 && caller != proc->proc.euid) PUSH_ERROR(DBUS_ERROR_ACCESS_DENIED, "access denied") if(is2) { u_flag_clear_flag(proc, (void *)id); } else { u_flag_clear_source(proc, U_DBUS_POINTER); } ret = dbus_message_new_method_return(m); goto finish; } else if(dbus_message_is_method_call(m, U_DBUS_SYSTEM_INTERFACE, "scheduleTask")) { u_proc *proc; uint32_t update; uint64_t tpid; if (!dbus_message_get_args(m, &error, DBUS_TYPE_UINT64, &tpid, DBUS_TYPE_BOOLEAN, &update, DBUS_TYPE_INVALID) || !dbus_message_iter_init (m, &imsg)) PUSH_ERROR(DBUS_ERROR_INVALID_ARGS, "wrong arguments") pid_t pid = (pid_t)tpid; proc = proc_by_pid_with_retry(pid); GET_CALLER() if(!proc) PUSH_ERROR(U_DBUS_ERROR_NO_PID, "wrong arguments") if(caller != 0 && caller != proc->proc.euid) PUSH_ERROR(DBUS_ERROR_ACCESS_DENIED, "access denied") ret = dbus_message_new_method_return(m); int suc = process_run_one(proc, update, FALSE); dbus_message_append_args (ret, DBUS_TYPE_BOOLEAN, &suc, DBUS_TYPE_INVALID); ret = dbus_message_new_method_return(m); goto finish; } else if(dbus_message_is_method_call(m, U_DBUS_SYSTEM_INTERFACE, "setSchedulerConfig")) { u_scheduler *sched = scheduler_get(); char *tmps = NULL; if (!sched || !sched->set_config) { ret = dbus_message_new_error(m, DBUS_ERROR_FAILED, "scheduler does not support setting config"); goto finish; } if(!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &tmps, DBUS_TYPE_INVALID)) { ret = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS , "wrong arguments"); goto finish; } if (!tmps) { ret = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS , "wrong arguments"); goto finish; } GET_CALLER() if(caller == 0) { //set_config_dbus(tmps); ret = dbus_message_new_method_return(m); g_message("DBUS: setSchedulerConfig(\"%s\") executed", tmps); dbus_bool_t rv = (dbus_bool_t)sched->set_config(tmps); dbus_message_append_args (ret, DBUS_TYPE_BOOLEAN, &rv, DBUS_TYPE_INVALID); goto finish; } else { if(!check_polkit("org.quamquam.ulatencyd.setConfig", c, m, "org.quamquam.ulatencyd.setConfig", set_config_callback, tmps, TRUE, NULL, tmps)) PUSH_ERROR(DBUS_ERROR_ACCESS_DENIED, "access denied") return DBUS_HANDLER_RESULT_HANDLED; } if(tmps) { goto finish; } } else if(dbus_message_is_method_call(m, U_DBUS_SYSTEM_INTERFACE, "listSchedulerConfigs")) { u_scheduler *sched = scheduler_get(); DBusMessageIter imsg, array; GPtrArray *configs; char *tmp; int i; if (!sched || !sched->list_configs) { ret = dbus_message_new_error(m, DBUS_ERROR_FAILED, "scheduler does not support setting config"); goto finish; } configs = sched->list_configs(); ret = dbus_message_new_method_return(m); dbus_message_iter_init_append(ret, &imsg); dbus_message_iter_open_container(&imsg, DBUS_TYPE_ARRAY, "s" , &array); if(configs) { for(i = 0; i < configs->len; i++) { tmp = g_ptr_array_index(configs, i); dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &tmp); } g_ptr_array_unref(configs); } dbus_message_iter_close_container(&imsg, &array); goto finish; } else if(dbus_message_is_method_call(m, U_DBUS_SYSTEM_INTERFACE, "getSchedulerConfigDescription")) { u_scheduler *sched = scheduler_get(); char *name, *desc; if (!sched || !sched->get_config_description) { ret = dbus_message_new_error(m, DBUS_ERROR_FAILED, "scheduler does not support config descriptions"); goto finish; } if(!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID)) { ret = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS , "wrong arguments"); goto finish; } ret = dbus_message_new_method_return(m); desc = sched->get_config_description(name); if(desc) { dbus_message_append_args(ret, DBUS_TYPE_STRING, &desc, DBUS_TYPE_INVALID); g_free(desc); } goto finish; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Get")) { const char *interface, *property; u_scheduler *sched = scheduler_get(); if (!dbus_message_get_args(m, &error, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) { g_warning("Failed to parse property get call: %s\n", error.message); ret = dbus_message_new_error(m, error.name, error.message); goto finish; } if (g_strcmp0(interface, U_DBUS_SYSTEM_INTERFACE) == 0) { ret = dbus_message_new_method_return(m); if(g_strcmp0(property, "config") == 0) { if(sched->get_config) { char *tmp = sched->get_config(); if(tmp) { dbus_message_append_args (ret, DBUS_TYPE_STRING, &tmp, DBUS_TYPE_INVALID); } g_free(tmp); } goto finish; } else if(g_strcmp0(property, "version") == 0) { const char *tmp = QUOTEME(VERSION); dbus_message_append_args (ret, DBUS_TYPE_STRING, &tmp, DBUS_TYPE_INVALID); goto finish; } dbus_message_unref(ret); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } else { return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) { ret = dbus_message_new_method_return(m); dbus_message_append_args(ret, DBUS_TYPE_STRING, &INTROSPECT_XML_SYSTEM, DBUS_TYPE_INVALID); } else return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; finish: if (ret) { dbus_connection_send(c, ret, NULL); dbus_message_unref(ret); } dbus_error_free(&error); return DBUS_HANDLER_RESULT_HANDLED; } void consolekit_init(); void systemd_init(); gboolean u_dbus_setup() { static const DBusObjectPathVTable utable = { .message_function = dbus_user_handler, }; static const DBusObjectPathVTable stable = { .message_function = dbus_system_handler, }; if(!U_dbus_connection) { g_warning("dbus connection missing. can't create dbus interface"); return FALSE; } DBusError error; DBusConnection *c = dbus_g_connection_get_connection(U_dbus_connection); dbus_error_init(&error); #ifdef DEVELOP_MODE if (dbus_bus_request_name(c, U_DBUS_SERVICE_NAME, DBUS_NAME_FLAG_REPLACE_EXISTING, &error) < 0) { g_warning("Failed to register name on bus: %s\n", error.message); goto fail; } #else if (dbus_bus_request_name(c, U_DBUS_SERVICE_NAME, DBUS_NAME_FLAG_DO_NOT_QUEUE, &error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { g_error("daemon already running, can't request: %s", U_DBUS_SERVICE_NAME); } #endif dbus_connection_register_object_path(c, U_DBUS_USER_PATH, &utable, NULL); dbus_connection_register_object_path(c, U_DBUS_SYSTEM_PATH, &stable, NULL); //systemd_init(); consolekit_init(); return TRUE; fail: dbus_error_free(&error); return FALSE; } poelzi-ulatencyd-55515a9/src/group.c000066400000000000000000000071341154664534000173660ustar00rootroot00000000000000/* Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. */ #include "config.h" #include "ulatency.h" #include #include // active user pid storage GList* active_users; static gint cmp_user(gconstpointer a,gconstpointer b) { const guint uid = *(guint *)b; const struct user_active *ua = a; return (ua->uid - uid); } static gint cmp_pid(gconstpointer a, gconstpointer b) { const guint pid = *(guint *)b; const struct user_active_process *up = a; return !(up->pid == pid); } struct user_active* get_userlist(guint uid, gboolean create) { struct user_active *ua; GError *error = NULL; GList* gls = g_list_find_custom(active_users, &uid, cmp_user); if(gls) return (struct user_active *)gls->data; if(create) { ua = g_malloc0(sizeof(struct user_active)); ua->uid = uid; ua->active_agent = USER_ACTIVE_AGENT_NONE; ua->max_processes = g_key_file_get_integer(config_data, "user", "default_active_list", &error); if(error && error->code) { ua->max_processes = 5; } ua->last_change = time(NULL); ua->actives = NULL; //g_list_alloc(); active_users = g_list_append(active_users, ua); return ua; } return NULL; } /* mark a process as active */ static gint cmp_last_change(gconstpointer a,gconstpointer b) { const struct user_active_process *u1 = a; const struct user_active_process *u2 = b; return (u2->last_change - u1->last_change); } void set_active_pid(guint uid, guint pid) { u_proc *proc; struct user_active_process *up; struct user_active *ua = get_userlist(uid, TRUE); GList* ups = g_list_find_custom(ua->actives, &pid, cmp_pid); if(!ups) { up = g_malloc(sizeof(struct user_active_process)); up->pid = pid; ua->actives = g_list_prepend(ua->actives, up); proc = proc_by_pid(pid); if(proc) { proc->changed = 1; process_run_one(proc, FALSE, FALSE); } } else { up = ups->data; } // remove the entries to much up->last_change = time(NULL); ua->actives = g_list_sort(ua->actives, cmp_last_change); while(g_list_length(ua->actives) > ua->max_processes) { up = g_list_last(ua->actives)->data; proc = proc_by_pid(up->pid); ua->actives = g_list_remove(ua->actives, up); if(proc) { proc->changed = 1; process_run_one(proc, FALSE, FALSE); } } } int is_active_pid(u_proc *proc) { GList* ups; struct user_active *ua = get_userlist(proc->proc.ruid, FALSE); if(!ua) return FALSE; ups = g_list_find_custom(ua->actives, &proc->pid, cmp_pid); if(!ups) return FALSE; return TRUE; } int get_active_pos(u_proc *proc) { int rv = 0; GList *cur; struct user_active *ua = get_userlist(proc->proc.ruid, FALSE); if(!ua) return 0; cur = ua->actives; while(cur) { rv++; if(((struct user_active_process *)cur->data)->pid == proc->pid) return rv; cur = g_list_next(cur); } return 0; } // cgroups //void set_active_pid(guint uid, guint pid); poelzi-ulatencyd-55515a9/src/linux_netlink.c000066400000000000000000000215511154664534000211140ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: s; c-basic-offset: 4 -*- * * Copyright (C) 2010 Richard Hughes * * Licensed under the GNU Lesser General Public License Version 2.1 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include //#include //#include //#include #include #include #include #include #include #include #include #include #include #include "ulatency.h" #include #include #define SEND_MESSAGE_LEN (NLMSG_LENGTH(sizeof(struct cn_msg) + \ sizeof(enum proc_cn_mcast_op))) #define RECV_MESSAGE_LEN (NLMSG_LENGTH(sizeof(struct cn_msg) + \ sizeof(struct proc_event))) #define SEND_MESSAGE_SIZE (NLMSG_SPACE(SEND_MESSAGE_LEN)) #define RECV_MESSAGE_SIZE (NLMSG_SPACE(RECV_MESSAGE_LEN)) #define BUFF_SIZE (MAX(MAX(SEND_MESSAGE_SIZE, RECV_MESSAGE_SIZE), 1024)) #define MIN_RECV_SIZE (MIN(SEND_MESSAGE_SIZE, RECV_MESSAGE_SIZE)) /** * Handle a netlink message. In the event of PROC_EVENT_UID or PROC_EVENT_GID, * we put the new events on the new event stack for processing when they exist a * given time * other events are ignored. * @param cn_hdr The netlink message * @return 0 on success, > 0 on error */ static int nl_handle_msg(struct cn_msg *cn_hdr) { /* The event to consider */ struct proc_event *ev; /* Return codes */ int ret = 0; /* Get the event data. We only care about two event types. */ ev = (struct proc_event*)cn_hdr->data; switch (ev->what) { // quite seldom events on old processes changing important parameters case PROC_EVENT_UID: u_trace("UID Event: PID = %d, tGID = %d, rUID = %d," " eUID = %d", ev->event_data.id.process_pid, ev->event_data.id.process_tgid, ev->event_data.id.r.ruid, ev->event_data.id.e.euid); //process_update_pid(ev->event_data.id.process_pid); process_new(ev->event_data.id.process_pid, FALSE); break; case PROC_EVENT_GID: u_trace("GID Event: PID = %d, tGID = %d, rGID = %d," " eGID = %d", ev->event_data.id.process_pid, ev->event_data.id.process_tgid, ev->event_data.id.r.rgid, ev->event_data.id.e.egid); //process_update_pid(ev->event_data.id.process_pid); process_new(ev->event_data.id.process_pid, FALSE); break; case PROC_EVENT_EXIT: u_trace("EXIT Event: PID = %d", ev->event_data.exit.process_pid); //g_ptr_array_foreach(stack, remove_pid_from_stack, &pid); // if the pid was found in the new stack, pid is set to 0 to indicate // the removal process_remove_by_pid(ev->event_data.exit.process_pid); break; case PROC_EVENT_EXEC: u_trace("EXEC Event: PID = %d, tGID = %d", ev->event_data.exec.process_pid, ev->event_data.exec.process_tgid); process_new_delay(ev->event_data.exec.process_tgid, 0); break; case PROC_EVENT_FORK: u_trace("FORK Event: PARENT = %d PID = %d tGID = %d", ev->event_data.fork.parent_tgid, ev->event_data.fork.child_pid, ev->event_data.fork.child_tgid); // we skip new threads for now // FIXME need filter block to get those events if(ev->event_data.fork.parent_tgid != ev->event_data.fork.child_pid) break; // parent does not mean the parent of the new proc, but the parent of // the forking process. so we lookup the parent of the forking process // first u_proc *rparent = proc_by_pid(ev->event_data.fork.parent_tgid); if(rparent) { u_proc_ensure(rparent, BASIC, FALSE); process_new_delay(ev->event_data.fork.child_tgid, rparent->proc.ppid); //ev->event_data.fork.parent_pid); } else process_new_delay(ev->event_data.fork.child_tgid, 0); break; default: return 0; } return ret; } static gboolean nl_connection_handler (GSocket *socket, GIOCondition condition, gpointer user_data) { GError *error = NULL; gsize len; gboolean ret = TRUE; char buff[BUFF_SIZE]; size_t recv_len; struct sockaddr_nl from_nla; socklen_t from_nla_len; struct nlmsghdr *nlh; struct sockaddr_nl kern_nla; struct cn_msg *cn_hdr; kern_nla.nl_family = AF_NETLINK; kern_nla.nl_groups = CN_IDX_PROC; kern_nla.nl_pid = 1; kern_nla.nl_pad = 0; memset(buff, 0, sizeof(buff)); from_nla_len = sizeof(from_nla); memcpy(&from_nla, &kern_nla, sizeof(from_nla)); /* the helper process exited */ // this should not happen to netlink if ((condition & G_IO_HUP) > 0) { g_warning ("socket was disconnected"); ret = FALSE; goto out; } /* there is data */ if ((condition & G_IO_IN) > 0) { len = g_socket_receive (socket, buff, sizeof(buff), NULL, &error); if (error != NULL) { g_warning ("failed to get data: %s", error->message); g_error_free (error); // no reason to stop goto out; } if (len == ENOBUFS) { g_warning("NETLINK BUFFER FULL, MESSAGE DROPPED!"); return 0; } if (len == 0) goto out; nlh = (struct nlmsghdr *)buff; while (NLMSG_OK(nlh, len)) { cn_hdr = NLMSG_DATA(nlh); if (nlh->nlmsg_type == NLMSG_NOOP) { nlh = NLMSG_NEXT(nlh, recv_len); continue; } if ((nlh->nlmsg_type == NLMSG_ERROR) || (nlh->nlmsg_type == NLMSG_OVERRUN)) break; if (nl_handle_msg(cn_hdr) < 0) return 1; if (nlh->nlmsg_type == NLMSG_DONE) break; nlh = NLMSG_NEXT(nlh, recv_len); } } out: return ret; } int init_netlink(GMainLoop *loop) { GSocket *gsocket = NULL; int socket_fd = 0; GError *error = NULL; GSource *source; struct sockaddr_nl my_nla; struct nlmsghdr *nl_hdr; char buff[BUFF_SIZE]; struct cn_msg *cn_hdr; enum proc_cn_mcast_op *mcop_msg; /* create socket */ /* * Create an endpoint for communication. Use the kernel user * interface device (PF_NETLINK) which is a datagram oriented * service (SOCK_DGRAM). The protocol used is the connector * protocol (NETLINK_CONNECTOR) */ socket_fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR); if (socket == NULL) { g_warning ("failed to create socket: %s", error->message); g_error_free (error); return 1; } my_nla.nl_family = AF_NETLINK; my_nla.nl_groups = CN_IDX_PROC; my_nla.nl_pid = getpid(); my_nla.nl_pad = 0; if (bind(socket_fd, (struct sockaddr *)&my_nla, sizeof(my_nla)) < 0) { g_warning("binding sk_nl error: %s\n", strerror(errno)); g_warning("realtime monitoring disabled. compile kernel with PROC_EVENTS enabled"); goto out; } gsocket = g_socket_new_from_fd(socket_fd, NULL); if(gsocket == NULL) { g_warning("can't create socket"); goto out; } nl_hdr = (struct nlmsghdr *)buff; cn_hdr = (struct cn_msg *)NLMSG_DATA(nl_hdr); mcop_msg = (enum proc_cn_mcast_op*)&cn_hdr->data[0]; g_debug("sending proc connector: PROC_CN_MCAST_LISTEN... "); memset(buff, 0, sizeof(buff)); *mcop_msg = PROC_CN_MCAST_LISTEN; /* fill the netlink header */ nl_hdr->nlmsg_len = SEND_MESSAGE_LEN; nl_hdr->nlmsg_type = NLMSG_DONE; nl_hdr->nlmsg_flags = 0; nl_hdr->nlmsg_seq = 0; nl_hdr->nlmsg_pid = getpid(); /* fill the connector header */ cn_hdr->id.idx = CN_IDX_PROC; cn_hdr->id.val = CN_VAL_PROC; cn_hdr->seq = 0; cn_hdr->ack = 0; cn_hdr->len = sizeof(enum proc_cn_mcast_op); g_debug("sending netlink message len=%d, cn_msg len=%d\n", nl_hdr->nlmsg_len, (int) sizeof(struct cn_msg)); if (send(socket_fd, nl_hdr, nl_hdr->nlmsg_len, 0) != nl_hdr->nlmsg_len) { g_warning("failed to send proc connector mcast ctl op!: %s\n", strerror(errno)); } g_debug("sent\n"); /* socket has data */ source = g_socket_create_source (gsocket, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, NULL); g_source_set_callback (source, (GSourceFunc) nl_connection_handler, loop, NULL); g_source_attach (source, NULL); return 0; out: return 1; } #if 0 gint main (void) { gboolean ret; GSocket *gsocket = NULL; int socket_fd = 0; GSocketAddress *address = NULL; GError *error = NULL; gsize wrote; GSource *source; GMainLoop *loop; struct sockaddr_nl my_nla; struct nlmsghdr *nl_hdr; char buff[BUFF_SIZE]; struct cn_msg *cn_hdr; enum proc_cn_mcast_op *mcop_msg; g_type_init (); loop = g_main_loop_new (NULL, FALSE); init_netlink(loop); g_debug ("running main loop"); g_main_loop_run (loop); out: if (loop != NULL) g_main_loop_unref (loop); if (socket != NULL) g_object_unref (socket); if (address != NULL) g_object_unref (address); return 0; } #endifpoelzi-ulatencyd-55515a9/src/lua_binding.c000066400000000000000000001517041154664534000205100ustar00rootroot00000000000000/* Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. */ #include "config.h" #include "ulatency.h" #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #ifndef __USE_GNU #define __USE_GNU #endif #include #include //#include #define UL_META "ulatency" #define LUA_TABLE_INT(NAME) \ lua_pushliteral(L, #NAME); \ lua_pushinteger(L, NAME); \ lua_settable(L, -3); //static proc_t *push_proc_t (lua_State *L); static u_proc *push_u_proc (lua_State *L, u_proc *proc); static int docall (lua_State *L, int narg, int nresults); int load_lua_rule_file(lua_State *L, const char *name); void stackdump_g(lua_State* l) { int i; int top = lua_gettop(l); printf("total in stack %d\n",top); for (i = 1; i <= top; i++) { /* repeat for each level */ int t = lua_type(l, i); switch (t) { case LUA_TSTRING: /* strings */ printf("string: '%s'\n", lua_tostring(l, i)); break; case LUA_TBOOLEAN: /* booleans */ printf("boolean %s\n",lua_toboolean(l, i) ? "true" : "false"); break; case LUA_TNUMBER: /* numbers */ printf("number: %g\n", lua_tonumber(l, i)); break; default: /* other values */ printf("%s\n", lua_typename(l, t)); break; } printf(" "); /* put a separator */ } printf("\n"); /* end the listing */ } static void l_hash_to_table(lua_State *L, GHashTable *table) { GHashTableIter iter; gpointer key, value; lua_newtable(L); g_hash_table_iter_init (&iter, table); while (g_hash_table_iter_next (&iter, &key, &value)) { lua_pushstring(L, (char *)key); lua_pushstring(L, (char *)value); lua_settable(L, -3); } } static void l_ptrarray_to_table(lua_State *L, GPtrArray *array) { int i = 0; lua_newtable(L); for (; i < array->len; i++) { lua_pushinteger(L, i+1); lua_pushstring(L, (char *)g_ptr_array_index(array, i)); lua_settable(L, -3); } } static void l_vstr_to_table(lua_State *L, char **vec, int len) { int i = 0; lua_newtable(L); if(len != -1) { for(i = 0; i < len; i++) { lua_pushinteger(L, i+1); lua_pushstring(L, (char *)vec[i]); lua_settable(L, -3); } } else { for(i = 0; vec[i]; i++) { lua_pushinteger(L, i+1); lua_pushstring(L, (char *)vec[i]); lua_settable(L, -3); } } } static int get_load (lua_State *L) { double av1, av5, av15; loadavg(&av1, &av5, &av15); lua_pushnumber(L, av1); lua_pushnumber(L, av5); lua_pushnumber(L, av15); return 3; } static int get_uptime (lua_State *L) { double uptime_secs, idle_secs; uptime(&uptime_secs, &idle_secs); lua_pushnumber(L, uptime_secs); lua_pushnumber(L, idle_secs); return 2; } static int l_get_last_load(lua_State *L) { lua_pushnumber(L, get_last_load()); return 1; } static int l_get_last_percent(lua_State *L) { lua_pushnumber(L, get_last_percent()); return 1; } static int get_meminfo (lua_State *L) { lua_createtable (L, 10, 0); meminfo(); LUA_TABLE_INT(kb_active) LUA_TABLE_INT(kb_main_shared) LUA_TABLE_INT(kb_main_buffers) LUA_TABLE_INT(kb_main_cached) LUA_TABLE_INT(kb_main_free) LUA_TABLE_INT(kb_main_total) LUA_TABLE_INT(kb_swap_free) LUA_TABLE_INT(kb_swap_total) LUA_TABLE_INT(kb_high_free) LUA_TABLE_INT(kb_high_total) LUA_TABLE_INT(kb_low_free) LUA_TABLE_INT(kb_low_total) LUA_TABLE_INT(kb_active) LUA_TABLE_INT(kb_inact_laundry) LUA_TABLE_INT(kb_inact_dirty) LUA_TABLE_INT(kb_inact_clean) LUA_TABLE_INT(kb_inact_target) LUA_TABLE_INT(kb_swap_cached) LUA_TABLE_INT(kb_swap_used) LUA_TABLE_INT(kb_main_used) LUA_TABLE_INT(kb_writeback) LUA_TABLE_INT(kb_slab) LUA_TABLE_INT(kb_committed_as) LUA_TABLE_INT(kb_dirty) LUA_TABLE_INT(kb_inactive) LUA_TABLE_INT(kb_mapped) LUA_TABLE_INT(kb_pagetables) return 1; } static int get_vminfo (lua_State *L) { lua_createtable (L, 10, 0); vminfo(); LUA_TABLE_INT(vm_nr_dirty) LUA_TABLE_INT(vm_nr_writeback) LUA_TABLE_INT(vm_nr_pagecache) LUA_TABLE_INT(vm_nr_page_table_pages) LUA_TABLE_INT(vm_nr_reverse_maps) LUA_TABLE_INT(vm_nr_mapped) LUA_TABLE_INT(vm_nr_slab) LUA_TABLE_INT(vm_pgpgin) LUA_TABLE_INT(vm_pgpgout) LUA_TABLE_INT(vm_pswpin) LUA_TABLE_INT(vm_pswpout) LUA_TABLE_INT(vm_pgalloc) LUA_TABLE_INT(vm_pgfree) LUA_TABLE_INT(vm_pgactivate) LUA_TABLE_INT(vm_pgdeactivate) LUA_TABLE_INT(vm_pgfault) LUA_TABLE_INT(vm_pgmajfault) LUA_TABLE_INT(vm_pgscan) LUA_TABLE_INT(vm_pgrefill) LUA_TABLE_INT(vm_pgsteal) LUA_TABLE_INT(vm_kswapd_steal) LUA_TABLE_INT(vm_pageoutrun) LUA_TABLE_INT(vm_allocstall) return 1; } static int l_get_pid_digits (lua_State *L) { lua_pushinteger(L, get_pid_digits()); return 1; } static int l_user_from_uid (lua_State *L) { int uid = luaL_checkint (L, 1); lua_pushstring(L, user_from_uid(uid)); return 1; } static int l_group_from_guid (lua_State *L) { int gid = luaL_checkint (L, 1); lua_pushstring(L, group_from_gid(gid)); return 1; } static int l_filter_rv (lua_State *L) { int flags = lua_tointeger(L, 1); int timeout = lua_tointeger(L, 2); lua_pushnumber(L, FILTER_MIX(flags, timeout)); return 1; } static int l_get_pid (lua_State *L) { int pid; u_proc *proc; pid = luaL_checkint (L, 1); proc = proc_by_pid(pid); if(!proc) return 0; push_u_proc(L, proc); return 1; } static int l_get_config (lua_State *L) { const char *group, *key; char *tmp; group = luaL_checkstring (L, 1); key = luaL_checkstring (L, 2); //proctab = openproc(PROC_PID, pid); tmp = g_key_file_get_string(config_data, group, key, NULL); if(tmp) { lua_pushstring(L, tmp); free(tmp); return 1; } return 0; } static int l_list_keys (lua_State *L) { const char *group; gchar **tmp; gsize len; int i; group = luaL_checkstring (L, 1); //proctab = openproc(PROC_PID, pid); tmp = g_key_file_get_keys(config_data, group, &len, NULL); if(len) { lua_newtable(L); for(i = 0; i < len; i++) { lua_pushinteger(L, i); lua_pushstring(L, tmp[i]); lua_settable (L, -3); } return 1; } return 0; } static int l_list_pids (lua_State *L) { int i = 1; GHashTableIter iter; gpointer ikey, value; u_proc *proc; lua_newtable (L); g_hash_table_iter_init (&iter, processes); while (g_hash_table_iter_next (&iter, &ikey, &value)) { proc = (u_proc *)value; lua_pushinteger(L, i); lua_pushinteger(L, proc->pid); lua_settable(L, -3); i++; } return 1; } static int l_list_processes (lua_State *L) { int i = 1; GHashTableIter iter; gpointer ikey, value; u_proc *proc; int changed = lua_toboolean(L, 1); lua_newtable (L); g_hash_table_iter_init (&iter, processes); while (g_hash_table_iter_next (&iter, &ikey, &value)) { proc = (u_proc *)value; if(changed) { if(!proc->changed) { continue; } } lua_pushinteger(L, i); push_u_proc(L, proc); lua_settable(L, -3); i++; } return 1; } static int l_get_number_of_processes(lua_State *L) { lua_pushinteger(L, get_number_of_processes()); return 1; } static int l_set_active_pid(lua_State *L) { lua_Integer uid = luaL_checkinteger (L, 1); lua_Integer pid = luaL_checkinteger (L, 2); set_active_pid((guint)uid, (guint)pid); return 0; } static int l_get_active_uids(lua_State *L) { GList *cur = g_list_first(active_users); struct user_active *ua = NULL; int i = 1; lua_newtable(L); while(cur) { ua = cur->data; lua_pushinteger(L, i); lua_newtable(L); lua_pushstring(L, "uid"); lua_pushinteger(L, ua->uid); lua_settable (L, -3); lua_pushstring(L, "max_processes"); lua_pushinteger(L, ua->max_processes); lua_settable (L, -3); lua_pushstring(L, "last_change"); lua_pushinteger(L, ua->last_change); lua_settable (L, -3); lua_settable (L, -3); i++; cur = g_list_next (cur); } return 1; } static int l_get_active_pids(lua_State *L) { lua_Integer uid = luaL_checkinteger (L, 1); struct user_active *ua = get_userlist((guint)uid, FALSE); struct user_active_process *up; GList *cur; int i = 1; if(!ua) return 0; cur = g_list_first(ua->actives); lua_newtable(L); while(cur) { up = cur->data; lua_pushinteger(L, i); lua_newtable(L); lua_pushstring(L, "pid"); lua_pushinteger(L, up->pid); lua_settable (L, -3); lua_pushstring(L, "last_change"); lua_pushinteger(L, up->last_change); lua_settable (L, -3); lua_settable (L, -3); i++; cur = g_list_next (cur); } return 1; } static int l_log (lua_State *L) { int level = luaL_checkint (L, 1); const char *str = luaL_checkstring(L, 2); g_log(G_LOG_DOMAIN, level, "%s", str); return 0; } static int l_quit (lua_State *L) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "%s", "quit called from script"); if(g_main_loop_is_running(main_loop)) g_main_loop_quit(main_loop); else exit(0); return 0; } gboolean l_call_function(gpointer data) { gboolean rv; struct lua_callback *cd = (struct lua_callback *)data; lua_rawgeti (cd->lua_state, LUA_REGISTRYINDEX, cd->lua_func); lua_rawgeti (cd->lua_state, LUA_REGISTRYINDEX, cd->lua_data); //stackdump_g(cd->lua_state); rv = docall(cd->lua_state, 1, 1); //stackdump_g(cd->lua_state); if(rv) { // FIXME case of errror, stop the filter ??? return TRUE; } rv = lua_toboolean (cd->lua_state, -1); lua_pop(cd->lua_state, 1); if(rv) { return TRUE; } luaL_unref (cd->lua_state, LUA_REGISTRYINDEX, cd->lua_data); luaL_unref (cd->lua_state, LUA_REGISTRYINDEX, cd->lua_func); luaL_unref (cd->lua_state, LUA_REGISTRYINDEX, cd->lua_state_id); free(data); return FALSE; } static int l_add_interval (lua_State *L) { luaL_checktype(L, 1, LUA_TFUNCTION); guint interval = luaL_checkint(L, 2); struct lua_callback *cd = malloc(sizeof(struct lua_callback)); memset(cd, 0, sizeof(struct lua_callback)); cd->lua_state = lua_newthread (L); cd->lua_state_id = luaL_ref(L, LUA_REGISTRYINDEX); cd->lua_data = luaL_ref(L, LUA_REGISTRYINDEX); cd->lua_func = luaL_ref(L, LUA_REGISTRYINDEX); g_timeout_add(interval,l_call_function, cd); return 0; } // type checks and pushes #define U_PROC "U_PROC" #define U_PROC_META "U_PROC_META" #define U_PROC_TASK "U_PROC_TASK" #define U_PROC_TASK_META "U_PROC_TASK_META" static u_proc *check_u_proc (lua_State *L, int index) { u_proc **p; luaL_checktype(L, index, LUA_TUSERDATA); p = (u_proc **)luaL_checkudata(L, index, U_PROC_META); if (p == NULL) luaL_typerror(L, index, U_PROC); return *p; } static u_proc *push_u_proc (lua_State *L, u_proc *upr) { u_proc *proc; //u_proc *p = (u_proc*)lua_newuserdata(L, sizeof(u_proc)); u_proc **p = (u_proc **)lua_newuserdata(L, sizeof(u_proc *)); if(!upr) { proc = u_proc_new(NULL); } else { proc = upr; INC_REF(proc); } *p = proc; //proc->in_lua = 1; //DEC_REF(proc); luaL_getmetatable(L, U_PROC_META); lua_setmetatable(L, -2); //up = /* memset(p, 0, sizeof(proc_t)); return p; */ return proc; } static int u_proc_gc (lua_State *L) { u_proc *proc = check_u_proc(L, 1); //printf("goodbye proc_t (%p)\n", proc); DEC_REF(proc); return 0; } static u_task *check_u_task (lua_State *L, int index) { u_task **p; luaL_checktype(L, index, LUA_TUSERDATA); p = (u_task **)luaL_checkudata(L, index, U_PROC_TASK_META); if (p == NULL) luaL_typerror(L, index, U_PROC_TASK); return *p; } static void push_u_task (lua_State *L, u_task *task) { u_task **p = (u_task **)lua_newuserdata(L, sizeof(u_task *)); INC_REF(task->proc); *p = task; luaL_getmetatable(L, U_PROC_TASK_META); lua_setmetatable(L, -2); return; } static int u_task_gc (lua_State *L) { u_task *task = check_u_task(L, 1); //printf("goodbye proc_t (%p)\n", proc); DEC_REF(task->proc); return 0; } #define U_FLAG "U_FLAG" #define U_FLAG_META "U_FLAG_META" static u_flag *check_u_flag (lua_State *L, int index) { u_flag **p; luaL_checktype(L, index, LUA_TUSERDATA); p = (u_flag **)luaL_checkudata(L, index, U_FLAG_META); if (p == NULL) luaL_typerror(L, index, U_FLAG); return *p; } static u_flag *push_u_flag (lua_State *L, u_flag *upr, void *source, const char *name) { u_flag *flag; //u_proc *p = (u_proc*)lua_newuserdata(L, sizeof(u_proc)); u_flag **p = (u_flag **)lua_newuserdata(L, sizeof(u_flag *)); if(!upr) { flag = u_flag_new(source, name); } else { flag = upr; INC_REF(flag); } *p = flag; luaL_getmetatable(L, U_FLAG_META); lua_setmetatable(L, -2); return flag; } // bindings to u_proc static int u_proc_get_parent (lua_State *L) { u_proc *proc = check_u_proc(L, 1); u_proc *parent; if(U_PROC_IS_INVALID(proc) || !proc->node || !proc->node->parent || !proc->node->parent->data) return 0; parent = (u_proc *)proc->node->parent->data; push_u_proc(L, parent); return 1; } static int u_proc_get_children (lua_State *L) { int i = 1, max; u_proc *child; u_proc *proc = check_u_proc(L, 1); if(!proc->node) return 0; max = g_node_n_children(proc->node); lua_newtable (L); for(i = 0; i < max; i++) { child = g_node_nth_child(proc->node, i)->data; lua_pushinteger(L, i+1); push_u_proc(L, child); lua_settable(L, -3); } return 1; } static int l_proc_list_flags (lua_State *L) { int i = 1; u_proc *proc = check_u_proc(L, 1); int recr = lua_toboolean(L, 2); u_flag *fl; GList *cur, *lst; lst = u_proc_list_flags(proc, recr); cur = lst; lua_newtable(L); while(cur) { fl = cur->data; lua_pushinteger(L, i); push_u_flag(L, fl, NULL, NULL); DEC_REF(fl); lua_settable(L, -3); i++; cur = g_list_next (cur); } g_list_free(lst); return 1; } static int u_proc_get_tasks (lua_State *L) { int i = 0; u_proc *proc = check_u_proc(L, 1); int update = lua_toboolean(L, 2); u_proc_ensure(proc, TASKS, update); lua_newtable(L); for(; i < proc->tasks->len; i++) { lua_pushinteger(L, i+1); //lua_pushinteger(L, g_array_index(proc->tasks, proc_t, i).tid); //lua_pushinteger(L, ((proc_t *)g_ptr_array_index(proc->tasks, i))->tid); push_u_task(L, (u_task *)g_ptr_array_index(proc->tasks, i)); lua_settable(L, -3); } return 1; } static int _u_proc_get_current_task_pids (lua_State *L) { int i = 0; u_proc *proc = check_u_proc(L, 1); GArray *lst = u_proc_get_current_task_pids(proc); if(!lst) return 0; lua_newtable(L); for(; i < lst->len; i++) { lua_pushinteger(L, i+1); lua_pushinteger(L, g_array_index(lst, pid_t, i)); lua_settable(L, -3); } g_array_unref(lst); return 1; } static int u_proc_add_flag (lua_State *L) { u_proc *proc = check_u_proc(L, 1); u_flag *flag = check_u_flag(L, 2); lua_pushinteger(L, u_flag_add(proc, flag)); return 1; } static int u_proc_del_flag (lua_State *L) { u_proc *proc = check_u_proc(L, 1); u_flag *flag = check_u_flag(L, 2); lua_pushinteger(L, u_flag_del(proc, flag)); return 1; } static int u_proc_clear_flag_name (lua_State *L) { u_proc *proc = check_u_proc(L, 1); const char *name = luaL_checkstring(L, 2); u_flag_clear_name(proc, name); return 0; } static int u_proc_clear_flag_source (lua_State *L) { u_proc *proc = check_u_proc(L, 1); u_flag_clear_source(proc, L); return 0; } static int u_proc_clear_flag_all (lua_State *L) { u_proc *proc = check_u_proc(L, 1); u_flag_clear_all(proc); return 0; } static int u_proc_clear_changed (lua_State *L) { u_proc *proc = check_u_proc(L, 1); proc->changed = 0; return 0; } static int u_proc_kill (lua_State *L) { u_proc *proc = check_u_proc(L, 1); int signal = SIGTERM; if(lua_isnumber(L, 2)) { signal = lua_tointeger(L, 2); } if(U_PROC_IS_VALID(proc)) { g_message("send signal to process: pid:%d signal:%d\n", proc->pid, signal); kill(proc->pid, signal); } return 0; } static int u_proc_get_n_children (lua_State *L) { u_proc *proc = check_u_proc(L, 1); if(!U_PROC_IS_VALID(proc)) return 0; lua_pushinteger(L, g_node_n_children(proc->node)); return 1; } static int u_proc_get_n_nodes (lua_State *L) { u_proc *proc = check_u_proc(L, 1); if(!U_PROC_HAS_STATE(proc, UPROC_ALIVE)) return 0; lua_pushinteger(L, g_node_n_nodes(proc->node, G_TRAVERSE_ALL)); return 1; } static int u_proc_set_block_scheduler (lua_State *L) { u_proc *proc = check_u_proc(L, 1); int value = luaL_checkint(L, 2); if(!U_PROC_HAS_STATE(proc, UPROC_ALIVE)) return 0; g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "block_scheduler set to: %d by %s", value, "(FIXME)"); proc->block_scheduler = value; return 0; } static int u_proc_set_rtprio (lua_State *L) { u_proc *proc = check_u_proc(L, 1); struct sched_param param = {0}; int value = luaL_checkint(L, 2); if(lua_isnumber(L, 3)) param.sched_priority = lua_tointeger(L, 3); if(!U_PROC_HAS_STATE(proc, UPROC_ALIVE)) return 0; g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "rtprio set to: %d by %s", value, "(FIXME)"); sched_setscheduler(proc->pid, value, ¶m); return 1; } /* disabled due bug (or at least i think it's one): https://bugzilla.kernel.org/show_bug.cgi?id=27092 static int u_proc_set_pgid (lua_State *L) { u_proc *proc = check_u_proc(L, 1); int value = luaL_checkint(L, 2); long pt; int rv; if(U_PROC_IS_INVALID(proc)) return 0; pt = ptrace (PTRACE_ATTACH, proc->pid, NULL, NULL); printf("ptrace: %d %ld\n", proc->pid, pt); wait(); rv = setpgid(proc->pid, 0); if (rv != 0) perror("setpgid() error"); lua_pushinteger(L, rv); lua_pushinteger(L, errno); if(!pt) pt = ptrace (PTRACE_DETACH, proc->pid, NULL, NULL); printf("ptrace2: %ld\n", pt); return 2; } */ static int u_proc_set_pgid (lua_State *L) { u_proc *proc = check_u_proc(L, 1); int value = luaL_checkint(L, 2); if(!U_PROC_HAS_STATE(proc, UPROC_ALIVE)) return 0; // we only set the fake value when it's differs from the original if(proc->proc.pgrp != value) { proc->fake_pgrp_old = proc->proc.pgrp; proc->fake_pgrp = value; } proc->changed = 1; lua_pushinteger(L, 0); lua_pushinteger(L, 0); return 2; } static int u_proc_set_oom_score (lua_State *L) { u_proc *proc = check_u_proc(L, 1); int value = luaL_checkint(L, 2); if(!U_PROC_HAS_STATE(proc, UPROC_ALIVE)) return 0; lua_pushboolean(L, !adj_oom_killer(proc->pid, value)); return 1; } static int u_proc_get_oom_score (lua_State *L) { u_proc *proc = check_u_proc(L, 1); if(!U_PROC_HAS_STATE(proc, UPROC_ALIVE)) return 0; lua_pushinteger(L, !get_oom_killer(proc->pid)); return 1; } static int u_proc_ioprio_set (lua_State *L) { u_proc *proc = check_u_proc(L, 1); int prio = luaL_checkint(L, 2); int class = luaL_checkint(L, 3); if(!U_PROC_HAS_STATE(proc, UPROC_ALIVE)) return 0; lua_pushinteger(L, !ioprio_setpid(proc->pid, prio, class)); return 1; } static int u_proc_ioprio_get (lua_State *L) { u_proc *proc = check_u_proc(L, 1); int prio = 0; int class = 0; if(!U_PROC_HAS_STATE(proc, UPROC_ALIVE)) return 0; ioprio_getpid(proc->pid, &prio, &class); lua_pushinteger(L, prio); lua_pushinteger(L, class); return 2; } #define PUSH_INT(name) \ if(!strcmp(key, #name )) { \ lua_pushinteger(L, (lua_Integer)proc->name); \ return 1; \ } #define PUSH_STR(name) \ if(!strcmp(key, #name )) { \ lua_pushstring(L, proc->name); \ return 1; \ } static const luaL_reg u_proc_methods[] = { {"get_parent", u_proc_get_parent}, {"get_children", u_proc_get_children}, {"list_flags", l_proc_list_flags}, {"add_flag", u_proc_add_flag}, {"del_flag", u_proc_del_flag}, {"clear_flag_name", u_proc_clear_flag_name}, {"clear_flag_source", u_proc_clear_flag_source}, {"clear_flag_all", u_proc_clear_flag_all}, {"clear_changed", u_proc_clear_changed}, {"kill", u_proc_kill}, {"get_n_children", u_proc_get_n_children}, {"get_n_nodes", u_proc_get_n_nodes}, {"set_block_scheduler", u_proc_set_block_scheduler}, {"set_rtprio", u_proc_set_rtprio}, {"set_pgid", u_proc_set_pgid}, {"set_oom_score", u_proc_set_oom_score}, {"get_oom_score", u_proc_get_oom_score}, {"set_ioprio", u_proc_ioprio_set}, {"get_ioprio", u_proc_ioprio_get}, {"get_tasks", u_proc_get_tasks}, {"get_current_task_pids", _u_proc_get_current_task_pids}, {NULL,NULL} }; static int handle_proc_t (lua_State *L, proc_t *proc, const char *key) { PUSH_INT(tid) // tid, // (special) task id, the POSIX thread ID (see also: tgid) PUSH_INT(ppid) // ppid; // stat,status pid of parent process PUSH_INT(state) // stat (special) %CPU usage (is not filled in by readproc!!!) PUSH_INT(utime) PUSH_INT(stime) PUSH_INT(cutime) PUSH_INT(cstime) // FIXME need bc lib here PUSH_INT(start_time) #ifdef SIGNAL_STRING PUSH_STR(signal) PUSH_STR(blocked) PUSH_STR(sigignore) PUSH_STR(sigcatch) PUSH_STR(_sigpnd) #endif // unsigned KLONG PUSH_INT(start_code) PUSH_INT(end_code) PUSH_INT(start_stack) PUSH_INT(kstk_esp) PUSH_INT(kstk_eip) PUSH_INT(wchan) PUSH_INT(priority) PUSH_INT(nice) PUSH_INT(rss) PUSH_INT(alarm) PUSH_INT(size) PUSH_INT(resident) PUSH_INT(share) PUSH_INT(trs) PUSH_INT(lrs) PUSH_INT(drs) PUSH_INT(dt) PUSH_INT(vm_size) PUSH_INT(vm_lock) PUSH_INT(vm_rss) PUSH_INT(vm_data) PUSH_INT(vm_stack) PUSH_INT(vm_exe) PUSH_INT(vm_lib) PUSH_INT(rtprio) PUSH_INT(sched) PUSH_INT(vsize) PUSH_INT(rss_rlim) PUSH_INT(flags) PUSH_INT(min_flt) PUSH_INT(maj_flt) PUSH_INT(cmin_flt) PUSH_INT(cmaj_flt) /*PUSH_INT(flags) PUSH_INT(flags) PUSH_INT(flags) PUSH_INT(flags) */ PUSH_STR(euser) PUSH_STR(ruser) PUSH_STR(suser) PUSH_STR(fuser) PUSH_STR(rgroup) PUSH_STR(egroup) PUSH_STR(sgroup) PUSH_STR(fgroup) PUSH_STR(cmd) PUSH_INT(nlwp) PUSH_INT(tid) PUSH_INT(tgid) PUSH_INT(tty) PUSH_INT(euid) PUSH_INT(egid) PUSH_INT(ruid) PUSH_INT(rgid) PUSH_INT(suid) PUSH_INT(sgid) PUSH_INT(fuid) PUSH_INT(fgid) PUSH_INT(tpgid) PUSH_INT(nsupgid) // *supgid, // status supplementary gid's PUSH_INT(exit_signal) PUSH_INT(processor) return 0; } #undef PUSH_INT #undef PUSH_STR static int u_proc_index (lua_State *L) { //char path[PROCPATHLEN]; u_proc *proc = check_u_proc(L, 1); const char *key = luaL_checkstring(L, 2); luaL_reg *lreg = (luaL_reg *)u_proc_methods; int rv = 0; for (; lreg->name; lreg++) { if(strcmp(lreg->name, key) == 0) { lua_pushcfunction(L, lreg->func); return 1; } } if(!strcmp(key, "is_valid" )) { \ lua_pushboolean(L, U_PROC_IS_VALID(proc)); return 1; } else if(!strcmp(key, "is_invalid" )) { lua_pushboolean(L, U_PROC_IS_INVALID(proc)); return 1; } else if(!strcmp(key, "pid" )) { lua_pushinteger(L, proc->pid); return 1; } else if(!strcmp(key, "changed" )) { lua_pushboolean(L, proc->changed); return 1; } else if(!strcmp(key, "block_scheduler" )) { lua_pushinteger(L, proc->block_scheduler); return 1; } else if(!strcmp(key, "data" )) { if(!proc->lua_data) { lua_newtable(L); lua_pushvalue(L, -1); proc->lua_data = luaL_ref(L, LUA_REGISTRYINDEX); return 1; } else { lua_rawgeti(L, LUA_REGISTRYINDEX, proc->lua_data); return 1; } return 0; } else if(!strcmp(key, "is_active" )) { lua_pushboolean(L, is_active_pid(proc)); return 1; } else if(!strcmp(key, "active_pos" )) { lua_pushinteger(L, get_active_pos(proc)); return 1; } else if(!strcmp(key, "received_rt" )) { lua_pushboolean(L, proc->received_rt); return 1; } if(!u_proc_ensure(proc, BASIC, FALSE)) { lua_pushfstring (L, "u_proc basic data not available ", proc->pid); lua_error(L); } // data of proc.proc must be invalidated as the process is already dead if(U_PROC_IS_INVALID(proc)) { lua_pushliteral(L, "u_proc state is invalid"); lua_error(L); } rv = handle_proc_t (L, &(proc->proc), key); if(rv) return rv; //FIXME // **environ, // (special) environment string vector (/proc/#/environ) // **cmdline; // (special) command line string vector (/proc/#/cmdline) if(!strcmp(key, "environ" )) { // lazy read u_proc_ensure(proc, ENVIRONMENT, TRUE); if(proc->environ) { l_hash_to_table(L, proc->environ); return 1; } return 0; } if(!strcmp(key, "cmdline" )) { if(u_proc_ensure(proc, CMDLINE, FALSE) && proc->cmdline) { l_ptrarray_to_table(L, proc->cmdline); return 1; } else { return 0; } } if(!strcmp(key, "cmdline_match" )) { if(u_proc_ensure(proc, CMDLINE, FALSE) && proc->cmdline_match) { lua_pushstring(L, proc->cmdline_match); return 1; } else { return 0; } } if(!strcmp(key, "cmdfile" )) { if(u_proc_ensure(proc, CMDLINE, FALSE) && proc->cmdfile) { lua_pushstring(L, proc->cmdfile); return 1; } else { return 0; } } if(!strcmp(key, "exe" )) { if(u_proc_ensure(proc, EXE, FALSE) && proc->exe) { lua_pushstring(L, proc->exe); return 1; } else { return 0; } } // **supgrp, // status supplementary groups if(!strcmp(key, "groups")) { if(proc->proc.supgrp) { l_vstr_to_table(L, proc->proc.supgrp, proc->proc.nsupgid); return 1; } else { return 0; } } // struct proc_t // *ring, // n/a thread group ring // *next; // n/a various library uses //PUSH_INT(pgrp) if(!strcmp(key, "pgrp" )) { lua_pushinteger(L, proc->fake_pgrp ? proc->fake_pgrp : (lua_Integer)proc->proc.pgrp); return 1; } if(!strcmp(key, "session" )) { lua_pushinteger(L, proc->fake_session ? proc->fake_session : (lua_Integer)proc->proc.session); return 1; } if(!strcmp(key, "cgroup" )) { if(proc->proc.cgroup) { l_vstr_to_table(L, proc->proc.cgroup, -1); return 1; } else { return 0; } } else if(!strcmp(key, "cgroup_origin" )) { if(proc->cgroup_origin) { l_vstr_to_table(L, proc->cgroup_origin, -1); return 1; } else { return 0; } } return 0; } static int u_task_index (lua_State *L) { u_task *task = check_u_task(L, 1); const char *key = luaL_checkstring(L, 2); return handle_proc_t (L, &(task->task), key); } static int u_proc_tostring (lua_State *L) { u_proc **proc = lua_touserdata(L, 1); lua_pushfstring(L, "u_proc: <%p> pid:%d %s", (*proc), (*proc)->pid, &(*proc)->proc.cmd); return 1; } static int u_proc_eq (lua_State *L) { u_proc *proc = check_u_proc(L, 1); u_proc *proc2 = check_u_proc(L, 2); lua_pushboolean(L, proc == proc2); return 1; } static const luaL_reg u_proc_meta[] = { {"__gc", u_proc_gc}, {"__tostring", u_proc_tostring}, {"__index", u_proc_index}, {"__eq", u_proc_eq}, {NULL, NULL} }; /********************************************* u_task setup */ static int u_task_tostring (lua_State *L) { u_task **task = lua_touserdata(L, 1); lua_pushfstring(L, "u_task: <%p> pid:%d tid:%d %s", (*task), (*task)->task.tgid, (*task)->task.tid, &(*task)->task.cmd); return 1; } static int u_task_eq (lua_State *L) { u_proc *proc = check_u_proc(L, 1); u_proc *proc2 = check_u_proc(L, 2); lua_pushboolean(L, proc == proc2); return 1; } static const luaL_reg u_task_meta[] = { {"__gc", u_task_gc}, {"__tostring", u_task_tostring}, {"__index", u_task_index}, {"__eq", u_task_eq}, {NULL, NULL} }; static const luaL_reg u_task_methods[] = { {NULL, NULL}, }; // u_flag static int u_flag_gc (lua_State *L) { u_flag *flag = check_u_flag(L, 1); //printf("goodbye proc_t (%p)\n", proc); DEC_REF(flag); return 0; } static int u_flag_tostring (lua_State *L) { u_flag **flag = lua_touserdata(L, 1); u_flag *flg = *flag; lua_pushfstring(L, "u_flag: <%p> %s ", flg, flg->name ? flg->name : "(no name)"); return 1; } #define PUSH_INT(name) \ if(!strcmp(key, #name )) { \ lua_pushinteger(L, (lua_Integer)flag->name); \ return 1; \ } #define PUSH_BOOL(name) \ if(!strcmp(key, #name )) { \ lua_pushboolean(L, flag->name); \ return 1; \ } #define PUSH_CHR(name) \ if(!strcmp(key, #name )) { \ lua_pushstring(L, flag->name); \ return 1; \ } #define PULL_INT(name) \ if(!strcmp(key, #name )) { \ flag->name = luaL_checkinteger(L, 3); \ return 0; \ } #define PULL_CHR(name) \ if(!strcmp(key, #name)) { \ if(flag->name) \ free(flag->name); \ flag->name = g_strdup(luaL_checkstring(L, 3)); \ return 0; \ } static int u_flag_index (lua_State *L) { u_flag *flag = check_u_flag(L, 1); const char *key = luaL_checkstring(L, 2); PUSH_CHR(name) PUSH_BOOL(inherit) PUSH_INT(priority) PUSH_INT(timeout) PUSH_CHR(reason) PUSH_INT(value) PUSH_INT(threshold) if(!strcmp(key, "is_source")) { lua_pushboolean(L, flag->source == L); } return 0; } static int u_flag_newindex (lua_State *L) { u_flag *flag = check_u_flag(L, 1); const char *key = luaL_checkstring(L, 2); PULL_CHR(name) PULL_CHR(reason) PULL_INT(inherit) PULL_INT(priority) PULL_INT(timeout) //PULL_INT(reason) PULL_INT(value) PULL_INT(threshold) return 0; } #define CHK_N_SET(name, conv) \ lua_getfield (L, 1, #name ); \ if(!lua_isnil (L, -1)) { \ flag-> name = conv ; \ } \ lua_pop(L, 1); static int l_flag_new (lua_State *L) { //u_flag *flag = check_u_filter(L, 1); u_flag *flag = NULL; //luaL_checktype(L, 1, LUA_TTABLE); const char *name = NULL; //void *source = NULL; if(lua_istable(L, 1)) { lua_getfield (L, 1, "name"); if(lua_isstring(L, -1)) { name = lua_tostring(L, -1); } lua_pop(L, 1); flag = push_u_flag(L, NULL, L, name); CHK_N_SET(inherit, lua_toboolean(L, -1)) CHK_N_SET(priority, lua_tointeger(L, -1)) CHK_N_SET(timeout, lua_tointeger(L, -1)) CHK_N_SET(reason, g_strdup(lua_tostring(L, -1)) ) CHK_N_SET(value, lua_tointeger(L, -1)) CHK_N_SET(threshold, lua_tointeger(L, -1)) return 1; } if(lua_isstring(L, 1)) { name = lua_tostring(L, 1); } //lua_getfield(L, 1, "__u_source"); //if(!lua_islightuserdata(L, -1)) // luaL_typerror(L, 2, "source instance not found"); //source = lua_touserdata(L, -1); push_u_flag(L, NULL, L, name); return 1; } #undef CHK_N_SET static int u_flag_eq (lua_State *L) { u_flag *flag = check_u_flag(L, 1); u_flag *flag2 = check_u_flag(L, 2); lua_pushboolean(L, flag == flag2); return 1; } static const luaL_reg u_flag_meta[] = { {"__gc", u_flag_gc}, {"__tostring", u_flag_tostring}, {"__index", u_flag_index}, {"__newindex", u_flag_newindex}, {"__eq", u_flag_eq}, {NULL, NULL} }; static const luaL_reg u_flag_methods[] = { {NULL,NULL} }; // system flags static int u_sys_list_flags (lua_State *L) { int i = 1; u_flag *fl; GList *cur; lua_newtable(L); cur = g_list_first(system_flags); while(cur) { fl = cur->data; lua_pushinteger(L, i); push_u_flag(L, fl, NULL, NULL); lua_settable(L, -3); i++; cur = g_list_next (cur); } return 1; } static int u_sys_add_flag (lua_State *L) { u_flag *flag = check_u_flag(L, 1); lua_pushinteger(L, u_flag_add(NULL, flag)); return 1; } static int u_sys_del_flag (lua_State *L) { u_flag *flag = check_u_flag(L, 1); lua_pushinteger(L, u_flag_del(NULL, flag)); return 1; } static int u_sys_clear_flag_name (lua_State *L) { const char *name = luaL_checkstring(L, 1); u_flag_clear_name(NULL, name); return 0; } static int u_sys_clear_flag_source (lua_State *L) { u_flag_clear_source(NULL, L); return 0; } static int u_sys_clear_flag_all (lua_State *L) { u_flag_clear_all(NULL); return 0; } static int u_sys_get_flags_changed(lua_State *L) { lua_pushboolean(L, system_flags_changed); return 1; } static int u_sys_set_flags_changed(lua_State *L) { system_flags_changed = luaL_checkint(L, 1); return 0; } int l_scheduler_run(lua_State *L, u_proc *proc) { int base = lua_gettop(L); char *key = "all"; int args = 1; int rv = 1; lua_getfield(L, LUA_GLOBALSINDEX, "ulatency"); /* function to be called */ lua_getfield(L, -1, "scheduler"); lua_remove(L, 1); if(lua_istable(L, 1)) { if(proc) { key = "one"; } lua_getfield(L, 1, key); if(!lua_isfunction(L, 2)) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "can't find lua scheduling handling function: %s", key); goto error; } lua_pushvalue(L, 1); if(proc) { push_u_proc(L, proc); args = 2; } if(docall(L, args, 1)) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "lua scheduler failed"); goto error; } if(!lua_toboolean(L, -1)) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "lua scheduler returned false"); } else { rv = 0; } } error: lua_pop(L, lua_gettop(L)-base); return rv; //stackdump_g(L); } static int wrap_l_scheduler_run() { return l_scheduler_run(lua_main_state, NULL); } static int wrap_l_scheduler_run_one(u_proc *proc) { return l_scheduler_run(lua_main_state, proc); } static int l_scheduler_set_config(char *name) { lua_State *L = lua_main_state; int base = lua_gettop(lua_main_state); int rv = FALSE; lua_getfield(L, LUA_GLOBALSINDEX, "ulatency"); /* function to be called */ lua_getfield(L, -1, "scheduler"); lua_remove(L, 1); if(lua_istable(L, 1) && name) { lua_getfield(L, 1, "set_config"); if(!lua_isfunction(L, 2)) { goto out; } lua_pushvalue(L, 1); lua_pushstring(L, name); if(docall(L, 2, 1)) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "lua scheduler.set_config failed"); goto out; } if(!lua_toboolean(L, -1)) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "lua scheduler.set_config returned false"); } else { rv = TRUE; } } out: lua_pop(L, lua_gettop(L)-base); return rv; } char *l_scheduler_get_config() { lua_State *L = lua_main_state; int base = lua_gettop(lua_main_state); char *rv = NULL; lua_getfield(L, LUA_GLOBALSINDEX, "ulatency"); /* function to be called */ lua_getfield(L, -1, "scheduler"); lua_remove(L, 1); if(lua_istable(L, 1)) { lua_getfield(L, 1, "get_config"); if(!lua_isfunction(L, 2)) { goto out; } lua_pushvalue(L, 1); if(docall(L, 1, 1)) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "lua scheduler.get_config failed"); goto out; } rv = g_strdup(lua_tostring(L, -1)); } out: lua_pop(L, lua_gettop(L)-base); return rv; } static GPtrArray *l_scheduler_list_configs() { lua_State *L = lua_main_state; int base = lua_gettop(lua_main_state); GPtrArray *rv = NULL; int len, i; lua_getfield(L, LUA_GLOBALSINDEX, "ulatency"); /* function to be called */ lua_getfield(L, -1, "scheduler"); lua_remove(L, 1); if(lua_istable(L, 1)) { lua_getfield(L, 1, "list_configs"); if(!lua_isfunction(L, 2)) { goto out; } lua_pushvalue(L, 1); lua_pushstring(L, "list_configs"); if(docall(L, 2, 1)) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "lua scheduler.list_configs failed"); goto out; } if(!lua_istable(L, -1)) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "lua scheduler.list_configs did't return a table"); } else { len = lua_objlen (L, -1); if(len) rv = g_ptr_array_new(); for(i = 1; i <= len; i++) { lua_pushinteger(L, i); lua_gettable(L, -2); g_ptr_array_add(rv, g_strdup(lua_tostring(L, -1))); lua_pop(L, 1); } } } out: lua_pop(L, lua_gettop(L)-base); return rv; } char *l_scheduler_get_description(char *name) { lua_State *L = lua_main_state; int base = lua_gettop(lua_main_state); char *rv = NULL; lua_getfield(L, LUA_GLOBALSINDEX, "ulatency"); /* function to be called */ lua_getfield(L, -1, "scheduler"); lua_remove(L, 1); if(lua_istable(L, 1) && name) { lua_getfield(L, 1, "get_config_description"); if(!lua_isfunction(L, 2)) { goto out; } lua_pushvalue(L, 1); lua_pushstring(L, name); if(docall(L, 2, 1)) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "lua scheduler.get_description failed"); goto out; } if(!lua_isnil(L, -1)) rv = g_strdup(lua_tostring(L, -1)); } out: lua_pop(L, lua_gettop(L)-base); return rv; } u_scheduler LUA_SCHEDULER = { .all=wrap_l_scheduler_run, .one=wrap_l_scheduler_run_one, .list_configs = l_scheduler_list_configs, .set_config = l_scheduler_set_config, .get_config = l_scheduler_get_config, .get_config_description = l_scheduler_get_description, }; // FILTER mappings int l_filter_run_table_proc(u_proc *proc, u_filter *flt, const char *key, int ignore) { gint rv; lua_State *L; struct lua_filter *lf = (struct lua_filter *)flt->data; g_assert(flt->type == FILTER_LUA); L = lf->lua_state; lua_rawgeti (L, LUA_REGISTRYINDEX, lf->lua_func); //lua_pushstring(lf->lua_state, "check") lua_getfield (L, -1, key); if(!lua_isfunction(L, -1)) { if(ignore) { lua_pop(L, 2); return 0; } g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "filter does not have a %s method, disable %s", key, flt->name); lua_pop(L, 2); return FILTER_STOP; } lua_pushvalue(L, -2); push_u_proc(L, proc); //cp_proc_t(proc, nproc); if(docall(L, 2, 1)) { // execution error. lua_pop(L, 1); return 0; } //stackdump_g(L); rv = lua_tointeger(L, -1); lua_pop(L, 2); return rv; } int l_filter_run_table(u_filter *flt, char *key) { gint rv; lua_State *L; struct lua_filter *lf = (struct lua_filter *)flt->data; g_assert(flt->type == FILTER_LUA); L = lf->lua_state; if(lf->min_percent && lf->min_percent >= get_last_percent()) return TRUE; lua_rawgeti (L, LUA_REGISTRYINDEX, lf->lua_func); //lua_pushstring(lf->lua_state, "check") lua_getfield (L, -1, key); if(!lua_isfunction(L, -1)) { lua_pop(L, 2); return TRUE; } lua_pushvalue(L, -2); if(docall(L, 1, 1)) { // execution error. lua_pop(L, 1); return FALSE; } rv = lua_toboolean(L, -1); lua_pop(L, 2); return rv; } int l_filter_callback(u_proc *proc, u_filter *flt) { return l_filter_run_table_proc(proc, flt, "check", FALSE); } int l_filter_exit(u_proc *proc, u_filter *flt) { return l_filter_run_table_proc(proc, flt, "exit", TRUE); } int l_filter_precheck(u_filter *flt) { return l_filter_run_table(flt, "precheck"); } int l_filter_postcheck(u_filter *flt) { return l_filter_run_table(flt, "postcheck"); } int l_filter_check(u_proc *proc, u_filter *flt) { struct lua_filter *lft = (struct lua_filter *)flt->data; if(lft->regexp_basename) { u_proc_ensure(proc, CMDLINE, FALSE); if(proc->cmdfile && g_regex_match(lft->regexp_basename, proc->cmdfile, 0, NULL)) return TRUE; } if(lft->regexp_cmdline) { u_proc_ensure(proc, CMDLINE, FALSE); if(proc->cmdline_match && g_regex_match(lft->regexp_cmdline, proc->cmdline_match, 0, NULL)) return TRUE; } /* lua_rawgeti (cd->lua_state, LUA_REGISTRYINDEX, cd->lua_func); lua_rawgeti (cd->lua_state, LUA_REGISTRYINDEX, cd->lua_data); //stackdump_g(cd->lua_state); lua_call (cd->lua_state, 1, 1); rv = lua_toboolean (cd->lua_state, -1); if(rv) return TRUE; luaL_unref (cd->lua_state, LUA_REGISTRYINDEX, cd->lua_func); free(data); return FALSE; */ return FALSE; } static GRegex *map_reg(lua_State *L, char *key) { GError *error = NULL; const char *tmp; GRegex *rv = NULL; lua_getfield (L, 1, key); //stackdump_g(L); if(lua_isstring(L, -1)) { rv = g_regex_new(lua_tostring(L, -1), G_REGEX_OPTIMIZE, 0, &error); if(error && error->code) { rv = NULL; luaL_where (L, 1); tmp = lua_tostring(L, -1); g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Error compiling filter in %s: %s", tmp, error->message ); g_error_free(error); lua_pop(L, 1); } } lua_pop(L, 1); return rv; } static int l_register_filter (lua_State *L) { lua_Debug ar; luaL_checktype(L, 1, LUA_TTABLE); //guint interval = luaL_checkint(L, 2); struct lua_filter *lf = malloc(sizeof(struct lua_filter)); u_filter *flt = filter_new(); memset(lf, 0, sizeof(struct lua_filter)); //lf->lua_state = L; lf->lua_state = lua_newthread (L); lf->lua_state_id = luaL_ref(L, LUA_REGISTRYINDEX); //lua_pushlightuserdata (L, flt); //lua_setfield (L, 1, "__u_source"); lua_pushvalue(L, 1); lf->lua_func = luaL_ref(L, LUA_REGISTRYINDEX); lua_pushlightuserdata (L, flt); lf->filter = luaL_ref(L, LUA_REGISTRYINDEX); lf->regexp_cmdline = map_reg(L, "re_cmdline"); lf->regexp_basename = map_reg(L, "re_basename"); lua_getfield (L, 1, "min_percent"); if (lua_isnumber(L, -1)) { lf->min_percent = lua_tonumber (L, 1); } else { lf->min_percent = 0.0; lua_pop(L, 1); } if(lf->regexp_cmdline || lf->regexp_basename || lf->min_percent) flt->check = l_filter_check; // construct a filter name if missing lua_getfield (L, 1, "name"); lua_getstack(L, 1, &ar); lua_getinfo(L, "Sl", &ar); if (lua_isstring(L, -1)) { lua_pushfstring(L, "%s: %s", ar.short_src, lua_tostring(L, -1)); } else { if (ar.currentline > 0) /* is there info? */ lua_pushfstring(L, "%s:%d (unknown)", ar.short_src, ar.currentline); else lua_pushstring(L, "(unknown)"); } flt->name = g_strdup(lua_tostring(L, -1)); // remove name strings lua_pop(L, 2); // finish data structures flt->data = lf; flt->callback = l_filter_callback; flt->precheck = l_filter_precheck; flt->postcheck = l_filter_postcheck; flt->exit = l_filter_exit; filter_register(flt, FALSE); return 0; } static int l_process_update (lua_State *L) { // DANGEROUS: can cause endless loop int pid = lua_tointeger(L, 1); if(pid) { process_update_pid(pid); } else { process_update_all(); } return 0; } static int l_run_interation (lua_State *L) { // DANGEROUS: can cause endless loop g_debug("run iteration from lua"); g_timeout_add(0, iterate, GUINT_TO_POINTER(0)); return 0; } static int l_get_uid (lua_State *L) { lua_pushinteger(L, getuid()); return 1; } static int l_get_time (lua_State *L) { time_t t = time(NULL); if(lua_isnumber(L, 1)) { t += lua_tointeger(L, 1); } lua_pushinteger(L, t); return 1; } static int user_load_lua_rule_file(lua_State *L) { char *full, *full2; const char *name = luaL_checkstring(L, 1); int abs = lua_toboolean(L, 2); if(!abs) { full = g_strconcat(QUOTEME(RULES_DIRECTORY), "/", name, NULL); full2 = realpath(full, NULL); if(!full2) { g_warning("load_rule_file: realpath failed for %s", full); g_free(full); return 0; } lua_pushboolean(L, !load_lua_rule_file(L, full2)); g_free(full); g_free(full2); } else { lua_pushboolean(L, !load_lua_rule_file(L, name)); } return 1; } static int user_load_rule_directory(lua_State *L) { char *full, *full2; const char *name = luaL_checkstring(L, 1); int abs = lua_toboolean(L, 2); if(!abs) { full = g_strconcat(QUOTEME(CONFIG_PATH), "/", name, NULL); full2 = realpath(full, NULL); if(!full2) { g_warning("load_rule_file: realpath failed for %s", full); g_free(full); return 0; } lua_pushboolean(L, !load_rule_directory(full2, NULL, FALSE)); g_free(full); g_free(full2); } else { lua_pushboolean(L, !load_rule_directory(name, NULL, FALSE)); } return 1; } static int l_uid_stats(lua_State *L) { GList *cur = U_session_list; u_session *sess; uid_t uid = luaL_checkinteger(L, 1); int act = 0; int idle = 1; while(cur) { sess = cur->data; if(sess->uid == uid) { if(sess->active) act = 1; if(!sess->idle) idle = 0; } cur = g_list_next(cur); } lua_pushboolean(L, act); lua_pushboolean(L, idle); return 2; } static int l_search_uid_env(lua_State *L) { uid_t uid = luaL_checkinteger(L, 1); const char *key = luaL_checkstring(L, 2); int update = FALSE; GPtrArray* data; if(lua_isnumber(L, 3)) update = lua_tointeger(L, 3); data = search_user_env(uid, key, update); l_ptrarray_to_table(L, data); g_ptr_array_unref(data); return 1; } static int l_get_sessions(lua_State *L) { GList *cur = U_session_list; u_session *sess; lua_newtable(L); int i = 1; while(cur) { sess = cur->data; lua_pushinteger(L, i); lua_newtable(L); lua_pushstring(L, sess->name); lua_setfield (L, -2, "name"); lua_pushstring(L, sess->X11Display); lua_setfield (L, -2, "X11Display"); lua_pushstring(L, sess->X11Device); lua_setfield (L, -2, "X11Device"); lua_pushstring(L, sess->dbus_session); lua_setfield (L, -2, "dbus_session"); lua_pushinteger(L, sess->uid); lua_setfield (L, -2, "uid"); lua_pushboolean(L, sess->idle); lua_setfield (L, -2, "idle"); lua_pushboolean(L, sess->active); lua_setfield (L, -2, "active"); lua_settable(L, -3); i++; cur = g_list_next(cur); } return 1; } #ifdef DEVELOP_MODE static int l_trap(lua_State *L) { asm("int3"); return 0; } #endif /* object table */ static const luaL_reg R[] = { // system load {"get_load", get_load}, {"get_uptime", get_uptime}, {"get_meminfo", get_meminfo}, {"get_vminfo", get_vminfo}, {"get_pid_digits", l_get_pid_digits}, {"get_last_load", l_get_last_load}, {"get_last_percent", l_get_last_percent}, // converts {"group_from_gid", l_group_from_guid}, {"user_from_uid", l_user_from_uid}, // pid receive {"get_pid", l_get_pid}, {"list_pids", l_list_pids}, {"list_processes", l_list_processes}, {"add_timeout", l_add_interval}, {"register_filter", l_register_filter}, {"get_number_of_processes", l_get_number_of_processes}, // flag code {"new_flag", l_flag_new}, // system flag manipulation {"list_flags", u_sys_list_flags}, {"add_flag", u_sys_add_flag}, {"del_flag", u_sys_del_flag}, {"clear_flag_name", u_sys_clear_flag_name}, {"clear_flag_source", u_sys_clear_flag_source}, {"clear_flag_all", u_sys_clear_flag_all}, {"get_flags_changed", u_sys_get_flags_changed}, {"set_flags_changed", u_sys_set_flags_changed}, // group code {"set_active_pid", l_set_active_pid}, {"get_active_uids", l_get_active_uids}, {"get_active_pids", l_get_active_pids}, // config {"get_config", l_get_config}, {"list_keys", l_list_keys}, // system & user querying {"get_sessions", l_get_sessions}, {"get_uid_stats", l_uid_stats}, {"search_uid_env", l_search_uid_env}, // misc {"filter_rv", l_filter_rv}, {"log", l_log}, {"get_uid", l_get_uid}, {"get_time", l_get_time}, {"quit_daemon", l_quit}, {"load_rule", user_load_lua_rule_file}, {"load_rule_directory", user_load_rule_directory}, {"process_update", l_process_update}, {"run_iteration", l_run_interation}, #ifdef DEVELOP_MODE {"trap", l_trap}, #endif {NULL, NULL} }; #undef PUSH_INT #undef PUSH_STR #define PUSH_INT(NAME, SYMBOLE)\ lua_pushinteger(L, SYMBOLE); \ lua_setfield(L, -2, #NAME); #define PUSH_STR(NAME, SYMBOLE)\ lua_pushstring(L, SYMBOLE); \ lua_setfield(L, -2, #NAME); int luaopen_ulatency(lua_State *L) { /* create metatable */ luaL_newmetatable(L, UL_META); /* metatable.__index = metatable */ lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); /* register module */ luaL_register(L, "ulatency", R); /* register metatable as socket_meta */ lua_pushvalue(L, -2); lua_setfield(L, -2, "meta_ulatency"); /* module version */ PUSH_STR(version, QUOTEME(VERSION)) PUSH_STR(release_agent, QUOTEME(RELEASE_AGENT)) PUSH_STR(path_rules_directory, QUOTEME(RULES_DIRECTORY)) PUSH_STR(path_config_directory, QUOTEME(CONFIG_PATH)) //PUSH_INT(hertz, Hertz) PUSH_INT(smp_num_cpus, smp_num_cpus) // glib log level PUSH_INT(LOG_LEVEL_ERROR, G_LOG_LEVEL_ERROR) PUSH_INT(LOG_LEVEL_CRITICAL, G_LOG_LEVEL_CRITICAL) PUSH_INT(LOG_LEVEL_WARNING, G_LOG_LEVEL_WARNING) PUSH_INT(LOG_LEVEL_MESSAGE, G_LOG_LEVEL_MESSAGE) PUSH_INT(LOG_LEVEL_INFO, G_LOG_LEVEL_INFO) PUSH_INT(LOG_LEVEL_DEBUG, G_LOG_LEVEL_DEBUG) PUSH_INT(LOG_LEVEL_SCHED, U_LOG_LEVEL_SCHED) PUSH_INT(LOG_LEVEL_TRACE, U_LOG_LEVEL_TRACE) PUSH_INT(FILTER_STOP, FILTER_STOP) PUSH_INT(FILTER_SKIP_CHILD, FILTER_SKIP_CHILD) PUSH_INT(IOPRIO_CLASS_NONE, IOPRIO_CLASS_NONE) PUSH_INT(IOPRIO_CLASS_RT, IOPRIO_CLASS_RT) PUSH_INT(IOPRIO_CLASS_BE, IOPRIO_CLASS_BE) PUSH_INT(IOPRIO_CLASS_IDLE, IOPRIO_CLASS_IDLE) // realtime priority stuff PUSH_INT(SCHED_OTHER, SCHED_OTHER) PUSH_INT(SCHED_FIFO, SCHED_FIFO) PUSH_INT(SCHED_RR, SCHED_RR) PUSH_INT(SCHED_BATCH, SCHED_BATCH) PUSH_INT(SCHED_IDLE, SCHED_IDLE) PUSH_INT(UPROC_NEW, UPROC_NEW) PUSH_INT(UPROC_INVALID, UPROC_INVALID) PUSH_INT(UPROC_ALIVE, UPROC_ALIVE) /* remove meta table */ lua_remove(L, -2); // map u_proc luaL_register(L, U_PROC, u_proc_methods); luaL_newmetatable(L, U_PROC_META); luaL_register(L, NULL, u_proc_meta); //lua_pushliteral(L, "__index"); //lua_pushvalue(L, -3); //lua_rawset(L, -3); /* metatable.__index = methods */ lua_pushliteral(L, "__metatable"); lua_pushvalue(L, -2); /* dup methods table*/ lua_rawset(L, -4); lua_pop(L, 1); // map u_proc luaL_register(L, U_PROC_TASK, u_task_methods); luaL_newmetatable(L, U_PROC_TASK_META); luaL_register(L, NULL, u_task_meta); //lua_pushliteral(L, "__index"); //lua_pushvalue(L, -3); //lua_rawset(L, -3); /* metatable.__index = methods */ lua_pushliteral(L, "__metatable"); lua_pushvalue(L, -2); /* dup methods table*/ lua_rawset(L, -4); lua_pop(L, 1); // map u_filter luaL_register(L, U_FLAG, u_flag_methods); luaL_newmetatable(L, U_FLAG_META); luaL_register(L, NULL, u_flag_meta); //lua_pushliteral(L, "__index"); //lua_pushvalue(L, -3); //lua_rawset(L, -3); /* metatable.__index = methods */ lua_pushliteral(L, "__metatable"); lua_pushvalue(L, -2); /* dup methods table*/ lua_rawset(L, -4); lua_pop(L, 1); luaL_ref(L, LUA_REGISTRYINDEX); luaL_ref(L, LUA_REGISTRYINDEX); luaL_ref(L, LUA_REGISTRYINDEX); luaL_ref(L, LUA_REGISTRYINDEX); luaL_ref(L, LUA_REGISTRYINDEX); return 1; } // misc functions static int report (lua_State *L, int status) { if (status && !lua_isnil(L, -1)) { const char *msg = lua_tostring(L, -1); if (msg == NULL) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "(status: %d, error object is not a string. it is: %s)", status, lua_typename(L, -1)); } else { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "%d: %s", status, msg); } lua_pop(L, 1); } //stackdump_g(L); return status; } static int traceback (lua_State *L) { //if (!lua_isstring(L, 1)) /* 'message' not a string? */ // return 1; /* keep it intact */ //lua_tostring(L, 1); if (!lua_isstring(L, 1)) { lua_getfield(L, LUA_GLOBALSINDEX, "tostring"); //lua_pushvalue(L, 1); /* pass error message */ lua_call(L, 1, 1); } lua_getfield(L, LUA_GLOBALSINDEX, "debug"); if (!lua_istable(L, -1)) { lua_pop(L, 1); return 1; } lua_getfield(L, -1, "traceback"); if (!lua_isfunction(L, -1)) { lua_pop(L, 2); return 1; } //lua_pushliteral(L, "bla"); lua_pushvalue(L, 1); /* pass error message */ lua_pushinteger(L, 2); /* skip this function and traceback */ lua_call(L, 2, 1); /* call debug.traceback */ return 1; } static int docall (lua_State *L, int narg, int nresults) { int status; int base = lua_gettop(L) - narg; /* function index */ //printf("docall\n"); lua_pushcfunction(L, traceback); /* push traceback function */ lua_insert(L, base); /* put it under chunk and args */ //stackdump_g(L); status = lua_pcall(L, narg, nresults, base); //printf("--- %d\n", status); //stackdump_g(L); lua_remove(L, base); /* remove traceback function */ /* force a complete garbage collection in case of errors */ if (status != 0) { report(L, status); lua_gc(L, LUA_GCCOLLECT, 0); } //stackdump_g(L); return status; } int load_lua_rule_file(lua_State *L, const char *name) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "load %s", name); if(luaL_loadfile(L, name)) { report(L, 1); return 1; } if(lua_pcall(L, 0, LUA_MULTRET, 0)) { report(L, 1); return 1; } return 0; } poelzi-ulatencyd-55515a9/src/lua_cgroup.c000066400000000000000000000420711154664534000203710ustar00rootroot00000000000000/* Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. */ #include "ulatency.h" #include #include #include #include #include #define CGROUP "cgroup_cgroup" #define CGROUP_CONTROLLER "cgroup_controller" #define CGROUP_META "cgroup" //typedef struct cgroup *cgroup_ptr; //typedef struct cgroup_controller *cgroup_controller_ptr; // init static int l_cgroup_get_subsys_mount_point (lua_State *L) { const char *cnt = luaL_checkstring(L, 1); char *out = NULL; cgroup_get_subsys_mount_point(cnt, &out); if(!out) return 0; lua_pushstring(L, out); free(out); return 1; } // configuration static int l_cgroup_config_load_config(lua_State *L) { lua_pushinteger(L, cgroup_config_load_config(luaL_checkstring(L, 1))); return 1; } // error handling static int l_cgroup_strerror (lua_State *L) { int eint = 0; const char *out = NULL; if(lua_isnumber(L, 1)) eint = lua_tointeger(L, 1); else eint = cgroup_get_last_errno(); out = cgroup_strerror(eint); if(out) { lua_pushstring(L, out); return 1; } return 0; } static int l_cgroup_get_last_errno(lua_State *L) { lua_pushinteger(L, cgroup_get_last_errno()); return 1; } // group manipulation // workaround structs to handle bad static struct u_cgroup *check_cgroup (lua_State *L, int index) { struct u_cgroup *p; luaL_checktype(L, index, LUA_TUSERDATA); p = (struct u_cgroup *)luaL_checkudata(L, index, CGROUP); if (p == NULL) luaL_typerror(L, index, CGROUP); return p; } static struct u_cgroup *push_cgroup(lua_State *L) { // proc_t *p = (proc_t*)lua_newuserdata(L, sizeof(proc_t)); struct u_cgroup *p = /*(struct cproc *)*/lua_newuserdata(L, sizeof(struct u_cgroup)); //struct cgroup *p = (struct cproc *)lua_newuserdata(L, sizeof(struct cgroup *)); memset(p, 0, sizeof(struct u_cgroup)); luaL_getmetatable(L, CGROUP); lua_setmetatable(L, -2); return p; } static int cgroup_tostring (lua_State *L) { struct u_cgroup *group = lua_touserdata(L, 1); lua_pushfstring(L, "cgroup: %s <%p>", group->name, group->group); return 1; } /*static int proc_t_gc (lua_State *L) { proc_t *proc = check_proc_t(L, 1); //printf("goodbye proc_t (%p)\n", proc); if (proc) { freesupgrp(proc); freeproc_light(proc); } return 0; } */ static struct u_cgroup_controller *check_cgroup_controller (lua_State *L, int index) { struct u_cgroup_controller *p; luaL_checktype(L, index, LUA_TUSERDATA); p = (struct u_cgroup_controller *)luaL_checkudata(L, index, CGROUP_CONTROLLER); if (p == NULL) luaL_typerror(L, index, CGROUP_CONTROLLER); return p; } static struct u_cgroup_controller *push_cgroup_controller(lua_State *L) { struct u_cgroup_controller *p = lua_newuserdata(L, sizeof(struct u_cgroup_controller)); memset(p, 0, sizeof(struct u_cgroup_controller)); luaL_getmetatable(L, CGROUP_CONTROLLER); lua_setmetatable(L, -2); return p; } static int cgroup_controller_tostring (lua_State *L) { struct u_cgroup *group = lua_touserdata(L, 1); lua_pushfstring(L, "cgroup_controller: %s <%p>", group->name, group->group); return 1; } static int l_cgroup_new_cgroup (lua_State *L) { //struct cgroup_controller *cc, *cc2 = NULL; struct u_cgroup *cgp; struct cgroup *cg; const char *name = luaL_checkstring(L, 1); cg = cgroup_new_cgroup(name); if(cg) { cgp = push_cgroup(L); cgp->group = cg; cgp->name = g_strdup(name); cgp->ref = 1; return 1; } return 0; } static int l_cgroup_add_controller (lua_State *L) { struct cgroup_controller *cc; struct u_cgroup_controller *uc = NULL; struct u_cgroup *cg = check_cgroup(L, 1); const char *name = luaL_checkstring(L, 2); if (cg) { cc = cgroup_add_controller (cg->group, name); if(cc) { uc = push_cgroup_controller(L); uc->controller = cc; uc->name = g_strdup(name); uc->ref = 1; } return 1; } lua_pushstring(L, "Not a valid cgroup"); lua_error (L); return 0; } static int l_cgroup_get_controller (lua_State *L) { struct cgroup_controller *cc; struct u_cgroup_controller *uc = NULL; struct u_cgroup *cg = check_cgroup(L, 1); const char *name = luaL_checkstring(L, 2); if (cg) { cc = cgroup_get_controller (cg->group, name); if(cc) { uc = push_cgroup_controller(L); uc->controller = cc; uc->name = g_strdup(name); uc->ref = 1; } return 1; } lua_pushstring(L, "Not a valid cgroup"); lua_error (L); return 0; } /* static int l_cgroup_get_controller (lua_State *L) { struct cgroup_controller *cc, *cc2 = NULL; struct cgroup *cg = check_cgroup(L, 1); const char *name = luaL_checkstring(L, 2); if (cg) { cc = cgroup_get_controller (cg, name); if(cc) { cc2 = push_cgroup_controller(L); cc2 = cc; } return 1; } lua_pushstring(L, "Not a valid cgroup"); lua_error (L); return 0; } */ static int l_cgroup_free_controllers (lua_State *L) { struct u_cgroup *cg = check_cgroup(L, 1); if (cg) { cgroup_free_controllers (cg->group); lua_pushboolean(L, 1); return 1; } lua_pushstring(L, "Not a valid cgroup"); lua_error (L); return 0; } #define CGROUP_INT(NAME, ARGS) \ static int l_##NAME (lua_State *L) \ { \ struct u_cgroup *cg = check_cgroup(L, 1); \ int para = lua_tointeger(L, 2); \ int rv = 0; \ if (cg) { \ rv = NAME ARGS ; \ lua_pushinteger(L, rv); \ return 1; \ } \ lua_pushstring(L, "Not a valid cgroup"); \ lua_error (L); \ return 0; \ } CGROUP_INT(cgroup_create_cgroup,(cg->group, para)); CGROUP_INT(cgroup_create_cgroup_from_parent,(cg->group, para)); //CGROUP_INT(cgroup_modify_cgroup,(cg->group)); CGROUP_INT(cgroup_delete_cgroup,(cg->group, para)); CGROUP_INT(cgroup_delete_cgroup_ext,(cg->group, para)); //CGROUP_INT(cgroup_get_cgroup,(cg->group)); static int l_cgroup_get_cgroup (lua_State *L) \ { \ struct u_cgroup *cg = check_cgroup(L, 1); \ int para = lua_tointeger(L, 2); \ int rv = 0; \ if (cg) { \ rv = cgroup_get_cgroup (cg->group) ; \ lua_pushinteger(L, rv); \ return 1; \ } \ lua_pushstring(L, "Not a valid cgroup"); \ lua_error (L); \ return 0; \ } static int l_cgroup_modify_cgroup (lua_State *L) { struct u_cgroup *cg = check_cgroup(L, 1); int para = lua_tointeger(L, 2); int rv = 0; if (cg) { rv = cgroup_modify_cgroup(cg->group); lua_pushinteger(L, rv); return 1; } lua_pushstring(L, "Not a valid cgroup"); lua_error (L); return 0; } static int l_cgroup_attach_task_pid(lua_State *L) { struct u_cgroup *uc = check_cgroup(L, 1); int pid = luaL_checkinteger(L, 2); lua_pushinteger(L, cgroup_attach_task_pid(uc->group, pid)); return 1; } static int l_cgroup_copy_cgroup (lua_State *L) { struct u_cgroup *udst = check_cgroup(L, 1); struct u_cgroup *usrc = check_cgroup(L, 2); int rv; if(udst && usrc) { rv = cgroup_copy_cgroup(udst->group, usrc->group); lua_pushinteger(L, rv); return 1; } lua_pushstring(L, "Not a valid cgroup"); lua_error (L); return 0; } static int l_cgroup_compare_cgroup (lua_State *L) { struct u_cgroup *usrc = check_cgroup(L, 1); struct u_cgroup *udst = check_cgroup(L, 2); int rv; if(udst && usrc) { rv = cgroup_compare_cgroup(udst->group, usrc->group); lua_pushinteger(L, rv); return 1; } lua_pushstring(L, "Not a valid cgroup"); lua_error (L); return 0; } static int l_cgroup_compare_controllers (lua_State *L) { struct u_cgroup_controller *usrc = check_cgroup_controller(L, 1); struct u_cgroup_controller *udst = check_cgroup_controller(L, 2); int rv; if(udst && usrc) { rv = cgroup_compare_controllers(udst->controller, usrc->controller); lua_pushinteger(L, rv); return 1; } lua_pushstring(L, "Not a valid cgroup"); lua_error (L); return 0; } static int l_cgroup_set_uid_gid(lua_State *L) { struct u_cgroup *usrc = check_cgroup(L, 1); int tuid = luaL_checkinteger(L, 2); int tgid = luaL_checkinteger(L, 3); int cuid = luaL_checkinteger(L, 4); int cgid = luaL_checkinteger(L, 5); int rv; if(usrc) { rv = cgroup_set_uid_gid(usrc->group, tuid, tgid, cuid, cgid); lua_pushinteger(L, rv); return 1; } lua_pushstring(L, "Not a valid cgroup"); lua_error (L); return 0; } static int l_cgroup_get_uid_gid(lua_State *L) { struct u_cgroup *usrc = check_cgroup(L, 1); int tuid, tgid, cuid, cgid; int rv; if(usrc) { rv = cgroup_get_uid_gid(usrc->group, &tuid, &tgid, &cuid, &cgid); lua_pushinteger(L, rv); lua_pushinteger(L, tuid); lua_pushinteger(L, tgid); lua_pushinteger(L, cuid); lua_pushinteger(L, cgid); return 5; } lua_pushstring(L, "Not a valid cgroup"); lua_error (L); return 0; } static int l_cgroup_add_value(lua_State *L) { struct u_cgroup_controller *uc = check_cgroup_controller(L, 1); const char *key = lua_tostring(L, 2); int rv; if(!uc) { lua_pushstring(L, "Not a valid cgroup"); lua_error (L); return 0; } if(lua_isstring(L, 3)) { rv = cgroup_add_value_string(uc->controller, key, lua_tostring(L, 3)); } else if(lua_isnumber(L, 3)) { rv = cgroup_add_value_int64(uc->controller, key, lua_tointeger(L, 3)); } else if(lua_isboolean(L, 3)) { rv = cgroup_add_value_bool(uc->controller, key, lua_toboolean(L, 3)); } else { lua_pushstring(L, "Not a valid type"); lua_error (L); } lua_pushinteger(L, rv); return 1; } static int l_cgroup_set_value(lua_State *L) { struct u_cgroup_controller *uc = check_cgroup_controller(L, 1); const char *key = lua_tostring(L, 2); int rv; if(!uc) { lua_pushstring(L, "Not a valid cgroup"); lua_error (L); return 0; } if(lua_isstring(L, 3)) { rv = cgroup_set_value_string(uc->controller, key, lua_tostring(L, 3)); } else if(lua_isnumber(L, 3)) { rv = cgroup_set_value_int64(uc->controller, key, lua_tointeger(L, 3)); } else if(lua_isboolean(L, 3)) { rv = cgroup_set_value_bool(uc->controller, key, lua_toboolean(L, 3)); } else { lua_pushstring(L, "Not a valid type"); lua_error (L); } lua_pushinteger(L, rv); return 1; } static int l_cgroup_get_value_int(lua_State *L) { struct u_cgroup_controller *uc = check_cgroup_controller(L, 1); const char *key = lua_tostring(L, 2); int64_t rv; if(!cgroup_get_value_int64 (uc->controller, key, &rv)) { lua_pushinteger(L, rv); return 1; } return 0; } static int l_cgroup_get_value_bool(lua_State *L) { struct u_cgroup_controller *uc = check_cgroup_controller(L, 1); const char *key = lua_tostring(L, 2); bool rv; if(!cgroup_get_value_bool(uc->controller, key, &rv)) { lua_pushboolean(L, rv); return 1; } return 0; } static int l_cgroup_get_value_str(lua_State *L) { struct u_cgroup_controller *uc = check_cgroup_controller(L, 1); const char *key = lua_tostring(L, 2); char *rv; if(!cgroup_get_value_string(uc->controller, key, &rv)) { lua_pushstring(L, rv); return 1; } return 0; } static int l_cgroup_get_names(lua_State *L) { struct u_cgroup_controller *uc = check_cgroup_controller(L, 1); int count = cgroup_get_value_name_count(uc->controller); int i; if(count == -1) return 0; count++; lua_createtable(L, count, 0); for(i = 1; i < count; i++) { lua_pushinteger(L, i); lua_pushstring(L, cgroup_get_value_name(uc->controller, i-1)); lua_settable(L, -3); } return 1; } static int l_cgroup_get_name(lua_State *L) { struct u_cgroup *uc = check_cgroup(L, 1); if(uc->name) lua_pushstring(L, uc->name); else return 0; return 1; } static int l_cgroup_get_name_controller(lua_State *L) { struct u_cgroup_controller *uc = check_cgroup_controller(L, 1); if(uc->name) lua_pushstring(L, uc->name); else return 0; return 1; } /* cgroup_copy_cgroup (struct cgroup *dst, struct cgroup *src) static int l_cgroup_copy_cgroup (lua_State *L) { struct cgroup_controller *cc, *cc2 = NULL; struct cgroup *cg = check_cgroup(L, 1); if (cg) { cgroup_free_controllers (cg); lua_pushboolean(L, 1); return 1; } lua_pushstring(L, "Not a valid cgroup"); lua_error (L); return 0; } */ static const luaL_reg cgroup_meta[] = { //{"__gc", proc_t_gc}, {"__tostring", cgroup_tostring}, {NULL, NULL} }; static const luaL_reg cgroup_methods[] = { {"add_controller", l_cgroup_add_controller}, {"get_controller", l_cgroup_get_controller}, {"free_controller", l_cgroup_free_controllers}, {"create_cgroup", l_cgroup_create_cgroup}, {"create_cgroup_from_parent", l_cgroup_create_cgroup_from_parent}, {"modify_cgroup", l_cgroup_modify_cgroup}, {"delete_cgroup", l_cgroup_delete_cgroup}, {"delete_cgroup_ext", l_cgroup_delete_cgroup_ext}, {"get_cgroup", l_cgroup_get_cgroup}, {"attach_pid", l_cgroup_attach_task_pid}, {"copy_from", l_cgroup_copy_cgroup}, {"get_name", l_cgroup_get_name}, {NULL,NULL} }; static const luaL_reg cgroup_controller_meta[] = { //{"__gc", proc_t_gc}, {"__tostring", cgroup_controller_tostring}, //{"__index", proc_t_index}, {NULL, NULL} }; static const luaL_reg cgroup_controller_methods[] = { {"add_value", l_cgroup_add_value}, {"set_value", l_cgroup_set_value}, {"get_value_int", l_cgroup_get_value_int}, {"get_value_bool", l_cgroup_get_value_bool}, {"get_value_string", l_cgroup_get_value_str}, {"get_names", l_cgroup_get_names}, {"get_name", l_cgroup_get_name_controller}, {NULL,NULL} }; /* object table */ static const luaL_reg R[] = { // system load {"get_errorno", l_cgroup_get_last_errno}, {"get_strerror", l_cgroup_strerror}, {"get_subsys_mount_point", l_cgroup_get_subsys_mount_point}, {"load_config", l_cgroup_config_load_config}, {"new_cgroup", l_cgroup_new_cgroup}, {NULL, NULL} }; #undef PUSH_INT #define PUSH_INT(NAME, SYMBOLE)\ lua_pushinteger(L, SYMBOLE); \ lua_setfield(L, -2, #NAME); int luaopen_cgroup(lua_State *L) { /* create metatable */ luaL_newmetatable(L, CGROUP_META); /* metatable.__index = metatable */ lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); /* register module */ luaL_register(L, "cgroups", R); /* register metatable as socket_meta */ lua_pushvalue(L, -2); lua_setfield(L, -2, "meta_cgroups"); /* module version */ // PUSH_INT(version, VERSION) PUSH_INT(CGFLAG_DELETE_IGNORE_MIGRATION, CGFLAG_DELETE_IGNORE_MIGRATION) PUSH_INT(CGFLAG_DELETE_RECURSIVE, CGFLAG_DELETE_RECURSIVE) // error ints PUSH_INT(ECGMOUNTNAMESPACE, ECGMOUNTNAMESPACE) PUSH_INT(ECGNAMESPACECONTROLLER, ECGNAMESPACECONTROLLER) PUSH_INT(ECGNAMESPACEPATHS, ECGNAMESPACEPATHS) PUSH_INT(ECGCONFIGPARSEFAIL, ECGCONFIGPARSEFAIL) PUSH_INT(ECGEOF, ECGEOF) PUSH_INT(ECGSENTINEL, ECGSENTINEL) PUSH_INT(ECGMOUNTFAIL, ECGMOUNTFAIL) PUSH_INT(ECGROUPNORULES, ECGROUPNORULES) PUSH_INT(ECGROUPPARSEFAIL, ECGROUPPARSEFAIL) PUSH_INT(ECGCONTROLLERNOTEQUAL, ECGCONTROLLERNOTEQUAL) PUSH_INT(ECGROUPNOTEQUAL, ECGROUPNOTEQUAL) PUSH_INT(ECGOTHER, ECGOTHER) PUSH_INT(ECGROUPVALUENOTEXIST, ECGROUPVALUENOTEXIST) PUSH_INT(ECGROUPNOTINITIALIZED, ECGROUPNOTINITIALIZED) PUSH_INT(ECGFAIL, ECGFAIL) PUSH_INT(ECGCONTROLLERCREATEFAILED, ECGCONTROLLERCREATEFAILED) PUSH_INT(ECGINVAL, ECGINVAL) PUSH_INT(ECGVALUEEXISTS, ECGVALUEEXISTS) PUSH_INT(ECGCONTROLLEREXISTS, ECGCONTROLLEREXISTS) PUSH_INT(ECGMAXVALUESEXCEEDED, ECGMAXVALUESEXCEEDED) PUSH_INT(ECGROUPNOTALLOWED, ECGROUPNOTALLOWED) PUSH_INT(ECGROUPMULTIMOUNTED, ECGROUPMULTIMOUNTED) PUSH_INT(ECGROUPNOTOWNER, ECGROUPNOTOWNER) PUSH_INT(ECGROUPSUBSYSNOTMOUNTED, ECGROUPSUBSYSNOTMOUNTED) PUSH_INT(ECGROUPNOTCREATED, ECGROUPNOTCREATED) PUSH_INT(ECGROUPNOTEXIST, ECGROUPNOTEXIST) PUSH_INT(ECGROUPNOTMOUNTED, ECGROUPNOTMOUNTED) PUSH_INT(ECGROUPNOTCOMPILED, ECGROUPNOTCOMPILED) /* remove meta table */ lua_remove(L, -2); // map cgroup luaL_register(L, CGROUP, cgroup_methods); luaL_newmetatable(L, CGROUP); luaL_register(L, NULL, cgroup_meta); lua_pushliteral(L, "__index"); lua_pushvalue(L, -3); lua_rawset(L, -3); /* metatable.__index = methods */ lua_pushliteral(L, "__metatable"); lua_pushvalue(L, -3); /* dup methods table*/ lua_rawset(L, -3); lua_pop(L, 1); // map cgroup_controller luaL_register(L, CGROUP_CONTROLLER, cgroup_controller_methods); luaL_newmetatable(L, CGROUP_CONTROLLER); luaL_register(L, NULL, cgroup_controller_meta); lua_pushliteral(L, "__index"); lua_pushvalue(L, -3); lua_rawset(L, -3); /* metatable.__index = methods */ lua_pushliteral(L, "__metatable"); lua_pushvalue(L, -3); /* dup methods table*/ lua_rawset(L, -3); lua_pop(L, 1); return 1; } poelzi-ulatencyd-55515a9/src/nls.h000066400000000000000000000026201154664534000170260ustar00rootroot00000000000000/* Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. */ #ifndef UTIL_LINUX_NLS_H #define UTIL_LINUX_NLS_H #ifndef __THROW #define __THROW #endif #ifndef LOCALEDIR #define LOCALEDIR "/usr/share/locale" #endif #ifdef HAVE_LOCALE_H # include #else # undef setlocale # define setlocale(Category, Locale) /* empty */ #endif #ifdef ENABLE_NLS # include # define _(Text) gettext (Text) # ifdef gettext_noop # define N_(String) gettext_noop (String) # else # define N_(String) (String) # endif #else # undef bindtextdomain # define bindtextdomain(Domain, Directory) /* empty */ # undef textdomain # define textdomain(Domain) /* empty */ # define _(Text) (Text) # define N_(Text) (Text) #endif #endif /* UTIL_LINUX_NLS_H */ poelzi-ulatencyd-55515a9/src/polkit.c000066400000000000000000000101671154664534000175340ustar00rootroot00000000000000#include "config.h" #include "ulatency.h" #include #include #include #include #include #include #include #define DMAX 5000 static void check_authorization_cb (PolkitAuthority *authority, GAsyncResult *res, gpointer user_data) { struct callback_data *data = user_data; PolkitAuthorizationResult *result; GError *error; error = NULL; result = polkit_authority_check_authorization_finish (authority, res, &error); if (error != NULL) { g_warning("Error checking authorization: %s\n", error->message); g_error_free (error); DBusMessage *ret = dbus_message_new_method_return(data->message); dbus_connection_send(data->connection, ret, NULL); dbus_message_unref(ret); } else { const gchar *result_str; if (polkit_authorization_result_get_is_authorized (result)) { g_debug("Authorization result: authorized"); data->callback(data); } else { if (polkit_authorization_result_get_is_challenge (result)) result_str = "challenge"; else result_str = "not authorized"; g_debug ("Authorization result: %s\n", result_str); } DBusMessage *ret = dbus_message_new_method_return(data->message); dbus_connection_send(data->connection, ret, NULL); dbus_message_unref(ret); } dbus_connection_unref(data->connection); dbus_message_unref(data->message); } int check_polkit(const char *methode, DBusConnection *connection, DBusMessage *message, char *action_id, void (*callback)(struct callback_data *data), void *user_data, int allow_user_interaction, u_proc *proc, char *config) { PolkitSubject *subject; PolkitDetails *details; PolkitCheckAuthorizationFlags flags; gchar tmp[DMAX+1]; /* Set details - see polkit-action-lookup.c for where * these key/value pairs are used */ printf("a\n"); details = polkit_details_new (); if (proc != NULL) { g_snprintf(&tmp[0], DMAX, "%d", proc->pid); polkit_details_insert (details, "pid", &tmp[0]); g_snprintf(&tmp[0], DMAX, "%d", proc->proc.ppid); polkit_details_insert (details, "ppid", &tmp[0]); g_snprintf(&tmp[0], DMAX, "%d", proc->proc.tpgid); polkit_details_insert (details, "gid", &tmp[0]); g_snprintf(&tmp[0], DMAX, "%d", proc->proc.pgrp); polkit_details_insert (details, "pgrp", &tmp[0]); g_snprintf(&tmp[0], DMAX, "%d", proc->proc.session); polkit_details_insert (details, "session", &tmp[0]); } if(config) { polkit_details_insert (details, "config", config); } subject = polkit_system_bus_name_new (dbus_message_get_sender (message)); //subject = polkit_unix_process_new (getpid()); struct callback_data *data = g_malloc0(sizeof(struct callback_data)); data->cancellable = g_cancellable_new (); dbus_connection_ref(connection); data->connection = connection; dbus_message_ref(message); data->message = message; data->user_data = user_data; data->callback = callback; flags = POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE; if (allow_user_interaction) flags |= POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION; g_debug("check authorization via polkit"); //g_timeout_add (10 * 1000, // (GSourceFunc) do_cancel, // data->cancellable); polkit_authority_check_authorization (U_polkit_authority, subject, action_id, details, flags, data->cancellable, (GAsyncReadyCallback) check_authorization_cb, data); g_object_unref (subject); g_object_unref (details); return TRUE; } poelzi-ulatencyd-55515a9/src/proc/000077500000000000000000000000001154664534000170245ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/src/proc/CMakeLists.txt000066400000000000000000000005001154664534000215570ustar00rootroot00000000000000#-DVERSION=\"$(VERSION)\" -DSUBVERSION=\"$(SUBVERSION)\" -DMINORVERSION=\"$(MINORVERSION)\" add_definitions(-DVERSION=\"3\" -DSUBVERSION=\"2\" -DMINORVERSION=\"8\") add_library(proc STATIC alloc.c devname.c escape.c ksym.c pwcache.c readproc.c sig.c slab.c smaps.c sysinfo.c version.c whattime.c) poelzi-ulatencyd-55515a9/src/proc/alloc.c000066400000000000000000000021301154664534000202560ustar00rootroot00000000000000// Copyright (C) 1992-1998 by Michael K. Johnson, johnsonm@redhat.com // Copyright 2002 Albert Cahalan // // This file is placed under the conditions of the GNU Library // General Public License, version 2, or any later version. // See file COPYING for information on distribution conditions. #include #include #include #include "alloc.h" void *xcalloc(void *pointer, int size) { void * ret; if (pointer) free(pointer); if (!(ret = calloc(1, size))) { fprintf(stderr, "xcalloc: allocation error, size = %d\n", size); exit(1); } return ret; } void *xmalloc(unsigned int size) { void *p; if (size == 0) ++size; p = malloc(size); memset(p, 0, size); if (!p) { fprintf(stderr, "xmalloc: malloc(%d) failed", size); perror(NULL); exit(1); } return(p); } void *xrealloc(void *oldp, unsigned int size) { void *p; if (size == 0) ++size; p = realloc(oldp, size); if (!p) { fprintf(stderr, "xrealloc: realloc(%d) failed", size); perror(NULL); exit(1); } return(p); } poelzi-ulatencyd-55515a9/src/proc/alloc.h000066400000000000000000000004271154664534000202720ustar00rootroot00000000000000#ifndef PROCPS_PROC_ALLOC_H #define PROCPS_PROC_ALLOC_H #include "procps.h" EXTERN_C_BEGIN extern void *xrealloc(void *oldp, unsigned int size) MALLOC; extern void *xmalloc(unsigned int size) MALLOC; extern void *xcalloc(void *pointer, int size) MALLOC; EXTERN_C_END #endif poelzi-ulatencyd-55515a9/src/proc/devname.c000066400000000000000000000274241154664534000206200ustar00rootroot00000000000000/* * Copyright 1998-2002 by Albert Cahalan; all rights resered. * This file may be used subject to the terms and conditions of the * GNU Library General Public License Version 2, or any later version * at your option, as published by the Free Software Foundation. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. */ #include #include #include #include #include #include #include #include "version.h" #include "devname.h" // This is the buffer size for a tty name. Any path is legal, // which makes PAGE_SIZE appropriate (see kernel source), but // that is only 99% portable and utmp only holds 32 anyway. // We need at least 20 for guess_name(). #define TTY_NAME_SIZE 128 /* Who uses what: * * tty_to_dev w (there is a fancy version in ps) * dev_to_tty top, ps */ #if 0 #include #define MAJOR_OF(d) ((unsigned)major(d)) #define MINOR_OF(d) ((unsigned)minor(d)) #else #define MAJOR_OF(d) ( ((unsigned)(d)>>8u) & 0xfffu ) #define MINOR_OF(d) ( ((unsigned)(d)&0xffu) | (((unsigned)(d)&0xfff00000u)>>12u) ) #undef major #undef minor #define major <-- do not use --> #define minor <-- do not use --> #endif typedef struct tty_map_node { struct tty_map_node *next; unsigned short devfs_type; // bool unsigned short major_number; unsigned minor_first; unsigned minor_last; char name[16]; } tty_map_node; static tty_map_node *tty_map = NULL; /* Load /proc/tty/drivers for device name mapping use. */ static void load_drivers(void){ char buf[10000]; char *p; int fd; int bytes; fd = open("/proc/tty/drivers",O_RDONLY); if(fd == -1) goto fail; bytes = read(fd, buf, sizeof(buf) - 1); if(bytes == -1) goto fail; buf[bytes] = '\0'; p = buf; while(( p = strstr(p, " /dev/") )){ // " /dev/" is the second column tty_map_node *tmn; int len; char *end; p += 6; end = strchr(p, ' '); if(!end) continue; len = end - p; tmn = calloc(1, sizeof(tty_map_node)); tmn->next = tty_map; tty_map = tmn; /* if we have a devfs type name such as /dev/tts/%d then strip the %d but keep a flag. */ if(len >= 3 && !strncmp(end - 2, "%d", 2)){ len -= 2; tmn->devfs_type = 1; } if(len >= sizeof tmn->name) len = sizeof tmn->name - 1; // mangle it to avoid overflow memcpy(tmn->name, p, len); p = end; /* set p to point past the %d as well if there is one */ while(*p == ' ') p++; tmn->major_number = atoi(p); p += strspn(p, "0123456789"); while(*p == ' ') p++; switch(sscanf(p, "%u-%u", &tmn->minor_first, &tmn->minor_last)){ default: /* Can't finish parsing this line so we remove it from the list */ tty_map = tty_map->next; free(tmn); break; case 1: tmn->minor_last = tmn->minor_first; break; case 2: break; } } fail: if(fd != -1) close(fd); if(!tty_map) tty_map = (tty_map_node *)-1; } /* Try to guess the device name from /proc/tty/drivers info. */ static int driver_name(char *restrict const buf, unsigned maj, unsigned min){ struct stat sbuf; tty_map_node *tmn; if(!tty_map) load_drivers(); if(tty_map == (tty_map_node *)-1) return 0; tmn = tty_map; for(;;){ if(!tmn) return 0; if(tmn->major_number == maj && tmn->minor_first <= min && tmn->minor_last >= min) break; tmn = tmn->next; } sprintf(buf, "/dev/%s%d", tmn->name, min); /* like "/dev/ttyZZ255" */ if(stat(buf, &sbuf) < 0){ if(tmn->devfs_type) return 0; sprintf(buf, "/dev/%s", tmn->name); /* like "/dev/ttyZZ255" */ if(stat(buf, &sbuf) < 0) return 0; } if(min != MINOR_OF(sbuf.st_rdev)) return 0; if(maj != MAJOR_OF(sbuf.st_rdev)) return 0; return 1; } // major 204 is a mess -- "Low-density serial ports" static const char low_density_names[][6] = { "LU0", "LU1", "LU2", "LU3", "FB0", "SA0", "SA1", "SA2", "SC0", "SC1", "SC2", "SC3", "FW0", "FW1", "FW2", "FW3", "AM0", "AM1", "AM2", "AM3", "AM4", "AM5", "AM6", "AM7", "AM8", "AM9", "AM10", "AM11", "AM12", "AM13", "AM14", "AM15", "DB0", "DB1", "DB2", "DB3", "DB4", "DB5", "DB6", "DB7", "SG0", "SMX0", "SMX1", "SMX2", "MM0", "MM1", "CPM0", "CPM1", "CPM2", "CPM3", /* "CPM4", "CPM5", */ // bad allocation? "IOC0", "IOC1", "IOC2", "IOC3", "IOC4", "IOC5", "IOC6", "IOC7", "IOC8", "IOC9", "IOC10", "IOC11", "IOC12", "IOC13", "IOC14", "IOC15", "IOC16", "IOC17", "IOC18", "IOC19", "IOC20", "IOC21", "IOC22", "IOC23", "IOC24", "IOC25", "IOC26", "IOC27", "IOC28", "IOC29", "IOC30", "IOC31", "VR0", "VR1", "IOC84", "IOC85", "IOC86", "IOC87", "IOC88", "IOC89", "IOC90", "IOC91", "IOC92", "IOC93", "IOC94", "IOC95", "IOC96", "IOC97", "IOC98", "IOC99", "IOC100", "IOC101", "IOC102", "IOC103", "IOC104", "IOC105", "IOC106", "IOC107", "IOC108", "IOC109", "IOC110", "IOC111", "IOC112", "IOC113", "IOC114", "IOC115", "SIOC0", "SIOC1", "SIOC2", "SIOC3", "SIOC4", "SIOC5", "SIOC6", "SIOC7", "SIOC8", "SIOC9", "SIOC10", "SIOC11", "SIOC12", "SIOC13", "SIOC14", "SIOC15", "SIOC16", "SIOC17", "SIOC18", "SIOC19", "SIOC20", "SIOC21", "SIOC22", "SIOC23", "SIOC24", "SIOC25", "SIOC26", "SIOC27", "SIOC28", "SIOC29", "SIOC30", "SIOC31", "PSC0", "PSC1", "PSC2", "PSC3", "PSC4", "PSC5", "AT0", "AT1", "AT2", "AT3", "AT4", "AT5", "AT6", "AT7", "AT8", "AT9", "AT10", "AT11", "AT12", "AT13", "AT14", "AT15", "NX0", "NX1", "NX2", "NX3", "NX4", "NX5", "NX6", "NX7", "NX8", "NX9", "NX10", "NX11", "NX12", "NX13", "NX14", "NX15", "J0", // minor is 186 "UL0","UL1","UL2","UL3", "xvc0", // FAIL -- "/dev/xvc0" lacks "tty" prefix "PZ0","PZ1","PZ2","PZ3", "TX0","TX1","TX2","TX3","TX4","TX5","TX6","TX7", "SC0","SC1","SC2","SC3", "MAX0","MAX1","MAX2","MAX3", }; #if 0 // test code #include #define AS(x) (sizeof(x)/sizeof((x)[0])) int main(int argc, char *argv[]){ int i = 0; while(i 255) return 0; // should never happen; array index protection t0 = "pqrstuvwxyzabcde"[tmpmin>>4]; t1 = "0123456789abcdef"[tmpmin&0x0f]; sprintf(buf, "/dev/tty%c%c", t0, t1); break; case 4: if(min<64){ sprintf(buf, "/dev/tty%d", min); break; } sprintf(buf, "/dev/ttyS%d", min-64); break; case 11: sprintf(buf, "/dev/ttyB%d", min); break; case 17: sprintf(buf, "/dev/ttyH%d", min); break; case 19: sprintf(buf, "/dev/ttyC%d", min); break; case 22: sprintf(buf, "/dev/ttyD%d", min); break; /* devices.txt */ case 23: sprintf(buf, "/dev/ttyD%d", min); break; /* driver code */ case 24: sprintf(buf, "/dev/ttyE%d", min); break; case 32: sprintf(buf, "/dev/ttyX%d", min); break; case 43: sprintf(buf, "/dev/ttyI%d", min); break; case 46: sprintf(buf, "/dev/ttyR%d", min); break; case 48: sprintf(buf, "/dev/ttyL%d", min); break; case 57: sprintf(buf, "/dev/ttyP%d", min); break; case 71: sprintf(buf, "/dev/ttyF%d", min); break; case 75: sprintf(buf, "/dev/ttyW%d", min); break; case 78: sprintf(buf, "/dev/ttyM%d", min); break; /* conflict */ case 105: sprintf(buf, "/dev/ttyV%d", min); break; case 112: sprintf(buf, "/dev/ttyM%d", min); break; /* conflict */ /* 136 ... 143 are /dev/pts/0, /dev/pts/1, /dev/pts/2 ... */ case 136 ... 143: sprintf(buf, "/dev/pts/%d", min+(maj-136)*256); break; case 148: sprintf(buf, "/dev/ttyT%d", min); break; case 154: sprintf(buf, "/dev/ttySR%d", min); break; case 156: sprintf(buf, "/dev/ttySR%d", min+256); break; case 164: sprintf(buf, "/dev/ttyCH%d", min); break; case 166: sprintf(buf, "/dev/ttyACM%d", min); break; /* bummer, 9-char */ case 172: sprintf(buf, "/dev/ttyMX%d", min); break; case 174: sprintf(buf, "/dev/ttySI%d", min); break; case 188: sprintf(buf, "/dev/ttyUSB%d", min); break; /* bummer, 9-char */ case 204: if(min >= sizeof low_density_names / sizeof low_density_names[0]) return 0; memcpy(buf,"/dev/tty",8); memcpy(buf+8, low_density_names[min], sizeof low_density_names[0]); buf[8 + sizeof low_density_names[0]] = '\0'; // snprintf(buf, 9 + sizeof low_density_names[0], "/dev/tty%.*s", sizeof low_density_names[0], low_density_names[min]); break; case 208: sprintf(buf, "/dev/ttyU%d", min); break; case 216: sprintf(buf, "/dev/ttyUB%d", min); break; // "/dev/rfcomm%d" now? case 224: sprintf(buf, "/dev/ttyY%d", min); break; case 227: sprintf(buf, "/dev/3270/tty%d", min); break; /* bummer, HUGE */ case 229: sprintf(buf, "/dev/iseries/vtty%d", min); break; /* bummer, HUGE */ case 256: sprintf(buf, "/dev/ttyEQ%d", min); break; default: return 0; } if(stat(buf, &sbuf) < 0) return 0; if(min != MINOR_OF(sbuf.st_rdev)) return 0; if(maj != MAJOR_OF(sbuf.st_rdev)) return 0; return 1; } /* Linux 2.2 can give us filenames that might be correct. * Useful names could be in /proc/PID/fd/2 (stderr, seldom redirected) * and in /proc/PID/fd/255 (used by bash to remember the tty). */ static int link_name(char *restrict const buf, unsigned maj, unsigned min, int pid, const char *restrict name){ struct stat sbuf; char path[32]; int count; sprintf(path, "/proc/%d/%s", pid, name); /* often permission denied */ count = readlink(path,buf,TTY_NAME_SIZE-1); if(count == -1) return 0; buf[count] = '\0'; if(stat(buf, &sbuf) < 0) return 0; if(min != MINOR_OF(sbuf.st_rdev)) return 0; if(maj != MAJOR_OF(sbuf.st_rdev)) return 0; return 1; } /* number --> name */ unsigned dev_to_tty(char *restrict ret, unsigned chop, dev_t dev_t_dev, int pid, unsigned int flags) { static char buf[TTY_NAME_SIZE]; char *restrict tmp = buf; unsigned dev = dev_t_dev; unsigned i = 0; int c; if(dev == 0u) goto no_tty; if(linux_version_code > LINUX_VERSION(2, 7, 0)){ // not likely to make 2.6.xx if(link_name(tmp, MAJOR_OF(dev), MINOR_OF(dev), pid, "tty" )) goto abbrev; } if(driver_name(tmp, MAJOR_OF(dev), MINOR_OF(dev) )) goto abbrev; if( link_name(tmp, MAJOR_OF(dev), MINOR_OF(dev), pid, "fd/2" )) goto abbrev; if( guess_name(tmp, MAJOR_OF(dev), MINOR_OF(dev) )) goto abbrev; if( link_name(tmp, MAJOR_OF(dev), MINOR_OF(dev), pid, "fd/255")) goto abbrev; // fall through if unable to find a device file no_tty: strcpy(ret, "?"); return 1; abbrev: if((flags&ABBREV_DEV) && !strncmp(tmp,"/dev/",5) && tmp[5]) tmp += 5; if((flags&ABBREV_TTY) && !strncmp(tmp,"tty", 3) && tmp[3]) tmp += 3; if((flags&ABBREV_PTS) && !strncmp(tmp,"pts/", 4) && tmp[4]) tmp += 4; /* gotta check before we chop or we may chop someone else's memory */ if(chop + (unsigned long)(tmp-buf) <= sizeof buf) tmp[chop] = '\0'; /* replace non-ASCII characters with '?' and return the number of chars */ for(;;){ c = *tmp; tmp++; if(!c) break; i++; if(c<=' ') c = '?'; if(c>126) c = '?'; *ret = c; ret++; } *ret = '\0'; return i; } /* name --> number */ int tty_to_dev(const char *restrict const name) { struct stat sbuf; static char buf[32]; if(name[0]=='/' && stat(name, &sbuf) >= 0) return sbuf.st_rdev; snprintf(buf,32,"/dev/%s",name); if(stat(buf, &sbuf) >= 0) return sbuf.st_rdev; snprintf(buf,32,"/dev/tty%s",name); if(stat(buf, &sbuf) >= 0) return sbuf.st_rdev; snprintf(buf,32,"/dev/pts/%s",name); if(stat(buf, &sbuf) >= 0) return sbuf.st_rdev; return -1; } poelzi-ulatencyd-55515a9/src/proc/devname.h000066400000000000000000000006571154664534000206240ustar00rootroot00000000000000#ifndef PROC_DEVNAME_H #define PROC_DEVNAME_H #include "procps.h" EXTERN_C_BEGIN #define ABBREV_DEV 1 /* remove /dev/ */ #define ABBREV_TTY 2 /* remove tty */ #define ABBREV_PTS 4 /* remove pts/ */ extern unsigned dev_to_tty(char *restrict ret, unsigned chop, dev_t dev_t_dev, int pid, unsigned int flags); extern int tty_to_dev(const char *restrict const name); EXTERN_C_END #endif poelzi-ulatencyd-55515a9/src/proc/escape.c000066400000000000000000000133421154664534000204330ustar00rootroot00000000000000/* * Copyright 1998-2002 by Albert Cahalan; all rights resered. * This file may be used subject to the terms and conditions of the * GNU Library General Public License Version 2, or any later version * at your option, as published by the Free Software Foundation. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. */ #include #include #include #include "procps.h" #include "escape.h" #include "readproc.h" #if (__GNU_LIBRARY__ >= 6) # include # include # include /* MB_CUR_MAX */ # include # include #endif #if (__GNU_LIBRARY__ >= 6) static int escape_str_utf8(char *restrict dst, const char *restrict src, int bufsize, int *maxcells){ int my_cells = 0; int my_bytes = 0; mbstate_t s; memset(&s, 0, sizeof (s)); for(;;) { wchar_t wc; int len = 0; if(my_cells >= *maxcells || my_bytes+1 >= bufsize) break; if (!(len = mbrtowc (&wc, src, MB_CUR_MAX, &s))) /* 'str' contains \0 */ break; if (len < 0) { /* invalid multibyte sequence -- zeroize state */ memset (&s, 0, sizeof (s)); *(dst++) = '?'; src++; my_cells++; my_bytes++; } else if (len==1) { /* non-multibyte */ *(dst++) = isprint(*src) ? *src : '?'; src++; my_cells++; my_bytes++; } else if (!iswprint(wc)) { /* multibyte - no printable */ *(dst++) = '?'; src+=len; my_cells++; my_bytes++; } else { /* multibyte - printable */ int wlen = wcwidth(wc); if (wlen==0) { // invisible multibyte -- we don't ignore it, because some terminal // interpret it wrong and more safe is replace it with '?' *(dst++) = '?'; src+=len; my_cells++; my_bytes++; } else { // multibyte - printable // Got space? if (my_cells+wlen > *maxcells || my_bytes+1+len >= bufsize) break; // 0x9b is control byte for some terminals if (memchr(src, 0x9B, len)) { // unsafe multibyte *(dst++) = '?'; src+=len; my_cells++; my_bytes++; } else { // safe multibyte memcpy(dst, src, len); my_cells += wlen; dst += len; my_bytes += len; src += len; } } } //fprintf(stdout, "cells: %d\n", my_cells); } *(dst++) = '\0'; // fprintf(stderr, "maxcells: %d, my_cells; %d\n", *maxcells, my_cells); *maxcells -= my_cells; return my_bytes; // bytes of text, excluding the NUL } #endif /* __GNU_LIBRARY__ */ /* sanitize a string via one-way mangle */ int escape_str(char *restrict dst, const char *restrict src, int bufsize, int *maxcells){ unsigned char c; int my_cells = 0; int my_bytes = 0; const char codes[] = "Z-------------------------------" "********************************" "********************************" "*******************************-" "--------------------------------" "********************************" "********************************" "********************************"; #if (__GNU_LIBRARY__ >= 6) static int utf_init=0; if(utf_init==0){ /* first call -- check if UTF stuff is usable */ char *enc = nl_langinfo(CODESET); utf_init = enc && strcasecmp(enc, "UTF-8")==0 ? 1 : -1; } if (utf_init==1) /* UTF8 locales */ return escape_str_utf8(dst, src, bufsize, maxcells); #endif if(bufsize > *maxcells+1) bufsize=*maxcells+1; // FIXME: assumes 8-bit locale for(;;){ if(my_cells >= *maxcells || my_bytes+1 >= bufsize) break; c = (unsigned char) *(src++); if(!c) break; if(codes[c]=='-') c='?'; my_cells++; my_bytes++; *(dst++) = c; } *(dst++) = '\0'; *maxcells -= my_cells; return my_bytes; // bytes of text, excluding the NUL } ///////////////////////////////////////////////// // escape an argv or environment string array // // bytes arg means sizeof(buf) int escape_strlist(char *restrict dst, const char *restrict const *restrict src, size_t bytes, int *cells){ size_t i = 0; for(;;){ i += escape_str(dst+i, *src, bytes-i, cells); if(bytes-i < 3) break; // need room for space, a character, and the NUL src++; if(!*src) break; // need something to print if (*cells<=1) break; // need room for printed size of text dst[i++] = ' '; --*cells; } return i; // bytes, excluding the NUL } /////////////////////////////////////////////////// int escape_command(char *restrict const outbuf, const proc_t *restrict const pp, int bytes, int *cells, unsigned flags){ int overhead = 0; int end = 0; if(flags & ESC_ARGS){ const char **lc = (const char**)pp->cmdline; if(lc && *lc) return escape_strlist(outbuf, lc, bytes, cells); } if(flags & ESC_BRACKETS){ overhead += 2; } if(flags & ESC_DEFUNCT){ if(pp->state=='Z') overhead += 10; // chars in " " else flags &= ~ESC_DEFUNCT; } if(overhead + 1 >= *cells){ // if no room for even one byte of the command name // you'd damn well better have _some_ space // outbuf[0] = '-'; // Oct23 outbuf[1] = '\0'; return 1; } if(flags & ESC_BRACKETS){ outbuf[end++] = '['; } *cells -= overhead; end += escape_str(outbuf+end, pp->cmd, bytes-overhead, cells); // Hmmm, do we want "[foo] " or "[foo ]"? if(flags & ESC_BRACKETS){ outbuf[end++] = ']'; } if(flags & ESC_DEFUNCT){ memcpy(outbuf+end, " ", 10); end += 10; } outbuf[end] = '\0'; return end; // bytes, not including the NUL } poelzi-ulatencyd-55515a9/src/proc/escape.h000066400000000000000000000014431154664534000204370ustar00rootroot00000000000000#ifndef PROCPS_PROC_ESCAPE_H #define PROCPS_PROC_ESCAPE_H //#include #include #include "procps.h" #include "readproc.h" EXTERN_C_BEGIN #define ESC_STRETCH 1 // since we mangle to '?' this is 1 (would be 4 for octal escapes) #define ESC_ARGS 0x1 // try to use cmdline instead of cmd #define ESC_BRACKETS 0x2 // if using cmd, put '[' and ']' around it #define ESC_DEFUNCT 0x4 // mark zombies with " " extern int escape_strlist(char *restrict dst, const char *restrict const *restrict src, size_t n, int *cells); extern int escape_str(char *restrict dst, const char *restrict src, int bufsize, int *maxcells); extern int escape_command(char *restrict const outbuf, const proc_t *restrict const pp, int bytes, int *cells, unsigned flags); EXTERN_C_END #endif poelzi-ulatencyd-55515a9/src/proc/ksym.c000066400000000000000000000424551154664534000201650ustar00rootroot00000000000000/* * Copyright 1998-2003 by Albert Cahalan; all rights reserved. * This file may be used subject to the terms and conditions of the * GNU Library General Public License Version 2, or any later version * at your option, as published by the Free Software Foundation. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include "procps.h" #include "version.h" #include "sysinfo.h" /* smp_num_cpus */ #include "wchan.h" // to verify prototypes #define KSYMS_FILENAME "/proc/ksyms" #if 0 #undef KSYMS_FILENAME #define KSYMS_FILENAME "/would/be/nice/to/have/this/file" #define SYSMAP_FILENAME "/home/albert/ps/45621/System.map-hacked" #define linux_version_code 131598 /* ? */ #define smp_num_cpus 2 #endif #if 0 #undef KSYMS_FILENAME #define KSYMS_FILENAME "/home/albert/ps/45621/ksyms-2.3.12" #define SYSMAP_FILENAME "/home/albert/ps/45621/System.map-2.3.12" #define linux_version_code 131852 /* 2.3.12 */ #define smp_num_cpus 2 #endif #if 0 #undef KSYMS_FILENAME #define KSYMS_FILENAME "/home/albert/ps/45621/ksyms-2.3.18ac8-MODVERS" #define SYSMAP_FILENAME "/home/albert/ps/45621/System.map-2.3.18ac8-MODVERS" #define linux_version_code 131858 /* 2.3.18ac8 */ #define smp_num_cpus 2 #endif #if 0 #undef KSYMS_FILENAME #define KSYMS_FILENAME "/home/albert/ps/45621/ksyms-2.3.18ac8-NOMODVERS" #define SYSMAP_FILENAME "/home/albert/ps/45621/System.map-2.3.18ac8-NOMODVERS" #define linux_version_code 131858 /* 2.3.18ac8 */ #define smp_num_cpus 2 #endif /* These are the symbol types, with relative popularity: * ? w machine type junk for Alpha -- odd syntax * ? S not for i386 * 4 W not for i386 * 60 R * 100 A * 125 r * 363 s not for i386 * 858 B * 905 g generated by modutils? * 929 G generated by modutils? * 1301 b * 2750 D * 4481 d * 11417 ? * 13666 t * 15442 T * * For i386, that is: "RArBbDd?tT" */ #define SYMBOL_TYPE_CHARS "Tt?dDbBrARGgsWS" /* * '?' is a symbol type * '.' is part of a name (versioning?) * "\t[]" are for the module name in /proc/ksyms */ #define LEGAL_SYSMAP_CHARS "0123456789_ ?.\n\t[]" \ "abcdefghijklmnopqrstuvwxyz" \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" /* System.map lines look like: * hex num, space, one of SYMBOL_TYPE_CHARS, space, LEGAL_SYSMAP_CHARS, \n * * Alpha systems can start with a few lines that have the address replaced * by space padding and a 'w' for the type. For those lines, the last space * is followed by something like: mikasa_primo_mv p2k_mv sable_gamma_mv * (just one of those, always with a "_mv", then the newline) * * The /proc/ksyms lines are like System.map lines w/o the symbol type char. * When odd features are used, the name part contains: * "(.*)_R(smp_|smp2gig_|2gig_)?[0-9a-fA-F]{8,}" * It is likely that more crap will be added... */ typedef struct symb { unsigned KLONG addr; const char *name; } symb; /* These mostly rely on POSIX to make them zero. */ static symb hashtable[256]; static char *sysmap_data; static unsigned sysmap_room; static symb *sysmap_index; static unsigned sysmap_count; static char *ksyms_data; static unsigned ksyms_room = 4096; static symb *ksyms_index; static unsigned ksyms_count; static unsigned idx_room; /*********************************/ /* Kill this: _R(smp_?|smp2gig_?|2gig_?)?[0-9a-f]{8,}$ * We kill: (_R[^A-Z]*[0-9a-f]{8,})+$ * * The loop should almost never be taken, but it has to be there. * It gets rid of anything that _looks_ like a version code, even * if a real version code has already been found. This is because * the inability to perfectly recognize a version code may lead to * symbol mangling, which in turn leads to mismatches between the * /proc/ksyms and System.map data files. */ #if 0 static char *chop_version(char *arg){ char *cp; cp = strchr(arg,'\t'); if(cp) *cp = '\0'; /* kill trailing module name first */ for(;;){ char *p; int len = 0; cp = strrchr(arg, 'R'); if(!cp || cp<=arg+1 || cp[-1]!='_') break; for(p=cp; *++p; ){ switch(*p){ default: goto out; case '0' ... '9': case 'a' ... 'f': len++; continue; case 'g' ... 'z': case '_': len=0; continue; } } if(len<8) break; cp[-1] = '\0'; } out: if(*arg=='G'){ int len = strlen(arg); while( len>8 && !memcmp(arg,"GPLONLY_",8) ){ arg += 8; len -= 8; } } return arg; } #endif static char *chop_version(char *arg){ char *cp; cp = strchr(arg,'\t'); if(cp) *cp = '\0'; /* kill trailing module name first */ for(;;){ int len; cp = strrchr(arg, 'R'); if(!cp || cp<=arg+1 || cp[-1]!='_') break; len=strlen(cp); if(len<9) break; if(strpbrk(cp+1,"ABCDEFGHIJKLMNOPQRSTUVWXYZ")) break; if(strspn(cp+len-8,"0123456789abcdef")!=8) break; cp[-1] = '\0'; } if(*arg=='G'){ int len = strlen(arg); while( len>8 && !memcmp(arg,"GPLONLY_",8) ){ arg += 8; len -= 8; } } return arg; } /***********************************/ static const symb *search(unsigned KLONG address, symb *idx, unsigned count){ unsigned left; unsigned mid; unsigned right; if(!idx) return NULL; /* maybe not allocated */ if(address < idx[0].addr) return NULL; if(address >= idx[count-1].addr) return idx+count-1; left = 0; right = count-1; for(;;){ mid = (left + right) / 2; if(address >= idx[mid].addr) left = mid; if(address <= idx[mid].addr) right = mid; if(right-left <= 1) break; } if(address == idx[right].addr) return idx+right; return idx+left; } /*********************************/ /* allocate if needed, read, and return buffer size */ static void read_file(const char *restrict filename, char **bufp, unsigned *restrict roomp) { int fd = 0; ssize_t done; char *buf = *bufp; ssize_t total = 0; unsigned room = *roomp; if(!room) goto hell; /* failed before */ if(!buf) buf = malloc(room); if(!buf) goto hell; open_again: fd = open(filename, O_RDONLY|O_NOCTTY|O_NONBLOCK); if(fd<0){ switch(errno){ case EINTR: goto open_again; default: _exit(101); case EACCES: /* somebody screwing around? */ /* FIXME: set a flag to disable symbol lookup? */ case ENOENT:; /* no module support */ } goto hell; } for(;;){ done = read(fd, buf+total, room-total-1); if(done==0) break; /* nothing left */ if(done==-1){ if(errno==EINTR) continue; /* try again */ perror(""); goto hell; } if(done==(ssize_t)room-total-1){ char *tmp; total += done; /* more to go, but no room in buffer */ room *= 2; tmp = realloc(buf, room); if(!tmp) goto hell; buf = tmp; continue; } if(done>0 && done<(ssize_t)room-total-1){ total += done; continue; /* OK, we read some. Go do more. */ } fprintf(stderr,"%ld can't happen\n", (long)done); /* FIXME: memory leak */ _exit(42); } buf[total] = '\0'; // parse_ksyms() expects NUL-terminated file *bufp = buf; *roomp = room; close(fd); return; hell: if(buf) free(buf); *bufp = NULL; *roomp = 0; /* this function will never work again */ total = 0; if(fd>0) close(fd); return; } /*********************************/ static int parse_ksyms(void) { char *endp; if(!ksyms_room || !ksyms_data) goto quiet_goodbye; endp = ksyms_data; ksyms_count = 0; if(idx_room) goto bypass; /* some space already allocated */ idx_room = 512; for(;;){ void *vp; idx_room *= 2; vp = realloc(ksyms_index, sizeof(symb)*idx_room); if(!vp) goto bad_alloc; ksyms_index = vp; bypass: for(;;){ char *saved; if(!*endp) return 1; saved = endp; ksyms_index[ksyms_count].addr = STRTOUKL(endp, &endp, 16); if(endp==saved || *endp != ' ') goto bad_parse; endp++; saved = endp; endp = strchr(endp,'\n'); if(!endp) goto bad_parse; /* no newline */ *endp = '\0'; ksyms_index[ksyms_count].name = chop_version(saved); ++endp; if(++ksyms_count >= idx_room) break; /* need more space */ } } if(0){ bad_alloc: fprintf(stderr, "Warning: not enough memory available\n"); } if(0){ bad_parse: fprintf(stderr, "Warning: "KSYMS_FILENAME" not normal\n"); } quiet_goodbye: idx_room = 0; if(ksyms_data) free(ksyms_data) , ksyms_data = NULL; ksyms_room = 0; if(ksyms_index) free(ksyms_index) , ksyms_index = NULL; ksyms_count = 0; return 0; } /*********************************/ #define VCNT 16 static int sysmap_mmap(const char *restrict const filename, message_fn message) { struct stat sbuf; char *endp; int fd; char Version[32]; fd = open(filename, O_RDONLY|O_NOCTTY|O_NONBLOCK); if(fd<0) return 0; if(fstat(fd, &sbuf) < 0) goto bad_open; if(!S_ISREG(sbuf.st_mode)) goto bad_open; if(sbuf.st_size < 5000) goto bad_open; /* if way too small */ /* Would be shared read-only, but we want '\0' after each name. */ endp = mmap(0, sbuf.st_size + 1, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); sysmap_data = endp; while(*endp==' '){ /* damn Alpha machine types */ if(strncmp(endp," w ", 19)) goto bad_parse; endp += 19; endp = strchr(endp,'\n'); if(!endp) goto bad_parse; /* no newline */ if(strncmp(endp-3, "_mv\n", 4)) goto bad_parse; endp++; } if(sysmap_data == (caddr_t) -1) goto bad_open; close(fd); fd = -1; sprintf(Version, "Version_%d", linux_version_code); sysmap_room = 512; for(;;){ void *vp; sysmap_room *= 2; vp = realloc(sysmap_index, sizeof(symb)*sysmap_room); if(!vp) goto bad_alloc; sysmap_index = vp; for(;;){ char *vstart; if(endp - sysmap_data >= sbuf.st_size){ /* if we reached the end */ int i = VCNT; /* check VCNT times to verify this file */ if(*Version) goto bad_version; if(!ksyms_index) return 1; /* if can not verify, assume success */ while(i--){ #if 1 const symb *findme; const symb *map_symb; /* Choose VCNT entries from /proc/ksyms to test */ findme = ksyms_index + (ksyms_count*i/VCNT); /* Search for them in the System.map */ map_symb = search(findme->addr, sysmap_index, sysmap_count); if(map_symb){ if(map_symb->addr != findme->addr) continue; /* backup to first matching address */ while (map_symb != sysmap_index){ if (map_symb->addr != (map_symb-1)->addr) break; map_symb--; } /* search for name in symbols with same address */ while (map_symb != (sysmap_index+sysmap_count)){ if (map_symb->addr != findme->addr) break; if (!strcmp(map_symb->name,findme->name)) goto good_match; map_symb++; } map_symb--; /* backup to last symbol with matching address */ message("{%s} {%s}\n",map_symb->name,findme->name); goto bad_match; } good_match:; #endif } return 1; /* success */ } sysmap_index[sysmap_count].addr = STRTOUKL(endp, &endp, 16); if(*endp != ' ') goto bad_parse; endp++; if(!strchr(SYMBOL_TYPE_CHARS, *endp)) goto bad_parse; endp++; if(*endp != ' ') goto bad_parse; endp++; vstart = endp; endp = strchr(endp,'\n'); if(!endp) goto bad_parse; /* no newline */ *endp = '\0'; ++endp; vstart = chop_version(vstart); sysmap_index[sysmap_count].name = vstart; if(*vstart=='V' && *Version && !strcmp(Version,vstart)) *Version='\0'; if(++sysmap_count >= sysmap_room) break; /* need more space */ } } if(0){ bad_match: message("Warning: %s does not match kernel data.\n", filename); } if(0){ bad_version: message("Warning: %s has an incorrect kernel version.\n", filename); } if(0){ bad_alloc: message("Warning: not enough memory available\n"); } if(0){ bad_parse: message("Warning: %s not parseable as a System.map\n", filename); } if(0){ bad_open: message("Warning: %s could not be opened as a System.map\n", filename); } sysmap_room=0; sysmap_count=0; if(sysmap_index) free(sysmap_index); sysmap_index = NULL; if(fd>=0) close(fd); if(sysmap_data) munmap(sysmap_data, sbuf.st_size + 1); sysmap_data = NULL; return 0; } /*********************************/ static void read_and_parse(void){ static time_t stamp; /* after data gets old, load /proc/ksyms again */ if(time(NULL) != stamp){ read_file(KSYMS_FILENAME, &ksyms_data, &ksyms_room); parse_ksyms(); memset((void*)hashtable,0,sizeof(hashtable)); /* invalidate cache */ stamp = time(NULL); } } /*********************************/ static void default_message(const char *restrict format, ...) __attribute__((format(printf,1,2))); static void default_message(const char *restrict format, ...) { va_list arg; va_start (arg, format); vfprintf (stderr, format, arg); va_end (arg); } /*********************************/ static int use_wchan_file; int open_psdb_message(const char *restrict override, message_fn message) { static const char *sysmap_paths[] = { "/boot/System.map-%s", "/boot/System.map", "/lib/modules/%s/System.map", "/usr/src/linux/System.map", "/System.map", NULL }; struct stat sbuf; struct utsname uts; char path[128]; const char **fmt = sysmap_paths; const char *sm; #ifdef SYSMAP_FILENAME /* debug feature */ override = SYSMAP_FILENAME; #endif // first allow for a user-selected System.map file if( (sm=override) || (sm=getenv("PS_SYSMAP")) || (sm=getenv("PS_SYSTEM_MAP")) ){ if(!have_privs){ read_and_parse(); if(sysmap_mmap(sm, message)) return 0; } /* failure is better than ignoring the user & using bad data */ return -1; /* ought to return "Namelist not found." */ } // next try the Linux 2.5.xx method if(!stat("/proc/self/wchan", &sbuf)){ use_wchan_file = 1; // hack return 0; } // finally, search for the System.map file uname(&uts); path[sizeof path - 1] = '\0'; do{ int did_ksyms = 0; snprintf(path, sizeof path - 1, *fmt, uts.release); if(!stat(path, &sbuf)){ if (did_ksyms++) read_and_parse(); if (sysmap_mmap(path, message)) return 0; } }while(*++fmt); /* TODO: Without System.map, no need to keep ksyms loaded. */ return -1; } /***************************************/ int open_psdb(const char *restrict override) { return open_psdb_message(override, default_message); } /***************************************/ static const char * read_wchan_file(unsigned pid){ static char buf[64]; const char *ret = buf; ssize_t num; int fd; snprintf(buf, sizeof buf, "/proc/%d/wchan", pid); fd = open(buf, O_RDONLY); if(fd==-1) return "?"; num = read(fd, buf, sizeof buf - 1); close(fd); if(num<1) return "?"; // allow for "0" buf[num] = '\0'; if(buf[0]=='0' && buf[1]=='\0') return "-"; // would skip over numbers if they existed -- but no // lame ppc64 has a '.' in front of every name if(*ret=='.') ret++; switch(*ret){ case 's': if(!strncmp(ret, "sys_", 4)) ret += 4; break; case 'd': if(!strncmp(ret, "do_", 3)) ret += 3; break; case '_': while(*ret=='_') ret++; break; } return ret; } /***************************************/ static const symb fail = { .name = "?" }; static const char dash[] = "-"; static const char star[] = "*"; #define MAX_OFFSET (0x1000*sizeof(long)) /* past this is generally junk */ /* return pointer to temporary static buffer with function name */ const char * lookup_wchan(unsigned KLONG address, unsigned pid) { const symb *mod_symb; const symb *map_symb; const symb *good_symb; const char *ret; unsigned hash; // can't cache it due to a race condition :-( if(use_wchan_file) return read_wchan_file(pid); if(!address) return dash; if(!~address) return star; read_and_parse(); hash = (address >> 4) & 0xff; /* got 56/63 hits & 7/63 misses */ if(hashtable[hash].addr == address) return hashtable[hash].name; mod_symb = search(address, ksyms_index, ksyms_count); if(!mod_symb) mod_symb = &fail; map_symb = search(address, sysmap_index, sysmap_count); if(!map_symb) map_symb = &fail; /* which result is closest? */ good_symb = (mod_symb->addr > map_symb->addr) ? mod_symb : map_symb ; if(address > good_symb->addr + MAX_OFFSET) good_symb = &fail; /* good_symb->name has the data, but needs to be trimmed */ ret = good_symb->name; // lame ppc64 has a '.' in front of every name if(*ret=='.') ret++; switch(*ret){ case 's': if(!strncmp(ret, "sys_", 4)) ret += 4; break; case 'd': if(!strncmp(ret, "do_", 3)) ret += 3; break; case '_': while(*ret=='_') ret++; break; } /* if(!*ret) ret = fail.name; */ /* not likely (name was "sys_", etc.) */ /* cache name after abbreviation */ hashtable[hash].addr = address; hashtable[hash].name = ret; return ret; } poelzi-ulatencyd-55515a9/src/proc/procps.h000066400000000000000000000070151154664534000205060ustar00rootroot00000000000000#ifndef PROCPS_PROC_PROCPS_H #define PROCPS_PROC_PROCPS_H #ifdef __cplusplus #define EXTERN_C_BEGIN extern "C" { #define EXTERN_C_END } #else #define EXTERN_C_BEGIN #define EXTERN_C_END #endif // Some ports make the mistake of running a 32-bit userspace // on a 64-bit kernel. Shame on them. It's not at all OK to // make everything "long long", since that causes unneeded // slowness on 32-bit hardware. // // SPARC: The 32-bit kernel was looking like an ex-penguin, // but it lives! ("I'm not dead yet.") So, 64-bit users will // just have to compile for 64-bit. Aw, the suffering. // // MIPS: Used 32-bit for embedded systems and obsolete hardware. // The 64-bit systems use an n32 format executable, defining // _ABIN32 to indicate this. Since n32 doesn't currently run on // any 32-bit system, nobody get hurt if it's bloated. Not that // this is sane of course, but it won't hurt the 32-bit users. // __mips_eabi means eabi, which comes in both sizes, but isn't used. // // PowerPC: Big ugly problem! 32-bit Macs are still popular. :-/ // // x86-64: So far, nobody has been dumb enough to go 32-bit. // // Unknown: PA-RISC and zSeries // #if defined(k64test) || (defined(_ABIN32) && _MIPS_SIM == _ABIN32) #define KLONG long long // not typedef; want "unsigned KLONG" to work #define KLF "L" #define STRTOUKL strtoull #else #define KLONG long #define KLF "l" #define STRTOUKL strtoul #endif // since gcc-2.5 #define NORETURN __attribute__((__noreturn__)) #define FUNCTION __attribute__((__const__)) // no access to global mem, even via ptr, and no side effect #if !defined(restrict) && __STDC_VERSION__ < 199901 #if __GNUC__ > 2 || __GNUC_MINOR__ >= 92 #define restrict __restrict__ #else #warning No restrict keyword? #define restrict #endif #endif #if __GNUC__ > 2 || __GNUC_MINOR__ >= 96 // won't alias anything, and aligned enough for anything #define MALLOC __attribute__ ((__malloc__)) // no side effect, may read globals #define PURE __attribute__ ((__pure__)) // tell gcc what to expect: if(unlikely(err)) die(err); #define likely(x) __builtin_expect(!!(x),1) #define unlikely(x) __builtin_expect(!!(x),0) #define expected(x,y) __builtin_expect((x),(y)) #else #define MALLOC #define PURE #define likely(x) (x) #define unlikely(x) (x) #define expected(x,y) (x) #endif #if SHARED==1 && (__GNUC__ > 2 || __GNUC_MINOR__ >= 96) #define LABEL_OFFSET #endif #define STRINGIFY_ARG(a) #a #define STRINGIFY(a) STRINGIFY_ARG(a) // marks old junk, to warn non-procps library users #if ( __GNUC__ == 3 && __GNUC_MINOR__ > 0 ) || __GNUC__ > 3 #define OBSOLETE __attribute__((deprecated)) #else #define OBSOLETE #endif #if ( __GNUC__ == 3 && __GNUC_MINOR__ > 1 ) || __GNUC__ > 3 // Tells gcc that function is library-internal; // so no need to do dynamic linking at run-time. // This might work with slightly older compilers too. #define HIDDEN __attribute__((visibility("hidden"))) // The opposite, in case -fvisibility=hidden used #define EXPORT __attribute__((visibility("default"))) // Tell g++ that a function won't throw exceptions. #define NOTHROW __attribute__((__nothrow__)) #else #define HIDDEN #define EXPORT #define NOTHROW #endif // Like HIDDEN, but for an alias that gets created. // In gcc-3.2 there is an alias+hidden conflict. // Many will have patched this bug, but oh well. #if ( __GNUC__ == 3 && __GNUC_MINOR__ > 2 ) || __GNUC__ > 3 #define HIDDEN_ALIAS(x) extern __typeof(x) x##_direct __attribute__((alias(#x),visibility("hidden"))) #else #define HIDDEN_ALIAS(x) extern __typeof(x) x##_direct __attribute__((alias(#x))) #endif #endif poelzi-ulatencyd-55515a9/src/proc/pwcache.c000066400000000000000000000034651154664534000206120ustar00rootroot00000000000000// Copyright (C) 1992-1998 by Michael K. Johnson, johnsonm@redhat.com // Note: most likely none of his code remains // // Copyright 2002, Albert Cahalan // // This file is placed under the conditions of the GNU Library // General Public License, version 2, or any later version. // See file COPYING for information on distribution conditions. #include #include #include #include #include #include "alloc.h" #include "pwcache.h" #include // might as well fill cache lines... else we waste memory anyway #define HASHSIZE 64 /* power of 2 */ #define HASH(x) ((x) & (HASHSIZE - 1)) static struct pwbuf { struct pwbuf *next; uid_t uid; char name[P_G_SZ]; } *pwhash[HASHSIZE]; char *user_from_uid(uid_t uid) { struct pwbuf **p; struct passwd *pw; p = &pwhash[HASH(uid)]; while (*p) { if ((*p)->uid == uid) return((*p)->name); p = &(*p)->next; } *p = (struct pwbuf *) xmalloc(sizeof(struct pwbuf)); (*p)->uid = uid; pw = getpwuid(uid); if(!pw || strlen(pw->pw_name) >= P_G_SZ) sprintf((*p)->name, "%u", uid); else strcpy((*p)->name, pw->pw_name); (*p)->next = NULL; return((*p)->name); } static struct grpbuf { struct grpbuf *next; gid_t gid; char name[P_G_SZ]; } *grphash[HASHSIZE]; char *group_from_gid(gid_t gid) { struct grpbuf **g; struct group *gr; g = &grphash[HASH(gid)]; while (*g) { if ((*g)->gid == gid) return((*g)->name); g = &(*g)->next; } *g = (struct grpbuf *) malloc(sizeof(struct grpbuf)); (*g)->gid = gid; gr = getgrgid(gid); if (!gr || strlen(gr->gr_name) >= P_G_SZ) sprintf((*g)->name, "%u", gid); else strcpy((*g)->name, gr->gr_name); (*g)->next = NULL; return((*g)->name); } poelzi-ulatencyd-55515a9/src/proc/pwcache.h000066400000000000000000000004711154664534000206110ustar00rootroot00000000000000#ifndef PROCPS_PROC_PWCACHE_H #define PROCPS_PROC_PWCACHE_H #include #include "procps.h" EXTERN_C_BEGIN // used in pwcache and in readproc to set size of username or groupname #define P_G_SZ 20 extern char *user_from_uid(uid_t uid); extern char *group_from_gid(gid_t gid); EXTERN_C_END #endif poelzi-ulatencyd-55515a9/src/proc/readproc.c000066400000000000000000001043341154664534000207740ustar00rootroot00000000000000/* * New Interface to Process Table -- PROCTAB Stream (a la Directory streams) * Copyright (C) 1996 Charles L. Blake. * Copyright (C) 1998 Michael K. Johnson * Copyright 1998-2003 Albert Cahalan * May be distributed under the conditions of the * GNU Library General Public License; a copy is in COPYING */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "version.h" #include "readproc.h" #include "alloc.h" #include "pwcache.h" #include "devname.h" #include "procps.h" #include #include #include #include #include #include #include #include #include #include #include #include // sometimes it's easier to do this manually, w/o gcc helping #ifdef PROF extern void __cyg_profile_func_enter(void*,void*); #define ENTER(x) __cyg_profile_func_enter((void*)x,(void*)x) #define LEAVE(x) __cyg_profile_func_exit((void*)x,(void*)x) #else #define ENTER(x) #define LEAVE(x) #endif // convert hex string to unsigned long long static unsigned long long unhex(const char *restrict cp){ unsigned long long ull = 0; for(;;){ char c = *cp++; if(unlikely(c<0x30)) break; ull = (ull<<4) | (c - (c>0x57) ? 0x57 : 0x30) ; } return ull; } static int task_dir_missing; /////////////////////////////////////////////////////////////////////////// typedef struct status_table_struct { unsigned char name[7]; // /proc/*/status field name unsigned char len; // name length #ifdef LABEL_OFFSET long offset; // jump address offset #else void *addr; #endif } status_table_struct; #ifdef LABEL_OFFSET #define F(x) {#x, sizeof(#x)-1, (long)(&&case_##x-&&base)}, #else #define F(x) {#x, sizeof(#x)-1, &&case_##x}, #endif #define NUL {"", 0, 0}, // Derived from: // gperf -7 --language=ANSI-C --key-positions=1,3,4 -C -n -c sml.gperf // // Suggested method: // Grep this file for "case_", then strip those down to the name. // (leave the colon and newline) So "Pid:\n" and "Threads:\n" // would be lines in the file. (no quote, no escape, etc.) // // Watch out for name size in the status_table_struct (grrr, expanding) // and the number of entries (we mask with 63 for now). The table // must be padded out to 64 entries, maybe 128 in the future. static void status2proc(char *S, proc_t *restrict P, int is_proc){ long Threads = 0; long Tgid = 0; long Pid = 0; int hash = 0; int isupgid = 0; static const unsigned char asso[] = { 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 0, 66, 66, 66, 66, 66, 66, 66, 3, 30, 20, 30, 66, 25, 66, 20, 66, 66, 30, 66, 25, 66, 0, 66, 8, 10, 3, 18, 5, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 3, 66, 10, 0, 0, 66, 25, 66, 5, 66, 66, 66, 25, 66, 5, 66, 66, 0, 66, 0, 0, 66, 66, 25, 66, 66, 66, 66, 66, 66, 66 }; static const status_table_struct table[] = { F(Pid) NUL NUL F(Threads) NUL F(PPid) NUL NUL F(Tgid) NUL F(ShdPnd) NUL NUL F(State) NUL F(VmStk) NUL NUL F(Uid) NUL F(VmSize) NUL NUL F(VmRSS) NUL F(Gid) NUL NUL F(VmData) NUL F(Groups) NUL NUL NUL NUL F(SigPnd) NUL NUL F(SigBlk) NUL F(VmLib) NUL NUL NUL NUL F(VmLck) NUL NUL NUL NUL F(Name) NUL NUL NUL NUL F(SigIgn) NUL NUL NUL NUL F(VmExe) NUL NUL NUL NUL F(SigCgt) }; #undef F #undef NUL ENTER(0x220); P->vm_size = 0; P->vm_lock = 0; P->vm_rss = 0; P->vm_data = 0; P->vm_stack= 0; P->vm_exe = 0; P->vm_lib = 0; P->nlwp = 0; P->nsupgid = 0; P->supgid = NULL; P->supgrp = NULL; P->signal[0] = '\0'; // so we can detect it as missing for very old kernels goto base; for(;;){ char *colon; status_table_struct entry; // advance to next line S = strchr(S, '\n'); if(unlikely(!S)) break; // if no newline S++; // examine a field name (hash and compare) base: if(unlikely(!*S)) break; hash = asso[S[3]] + asso[S[2]] + asso[S[0]]; if (hash > 65) continue; entry = table[hash]; colon = strchr(S, ':'); if(unlikely(!colon)) break; if(unlikely(colon[1]!='\t')) break; if(unlikely(colon-S != entry.len)) continue; if(unlikely(memcmp(entry.name,S,colon-S))) continue; S = colon+2; // past the '\t' #ifdef LABEL_OFFSET goto *(&&base + entry.offset); #else goto *entry.addr; #endif case_Name:{ unsigned u = 0; while(u < sizeof P->cmd - 1u){ int c = *S++; if(unlikely(c=='\n')) break; if(unlikely(c=='\0')) break; // should never happen if(unlikely(c=='\\')){ c = *S++; if(c=='\n') break; // should never happen if(!c) break; // should never happen if(c=='n') c='\n'; // else we assume it is '\\' } P->cmd[u++] = c; } P->cmd[u] = '\0'; S--; // put back the '\n' or '\0' continue; } #ifdef SIGNAL_STRING case_ShdPnd: memcpy(P->signal, S, 16); P->signal[16] = '\0'; continue; case_SigBlk: memcpy(P->blocked, S, 16); P->blocked[16] = '\0'; continue; case_SigCgt: memcpy(P->sigcatch, S, 16); P->sigcatch[16] = '\0'; continue; case_SigIgn: memcpy(P->sigignore, S, 16); P->sigignore[16] = '\0'; continue; case_SigPnd: memcpy(P->_sigpnd, S, 16); P->_sigpnd[16] = '\0'; continue; #else case_ShdPnd: P->signal = unhex(S); continue; case_SigBlk: P->blocked = unhex(S); continue; case_SigCgt: P->sigcatch = unhex(S); continue; case_SigIgn: P->sigignore = unhex(S); continue; case_SigPnd: P->_sigpnd = unhex(S); continue; #endif case_State: P->state = *S; continue; case_Tgid: Tgid = strtol(S,&S,10); continue; case_Pid: Pid = strtol(S,&S,10); continue; case_PPid: P->ppid = strtol(S,&S,10); continue; case_Threads: Threads = strtol(S,&S,10); continue; case_Uid: P->ruid = strtol(S,&S,10); P->euid = strtol(S,&S,10); P->suid = strtol(S,&S,10); P->fuid = strtol(S,&S,10); continue; case_Gid: P->rgid = strtol(S,&S,10); P->egid = strtol(S,&S,10); P->sgid = strtol(S,&S,10); P->fgid = strtol(S,&S,10); continue; case_Groups: isupgid = 0; if (*S != '\n'){ // Is there any supplementary group ? P->supgid = (int *) xmalloc(0x0004 * sizeof(int)); int vctsize = 0x0004; while (S[1] != '\n' && isupgidsupgid = (int *)xrealloc(P->supgid,vctsize * sizeof(int)); } P->supgid[isupgid++] = strtol(S,&S,10); P->nsupgid++; } } continue; case_VmData: P->vm_data = strtol(S,&S,10); continue; case_VmExe: P->vm_exe = strtol(S,&S,10); continue; case_VmLck: P->vm_lock = strtol(S,&S,10); continue; case_VmLib: P->vm_lib = strtol(S,&S,10); continue; case_VmRSS: P->vm_rss = strtol(S,&S,10); continue; case_VmSize: P->vm_size = strtol(S,&S,10); continue; case_VmStk: P->vm_stack = strtol(S,&S,10); continue; } #if 0 // recent kernels supply per-tgid pending signals if(is_proc && *ShdPnd){ memcpy(P->signal, ShdPnd, 16); P->signal[16] = '\0'; } #endif // recent kernels supply per-tgid pending signals #ifdef SIGNAL_STRING if(!is_proc || !P->signal[0]){ memcpy(P->signal, P->_sigpnd, 16); P->signal[16] = '\0'; } #else if(!is_proc || !have_process_pending){ P->signal = P->_sigpnd; } #endif // Linux 2.4.13-pre1 to max 2.4.xx have a useless "Tgid" // that is not initialized for built-in kernel tasks. // Only 2.6.0 and above have "Threads" (nlwp) info. if(Threads){ P->nlwp = Threads; P->tgid = Tgid; // the POSIX PID value P->tid = Pid; // the thread ID }else{ P->nlwp = 1; P->tgid = Pid; P->tid = Pid; } LEAVE(0x220); } /////////////////////////////////////////////////////////////////////// // Reads /proc/*/stat files, being careful not to trip over processes with // names like ":-) 1 2 3 4 5 6". static void stat2proc(const char* S, proc_t *restrict P) { unsigned num; char* tmp; ENTER(0x160); /* fill in default values for older kernels */ P->processor = 0; P->rtprio = -1; P->sched = -1; P->nlwp = 0; S = strchr(S, '(') + 1; tmp = strrchr(S, ')'); num = tmp - S; if(unlikely(num >= sizeof P->cmd)) num = sizeof P->cmd - 1; memcpy(P->cmd, S, num); P->cmd[num] = '\0'; S = tmp + 2; // skip ") " num = sscanf(S, "%c " "%d %d %d %d %d " "%lu %lu %lu %lu %lu " "%Lu %Lu %Lu %Lu " /* utime stime cutime cstime */ "%ld %ld " "%d " "%ld " "%Lu " /* start_time */ "%lu " "%ld " "%lu %"KLF"u %"KLF"u %"KLF"u %"KLF"u %"KLF"u " "%*s %*s %*s %*s " /* discard, no RT signals & Linux 2.1 used hex */ "%"KLF"u %*lu %*lu " "%d %d " "%lu %lu", &P->state, &P->ppid, &P->pgrp, &P->session, &P->tty, &P->tpgid, &P->flags, &P->min_flt, &P->cmin_flt, &P->maj_flt, &P->cmaj_flt, &P->utime, &P->stime, &P->cutime, &P->cstime, &P->priority, &P->nice, &P->nlwp, &P->alarm, &P->start_time, &P->vsize, &P->rss, &P->rss_rlim, &P->start_code, &P->end_code, &P->start_stack, &P->kstk_esp, &P->kstk_eip, /* P->signal, P->blocked, P->sigignore, P->sigcatch, */ /* can't use */ &P->wchan, /* &P->nswap, &P->cnswap, */ /* nswap and cnswap dead for 2.4.xx and up */ /* -- Linux 2.0.35 ends here -- */ &P->exit_signal, &P->processor, /* 2.2.1 ends with "exit_signal" */ /* -- Linux 2.2.8 to 2.5.17 end here -- */ &P->rtprio, &P->sched /* both added to 2.5.18 */ ); if(!P->nlwp){ P->nlwp = 1; } LEAVE(0x160); } ///////////////////////////////////////////////////////////////////////// static void statm2proc(const char* s, proc_t *restrict P) { int num; num = sscanf(s, "%ld %ld %ld %ld %ld %ld %ld", &P->size, &P->resident, &P->share, &P->trs, &P->lrs, &P->drs, &P->dt); /* fprintf(stderr, "statm2proc converted %d fields.\n",num); */ } int file2str(const char *directory, const char *what, char *ret, int cap) { static char filename[80]; int fd, num_read; sprintf(filename, "%s/%s", directory, what); fd = open(filename, O_RDONLY, 0); if(unlikely(fd==-1)) return -1; num_read = read(fd, ret, cap - 1); close(fd); if(unlikely(num_read<=0)) return -1; ret[num_read] = '\0'; return num_read; } char** file2strvec_ext(const char* directory, const char* what, char terminator) { char buf[2048]; /* read buf bytes at a time */ char *p, *rbuf = 0, *endbuf, **q, **ret; int fd, tot = 0, n, c, end_of_file = 0; int align; sprintf(buf, "%s/%s", directory, what); fd = open(buf, O_RDONLY, 0); if(fd==-1) return NULL; /* read whole file into a memory buffer, allocating as we go */ while ((n = read(fd, buf, sizeof buf - 1)) > 0) { if (n < (int)(sizeof buf - 1)) end_of_file = 1; if (n == 0 && rbuf == 0) return NULL; /* process died between our open and read */ if (n < 0) { if (rbuf) free(rbuf); return NULL; /* read error */ } if (end_of_file && buf[n-1] != terminator) /* last read char not null */ buf[n++] = '\0'; /* so append null-terminator */ rbuf = xrealloc(rbuf, tot + n); /* allocate more memory */ memcpy(rbuf + tot, buf, n); /* copy buffer into it */ tot += n; /* increment total byte ctr */ if (end_of_file) break; } close(fd); if (n <= 0 && !end_of_file) { if (rbuf) free(rbuf); return NULL; /* read error */ } endbuf = rbuf + tot; /* count space for pointers */ align = (sizeof(char*)-1) - ((tot + sizeof(char*)-1) & (sizeof(char*)-1)); for (c = 0, p = rbuf; p < endbuf; p++) { if (*p == terminator) *p = 0; if (!*p) c += sizeof(char*); } c += sizeof(char*); /* one extra for NULL term */ rbuf = xrealloc(rbuf, tot + c + align); /* make room for ptrs AT END */ endbuf = rbuf + tot; /* addr just past data buf */ q = ret = (char**) (endbuf+align); /* ==> free(*ret) to dealloc */ *q++ = p = rbuf; /* point ptrs to the strings */ endbuf--; /* do not traverse final NUL */ while (++p < endbuf) if (!*p) /* NUL char implies that */ *q++ = p+1; /* next string -> next char */ *q = 0; /* null ptr list terminator */ return ret; } char** file2strvec(const char* directory, const char* what) { return file2strvec_ext(directory, what, '\0'); } // warning: interface may change int read_cmdline(char *restrict const dst, unsigned sz, unsigned pid){ char name[32]; int fd; unsigned n = 0; dst[0] = '\0'; snprintf(name, sizeof name, "/proc/%u/cmdline", pid); fd = open(name, O_RDONLY); if(fd==-1) return 0; for(;;){ ssize_t r = read(fd,dst+n,sz-n); if(r==-1){ if(errno==EINTR) continue; break; } n += r; if(n==sz) break; // filled the buffer if(r==0) break; // EOF } close(fd); if(n){ int i; if(n==sz) n--; dst[n] = '\0'; i=n; while(i--){ int c = dst[i]; if(c<' ' || c>'~') dst[i]=' '; } } return n; } /* These are some nice GNU C expression subscope "inline" functions. * The can be used with arbitrary types and evaluate their arguments * exactly once. */ /* Test if item X of type T is present in the 0 terminated list L */ # define XinL(T, X, L) ( { \ T x = (X), *l = (L); \ while (*l && *l != x) l++; \ *l == x; \ } ) /* Test if item X of type T is present in the list L of length N */ # define XinLN(T, X, L, N) ( { \ T x = (X), *l = (L); \ int i = 0, n = (N); \ while (i < n && l[i] != x) i++; \ i < n && l[i] == x; \ } ) ////////////////////////////////////////////////////////////////////////////////// // This reads process info from /proc in the traditional way, for one process. // The pid (tgid? tid?) is already in p, and a path to it in path, with some // room to spare. static proc_t* simple_readproc(PROCTAB *restrict const PT, proc_t *restrict const p) { static struct stat sb; // stat() buffer static char sbuf[1024]; // buffer for stat,statm char *restrict const path = PT->path; unsigned flags = PT->flags; if (unlikely(stat(path, &sb) == -1)) /* no such dirent (anymore) */ goto next_proc; if ((flags & PROC_UID) && !XinLN(uid_t, sb.st_uid, PT->uids, PT->nuid)) goto next_proc; /* not one of the requested uids */ p->euid = sb.st_uid; /* need a way to get real uid */ p->egid = sb.st_gid; /* need a way to get real gid */ if (flags & PROC_FILLSTAT) { /* read, parse /proc/#/stat */ if (unlikely( file2str(path, "stat", sbuf, sizeof sbuf) == -1 )) goto next_proc; /* error reading /proc/#/stat */ stat2proc(sbuf, p); /* parse /proc/#/stat */ } if (unlikely(flags & PROC_FILLMEM)) { /* read, parse /proc/#/statm */ if (likely( file2str(path, "statm", sbuf, sizeof sbuf) != -1 )) statm2proc(sbuf, p); /* ignore statm errors here */ } /* statm fields just zero */ if (flags & PROC_FILLSTATUS) { /* read, parse /proc/#/status */ if (likely( file2str(path, "status", sbuf, sizeof sbuf) != -1 )){ status2proc(sbuf, p, 1); } } // if multithreaded, some values are crap if(p->nlwp > 1){ p->wchan = (KLONG)~0ull; } /* some number->text resolving which is time consuming and kind of insane */ if (flags & PROC_FILLUSR){ memcpy(p->euser, user_from_uid(p->euid), sizeof p->euser); if(flags & PROC_FILLSTATUS) { memcpy(p->ruser, user_from_uid(p->ruid), sizeof p->ruser); memcpy(p->suser, user_from_uid(p->suid), sizeof p->suser); memcpy(p->fuser, user_from_uid(p->fuid), sizeof p->fuser); } } /* some number->text resolving which is time consuming and kind of insane */ if (flags & PROC_FILLGRP){ memcpy(p->egroup, group_from_gid(p->egid), sizeof p->egroup); if(flags & PROC_FILLSTATUS) { memcpy(p->rgroup, group_from_gid(p->rgid), sizeof p->rgroup); memcpy(p->sgroup, group_from_gid(p->sgid), sizeof p->sgroup); memcpy(p->fgroup, group_from_gid(p->fgid), sizeof p->fgroup); } } if (flags & PROC_FILLSUPGRP && p->nsupgid > 0){ allocsupgrp(p); int i; for (i=0; i < p->nsupgid; i++) memcpy(p->supgrp[i], group_from_gid(p->supgid[i]), P_G_SZ); } if ((flags & PROC_FILLCOM) || (flags & PROC_FILLARG)) /* read+parse /proc/#/cmdline */ p->cmdline = file2strvec(path, "cmdline"); else p->cmdline = NULL; if (unlikely(flags & PROC_FILLENV)) /* read+parse /proc/#/environ */ p->environ = file2strvec(path, "environ"); else p->environ = NULL; if(linux_version_code>=LINUX_VERSION(2,6,24) && (flags & PROC_FILLCGROUP)) { p->cgroup = file2strvec_ext(path, "cgroup", '\n'); /* read /proc/#/cgroup */ if(p->cgroup && *p->cgroup) { int i = strlen(*p->cgroup); if( (*p->cgroup)[i-1]=='\n' ) (*p->cgroup)[i-1] = ' '; //little hack to remove trailing \n } } else p->cgroup = NULL; return p; next_proc: return NULL; } ////////////////////////////////////////////////////////////////////////////////// // This reads /proc/*/task/* data, for one task. // p is the POSIX process (task group summary) (not needed by THIS implementation) // t is the POSIX thread (task group member, generally not the leader) // path is a path to the task, with some room to spare. static proc_t* simple_readtask(PROCTAB *restrict const PT, const proc_t *restrict const p, proc_t *restrict const t, char *restrict const path) { static struct stat sb; // stat() buffer static char sbuf[1024]; // buffer for stat,statm unsigned flags = PT->flags; //printf("hhh\n"); if (unlikely(stat(path, &sb) == -1)) /* no such dirent (anymore) */ goto next_task; // if ((flags & PROC_UID) && !XinLN(uid_t, sb.st_uid, PT->uids, PT->nuid)) // goto next_task; /* not one of the requested uids */ t->euid = sb.st_uid; /* need a way to get real uid */ t->egid = sb.st_gid; /* need a way to get real gid */ //printf("iii\n"); if (flags & PROC_FILLSTAT) { /* read, parse /proc/#/stat */ if (unlikely( file2str(path, "stat", sbuf, sizeof sbuf) == -1 )) goto next_task; /* error reading /proc/#/stat */ stat2proc(sbuf, t); /* parse /proc/#/stat */ } if (unlikely(flags & PROC_FILLMEM)) { /* read, parse /proc/#/statm */ #if 0 if (likely( file2str(path, "statm", sbuf, sizeof sbuf) != -1 )) statm2proc(sbuf, t); /* ignore statm errors here */ #else t->size = p->size; t->resident = p->resident; t->share = p->share; t->trs = p->trs; t->lrs = p->lrs; t->drs = p->drs; t->dt = p->dt; #endif } /* statm fields just zero */ if (flags & PROC_FILLSTATUS) { /* read, parse /proc/#/status */ if (likely( file2str(path, "status", sbuf, sizeof sbuf) != -1 )){ status2proc(sbuf, t, 0); } } /* some number->text resolving which is time consuming */ if (flags & PROC_FILLUSR){ memcpy(t->euser, user_from_uid(t->euid), sizeof t->euser); if(flags & PROC_FILLSTATUS) { memcpy(t->ruser, user_from_uid(t->ruid), sizeof t->ruser); memcpy(t->suser, user_from_uid(t->suid), sizeof t->suser); memcpy(t->fuser, user_from_uid(t->fuid), sizeof t->fuser); } } /* some number->text resolving which is time consuming */ if (flags & PROC_FILLGRP){ memcpy(t->egroup, group_from_gid(t->egid), sizeof t->egroup); if(flags & PROC_FILLSTATUS) { memcpy(t->rgroup, group_from_gid(t->rgid), sizeof t->rgroup); memcpy(t->sgroup, group_from_gid(t->sgid), sizeof t->sgroup); memcpy(t->fgroup, group_from_gid(t->fgid), sizeof t->fgroup); } } if (flags & PROC_TASKSUPGRP && t->nsupgid > 0){ allocsupgrp(t); int i; for (i=0; i < t->nsupgid; i++) memcpy(t->supgrp[i], group_from_gid(t->supgid[i]), P_G_SZ); } #if 0 if ((flags & PROC_FILLCOM) || (flags & PROC_FILLARG)) /* read+parse /proc/#/cmdline */ t->cmdline = file2strvec(path, "cmdline"); else t->cmdline = NULL; if (unlikely(flags & PROC_FILLENV)) /* read+parse /proc/#/environ */ t->environ = file2strvec(path, "environ"); else t->environ = NULL; #else t->cmdline = p->cmdline; // better not free these until done with all threads! t->environ = p->environ; #endif t->cgroup = p->cgroup; t->ppid = p->ppid; // ought to put the per-task ppid somewhere return t; next_task: return NULL; } ////////////////////////////////////////////////////////////////////////////////// // This finds processes in /proc in the traditional way. // Return non-zero on success. static int simple_nextpid(PROCTAB *restrict const PT, proc_t *restrict const p) { static struct direct *ent; /* dirent handle */ char *restrict const path = PT->path; for (;;) { ent = readdir(PT->procfs); if(unlikely(unlikely(!ent) || unlikely(!ent->d_name))) return 0; if(likely( likely(*ent->d_name > '0') && likely(*ent->d_name <= '9') )) break; } p->tgid = strtoul(ent->d_name, NULL, 10); p->tid = p->tgid; memcpy(path, "/proc/", 6); strcpy(path+6, ent->d_name); // trust /proc to not contain evil top-level entries return 1; } ////////////////////////////////////////////////////////////////////////////////// // This finds tasks in /proc/*/task/ in the traditional way. // Return non-zero on success. static int simple_nexttid(PROCTAB *restrict const PT, const proc_t *restrict const p, proc_t *restrict const t, char *restrict const path) { static struct direct *ent; /* dirent handle */ if(PT->taskdir_user != p->tgid){ if(PT->taskdir){ closedir(PT->taskdir); } // use "path" as some tmp space snprintf(path, PROCPATHLEN, "/proc/%d/task", p->tgid); PT->taskdir = opendir(path); if(!PT->taskdir) return 0; PT->taskdir_user = p->tgid; } for (;;) { ent = readdir(PT->taskdir); if(unlikely(unlikely(!ent) || unlikely(!ent->d_name))) return 0; if(likely( likely(*ent->d_name > '0') && likely(*ent->d_name <= '9') )) break; } t->tid = strtoul(ent->d_name, NULL, 10); t->tgid = p->tgid; t->ppid = p->ppid; // cover for kernel behavior? we want both actually...? snprintf(path, PROCPATHLEN, "/proc/%d/task/%s", p->tgid, ent->d_name); return 1; } ////////////////////////////////////////////////////////////////////////////////// // This "finds" processes in a list that was given to openproc(). // Return non-zero on success. (tgid was handy) static int listed_nextpid(PROCTAB *restrict const PT, proc_t *restrict const p) { char *restrict const path = PT->path; pid_t tgid = *(PT->pids)++; if(likely( tgid )){ snprintf(path, PROCPATHLEN, "/proc/%d", tgid); p->tgid = tgid; p->tid = tgid; // they match for leaders } return tgid; } ////////////////////////////////////////////////////////////////////////////////// /* readproc: return a pointer to a proc_t filled with requested info about the * next process available matching the restriction set. If no more such * processes are available, return a null pointer (boolean false). Use the * passed buffer instead of allocating space if it is non-NULL. */ /* This is optimized so that if a PID list is given, only those files are * searched for in /proc. If other lists are given in addition to the PID list, * the same logic can follow through as for the no-PID list case. This is * fairly complex, but it does try to not to do any unnecessary work. */ proc_t* readproc(PROCTAB *restrict const PT, proc_t *restrict p) { proc_t *ret; proc_t *saved_p; PT->did_fake=0; // if (PT->taskdir) { // closedir(PT->taskdir); // PT->taskdir = NULL; // PT->taskdir_user = -1; // } saved_p = p; if(!p) p = xcalloc(p, sizeof *p); /* passed buf or alloced mem */ else memset(p, 0, sizeof *p); for(;;){ // fills in the path, plus p->tid and p->tgid if (unlikely(! PT->finder(PT,p) )) goto out; // go read the process data ret = PT->reader(PT,p); if(ret) return ret; } out: if(!saved_p) free(p); // FIXME: maybe set tid to -1 here, for "-" in display? return NULL; } ////////////////////////////////////////////////////////////////////////////////// // readtask: return a pointer to a proc_t filled with requested info about the // next task available. If no more such tasks are available, return a null // pointer (boolean false). Use the passed buffer instead of allocating // space if it is non-NULL. proc_t* readtask(PROCTAB *restrict const PT, const proc_t *restrict const p, proc_t *restrict t) { static char path[PROCPATHLEN]; // must hold /proc/2000222000/task/2000222000/cmdline proc_t *ret; proc_t *saved_t; saved_t = t; if(!t) t = xcalloc(t, sizeof *t); /* passed buf or alloced mem */ else memset(t, 0, sizeof *t); // 1. got to fake a thread for old kernels // 2. for single-threaded processes, this is faster (but must patch up stuff that differs!) if(task_dir_missing || p->nlwp < 2){ if(PT->did_fake) goto out; PT->did_fake=1; memcpy(t,p,sizeof(proc_t)); // use the per-task pending, not per-tgid pending #ifdef SIGNAL_STRING memcpy(&t->signal, &t->_sigpnd, sizeof t->signal); #else t->signal = t->_sigpnd; #endif return t; } for(;;){ // fills in the path, plus t->tid and t->tgid if (unlikely(! PT->taskfinder(PT,p,t,path) )) goto out; // simple_nexttid // go read the task data ret = PT->taskreader(PT,p,t,path); // simple_readtask if(ret) return ret; } out: if(!saved_t) free(t); return NULL; } ////////////////////////////////////////////////////////////////////////////////// // initiate a process table scan PROCTAB* openproc(int flags, ...) { va_list ap; struct stat sbuf; static int did_stat; PROCTAB* PT = xmalloc(sizeof(PROCTAB)); if(!did_stat){ task_dir_missing = stat("/proc/self/task", &sbuf); did_stat = 1; } PT->taskdir = NULL; PT->taskdir_user = -1; PT->taskfinder = simple_nexttid; PT->taskreader = simple_readtask; PT->reader = simple_readproc; if (flags & PROC_PID){ PT->procfs = NULL; PT->finder = listed_nextpid; }else{ PT->procfs = opendir("/proc"); if(!PT->procfs) return NULL; PT->finder = simple_nextpid; } PT->flags = flags; va_start(ap, flags); /* Init args list */ if (flags & PROC_PID) PT->pids = va_arg(ap, pid_t*); else if (flags & PROC_UID) { PT->uids = va_arg(ap, uid_t*); PT->nuid = va_arg(ap, int); } va_end(ap); /* Clean up args list */ return PT; } // terminate a process table scan void closeproc(PROCTAB* PT) { if (PT){ if (PT->procfs) closedir(PT->procfs); if (PT->taskdir) closedir(PT->taskdir); memset(PT,'#',sizeof(PROCTAB)); free(PT); } } // allocate memory for supgrp void allocsupgrp(proc_t *p) { if (!p || p->nsupgid == 0) return; p->supgrp = (char**)xmalloc(p->nsupgid * sizeof(char*)); int i; for (i=0; insupgid; i++) p->supgrp[i] = (char*)xmalloc(P_G_SZ * sizeof(char)); } // free memory allocated for supgrp void freesupgrp(proc_t *p) { int i; if(!p->supgid) return; for (i=0; insupgid; i++) if (p->supgrp && p->supgrp[i]) free(p->supgrp[i]); free(p->supgid); free(p->supgrp); p->supgid = NULL; p->supgrp = NULL; } // deallocate the space allocated by readproc if the passed rbuf was NULL void freeproc(proc_t* p) { if (!p) /* in case p is NULL */ return; /* ptrs are after strings to avoid copying memory when building them. */ /* so free is called on the address of the address of strvec[0]. */ if (p->cmdline) free((void*)*p->cmdline); if (p->environ) free((void*)*p->environ); if (p->cgroup) free((void*)*p->cgroup); if (p->supgid) free(p->supgid); //printf("frp: %p\n", p); free(p); } // deallocate the space allocated by readproc if the passed rbuf was NULL void freeproc_light(proc_t* p) { if (!p) /* in case p is NULL */ return; /* ptrs are after strings to avoid copying memory when building them. */ /* so free is called on the address of the address of strvec[0]. */ if (p->cmdline) free((void*)*p->cmdline); if (p->environ) free((void*)*p->environ); if (p->cgroup) free((void*)*p->cgroup); if (p->supgid) free(p->supgid); p->cmdline = NULL; p->environ = NULL; p->cgroup = NULL; p->supgid = NULL; //printf("frp: %p\n", p); } ////////////////////////////////////////////////////////////////////////////////// void look_up_our_self(proc_t *p) { char sbuf[1024]; if(file2str("/proc/self", "stat", sbuf, sizeof sbuf) == -1){ fprintf(stderr, "Error, do this: mount -t proc none /proc\n"); _exit(47); } stat2proc(sbuf, p); // parse /proc/self/stat } HIDDEN_ALIAS(readproc); HIDDEN_ALIAS(readtask); /* Convenient wrapper around openproc and readproc to slurp in the whole process * table subset satisfying the constraints of flags and the optional PID list. * Free allocated memory with exit(). Access via tab[N]->member. The pointer * list is NULL terminated. */ proc_t** readproctab(int flags, ...) { PROCTAB* PT = NULL; proc_t** tab = NULL; int n = 0; va_list ap; va_start(ap, flags); /* pass through args to openproc */ if (flags & PROC_UID) { /* temporary variables to ensure that va_arg() instances * are called in the right order */ uid_t* u; int i; u = va_arg(ap, uid_t*); i = va_arg(ap, int); PT = openproc(flags, u, i); } else if (flags & PROC_PID) PT = openproc(flags, va_arg(ap, void*)); /* assume ptr sizes same */ else PT = openproc(flags); va_end(ap); if (!PT) return 0; do { /* read table: */ tab = xrealloc(tab, (n+1)*sizeof(proc_t*));/* realloc as we go, using */ tab[n] = readproc_direct(PT, NULL); /* final null to terminate */ } while (tab[n++]); /* stop when NULL reached */ closeproc(PT); return tab; } // Try again, this time with threads and selection. proc_data_t *readproctab2(int(*want_proc)(proc_t *buf), int(*want_task)(proc_t *buf), PROCTAB *restrict const PT) { proc_t** ptab = NULL; unsigned n_proc_alloc = 0; unsigned n_proc = 0; proc_t** ttab = NULL; unsigned n_task_alloc = 0; unsigned n_task = 0; proc_t* data = NULL; unsigned n_alloc = 0; unsigned long n_used = 0; proc_data_t *pd; for(;;){ proc_t *tmp; if(n_alloc == n_used){ //proc_t *old = data; n_alloc = n_alloc*5/4+30; // grow by over 25% data = realloc(data,sizeof(proc_t)*n_alloc); //if(!data) return NULL; } if(n_proc_alloc == n_proc){ //proc_t **old = ptab; n_proc_alloc = n_proc_alloc*5/4+30; // grow by over 25% ptab = realloc(ptab,sizeof(proc_t*)*n_proc_alloc); //if(!ptab) return NULL; } tmp = readproc_direct(PT, data+n_used); if(!tmp) break; if(!want_proc(tmp)) continue; ptab[n_proc++] = (proc_t*)(n_used++); if(!( PT->flags & PROC_LOOSE_TASKS )) continue; for(;;){ proc_t *t; if(n_alloc == n_used){ proc_t *old = data; n_alloc = n_alloc*5/4+30; // grow by over 25% data = realloc(data,sizeof(proc_t)*n_alloc); // have to move tmp too tmp = data+(tmp-old); //if(!data) return NULL; } if(n_task_alloc == n_task){ //proc_t **old = ttab; n_task_alloc = n_task_alloc*5/4+1; // grow by over 25% ttab = realloc(ttab,sizeof(proc_t*)*n_task_alloc); //if(!ttab) return NULL; } t = readtask_direct(PT, tmp, data+n_used); if(!t) break; if(!want_task(t)) continue; ttab[n_task++] = (proc_t*)(n_used++); } } pd = malloc(sizeof(proc_data_t)); pd->proc = ptab; pd->task = ttab; pd->nproc = n_proc; pd->ntask = n_task; if(PT->flags & PROC_LOOSE_TASKS){ pd->tab = ttab; pd->n = n_task; }else{ pd->tab = ptab; pd->n = n_proc; } // change array indexes to pointers while(n_proc--) ptab[n_proc] = data+(long)(ptab[n_proc]); while(n_task--) ttab[n_task] = data+(long)(ttab[n_task]); return pd; } /* * get_proc_stats - lookup a single tasks information and fill out a proc_t * * On failure, returns NULL. On success, returns 'p' and 'p' is a valid * and filled out proc_t structure. */ proc_t * get_proc_stats(pid_t pid, proc_t *p) { static char path[32], sbuf[1024]; struct stat statbuf; sprintf(path, "/proc/%d", pid); if (stat(path, &statbuf)) { perror("stat"); return NULL; } if (file2str(path, "stat", sbuf, sizeof sbuf) >= 0) stat2proc(sbuf, p); /* parse /proc/#/stat */ if (file2str(path, "statm", sbuf, sizeof sbuf) >= 0) statm2proc(sbuf, p); /* ignore statm errors here */ if (file2str(path, "status", sbuf, sizeof sbuf) >= 0) status2proc(sbuf, p, 0 /*FIXME*/); return p; } poelzi-ulatencyd-55515a9/src/proc/readproc.h000066400000000000000000000301011154664534000207670ustar00rootroot00000000000000#ifndef PROCPS_PROC_READPROC_H #define PROCPS_PROC_READPROC_H // New Interface to Process Table -- PROCTAB Stream (a la Directory streams) // Copyright 1996 Charles L. Blake. // Copyright 1998 Michael K. Johnson // Copyright 1998-2003 Albert Cahalan // May be distributed under the terms of the // GNU Library General Public License, a copy of which is provided // in the file COPYING #include "procps.h" #include "pwcache.h" #define SIGNAL_STRING EXTERN_C_BEGIN // ld cutime, cstime, priority, nice, timeout, alarm, rss, // c state, // d ppid, pgrp, session, tty, tpgid, // s signal, blocked, sigignore, sigcatch, // lu flags, min_flt, cmin_flt, maj_flt, cmaj_flt, utime, stime, // lu rss_rlim, start_code, end_code, start_stack, kstk_esp, kstk_eip, // lu start_time, vsize, wchan, // This is to help document a transition from pid to tgid/tid caused // by the introduction of thread support. It is used in cases where // neither tgid nor tid seemed correct. (in other words, FIXME) #define XXXID tid // Basic data structure which holds all information we can get about a process. // (unless otherwise specified, fields are read from /proc/#/stat) // // Most of it comes from task_struct in linux/sched.h // typedef struct proc_t { // 1st 16 bytes int tid, //!< (special) task id, the POSIX thread ID (see also: tgid) ppid; //!< stat,status pid of parent process unsigned pcpu; //!< stat (special) %CPU usage (is not filled in by readproc!!!) char state, //!< stat,status single-char code for process state (S=sleeping) pad_1, //!< n/a padding pad_2, //!< n/a padding pad_3; //!< n/a padding //!< 2nd 16 bytes unsigned long long utime, //!< stat user-mode CPU time accumulated by process stime, //!< stat kernel-mode CPU time accumulated by process //!< and so on... cutime, //!< stat cumulative utime of process and reaped children cstime, //!< stat cumulative stime of process and reaped children start_time; //!< stat start time of process -- seconds since 1-1-70 #ifdef SIGNAL_STRING char //!< Linux 2.1.7x and up have 64 signals. Allow 64, plus '\0' and padding. signal[18], //!< status mask of pending signals, per-task for readtask() but per-proc for readproc() blocked[18], //!< status mask of blocked signals sigignore[18], //!< status mask of ignored signals sigcatch[18], //!< status mask of caught signals _sigpnd[18]; //!< status mask of PER TASK pending signals #else long long //!< Linux 2.1.7x and up have 64 signals. signal, //!< status mask of pending signals, per-task for readtask() but per-proc for readproc() blocked, //!< status mask of blocked signals sigignore, //!< status mask of ignored signals sigcatch, //!< status mask of caught signals _sigpnd; //!< status mask of PER TASK pending signals #endif unsigned KLONG start_code, //!< stat address of beginning of code segment end_code, //!< stat address of end of code segment start_stack, //!< stat address of the bottom of stack for the process kstk_esp, //!< stat kernel stack pointer kstk_eip, //!< stat kernel instruction pointer wchan; //!< stat (special) address of kernel wait channel proc is sleeping in long priority, //!< stat kernel scheduling priority nice, //!< stat standard unix nice level of process rss, //!< stat resident set size from /proc/#/stat (pages) alarm, //!< stat ? //!< the next 7 members come from /proc/#/statm size, //!< statm total # of pages of memory resident, //!< statm number of resident set (non-swapped) pages (4k) share, //!< statm number of pages of shared (mmap'd) memory trs, //!< statm text resident set size lrs, //!< statm shared-lib resident set size drs, //!< statm data resident set size dt; //!< statm dirty pages unsigned long vm_size, //!< status same as vsize in kb vm_lock, //!< status locked pages in kb vm_rss, //!< status same as rss in kb vm_data, //!< status data size vm_stack, //!< status stack size vm_exe, //!< status executable size vm_lib, //!< status library size (all pages, not just used ones) rtprio, //!< stat real-time priority sched, //!< stat scheduling class vsize, //!< stat number of pages of virtual memory ... rss_rlim, //!< stat resident set size limit? flags, //!< stat kernel flags for the process min_flt, //!< stat number of minor page faults since process start maj_flt, //!< stat number of major page faults since process start cmin_flt, //!< stat cumulative min_flt of process and child processes cmaj_flt; //!< stat cumulative maj_flt of process and child processes char **environ, //!< (special) environment string vector (/proc/#/environ) **cmdline; //!< (special) command line string vector (/proc/#/cmdline) char //!< Be compatible: Digital allows 16 and NT allows 14 ??? euser[P_G_SZ], //!< stat(),status effective user name ruser[P_G_SZ], //!< status real user name suser[P_G_SZ], //!< status saved user name fuser[P_G_SZ], //!< status filesystem user name rgroup[P_G_SZ], //!< status real group name egroup[P_G_SZ], //!< status effective group name sgroup[P_G_SZ], //!< status saved group name fgroup[P_G_SZ], //!< status filesystem group name **supgrp, //!< status supplementary groups cmd[32]; //!< stat,status basename of executable file in call to exec(2) struct proc_t *ring, //!< n/a thread group ring *next; //!< n/a various library uses int pgrp, //!< stat process group id session, //!< stat session id nlwp, //!< stat,status number of threads, or 0 if no clue tgid, //!< (special) task group ID, the POSIX PID (see also: tid) tty, //!< stat full device number of controlling terminal euid, egid, //!< stat(),status effective ruid, rgid, //!< status real suid, sgid, //!< status saved fuid, fgid, //!< status fs (used for file access only) tpgid, //!< stat terminal process group id nsupgid, //!< status number of supplementary groups *supgid, //!< status supplementary gid's exit_signal, //!< stat might not be SIGCHLD processor; //!< stat current (or most recent?) CPU char **cgroup; //!< cgroup current cgroup, looks like a classic filepath } proc_t; // PROCTAB: data structure holding the persistent information readproc needs // from openproc(). The setup is intentionally similar to the dirent interface // and other system table interfaces (utmp+wtmp come to mind). #include #include #include #define PROCPATHLEN 64 // must hold /proc/2000222000/task/2000222000/cmdline typedef struct PROCTAB { DIR* procfs; // char deBug0[64]; DIR* taskdir; // for threads // char deBug1[64]; pid_t taskdir_user; // for threads int did_fake; // used when taskdir is missing int(*finder)(struct PROCTAB *restrict const, proc_t *restrict const); proc_t*(*reader)(struct PROCTAB *restrict const, proc_t *restrict const); int(*taskfinder)(struct PROCTAB *restrict const, const proc_t *restrict const, proc_t *restrict const, char *restrict const); proc_t*(*taskreader)(struct PROCTAB *restrict const, const proc_t *restrict const, proc_t *restrict const, char *restrict const); pid_t* pids; // pids of the procs uid_t* uids; // uids of procs int nuid; // cannot really sentinel-terminate unsigned short[] int i; // generic unsigned flags; unsigned u; // generic void * vp; // generic char path[PROCPATHLEN]; // must hold /proc/2000222000/task/2000222000/cmdline unsigned pathlen; // length of string in the above (w/o '\0') } PROCTAB; // initialize a PROCTAB structure holding needed call-to-call persistent data extern PROCTAB* openproc(int flags, ... /* pid_t*|uid_t*|dev_t*|char* [, int n] */ ); typedef struct proc_data_t { proc_t **tab; proc_t **proc; proc_t **task; int n; int nproc; int ntask; } proc_data_t; extern proc_data_t *readproctab2(int(*want_proc)(proc_t *buf), int(*want_task)(proc_t *buf), PROCTAB *restrict const PT); // Convenient wrapper around openproc and readproc to slurp in the whole process // table subset satisfying the constraints of flags and the optional PID list. // Free allocated memory with exit(). Access via tab[N]->member. The pointer // list is NULL terminated. extern proc_t** readproctab(int flags, ... /* same as openproc */ ); // clean-up open files, etc from the openproc() extern void closeproc(PROCTAB* PT); // allocate memory for supgrp extern void allocsupgrp(proc_t *p); // free memory allocated for supgrp extern void freesupgrp(proc_t *p); // retrieve the next process matching the criteria set by the openproc() extern proc_t* readproc(PROCTAB *restrict const PT, proc_t *restrict p); extern proc_t* readtask(PROCTAB *restrict const PT, const proc_t *restrict const p, proc_t *restrict t); // warning: interface may change extern int read_cmdline(char *restrict const dst, unsigned sz, unsigned pid); extern void look_up_our_self(proc_t *p); // deallocate space allocated by readproc extern void freeproc(proc_t* p); extern void freeproc_light(proc_t* p); //fill out a proc_t for a single task extern proc_t * get_proc_stats(pid_t pid, proc_t *p); // openproc/readproctab: // // Return PROCTAB* / *proc_t[] or NULL on error ((probably) "/proc" cannot be // opened.) By default readproc will consider all processes as valid to parse // and return, but not actually fill in the cmdline, environ, and /proc/#/statm // derived memory fields. // // `flags' (a bitwise-or of PROC_* below) modifies the default behavior. The // "fill" options will cause more of the proc_t to be filled in. The "filter" // options all use the second argument as the pointer to a list of objects: // process status', process id's, user id's. The third // argument is the length of the list (currently only used for lists of user // id's since uid_t supports no convenient termination sentinel.) #define PROC_FILLMEM 0x0001 // read statm #define PROC_FILLCOM 0x0002 // alloc and fill in `cmdline' #define PROC_FILLENV 0x0004 // alloc and fill in `environ' #define PROC_FILLUSR 0x0008 // resolve user id number -> user name #define PROC_FILLGRP 0x0010 // resolve group id number -> group name #define PROC_FILLSTATUS 0x0020 // read status -- currently unconditional #define PROC_FILLSTAT 0x0040 // read stat -- currently unconditional #define PROC_FILLWCHAN 0x0080 // look up WCHAN name #define PROC_FILLARG 0x0100 // alloc and fill in `cmdline' #define PROC_FILLCGROUP 0x0200 // alloc and fill in `cgroup` #define PROC_FILLSUPGRP 0x0400 // resolve supplementary group id number -> group name #define PROC_TASKSUPGRP 0x0800 // resolve supplementary group id number -> group name #define PROC_LOOSE_TASKS 0x2000 // threat threads as if they were processes // Obsolete, consider only processes with one of the passed: #define PROC_PID 0x1000 // process id numbers ( 0 terminated) #define PROC_UID 0x4000 // user id numbers ( length needed ) // it helps to give app code a few spare bits #define PROC_SPARE_1 0x01000000 #define PROC_SPARE_2 0x02000000 #define PROC_SPARE_3 0x04000000 #define PROC_SPARE_4 0x08000000 char** file2strvec(const char* directory, const char* what); char** file2strvec_ext(const char* directory, const char* what, char terminator); EXTERN_C_END #endif poelzi-ulatencyd-55515a9/src/proc/sig.c000066400000000000000000000142531154664534000177570ustar00rootroot00000000000000/* * Copyright 1998-2003 by Albert Cahalan; all rights resered. * This file may be used subject to the terms and conditions of the * GNU Library General Public License Version 2, or any later version * at your option, as published by the Free Software Foundation. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. */ #include #include #include #include #include "sig.h" /* Linux signals: * * SIGSYS is required by Unix98. * SIGEMT is part of SysV, BSD, and ancient UNIX tradition. * * They are provided by these Linux ports: alpha, mips, sparc, and sparc64. * You get SIGSTKFLT and SIGUNUSED instead on i386, m68k, ppc, and arm. * (this is a Linux & libc bug -- both must be fixed) * * Total garbage: SIGIO SIGINFO SIGIOT SIGLOST SIGCLD * (popular ones are handled as aliases) * Nearly garbage: SIGSTKFLT SIGUNUSED (nothing else to fill slots) */ /* Linux 2.3.29 replaces SIGUNUSED with the standard SIGSYS signal */ #ifndef SIGSYS # warning Standards require that define SIGSYS # define SIGSYS SIGUNUSED #endif /* If we see both, it is likely SIGSTKFLT (junk) was replaced. */ #ifdef SIGEMT # undef SIGSTKFLT #endif #ifndef SIGRTMIN # warning Standards require that define SIGRTMIN; assuming 32 # define SIGRTMIN 32 #endif /* It seems the SPARC libc does not know the kernel supports SIGPWR. */ #ifndef SIGPWR # warning Your header files lack SIGPWR. (assuming it is number 29) # define SIGPWR 29 #endif typedef struct mapstruct { const char *name; int num; } mapstruct; static const mapstruct sigtable[] = { {"ABRT", SIGABRT}, /* IOT */ {"ALRM", SIGALRM}, {"BUS", SIGBUS}, {"CHLD", SIGCHLD}, /* CLD */ {"CONT", SIGCONT}, #ifdef SIGEMT {"EMT", SIGEMT}, #endif {"FPE", SIGFPE}, {"HUP", SIGHUP}, {"ILL", SIGILL}, {"INT", SIGINT}, {"KILL", SIGKILL}, {"PIPE", SIGPIPE}, {"POLL", SIGPOLL}, /* IO */ {"PROF", SIGPROF}, {"PWR", SIGPWR}, {"QUIT", SIGQUIT}, {"SEGV", SIGSEGV}, #ifdef SIGSTKFLT {"STKFLT", SIGSTKFLT}, #endif {"STOP", SIGSTOP}, {"SYS", SIGSYS}, /* UNUSED */ {"TERM", SIGTERM}, {"TRAP", SIGTRAP}, {"TSTP", SIGTSTP}, {"TTIN", SIGTTIN}, {"TTOU", SIGTTOU}, {"URG", SIGURG}, {"USR1", SIGUSR1}, {"USR2", SIGUSR2}, {"VTALRM", SIGVTALRM}, {"WINCH", SIGWINCH}, {"XCPU", SIGXCPU}, {"XFSZ", SIGXFSZ} }; static const int number_of_signals = sizeof(sigtable)/sizeof(mapstruct); static int compare_signal_names(const void *a, const void *b){ return strcasecmp( ((const mapstruct*)a)->name, ((const mapstruct*)b)->name ); } /* return -1 on failure */ int signal_name_to_number(const char *restrict name){ long val; int offset; /* clean up name */ if(!strncasecmp(name,"SIG",3)) name += 3; if(!strcasecmp(name,"CLD")) return SIGCHLD; if(!strcasecmp(name,"IO")) return SIGPOLL; if(!strcasecmp(name,"IOT")) return SIGABRT; /* search the table */ { const mapstruct ms = {name,0}; const mapstruct *restrict const ptr = bsearch( &ms, sigtable, number_of_signals, sizeof(mapstruct), compare_signal_names ); if(ptr) return ptr->num; } if(!strcasecmp(name,"RTMIN")) return SIGRTMIN; if(!strcasecmp(name,"EXIT")) return 0; if(!strcasecmp(name,"NULL")) return 0; offset = 0; if(!strncasecmp(name,"RTMIN+",6)){ name += 6; offset = SIGRTMIN; } /* not found, so try as a number */ { char *endp; val = strtol(name,&endp,10); if(*endp || endp==name) return -1; /* not valid */ } if(val+SIGRTMIN>127) return -1; /* not valid */ return val+offset; } const char *signal_number_to_name(int signo){ static char buf[32]; int n = number_of_signals; signo &= 0x7f; /* need to process exit values too */ while(n--){ if(sigtable[n].num==signo) return sigtable[n].name; } if(signo == SIGRTMIN) return "RTMIN"; if(signo) sprintf(buf, "RTMIN+%d", signo-SIGRTMIN); else strcpy(buf,"0"); /* AIX has NULL; Solaris has EXIT */ return buf; } int print_given_signals(int argc, const char *restrict const *restrict argv, int max_line){ char buf[1280]; /* 128 signals, "RTMIN+xx" is largest */ int ret = 0; /* to be used as exit code by caller */ int place = 0; /* position on this line */ int amt; if(argc > 128) return 1; while(argc--){ char tmpbuf[16]; const char *restrict const txt = *argv; if(*txt >= '0' && *txt <= '9'){ long val; char *endp; val = strtol(txt,&endp,10); if(*endp){ fprintf(stderr, "Signal \"%s\" not known.\n", txt); ret = 1; goto end; } amt = sprintf(tmpbuf, "%s", signal_number_to_name(val)); }else{ int sno; sno = signal_name_to_number(txt); if(sno == -1){ fprintf(stderr, "Signal \"%s\" not known.\n", txt); ret = 1; goto end; } amt = sprintf(tmpbuf, "%d", sno); } if(!place){ strcpy(buf,tmpbuf); place = amt; goto end; } if(amt+place+1 > max_line){ printf("%s\n", buf); strcpy(buf,tmpbuf); place = amt; goto end; } sprintf(buf+place, " %s", tmpbuf); place += amt+1; end: argv++; } if(place) printf("%s\n", buf); return ret; } void pretty_print_signals(void){ int i = 0; while(++i <= number_of_signals){ int n; n = printf("%2d %s", i, signal_number_to_name(i)); if(i%7) printf(" \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + n); else printf("\n"); } if((i-1)%7) printf("\n"); } void unix_print_signals(void){ int pos = 0; int i = 0; while(++i <= number_of_signals){ if(i-1) printf("%c", (pos>73)?(pos=0,'\n'):(pos++,' ') ); pos += printf("%s", signal_number_to_name(i)); } printf("\n"); } /* sanity check */ static int init_signal_list(void) __attribute__((constructor)); static int init_signal_list(void){ if(number_of_signals != 31){ fprintf(stderr, "WARNING: %d signals -- adjust and recompile.\n", number_of_signals); } return 0; } poelzi-ulatencyd-55515a9/src/proc/sig.h000066400000000000000000000016601154664534000177620ustar00rootroot00000000000000#ifndef PROC_SIG_H #define PROC_SIG_H /* * Copyright 1998-2003 by Albert Cahalan; all rights resered. * This file may be used subject to the terms and conditions of the * GNU Library General Public License Version 2, or any later version * at your option, as published by the Free Software Foundation. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. */ #include "procps.h" EXTERN_C_BEGIN /* return -1 on failure */ extern int signal_name_to_number(const char *restrict name); extern const char *signal_number_to_name(int signo); extern int print_given_signals(int argc, const char *restrict const *restrict argv, int max_line); extern void pretty_print_signals(void); extern void unix_print_signals(void); EXTERN_C_END #endif poelzi-ulatencyd-55515a9/src/proc/slab.c000066400000000000000000000215461154664534000201210ustar00rootroot00000000000000/* * slab.c - slab related functions for libproc * * Chris Rivera * Robert Love * * This program is licensed under the GNU Library General Public License, v2 * * Copyright (C) 2003 Chris Rivera * Copyright 2004, Albert Cahalan */ #include #include #include #include #include #include "slab.h" #include "procps.h" #define SLABINFO_LINE_LEN 2048 #define SLABINFO_VER_LEN 100 #define SLABINFO_FILE "/proc/slabinfo" static struct slab_info *free_index; /* * get_slabnode - allocate slab_info structures using a free list * * In the fast path, we simply return a node off the free list. In the slow * list, we malloc() a new node. The free list is never automatically reaped, * both for simplicity and because the number of slab caches is fairly * constant. */ static struct slab_info *get_slabnode(void) { struct slab_info *node; if (free_index) { node = free_index; free_index = free_index->next; } else { node = malloc(sizeof(struct slab_info)); if (!node) perror("malloc"); } return node; } /* * slab_badname_detect - return true if current slab was declared with * whitespaces for instance * FIXME :Other cases ? */ static int slab_badname_detect(const char *restrict buffer) { int numberarea=0; while (*buffer){ if((*buffer)==' ') numberarea=1; if(isalpha(*buffer)&&numberarea) return 1; buffer++; } return 0; } /* * put_slabinfo - return all allocated nodes to the free list */ void put_slabinfo(struct slab_info *head) { free_index = head; } /* * free_slabinfo - deallocate the memory associated with each node in the * slab_info linked list */ void free_slabinfo(struct slab_info *list) { while (list) { struct slab_info *temp = list->next; free(list); list = temp; } } // parse_slabinfo20 - actual parse routine for slabinfo 2.x (2.6 kernels) // Note: difference between 2.0 and 2.1 is in the ": globalstat" part where version 2.1 // has extra column . We don't use ": globalstat" part in both versions. // // Formats (we don't use "statistics" extensions) // // slabinfo - version: 2.1 // # name \ // : tunables \ // : slabdata // // slabinfo - version: 2.1 (statistics) // # name \ // : tunables \ // : slabdata \ // : globalstat \ // : cpustat // // slabinfo - version: 2.0 // # name \ // : tunables \ // : slabdata // // slabinfo - version: 2.0 (statistics) // # name \ // : tunables \ // : slabdata \ // : globalstat \ // : cpustat static int parse_slabinfo20(struct slab_info **list, struct slab_stat *stats, FILE *f) { struct slab_info *curr = NULL, *prev = NULL; char buffer[SLABINFO_LINE_LEN]; int entries = 0; int page_size = getpagesize(); stats->min_obj_size = INT_MAX; stats->max_obj_size = 0; while (fgets(buffer, SLABINFO_LINE_LEN, f)) { int assigned; if (buffer[0] == '#') continue; curr = get_slabnode(); if (!curr) break; if (entries++ == 0) *list = curr; else prev->next = curr; assigned = sscanf(buffer, "%" STRINGIFY(SLAB_INFO_NAME_LEN) "s %d %d %d %d %d : tunables %*d %*d %*d : \ slabdata %d %d %*d", curr->name, &curr->nr_active_objs, &curr->nr_objs, &curr->obj_size, &curr->objs_per_slab, &curr->pages_per_slab, &curr->nr_active_slabs, &curr->nr_slabs); if (assigned < 8) { fprintf(stderr, "unrecognizable data in slabinfo!\n"); curr = NULL; break; } if (curr->obj_size < stats->min_obj_size) stats->min_obj_size = curr->obj_size; if (curr->obj_size > stats->max_obj_size) stats->max_obj_size = curr->obj_size; curr->cache_size = (unsigned long)curr->nr_slabs * curr->pages_per_slab * page_size; if (curr->nr_objs) { curr->use = 100 * curr->nr_active_objs / curr->nr_objs; stats->nr_active_caches++; } else curr->use = 0; stats->nr_objs += curr->nr_objs; stats->nr_active_objs += curr->nr_active_objs; stats->total_size += (unsigned long)curr->nr_objs * curr->obj_size; stats->active_size += (unsigned long)curr->nr_active_objs * curr->obj_size; stats->nr_pages += curr->nr_slabs * curr->pages_per_slab; stats->nr_slabs += curr->nr_slabs; stats->nr_active_slabs += curr->nr_active_slabs; prev = curr; } if (!curr) { fprintf(stderr, "\rerror reading slabinfo!\n"); return 1; } curr->next = NULL; stats->nr_caches = entries; if (stats->nr_objs) stats->avg_obj_size = stats->total_size / stats->nr_objs; return 0; } /* * parse_slabinfo11 - actual parsing routine for slabinfo 1.1 (2.4 kernels) */ static int parse_slabinfo11(struct slab_info **list, struct slab_stat *stats, FILE *f) { struct slab_info *curr = NULL, *prev = NULL; char buffer[SLABINFO_LINE_LEN]; int entries = 0; int page_size = getpagesize(); stats->min_obj_size = INT_MAX; stats->max_obj_size = 0; while (fgets(buffer, SLABINFO_LINE_LEN, f)) { int assigned; curr = get_slabnode(); if (!curr) break; if (entries++ == 0) *list = curr; else prev->next = curr; assigned = sscanf(buffer, "%" STRINGIFY(SLAB_INFO_NAME_LEN) "s %d %d %d %d %d %d", curr->name, &curr->nr_active_objs, &curr->nr_objs, &curr->obj_size, &curr->nr_active_slabs, &curr->nr_slabs, &curr->pages_per_slab); if (assigned < 6) { fprintf(stderr, "unrecognizable data in your slabinfo version 1.1\n\r"); if(slab_badname_detect(buffer)) fprintf(stderr, "Found an error in cache name at line %s\n", buffer); curr = NULL; break; } if (curr->obj_size < stats->min_obj_size) stats->min_obj_size = curr->obj_size; if (curr->obj_size > stats->max_obj_size) stats->max_obj_size = curr->obj_size; curr->cache_size = (unsigned long)curr->nr_slabs * curr->pages_per_slab * page_size; if (curr->nr_objs) { curr->use = 100 * curr->nr_active_objs / curr->nr_objs; stats->nr_active_caches++; } else curr->use = 0; if (curr->obj_size) curr->objs_per_slab = curr->pages_per_slab * page_size / curr->obj_size; stats->nr_objs += curr->nr_objs; stats->nr_active_objs += curr->nr_active_objs; stats->total_size += (unsigned long)curr->nr_objs * curr->obj_size; stats->active_size += (unsigned long)curr->nr_active_objs * curr->obj_size; stats->nr_pages += curr->nr_slabs * curr->pages_per_slab; stats->nr_slabs += curr->nr_slabs; stats->nr_active_slabs += curr->nr_active_slabs; prev = curr; } if (!curr) { fprintf(stderr, "\rerror reading slabinfo!\n"); return 1; } curr->next = NULL; stats->nr_caches = entries; if (stats->nr_objs) stats->avg_obj_size = stats->total_size / stats->nr_objs; return 0; } /* * parse_slabinfo10 - actual parsing routine for slabinfo 1.0 (2.2 kernels) * * Not yet implemented. Please feel free. */ static int parse_slabinfo10(struct slab_info **list, struct slab_stat *stats, FILE *f) { (void) list, (void) stats, (void) f; fprintf(stderr, "slabinfo version 1.0 not yet supported\n"); return 1; } /* * slabinfo - parse the system's slabinfo and fill out both a linked list of * slab_info structures and the slab_stat structure * * The function returns zero on success, in which case 'list' and 'stats' are * valid. Nonzero is returned on failure and the state of 'list' and 'stats' * are undefined. */ int get_slabinfo(struct slab_info **list, struct slab_stat *stats) { FILE *slabfile; char buffer[SLABINFO_VER_LEN]; int major, minor, ret = 0; slabfile = fopen(SLABINFO_FILE, "r"); if (!slabfile) { perror("fopen " SLABINFO_FILE); return 1; } if (!fgets(buffer, SLABINFO_VER_LEN, slabfile)) { fprintf(stderr, "cannot read from slabinfo\n"); return 1; } if (sscanf(buffer, "slabinfo - version: %d.%d", &major, &minor) != 2) { fprintf(stderr, "not the good old slabinfo we know\n"); return 1; } if (major == 2) ret = parse_slabinfo20(list, stats, slabfile); else if (major == 1 && minor == 1) ret = parse_slabinfo11(list, stats, slabfile); else if (major == 1 && minor == 0) ret = parse_slabinfo10(list, stats, slabfile); else { fprintf(stderr, "unrecognizable slabinfo version\n"); return 1; } fclose(slabfile); return ret; } poelzi-ulatencyd-55515a9/src/proc/slab.h000066400000000000000000000034021154664534000201150ustar00rootroot00000000000000#ifndef _PROC_SLAB_H #define _PROC_SLAB_H #define SLAB_INFO_NAME_LEN 64 struct slab_info { char name[SLAB_INFO_NAME_LEN]; /* name of this cache */ struct slab_info *next; unsigned long cache_size; /* size of entire cache */ unsigned nr_objs; /* number of objects in this cache */ unsigned nr_active_objs; /* number of active objects */ unsigned obj_size; /* size of each object */ unsigned objs_per_slab; /* number of objects per slab */ unsigned pages_per_slab; /* number of pages per slab */ unsigned nr_slabs; /* number of slabs in this cache */ unsigned nr_active_slabs; /* number of active slabs */ unsigned use; /* percent full: total / active */ }; struct slab_stat { unsigned long total_size; /* size of all objects */ unsigned long active_size; /* size of all active objects */ unsigned nr_objs; /* number of objects, among all caches */ unsigned nr_active_objs; /* number of active objects, among all caches */ unsigned nr_pages; /* number of pages consumed by all objects */ unsigned nr_slabs; /* number of slabs, among all caches */ unsigned nr_active_slabs; /* number of active slabs, among all caches */ unsigned nr_caches; /* number of caches */ unsigned nr_active_caches; /* number of active caches */ unsigned avg_obj_size; /* average object size */ unsigned min_obj_size; /* size of smallest object */ unsigned max_obj_size; /* size of largest object */ }; extern void put_slabinfo(struct slab_info *); extern void free_slabinfo(struct slab_info *); extern int get_slabinfo(struct slab_info **, struct slab_stat *); #endif /* _PROC_SLAB_H */ poelzi-ulatencyd-55515a9/src/proc/smaps.c000066400000000000000000000071711154664534000203210ustar00rootroot00000000000000#if 0 #include "procps.h" #include #include #include #include #include #include #include #include "procps.h" struct smap_entry { unsigned KLONG start; unsigned KLONG beyond; long long offset; char flags[8]; unsigned dev_major; unsigned dev_minor; unsigned long long inode; unsigned long rss; unsigned long pss; unsigned long sclean; unsigned long sdirty; unsigned long pclean; unsigned long pdirty; unsigned long ref; unsigned long swap; }; //////////////////////////////////////////////////////////////////////////////// // This code will surely make normal programmers cry. I need speed though, // and /proc/*/smaps should make anybody cry. (WTF kind of brain damage...?) struct smap_summary { unsigned long size; unsigned long rss; unsigned long pss; unsigned long sclean; unsigned long sdirty; unsigned long pclean; unsigned long pdirty; unsigned long ref; unsigned long swap; }; struct ssjt { char str[16]; int len; int offset; }; #define JTE(o,x) {#x,sizeof(#x)-1,o} void get_smap_sums(struct smap_summary *restrict ss, const char *restrict const filename){ static struct ssjt table[] = { JTE(-1,default), JTE( 1,Rss), JTE(-1,default), JTE( 2,Pss), JTE( 8,Swap), JTE( 5,Private_Clean), JTE( 6,Private_Dirty), JTE(-1,default), JTE( 7,Referenced), JTE(-1,default), JTE( 0,Size), JTE(-1,default), JTE(-1,default), JTE(-1,default), JTE(-1,default), JTE(-1,default), // KernelPageSize would go here JTE(-1,default), JTE(-1,default), JTE( 4,Shared_Dirty), JTE(-1,default), JTE(-1,default), JTE(-1,default), JTE(-1,default), JTE( 3,Shared_Clean), JTE(-1,default), JTE(-1,default), JTE(-1,default), JTE(-1,default), JTE(-1,default), JTE(-1,default), JTE(-1,default), JTE(-1,default), }; char buf[20480]; int p1 = 0; int p2 = 0; memset(ss,0,sizeof *ss); int fd = open(filename,O_RDONLY); if(fd==-1) return; for(;;){ char *nlp = memchr(buf+p1,'\n',p2-p1); if(!nlp){ if(p1){ // the memmove should never do anything, because the // kernel seems to give us the greatest number of // complete lines of text that fit in a single page // (and thus p2-p1 is zero) memmove(buf,buf+p1,p2-p1); p2 -= p1; p1 = 0; } ssize_t rb = read(fd,buf+p1,sizeof buf - p1); if(rb < 1) break; p2 += rb; nlp = memchr(buf+p1,'\n',p2-p1); if(!nlp) break; } char *s = buf+p1; int len = nlp-s; p1 += len+1; if(len<27) continue; //printf("j <%13.13s>\n",s); if(s[0]<'A' || s[0]>'Z') continue; unsigned hash = ( (s[8]&15) + (s[1]&15) ) ^ (s[0]&3); hash &= 31; //printf("x %2d <%13.13s>\n",hash,s); if(s[table[hash].len] != ':') continue; //printf("y %2d <%13.13s>\n",hash,s); if(memcmp(table[hash].str,s,table[hash].len)) continue; //printf("z %2d <%13.13s>\n",hash,s); s += table[hash].len; while(*++s==' ') ; unsigned long ul = 0; for(;;){ char c = *s++; if(c != ' '){ ul *= 10; ul += c-'0'; continue; } break; } // if(table[hash].offset == 2) // printf("Pss:%20lu kB\n",ul); unsigned long *ulp = &ss->size + table[hash].offset; *ulp += ul; // memcpy(ss+table[hash].offset*sizeof(unsigned long), &ul, sizeof(unsigned long)); } close(fd); } int main(int argc, char *argv[]){ struct smap_summary ss; get_smap_sums(&ss, argv[1]); printf("%9lu\n",ss.size); printf("%9lu\n",ss.rss); printf("%9lu\n",ss.pss); printf("%9lu\n",ss.sclean); printf("%9lu\n",ss.sdirty); printf("%9lu\n",ss.pclean); printf("%9lu\n",ss.pdirty); printf("%9lu\n",ss.ref); printf("%9lu\n",ss.swap); return 0; } #endif poelzi-ulatencyd-55515a9/src/proc/sysinfo.c000066400000000000000000001010641154664534000206640ustar00rootroot00000000000000// Copyright (C) 1992-1998 by Michael K. Johnson, johnsonm@redhat.com // Copyright 1998-2003 Albert Cahalan // // This file is placed under the conditions of the GNU Library // General Public License, version 2, or any later version. // See file COPYING for information on distribution conditions. // // File for parsing top-level /proc entities. */ // // June 2003, Fabian Frederick, disk and slab info #include #include #include #include #include #include #include #include "version.h" #include "sysinfo.h" /* include self to verify prototypes */ #ifndef HZ #include /* htons */ #endif long smp_num_cpus; /* number of CPUs */ #define BAD_OPEN_MESSAGE \ "Error: /proc must be mounted\n" \ " To mount /proc at boot you need an /etc/fstab line like:\n" \ " /proc /proc proc defaults\n" \ " In the meantime, run \"mount /proc /proc -t proc\"\n" #define STAT_FILE "/proc/stat" static int stat_fd = -1; #define UPTIME_FILE "/proc/uptime" static int uptime_fd = -1; #define LOADAVG_FILE "/proc/loadavg" static int loadavg_fd = -1; #define MEMINFO_FILE "/proc/meminfo" static int meminfo_fd = -1; #define VMINFO_FILE "/proc/vmstat" static int vminfo_fd = -1; // As of 2.6.24 /proc/meminfo seems to need 888 on 64-bit, // and would need 1258 if the obsolete fields were there. static char buf[2048]; /* This macro opens filename only if necessary and seeks to 0 so * that successive calls to the functions are more efficient. * It also reads the current contents of the file into the global buf. */ #define FILE_TO_BUF(filename, fd) do{ \ static int local_n; \ if (fd == -1 && (fd = open(filename, O_RDONLY)) == -1) { \ fputs(BAD_OPEN_MESSAGE, stderr); \ fflush(NULL); \ _exit(102); \ } \ lseek(fd, 0L, SEEK_SET); \ if ((local_n = read(fd, buf, sizeof buf - 1)) < 0) { \ perror(filename); \ fflush(NULL); \ _exit(103); \ } \ buf[local_n] = '\0'; \ }while(0) /* evals 'x' twice */ #define SET_IF_DESIRED(x,y) do{ if(x) *(x) = (y); }while(0) /***********************************************************************/ int uptime(double *restrict uptime_secs, double *restrict idle_secs) { double up=0, idle=0; char *restrict savelocale; FILE_TO_BUF(UPTIME_FILE,uptime_fd); savelocale = setlocale(LC_NUMERIC, NULL); setlocale(LC_NUMERIC,"C"); if (sscanf(buf, "%lf %lf", &up, &idle) < 2) { setlocale(LC_NUMERIC,savelocale); fputs("bad data in " UPTIME_FILE "\n", stderr); return 0; } setlocale(LC_NUMERIC,savelocale); SET_IF_DESIRED(uptime_secs, up); SET_IF_DESIRED(idle_secs, idle); return up; /* assume never be zero seconds in practice */ } /*********************************************************************** * Some values in /proc are expressed in units of 1/HZ seconds, where HZ * is the kernel clock tick rate. One of these units is called a jiffy. * The HZ value used in the kernel may vary according to hacker desire. * According to Linus Torvalds, this is not true. He considers the values * in /proc as being in architecture-dependant units that have no relation * to the kernel clock tick rate. Examination of the kernel source code * reveals that opinion as wishful thinking. * * In any case, we need the HZ constant as used in /proc. (the real HZ value * may differ, but we don't care) There are several ways we could get HZ: * * 1. Include the kernel header file. If it changes, recompile this library. * 2. Use the sysconf() function. When HZ changes, recompile the C library! * 3. Ask the kernel. This is obviously correct... * * Linus Torvalds won't let us ask the kernel, because he thinks we should * not know the HZ value. Oh well, we don't have to listen to him. * Someone smuggled out the HZ value. :-) * * This code should work fine, even if Linus fixes the kernel to match his * stated behavior. The code only fails in case of a partial conversion. * * Recent update: on some architectures, the 2.4 kernel provides an * ELF note to indicate HZ. This may be for ARM or user-mode Linux * support. This ought to be investigated. Note that sysconf() is still * unreliable, because it doesn't return an error code when it is * used with a kernel that doesn't support the ELF note. On some other * architectures there may be a system call or sysctl() that will work. */ unsigned long long Hertz; static void old_Hertz_hack(void){ unsigned long long user_j, nice_j, sys_j, other_j, wait_j, hirq_j, sirq_j, stol_j; /* jiffies (clock ticks) */ double up_1, up_2, seconds; unsigned long long jiffies; unsigned h; char *restrict savelocale; wait_j = hirq_j = sirq_j = stol_j = 0; savelocale = setlocale(LC_NUMERIC, NULL); setlocale(LC_NUMERIC, "C"); do{ FILE_TO_BUF(UPTIME_FILE,uptime_fd); sscanf(buf, "%lf", &up_1); /* uptime(&up_1, NULL); */ FILE_TO_BUF(STAT_FILE,stat_fd); sscanf(buf, "cpu %Lu %Lu %Lu %Lu %Lu %Lu %Lu %Lu", &user_j, &nice_j, &sys_j, &other_j, &wait_j, &hirq_j, &sirq_j, &stol_j); FILE_TO_BUF(UPTIME_FILE,uptime_fd); sscanf(buf, "%lf", &up_2); /* uptime(&up_2, NULL); */ } while((long long)( (up_2-up_1)*1000.0/up_1 )); /* want under 0.1% error */ setlocale(LC_NUMERIC, savelocale); jiffies = user_j + nice_j + sys_j + other_j + wait_j + hirq_j + sirq_j + stol_j ; seconds = (up_1 + up_2) / 2; h = (unsigned)( (double)jiffies/seconds/smp_num_cpus ); /* actual values used by 2.4 kernels: 32 64 100 128 1000 1024 1200 */ switch(h){ case 9 ... 11 : Hertz = 10; break; /* S/390 (sometimes) */ case 18 ... 22 : Hertz = 20; break; /* user-mode Linux */ case 30 ... 34 : Hertz = 32; break; /* ia64 emulator */ case 48 ... 52 : Hertz = 50; break; case 58 ... 61 : Hertz = 60; break; case 62 ... 65 : Hertz = 64; break; /* StrongARM /Shark */ case 95 ... 105 : Hertz = 100; break; /* normal Linux */ case 124 ... 132 : Hertz = 128; break; /* MIPS, ARM */ case 195 ... 204 : Hertz = 200; break; /* normal << 1 */ case 247 ... 252 : Hertz = 250; break; case 253 ... 260 : Hertz = 256; break; case 393 ... 408 : Hertz = 400; break; /* normal << 2 */ case 790 ... 808 : Hertz = 800; break; /* normal << 3 */ case 990 ... 1010 : Hertz = 1000; break; /* ARM */ case 1015 ... 1035 : Hertz = 1024; break; /* Alpha, ia64 */ case 1180 ... 1220 : Hertz = 1200; break; /* Alpha */ default: #ifdef HZ Hertz = (unsigned long long)HZ; /* */ #else /* If 32-bit or big-endian (not Alpha or ia64), assume HZ is 100. */ Hertz = (sizeof(long)==sizeof(int) || htons(999)==999) ? 100UL : 1024UL; #endif fprintf(stderr, "Unknown HZ value! (%d) Assume %Ld.\n", h, Hertz); } } // same as: euid != uid || egid != gid #ifndef AT_SECURE #define AT_SECURE 23 // secure mode boolean (true if setuid, etc.) #endif #ifndef AT_CLKTCK #define AT_CLKTCK 17 // frequency of times() #endif #define NOTE_NOT_FOUND 42 //extern char** environ; /* for ELF executables, notes are pushed before environment and args */ /* static unsigned long find_elf_note(unsigned long findme){ unsigned long *ep = (unsigned long *)environ; while(*ep++); while(*ep){ if(ep[0]==findme) return ep[1]; ep+=2; } return NOTE_NOT_FOUND; } */ int have_privs; /* static int check_for_privs(void){ unsigned long rc = find_elf_note(AT_SECURE); if(rc==NOTE_NOT_FOUND){ // not valid to run this code after UID or GID change! // (if needed, may use AT_UID and friends instead) rc = geteuid() != getuid() || getegid() != getgid(); } return !!rc; } */ static void init_libproc(void) __attribute__((constructor)); static void init_libproc(void){ //have_privs = check_for_privs(); init_Linux_version(); /* Must be called before we check code */ // ought to count CPUs in /proc/stat instead of relying // on glibc, which foolishly tries to parse /proc/cpuinfo // // SourceForge has an old Alpha running Linux 2.2.20 that // appears to have a non-SMP kernel on a 2-way SMP box. // _SC_NPROCESSORS_CONF returns 2, resulting in HZ=512 // _SC_NPROCESSORS_ONLN returns 1, which should work OK smp_num_cpus = sysconf(_SC_NPROCESSORS_ONLN); if(smp_num_cpus<1) smp_num_cpus=1; /* SPARC glibc is buggy */ /* #ifdef __linux__ if(linux_version_code > LINUX_VERSION(2, 4, 0)){ Hertz = find_elf_note(AT_CLKTCK); if(Hertz!=NOTE_NOT_FOUND) return; fputs("2.4+ kernel w/o ELF notes? -- report this\n", stderr); } #endif */ #if defined(__FreeBSD_kernel__) || defined(__FreeBSD__) /* On FreeBSD the Hertz hack is unrelaible, there is no ELF note and * Hertz isn't defined in asm/params.h * See Debian Bug #460331 */ Hertz = 100; return; #endif // old_Hertz_hack(); } #if 0 /*********************************************************************** * The /proc filesystem calculates idle=jiffies-(user+nice+sys) and we * recover jiffies by adding up the 4 or 5 numbers we are given. SMP kernels * (as of pre-2.4 era) can report idle time going backwards, perhaps due * to non-atomic reads and updates. There is no locking for these values. */ #ifndef NAN #define NAN (-0.0) #endif #define JT unsigned long long void eight_cpu_numbers(double *restrict uret, double *restrict nret, double *restrict sret, double *restrict iret, double *restrict wret, double *restrict xret, double *restrict yret, double *restrict zret){ double tmp_u, tmp_n, tmp_s, tmp_i, tmp_w, tmp_x, tmp_y, tmp_z; double scale; /* scale values to % */ static JT old_u, old_n, old_s, old_i, old_w, old_x, old_y, old_z; JT new_u, new_n, new_s, new_i, new_w, new_x, new_y, new_z; JT ticks_past; /* avoid div-by-0 by not calling too often :-( */ tmp_w = 0.0; new_w = 0; tmp_x = 0.0; new_x = 0; tmp_y = 0.0; new_y = 0; tmp_z = 0.0; new_z = 0; FILE_TO_BUF(STAT_FILE,stat_fd); sscanf(buf, "cpu %Lu %Lu %Lu %Lu %Lu %Lu %Lu %Lu", &new_u, &new_n, &new_s, &new_i, &new_w, &new_x, &new_y, &new_z); ticks_past = (new_u+new_n+new_s+new_i+new_w+new_x+new_y+new_z)-(old_u+old_n+old_s+old_i+old_w+old_x+old_y+old_z); if(ticks_past){ scale = 100.0 / (double)ticks_past; tmp_u = ( (double)new_u - (double)old_u ) * scale; tmp_n = ( (double)new_n - (double)old_n ) * scale; tmp_s = ( (double)new_s - (double)old_s ) * scale; tmp_i = ( (double)new_i - (double)old_i ) * scale; tmp_w = ( (double)new_w - (double)old_w ) * scale; tmp_x = ( (double)new_x - (double)old_x ) * scale; tmp_y = ( (double)new_y - (double)old_y ) * scale; tmp_z = ( (double)new_z - (double)old_z ) * scale; }else{ tmp_u = NAN; tmp_n = NAN; tmp_s = NAN; tmp_i = NAN; tmp_w = NAN; tmp_x = NAN; tmp_y = NAN; tmp_z = NAN; } SET_IF_DESIRED(uret, tmp_u); SET_IF_DESIRED(nret, tmp_n); SET_IF_DESIRED(sret, tmp_s); SET_IF_DESIRED(iret, tmp_i); SET_IF_DESIRED(wret, tmp_w); SET_IF_DESIRED(xret, tmp_x); SET_IF_DESIRED(yret, tmp_y); SET_IF_DESIRED(zret, tmp_z); old_u=new_u; old_n=new_n; old_s=new_s; old_i=new_i; old_w=new_w; old_x=new_x; old_y=new_y; old_z=new_z; } #undef JT #endif /***********************************************************************/ void loadavg(double *restrict av1, double *restrict av5, double *restrict av15) { double avg_1=0, avg_5=0, avg_15=0; char *restrict savelocale; FILE_TO_BUF(LOADAVG_FILE,loadavg_fd); savelocale = setlocale(LC_NUMERIC, NULL); setlocale(LC_NUMERIC, "C"); if (sscanf(buf, "%lf %lf %lf", &avg_1, &avg_5, &avg_15) < 3) { fputs("bad data in " LOADAVG_FILE "\n", stderr); exit(1); } setlocale(LC_NUMERIC, savelocale); SET_IF_DESIRED(av1, avg_1); SET_IF_DESIRED(av5, avg_5); SET_IF_DESIRED(av15, avg_15); } static char buff[BUFFSIZE]; /* used in the procedures */ /***********************************************************************/ static void crash(const char *filename) { perror(filename); exit(EXIT_FAILURE); } /***********************************************************************/ static void getrunners(unsigned int *restrict running, unsigned int *restrict blocked) { struct direct *ent; DIR *proc; *running=0; *blocked=0; if((proc=opendir("/proc"))==NULL) crash("/proc"); while(( ent=readdir(proc) )) { char tbuf[32]; char *cp; int fd; char c; if (!isdigit(ent->d_name[0])) continue; sprintf(tbuf, "/proc/%s/stat", ent->d_name); fd = open(tbuf, O_RDONLY, 0); if (fd == -1) continue; memset(tbuf, '\0', sizeof tbuf); // didn't feel like checking read() read(fd, tbuf, sizeof tbuf - 1); // need 32 byte buffer at most close(fd); cp = strrchr(tbuf, ')'); if(!cp) continue; c = cp[2]; if (c=='R') { (*running)++; continue; } if (c=='D') { (*blocked)++; continue; } } closedir(proc); } /***********************************************************************/ void getstat(jiff *restrict cuse, jiff *restrict cice, jiff *restrict csys, jiff *restrict cide, jiff *restrict ciow, jiff *restrict cxxx, jiff *restrict cyyy, jiff *restrict czzz, unsigned long *restrict pin, unsigned long *restrict pout, unsigned long *restrict s_in, unsigned long *restrict sout, unsigned *restrict intr, unsigned *restrict ctxt, unsigned int *restrict running, unsigned int *restrict blocked, unsigned int *restrict btime, unsigned int *restrict processes) { static int fd; unsigned long long llbuf = 0; int need_vmstat_file = 0; int need_proc_scan = 0; const char* b; buff[BUFFSIZE-1] = 0; /* ensure null termination in buffer */ if(fd){ lseek(fd, 0L, SEEK_SET); }else{ fd = open("/proc/stat", O_RDONLY, 0); if(fd == -1) crash("/proc/stat"); } read(fd,buff,BUFFSIZE-1); *intr = 0; *ciow = 0; /* not separated out until the 2.5.41 kernel */ *cxxx = 0; /* not separated out until the 2.6.0-test4 kernel */ *cyyy = 0; /* not separated out until the 2.6.0-test4 kernel */ *czzz = 0; /* not separated out until the 2.6.11 kernel */ b = strstr(buff, "cpu "); if(b) sscanf(b, "cpu %Lu %Lu %Lu %Lu %Lu %Lu %Lu %Lu", cuse, cice, csys, cide, ciow, cxxx, cyyy, czzz); b = strstr(buff, "page "); if(b) sscanf(b, "page %lu %lu", pin, pout); else need_vmstat_file = 1; b = strstr(buff, "swap "); if(b) sscanf(b, "swap %lu %lu", s_in, sout); else need_vmstat_file = 1; b = strstr(buff, "intr "); if(b) sscanf(b, "intr %Lu", &llbuf); *intr = llbuf; b = strstr(buff, "ctxt "); if(b) sscanf(b, "ctxt %Lu", &llbuf); *ctxt = llbuf; b = strstr(buff, "btime "); if(b) sscanf(b, "btime %u", btime); b = strstr(buff, "processes "); if(b) sscanf(b, "processes %u", processes); b = strstr(buff, "procs_running "); if(b) sscanf(b, "procs_running %u", running); else need_proc_scan = 1; b = strstr(buff, "procs_blocked "); if(b) sscanf(b, "procs_blocked %u", blocked); else need_proc_scan = 1; if(need_proc_scan){ /* Linux 2.5.46 (approximately) and below */ getrunners(running, blocked); } (*running)--; // exclude vmstat itself if(need_vmstat_file){ /* Linux 2.5.40-bk4 and above */ vminfo(); *pin = vm_pgpgin; *pout = vm_pgpgout; *s_in = vm_pswpin; *sout = vm_pswpout; } } /***********************************************************************/ /* * Copyright 1999 by Albert Cahalan; all rights reserved. * This file may be used subject to the terms and conditions of the * GNU Library General Public License Version 2, or any later version * at your option, as published by the Free Software Foundation. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. */ typedef struct mem_table_struct { const char *name; /* memory type name */ unsigned long *slot; /* slot in return struct */ } mem_table_struct; static int compare_mem_table_structs(const void *a, const void *b){ return strcmp(((const mem_table_struct*)a)->name,((const mem_table_struct*)b)->name); } /* example data, following junk, with comments added: * * MemTotal: 61768 kB old * MemFree: 1436 kB old * MemShared: 0 kB old (now always zero; not calculated) * Buffers: 1312 kB old * Cached: 20932 kB old * Active: 12464 kB new * Inact_dirty: 7772 kB new * Inact_clean: 2008 kB new * Inact_target: 0 kB new * Inact_laundry: 0 kB new, and might be missing too * HighTotal: 0 kB * HighFree: 0 kB * LowTotal: 61768 kB * LowFree: 1436 kB * SwapTotal: 122580 kB old * SwapFree: 60352 kB old * Inactive: 20420 kB 2.5.41+ * Dirty: 0 kB 2.5.41+ * Writeback: 0 kB 2.5.41+ * Mapped: 9792 kB 2.5.41+ * Slab: 4564 kB 2.5.41+ * Committed_AS: 8440 kB 2.5.41+ * PageTables: 304 kB 2.5.41+ * ReverseMaps: 5738 2.5.41+ * SwapCached: 0 kB 2.5.??+ * HugePages_Total: 220 2.5.??+ * HugePages_Free: 138 2.5.??+ * Hugepagesize: 4096 kB 2.5.??+ */ /* obsolete */ unsigned long kb_main_shared; /* old but still kicking -- the important stuff */ unsigned long kb_main_buffers; unsigned long kb_main_cached; unsigned long kb_main_free; unsigned long kb_main_total; unsigned long kb_swap_free; unsigned long kb_swap_total; /* recently introduced */ unsigned long kb_high_free; unsigned long kb_high_total; unsigned long kb_low_free; unsigned long kb_low_total; /* 2.4.xx era */ unsigned long kb_active; unsigned long kb_inact_laundry; unsigned long kb_inact_dirty; unsigned long kb_inact_clean; unsigned long kb_inact_target; unsigned long kb_swap_cached; /* late 2.4 and 2.6+ only */ /* derived values */ unsigned long kb_swap_used; unsigned long kb_main_used; /* 2.5.41+ */ unsigned long kb_writeback; unsigned long kb_slab; unsigned long nr_reversemaps; unsigned long kb_committed_as; unsigned long kb_dirty; unsigned long kb_inactive; unsigned long kb_mapped; unsigned long kb_pagetables; // seen on a 2.6.x kernel: static unsigned long kb_vmalloc_chunk; static unsigned long kb_vmalloc_total; static unsigned long kb_vmalloc_used; // seen on 2.6.24-rc6-git12 static unsigned long kb_anon_pages; static unsigned long kb_bounce; static unsigned long kb_commit_limit; static unsigned long kb_nfs_unstable; static unsigned long kb_swap_reclaimable; static unsigned long kb_swap_unreclaimable; void meminfo(void){ char namebuf[16]; /* big enough to hold any row name */ mem_table_struct findme = { namebuf, NULL}; mem_table_struct *found; char *head; char *tail; static const mem_table_struct mem_table[] = { {"Active", &kb_active}, // important {"AnonPages", &kb_anon_pages}, {"Bounce", &kb_bounce}, {"Buffers", &kb_main_buffers}, // important {"Cached", &kb_main_cached}, // important {"CommitLimit", &kb_commit_limit}, {"Committed_AS", &kb_committed_as}, {"Dirty", &kb_dirty}, // kB version of vmstat nr_dirty {"HighFree", &kb_high_free}, {"HighTotal", &kb_high_total}, {"Inact_clean", &kb_inact_clean}, {"Inact_dirty", &kb_inact_dirty}, {"Inact_laundry",&kb_inact_laundry}, {"Inact_target", &kb_inact_target}, {"Inactive", &kb_inactive}, // important {"LowFree", &kb_low_free}, {"LowTotal", &kb_low_total}, {"Mapped", &kb_mapped}, // kB version of vmstat nr_mapped {"MemFree", &kb_main_free}, // important {"MemShared", &kb_main_shared}, // important, but now gone! {"MemTotal", &kb_main_total}, // important {"NFS_Unstable", &kb_nfs_unstable}, {"PageTables", &kb_pagetables}, // kB version of vmstat nr_page_table_pages {"ReverseMaps", &nr_reversemaps}, // same as vmstat nr_page_table_pages {"SReclaimable", &kb_swap_reclaimable}, // "swap reclaimable" (dentry and inode structures) {"SUnreclaim", &kb_swap_unreclaimable}, {"Slab", &kb_slab}, // kB version of vmstat nr_slab {"SwapCached", &kb_swap_cached}, {"SwapFree", &kb_swap_free}, // important {"SwapTotal", &kb_swap_total}, // important {"VmallocChunk", &kb_vmalloc_chunk}, {"VmallocTotal", &kb_vmalloc_total}, {"VmallocUsed", &kb_vmalloc_used}, {"Writeback", &kb_writeback}, // kB version of vmstat nr_writeback }; const int mem_table_count = sizeof(mem_table)/sizeof(mem_table_struct); FILE_TO_BUF(MEMINFO_FILE,meminfo_fd); kb_inactive = ~0UL; head = buf; for(;;){ tail = strchr(head, ':'); if(!tail) break; *tail = '\0'; if(strlen(head) >= sizeof(namebuf)){ head = tail+1; goto nextline; } strcpy(namebuf,head); found = bsearch(&findme, mem_table, mem_table_count, sizeof(mem_table_struct), compare_mem_table_structs ); head = tail+1; if(!found) goto nextline; *(found->slot) = (unsigned long)strtoull(head,&tail,10); nextline: tail = strchr(head, '\n'); if(!tail) break; head = tail+1; } if(!kb_low_total){ /* low==main except with large-memory support */ kb_low_total = kb_main_total; kb_low_free = kb_main_free; } if(kb_inactive==~0UL){ kb_inactive = kb_inact_dirty + kb_inact_clean + kb_inact_laundry; } kb_swap_used = kb_swap_total - kb_swap_free; kb_main_used = kb_main_total - kb_main_free; } /*****************************************************************/ /* read /proc/vminfo only for 2.5.41 and above */ typedef struct vm_table_struct { const char *name; /* VM statistic name */ unsigned long *slot; /* slot in return struct */ } vm_table_struct; static int compare_vm_table_structs(const void *a, const void *b){ return strcmp(((const vm_table_struct*)a)->name,((const vm_table_struct*)b)->name); } // see include/linux/page-flags.h and mm/page_alloc.c unsigned long vm_nr_dirty; // dirty writable pages unsigned long vm_nr_writeback; // pages under writeback unsigned long vm_nr_pagecache; // pages in pagecache -- gone in 2.5.66+ kernels unsigned long vm_nr_page_table_pages;// pages used for pagetables unsigned long vm_nr_reverse_maps; // includes PageDirect unsigned long vm_nr_mapped; // mapped into pagetables unsigned long vm_nr_slab; // in slab unsigned long vm_pgpgin; // kB disk reads (same as 1st num on /proc/stat page line) unsigned long vm_pgpgout; // kB disk writes (same as 2nd num on /proc/stat page line) unsigned long vm_pswpin; // swap reads (same as 1st num on /proc/stat swap line) unsigned long vm_pswpout; // swap writes (same as 2nd num on /proc/stat swap line) unsigned long vm_pgalloc; // page allocations unsigned long vm_pgfree; // page freeings unsigned long vm_pgactivate; // pages moved inactive -> active unsigned long vm_pgdeactivate; // pages moved active -> inactive unsigned long vm_pgfault; // total faults (major+minor) unsigned long vm_pgmajfault; // major faults unsigned long vm_pgscan; // pages scanned by page reclaim unsigned long vm_pgrefill; // inspected by refill_inactive_zone unsigned long vm_pgsteal; // total pages reclaimed unsigned long vm_kswapd_steal; // pages reclaimed by kswapd // next 3 as defined by the 2.5.52 kernel unsigned long vm_pageoutrun; // times kswapd ran page reclaim unsigned long vm_allocstall; // times a page allocator ran direct reclaim unsigned long vm_pgrotated; // pages rotated to the tail of the LRU for immediate reclaim // seen on a 2.6.8-rc1 kernel, apparently replacing old fields static unsigned long vm_pgalloc_dma; // static unsigned long vm_pgalloc_high; // static unsigned long vm_pgalloc_normal; // static unsigned long vm_pgrefill_dma; // static unsigned long vm_pgrefill_high; // static unsigned long vm_pgrefill_normal; // static unsigned long vm_pgscan_direct_dma; // static unsigned long vm_pgscan_direct_high; // static unsigned long vm_pgscan_direct_normal; // static unsigned long vm_pgscan_kswapd_dma; // static unsigned long vm_pgscan_kswapd_high; // static unsigned long vm_pgscan_kswapd_normal; // static unsigned long vm_pgsteal_dma; // static unsigned long vm_pgsteal_high; // static unsigned long vm_pgsteal_normal; // // seen on a 2.6.8-rc1 kernel static unsigned long vm_kswapd_inodesteal; // static unsigned long vm_nr_unstable; // static unsigned long vm_pginodesteal; // static unsigned long vm_slabs_scanned; // void vminfo(void){ char namebuf[16]; /* big enough to hold any row name */ vm_table_struct findme = { namebuf, NULL}; vm_table_struct *found; char *head; char *tail; static const vm_table_struct vm_table[] = { {"allocstall", &vm_allocstall}, {"kswapd_inodesteal", &vm_kswapd_inodesteal}, {"kswapd_steal", &vm_kswapd_steal}, {"nr_dirty", &vm_nr_dirty}, // page version of meminfo Dirty {"nr_mapped", &vm_nr_mapped}, // page version of meminfo Mapped {"nr_page_table_pages", &vm_nr_page_table_pages},// same as meminfo PageTables {"nr_pagecache", &vm_nr_pagecache}, // gone in 2.5.66+ kernels {"nr_reverse_maps", &vm_nr_reverse_maps}, // page version of meminfo ReverseMaps GONE {"nr_slab", &vm_nr_slab}, // page version of meminfo Slab {"nr_unstable", &vm_nr_unstable}, {"nr_writeback", &vm_nr_writeback}, // page version of meminfo Writeback {"pageoutrun", &vm_pageoutrun}, {"pgactivate", &vm_pgactivate}, {"pgalloc", &vm_pgalloc}, // GONE (now separate dma,high,normal) {"pgalloc_dma", &vm_pgalloc_dma}, {"pgalloc_high", &vm_pgalloc_high}, {"pgalloc_normal", &vm_pgalloc_normal}, {"pgdeactivate", &vm_pgdeactivate}, {"pgfault", &vm_pgfault}, {"pgfree", &vm_pgfree}, {"pginodesteal", &vm_pginodesteal}, {"pgmajfault", &vm_pgmajfault}, {"pgpgin", &vm_pgpgin}, // important {"pgpgout", &vm_pgpgout}, // important {"pgrefill", &vm_pgrefill}, // GONE (now separate dma,high,normal) {"pgrefill_dma", &vm_pgrefill_dma}, {"pgrefill_high", &vm_pgrefill_high}, {"pgrefill_normal", &vm_pgrefill_normal}, {"pgrotated", &vm_pgrotated}, {"pgscan", &vm_pgscan}, // GONE (now separate direct,kswapd and dma,high,normal) {"pgscan_direct_dma", &vm_pgscan_direct_dma}, {"pgscan_direct_high", &vm_pgscan_direct_high}, {"pgscan_direct_normal",&vm_pgscan_direct_normal}, {"pgscan_kswapd_dma", &vm_pgscan_kswapd_dma}, {"pgscan_kswapd_high", &vm_pgscan_kswapd_high}, {"pgscan_kswapd_normal",&vm_pgscan_kswapd_normal}, {"pgsteal", &vm_pgsteal}, // GONE (now separate dma,high,normal) {"pgsteal_dma", &vm_pgsteal_dma}, {"pgsteal_high", &vm_pgsteal_high}, {"pgsteal_normal", &vm_pgsteal_normal}, {"pswpin", &vm_pswpin}, // important {"pswpout", &vm_pswpout}, // important {"slabs_scanned", &vm_slabs_scanned}, }; const int vm_table_count = sizeof(vm_table)/sizeof(vm_table_struct); vm_pgalloc = 0; vm_pgrefill = 0; vm_pgscan = 0; vm_pgsteal = 0; FILE_TO_BUF(VMINFO_FILE,vminfo_fd); head = buf; for(;;){ tail = strchr(head, ' '); if(!tail) break; *tail = '\0'; if(strlen(head) >= sizeof(namebuf)){ head = tail+1; goto nextline; } strcpy(namebuf,head); found = bsearch(&findme, vm_table, vm_table_count, sizeof(vm_table_struct), compare_vm_table_structs ); head = tail+1; if(!found) goto nextline; *(found->slot) = strtoul(head,&tail,10); nextline: //if(found) fprintf(stderr,"%s=%d\n",found->name,*(found->slot)); //else fprintf(stderr,"%s not found\n",findme.name); tail = strchr(head, '\n'); if(!tail) break; head = tail+1; } if(!vm_pgalloc) vm_pgalloc = vm_pgalloc_dma + vm_pgalloc_high + vm_pgalloc_normal; if(!vm_pgrefill) vm_pgrefill = vm_pgrefill_dma + vm_pgrefill_high + vm_pgrefill_normal; if(!vm_pgscan) vm_pgscan = vm_pgscan_direct_dma + vm_pgscan_direct_high + vm_pgscan_direct_normal + vm_pgscan_kswapd_dma + vm_pgscan_kswapd_high + vm_pgscan_kswapd_normal; if(!vm_pgsteal) vm_pgsteal = vm_pgsteal_dma + vm_pgsteal_high + vm_pgsteal_normal; } /////////////////////////////////////////////////////////////////////// // based on Fabian Frederick's /proc/diskstats parser unsigned int getpartitions_num(struct disk_stat *disks, int ndisks){ int i=0; int partitions=0; for (i=0;i #include #include "procps.h" EXTERN_C_BEGIN extern unsigned long long Hertz; /* clock tick frequency */ extern long smp_num_cpus; /* number of CPUs */ extern int have_privs; /* boolean, true if setuid or similar */ #if 0 #define JT double extern void eight_cpu_numbers(JT *uret, JT *nret, JT *sret, JT *iret, JT *wret, JT *xret, JT *yret, JT *zret); #undef JT #endif extern int uptime (double *uptime_secs, double *idle_secs); extern void loadavg(double *av1, double *av5, double *av15); /* obsolete */ extern unsigned long kb_main_shared; /* old but still kicking -- the important stuff */ extern unsigned long kb_main_buffers; extern unsigned long kb_main_cached; extern unsigned long kb_main_free; extern unsigned long kb_main_total; extern unsigned long kb_swap_free; extern unsigned long kb_swap_total; /* recently introduced */ extern unsigned long kb_high_free; extern unsigned long kb_high_total; extern unsigned long kb_low_free; extern unsigned long kb_low_total; /* 2.4.xx era */ extern unsigned long kb_active; extern unsigned long kb_inact_laundry; // grrr... extern unsigned long kb_inact_dirty; extern unsigned long kb_inact_clean; extern unsigned long kb_inact_target; extern unsigned long kb_swap_cached; /* late 2.4+ */ /* derived values */ extern unsigned long kb_swap_used; extern unsigned long kb_main_used; /* 2.5.41+ */ extern unsigned long kb_writeback; extern unsigned long kb_slab; extern unsigned long nr_reversemaps; extern unsigned long kb_committed_as; extern unsigned long kb_dirty; extern unsigned long kb_inactive; extern unsigned long kb_mapped; extern unsigned long kb_pagetables; #define BUFFSIZE (64*1024) typedef unsigned long long jiff; extern void getstat(jiff *restrict cuse, jiff *restrict cice, jiff *restrict csys, jiff *restrict cide, jiff *restrict ciow, jiff *restrict cxxx, jiff *restrict cyyy, jiff *restrict czzz, unsigned long *restrict pin, unsigned long *restrict pout, unsigned long *restrict s_in, unsigned long *restrict sout, unsigned *restrict intr, unsigned *restrict ctxt, unsigned int *restrict running, unsigned int *restrict blocked, unsigned int *restrict btime, unsigned int *restrict processes); extern void meminfo(void); extern unsigned long vm_nr_dirty; extern unsigned long vm_nr_writeback; extern unsigned long vm_nr_pagecache; extern unsigned long vm_nr_page_table_pages; extern unsigned long vm_nr_reverse_maps; extern unsigned long vm_nr_mapped; extern unsigned long vm_nr_slab; extern unsigned long vm_pgpgin; extern unsigned long vm_pgpgout; extern unsigned long vm_pswpin; extern unsigned long vm_pswpout; extern unsigned long vm_pgalloc; extern unsigned long vm_pgfree; extern unsigned long vm_pgactivate; extern unsigned long vm_pgdeactivate; extern unsigned long vm_pgfault; extern unsigned long vm_pgmajfault; extern unsigned long vm_pgscan; extern unsigned long vm_pgrefill; extern unsigned long vm_pgsteal; extern unsigned long vm_kswapd_steal; extern unsigned long vm_pageoutrun; extern unsigned long vm_allocstall; extern void vminfo(void); typedef struct disk_stat{ unsigned long long reads_sectors; unsigned long long written_sectors; char disk_name [16]; unsigned inprogress_IO; unsigned merged_reads; unsigned merged_writes; unsigned milli_reading; unsigned milli_spent_IO; unsigned milli_writing; unsigned partitions; unsigned reads; unsigned weighted_milli_spent_IO; unsigned writes; }disk_stat; typedef struct partition_stat{ char partition_name [16]; unsigned long long reads_sectors; unsigned parent_disk; // index into a struct disk_stat array unsigned reads; unsigned writes; unsigned long long requested_writes; }partition_stat; extern unsigned int getpartitions_num(struct disk_stat *disks, int ndisks); extern unsigned int getdiskstat (struct disk_stat**,struct partition_stat**); typedef struct slab_cache{ char name[48]; unsigned active_objs; unsigned num_objs; unsigned objsize; unsigned objperslab; }slab_cache; extern unsigned int getslabinfo (struct slab_cache**); extern unsigned get_pid_digits(void) FUNCTION; EXTERN_C_END #endif /* SYSINFO_H */ poelzi-ulatencyd-55515a9/src/proc/version.c000066400000000000000000000032421154664534000206560ustar00rootroot00000000000000/* Suite version information for procps utilities * Copyright (c) 1995 Martin Schulze * Ammended by cblake to only export the function symbol. * * Modified by Albert Cahalan, ????-2003 * * Redistributable under the terms of the * GNU Library General Public License; see COPYING */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "version.h" #ifdef MINORVERSION const char procps_version[] = "procps version " VERSION "." SUBVERSION "." MINORVERSION; #else const char procps_version[] = "procps version " VERSION "." SUBVERSION; #endif void display_version(void) { fprintf(stdout, "%s\n", procps_version); } /* Linux kernel version information for procps utilities * Copyright (c) 1996 Charles Blake */ #include #define LINUX_VERSION(x,y,z) (0x10000*(x) + 0x100*(y) + z) int linux_version_code; void init_Linux_version(void) { int x = 0, y = 0, z = 0; /* cleared in case sscanf() < 3 */ FILE *fp; char buf[256]; if ( (fp=fopen("/proc/version","r")) == NULL) { fprintf(stderr, "Cannot find /proc/version - is /proc mounted?\n"); exit(1); } if (fgets(buf, 256, fp) == NULL) { fprintf(stderr, "Cannot read kernel version from /proc/version\n"); fclose(fp); exit(1); } fclose(fp); if (sscanf(buf, "Linux version %d.%d.%d", &x, &y, &z) < 3) fprintf(stderr, /* *very* unlikely to happen by accident */ "Non-standard uts for running kernel:\n" "release %s=%d.%d.%d gives version code %d\n", buf, x, y, z, LINUX_VERSION(x,y,z)); linux_version_code = LINUX_VERSION(x, y, z); } poelzi-ulatencyd-55515a9/src/proc/version.h000066400000000000000000000020341154664534000206610ustar00rootroot00000000000000#ifndef PROC_VERSION_H #define PROC_VERSION_H #include "procps.h" /* Suite version information for procps utilities * Copyright (c) 1995 Martin Schulze * Linux kernel version information for procps utilities * Copyright (c) 1996 Charles Blake * Distributable under the terms of the GNU Library General Public License * * Copyright 2002 Albert Cahalan */ EXTERN_C_BEGIN void init_Linux_version(void); /* Get Linux version */ extern void display_version(void); /* display suite version */ extern const char procps_version[]; /* global buf for suite version */ extern int linux_version_code; /* runtime version of LINUX_VERSION_CODE in /usr/include/linux/version.h */ /* Convenience macros for composing/decomposing version codes */ #define LINUX_VERSION(x,y,z) (0x10000*(x) + 0x100*(y) + z) #define LINUX_VERSION_MAJOR(x) (((x)>>16) & 0xFF) #define LINUX_VERSION_MINOR(x) (((x)>> 8) & 0xFF) #define LINUX_VERSION_PATCH(x) ( (x) & 0xFF) EXTERN_C_END #endif /* PROC_VERSION_H */ poelzi-ulatencyd-55515a9/src/proc/wchan.h000066400000000000000000000006421154664534000202770ustar00rootroot00000000000000#ifndef PROCPS_PROC_WCHAN_H #define PROCPS_PROC_WCHAN_H #include "procps.h" EXTERN_C_BEGIN typedef void (*message_fn)(const char *restrict, ...) __attribute__((format(printf,1,2))); extern const char * lookup_wchan(unsigned KLONG address, unsigned pid); extern int open_psdb(const char *restrict override); extern int open_psdb_message(const char *restrict override, message_fn message); EXTERN_C_END #endif poelzi-ulatencyd-55515a9/src/proc/whattime.c000066400000000000000000000042701154664534000210150ustar00rootroot00000000000000/* This is a trivial uptime program. I hereby release this program * into the public domain. I disclaim any responsibility for this * program --- use it at your own risk. (as if there were any.. ;-) * -michaelkjohnson (johnsonm@sunsite.unc.edu) * * Modified by Larry Greenfield to give a more traditional output, * count users, etc. (greenfie@gauss.rutgers.edu) * * Modified by mkj again to fix a few tiny buglies. * * Modified by J. Cowley to add printing the uptime message to a * string (for top) and to optimize file handling. 19 Mar 1993. * */ #include #include #include #include #include #include #include #include #include "whattime.h" #include "sysinfo.h" static char buf[128]; static double av[3]; char *sprint_uptime(void) { struct utmp *utmpstruct; int upminutes, uphours, updays; int pos; struct tm *realtime; time_t realseconds; int numuser; double uptime_secs, idle_secs; /* first get the current time */ time(&realseconds); realtime = localtime(&realseconds); pos = sprintf(buf, " %02d:%02d:%02d ", realtime->tm_hour, realtime->tm_min, realtime->tm_sec); /* read and calculate the amount of uptime */ uptime(&uptime_secs, &idle_secs); updays = (int) uptime_secs / (60*60*24); strcat (buf, "up "); pos += 3; if (updays) pos += sprintf(buf + pos, "%d day%s, ", updays, (updays != 1) ? "s" : ""); upminutes = (int) uptime_secs / 60; uphours = upminutes / 60; uphours = uphours % 24; upminutes = upminutes % 60; if(uphours) pos += sprintf(buf + pos, "%2d:%02d, ", uphours, upminutes); else pos += sprintf(buf + pos, "%d min, ", upminutes); /* count the number of users */ numuser = 0; setutent(); while ((utmpstruct = getutent())) { if ((utmpstruct->ut_type == USER_PROCESS) && (utmpstruct->ut_name[0] != '\0')) numuser++; } endutent(); pos += sprintf(buf + pos, "%2d user%s, ", numuser, numuser == 1 ? "" : "s"); loadavg(&av[0], &av[1], &av[2]); pos += sprintf(buf + pos, " load average: %.2f, %.2f, %.2f", av[0], av[1], av[2]); return buf; } void print_uptime(void) { printf("%s\n", sprint_uptime()); } poelzi-ulatencyd-55515a9/src/proc/whattime.h000066400000000000000000000002561154664534000210220ustar00rootroot00000000000000#ifndef PROC_WHATTIME_H #define PROC_WHATTIME_H #include "procps.h" EXTERN_C_BEGIN extern void print_uptime(void); extern char *sprint_uptime(void); EXTERN_C_END #endif poelzi-ulatencyd-55515a9/src/sysctl.c000066400000000000000000000073711154664534000175560ustar00rootroot00000000000000/* Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. */ #include "config.h" #include "ulatency.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nls.h" #ifndef __USE_GNU #define __USE_GNU #endif #include #ifndef OOM_SCORE_ADJ_MIN #define OOM_SCORE_ADJ_MIN (-1000) #define OOM_SCORE_ADJ_MAX 1000 #endif const char *to_prio[] = { "none", "realtime", "best-effort", "idle", }; // IO PRIO stuff static inline int ioprio_set(int which, int who, int ioprio) { return syscall(__NR_ioprio_set, which, who, ioprio); } static inline int ioprio_get(int which, int who) { return syscall(__NR_ioprio_get, which, who); } enum { IOPRIO_WHO_PROCESS = 1, IOPRIO_WHO_PGRP, IOPRIO_WHO_USER, }; #define IOPRIO_CLASS_SHIFT 13 static void ioprio_print(int pid) { int ioprio, ioclass; ioprio = ioprio_get(IOPRIO_WHO_PROCESS, pid); if (ioprio == -1) err(EXIT_FAILURE, _("ioprio_get failed")); else { ioclass = ioprio >> IOPRIO_CLASS_SHIFT; if (ioclass != IOPRIO_CLASS_IDLE) { ioprio = ioprio & 0xff; printf("%s: prio %d\n", to_prio[ioclass], ioprio); } else printf("%s\n", to_prio[ioclass]); } } int ioprio_getpid(pid_t pid, int *ioprio, int *ioclass) { int rv = ioprio_get(IOPRIO_WHO_PROCESS, pid); if (rv == -1) return -1; *ioclass = (rv >> IOPRIO_CLASS_SHIFT); *ioprio = (rv & 0xff); return 0; } int ioprio_setpid(pid_t pid, int ioprio, int ioclass) { int rc = ioprio_set(IOPRIO_WHO_PROCESS, pid, ioprio | ioclass << IOPRIO_CLASS_SHIFT); return rc; } // renice interface int renice_pid(int pid, int prio) { int oldprio; errno = 0; oldprio = getpriority(PRIO_PROCESS, pid); if (oldprio == -1 && errno) return -1; if(oldprio == prio) return 0; if (setpriority(PRIO_PROCESS, pid, prio) < 0) return -1; return 0; } // oom adjusting int adj_oom_killer(pid_t pid, int adj) { int oomfd, val; char aval[6]; char *path; val = MAX(OOM_SCORE_ADJ_MIN, MIN(adj, OOM_SCORE_ADJ_MAX)); g_snprintf(&aval[0], 6, "%d", val); path = g_strdup_printf("/proc/%d/oom_score_adj", pid); oomfd = open(path, O_NOFOLLOW | O_WRONLY); if (oomfd >= 0) { if(write(oomfd, &aval, strlen(&aval[0])) < 1) {} // stupid warning :-) close(oomfd); free(path); return 0; } free(path); return -1; } int get_oom_killer(pid_t pid) { char *contents, *path; gsize length; GError *error = NULL; int rv, res; path = g_strdup_printf ("/proc/%u/oom_score_adj", (guint)pid); res = g_file_get_contents (path, &contents, &length, &error); if (!res) { g_error_free (error); return 0; } if(!sscanf(contents, "%d", &rv)) rv = 0; g_free(contents); g_free(path); return rv; } poelzi-ulatencyd-55515a9/src/sysinfo.c000066400000000000000000000401261154664534000177220ustar00rootroot00000000000000/* Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. */ #include "config.h" #include "ulatency.h" #include #include GList *U_session_list; /* adapted from consolekit */ GHashTable * u_read_env_hash (pid_t pid) { char *path; gboolean res; char *contents; gsize length; GError *error; GHashTable *hash; int i; gboolean last_was_null; contents = NULL; hash = NULL; path = g_strdup_printf ("/proc/%u/environ", (guint)pid); error = NULL; res = g_file_get_contents (path, &contents, &length, &error); if (! res) { //g_debug("Couldn't read %s: %s", path, error->message); g_error_free (error); goto out; } hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); last_was_null = TRUE; for (i = 0; i < length; i++) { if (contents[i] == '\0') { last_was_null = TRUE; continue; } if (last_was_null) { char **vals; vals = g_strsplit (contents + i, "=", 2); if (vals != NULL) { g_hash_table_insert (hash, g_strdup (vals[0]), g_strdup (vals[1])); g_strfreev (vals); } } last_was_null = FALSE; } out: g_free (contents); g_free (path); return hash; } char * u_pid_get_env (pid_t pid, const char *var) { char *path; gboolean res; char *contents; char *val; gsize length; GError *error; int i; char *prefix; int prefix_len; gboolean last_was_null; val = NULL; contents = NULL; prefix = NULL; path = g_strdup_printf ("/proc/%u/environ", (guint)pid); error = NULL; res = g_file_get_contents (path, &contents, &length, &error); if (! res) { //g_debug ("Couldn't read %s: %s", path, error->message); g_error_free (error); goto out; } prefix = g_strdup_printf ("%s=", var); prefix_len = strlen(prefix); /* FIXME: make more robust */ last_was_null = TRUE; for (i = 0; i < length; i++) { if (contents[i] == '\0') { last_was_null = TRUE; continue; } if (last_was_null && g_str_has_prefix (contents + i, prefix)) { val = g_strdup (contents + i + prefix_len); break; } last_was_null = FALSE; } out: g_free (prefix); g_free (contents); g_free (path); return val; } GPtrArray * u_read_0file (pid_t pid, const char *what) { char *path; gboolean res; char *contents; gsize length; GError *error; GPtrArray *rv = NULL; int i; gboolean last_was_null; contents = NULL; path = g_strdup_printf ("/proc/%u/%s", (guint)pid, what); error = NULL; res = g_file_get_contents (path, &contents, &length, &error); if (! res) { //g_debug ("Couldn't read %s: %s", path, error->message); g_error_free (error); goto out; } rv = g_ptr_array_new_with_free_func(g_free); last_was_null = TRUE; for (i = 0; i < length; i++) { if (contents[i] == '\0') { last_was_null = TRUE; continue; } if (last_was_null) { g_ptr_array_add(rv, g_strdup(contents + i)); } last_was_null = FALSE; } out: g_free (contents); g_free (path); return rv; } GPtrArray* search_user_env(uid_t uid, const char *name, int update) { GPtrArray* rv = g_ptr_array_new_with_free_func(g_free); u_proc *proc = NULL; GHashTableIter iter; char *val; int i, found; gpointer ikey, value; g_hash_table_iter_init (&iter, processes); while (g_hash_table_iter_next (&iter, &ikey, &value)) { proc = (u_proc *)value; if(proc->proc.euid != uid) continue; u_proc_ensure(proc, ENVIRONMENT, update); if(!proc->environ) continue; val = g_hash_table_lookup(proc->environ, name); if(val) { found = FALSE; for(i = 0; i < rv->len; i++) { if(g_strcmp0((char *)g_ptr_array_index(rv, i), val) == 0) { found = TRUE; break; } } if(!found) g_ptr_array_add(rv, g_strdup(val)); } } return rv; } uint64_t get_number_of_processes() { uint64_t rv = 0; DIR *dip = opendir("/proc"); struct dirent *dit; if(!dip) return 0; while ((dit = readdir(dip)) != NULL) { if(!strcmp(dit->d_name, ".") || !strcmp(dit->d_name, "..")) continue; if(likely( likely(*dit->d_name > '0') && likely(*dit->d_name <= '9') )) { rv++; } } closedir(dip); return rv; } #ifdef ENABLE_DBUS // things would be so much easier here when consolekit would just emit a // SessionAdded/SessionRemoved in the manager, we so don't care about seats... static DBusGProxy *ck_manager_proxy = NULL; struct ck_seat { DBusGProxy *proxy; char *name; }; // list of current seats static GList *ck_seats = NULL; gint match_session(gconstpointer a, gconstpointer b) { const u_session *sa = a; return g_strcmp0(sa->name, (const char *)b); } // updates the idle hint of a session static void session_idle_hint_changed(DBusGProxy *proxy, gboolean hint, u_session *sess) { g_debug("CK: idle changed %s -> %d", sess->name, hint); sess->idle = hint; } static void session_active_changed(DBusGProxy *proxy, gboolean active, u_session *sess) { g_debug("CK: active changed %s -> %d", sess->name, active); sess->active = active; } static void ck_session_added(DBusGProxy *proxy, gchar *name, gpointer ignored) { GError *error = NULL; u_session *sess = g_malloc0(sizeof(u_session)); g_message("CK: Session added %s", name); sess->proxy = dbus_g_proxy_new_for_name_owner(U_dbus_connection_system, "org.freedesktop.ConsoleKit", name, "org.freedesktop.ConsoleKit.Session", &error); if(error) { g_warning ("CK Error: %s\n", error->message); g_free(name); g_free(sess); g_error_free(error); return; } sess->name = g_strdup(name); U_session_list = g_list_append(U_session_list, sess); // connect to signals dbus_g_proxy_add_signal (sess->proxy, "IdleHintChanged", G_TYPE_BOOLEAN, G_TYPE_INVALID); dbus_g_proxy_connect_signal(sess->proxy, "IdleHintChanged", G_CALLBACK(session_idle_hint_changed), sess, NULL); dbus_g_proxy_add_signal (sess->proxy, "ActiveChanged", G_TYPE_BOOLEAN, G_TYPE_INVALID); dbus_g_proxy_connect_signal(sess->proxy, "ActiveChanged", G_CALLBACK(session_active_changed), sess, NULL); if(!dbus_g_proxy_call (sess->proxy, "GetIdleHint", &error, G_TYPE_INVALID, G_TYPE_BOOLEAN, &sess->idle, G_TYPE_INVALID)) { g_warning ("CK Error: %s\n", error->message); g_error_free(error); error = NULL; } if(!dbus_g_proxy_call (sess->proxy, "IsActive", &error, G_TYPE_INVALID, G_TYPE_BOOLEAN, &sess->active, G_TYPE_INVALID)) { g_warning ("CK Error: %s\n", error->message); g_error_free(error); error = NULL; } if (!dbus_g_proxy_call (sess->proxy, "GetUnixUser", &error, G_TYPE_INVALID, G_TYPE_UINT, &sess->uid, G_TYPE_INVALID)) { g_warning ("CK Error: %s\n", error->message); g_error_free(error); error = NULL; } if (!dbus_g_proxy_call (sess->proxy, "GetX11Display", &error, G_TYPE_INVALID, G_TYPE_STRING, &sess->X11Display, G_TYPE_INVALID)) { g_warning ("CK Error: %s\n", error->message); g_error_free(error); error = NULL; } if (!dbus_g_proxy_call (sess->proxy, "GetX11Display", &error, G_TYPE_INVALID, G_TYPE_STRING, &sess->X11Device, G_TYPE_INVALID)) { g_warning ("CK Error: %s\n", error->message); g_error_free(error); error = NULL; } } static void ck_session_removed(DBusGProxy *proxy, gchar *name, gpointer ignored) { u_session *sess = NULL; GList *cur = U_session_list; g_message("CK: Session removed %s", name); while(cur) { sess = cur->data; if(g_strcmp0(name, sess->name) == 0) { g_object_unref(sess->proxy); g_free(sess->name); g_free(sess->X11Display); g_free(sess->X11Device); g_free(sess->dbus_session); U_session_list = g_list_remove(U_session_list, sess); break; } cur = g_list_next(cur); } } static void ck_seat_added(DBusGProxy *proxy, gchar *name, gpointer ignored) { GError *error = NULL; g_debug("CK: Seat added %s", name); struct ck_seat *seat = g_malloc0(sizeof(struct ck_seat)); seat->proxy = dbus_g_proxy_new_for_name_owner(U_dbus_connection_system, "org.freedesktop.ConsoleKit", name, "org.freedesktop.ConsoleKit.Seat", &error); if(error) { g_warning ("CK Error: %s\n", error->message); g_free(name); g_free(seat); g_error_free(error); return; } seat->name = name; ck_seats = g_list_append(ck_seats, seat); dbus_g_proxy_add_signal (seat->proxy, "SessionAdded", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID); dbus_g_proxy_connect_signal(seat->proxy, "SessionAdded", G_CALLBACK(ck_session_added), NULL, NULL); dbus_g_proxy_add_signal (seat->proxy, "SessionRemoved", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID); dbus_g_proxy_connect_signal(seat->proxy, "SessionRemoved", G_CALLBACK(ck_session_removed), NULL, NULL); } static void ck_seat_removed(DBusGProxy *proxy, gchar *name, gpointer ignored) { struct ck_seat *seat = NULL; g_debug("CK: Seat removed %s", name); GList *cur = ck_seats; while(cur) { seat = cur->data; if(g_strcmp0(name, seat->name) == 0) { g_object_unref(seat->proxy); g_free(seat->name); ck_seats = g_list_remove(ck_seats, seat); break; } cur = g_list_next(cur); } } void consolekit_init() { GPtrArray *array; GError *error = NULL; int i; if(!U_dbus_connection_system) return; // cleanup first. the dbus connection could be new struct ck_seat *seat = NULL; GList *cur = ck_seats; if(ck_manager_proxy) g_object_unref (ck_manager_proxy); while(cur) { seat = cur->data; g_object_unref(seat->proxy); g_free(seat->name); cur = g_list_next(cur); } g_list_free(ck_seats); while(U_session_list) { u_session *sess= U_session_list->data; ck_session_removed(NULL, sess->name, NULL); } ck_manager_proxy = dbus_g_proxy_new_for_name_owner(U_dbus_connection_system, "org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", &error); if(error) { g_warning ("CK Error: %s\n", error->message); g_error_free(error); return; } dbus_g_proxy_add_signal (ck_manager_proxy, "SeatAdded", G_TYPE_STRING, G_TYPE_INVALID); dbus_g_proxy_connect_signal(ck_manager_proxy, "SeatAdded", G_CALLBACK(ck_seat_added), NULL, NULL); dbus_g_proxy_add_signal (ck_manager_proxy, "SeatRemoved", G_TYPE_STRING, G_TYPE_INVALID); dbus_g_proxy_connect_signal(ck_manager_proxy, "SeatRemoved", G_CALLBACK(ck_seat_removed), NULL, NULL); if (!dbus_g_proxy_call (ck_manager_proxy, "GetSeats", &error, G_TYPE_INVALID, dbus_g_type_get_collection("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), &array, G_TYPE_INVALID)) { g_warning("CK Error: %s\n", error->message); g_error_free(error); } for (i = 0; i < array->len; i++) { error = NULL; ck_seat_added(NULL, g_ptr_array_index(array, i), NULL); } g_ptr_array_free(array, TRUE); if (!dbus_g_proxy_call (ck_manager_proxy, "GetSessions", &error, G_TYPE_INVALID, dbus_g_type_get_collection("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), &array, G_TYPE_INVALID)) { g_warning("CK Error: %s\n", error->message); g_error_free(error); } for (i = 0; i < array->len; i++) { error = NULL; ck_session_added(NULL, g_ptr_array_index(array, i), NULL); } g_ptr_array_free(array, TRUE); } #if 0 // systemd does not yet support setting properties void systemd_init() { GError *error = NULL; DBusGProxy *systemd_proxy = dbus_g_proxy_new_for_name_owner(U_dbus_connection_system, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", DBUS_INTERFACE_PROPERTIES, &error); if(error) { g_message ("systemd: %s\n", error->message); g_error_free(error); goto out; } char **empty = NULL; GValue val = {0, }; g_value_init (&val, G_TYPE_STRV); //g_value_set_string (&val, &empty); if(!dbus_g_proxy_call(systemd_proxy, "Set", &error, G_TYPE_STRING, "org.freedesktop.systemd1.Manager", G_TYPE_STRING, "DefaultControllers", G_TYPE_VALUE, &val, G_TYPE_INVALID)) { g_debug("can't unset systemd DefaultControllers: %s", error->message); g_error_free(error); goto out; } out: g_object_unref (systemd_proxy); } #endif #endif poelzi-ulatencyd-55515a9/src/tools.c000066400000000000000000000026651154664534000173760ustar00rootroot00000000000000#include "config.h" #include "ulatency.h" #include #include #include #include void recursive_rmdir(const char *path, int add_level) { FTS *fts; FTSENT *ftsent; char *const paths[] = { (char *)path, NULL }; /* * This means there can't be any autofs mounts yet, so * this is the first time we're being run since a reboot. * Clean out any stuff left in /Network from the reboot. */ fts = fts_open(paths, FTS_NOCHDIR|FTS_PHYSICAL, NULL); if (fts != NULL) { while ((ftsent = fts_read(fts)) != NULL) { /* * We only remove directories - if * there are files, we assume they're * there for a purpose. * * We remove directories after we've * removed their children, so we want * to process directories visited in * post-order. */ if (ftsent->fts_info == FTS_DP && ftsent->fts_level >= FTS_ROOTLEVEL + add_level) rmdir(ftsent->fts_accpath); } fts_close(fts); } } void u_timer_start(struct u_timer *t) { if(!t->count) { g_timer_continue(t->timer); }; t->count++; } void u_timer_stop(struct u_timer *t) { t->count--; g_assert(t->count >= 0); if(!t->count) { g_timer_stop(t->timer); }; } void u_timer_stop_clear(struct u_timer *t) { g_timer_start(t->timer); g_timer_stop(t->timer); } poelzi-ulatencyd-55515a9/src/ulatency.h000066400000000000000000000324141154664534000200620ustar00rootroot00000000000000/* Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. */ #ifndef __ulatency_h__ #define __ulatency_h__ #include #include #include #include #include #include #include #include "proc/procps.h" #include "proc/readproc.h" #ifdef ENABLE_DBUS #include #include #endif #ifdef POLKIT_FOUND #include #endif //#include #define U_LOG_LEVEL_SCHED 1 << 8 #define u_sched(...) g_log (G_LOG_DOMAIN, \ U_LOG_LEVEL_SCHED, \ __VA_ARGS__) #define U_LOG_LEVEL_TRACE 1 << 9 #define u_trace(...) g_log (G_LOG_DOMAIN, \ U_LOG_LEVEL_TRACE, \ __VA_ARGS__) #define VERSION 0.5.0 #define OPENPROC_FLAGS (PROC_FILLMEM | \ PROC_FILLUSR | PROC_FILLGRP | PROC_FILLSTATUS | PROC_FILLSTAT | \ PROC_FILLWCHAN | PROC_FILLCGROUP | PROC_FILLSUPGRP | PROC_FILLCGROUP | PROC_LOOSE_TASKS) #define OPENPROC_FLAGS_MINIMAL (PROC_FILLSTATUS) #define CONFIG_CORE "core" #define U_HEAD \ guint ref; \ void (*free_fnk)(void *data); struct _U_HEAD { U_HEAD; }; enum U_PROC_STATE { UPROC_NEW = (1<<0), UPROC_INVALID = (1<<1), UPROC_BASIC = (1<<2), UPROC_ALIVE = (1<<3), UPROC_HAS_PARENT = (1<<4), }; #define U_PROC_OK_MASK ~UPROC_INVALID #define U_PROC_IS_INVALID(P) ( P ->ustate & UPROC_INVALID ) #define U_PROC_IS_VALID(P) ((( P ->ustate & U_PROC_OK_MASK ) & UPROC_INVALID ) == 0) #define U_PROC_SET_STATE(P,STATE) ( P ->ustate = ( P ->ustate | STATE )) #define U_PROC_UNSET_STATE(P,STATE) ( P ->ustate = ( P ->ustate & ~STATE )) #define U_PROC_HAS_STATE(P,STATE) ( ( P ->ustate & STATE ) == STATE ) enum FILTER_TYPES { FILTER_LUA, FILTER_C }; enum FILTER_FLAGS { FILTER_STOP = (1<<0), FILTER_SKIP_CHILD = (1<<1), FILTER_RERUN_EXEC = (1<<2), FILTER_SKIP_THREADS = (1<<3), }; #define FILTER_TIMEOUT(v) ( v & 0xFFFF) #define FILTER_FLAGS(v) ( v >> 16) #define FILTER_MIX(flags,timeout) (( flags << 16 ) | timeout ) enum FILTER_PRIORITIES { PRIO_IDLE=-1, }; // default categories for convinience enum IO_PRIO_CLASS { IOPRIO_CLASS_NONE, IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE, }; struct lua_callback { lua_State *lua_state; int lua_state_id; int lua_func; int lua_data; }; struct lua_filter { lua_State *lua_state; int lua_state_id; int lua_func; int lua_data; int filter; GRegex *regexp_cmdline; GRegex *regexp_basename; double min_percent; }; struct filter_block { GTime timeout; int flags; }; typedef struct { U_HEAD; int pid; //!< duplicate of proc.tgid int ustate; //!< status bits for process struct proc_t proc; //!< main data storage char **cgroup_origin; //!< the original cgroups this process was created in GArray proc_history; //!< list of history elements int history_len; //!< desigered history len guint last_update; //!< counter for detecting dead processes GNode *node; //!< for parent/child lookups and transversal GHashTable *skip_filter; //!< storage of #filter_block for filters GList *flags; //!< list of #u_flag int changed; //!< flags or main parameters of process like uid, gid, sid changed int block_scheduler; //!< indicates that the process should not be touched by the scheduler GPtrArray *tasks; //!< pointer array to all process tasks of type #u_task int received_rt; //!< indicates a process had realtime prio at least once int lua_data; //!< id for per process lua storage // we don't use the libproc parsers here as we do not update these values // that often char *cmdfile; //!< basename of exe file GPtrArray *cmdline; //!< array of char * of cmdline arguments char *cmdline_match; //!< space concated version of cmdline GHashTable *environ; //!< char *:char * hash table of process environment char *exe; //!< executeable of the process // fake pgid because it can't be changed. pid_t fake_pgrp; //!< fake value for pgrp pid_t fake_pgrp_old; pid_t fake_session; //!< fake value of session pid_t fake_session_old; } u_proc; typedef struct { u_proc *proc; //!< process this task belongs to proc_t task; } u_task; typedef struct _filter { U_HEAD; enum FILTER_TYPES type; char *name; //!< name of filter int (*precheck)(struct _filter *filter); int (*check)(u_proc *pr, struct _filter *filter); int (*postcheck)(struct _filter *filter); int (*callback)(u_proc *pr, struct _filter *filter); int (*exit)(u_proc *pr, struct _filter *filter); void *data; } u_filter; #define INC_REF(P) P ->ref++; #define DEC_REF(P) \ do { struct _U_HEAD *uh = (struct _U_HEAD *) P ; uh->ref--; g_assert(uh->ref >= 0); \ if( uh->ref == 0 && uh->free_fnk) { uh->free_fnk( P ); P = NULL; }} while(0); #define FREE_IF_UNREF(P,FNK) if( P ->ref == 0 ) { FNK ( P ); } #define U_MALLOC(SIZE) g_malloc0(gsize n_bytes); #define U_FREE(PTR) g_free( PTR ); /*typedef enum { NONE = 0, REPLACE_SOURCE, ADD, } FLAG_BEHAVIOUR; */ typedef struct _FLAG { U_HEAD; void *source; // pointer to a data structure that is the "owner" // FLAG_BEHAVIOUR age; char *name; // label name char *reason; // why the flag was set. This makes most sense with emergency flags int64_t tid; // task id, if != 0 belongs to a process task time_t timeout; // timeout when the flag will disapear int32_t priority; // custom data: priority int64_t value; // custom data: value int64_t threshold; // custom data: threshold uint32_t inherit : 1; // will apply to all children } u_flag; u_flag *u_flag_new(u_filter *source, const char *name); void u_flag_free(void *data); int u_flag_add(u_proc *proc, u_flag *flag); int u_flag_del(u_proc *proc, u_flag *flag); int u_flag_clear_source(u_proc *proc, const void *source); int u_flag_clear_name(u_proc *proc, const char *name); int u_flag_clear_all(u_proc *proc); int u_flag_clear_flag(u_proc *proc, const void *flag); int u_flag_clear_timeout(u_proc *proc, time_t timeout); struct u_cgroup { struct cgroup *group; char *name; int ref; }; struct u_cgroup_controller { struct cgroup_controller *controller; char *name; int ref; // struct }; struct user_active_process { guint pid; time_t last_change; }; enum USER_ACTIVE_AGENT { USER_ACTIVE_AGENT_NONE = 0, USER_ACTIVE_AGENT_DISABLED, USER_ACTIVE_AGENT_DBUS, USER_ACTIVE_AGENT_MODULE=1000, }; // tracking for user sessions typedef struct { gchar *name; gchar *X11Display; gchar *X11Device; // most likely dbus session gchar *dbus_session; uid_t uid; uint32_t idle; uint32_t active; #ifdef ENABLE_DBUS DBusGProxy *proxy; #endif } u_session; // list of active sessions extern GList *U_session_list; struct user_active { uid_t uid; guint max_processes; guint active_agent; // tracker of the active list // FIXME: last change time time_t last_change; // time when the last change happend GList *actives; // list of user_active_process }; typedef struct { int (*all)(void); // make scheduler run over all processes int (*one)(u_proc *); // schedule for one (new) process int (*set_config)(char *name); // configure the scheduler for using a different configuration char *(*get_config)(void); // returns the name of current config GPtrArray *(*list_configs)(void); // returns a list of valid configs char *(*get_config_description)(char *name); } u_scheduler; // module prototype int (*MODULE_INIT)(void); // global variables extern GMainLoop *main_loop; extern GList *filter_list; extern GKeyFile *config_data; extern GList* active_users; extern GHashTable* processes; extern GNode* processes_tree; extern lua_State *lua_main_state; extern GList* system_flags; extern int system_flags_changed; #ifdef ENABLE_DBUS extern DBusGConnection *U_dbus_connection; // usully the system bus, but may differ on develop mode extern DBusGConnection *U_dbus_connection_system; // always the system bus struct callback_data; struct callback_data { GCancellable *cancellable; DBusConnection *connection; DBusMessage *message; void (*callback)(struct callback_data *data); void *user_data; }; #endif #ifdef POLKIT_FOUND PolkitAuthority *U_polkit_authority; int check_polkit(const char *methode, DBusConnection *connection, DBusMessage *context, char *action_id, void (*callback)(struct callback_data *data), void *user_data, int allow_user_interaction, u_proc *proc, char *config); #else #define check_polkit(...) FALSE #endif //extern gchar *load_pattern; // core.c int load_modules(char *path); int load_rule_directory(const char *path, const char *load_pattern, int fatal); int load_rule_file(const char *name); int load_lua_rule_file(lua_State *L, const char *name); /* u_proc* u_proc_new(proc_t proc) * * Allocates a new u_proc structure. * * @param proc: optional proc_t to copy data from. Will cause state U_PROC_ALIVE. * Returns: new allocated u_proc with refcount 1 */ u_proc* u_proc_new(proc_t *proc); void cp_proc_t(const struct proc_t *src,struct proc_t *dst); enum ENSURE_WHAT { BASIC, ENVIRONMENT, CMDLINE, EXE, TASKS, }; int u_proc_ensure(u_proc *proc, enum ENSURE_WHAT what, int update); GList *u_proc_list_flags (u_proc *proc, gboolean recrusive); GArray *u_proc_get_current_task_pids(u_proc *proc); u_filter *filter_new(); void filter_register(u_filter *filter, int instant); void filter_free(u_filter *filter); void filter_unregister(u_filter *filter); void filter_run(); void filter_for_proc(u_proc *proc, GList *list); int filter_run_for_proc(gpointer data, gpointer user_data); void cp_proc_t(const struct proc_t *src, struct proc_t *dst); // notify system of a new pids/changed/dead pids int process_new(pid_t pid, int noupdate); int process_new_delay(pid_t pid, pid_t parent); int process_new_list(GArray *list, int noupdate, int instant); int process_remove(u_proc *proc); int process_remove_by_pid(pid_t pid); // low level update api int process_update_pids(pid_t pids[]); int process_update_pid(pid_t pid); int process_run_one(u_proc *proc, int update, int instant); void clear_process_skip_filters(u_proc *proc, int block_types); int process_update_all(); static inline u_proc *proc_by_pid(pid_t pid) { return g_hash_table_lookup(processes, GUINT_TO_POINTER(pid)); } static inline u_proc *proc_by_pid_with_retry(pid_t pid) { u_proc *proc = g_hash_table_lookup(processes, GUINT_TO_POINTER(pid)); if(proc) return proc; if(process_update_pid(pid)) return g_hash_table_lookup(processes, GUINT_TO_POINTER(pid)); return NULL; } int scheduler_run_one(u_proc *proc); int scheduler_run(); u_scheduler *scheduler_get(); int scheduler_set(u_scheduler *scheduler); int iterate(void *); int core_init(); void core_unload(); // caches double get_last_load(); double get_last_percent(); // misc stuff guint get_plugin_id(); // tools.c struct u_timer { GTimer *timer; int count; }; void recursive_rmdir(const char *path, int add_level); void u_timer_start(struct u_timer *t); void u_timer_stop(struct u_timer *t); void u_timer_stop_clear(struct u_timer *t); // lua_binding int l_filter_run_for_proc(u_proc *pr, u_filter *flt); extern u_scheduler LUA_SCHEDULER; // sysctrl.c int ioprio_getpid(pid_t pid, int *ioprio, int *ioclass); int ioprio_setpid(pid_t pid, int ioprio, int ioclass); int adj_oom_killer(pid_t pid, int adj); int get_oom_killer(pid_t pid); // group.c void set_active_pid(unsigned int uid, unsigned int pid); struct user_active* get_userlist(guint uid, gboolean create); int is_active_pid(u_proc *proc); int get_active_pos(u_proc *proc); // sysinfo.c GHashTable * u_read_env_hash (pid_t pid); char * u_pid_get_env (pid_t pid, const char *var); GPtrArray * search_user_env(uid_t uid, const char *name, int update); GPtrArray * u_read_0file (pid_t pid, const char *what); uint64_t get_number_of_processes(); // dbus consts #define U_DBUS_SERVICE_NAME "org.quamquam.ulatencyd" #define U_DBUS_USER_PATH "/org/quamquam/ulatencyd/User" #define U_DBUS_USER_INTERFACE "org.quamquam.ulatencyd.User" #define U_DBUS_SYSTEM_PATH "/org/quamquam/ulatencyd/System" #define U_DBUS_SYSTEM_INTERFACE "org.quamquam.ulatencyd.System" #endifpoelzi-ulatencyd-55515a9/src/ulatency.lua000066400000000000000000000031431154664534000204110ustar00rootroot00000000000000-- this file is for documentation purpuses only --! @file ulatency.lua --! This file documents the 'ulatency' table which is implemented in the core --! --! @brief u_proc class u_proc = {} --! @brief return io prio and class function u_proc:get_ioprio() return prio, class end --! @brief sets io prio function u_proc:set_ioprio(prio, class) end --! @brief parent of process --! @return u_proc instance or nil function u_proc:get_parent() end ulatency = {} ulatency.version="VERSION" ulatency.release_agent = "/PATH/TO/RELEASEAGENT" ulatency.path_rules_directory = "/PATH/TO/RULES" ulatency.path_config_directory = "/PATH/TO/CONFIG" ulatency.smp_num_cpus = 23 // glib log level ulatency.LOG_LEVEL_ERROR = G_LOG_LEVEL_ERROR ulatency.LOG_LEVEL_CRITICAL = G_LOG_LEVEL_CRITICAL ulatency.LOG_LEVEL_WARNING = G_LOG_LEVEL_WARNING ulatency.LOG_LEVEL_MESSAGE = G_LOG_LEVEL_MESSAGE ulatency.LOG_LEVEL_INFO = G_LOG_LEVEL_INFO ulatency.LOG_LEVEL_DEBUG = G_LOG_LEVEL_DEBUG ulatency.LOG_LEVEL_SCHED = U_LOG_LEVEL_SCHED ulatency.LOG_LEVEL_TRACE = U_LOG_LEVEL_TRACE ulatency.FILTER_STOP = FILTER_STOP ulatency.FILTER_SKIP_CHILD = FILTER_SKIP_CHILD ulatency.IOPRIO_CLASS_NONE = IOPRIO_CLASS_NONE ulatency.IOPRIO_CLASS_RT = IOPRIO_CLASS_RT ulatency.IOPRIO_CLASS_BE = IOPRIO_CLASS_BE ulatency.IOPRIO_CLASS_IDLE = IOPRIO_CLASS_IDLE // realtime priority stuff ulatency.SCHED_OTHER = SCHED_OTHER ulatency.SCHED_FIFO = SCHED_FIFO ulatency.SCHED_RR = SCHED_RR ulatency.SCHED_BATCH = SCHED_BATCH ulatency.SCHED_IDLE = SCHED_IDLE ulatency.UPROC_NEW = UPROC_NEW ulatency.UPROC_INVALID = UPROC_INVALID ulatency.UPROC_ALIVE = UPROC_ALIVE poelzi-ulatencyd-55515a9/src/ulatencyd.c000066400000000000000000000361601154664534000202230ustar00rootroot00000000000000/* Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. */ #include "ulatency.h" #include "config.h" #include #include #include #include #include #include #include #include #include #ifndef O_NOFOLLOW // not important here to have NOFOLLOW. very unlikly attack #define O_NOFOLLOW 0 #endif #ifdef ENABLE_DBUS #include #include #include DBusGConnection *U_dbus_connection; DBusGConnection *U_dbus_connection_system; #endif #include #include "proc/sysinfo.h" #include "proc/readproc.h" #ifdef LIBCGROUP #include #endif #include #include static gchar *config_file = QUOTEME(CONFIG_PATH)"/ulatencyd.conf"; static gchar *rules_directory = QUOTEME(RULES_DIRECTORY); static gchar *modules_directory = QUOTEME(MODULES_DIRECTORY); static gchar *load_pattern = NULL; static gint verbose = 1<<5; static char *mount_point; static char *log_file = NULL; int log_fd = -1; GKeyFile *config_data; static char *config_cgroup_root; /* static gint max_size = 8; static gboolean beep = FALSE; */ //static gboolean rand = FALSE; static gboolean opt_daemon = FALSE; int init_netlink(GMainLoop *loop); static gboolean opt_verbose(const gchar *option_name, const gchar *value, gpointer data, GError **error) { int i = 1; if(value) { i = atoi(value); } verbose = verbose << i; return TRUE; } static gboolean opt_quiet(const gchar *option_name, const gchar *value, gpointer data, GError **error) { int i = 1; if(value) { i = atoi(value); } verbose = verbose >> i; return TRUE; } static GOptionEntry entries[] = { { "config", 'c', 0, G_OPTION_ARG_FILENAME, &config_file, "Use config file", NULL}, { "rules-directory", 'r', 0, G_OPTION_ARG_FILENAME, &rules_directory, "Path with ", NULL}, { "rule-pattern", 0, 0, G_OPTION_ARG_STRING, &load_pattern, "Load only rules matching the pattern", NULL}, { "verbose", 'v', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, &opt_verbose, "More verbose. Can be passed multiple times", NULL }, { "quiet", 'q', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, &opt_quiet, "More quiet. Can be passed multiple times", NULL }, { "log-file", 'f', 0, G_OPTION_ARG_FILENAME, &log_file, "Log to file", NULL}, { "daemonize", 'd', 0, G_OPTION_ARG_NONE, &opt_daemon, "Run daemon in background", NULL }, { NULL } }; int filter_interval; GMainContext *main_context; GMainLoop *main_loop; void cleanup() { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "cleanup daemon"); #ifdef LIBCGROUP cgroup_unload_cgroups(); #endif // for valgrind core_unload(); } #define FORMAT_UNSIGNED_BUFSIZE ((GLIB_SIZEOF_LONG * 3) + 3) #define STRING_BUFFER_SIZE (FORMAT_UNSIGNED_BUFSIZE + 32) #define CHAR_IS_SAFE(wc) (!((wc < 0x20 && wc != '\t' && wc != '\n' && wc != '\r') || \ (wc == 0x7f) || \ (wc >= 0x80 && wc < 0xa0))) static void escape_string (GString *string) { const char *p = string->str; gunichar wc; while (p < string->str + string->len) { gboolean safe; wc = g_utf8_get_char_validated (p, -1); if (wc == (gunichar)-1 || wc == (gunichar)-2) { gchar *tmp; guint pos; pos = p - string->str; /* Emit invalid UTF-8 as hex escapes */ tmp = g_strdup_printf ("\\x%02x", (guint)(guchar)*p); g_string_erase (string, pos, 1); g_string_insert (string, pos, tmp); p = string->str + (pos + 4); /* Skip over escape sequence */ g_free (tmp); continue; } if (wc == '\r') safe = *(p + 1) == '\n'; else safe = CHAR_IS_SAFE (wc); if (!safe) { gchar *tmp; guint pos; pos = p - string->str; /* Largest char we escape is 0x0a, so we don't have to worry * about 8-digit \Uxxxxyyyy */ tmp = g_strdup_printf ("\\u%04x", wc); g_string_erase (string, pos, g_utf8_next_char (p) - p); g_string_insert (string, pos, tmp); g_free (tmp); p = string->str + (pos + 6); /* Skip over escape sequence */ } else p = g_utf8_next_char (p); } } static void log_file_handler (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer unused_data) { gboolean is_fatal = (log_level & G_LOG_FLAG_FATAL) != 0; gchar level_prefix[STRING_BUFFER_SIZE], *string; GString *gstring; if (log_level & G_LOG_FLAG_RECURSION) { return; } gstring = g_string_new (NULL); if (log_domain) { g_string_append (gstring, log_domain); g_string_append_c (gstring, '-'); } switch (log_level & G_LOG_LEVEL_MASK) { case G_LOG_LEVEL_ERROR: strcpy (level_prefix, "ERROR"); break; case G_LOG_LEVEL_CRITICAL: strcpy (level_prefix, "CRITICAL"); break; case G_LOG_LEVEL_WARNING: strcpy (level_prefix, "WARNING"); break; case G_LOG_LEVEL_MESSAGE: strcpy (level_prefix, "Message"); break; case G_LOG_LEVEL_INFO: strcpy (level_prefix, "INFO"); break; case G_LOG_LEVEL_DEBUG: strcpy (level_prefix, "DEBUG"); break; case U_LOG_LEVEL_TRACE: strcpy (level_prefix, "TRACE"); break; case U_LOG_LEVEL_SCHED: strcpy (level_prefix, "SCHED"); break; default: strcpy (level_prefix, "LOG-"); } g_string_append (gstring, level_prefix); g_string_append (gstring, ": "); if (!message) { g_string_append (gstring, "(NULL) message"); } else { GString *msg; msg = g_string_new (message); escape_string (msg); g_string_append (gstring, msg->str); /* charset is UTF-8 already */ g_string_free (msg, TRUE); } if (is_fatal) g_string_append (gstring, "\naborting...\n"); else g_string_append (gstring, "\n"); string = g_string_free (gstring, FALSE); write (log_fd, string, strlen (string)); g_free (string); } static void close_logfile() { if(log_fd >= 0) close(log_fd); log_fd = -1; } static int open_logfile(char *file) { if(file == NULL) file = log_file; if(file == NULL) return FALSE; if(log_fd) close_logfile(); log_fd = open(file, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if(log_fd == -1) g_warning("can't write to log file %s", file); return TRUE; } static void filter_log_handler(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer unused_data) { if(log_level <= verbose) { if(log_fd != -1) log_file_handler(log_domain, log_level, message, unused_data); else g_log_default_handler(log_domain, log_level, message, unused_data); } } void load_config() { GError *error = NULL; if(!g_key_file_load_from_file(config_data, config_file, G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS, &error)) { g_error("could not load config file: %s: %s", config_file, error->message); } filter_interval = g_key_file_get_integer(config_data, CONFIG_CORE, "interval", NULL); if(!filter_interval) filter_interval = 60; mount_point = g_key_file_get_string(config_data, CONFIG_CORE, "mount_point", NULL); if(!mount_point) mount_point = "/dev/cgroups"; } #define DEFAULT_CGROUPS "cpu,memory" int mount_cgroups() { gchar *argv[10]; gint i, rv; gint result; GError *error = NULL; char *sub = g_key_file_get_string(config_data, CONFIG_CORE, "cgroup_subsys", NULL); if (!sub) { sub = DEFAULT_CGROUPS; } argv[i=0] = "/bin/mount"; argv[++i] = "-t"; argv[++i] = "cgroup"; argv[++i] = "-o"; argv[++i] = sub; argv[++i] = "none"; argv[++i] = mount_point; argv[++i] = NULL; rv = g_spawn_sync(NULL, argv, NULL, 0, NULL, NULL, NULL, NULL, &result, &error); if(rv && !result) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "mounted cgroups on %s", mount_point); } else { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "error mounting cgroups on %s: %s", mount_point, (error && error->message) ? error->message : ""); return FALSE; } return TRUE; } /* gboolean g_spawn_sync ("/", gchar **argv, gchar **envp, GSpawnFlags flags, GSpawnChildSetupFunc child_setup, gpointer user_data, gchar **standard_output, gchar **standard_error, gint *exit_status, GError **error); } */ #ifdef ENABLE_DBUS static int do_dbus_init() { GError *error = NULL; DBusConnection *con; #ifdef DEVELOP_DBUS_SESSION char *env_uid; uid_t target = 0; if(getuid() == 0) { env_uid = getenv("SUDO_UID"); if(!env_uid) g_error("please set SUDO_UID env"); target = atoi(env_uid); seteuid(target); U_dbus_connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); seteuid(0); } else { U_dbus_connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); } U_dbus_connection_system = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); g_warning("DEVELOP_MODE ON: using session dbus"); #else U_dbus_connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); U_dbus_connection_system = U_dbus_connection; #endif if (U_dbus_connection == NULL) { g_warning("Failed to open connection to bus: %s\n", error->message); g_error_free (error); return FALSE; } con = dbus_g_connection_get_connection(U_dbus_connection); #ifndef DEVELOP_MODE dbus_connection_set_exit_on_disconnect (con, FALSE); #endif return TRUE; } #endif static void signal_reload (int signal) { g_warning("FIXME: reload config"); } static void signal_cleanup (int signal) { // we have to make sure cgroup_unload_cgroups is called printf("abort cleanup\n"); #ifdef LIBCGROUP cgroup_unload_cgroups(); #endif exit(1); } static void signal_logrotate (int signal) { close_logfile(); open_logfile(log_file); } int timeout_long(gpointer data) { static int run = 0; // check if dbus connection is still alive #ifdef ENABLE_DBUS if(U_dbus_connection) { DBusConnection *con = dbus_g_connection_get_connection(U_dbus_connection); if(!dbus_connection_get_is_connected(con)) { g_warning("got disconnected from dbus system bus. reconnecting..."); dbus_g_connection_unref(U_dbus_connection); do_dbus_init(); } } else { do_dbus_init(); } #endif if(run == 0 && config_cgroup_root) { g_debug("cleanup cgroup directory: %s", config_cgroup_root); // FIXME: why causes this hung tasks in the kernel ???? //recursive_rmdir(config_cgroup_root, 2); } run = (run + 1)%10; return TRUE; } int main (int argc, char *argv[]) { GError *error = NULL; GOptionContext *context; // required for dbus g_type_init (); config_data = g_key_file_new(); #ifdef ENABLE_DBUS g_thread_init(NULL); dbus_g_thread_init(); do_dbus_init(); #endif context = g_option_context_new ("- latency optimizing daemon"); g_option_context_add_main_entries (context, entries, /*GETTEXT_PACKAGE*/NULL); if (!g_option_context_parse (context, &argc, &argv, &error)) { g_print ("option parsing failed: %s\n", error->message); exit (1); } pid_t pid, sid; if (opt_daemon) { pid = fork(); if (pid < 0) { exit (1); } if (pid > 0) { exit (0); } umask (0); sid = setsid(); if (sid < 0) { exit (1); } close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); } load_config(); g_log_set_default_handler(filter_log_handler, NULL); if(log_file) { open_logfile(log_file); } main_context = g_main_context_default(); main_loop = g_main_loop_new(main_context, FALSE); #if LIBCGROUP if(cgroup_init()) { g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "could not init libcgroup. try mounting cgroups..."); g_mkdir_with_parents(mount_point, 0755); if(!mount_cgroups() || cgroup_init()) { #ifdef DEVELOP_MODE g_log(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, "give up init libcgroup"); //g_log(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "give up init libcgroup"); #else g_log(G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, "give up init libcgroup"); #endif } } #else //mount_cgroups(); #endif g_atexit(cleanup); if (signal (SIGABRT, signal_cleanup) == SIG_IGN) signal (SIGABRT, SIG_IGN); if (signal (SIGINT, signal_cleanup) == SIG_IGN) signal (SIGINT, SIG_IGN); if (signal (SIGTERM, signal_cleanup) == SIG_IGN) signal (SIGTERM, SIG_IGN); if (signal (SIGUSR1, signal_reload) == SIG_IGN) signal (SIGUSR1, SIG_IGN); if (signal (SIGUSR2, signal_logrotate) == SIG_IGN) signal (SIGUSR2, SIG_IGN); //signal (SIGABRT, cleanup_on_abort); core_init(); // set the cgroups root path lua_getfield(lua_main_state, LUA_GLOBALSINDEX, "CGROUP_ROOT"); /* function to be called */ config_cgroup_root = g_strdup(lua_tostring(lua_main_state, -1)); lua_pop(lua_main_state, 1); if(!strcmp(config_cgroup_root, "/") || !strcmp(config_cgroup_root, "")) { g_warning("bad cgroup root path: %s", config_cgroup_root); g_free(config_cgroup_root); config_cgroup_root = NULL; } if(config_cgroup_root) { g_debug("cleanup cgroup directory: %s", config_cgroup_root); recursive_rmdir(config_cgroup_root, 2); } adj_oom_killer(getpid(), -1000); load_modules(modules_directory); load_rule_directory(rules_directory, load_pattern, TRUE); process_update_all(); gboolean el = g_key_file_get_boolean(config_data, "core", "netlink", &error); if(el || error) init_netlink(main_loop); else g_message("netlink support disabled. no fast reactions possible"); if(error) g_error_free(error), error = NULL; // small hack timeout_long(NULL); iterate(GUINT_TO_POINTER(0)); g_timeout_add_seconds(60, timeout_long, GUINT_TO_POINTER(1)); g_timeout_add_seconds(filter_interval, iterate, GUINT_TO_POINTER(1)); g_log(G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, "ulatencyd started successfull"); g_main_loop_run(main_loop); return 0; } poelzi-ulatencyd-55515a9/src/ulatencyd_cleanup.lua.tmpl000077500000000000000000000010341154664534000232370ustar00rootroot00000000000000#!/usr/bin/env lua CONFIG_PATH = "${CONFIG_DIR}/cgroups.conf" chunk = loadfile(CONFIG_PATH) if not chunk then print("can't load config") os.exit(1) end chunk() require("posix") if string.sub(CGROUP_ROOT, -1) ~= "/" then CGROUP_ROOT = CGROUP_ROOT .. "/" end if not arg[1] then print "path required" os.exit(1) end for mnt,_ in pairs(CGROUP_MOUNTPOINTS) do local path = CGROUP_ROOT .. mnt .. "/" .. arg[1] if posix.access(path) == 0 then if posix.rmdir(path) == 0 then os.exit(0) end end end os.exit(1) poelzi-ulatencyd-55515a9/tests/000077500000000000000000000000001154664534000164345ustar00rootroot00000000000000poelzi-ulatencyd-55515a9/tests/CMakeLists.txt000066400000000000000000000015361154664534000212010ustar00rootroot00000000000000add_executable(memleak memleak.c) SET_TARGET_PROPERTIES(memleak PROPERTIES COMPILE_FLAGS "${ADD_COMPILE_FLAGS}") add_executable(forkbomb forkbomb.c) SET_TARGET_PROPERTIES(forkbomb PROPERTIES COMPILE_FLAGS "${ADD_COMPILE_FLAGS}") if(XCB_FOUND AND XAU_FOUND AND DBUS_FOUND AND ENABLE_DBUS) # FIXME needs rework #add_executable(test_xwatch test_xwatch.c) include_directories(${XCB_INCLUDE_DIRS} ${XAU_INCLUDE_DIRS} ${DBUS_INCLUDE_DIRS}) #target_link_libraries(test_xwatch ${GLIB2_LIBRARIES} ${XCB_LIBRARIES} # ${XAU_LIBRARIES} ${DBUS_LIBRARIES}) #SET_TARGET_PROPERTIES(test_xwatch PROPERTIES COMPILE_FLAGS "${ADD_COMPILE_FLAGS} -O0") else(XCB_FOUND AND XAU_FOUND AND DBUS_FOUND AND ENABLE_DBUS) message("xcb, xau or dbus headers missing. disable xwatch module") endif(XCB_FOUND AND XAU_FOUND AND DBUS_FOUND AND ENABLE_DBUS) poelzi-ulatencyd-55515a9/tests/forkbomb.c000066400000000000000000000063441154664534000204100ustar00rootroot00000000000000/* Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. */ #include #include #include #include #include #include #include #include int main (argc, argv) int argc; char **argv; { int c = 0; int i = 0; int nums = 2000; int delay = 50000; int work = 0; int rand = open("/dev/urandom",O_RDONLY); int data[2]; int tmpi; pid_t pid; while (1) { int option_index = 0; static struct option long_options[] = { {"nums", 1, 0, 'n'}, {"delay", 1, 0, 'd'}, {"work", 0, 0, 'w'}, {"help", 0, 0, 'h'}, {0, 0, 0, 0} }; c = getopt_long (argc, argv, "n:c:hd:", long_options, &option_index); if (c == -1) break; switch (c) { case 0: printf ("option %s", long_options[option_index].name); if (optarg) printf (" with arg %s", optarg); printf ("\n"); break; case 'h': printf ("forkbomb:\n"); printf ("-n num number of forks to create\n"); printf ("-d delay delay in usecs between alloc \n"); exit(0); break; case 'n': nums = atoi(optarg); break; case 'w': work = 1; break; case 'd': delay = atoi(optarg); break; case '?': break; default: printf ("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) printf ("%s ", argv[optind++]); printf ("\n"); } printf( "!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!\n" "!!!!! THIS PROGRAM WILL LIKELY KILL YOUR COMPUTER !!!!!\n" "!!!!! ulatency may rescue you ;-) !!!!!\n" "!!!!! press ctrl+c to stop !!!!!\n" "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" ); sleep(3); printf ("fork %d childs %s (delay %d us)e:\n", nums, work ? "working" : "", delay); while(1) { if(i < nums) { pid = fork(); if(pid < 0) { printf("can't fork\n"); } if(pid == 0) { printf("."); fflush(stdout); if(work) { read(rand, &data, sizeof(int)*2); if(data[1] != 0) { tmpi = data[0]/data[1]; } } else { sleep(10000); } } i++; } usleep(delay); } exit (0); } poelzi-ulatencyd-55515a9/tests/lunatest.lua000066400000000000000000000755101154664534000210060ustar00rootroot00000000000000----------------------------------------------------------------------- -- -- Copyright (c) 2009 Scott Vokes -- -- Permission is hereby granted, free of charge, to any person -- obtaining a copy of this software and associated documentation -- files (the "Software"), to deal in the Software without -- restriction, including without limitation the rights to use, -- copy, modify, merge, publish, distribute, sublicense, and/or sell -- copies of the Software, and to permit persons to whom the -- Software is furnished to do so, subject to the following -- conditions: -- -- The above copyright notice and this permission notice shall be -- included in all copies or substantial portions of the Software. -- -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -- HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -- OTHER DEALINGS IN THE SOFTWARE. -- ------------------------------------------------------------------------ -- -- This is a library for randomized testing with Lua. -- For usage and examples, see README and the test suite. -- ------------------------------------------------------------------------ ------------ -- Module -- ------------ -- standard libraries used local debug, io, math, os, string, table = debug, io, math, os, string, table -- required core global functions local assert, error, ipairs, pairs, pcall, print, setmetatable, tonumber = assert, error, ipairs, pairs, pcall, print, setmetatable, tonumber local fmt, tostring, type, unpack = string.format, tostring, type, unpack local getmetatable, rawget, setmetatable, xpcall = getmetatable, rawget, setmetatable, xpcall local exit, next, require = os.exit, next, require -- Get containing env, Lua 5.1-style local getenv = getfenv ---Use lhf's random, if available. It provides an RNG with better -- statistical properties, and it gives consistent values across OSs. -- http://www.tecgraf.puc-rio.br/~lhf/ftp/lua/#lrandom pcall(require, "random") local random = random ---If available, use luasocket's gettime() for timestamps. pcall(require, "socket") local now = socket and socket.gettime -- Get env immediately wrapping module, to put assert_ tests there. local _importing_env = getenv() -- Check command line arguments: -- -v / --verbose, default to verbose_hooks. -- -s or --suite, only run the named suite(s). -- -t or --test, only run tests matching the pattern. local lt_arg = arg -- ##################### -- # Utility functions # -- ##################### local function printf(...) print(string.format(...)) end local function result_table(name) return { name=name, pass={}, fail={}, skip={}, err={} } end local function combine_results(to, from) local s_name = from.name for _,set in ipairs{"pass", "fail", "skip", "err" } do local fs, ts = from[set], to[set] for name,val in pairs(fs) do ts[s_name .. "." .. name] = val end end end local function is_func(v) return type(v) == "function" end local function count(t) local ct = 0 for _ in pairs(t) do ct = ct + 1 end return ct end -- ########### -- # Results # -- ########### local function msec(t) if t and type(t) == "number" then return fmt(" (%.2fms)", t * 1000) else return "" end end local RPass = {} local passMT = {__index=RPass} function RPass:tostring_char() return "." end function RPass:add(s, name) s.pass[name] = self end function RPass:type() return "pass" end function RPass:tostring(name) return fmt("PASS: %s%s%s", name or "(unknown)", msec(self.elapsed), self.msg and (": " .. tostring(self.msg)) or "") end local RFail = {} local failMT = {__index=RFail} function RFail:tostring_char() return "F" end function RFail:add(s, name) s.fail[name] = self end function RFail:type() return "fail" end function RFail:tostring(name) return fmt("FAIL: %s%s: %s%s", name or "(unknown)", msec(self.elapsed), self.reason or "", self.msg and (" - " .. tostring(self.msg)) or "") end local RSkip = {} local skipMT = {__index=RSkip} function RSkip:tostring_char() return "s" end function RSkip:add(s, name) s.skip[name] = self end function RSkip:type() return "skip" end function RSkip:tostring(name) return fmt("SKIP: %s()%s", name or "unknown", self.msg and (" - " .. tostring(self.msg)) or "") end local RError = {} local errorMT = {__index=RError} function RError:tostring_char() return "E" end function RError:add(s, name) s.err[name] = self end function RError:type() return "error" end function RError:tostring(name) return self.msg or fmt("ERROR (in %s%s, couldn't get traceback)", msec(self.elapsed), name or "(unknown)") end local function Pass(t) return setmetatable(t or {}, passMT) end local function Fail(t) return setmetatable(t, failMT) end local function Skip(t) return setmetatable(t, skipMT) end local function Error(t) return setmetatable(t, errorMT) end -- ############## -- # Assertions # -- ############## ---Renamed standard assert. local checked = 0 local TS = tostring local function wraptest(flag, msg, t) checked = checked + 1 t.msg = msg if not flag then error(Fail(t)) end end ---Fail a test. -- @param no_exit Unless set to true, the presence of any failures -- causes the test suite to terminate with an exit status of 1. function fail(msg, no_exit) error(Fail { msg=msg, reason="(Failed)", no_exit=no_exit }) end ---Skip a test, with a note, e.g. "TODO". function skip(msg) error(Skip { msg=msg }) end ---got == true. -- (Named "assert_true" to not conflict with standard assert.) -- @param msg Message to display with the result. function assert_true(got, msg) wraptest(got, msg, { reason=fmt("Expected success, got %s.", TS(got)) }) end ---got == false. function assert_false(got, msg) wraptest(not got, msg, { reason=fmt("Expected false, got %s", TS(got)) }) end --got == nil function assert_nil(got, msg) wraptest(got == nil, msg, { reason=fmt("Expected nil, got %s", TS(got)) }) end --got ~= nil function assert_not_nil(got, msg) wraptest(got ~= nil, msg, { reason=fmt("Expected non-nil value, got %s", TS(got)) }) end local function tol_or_msg(t, m) if not t and not m then return 0, nil elseif type(t) == "string" then return 0, t elseif type(t) == "number" then return t, m else error("Neither a numeric tolerance nor string") end end ---exp == got. function assert_equal(exp, got, tol, msg) tol, msg = tol_or_msg(tol, msg) if type(exp) == "number" and type(got) == "number" then wraptest(math.abs(exp - got) <= tol, msg, { reason=fmt("Expected %s +/- %s, got %s", TS(exp), TS(tol), TS(got)) }) else wraptest(exp == got, msg, { reason=fmt("Expected %q, got %q", TS(exp), TS(got)) }) end end ---exp ~= got. function assert_not_equal(exp, got, msg) wraptest(exp ~= got, msg, { reason="Expected something other than " .. TS(exp) }) end ---val > lim. function assert_gt(lim, val, msg) wraptest(val > lim, msg, { reason=fmt("Expected a value > %s, got %s", TS(lim), TS(val)) }) end ---val >= lim. function assert_gte(lim, val, msg) wraptest(val >= lim, msg, { reason=fmt("Expected a value >= %s, got %s", TS(lim), TS(val)) }) end ---val < lim. function assert_lt(lim, val, msg) wraptest(val < lim, msg, { reason=fmt("Expected a value < %s, got %s", TS(lim), TS(val)) }) end ---val <= lim. function assert_lte(lim, val, msg) wraptest(val <= lim, msg, { reason=fmt("Expected a value <= %s, got %s", TS(lim), TS(val)) }) end ---#val == len. function assert_len(len, val, msg) wraptest(#val == len, msg, { reason=fmt("Expected #val == %d, was %d", len, #val) }) end ---#val ~= len. function assert_not_len(len, val, msg) wraptest(#val ~= len, msg, { reason=fmt("Expected length other than %d", len) }) end ---Test that the string s matches the pattern exp. function assert_match(pat, s, msg) s = tostring(s) wraptest(type(s) == "string" and s:match(pat), msg, { reason=fmt("Expected string to match pattern %s, was %s", pat, (s:len() > 30 and (s:sub(1,30) .. "...")or s)) }) end ---Test that the string s doesn't match the pattern exp. function assert_not_match(pat, s, msg) wraptest(type(s) ~= "string" or not s:match(pat), msg, { reason=fmt("Should not match pattern %s", pat) }) end ---Test that val is a boolean. function assert_boolean(val, msg) wraptest(type(val) == "boolean", msg, { reason=fmt("Expected type boolean but got %s", type(val)) }) end ---Test that val is not a boolean. function assert_not_boolean(val, msg) wraptest(type(val) ~= "boolean", msg, { reason=fmt("Expected type other than boolean but got %s", type(val)) }) end ---Test that val is a number. function assert_number(val, msg) wraptest(type(val) == "number", msg, { reason=fmt("Expected type number but got %s", type(val)) }) end ---Test that val is not a number. function assert_not_number(val, msg) wraptest(type(val) ~= "number", msg, { reason=fmt("Expected type other than number but got %s", type(val)) }) end ---Test that val is a string. function assert_string(val, msg) wraptest(type(val) == "string", msg, { reason=fmt("Expected type string but got %s", type(val)) }) end ---Test that val is not a string. function assert_not_string(val, msg) wraptest(type(val) ~= "string", msg, { reason=fmt("Expected type other than string but got %s", type(val)) }) end ---Test that val is a table. function assert_table(val, msg) wraptest(type(val) == "table", msg, { reason=fmt("Expected type table but got %s", type(val)) }) end ---Test that val is not a table. function assert_not_table(val, msg) wraptest(type(val) ~= "table", msg, { reason=fmt("Expected type other than table but got %s", type(val)) }) end ---Test that val is a function. function assert_function(val, msg) wraptest(type(val) == "function", msg, { reason=fmt("Expected type function but got %s", type(val)) }) end ---Test that val is not a function. function assert_not_function(val, msg) wraptest(type(val) ~= "function", msg, { reason=fmt("Expected type other than function but got %s", type(val)) }) end ---Test that val is a thread (coroutine). function assert_thread(val, msg) wraptest(type(val) == "thread", msg, { reason=fmt("Expected type thread but got %s", type(val)) }) end ---Test that val is not a thread (coroutine). function assert_not_thread(val, msg) wraptest(type(val) ~= "thread", msg, { reason=fmt("Expected type other than thread but got %s", type(val)) }) end ---Test that val is a userdata (light or heavy). function assert_userdata(val, msg) wraptest(type(val) == "userdata", msg, { reason=fmt("Expected type userdata but got %s", type(val)) }) end ---Test that val is not a userdata (light or heavy). function assert_not_userdata(val, msg) wraptest(type(val) ~= "userdata", msg, { reason=fmt("Expected type other than userdata but got %s", type(val)) }) end ---Test that a value has the expected metatable. function assert_metatable(exp, val, msg) local mt = getmetatable(val) wraptest(mt == exp, msg, { reason=fmt("Expected metatable %s but got %s", TS(exp), TS(mt)) }) end ---Test that a value does not have a given metatable. function assert_not_metatable(exp, val, msg) local mt = getmetatable(val) wraptest(mt ~= exp, msg, { reason=fmt("Expected metatable other than %s", TS(exp)) }) end ---Test that the function raises an error when called. function assert_error(f, msg) local ok, err = pcall(f) wraptest(not ok, msg, { exp="an error", got=ok or err, reason=fmt("Expected an error, got %s", TS(got)) }) end ---Run a test case with randomly instantiated arguments, -- running the test function f opt.count (default: 100) times. -- @param opt A table with options, or just a test name string.
-- opt.count: how many random trials to perform
-- opt.seed: Start the batch of trials with a specific seed
-- opt.always: Always test these seeds (for regressions)
-- opt.show_progress: Whether to print a . after every opt.tick trials.
-- opt.seed_limit: Max seed to allow.
-- opt.max_failures, max_errors, max_skips: Give up after X of each.
-- @param f A test function, run as f(unpack(randomized_args(...))) -- @param ... the arg specification. For each argument, creates a -- random instance of that type.
-- boolean: return true or false
-- number n: returns 0 <= x < n, or -n <= x < n if negative. -- If n has a decimal component, so will the result.
-- string: Specifiedd as "(len[,maxlen]) (pattern)".
-- "10 %l" means 10 random lowercase letters.
-- "10,30 [aeiou]" means between 10-30 vowels.
-- function: Just call (as f()) and return result.
-- table or userdata: Call v.__random() and return result.
-- @usage function assert_random(opt, f, ...) -- Stub. Exported to the same namespace, but code appears below. end -- #################### -- # Module beginning # -- #################### ---Unit testing module, with extensions for random testing. module("lunatest") VERSION = "0.91" -- ######### -- # Hooks # -- ######### local dot_ct = 0 local cols = 70 local iow = io.write -- Print a char ([.fEs], etc.), wrapping at 70 columns. local function dot(c) c = c or "." io.write(c) dot_ct = dot_ct + 1 if dot_ct > cols then io.write("\n ") dot_ct = 0 end io.stdout:flush() end local function print_totals(r) local ps, fs = count(r.pass), count(r.fail) local ss, es = count(r.skip), count(r.err) local elapsed = "" if r.t_pre and r.t_post then local el, unit = r.t_post - r.t_pre, "s" if el < 1 then unit = "ms"; el = el * 1000 end elapsed = fmt(" in %.2f %s", el, unit) end local buf = {"\n---- Testing finished%s, ", "with %d assertion(s) ----\n", " %d passed, %d failed, ", "%d error(s), %d skipped."} printf(table.concat(buf), elapsed, checked, ps, fs, es, ss) end ---Default behavior. default_hooks = { begin = false, begin_suite = function(s_env, tests) iow(fmt("\n-- Starting suite %q, %d test(s)\n ", s_env.name, count(tests))) end, end_suite = false, pre_test = false, post_test = function(name, res) dot(res:tostring_char()) end, done = function(r) print_totals(r) for _,ts in ipairs{ r.fail, r.err, r.skip } do for name,res in pairs(ts) do printf("%s", res:tostring(name)) end end end, } ---Default verbose behavior. verbose_hooks = { begin = function(res, suites) local s_ct = count(suites) if s_ct > 0 then printf("Starting tests, %d suite(s)", s_ct) end end, begin_suite = function(s_env, tests) dot_ct = 0 printf("-- Starting suite %q, %d test(s)", s_env.name, count(tests)) end, end_suite = function(s_env) local ps, fs = count(s_env.pass), count(s_env.fail) local ss, es = count(s_env.skip), count(s_env.err) dot_ct = 0 printf(" Finished suite %q, +%d -%d E%d s%d", s_env.name, ps, fs, es, ss) end, pre_test = false, post_test = function(name, res) printf("%s", res:tostring(name)) dot_ct = 0 end, done = function(r) print_totals(r) end } setmetatable(verbose_hooks, {__index = default_hooks }) -- ################ -- # Registration # -- ################ local suites = {} local failed_suites = {} ---Check if a function name should be considered a test key. -- Defaults to functions starting or ending with "test", with -- leading underscores allowed. function is_test_key(k) return type(k) == "string" and k:match("_*test.*") end local function get_tests(mod) local ts = {} for k,v in pairs(mod) do if is_test_key(k) and type(v) == "function" then ts[k] = v end end ts.setup = rawget(mod, "setup") ts.teardown = rawget(mod, "teardown") ts.ssetup = rawget(mod, "suite_setup") ts.steardown = rawget(mod, "suite_teardown") return ts end function get_suites() return suites end ---Add a file as a test suite. -- @param modname The module to load as a suite. The file is -- interpreted in the same manner as require "modname". -- Which functions are tests is determined by is_test_key(name). function suite(modname) local ok, err = pcall( function() local mod, r_err = require(modname) suites[modname] = get_tests(mod) end) if not ok then print(fmt(" * Error loading test suite %q:\n%s", modname, tostring(err))) failed_suites[#failed_suites+1] = modname end end -- ########### -- # Running # -- ########### local ok_types = { pass=true, fail=true, skip=true } local function err_handler(name) return function (e) if e and e.type and ok_types[e.type()] then return e end local msg = fmt("ERROR in %s():\n\t%s", name, tostring(e)) msg = debug.traceback(msg, 3) return Error { msg=msg } end end local function run_test(name, test, suite, hooks, setup, teardown) local result if is_func(hooks.pre_test) then hooks.pre_test(name) end local t_pre, t_post, elapsed --timestamps. requires luasocket. if now then t_pre = now() end local ok, err = xpcall( function() if is_func(setup) then setup(name) end test() end, err_handler(name)) if now then t_post = now() end if t_pre and t_post then elapsed = t_post - t_pre end if ok and is_func(teardown) then ok, err = xpcall(function() teardown(name, elapsed) end, err_handler(name)) end if ok then err = Pass() end result = err if elapsed then result.elapsed = elapsed end -- TODO: log tests w/ no assertions? result:add(suite, name) if is_func(hooks.post_test) then hooks.post_test(name, result) end end local function cmd_line_switches(arg) local opts = {} for i=1,#arg do local v = arg[i] if v == "-v" or v == "--verbose" then opts.verbose=true elseif v == "-s" or v == "--suite" then opts.suite_pat = arg[i+1] elseif v == "-t" or v == "--test" then opts.test_pat = arg[i+1] end end return opts end local function failures_or_errors(r) if next(r.err) then return true end for k,f in pairs(r.fail) do if not f.no_exit then return true end end end local function run_suite(hooks, opts, results, suite_filter, sname, tests) local ssetup, steardown = tests.ssetup, tests.steardown tests.ssetup, tests.steardown = nil, nil if not suite_filter or sname:match(suite_filter) then local run_suite = true local res = result_table(sname) if ssetup then local ok, err = pcall(ssetup) if not ok or (ok and err == false) then run_suite = false local msg = fmt("Error in %s's suite_setup: %s", sname, tostring(err)) failed_suites[#failed_suites+1] = sname results.err[sname] = Error{msg=msg} end end if run_suite and count(tests) > 0 then local setup, teardown = tests.setup, tests.teardown tests.setup, tests.teardown = nil, nil if hooks.begin_suite then hooks.begin_suite(res, tests) end res.tests = tests for name, test in pairs(tests) do if not opts.test_pat or name:match(opts.test_pat) then run_test(name, test, res, hooks, setup, teardown) end end if steardown then pcall(steardown) end if hooks.end_suite then hooks.end_suite(res) end combine_results(results, res) end end end ---Run all known test suites, with given configuration hooks. -- @param hooks Override the default hooks. -- @param suite_filter If set, only run suite(s) with names -- matching this pattern. -- @usage If no hooks are provided and arg[1] == "-v", the -- verbose_hooks will be used. function run(hooks, suite_filter) -- also check the namespace it's run in local opts = cmd_line_switches(lt_arg) if hooks == true or (hooks == nil and opts.verbose) then hooks = verbose_hooks else hooks = hooks or {} end setmetatable(hooks, {__index = default_hooks}) local results = result_table("main") if now then results.t_pre = now() end -- If it's all in one test file, check its environment, too. local env = getenv(1) if env then suites.main = get_tests(env) end if hooks.begin then hooks.begin(results, suites) end local suite_filter = opts.suite_pat or suite_filter for sname,suite in pairs(suites) do run_suite(hooks, opts, results, suite_filter, sname, suite) end if now then results.t_post = now() end if hooks.done then hooks.done(results) end if failures_or_errors(results) or #failed_suites > 0 then os.exit(1) end end -- ######################## -- # Randomization basics # -- ######################## local _r if random then _r = random.new() end ---Set random seed. function set_seed(s) _r:seed(s) end ---Get a random value low <= x <= high. function random_int(low, high) if not high then high = low; low = 0 end return _r:value(low, high) end ---Get a random bool. function random_bool() return random_int(0, 1) == 1 end ---Get a random float low <= x < high. function random_float(low, high) return random_int(low, high - 1) + _r:value() end if not random then set_seed = math.randomseed random_bool = function() return math.random(0, 1) == 1 end random_float = function(l, h) return random_int(l, h - 1) + math.random() end random_int = function(l, h) if not h then h = l; l = 0 end return math.random(l, h) end end -- Lua_number's bits of precision. IEEE 754 doubles have 52. local function determine_accuracy() for i=1,128 do if 2^i == (2^i + 1) then return i - 1 end end return 128 --long long ints? end local bits_of_accuracy = determine_accuracy() -- ################## -- # Random strings # -- ################## -- For valid char classes, see Lua Reference Manual 5.1, p. 77 -- or http://www.lua.org/manual/5.1/manual.html#5.4.1 . local function charclass(pat) local m = {} local match, char = string.match, string.char for i=0,255 do local c = char(i) if match(c, pat) then m[#m+1] = c end end return table.concat(m) end -- Return a (() -> random char) iterator from a pattern. local function parse_pattern(pattern) local cs = {} --charset local idx = 1 local len = string.len(pattern) assert(len > 0, "Cannot generate pattern from empty string.") local function at_either_end() return #cs == 0 or #cs == len end local function slice(i) return string.sub(pattern, i, i) end while idx <= len do local c = slice(idx) if c == "-" then if at_either_end() then cs[#cs+1] = c --literal - at start or end else --range local low = string.byte(slice(idx-1)) + 1 local high = string.byte(slice(idx+1)) assert(low < high, "Invalid character range: " .. pattern) for asc=low,high do cs[#cs+1] = string.char(asc) end idx = idx + 1 end elseif c == "%" then local nextc = slice(idx + 1) cs[#cs+1] = charclass("%" .. nextc) idx = idx + 1 else cs[#cs+1] = c end idx = idx + 1 end cs = table.concat(cs) local len = string.len(cs) assert(len > 0, "Empty charset") return function() local idx = random_int(1, len) return string.sub(cs, idx, idx) end end -- Read a random string spec, return a config table. local function parse_randstring(s) local low, high, rest = string.match(s, "([0-9]+),?([0-9]*) (.*)") if low then --any match if high == "" then high = low end return { low = tonumber(low), high = tonumber(high), gen = parse_pattern(rest) } else local err = "Invalid random string spec: " .. s error(err, 2) end end -- Generate a random string. -- @usage e.g. "20 listoftwentycharstogenerate" or "10,20 %l". function random_string(spec) local info = parse_randstring(spec) local ct, diff diff = info.high - info.low if diff == 0 then ct = info.low else ct = random_int(diff) + info.low end local acc = {} for i=1,ct do acc[i] = info.gen(self) end local res = table.concat(acc) assert(res:len() == ct, "Bad string gen") return res end -- ######################### -- # General random values # -- ######################### -- Generate a random number, according to arg. local function gen_number(arg) arg = arg or math.huge local signed = (arg < 0) local float if signed then float = (math.ceil(arg) ~= arg) else float = (math.floor(arg) ~= arg) end local f = float and random_float or random_int if signed then return f(arg, -arg) else return f(0, arg) end end -- Create an arbitrary instance of a value. local function generate_arbitrary(arg) local t = type(arg) if t == "number" then return gen_number(arg) elseif t == "function" then return arg(gen_number()) -- assume f(number) -> val elseif t == "string" then return random_string(arg) elseif t == "table" or t == "userdata" then assert(arg.__random, t .. " has no __random method") -- assume arg.__random(number) -> val return arg.__random(gen_number()) elseif t == "boolean" then return random_bool() else error("Cannot randomly generate values of type " .. t .. ".") end end local random_test_defaults = { count = 100, max_failures = 10, max_errors = 5, max_skips = 50, random_bound = 2^bits_of_accuracy, seed_limit = math.min(1e13, 2^bits_of_accuracy), always = {}, seed = nil, show_progress = true } local function random_args(args) local as = {} for i=1,#args do as[i] = generate_arbitrary(args[i]) end return as end local function new_seed(limit) limit = limit or 1e13 return random_int(0, limit) end local function get_seeds_and_args(t) local ss = {} for _,r in ipairs(t) do if r.seed then ss[#ss+1] = fmt("%s %s\n Seed: %s", r.reason or "", r.msg and ("\n " .. r.msg) or "", r.seed) end if r.args then for i,arg in ipairs(r.args) do ss[#ss+1] = " * " .. arg end end ss[#ss+1] = "" end return ss end local function run_randtest(seed, f, args, r, limit) local try_ct = 0 while r.tried[seed] and try_ct < 50 do seed = new_seed(limit) try_ct = try_ct + 1 end if try_ct >= 50 then error(Fail { reason = "Exhausted all seeds" }) end set_seed(seed) r.tried[seed] = true local result local r_args = random_args(args) local ok, err = pcall(function() f(unpack(r_args)) end) if ok then result = Pass() result.seed = seed r.ps[#r.ps+1] = result else -- So errors in the suite itself get through... if type(err) == "string" then error(err) end result = err result.seed = seed local rt = result:type() if rt == "pass" then r.ps[#r.ps+1] = result elseif rt == "fail" then r.fs[#r.fs+1] = result elseif rt == "error" then r.es[#r.es+1] = result elseif rt == "skip" then r.ss[#r.ss+1] = result else error("unmatched") end end seed = new_seed(limit) r.ts = r.ts + 1 local str_args = {} -- Convert args to strs (for display) and add to result. for i,v in ipairs(r_args) do str_args[i] = tostring(v) end result.args = str_args return seed end local function report_trial(r, opt) if #r.es > 0 then local seeds = get_seeds_and_args(r.es) error(Fail { reason = fmt("%d tests, %d error(s).\n %s", r.ts, #r.es, table.concat(seeds, "\n ")), seeds = seeds}) elseif #r.fs > 0 then local seeds = get_seeds_and_args(r.fs) error(Fail { reason = fmt("%d tests, %d failure(s).\n %s", r.ts, #r.fs, table.concat(seeds, "\n ")), seeds = seeds}) elseif #r.ss >= opt.max_skips then error(Fail { reason = fmt("Too many cases skipped.")}) else error(Pass { reason = fmt(": %d cases passed.", #r.ps) }) end end local function assert_random(opt, f, ...) local args = { ... } if type(opt) == "string" then opt = { name=opt } elseif type(opt) == "function" then table.insert(args, 1, f) f = opt opt = {} end setmetatable(opt, { __index=random_test_defaults }) local seed = opt.seed or os.time() local r = { ps={}, fs={}, es={}, ss={}, ts=0, tried={} } -- Run these seeds every time, for easy regression testing. for _,s in ipairs(opt.always) do run_randtest(s, f, args, r, opt.seed_limit) end set_seed(seed) local tick = opt.tick or opt.count / 10 for i=1,opt.count do seed = run_randtest(seed, f, args, r, opt.seed_limit) if #r.ss >= opt.max_skips or #r.fs >= opt.max_failures or #r.es >= opt.max_errors then break end if opt.show_progress and i % tick == 0 then dot(".") io.stdout:flush() end end local overall_status = (passed == count and "PASS" or "FAIL") report_trial(r, opt) end -- Put it in the same namespace as the other assert_ functions. _importing_env.assert_random = assert_random poelzi-ulatencyd-55515a9/tests/memleak.c000066400000000000000000000072551154664534000202240ustar00rootroot00000000000000/* Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. */ #include #include #include #include #include #include #define MIN(a, b) (((a) < (b)) ? (a) : (b)) int main (argc, argv) int argc; char **argv; { int c = 0; int j, i = 0; int stress = 0; int chunk = 1024; int nums = 10; int delay = 50000; char *tmp; while (1) { int option_index = 0; static struct option long_options[] = { {"chunk", 1, 0, 'c'}, {"nums", 1, 0, 'n'}, {"delay", 1, 0, 'd'}, {"stress", 0, 0, 's'}, {"help", 0, 0, 'h'}, {0, 0, 0, 0} }; c = getopt_long (argc, argv, "n:c:hd:", long_options, &option_index); if (c == -1) break; switch (c) { case 0: printf ("option %s", long_options[option_index].name); if (optarg) printf (" with arg %s", optarg); printf ("\n"); break; case 'h': printf ("memleak:\n"); printf ("-c size size of chunk in kb\n"); printf ("-n num number of memory chunks\n"); printf ("-d delay delay in usecs between alloc \n"); printf ("-s stress test allocated memory \n"); exit(0); break; case 'b': printf ("option b\n"); break; case 'c': //printf ("option c with value '%s'\n", optarg); chunk = atoi(optarg); break; case 'n': nums = atoi(optarg); break; case 'd': delay = atoi(optarg); break; case 's': stress = 1; break; case '?': break; default: printf ("?? getopt returned character code 0%o ??\n", c); } } if (optind < argc) { printf ("non-option ARGV-elements: "); while (optind < argc) printf ("%s ", argv[optind++]); printf ("\n"); } printf( "!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!\n" "!!!!! THIS PROGRAM WILL LIKELY KILL YOUR COMPUTER !!!!!\n" "!!!!! ulatency may rescue you ;-) !!!!!\n" "!!!!! press ctrl+c to stop !!!!!\n" "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" ); sleep(3); void **ptr = malloc(sizeof(void *) * nums); void *ttmp; char cmp[10] = "iutare29i3"; printf ("malloc %d chunks of %d kb (delay %d us):\n", nums, chunk, delay); while(1) { if(i < nums) { tmp = malloc(chunk*1024); ptr[i] = memset(tmp, 0, chunk*1024); printf("."); fflush(stdout); i++; } if(stress) { for(j = 0; i/3 < j; j++) { ttmp = ptr[rand()%i]; if(rand()%6 == 1) memset(ttmp + (rand()%chunk) - sizeof(int) , rand(), sizeof(int)); else memcmp(ttmp + (rand()%chunk) - MIN(10, chunk), cmp, MIN(10, chunk)); } } usleep(delay); } exit (0); } poelzi-ulatencyd-55515a9/tests/misc.lua000066400000000000000000000060671154664534000201030ustar00rootroot00000000000000module(..., package.seeall) require("posix") test_active_done = false function test_active() TEST_PIDS = {23, 43, 53, 1231, 23, 743, 235, 23} RV = {} RV[1] = {{pid=23}} RV[2] = {{pid=43} , {pid=23}} RV[3] = {{pid=53} , {pid=43} , {pid=23}} RV[4] = {{pid=1231}, {pid=53} , {pid=43} , {pid=23}} RV[5] = {{pid=23} , {pid=1231}, {pid=53} , {pid=43}} RV[6] = {{pid=743} , {pid=23} , {pid=1231}, {pid=53} , {pid=43}} RV[7] = {{pid=235} , {pid=743} , {pid=23} , {pid=1231}, {pid=53}} RV[8] = {{pid=23} , {pid=235} , {pid=743} , {pid=1231}, {pid=53}} TEST_I = 1 function add_active() print("test active list ["..tostring(TEST_I).."/8]") ulatency.set_active_pid(1, TEST_PIDS[TEST_I]) assert_cmp_table(RV[TEST_I], ulatency.get_active_pids(1), nil, {last_change=true}) TEST_I = TEST_I + 1 if TEST_I > #TEST_PIDS then test_active_done = true return false end return true end ulatency.add_timeout(add_active, 1000) end function test_sysflags() flag = ulatency.new_flag{name="hello"} ulatency.add_flag(flag) assert_len(1, ulatency.list_flags(), "len of system flags not right") flag2 = ulatency.new_flag{name="hello2", reason="2"} ulatency.add_flag(flag) assert_len(1, ulatency.list_flags(), "len of system flags not right") ulatency.add_flag(flag2) assert_len(2, ulatency.list_flags(), "len of system flags not right") ulatency.del_flag(flag2) assert_len(1, ulatency.list_flags(), "len of system flags not right") ulatency.clear_flag_source() assert_len(0, ulatency.list_flags(), "len of system flags not right") ulatency.add_flag(flag) ulatency.clear_flag_name("hello") assert_len(0, ulatency.list_flags(), "len of system flags not right") end function test_cgroups_list() if posix.access("/proc/cgroups") == 0 then grps = ulatency.get_cgroup_subsystems() assert_true(#grps > 0, "cgroups subsystems empty") end end function test_find_flags() flag = ulatency.new_flag{name="hello"} flag2 = ulatency.new_flag{name="hello2"} flag3 = ulatency.new_flag{name="bla", value=3} flag4 = ulatency.new_flag{name="bla", threshold=4} all = {flag4, flag3, flag2, flag} assert_equal(nil, ulatency.find_flag(all, {name = "user.poison.session"}), "user.poision not in list") assert_equal(flag, ulatency.find_flag(all, {name = "hello"}), "hello in list") assert_equal(flag2, ulatency.find_flag(all, {name = "hello2"}), "hello in list") assert_equal(nil, ulatency.find_flag(all, {name = "hello2", value = 32}), "hello2 value not in list") assert_equal(flag3, ulatency.find_flag(all, {name = "bla", value = 3}), "bla value in list") assert_equal(nil, ulatency.find_flag(all, {name = "bla", value = 4}), "bla value 4 not in list") assert_equal(flag4, ulatency.find_flag(all, {name = "bla", threshold = 4}), "bla value 4 in list") end function test_find_flags() assert_true(ulatency.get_sysctl("kernel.version"), "kernel.version not existing") assert_false(ulatency.set_sysctl("kernel.version", "bla"), "kernel.version should not be writeable") end function test_done() return test_active_done end poelzi-ulatencyd-55515a9/tests/processes.lua000066400000000000000000000075531154664534000211570ustar00rootroot00000000000000module(..., package.seeall) function test_list_pids() pids = ulatency.list_pids() assert_true(#pids > 10, "very unlikely that less then 10 processes exist") for k,v in pairs(pids) do assert_number(k, "key not number") assert_number(v, "pid not number") assert_true(v > 0, "pid not > 0") end end function test_get_pid() pid = ulatency.get_pid(1) assert_equal(1, pid.pid) assert_equal(true, pid.is_valid) assert_true(pid:get_n_children() > 1, "init should have more then 1 children") local childs = pid:get_children() assert_equal(pid:get_n_children(), #childs, "size of get_n_children differs") for k,v in pairs(childs) do assert_u_proc(v) end end function test_flag_inherence() pid = ulatency.get_pid(1) local flag_in = ulatency.new_flag{name = "inher", inherit = true} local flag_non = ulatency.new_flag{name = "loc", inherit = false} pid:add_flag(flag_in) pid:add_flag(flag_non) local childs = pid:get_children() assert_cmp_table({flag_non, flag_in}, pid:list_flags()) local exp_child = {flag_in} for k,v in pairs(childs) do assert_u_proc(v) assert_cmp_table(exp_child, v:list_flags(true)) end end function test_list_processes() procs = ulatency.list_processes() assert_true(#procs > 10, "very unlikely that less then 10 processes exist") for k,v in pairs(procs) do assert_userdata(v, "Not a userdata") assert_u_proc(v) -- FIXME --assert_metatable(ulatency, v, "Not metatable U_PROC") assert_number(k, "key not a number") -- except for the root process very one must have a parent local parent = v:get_parent() --print("pid", v.pid) --print("parent:", parent) if(v.ppid == 0) then assert_nil(v:get_parent(), "Parent of pid " .. tostring(v.pid) .. " is not nil") else assert_u_proc(parent) end end end function test_new_flag() local flag = ulatency.new_flag("test") assert_u_flag(flag) assert_equal("test", flag.name) flag.name = "blubb" assert_equal("blubb", flag.name) assert_equal(false, flag.inherit, "inherit") assert_equal(0, flag.priority, "priority") assert_equal(0, flag.timeout, "timeout") assert_equal(nil, flag.reason, "reason") assert_equal(0, flag.value, "value") assert_equal(0, flag.threshold, "threshold") flag.reason = "bla" assert_equal("bla", flag.reason, "reason test") flag.value = -21823 assert_equal(-21823, flag.value) local pid = ulatency.get_pid(1) pid:add_flag(flag) pid:add_flag(flag) pid:add_flag(flag) pprint(pid:list_flags()) assert_len(1, pid:list_flags(), "to much flags on proc") local flag2 = ulatency.new_flag("haha") pid:add_flag(flag2) pprint(pid:list_flags()) assert_len(2, pid:list_flags(), "to much flags on proc") pid:del_flag(flag2) pprint(pid:list_flags()) assert_len(1, pid:list_flags(), "to much flags on proc") flag2.name = "haha" pid:add_flag(flag2) pid:clear_flag_name("haha") assert_len(1, pid:list_flags(), "to much flags on proc") pid:clear_flag_source() assert_len(0, pid:list_flags(), "source clear failed") pid:add_flag(flag2) pid:add_flag(flag) pid:clear_flag_all() assert_len(0, pid:list_flags(), "all clear failed") -- test with table init local flag3 = ulatency.new_flag{name = "huhu", inherit = 1, priority = 64, value = 23, threshold = 443, timeout = 55, reason = 2, nonsense = 22345} assert_equal("huhu", flag3.name, "name") assert_equal(true, flag3.inherit) assert_equal(64, flag3.priority) assert_equal(55, flag3.timeout) assert_equal("2", flag3.reason) assert_equal(23, flag3.value) assert_equal(443, flag3.threshold) assert_equal(nil, flag3.nonsense) end poelzi-ulatencyd-55515a9/tests/test.lua000066400000000000000000000052651154664534000201260ustar00rootroot00000000000000-- adjust package.path package.path = package.path .. ";tests/?.lua" function to_string(data, indent) local str = "" if(indent == nil) then indent = 0 end -- Check the type if(type(data) == "string") then str = str .. (" "):rep(indent) .. data .. "\n" elseif(type(data) == "number") then str = str .. (" "):rep(indent) .. data .. "\n" elseif(type(data) == "boolean") then if(data == true) then str = str .. "true" else str = str .. "false" end elseif(type(data) == "table") then local i, v for i, v in pairs(data) do -- Check for a table in a table if(type(v) == "table") then str = str .. (" "):rep(indent) .. i .. ":\n" str = str .. to_string(v, indent + 2) else str = str .. (" "):rep(indent) .. i .. ": " .. to_string(v, 0) end end else return tostring(data).."\n" end return str end function pprint(data) print(to_string(data)) end U_PROC_META = debug.getregistry()["U_PROC_META"] U_FLAG_META = debug.getregistry()["U_FLAG_META"] function assert_u_proc(data) return assert_userdata(data, "Not a u_proc object") or assert_metatable(U_PROC_META, data, "Not a u_proc object") end function assert_u_flag(data) return assert_userdata(data, "Not a u_flag object") or assert_metatable(U_FLAG_META, data, "Not a u_flag object") end function assert_cmp_table(exp, val, err, ign) for k, v in pairs(exp) do if not ign or not ign.k then if type(v) == "table" then assert_cmp_table(v, val[k], "sub value in table differ", ign) else assert_equal(v, val[k], err or "value in table differs") end end end end arg = {} require('tests.lunatest') suites = {"processes", "misc"} loaded_suites = lunatest.get_suites() current_id = 0 current_suite = suites[current_id] function run_suite(bla) while true do if loaded_suites[current_suite] then if loaded_suites[current_suite].test_done then if not loaded_suites[current_suite].test_done() then return true end end end current_id = current_id + 1 current_suite = suites[current_id] if not current_suite then print("all lua tests done") ulatency.quit_daemon(0) return false end lunatest.suite(current_suite) loaded_suites = lunatest.get_suites() lunatest.run(nil, current_suite) if loaded_suites[current_suite].test_done then return true end end end ulatency.add_timeout(run_suite, 500) --ulatency.quit_daemon() ulatency.add_timeout(ulatency.quit_daemon, 100000) poelzi-ulatencyd-55515a9/tests/test_xwatch.c000066400000000000000000000065621154664534000211460ustar00rootroot00000000000000/* Copyright 2010,2011 ulatencyd developers This file is part of ulatencyd. ulatencyd is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ulatencyd is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ulatencyd. If not, see http://www.gnu.org/licenses/. */ #define G_LOG_DOMAIN "test_xwatch" #include "config.h" #include "ulatency.h" #define TEST_XWATCH #include "../modules/xwatch.c" DBusGConnection *U_dbus_connection; int main () { /* Open the connection to the X server. Use the DISPLAY environment variable */ struct x_server *xs; GList *cur; g_type_init (); GError *error = NULL; U_dbus_connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error); if (U_dbus_connection == NULL) { g_warning("Failed to open connection to bus: %s\n", error->message); g_error_free (error); } xwatch_init(); add_all_console_kit(FALSE); add_all_console_kit(TRUE); add_all_console_kit(FALSE); /* xs = add_connection(1000, ":0"); printf("xauth %p\n", xs->auth); printf("localhost: %s\n", localhost); printf ("\n"); printf ("Informations of screen %ld:\n", xs->screen->root); printf (" width.........: %d\n", xs->screen->width_in_pixels); printf (" height........: %d\n", xs->screen->height_in_pixels); printf (" white pixel...: %ld\n", xs->screen->white_pixel); printf (" black pixel...: %ld\n", xs->screen->black_pixel); printf ("\n"); printf ("current pid: %d\n", read_pid(xs)); printf ("current pid: %d\n", read_pid(xs)); */ while(TRUE) { add_all_console_kit(TRUE); cur = server_list; printf("### run ### %d\n", g_list_length(server_list)); while(cur) { printf ("current pid: %d\n", read_pid((struct x_server *)cur->data)); cur = g_list_next(cur); } sleep(1); } /* xcb_property_handlers_init (handle_prop, xcb_event_handlers_t * evenths ) */ /* cookie = xcb_get_any_property (connection, 0, screen->root, "_NET_ACTIVE_WINDOW", 100 ); prop = xcb_get_window_attributes_reply (connection, cookie, &error); */ /* cookie2 = xcb_get_text_property (connection, screen->root, "_NET_ACTIVE_WINDOW" ); */ //xcb_event_handlers_init(connection, &doevent); //xcb_event_set_handler(&doevent, FOCUS_IN, handle_event, NULL); // static inline void xcb_event_set_##lkind##_handler(xcb_event_handlers_t *evenths, int (*handler)(void *, xcb_connection_t *, xcb_##lkind##_event_t *), void *data) //xcb_event_set_focus_in_handler(&doevent, handle_event, NULL); // xcb_event_handlers_t *evenths, int (*handler)(void *, xcb_connection_t *, xcb_##lkind##_event_t *), void *data //xcb_event_wait_for_event_loop(&doevent); /* while(event = xcb_wait_for_event (connection)) { handle_event(event); } */ return 0; }