hindsight-0.12.7/000077500000000000000000000000001301315165600135635ustar00rootroot00000000000000hindsight-0.12.7/.gitignore000066400000000000000000000000111301315165600155430ustar00rootroot00000000000000release/ hindsight-0.12.7/CMakeLists.txt000066400000000000000000000034131301315165600163240ustar00rootroot00000000000000# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. cmake_minimum_required(VERSION 3.0 FATAL_ERROR) project(hindsight VERSION 0.12.7 LANGUAGES C) set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Hindsight") set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) set(CPACK_PACKAGE_VENDOR "Mozilla Services") set(CPACK_PACKAGE_CONTACT "Mike Trinkala ") set(CPACK_STRIP_FILES TRUE) set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") set(CPACK_RPM_PACKAGE_LICENSE "MPLv2.0") set(CPACK_DEBIAN_PACKAGE_DEPENDS "luasandbox (>= 1.1)") set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") include(mozsvc) find_package(luasandbox 1.2.0 REQUIRED CONFIG) include(GNUInstallDirs) if(CMAKE_HOST_UNIX) find_library(PTHREAD_LIBRARY pthread) find_library(LIBDL_LIBRARY dl) find_library(LIBM_LIBRARY m) find_library(LIBRT_LIBRARY rt) set(UNIX_LIBRARIES ${PTHREAD_LIBRARY} ${LIBDL_LIBRARY} ${LIBM_LIBRARY} ${LIBRT_LIBRARY}) add_definitions(-D_POSIX_C_SOURCE=199309L -D_XOPEN_SOURCE=600) endif() set(INSTALL_SANDBOX_PATH ${CMAKE_INSTALL_DATAROOTDIR}/luasandbox/sandboxes) set(DPERMISSION DIRECTORY_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) set(EMPTY_DIR ${CMAKE_BINARY_DIR}/empty) file(MAKE_DIRECTORY ${EMPTY_DIR}) install(DIRECTORY ${EMPTY_DIR}/ DESTINATION ${INSTALL_SANDBOX_PATH} ${DPERMISSION}) install(DIRECTORY ${CMAKE_SOURCE_DIR}/sandboxes/ DESTINATION ${INSTALL_SANDBOX_PATH}/heka ${DPERMISSION}) add_subdirectory(src) hindsight-0.12.7/LICENSE000066400000000000000000000413151301315165600145740ustar00rootroot00000000000000Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. hindsight-0.12.7/README.md000066400000000000000000000021701301315165600150420ustar00rootroot00000000000000# Hindsight ## Overview Hindsight is a C based data processing infrastructure based on the [lua sandbox] (https://github.com/mozilla-services/lua_sandbox) project. I have received several inquiries about a lighter weight and faster data pipeline with delivery guarantees to replace [Heka](https://github.com/mozilla-services/heka). Hindsight is that light weight skeleton around the same lua sandbox offering 'at least once' delivery semantics. * [Full Documentation](docs/index.md) * Support * IRC: [#hindsight on irc.mozilla.org](irc://irc.mozilla.org/hindsight) * Mailing list: https://mail.mozilla.org/listinfo/hindsight ## Build ### Prerequisites * Clang 3.1 or GCC 4.7+ * CMake (3.0+) - http://cmake.org/cmake/resources/software.html * lua_sandbox (1.1+) - https://github.com/mozilla-services/lua_sandbox ### CMake Build Instructions git clone https://github.com/mozilla-services/hindsight.git cd hindsight mkdir release cd release # Linux cmake -DCMAKE_BUILD_TYPE=release .. make ctest cpack -G TGZ # (DEB|RPM|ZIP) # Cross platform support is planned but not supported yet hindsight-0.12.7/benchmarks/000077500000000000000000000000001301315165600157005ustar00rootroot00000000000000hindsight-0.12.7/benchmarks/analysis.toml000066400000000000000000000007471301315165600204300ustar00rootroot00000000000000[hekad] maxprocs = 1 base_dir = "." share_dir = "." [FxaAuthWebserverBin] type = "LogstreamerInput" log_directory = "/work/git/hindsight/release/output" file_match = '0\.log' decoder = "ProtobufDecoder" #synchronous_decode = true [Counter] type = "SandboxFilter" filename = "analysis_run/counter.lua" message_matcher = "TRUE" #[FileOutput] #message_matcher = "TRUE" #path = "heka_analysis_out.log" #perm = "666" #flush_count = 100 #flush_operator = "OR" #encoder = "ProtobufEncoder" hindsight-0.12.7/benchmarks/hsr.cfg000066400000000000000000000022021301315165600171510ustar00rootroot00000000000000output_path = "output_hsr" output_size = 1024 * 1024 * 1024 sandbox_load_path = "" -- disable dynamic loading sandbox_run_path = "run_hsr" analysis_threads = 1 analysis_lua_path = "/usr/lib/luasandbox/modules/?.lua;/usr/lib64/luasandbox/modules/?.lua" analysis_lua_cpath = "/usr/lib/luasandbox/modules/?.so;/usr/lib64/luasandbox/modules/?.so" io_lua_path = analysis_lua_path .. ";/usr/lib/luasandbox/io_modules/?.lua;/usr/lib64/luasandbox/io_modules/?.lua" io_lua_cpath = analysis_lua_cpath .. ";/usr/lib/luasandbox/io_modules/?.so;/usr/lib64/luasandbox/io_modules/?.so" max_message_size = 128000 analysis_defaults = { output_limit = 1024 * 64, -- default memory_limit = 1024 * 1024 * 8, -- default instruction_limit = 1000000, -- default preserve_data = false, -- default ticker_interval = 60, } input_defaults = { output_limit = 1024 * 1024 * 8, instruction_limit = 0, } output_defaults = { output_limit = 1024 * 1024 * 8, ticker_interval = 60, } hindsight-0.12.7/benchmarks/multiple.cfg000066400000000000000000000021541301315165600202160ustar00rootroot00000000000000output_path = "output_multiple" output_size = 1024 * 1024 * 1024 sandbox_load_path = "" -- disable dynamic loading sandbox_run_path = "run_multiple" analysis_threads = 1 analysis_lua_path = "/usr/lib/luasandbox/modules/?.lua;/usr/lib64/luasandbox/modules/?.lua" analysis_lua_cpath = "/usr/lib/luasandbox/modules/?.so;/usr/lib64/luasandbox/modules/?.so" io_lua_path = analysis_lua_path .. ";/usr/lib/luasandbox/io_modules/?.lua;/usr/lib64/luasandbox/io_modules/?.lua" io_lua_cpath = analysis_lua_cpath .. ";/usr/lib/luasandbox/io_modules/?.so;/usr/lib64/luasandbox/io_modules/?.so" analysis_defaults = { output_limit = 1024 * 64, -- default memory_limit = 1024 * 1024 * 8, -- default instruction_limit = 1000000, -- default preserve_data = false, -- default ticker_interval = 60, } input_defaults = { output_limit = 1024 * 1024 * 8, instruction_limit = 0, } output_defaults = { output_limit = 1024 * 1024 * 8, ticker_interval = 60, } hindsight-0.12.7/benchmarks/multiple.toml000066400000000000000000000016241301315165600204330ustar00rootroot00000000000000[hekad] maxprocs = 4 base_dir = "." share_dir = "." [FxaAuthWebserver] type = "LogstreamerInput" log_directory = "/work/git/misc-heka/benchmark" file_match = 'access_msec\.log' decoder = "FxaAuthDecoder" [FxaAuthWebserver1] type = "LogstreamerInput" log_directory = "/work/git/misc-heka/benchmark" file_match = 'access_msec1\.log' decoder = "FxaAuthDecoder" [FxaAuthWebserver2] type = "LogstreamerInput" log_directory = "/work/git/misc-heka/benchmark" file_match = 'access_msec2\.log' decoder = "FxaAuthDecoder" [FxaAuthDecoder] type = "SandboxDecoder" script_type = "lua" filename = "lua_decoders/nginx_access.lua" [FxaAuthDecoder.config] log_format = '$remote_addr $msec "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"' [FileOutput] message_matcher = "TRUE" path = "hekaout.log" perm = "666" flush_count = 100 flush_operator = "OR" encoder = "ProtobufEncoder" hindsight-0.12.7/benchmarks/run/000077500000000000000000000000001301315165600165045ustar00rootroot00000000000000hindsight-0.12.7/benchmarks/run/analysis/000077500000000000000000000000001301315165600203275ustar00rootroot00000000000000hindsight-0.12.7/benchmarks/run/analysis/counter.cfg000066400000000000000000000001261301315165600224660ustar00rootroot00000000000000filename = "counter.lua" message_matcher = "TRUE" ticker_interval = 5 thread = 0 hindsight-0.12.7/benchmarks/run/analysis/counter.lua000066400000000000000000000005341301315165600225130ustar00rootroot00000000000000-- This Source Code Form is subject to the terms of the Mozilla Public -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at http://mozilla.org/MPL/2.0/. local cnt = 0 function process_message() cnt = cnt + 1 return 0 end function timer_event() inject_payload("txt", "count", cnt) end hindsight-0.12.7/benchmarks/run/input/000077500000000000000000000000001301315165600176435ustar00rootroot00000000000000hindsight-0.12.7/benchmarks/run/input/input_test.cfg000066400000000000000000000001461301315165600225230ustar00rootroot00000000000000filename = "input_test.lua" instruction_limit = 0 input_file = "access_msec.log" preserve_data = true hindsight-0.12.7/benchmarks/run/input/input_test.lua000066400000000000000000000021031301315165600225400ustar00rootroot00000000000000-- This Source Code Form is subject to the terms of the Mozilla Public -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at http://mozilla.org/MPL/2.0/. require "io" local clf = require "lpeg.common_log_format" local msg = { Timestamp = nil, Type = "logfile", Hostname = "trink-x230", Logger = "FxaAuthWebserver", Payload = nil, Fields = nil } local grammar = clf.build_nginx_grammar('$remote_addr $msec "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"' ) local cnt = 0; local fn = read_config("input_file") function process_message(offset) local fh = assert(io.open(fn, "rb")) if offset then fh:seek("set", offset) end for line in fh:lines() do local fields = grammar:match(line) if fields then msg.Timestamp = fields.time fields.time = nil msg.Fields = fields inject_message(msg, fh:seek()) cnt = cnt + 1 end end fh:close() return 0, tostring(cnt) end hindsight-0.12.7/benchmarks/run/output/000077500000000000000000000000001301315165600200445ustar00rootroot00000000000000hindsight-0.12.7/benchmarks/run/output/counter.cfg000066400000000000000000000000641301315165600222040ustar00rootroot00000000000000filename = "counter.lua" message_matcher = "TRUE" hindsight-0.12.7/benchmarks/run/output/counter.lua000066400000000000000000000005031301315165600222240ustar00rootroot00000000000000-- This Source Code Form is subject to the terms of the Mozilla Public -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at http://mozilla.org/MPL/2.0/. local cnt = 0 function process_message() cnt = cnt + 1 return 0 end function timer_event() print(cnt) end hindsight-0.12.7/benchmarks/run_hsr/000077500000000000000000000000001301315165600173605ustar00rootroot00000000000000hindsight-0.12.7/benchmarks/run_hsr/analysis/000077500000000000000000000000001301315165600212035ustar00rootroot00000000000000hindsight-0.12.7/benchmarks/run_hsr/analysis/empty.txt000066400000000000000000000000001301315165600230700ustar00rootroot00000000000000hindsight-0.12.7/benchmarks/run_hsr/input/000077500000000000000000000000001301315165600205175ustar00rootroot00000000000000hindsight-0.12.7/benchmarks/run_hsr/input/readmsg.cfg000066400000000000000000000001551301315165600226230ustar00rootroot00000000000000filename = "readmsg.lua" instruction_limit = 0 input_file = "/work/git/hindsight/release/output/input/0.log" hindsight-0.12.7/benchmarks/run_hsr/input/readmsg.lua000066400000000000000000000014651301315165600226520ustar00rootroot00000000000000-- This Source Code Form is subject to the terms of the Mozilla Public -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at http://mozilla.org/MPL/2.0/. require "io" require "string" local fn = read_config("input_file") local hsr = create_stream_reader(fn) local cnt = 0 function process_message(offset) local fh = assert(io.open(fn, "rb")) if offset then fh:seek("set", offset) else offset = 0 end local found, read, consumed repeat repeat found, consumed, read = hsr:find_message(fh) offset = offset + consumed if found then cnt = cnt + 1 inject_message(hsr, offset) end until not found until read == 0 fh:close() return 0, tostring(cnt) end hindsight-0.12.7/benchmarks/run_hsr/input/tcpinput.lua000066400000000000000000000045301301315165600230720ustar00rootroot00000000000000-- This Source Code Form is subject to the terms of the Mozilla Public -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at http://mozilla.org/MPL/2.0/. require "coroutine" local socket = require "socket" require "string" require "table" local address = read_config("address") or "127.0.0.1" local port = read_config("port") or 5565 local server = assert(socket.bind(address, port)) server:settimeout(0) local threads = {} local sockets = {server} local function handle_client(client) local found, consumed, need = false, 0, 8192 * 4 local caddr, cport = client:getpeername() if not caddr then caddr = "unknown" cport = 0 end local hsr = create_stream_reader( string.format("%s:%d -> %s:%d", caddr, cport, address, port)) client:settimeout(0) while client do local buf, err, partial = client:receive(need) if partial then buf = partial end if not buf then break end repeat found, consumed, need = hsr:find_message(buf) if found then inject_message(hsr) end buf = nil until not found if err == "closed" then break end coroutine.yield() end end function process_message() while true do local ready = socket.select(sockets, nil, 1) if ready then for _, s in ipairs(ready) do if s == server then local client = s:accept() if client then sockets[#sockets + 1] = client threads[client] = coroutine.create( function() handle_client(client) end) end else if threads[s] then local status = coroutine.resume(threads[s]) if not status then s:close() for i = #sockets, 2, -1 do if s == sockets[i] then table.remove(sockets, i) break end end threads[s] = nil end end end end end end return 0 end hindsight-0.12.7/benchmarks/run_hsr/input/tcpinput.off000066400000000000000000000000601301315165600230550ustar00rootroot00000000000000filename = "tcpinput.lua" instruction_limit = 0 hindsight-0.12.7/benchmarks/run_hsr/output/000077500000000000000000000000001301315165600207205ustar00rootroot00000000000000hindsight-0.12.7/benchmarks/run_hsr/output/tcpoutput.lua000066400000000000000000000027021301315165600234730ustar00rootroot00000000000000-- This Source Code Form is subject to the terms of the Mozilla Public -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at http://mozilla.org/MPL/2.0/. local socket = require "socket" local address = read_config("address") or "127.0.0.1" local port = read_config("port") or 5565 local timeout = read_config("timeout") or 10 local function create_client() local c, err = socket.connect(address, port) if c then c:setoption("tcp-nodelay", true) c:setoption("keepalive", true) c:settimeout(timeout) end return c, err end local client, err = create_client() local function send_message(msg, i) -- client:settimeout(0) -- local data, err = client:receive(0) -- test connection -- if err == "closed" then -- client:close() -- client = nil -- return -3, err -- end -- -- client:settimeout(timeout) local len, err, i = client:send(msg, i) if not len then if err == "timeout" or err == "closed" then client:close() client = nil return -3, err end return send_message(msg, i) end return 0 end --- function process_message() if not client then client, err = create_client() end if not client then return -3, err end -- retry indefinitely local ret, err = send_message(read_message("framed"), 1) return ret, err end function timer_event(ns) end hindsight-0.12.7/benchmarks/run_hsr/output/tcpoutput.off000066400000000000000000000001171301315165600234620ustar00rootroot00000000000000filename = "tcpoutput.lua" message_matcher = "Type == 'heartbeat'" port = 5566 hindsight-0.12.7/benchmarks/run_multiple/000077500000000000000000000000001301315165600204175ustar00rootroot00000000000000hindsight-0.12.7/benchmarks/run_multiple/analysis/000077500000000000000000000000001301315165600222425ustar00rootroot00000000000000hindsight-0.12.7/benchmarks/run_multiple/analysis/empty.txt000066400000000000000000000000001301315165600241270ustar00rootroot00000000000000hindsight-0.12.7/benchmarks/run_multiple/input/000077500000000000000000000000001301315165600215565ustar00rootroot00000000000000hindsight-0.12.7/benchmarks/run_multiple/input/input_test.cfg000066400000000000000000000002041301315165600244310ustar00rootroot00000000000000filename = "input_test.lua" instruction_limit = 0 input_file = "/work/git/misc-heka/benchmark/access_msec.log" preserve_data = true hindsight-0.12.7/benchmarks/run_multiple/input/input_test.lua000066400000000000000000000037341301315165600244660ustar00rootroot00000000000000-- This Source Code Form is subject to the terms of the Mozilla Public -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at http://mozilla.org/MPL/2.0/. require "io" local clf = require "lpeg.common_log_format" local msg = { Timestamp = nil, Type = "logfile", Hostname = "trink-x230", Logger = "FxaAuthWebserver", Payload = nil, Fields = nil } local grammar = clf.build_nginx_grammar('$remote_addr $msec "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"' ) local cnt = 0; local fn = read_config("input_file") --[[ function process_message() local fh = assert(io.open(fn, "rb")) for line in io.input(fh):lines() do local fields = grammar:match(line) if fields then msg.Timestamp = fields.time fields.time = nil msg.Fields = fields inject_message(msg) cnt = cnt + 1 end end fh:close() return 0, tostring(cnt) end --]] function process_message(offset) local fh = assert(io.open(fn, "rb")) if offset then fh:seek("set", offset) end for line in io.input(fh):lines() do local fields = grammar:match(line) if fields then msg.Timestamp = fields.time fields.time = nil msg.Fields = fields inject_message(msg, fh:seek()) cnt = cnt + 1 end end fh:close() return 0, tostring(cnt) end --[[ function process_message(offset) local fh = assert(io.open(fn, "rb")) if offset then fh:seek("set", tonumber(offset)) end for line in io.input(fh):lines() do local fields = grammar:match(line) if fields then msg.Timestamp = fields.time fields.time = nil msg.Fields = fields inject_message(msg, tostring(fh:seek())) cnt = cnt + 1 end end fh:close() return 0, tostring(cnt) end --]] hindsight-0.12.7/benchmarks/run_multiple/input/input_test1.cfg000066400000000000000000000001601301315165600245130ustar00rootroot00000000000000filename = "input_test.lua" instruction_limit = 0 input_file = "/work/git/misc-heka/benchmark/access_msec1.log" hindsight-0.12.7/benchmarks/run_multiple/input/input_test2.cfg000066400000000000000000000001601301315165600245140ustar00rootroot00000000000000filename = "input_test.lua" instruction_limit = 0 input_file = "/work/git/misc-heka/benchmark/access_msec2.log" hindsight-0.12.7/benchmarks/run_multiple/output/000077500000000000000000000000001301315165600217575ustar00rootroot00000000000000hindsight-0.12.7/benchmarks/run_multiple/output/empty.txt000066400000000000000000000000001301315165600236440ustar00rootroot00000000000000hindsight-0.12.7/benchmarks/single.cfg000066400000000000000000000021311301315165600176370ustar00rootroot00000000000000output_path = "output" output_size = 1024 * 1024 * 1024 sandbox_load_path = "" -- disable dynamic loading sandbox_run_path = "run" analysis_threads = 1 analysis_lua_path = "/usr/lib/luasandbox/modules/?.lua;/usr/lib64/luasandbox/modules/?.lua" analysis_lua_cpath = "/usr/lib/luasandbox/modules/?.so;/usr/lib64/luasandbox/modules/?.so" io_lua_path = analysis_lua_path .. ";/usr/lib/luasandbox/io_modules/?.lua;/usr/lib64/luasandbox/io_modules/?.lua" io_lua_cpath = analysis_lua_cpath .. ";/usr/lib/luasandbox/io_modules/?.so;/usr/lib64/luasandbox/io_modules/?.so" analysis_defaults = { output_limit = 1024 * 64, -- default memory_limit = 1024 * 1024 * 8, -- default instruction_limit = 1000000, -- default preserve_data = false, -- default ticker_interval = 60, } input_defaults = { output_limit = 1024 * 1024 * 8, instruction_limit = 0, } output_defaults = { output_limit = 1024 * 1024 * 8, ticker_interval = 60, } hindsight-0.12.7/benchmarks/single.toml000066400000000000000000000011361301315165600200570ustar00rootroot00000000000000[hekad] maxprocs = 4 base_dir = "." share_dir = "." [FxaAuthWebserver] type = "LogstreamerInput" log_directory = "/work/git/misc-heka/benchmark" file_match = 'access_msec\.log' decoder = "FxaAuthDecoder" [FxaAuthDecoder] type = "SandboxDecoder" script_type = "lua" filename = "lua_decoders/nginx_access.lua" [FxaAuthDecoder.config] log_format = '$remote_addr $msec "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"' [FileOutput] message_matcher = "TRUE" path = "hekaout.log" perm = "666" flush_count = 100 flush_operator = "OR" encoder = "ProtobufEncoder" hindsight-0.12.7/cmake/000077500000000000000000000000001301315165600146435ustar00rootroot00000000000000hindsight-0.12.7/cmake/mozsvc.cmake000066400000000000000000000025571301315165600171770ustar00rootroot00000000000000# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. if(MSVC) # Predefined Macros: http://msdn.microsoft.com/en-us/library/b0084kay.aspx # Compiler options: http://msdn.microsoft.com/en-us/library/fwkeyyhe.aspx # set a high warning level and treat them as errors set(CMAKE_C_FLAGS "/W3 /WX") # debug multi threaded dll runtime, complete debugging info, runtime error checking set(CMAKE_C_FLAGS_DEBUG "/MDd /Zi /RTC1") # multi threaded dll runtime, optimize for speed, auto inlining set(CMAKE_C_FLAGS_RELEASE "/MD /O2 /Ob2") set(CPACK_GENERATOR "NSIS") else() # Predefined Macros: clang|gcc -dM -E -x c /dev/null # Compiler options: http://gcc.gnu.org/onlinedocs/gcc/Invoking-GCC.html#Invoking-GCC set(CMAKE_C_FLAGS "-std=c99 -pedantic -Werror -Wno-error=deprecated -Wall -Wextra -fPIC") set(CMAKE_C_FLAGS_DEBUG "-g") set(CMAKE_C_FLAGS_RELEASE "-O2") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE} -g -pg") set(CPACK_GENERATOR "TGZ") set(CMAKE_SKIP_BUILD_RPATH FALSE) set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE) endif() include(CPack) include(CTest) hindsight-0.12.7/docs/000077500000000000000000000000001301315165600145135ustar00rootroot00000000000000hindsight-0.12.7/docs/architecture.md000066400000000000000000000076551301315165600175340ustar00rootroot00000000000000### Architecture Hindsight consists of three types of plugins: Input, Analysis, and Output. All plugins are written in Lua and based on the [Lua Sandbox] (https://github.com/mozilla-services/lua_sandbox) project. #### Input Plugins Input plugins are used to transform external data formats into a Heka messages for processing. Each input plugin runs on a dedicated thread and all messages generated by the plugins are multiplexed to a single output stream. #### Analysis Plugins Analysis plugins are used for simple or complex event processing such as: aggregation, sessionization, anomaly detection etc. Analysis plugins can share a thread of execution; the work distribution across threads is user configurable and can be tailored to specific use cases and needs e.g., by performance characteristics, work load distribution or type. Every analysis thread uses a dedicated reader to process the data stream produced by the input plugins. The messages generated by all analysis plugins are multiplexed to a single output stream. #### Output Plugins Output plugins are used transform the internal Heka messages into whatever format is needed e.g., txt, html, tsv, alternate binary encoding. The message/transformation can then be feed to other systems such as data warehouses, information retrieval indexes, dashboards, notifiers etc. Each output plugin runs on a dedicated thread and has its own readers to process the data stream produced by the input and analysis plugins. The input and analysis streams are multiplexed with the oldest message being delivered first. #### Sample High Level Data Flow ![Sample High Level Data Flow](hindsight_data_flow.png) #### Reliability ##### Durability The durability of the data is equal to durability of storage where the `output_path` (data stream files and state preservation files) is configured. ##### Delivery Guarantee The at least once delivery guarantee only applies to software failures. There are no guarantees if the underlying storage system fails. Crashing or killing the Hindsight process should not cause any messages to be lost or skipped and will, at most, duplicate/re-process one second of data on restart. However, sandbox state is only preserved on a clean shutdown i.e., if an analysis plugin was counting the number of messages going through the system, and the system was killed, the count would be reset to last saved state. This behaviour should be taken into account for any plugins performing and preserving stateful analysis. #### Dynamic Plugin Loading The Hindsight infrastructure can be run as a service with no initial business logic loaded since all plugin types can be dynamically loaded and unloaded as necessary. ```lua sandbox_load_path = "hs_load" sandbox_run_path = "hs_run" ``` ##### Starting a New Plugin 1. `cp test.lua hs_load/analysis/test.lua` 1. `cp my_test.cfg hs_load/analysis/my_test.cfg` 1. `cp your_test.cfg hs_load/analysis/your_test.cfg` 1. *Hindsight scans the directory* 1. test.lua is moved to hs_run/analysis/test.lua 1. my_test.cfg is moved to hs_run/analysis/my_test.cfg 1. Hindsight attempts to run my_test.cfg 1. your_test.cfg is moved to hs_run/analysis/your_test.cfg 1. Hindsight attempts to run your_test.cfg ##### Restarting a Plugin 1. `cp my_test.cfg hs_load/analysis/my_test.cfg` 1. *Hindsight scans the directory* 1. my_test.cfg is moved to hs_run/analysis/my_test.cfg 1. Hindsight attempts to restart my_test.cfg (no data gaps/loss) ##### Updating the Business Logic 1. `cp test.lua hs_load/analysis/test.lua` 1. *Hindsight scans the directory* 1. test.lua is moved to hs_run/analysis/test.lua 1. Hindsight attempts to restart my_test.cfg and your_test.cfg since they use the same underlying business logic (no data gaps/loss) ##### Stopping a Plugin 1. `touch hs_load/analysis/my_test.off` 1. *Hindsight scans the directory* 1. hs_load/analysis/my_test.off is deleted 1. hs_run/analysis/my_test.cfg is renamed to hs_run/analysis/my_test.off 1. Hindsight stops my_test.cfg hindsight-0.12.7/docs/configuration.md000066400000000000000000000204121301315165600177030ustar00rootroot00000000000000### Hindsight Main Configuration #### Configuration Variables * **output_path** - base path where the Heka protobuf streams, checkpoints, state files, and statistics are stored * input (directory) - stores the Heka protobuf stream generated by all input plugins * analysis (directory) - stores the Heka protobuf stream generated by all analysis plugins * hindsight.cp - checkpoint file for all input, analysis and output threads * hindsight.tsv - performance metrics on all running plugins * **output_size** - size at which the output files are rolled (bytes, default 64MiB) * **sandbox_load_path** - base path that Hindsight scans for new cfgs and Lua (string, default "" (dynamic loading disabled)). If the string is not empty the following directory structure must exist under the base path: * input (directory) - input plugin queue * analysis (directory) - analysis plugin queue * output (directory) - output plugin queue * **sandbox_run_path** - base path containing the running cfgs and dynamically loaded lua. The following directory structure must exist under the base path: * input (directory) - input plugins * analysis (directory) - analysis plugins * output (directory) - output plugins * **sandbox_install_path** - base path to the install location of the sandbox packages default: `/usr/share/luasandbox/sandboxes/heka`. This path should contain the input, analysis, and output subdirectory structure. * **analysis_threads** - number of analysis threads to run (count, max 64) * **analysis_lua_path** - path used by the analysis plugins to look for Lua modules * **analysis_lua_cpath** - path used by the analysis plugins to look for Lua C modules * **io_lua_path** - path used by the input and output plugins to look for Lua modules * **io_lua_cpath** - path used by the input and output plugins to look for Lua C modules * **max_message_size** - maximum size of a valid message (bytes, default 64KiB) * **backpressure** - delta between the writer queue file and the slowest reader, when exceeded backpressure is applied (message injection will be slowed) until the writer and reader are both on the same file (count, default 0 (no backpressure)) e.g. ```lua backpressure = 10 -- [writer = 100.log, slowest reader = 89.log, delta = 11] ``` * **backpressure_disk_free** - Number of output file units (`N * `). Backpressure is applied (message injection will be slowed) until the free space rises above this threshold (count, default 4 (0 to disable)) e.g. ```lua backpressure_disk_free = 4 -- [256MiB when using the defaults] ``` * **hostname** - hostname used in logging/messages (default gethostname()) ```lua output_path = "output" output_size = 64 * 1024 * 1024 sandbox_load_path = "load" sandbox_run_path = "run" sandbox_install_path = "/usr/share/luasandbox/sandboxes/heka" analysis_threads = 1 analysis_lua_path = "/usr/lib/luasandbox/modules/?.lua" analysis_lua_cpath = "/usr/lib/luasandbox/modules/?.so" io_lua_path = analysis_lua_path .. ";/usr/lib/luasandbox/io_modules/?.lua" io_lua_cpath = analysis_lua_cpath .. ";/usr/lib/luasandbox/io_modules/?.so" max_message_size = 64 * 1024 backpressure = 0 backpressure_disk_free = 4 -- hostname = "hindsight.example.com" input_defaults = { -- see: Default Sandbox Configuration Variables -- output_limit = 64 * 1024 -- memory_limit = 8 * 1024 * 1024 -- instruction_limit = 1e6 -- preserve_data = false -- restricted_headers = false -- ticker_interval = 0 -- shutdown_on_terminate = false } analysis_defaults = { -- see: Default Sandbox Configuration Variables } output_defaults = { -- see: Default Sandbox Configuration Variables -- remove_checkpoints_on_terminate = false } ``` ### Hindsight Sandbox Configuration #### Default Sandbox Configuration Variables * **output_limit** - the largest message an input or analysis plugin can inject into Hindsight (bytes, default 64KiB) * **memory_limit** - the maximum amount of memory a plugin can use before being terminated (bytes, default 8MiB) * **instruction_limit** - the maximum number of Lua instructions a plugin can execute in a single `process_message` or `timer_event` function call (count, default 1MM) * **preserve_data** - flag indicating if all global data should be saved on shutdown (bool, default false) * **restricted_headers** - flag indicating that the following header fields are not user modifiable `Hostname` and `Logger` (bool, default true (analysis), false (input/output)) * **ticker_interval** (default 0) * For input plugins it is the poll interval when `process_message` is called see: [polling input plugins](https://github.com/mozilla-services/lua_sandbox/blob/master/docs/heka/input.md#polling) * For analysis and output plugins it is the amount of time between `timer_event` function calls * **shutdown_on_terminate** - cleanly shuts down Hindsight if this plugin is terminated (default false) #### Default Analysis Sandbox Configuration Variables * **process_message_inject_limit** - the maximum number of messages the `process_message` function can inject in a single invocation (count (0-255), default 0). * **timer_event_inject_limit** - the maximum number of messages the `timer_event` function can inject in a single invocation (count (0-255), default 10). #### Default Output Sandbox Configuration Variables * **remove_checkpoints_on_terminate** - removes the checkpoint entries when the plugin is terminated (default false). This prevents the data from being pruned until the plugin can be fixed but it can also cause the system to back pressure as it will start filling the disk. ---- #### Common Plugin Configuration Variables * **filename** - plugin source file (must have no path component and a `.lua` extension). Search order: sandbox_run_path, sandbox_install_path ##### Reserved (Hindsight populates the following variables in the configuration) * **Logger** - configuration filename without the `.cfg` extension prefixed with the sandbox type e.g., `analysis.myplugin` * **Pid** - process ID * **Hostname** - configuration hostname. * **path** - Lua module search path (corresponding to the type of plugin) * **cpath** - Lua C module search path (corresponding to the type of plugin) * **log_level** - Integer specifying the syslog severity level, when set to debug (7) the sandbox print function will be wired into the Hindsight logger ###### For input/output plugins these additional Hindsight configuration options are available from read_config() * **output_path** * **output_size** * **max_message_size** * **sandbox_load_path** * **sandbox_run_path** * **sandbox_install_path** ##### User Defined * *key* (string | number) - user defined variable with a (string | number | boolean | table) value ###### User Defined Private Variable * **_***key* (string) - user defined variable beginning with an underscore (allows filtering in UI displays) #### Input Plugin Configuration Variables * [Default Configuration Variables](#default-sandbox-configuration-variables) * [Common Plugin Configuration Variables](#common-plugin-configuration-variables) #### Analysis Plugin Configuration Variables * [Default Configuration Variables](#default-sandbox-configuration-variables) * [Common Plugin Configuration Variables](#common-plugin-configuration-variables) * **message_matcher** - filter to select which messages this plugin receives see: [Message Matcher](https://github.com/mozilla-services/lua_sandbox/blob/master/docs/heka/message_matcher.md) * **thread** - analysis thread the plugin will be run on (`thread % analysis_threads`) #### Output Plugin Configuration Variables * [Default Configuration Variables](#default-sandbox-configuration-variables) * [Common Plugin Configuration Variables](#common-plugin-configuration-variables) * **message_matcher** - filter to select which messages this plugin receives see: [Message Matcher](https://github.com/mozilla-services/lua_sandbox/blob/master/docs/heka/message_matcher.md) * **async_buffer_size** - when using asynchronous output this controls the number of message checkpoints to hold in memory. i.e., if the output can have 1000 messages in flight the async_buffer_size should be set to 1000 hindsight-0.12.7/docs/hindsight_data_flow.png000066400000000000000000001027531301315165600212320ustar00rootroot00000000000000‰PNG  IHDRhã2x¼ IDATxœìy\ãÿEÈ£„TDu•Ã#DA¼PË ÏÒµž§C-ýZyKä‘xßGeZ’h’á­%„€)˜’©€Ê½(Âç÷‡¿™‡X`Y˜]öó~½Þ¯—ììÎÎÌîÎî¼CB!„B!„5‘I=„B!„BÑ]!„B!„¢6 „B!„BQ†B!„B!„¨ Ã!„B!„BÔ†aB!„B!jð@!„B!„µaX „B!„BˆÚ0,B!„B!Dm!„B!„¢6 „B!„BQ†B!„B!„¨ Ã!„B!„BÔ†aB!„B!jð@!„B!„µaX „B!„BˆÚ0,B!„B!Dm!„B!„¢6 „B!„BQ†B!„B!„¨ Ã!„B!„BÔ†aB!„B!jð@!„B!„µaX „B!„BˆÚ0,B!„B!Dm!„B!„¢6 „B!„BQ†B!„B!„¨ Ã!„B!„BÔ†aB!„B!jð@!„B!„µaX „B!„BˆÚ0,B!„B!Dm!„B!„¢6 „B!„BQ†B!„B!„¨ Ã!„B!„BÔ†aB!„B!jð@!„B!„µaX „B!„BˆÚ0,B!„B!Dm!„B!„¢6 „B!„BQ†B!„B!„¨ Ã!„B!„BÔ†aB!„B!jð@!„B!„µaX „B!„BˆÚ0,B!„B!DmˆÎrüøq=z”RJ)¥”R*Ç—z“€h Dg9zô( ¥”RJ)¥T=*õ&шΰ@)¥”RJ©t2,†¢³0,PJ)¥”R* D€aè, ”RJ)¥”J'Ã`X : Ã¥”RJ)¥Òɰ@ˆÎ°@)¥”RJ©t2,†¢³0,PJ)¥”R* D€aè, ”RJ)¥”J'Ã`X : Ã¥”RJ)¥Òɰ@ˆÎ°@)¥õc~~>d2þûßÿ*ݾråJB¡Pà×_E»víj=îøøøjWÓqÿöÛo°±±©rø¶mÛ$_–”RÚ˜eX  DgaX ”Òú±&a!??éééµwMÂBMÇ­*,ÁÊÊJòeI)¥Y†"À°@t†J)­k»ÇB||<ºté‚?þ>>>èС"##ÅÇ-Y²¶¶¶èÒ¥ -Z$>.77cÇŽEÛ¶maoo   lmmááá5kÖ }ûö5zÞÀÂÂÑÑÑ’/SJ)mŒ2,†¢³0,PJ©~XXX(þûøñãèÞ½»äÓD)¥”aü†¢³0,PJiã7-- HNNFQQ&Mš„wÞyGò颔Rʰ@þÃÑY(¥T?\¿~=ìííѦM 6 ééé’O¥”R†ò?ˆÎ°@)¥”RJ©t2,†¢³0,PJ)¥”R* D@¯Â‚\.§Z¨º¨¤ž_Z¹R1RJ)¥”ÒÚɰ@¨äª ÃBãRê/FJ)¥”RZ;ˆ€^†¢HˆvÀ°@)¥”Rª›2,†" `X ”RJ)ÕUˆÃ‘ †0,PJ)¥”êª D€aHÃ(¥”RJuU†"À°@$ƒa ”RJ)¥º*Ã`X ’Á°@†J)¥”R]•a0,É`X Ã¥”RJ©®Ê°@ˆd0,€aRJ)¥TWeX  D2À°@)¥”Rª«2,†" `X ”RJ)ÕUˆÃ‘ †0,PJ)¥”êª D€aHÃ(¥”RJuU†"À°@$£>ÃBNN²³³QPPPaßÚÃ¥”RJ©nʰ@ˆdÔgX¸uë>ÿüslݺyyyJÃø>Ð.(¥”RJuS†"À° ‰‰‰hß¾½Fǹ{÷nܧ¡¨ï°0mÚ4ØÛÛcÆ ÈÍ͇Õõymmm­öã«£²×¨¤¤2™ ÆÆÆ066FóæÍѽ{wœ:u €fÞO.\¨ñ8JJJðÑGÁÀÀYYYuz^†J)¥”RÝ”a0,H„¦ÃBYY¬­­ë|Ÿ†¤!‚L&ƒ­­-V¯^‡B¡Ðî°PÕk$„…´´4ñï}ûöÁÌÌ YYYy?•””Ô8Œ1 .DÓ¦M(¥”RJõT†"À° Ïn†‡‡£S§Npqq®^½* …••ºu놭[·ÂÞÞ¾ÂøFŽ têÔ Ÿ~ú)ÅaþþþذaƒÒ}þùçŸz¿šÐPaA&“ÁÚÚŸþ9²³³5áîîŽ À××NNN8vì !!;wÆ{ï½ooo8::âܹs€óçÏÃÙÙY_ù¿«zž ]ºtÁáÇ•ÞOªÆTý~*¿Ç‚ªy†`X ”RJ)ÕcˆÃ‚D”ß¼uëÌÌÌ’’ذa<==ÉÉÉ077ÇíÛ·Q\\Œ€€€Jÿg:++ ÆÆÆ€'Ož {÷î8~ü8~øáxyy¡´´Té>Ú@C†™LKKK,Y²]»vÕHXHJJ‚¡¡¡¸BÝ¿?¼½½<}} pøða@dd$œœœ¨Þð¯ê5ª*,tîÜQQQ5 ªÞOåªy+&‚»»;:v숨¨¨J=q⢣£qõêUq¯J)¥”R*½ D€aA"ÊoîØ±Ci…B&Mš 77›6mÂÈ‘#ÅaÕ†¸xñ":wî $%%Uz©qvv†™™ êÅòQAÐÂÂ666èÚµ«ÚÓ]>,˜™™‰·_¾|vvvž¾¾æææâ°’’ 33S#aáñãÇØ·o,--‘““Sã° êýôlX¨jÞÊ£‰°Ð±cG´lÙ½zõªÔ—_~˜0a,X€ÈÈHdddHþEJ)¥”Rªï2,†‰(¿!ŠÉ“'+ oÑ¢®_¿ŽL:U¼=..®Fazõê…¾}ûª¼”8;;ÃÔÔ´ÒP_ZXX U«VpwwW{ºË‡[[[ñöò'&&ÂÁÁAéq&&&HII©SXNÞøüóÏÃÃñ±±âóÕ$,¨z?=ªš·òh*,XYYÁ××·RûôéwwwX[[ÃÌÌ X¾|9RRR$ÿ2¥”RJ)ÕgˆÃ‚D”ßܵk†.S(žî±——‡õë×#((HvèС……#GŽàå—_†‡‡~úé§Jï#5ÂëQVV¦–QQQ(**ªÔ›7o"88X)*üë_ÿÂâÅ‹5z(„ª°`jjв²2@qq±x…„„tèÐA|ܱcÇÔ>B üûIÕøU½Ÿ¤ ]»v…««+NŸ>]ÁS§NáèÑ£øþûï±fÍLš4 íÚµCÛ¶m±dÉüý÷ß’¡RJ)¥”ê« D€aA"Êo¦¥¥ÁÂÂׯ_„……‰{ÄÇÇÃÒÒYYYxôè† ¢öîÝ‹»wï"''M›6EAA о}{üñLj‡ƒƒ ”î£ 4ôÉCCC5zòÆê‚¡¡!ÂÃÃ_ý5\]]<}½MLLŸŸ7ü«zjT_ÕûIª°PÓ“7!%%¡¡¡pqq‹‹ vîÜ©t)QJ)¥”RÚp2,†‰xöª„››œáçç‡ÔÔTqØìٳѺukxyyaãÆptt‡Y[[‹—>ô÷÷‡……Æ÷ß_¼Ï”)SðÁ(Ý'>>¾¾g±Z*,ØØØà‹/¾Àƒ Phîr“Õ…GGGÌž=NNNhÓ¦ bbbÄû~ðÁpuuÅÀ&žØ¨ü5ªMX¨nüU½Ÿj²³³ÅC2Êžq÷îÝÚ/LÔþª7oÞÄœ9s`ii‰ &àÊ•+’©RJ)¥”ê£ D€aA(--ÿ}öìYôèÑC©Ñ ìíí±aÃ¥ÿÕnˆ÷Á³úÚ„¶½ŸÔ¹ÜäñãÇÑ·o_¸¹¹á»ï¾“üK•RJ)¥TeX  ZNff¦x˜DYY¦L™‚wß}WêÉÒõ>ÿüslݺyyyJÃô9,hãûI°žžŽÿûß033ÃÒ¥K‘ŸŸ/ù+¥”RJ©¾É°@t€-[¶ÀÁÁvvv>|xi×ê3,äää ;;†ésX´ïý¤NX(**ÂçŸSSSLŸ>iii’±RJ)¥”ê› D€aHF}†Uò} ]¨ vî܉¶mÛâõ×_Çü!ù+¥”RJ©¾É°@ˆd0,@ý°°ÿ~¸¹¹!00qqq’±RJ)¥”ê› D€aHÃÔ ‡‚\.G@@¢££%ÿb¥”RJ)Õ7ˆÃ‘ †¨Ž9oooôïßgΜ‘ü‹•RJ)¥TßdX  D2 ~XˆŠŠBïÞ½áëë‹S§NIþÅJ)¥”Rªo2,†" `X ”RJ)ÕUˆÃ©”²²²z†0,PJ)¥”êª D€aA(--Å­[·ðóÏ?cß¾}XµjfÍš…àà`Œ3C‡…¯¯/¼¼¼Äeåíí ___ 6 cÇŽÅ´iÓðÑG!,, û÷ïGtt4222Ô  `X ”RJ)ÕUˆÃB#&==?ýôBBB0nÜ8x{{‹Ë@ÓöíÛ'NÄŠ+pâÄ Ü»w¯ÚécX Ã¥”RJ©®Ê°@¹¹¹8}ú4–.]ŠÁƒWüýý1cÆ |öÙgøòË/qüøq$&&âÆÈÉÉAnn.=z$޳¸¸¹¹¹xðàRSSqñâE=z;wîÄÒ¥K __ßJŸkÔ¨QX¹r%¢££QXXXazÀ°@)¥”Rª«2,†§¸¸'NœÀ¬Y³*ì‘àïïY³façÎ8þ<>|XoÓ‘™™‰èèhlÞ¼ÿùÏðòË/+MKŸ>}ðÉ'Ÿ ::%%%ÈS(¥”RJuS†"À° ƒ”––"66 ,@ß¾}ÅùêÑ£&OžŒ]»váÚµk rƪxòä .]º„Í›7#((H)2ôïߟ}öÃÀ°@)¥”Rª«2,½ ëׯÇõë×¥žœZóÇ`ÅŠð÷÷WÚPýõ×±k×.ܾ}[êI¬’¿þú 6l@```…C&Ô¥®aj—µ}뤞_Ú¸•ú‡¥”RÚ2,½ C‡Uúá§ äÿý76mÚ„áÇ+MûСCu2”••á÷ßÇŠ+0pà@Èå T½ 1†ªÍJýCRJ)mˆ€^……ÒÒRÄÅÅaÉ’%J'ìÑ£Þzë-­;„`üøñJ?T°råJ\¾|YÒiÔ¥¥¥ˆŽŽVûñê†Ú8ÔTX D“0,PJ)Õ'ˆ€^……ò”””àܹs˜?~… öíÛo¿ý6¶lÙ‚ØØØz?éá¹sç°víZLž<¹Â  €\¸p¥¥¥õ6ºÂ~˰@´†J)¥ú$ÃÐÛ°Pžâââ]¦ñí·ß/ÓxâÄ \¸p×®]CFFF•—iÌÈÈÀŸþ‰øøxµ.Ó(\AT„aA¿eX ÚÃ¥”R}’a0,TÂ;wpâÄ „……aúôéèׯ_½‡ëçç‡wÞy7nÄÏ?ÿŒ¬¬,©g_g`XÐoˆ6°@)¥TŸdX  5$##¿üò öíÛ‡U«VaöìÙƘ1c0lØ0øúúÂËËKüQéíí ___bìØ±˜6m>úè#„……aÿþýøõ×_qïÞ=©gK§aXÐoˆ6°@)¥TŸdX  DgaXÐoˆ6°@)¥TŸdX  DgaXÐoˆ6°@)¥TŸdX  DgaXÐoˆ6°@)¥TŸdX  DgaXÐo=Š>}úÀÇÇ'Ož¬õãH}À°@)¥TŸdX  DgaXÐoOž<‰~ýú¡wïÞˆŠŠªõãH}À°@)¥TŸdX  DgaXÐoýõW 2]ºtÁþýûkýx†R0,PJ)Õ'ˆÃÑYôÛ?ÿüãÇG«V­°aÆJïsçÎ\¹rééé†1,ú€aRJ©>ɰ@ˆÎ° ßfggcîܹ055Åœ9s••Uá>—/_ÆèÑ£1iÒ$ܺuKié(¥”ê“ D€aè, tÇŽpppÀàÁƒSaøÅ‹ѹsgaìØ±HMM‡5tXؽ{·VŒ£&ãOJJ‚­­­ÊûúûûcÏž=hß¾}ƒLSùç½xñ"`úôéÈÏÏŒ?¾Âcë}úž…aRJ©>ɰ@ˆÎ°@0räH¼ôÒKX¼x1nß¾­4üâÅ‹èÔ©d2š5k†W_})))P(6,”••ÁÚÚZòqÔtüÕ……ââb´nÝ?FVVVƒLSùç-++Œ;PRR‚I“&Ux<Ã¥”RZ¿2,†¢³0,Ðû÷ïcݺuppp€»»;Ö®]‹ÔÔTA¡P 2™ †††:t(’““Õ áááèÔ©\\\àãルW¯Ο?gggñ~åÿ9r$ ЩS'üøãèܹ3Þ{ï=x{{ÃÑÑçΫÕ8þùçŸ*§/!!¡ÊñÀöíÛÑ¡C888ÀÇÇGWùñŸ›6mÂÁƒ±yófØÛÛ‹aA&“¡iÓ¦ð÷÷W+,ܺu fffHIIlذžžžTo geeÁØØÀÓ]>| ''§ZCªÆŸ™™ ###ܸq0eÊLŸ>½Âø“’’`llŒ¯¾ú °mÛ6ôêÕK|޹sçbË–-Ja!)) †††âŒýû÷ÃÛÛ[£ó,˜˜$''#++K ¨”R*¥ D€aè, T0)) ‹/†««+ŒŒŒ`dd„fÍš¡I“&JQA°I“&j……;v 00Pü[¡P I“&ÈÍÍ­UX077ïWRRdffj,,T5~ÈˡíÝ»þþþÆŸ””SSSñ~—/_†ø·\.Çÿûß aÁÌ̬ÒÇhjž…çžÆ‰}ûöæÌ™ƒ›7oVº,„éSõÚmÚ´ #GއEDD¨:v숖-[¢W¯^”jÌ>}úÀ×׃ ˜1c0{öllذ¿üò îÝ»'ù:˜Rª¿2,†¢³0,PÁ[·naöìÙxá…* š ¡¡¡˜‰¨lYÓ§êµ ÁÔ©SÅÛãââꬬ¬àëëK©Æôññ··7ºu똚š¢U«V8p BBB˜˜ˆÂÂBÉ×Å”Rý“a0,…a* dddàÃ?„‰‰ ÌÌÌ0jÔ(lÙ²?üð¶oߎ¶mÛV8ÂÏÏO­°°k×. >\ü[¡xú¿ÞyyyHHH@‡ÄaÇŽ«2,˜ššŠ' ,..†²²²j<U¨ÿàææ†œœÀW_}Uë°Ž)S¦¨]X¨ë<—Þ1cÆ M›6>|8FŒÖ­[cĈˆ‹‹«°,„éSõÚ­_¿AAAâ°C‡©ºví WWWœ>}šRyâÄ üôÓOˆˆˆÀöíÛ±`Á¼úê«°³³ƒ Þzë-ÄÄÄ0.PJ\†"À°@t†ZXXˆÕ«WÃÒÒ...ؽ{7òòòÄáš>ycZZ,,,pýúu@XXúöí+311/,n çää iÓ¦(((@bb" øúë¯áêêZ«q¨Bոם_aÆ>|xyyU¿ªH,‚P›°P×y.ÿ¼Àÿ./yíÚ5,Z´HiìÝ»wïÞU ª^»øøxXZZ"++ =Â!CÔ hÙ²%¦L™‚Ë—/K>m”Rý’a0,…aÆÆÆbèС055Å’%K››«4¼|X044Ä+¯¼RçËMoJJ .\عs'NŸ>­´ ¬­­]᪪^»Ù³g£uëÖðòòÂÆáèèXå2VÃmH³³³ŽÞ½{ÃÖÖ+V¬@vv¶äÓE)ÕˆÃÑYè¶mÛ`gg‡aÆ!..®Âð‹/¢sçÎ022˜1cšš*S7,Ô•g7vumüê Óô,¥¥¥â¿Ïž=‹=z¨5†ÚÐfeeaåÊ•°µµÅ!Cð믿J>M”Rý‘a0,…aA¿½ÿ>fÏžfÍšaÞ¼yÈÉÉ©pŸË—/ãµ×^Ûo¾‰›7o* cXh8´qšÊ“™™)&QVV†)S¦àÝwßUk\ T 0|øpØÚÚbãÆ’O¥TdX  DgaXÐoÿüóOŒ?666Uþ¾sç®\¹‚´´´ Ãt5,\»v :uªÔQ£FiåF¼6NÓ³lÙ²°³³ÃðáÑ••¥Öx¨>xðŸ|ò Z´h?ü‡CPJL†"À°@t†ý6::ƒ B×®]ñý÷ß×úñR…Ò¸aX R¹qãF´jÕ o¼ñþúë/ɧ‡Rª2,†¢³¨„þT»¬íëxêÔ)ôë×½{÷FTT”ÚïB4 ÕÊo¾ù...xå•WpñâEɧ‡Rª2,†¢³0,4.kû:FEE¡wïÞðõõÅ©S§Ô~¢I¨T|¿ÿþ;rssÕžW†*• ”R)dX  5àöíÛ>|¸ägÍ×'ÇŒƒû÷ï«|] ‡þùgϞŗ_~‰Å‹cÒ¤Iðõõ­öóÞ³gO 8¯¿þ:þýïãÓO?ŪU«°zõjìØ±;wîÄþýûqàÀDEEáĉˆŽŽ=q⢢¢ðý÷ßcÿþýرc¶oߎիWcåÊ•øøã1cÆ Œ=þþþèÑ£GµÓäç燩S§bÙ²eسgbbb‘‘Qí2`X Rɰ@)•B†"À°P ·oßF`` äÚúä¸qã““SíkðÐ8`XÐMŠ‹‹qéÒ%|õÕWøðÃ1pàÀ*?Ó½{÷Ƹqã0wî\lÙ²‡ÂÏ?ÿŒäädddd °°°A -- üñΜ9ƒˆˆlذ~ø!‚‚‚àååUå¼ :ü1öîÝ‹¤¤$”””(-†*• ”R)dX  *(ÆW§ÝcIÕ¨»œênˆEFFÂÓÓ~~~8{ö,ßõLzz:~úé'¬X±&L€‡‡G…î`Ú´iâÿòÿòË/¸yó¦ä?zjkaa!RSSqæÌìÞ½‹/Æ[o½…~ýúU˜goooLž<aaa8qâÕL†J©2,†…*`Th겜ênˆ8p]»vÅ!C«ÖûÀÃÃsçÎÅž={ðûï¿£¸¸XêÅQ)YYYˆÇåË—d]TXXˆß~û ;wîÄÌ™3áïï_aƒÚÓÓ'NDhh("##‘ššZ/?X„Ãòóó%ÿñTTT„«W¯"""‹/FPPzöìYéž RO+Õ?(¥RȰ@*Q¡a¨ërfXh¨»!¶oß>¸ººbĈ¸páB­_Ù®ûžžžxã7°bÅ =zéééR/À¯¿þŠ©S§bΜ9¸zõªFÇ]VV†¿ÿþ‘‘‘ Áرc+ÝXöööFpp0vïÞ„„äää4È–¢W¯^ÈÎΖüÇSeÞ¿±±±Øºu+Þÿ}ñd”ROÕ?(¥RȰ@žQ¡aÐÄrV ‘››‹‚‚‚ ô uúuë`ee… &àÚµkj}¦¦¦â‡~@hh(ÆWéîýþþþ˜1cBCCñÝwßáüùó¸sçNƒ.£ü={öİaàÖ8JKK‘žžŽ˜˜ìٳ˖-ÃÔ©S+=¹¢——&Mš„•+WbÅŠèÕ«—d+æææÉd¸{÷®ä?žjbQQ’““%Ÿª2,PJ¥a0,”ƒQ¡aÐÔrVrrr„„„ »P3,hê„…ÂÂBÌŸ?M›6ŬY³ðàÁ|9æää ..;vì¨òÁ>}ú`„ øøã±sçN9r±±±¸~ý:²³³QZZª±eT“°ðäÉdff"%%111ˆŒŒÄÖ­[1wî\Œ;ÞÞÞUÎË!C0wî\|õÕW¸xñ¢ÒgFê] ”J¥ÔŸUJ©~ʰ@þ?Œ ƒ&—³ª°ðàÁ¼ù曘8q"~ýõWäåå‰Ã´ uÂBRRÆŒƒ—^z ëׯ¯×/ÌŒŒ ÄÄÄ`ß¾}X¶l¦M›¦28öìÙ3f Þ}÷],\¸k׮Ŏ;ðí·ß"22§OŸF|||µ†„„ÀÑÑݺuÃ'Ÿ|‚5kÖ`þüùxçwðúë¯×øŠC† ÁÛo¿åË—cÿþýˆ‹‹«vƒ]ê†Jk¦ÔŸUJ©~ʰ@À¨ÐPhz9W^y嘘˜ ((çÎCnn.Šº‡[[[DGG×iÚU±{÷î ·•””@&“ÁØØÆÆÆhÞ¼9ºwïŽS§NѾ}û:=ï… j<ŽÃ‡ÃÅÅæææðññAJJŠÚÏ«NXصkÚµk‡þýûãôéÓ’|‘fff"!!áááXµjæÍ›‡àà`Œ5 ýû÷¯v#¿6:::âù矇¹¹9\\\*½O=àïïÑ£GcÆŒøøã±zõj}ÀŽ;°nÝ:„††âÓO?ÅÌ™31}úôj9r$lllбcG|øá‡Ø¹s'"""pæÌüñÇHKK«ô|"šPê†Jk¦ÔŸUJ©~ʰ@ô:,0*4 õµœkd2š7oŽ‘#GâäÉ“èÖ­›FÂBbb"ÜÝݱ`ÁøúúÂÉÉ ÇŽ$$$ sçÎxï½÷àíí GGGœ;wpþüy8;;‹ã+ÿ÷È‘#a``€N:áŸþïólXèÒ¥ >¬TBCCaee…nݺaëÖ­°·· ¼Ç‚ªyKOOGDD„8¾K—.ÁÖÖVíåYÓ°PPP€'N`À€hÒ¤  „˜˜É¿PˆˆñP††žg©7V(­™RV)¥ú)ÃÐÛ° é]aÃïÝwßUºýСC1bDÆQÙ.ðš@ÿ›]®]»†Í›7cÑ¢E˜={6œœœÐªU+¸¹¹aÞ¼yX´h‘F?~<>ýôÓJ;w.\\\İ F'''tëÖMíùÂBRR Åêþýûáíí àé2700ÀáÇ‘‘‘prr zÃ?++ ÆÆÆž³ª°Ð¹sgDEEÕ8,$''ÃÜÜ·oßFqq1ÄÇ• ªæíY–/_Ž   /¿géܹ3ìììðá‡Vê¬Y³ðÖ[o¡[·n066†L&ƒNœ8!ù—iCɰÀ°@iuJýY¥”ê§ D@/ÃB}üzII š7oŽV­ZáÊ•+âí5 Uí_WJKK<,>|îîîJõÚb‹-кuktíÚUíù+ÌÌÌÄÛ/_¾ ;;;O¹¹¹8¬¤¤ÈÌÌÔHXxüø1öíÛKKKäääÔ8,lÚ´ #GއEDDTªš·ò;v mÛ¶­’’’$ÿ"mH(­N©?«”Rý”aè]X¨¯ÝòKJJðÜsÏaË–-8p xû³a!22nnnh×® €ÌÌLÊ»À·jÕ ×¯_|÷ÝwhÖ¬ _|ñ…¸WDxx8:uêøøøàêÕ«žîšÞ­[7L˜0¾¾¾J?F¿~ý°|ùrÌwe\»v ¡¡¡J{*üßÿý.\¨QÇO>ù¤RçÌ™Sé Æ Óè å(ÿwbb"”gbb‚”””:…áäÏ?ÿ<<<<+>_MÂBHH¦N*‹‹‹«2,T5o{÷î…³³³ø^U777´mÛK—.­ÔeË–á‹/¾ÀO?ý„ŒŒ Iþ%ÚÐ2,0,PZRV)¥ú)ÃЫ°PŸçT())¡¡!žîرcj !Pþ5V5þõë×+¶pèÐ!µÂÂ?ü777ܹs§ÚåUê\BßdX`X ´:¥þ¬RJõS†" 7a¡¾7vKJJдiSÀÙ³gáèèˆG)……­[·bذaâcòóóѬY3<~üXiƒr×®]xûí·Ý»wÇöíÛ±lÙ2ÀK/½„œœìر⸠š4i‚ÜÜ\$%%¡yóæ(--ð¿ÎM›6ÁÏÏ%%%÷ò4ä 1ksUˆQ£FáÌ™3½*DuaÁÐÐááု¿þ®®®ž^UÁÄÄùùù€àà`qÃ?''M›6EAAÒsÖ&,¨||<,--‘••…GaÈ!µ <@ëÖ­qãÆ u_ª—aaÒê”ú³J)ÕOˆ€^„…†ØØ-àÕW_Å矮–/_ØÛÛ‹ZXXàöíÛJaáÆèÚµ+`Þ¼yhß¾½îÞ½ +++ñ¼ øÏþ â.ð“'O†§§'8àé¹¼¼¼pêÔ)Oww·°°Oœ†¾}û¨ø¿Íå7r£££ñÒK/‰'ÔRD@uXÈÉÉÁüü|¥aú233Å÷MYY¦L™Rá© ÃBõ2,0,PZRV)¥ú)Ãh´a¡¡7v+ ùùù°±±QÚKáÈ‘#pssƒ££#är9¢££Åaåwÿúë¯!“ÉpûömÀŠ+`ll …B!ÞÿàÁƒpssƒ³³3üüüšš @uX€™3gbÔ¨Q™o©¢ :,"77†ésX€-[¶ÀÁÁvvv>|8²²²$†…êeX¨¿° ¼ÿ¨n*õgS›”ú³J©Ôë*Íú´¾ÂBjj*z÷î-ùrÑf…‹h 2,H¹±«OH½œU……š|ñí ®_hú Ãí\©?›Ú¤ÔŸUJ¥^PiÖ§õ F-ù2ÑfCCC5¾ÜëJ£ Roìê Ú°œuýBÓê?,Ý‚ë íû¬RÊõ©n¢a!$$r¹ãÇÇãÇ5>~]EÛ—K£ Ú°±«hËrfXhp¡zˆ2\ohßg•R®Oum §OŸ†\.GŸ>}4vióÆ€.,—F´ec·±£MË™a¡qÀ „êeX`X Êp½¡}ŸUJ¹>ÕM´),äççcРAËåØ¿¿ÆÆ«ëèÊriaA›6v3Ú¶œÜ@¨^††¢ ×Ú÷Y¥”ëSÝD›ÂBhh(är9‚ƒƒQVV¦±ñê:º²\t>,hÛÆncE—3ÃBã€Õ˰À°@”ázCû>«”r}ª›hKXHNNFÏž=Ñ«W/ܺuK#ãl èÒrÑé° »m]Î n T/ÃÃQ†ë íû¬RÊõ©n¢-a!88r¹7nÔÈø º´\t6,hëÆncC›—3ÃBã€Õ˰À°@”ázCû>«”r}ª›hCXøå—_ —Ëáç燂‚ ÌUã@×–‹N†mÞØmLhûrfXhp¡zô',ôèÑNNNWbb"Ú·o¯Öc/\¸ öc®7´ï³J©¶­OIÍІ°0mÚ4Èår|ûí·˜£Æƒ®- Ú¾±ÛXÐ…å̰Ð8àBõ2,èGXHJJB=àéé‰ØØØ:¯.a¡¤¤YYYuž†ú‚ë íû¬RªMëSRs¤ ×®]ƒ\.G¿~ýPXX¨¡¹Ò}tq¹èTX¸s玸±KFm @ÝÃÕ.¥þA¤ÍêsXxá… “Épûöíz¿ðþÓ>üðC¬^½7nÄŒ3ÄÛáîîŽ À××NNN8vì˜8|ûöíèСàããƒþùG|œzô興ñ1?ýôÜÝÝñèÑ#L˜0íÚµƒƒƒÆ‡¢¢"¥=ªº”p½QÑãÇ£oß¾xùå—qâÄ É§‡êŸÚ´>%5§®ëÓº†…ùóçC.—cÍš5š£Æ.. ÷îÝÈ#$ßøÑ'ƒ‚‚´6* M©i³úlll “Épýúõz¿ðþ“š'OžÀÎΙ™™xøð!lllðèÑ#O÷d044¼íß¿ÞÞÞ€ÌÌLáÆ€)S¦`úôé”ÃÂ_|1cƈÏ7cÆ „††âûᅦ¿¿?ÊÊÊPZZŠY³f!&&F),Tu)áz£¢?ÿü3üüüàááÈÈHɧ‡êŸÚ²>%µ£®ëÓº„…ÜÜ\xzz¢gÏž¸}û¶çJ·ÑÕå¢3agQ7,PªkêsXðôô„L&«·ÿÕ–Â?ýôF%þ=nÜ88pÀÓ°`ff&»|ù2ìììÄ¿óòòÄïÝ»þþþ”ÃBFFÌÌÌ P(mÚ´Ajj*¢££akk‹#GŽˆÃås,Tu)aX¨èÅ‹ñꫯÂÙÙ_ýµäÓCõOmYŸ’Ú!eX8|ø0är9‚ƒƒ58Gº®.†¢³0,P}QŸÃÂ;ï¼™L†… Öi|xyy¨ûL®|XPu‰áòèbXÉd077Çüùó1qâDÈår\¾|Y­éNNNÆ Aƒ`eeKKKxxxàäÉ“âpM#S§N­Ór‘†¢³0,P}‘aá©W®\ÁÂ… áïïKKK¡}ûöèÞ½;zõê¥Ò^xAé‡L&CË–-aoo¯aaÞ¼yhÓ¦ ~üñG©'E#tìØ-[¶¬òõ~ùå—€ &`Á‚ˆŒŒDFF†äŸå†2??qqqX´h kkk˜šš¢}ûöËåðöö®ö3C©··7ÜÝÝakk SSS¡I“&Ö¥2™ Mš4¹¹9ÜÜÜÔúL¯_¿^<ñì³lÚ´ ýúõ«UX077ïWRRdffj$,<~üûö탥¥%rrrj6mÚ„‘#GŠÃ"""ª ª.1, ©°àìì 333Ô‹•½_LMMÑ®];¸»»#;;[­éöd)--EYYÂÃÃÑ¢E Ü¿_­CcJKKÕšM3xð`Èårµ—‹”0,…aê‹ ÿ3//¿üò Ö¬Yƒ™3gâµ×^ÃàÁƒÑ¿•ZZZVìììô",46:vì+++øúúVjŸ>}еkWX[[ÃÌÌ X¾|9RRR$7¤÷îÝÃÙ³g±fͼ÷Þ{=z4пÿ*—¯¯oµÃë¢0nM>G}Lo}ŽSæ½OŸ>hÛ¶-ŒŒŒ*Ý0¬,,˜™™¡sçÎj}¦¿ýö[tìØ±ÒaóçÏÇ믿^«°ààà 4¤¤¤Ô),'o|þùçáááØØXñùjBBB0uêTqX\\\•a¡ªóê”G“aÁÔÔ´F¯³¦lÑ¢lllÐ¥K— W‹¨ ?†L&ÃíÛ·•nÿ믿ðäÉ“ ‡µ„‡‡£S§Npqq®^½ ¸téºuë† &À××°}ûvtèÐðññQ:,¦ªCY€§{Ƹ¹¹¡]»v0`233Õx5€^½zA.—«µ\¤†aè, T_dX¨Üû÷ïãòåˈ‰‰ÁéÓ§UêååUáPˆÏ>û ]»veXÐAºví WW×J_ëS§NáèÑ£øþûï±fÍLš4 íÚµCÛ¶m±dÉ¥³Øë‹EEE¸{÷.~ÿýwœ;wgΜ©ö3Cõ×~øcÇŽÅsÏ= 0¡¡¡8pà¶mÛ ‘‘‘fÍšáÒ¥KJ·—––ÂÅÅÛ¶mCBB:tè ;vìX•oa² IDATaÁÔÔeee€ââbñ 5Gyª:B |XP5þõë×+]èСCZ„C!ŠŠŠÔ2**J¼jг>|ø|ðA…C!æÍ›wwwxzzª=ÝÆ ƒ\.Ç7ß|S!0”-oݺ333¤¤¤6lØ >oRRLLLÄ(gffÂÈÈ7nÜL™2Ó§O úP–›7oÂÔÔ¿ÿþ;`ÕªUJ{§Ô”GA.—×i¹H ÃÑY¨¾È°PwËŸ¼ÑÆÆ+W®ÔÊ“7–••aÕªUpqq‘‘Z¶l‰W^yEüAÔP=z;wÆ‹/¾ˆ€€Ü¹s§AŸ¿:jzLpQQ®]»†Ï>û ...pqqÁ®]»››+ù{’Rm´°°_|ñ,--áââ‚Ý»w#??_^ÙɇZç“7ÀG}„öíÛã×_Å“'OžžŽ   téÒ ÅÓùš˜˜ˆW¥ 7ÚsrrдiS 11†††|ýõ×puu€£<µ ªÆKKKdeeáÑ£G2dˆV…uß3Ꜽ1==r¹>>>jO·B¡ÀÚµkñòË/ÃØØîîîøþûï(‡…;v 00PéqMš4Ann.’’’мys¥Ã „«VOÏýáïï@õ¡,[·n/ý ùùùhÖ¬?~\«yÊÏϯór‘†¢³0,P}‘a¡î a¡M›6 ÓªËM–gæÌ™hß¾½x¹µôôtÌ;/½ôrss•î[ZOǃæääÀÒÒ±±±())Á'Ÿ|‚Q£FÕËs©Kmß¼y}ô,--1qâD\¹rEò÷$¥Úhll,† SSS,]º´B„{ör“¯¾ú*®]»…¢îëÓ²²2|ñÅprr‚‘‘¬¬¬0uêT¥cÍ?øà¸ººbàÀ OÊþþþ°°°Àž={àèèˆÙ³gÃÉÉ mÚ´ALLL­Æ/ÞV›°PÝøgÏžÖ­[ÃËË 7n„££#€š‡…ììlñŒò‡gܽ{·v » }¹ÉÜÜ\ÿÏ|QQ¾ù昘˜ ..N),„††bòäÉJ÷oÑ¢®_¿Ž¤¤$ØØØˆ·—••aÉ’%èÙ³'<<<СCñܪeY¾|9LLL`oo/jaaQaOŠêà „HÃÕêîàÁƒÑ¶m[¬_¿999âíÚîÞ½‹fÍšUz&èÔÔT•ZÕ±£ªŽõMHH¨òrlááá—““ccc×ÏŒ«:?„;†¾}ûÂÍÍ û÷ï—ü=I©6ºmÛ6ØÙÙ!00qqq†_¼x:u‚‘‘ÆŽ‹ÔÔTq˜¶¬OŸÝÐ×&Êá³gÏ¢GNÍS",XYYaÅŠâž@ÝÎ%ðÏ?ÿTú¼ؼy³RXصk†.ÞG¡xºÇB^^^…ˆsàÀ¸¹¹‰WOúꫯİ êP–={ö`ĈµžÊà9‘†ª/2,ÔÝ™3gbÛ¶mÈËËSº][~À?ü ô?[•ñìñ ªŽUT]ŽmÙ²ex÷Ýw•ž×ÚÚºÁÇP…:?„ÓÓÓñÎ;ïÀÌÌ K—.UÚ½›Rúô¼5³fÍB³fÍ0oÞ¼JJLLÄk¯½†7ß|·nÝR¦-ëSm ™™™°°°Àõë×QVV†)S¦TX×JA}‡…U«Vaݺu6”…«Ü¿¿ÖÓ|íÚ5˜››ãСCxòä ÊÊÊpöìY¼øâ‹¸|ù²Òa-iiiâr€°°0ôíÛ@ŽCÖ¯_/ÒððáCøûûÃËË €êCYîÞ½ +++ñ{2!!ÿùÏj=_u].Rð@t†ª/2,ÔÝóçÏ£   ÂíÚòCxzÍí>}úˆçææÂÚÚZtÛ¶mŽUuìhua¡ªË±}üñǘ3gŽÒ´µmÛ‰‰‰õ2ßê Îᢢ"|þùç055ÅŒ3––&ùû’RmòÏ?ÿĸqã`ccƒM›6UzŸ»wïâÊ•+•~~´e}ª­a¶lÙØÙÙaøðá9GB]©Ï°ðèÑ#äççãÑ£G†M:r¹¼Ò½ôjÂÉ“'Ñ»wo¼øâ‹°´´„‡‡:$/XËÁƒáæægggøùù‰{>233áéé '''øùù!66ÖÖÖ˜7o€ªe€#GŽÀÍÍ ŽŽŽË刎ŽVk¾êº\¤„aè, T_dX¨?µå‡0üøãhÛ¶­øwYYîܹƒ;wîà7Þ@XXX…ãAU;Z]X¨êrl!!!ø÷¿ÿ­4¬eË–âÿöhêþÞ±cÚ¶m‹   üñÇ’¿ÿ(Õ&£££€®]»âÀµ~¼6­OIͩϰ Š%K–@.—‹{Îé q(‹..†¢³0,P}‘a¡þÔ¦Â÷ïßÇsÏ='^½<Ó¦MÃBùÿ]Qu쨪˞©º[DD„xL)Ü»wÏ?ÿÏê¢îáýû÷ÃÍÍ ˆ—üýG©6yòäIôë×½{÷Vë7–6­OIÍ‘*,ìÙ³r¹!!!ž£ú¡¡eѵåR†¢³0,P}‘a¡þÔ¶‹/FëÖ­qòäI””” ;;ëׯ‡¹¹9Ž?^!,¨:vTÕeÏT]Ž-//-[¶ÄéÓÿ¯½sËñ~ü9µ-Š94‡ht0„­…%Is(&æ”M_Œ¶…‰Òìc˜>Í1¢V1ÇaE·†œÊrK ³JV9¤K÷ë÷‡ß}}Juw×Ýåz=çã¡®ëzßW×}ßu¿ŸîûºN °°_ý5¾øâ‹Z;•AÙÂ{÷î…™™ìíí¥öÇ¥uÉ#GŽÀÊÊ 6668qâD•·¯k¿OIåPWXP\¢täÈ‘*þ‰jŽÚø(‹‹†"Z¨TdX¨9ëâ ကtíÚZZZhÞ¼9FŒ˜˜e_ϼ¼ÏŽå_öL&“Ux9¶cÇŽ¡K—.x÷Ýw1lذ—{« (ûBøÐ¡CèÝ»7lmmqòäIµ?þ(­K2,Hu……¢¢"|òÉ'033«scÔ‰˜ Ã- T*2,ÔœR}!\—OnV”}!\݉¥o² ÒD]a¾ûî;˜™™!,,L…?‘øëqaX ¢…aJE†…šSª/„(}U†i¢Î°°ÿ~˜™™aêÔ©*ü‰ÄX Ã- T*2,ÔœR}!̰À°@é«2,Hu†…ììlôêÕ ¸ÿ¾ *q#Öã°@D Ã•Š 5'_‹†JU/Â4QgX€ÿüç?033ÃêÕ«Uô½ˆñ¸0,Ѱ@¥"ÃBÍÉÂâ„aRÕ˰ MÔnܸ333ôïßyyy*ú©Ä Ã- T*2,Ôœ|!,N(U½ ÒDÝa¾úê+˜™™açÎ*ø‰ÞÄv\ˆhaX R‘a¡æä aq°@©êeX&u!,œ>}fff8p rssUðS½ˆí¸0,Ѱ@¥"ÃBÍÉÂâ„aRÕ˰ MêBX€©S§ÂÌÌ *ïMALÇ…aˆ†*jNÅ **N•y<3,PZ¶ª Tœ*û¸QUXHHH€……¬¬¬œœ¬’1ßÄt\ˆhaX R‘a¡æT÷ 9Z»/„(-_†i«ìãFUa|||`ff†©S§B.—«l\±#–ã°@D Ã•Š ”ªF†J˗ϪŒª 999øôÓOaff†ÐÐP•+vÄr\ˆhaX R‘aRÕȉ¥åËçUFU†8qâÌÌÌзo_ܹsG¥c‹1†"Z¨TdX T5râDiùòùA•QÕa¼½½aff† &àßÿUùøb¥®†"Z¨TdX T5râDiùòùA•±&ÂBAAF­öóNÔe—.]ªòã^]ˆhaX R‘aRÕȉ¥åËçUÆš ð÷ߣwïÞjŸÀ×e#""jäØ+ Ã- T*2,Pª9q¢´|ùü ÊXSaˆ†"Z¨TdX T5râDiùòùA•‘a(`X ¢…aJE†JU#'N”–/ŸTˆ†"Z¨TdX T5râDiùòùA•‘a(`X ¢…aJE†JUcu'Nê>Q¥U±¶ŸTš2, D´0,P©È°@©jdX R²¶ŸTš2, D´0,P©È°@©jTUX ¤.£lXØ»w/ÌÍÍaoo¨¨(µ?_©8dX ˆhaX R‘aRÕȰ@¤€²a!$$Ý»w‡ƒƒ¢££Õþ|¥âa(`X ¢…aJE†JU#Ñʆ…Í›7ÃÐÐ#GŽÄåË—Õþ|¥âa(`X ¢…aJE†JU#Ñʆ…eË–AWW_}õ’““Õþ|¥âa(`X ¢…aJE†JU#Ñʄ…ÌÌLÌš5 õë×Ç?ü€¼¼<µ?_©8dX ˆhaX R‘aRÕȰ@¤€2aáÌ™34hLLL°uëVµ?W©xdX ˆhaX R‘aRÕȰ@¤@UÓ'O°hÑ"4nÜ#GŽDll¬ÚŸ«T<2, D´0,P©È°@©jdX R *a!77›6m‚±±1Z·nµk×"??_íÏU*ˆ†"Z¨TdX T5=z}úôAÿþýYå툨LXÈÉÉÁŸþ‰éÓ§£yóæxçw0{öl¤¤¤¨ýyJÅ%ÃQÀ°@D Ã•Š ”ªÆÈÈHX[[£OŸ>Jý aX bÀØØºººhÖ¬Y™êêêBKK šššÐÐÐ@³fÍàî¤$µ?G©ødX ˆhaX R‘aRÕxîÜ9 <Ý»wGhhh•·gX bÀÈÈ7†††F¹jjj¢cÇŽ˜6mNœ8§OŸªýùIÅ)ÃQÀ°@D Ã•Š ”ªÆÄÄDL˜0­[·†¿¿™ë¤§§ãÚµkHMM-µŒaˆ>ú={öÄýû÷ËõÑ£GÈÏÏçùhµeX ˆhaX R‘aRÕ˜••4iÒžžžÈÌÌ,µŽL&ÃèÑ£1iÒ$Ü»w¯Ä2†"”¹Ü$¥Êʰ@0,Ѱ@¥"Ã¥ªsýúõ000ÀàÁƒqîܹRËcccѵkW4jÔãÇÇíÛ·…eê ›6mªcTfüøøx´mÛ¶Âuíìì«ò}ÉdèÔ©“RÛ^ºt©ÒÛßÿiÓ¦!''0a•îSu`X µ)ÃQÀ°@D Ã•Š ”ªÎ˜˜ >­[·ÆâÅ‹qÿþýËcccÑ¥Khhh aÆprrÂ_ý…‚‚Ú r¹zzzj£²ã¿.,<{ö íÚµƒ\.Wù~Tg_XXˆÌÌÌ×®÷êþ7NØ~Ò¤I*ݧêÀ°@kS†¢€aˆ†*(U>„ŸŸ УGøùù!))Iø¬yñ° ¡¡ `ذaHHH¨VX E×®]ѹsgX[[ãúõë€ .ÀÄÄDX¯ø×#FŒ€¦¦&ºv튃¢[·n˜1c,--ahhˆ3gÎTiŒ{÷î•»111åŽëׯ‡‘‘ `mm-ŒU|üÈÈHtèЋ-‚:tè€ððpaŒãÇcôèÑŽ'“ÉУG,X°666066.1FEÛ)&ñæææ ¶9|ø0zôèçÏŸÃÙÙ;v„ÆüüüïX(oW÷ÆŽ HLLÄ’%KJÓWÃByðññA«V­ðá‡"((:t(÷¾z ´6eX ˆhaX R‘aRÕzãÆ xxx ]»vèܹ3¦M›†€€ìÙ³èСC‰3èׯ_ƒ B×®]• ÉÉÉÐÑÑÁÍ›7ëÖ­C¯^½T233¡¥¥àå$USSû÷ï8pÆÆÆU£"*?##5Â;w“'OÆ´iÓJ---lÙ² +++á6<==XáxñññhР0Y ¥¥åk÷£ø$~åʕ¤¦OŸìÚµ vvvËå(**ÂìÙ³qöìÙa¡¼uŠï¿¯¯/ìííahhˆáÇÃÜܽzõºuëJSŸ= ««‹û÷ïãÙ³g°··¯Ö;hmʰ@0,Ѱ@¥"Ã¥ª7>> .Ä|€F¡Q£Fhذ!êÕ«WæåùêÕ«]]]˜ššVùïÕ† ààà |]PP€zõê!;;»JaAWWWX¯°°šššÈÈÈPYX(o|xúô©°lûöí°³³+5~||<š4i"¬wåÊ´oß^øÚÌÌ ÿýw…ãÅÇÇCGG§Ü1ÊÛ®ø$>-- :::(((èëë#)) QQQhÛ¶-:$,Jžc¡¼u^Ýÿ`ÇŽܽ{·Ìcª·¢Ç@@@FŒ!, cX ¢‘a(`X ¢…aJE†JUorr2fÏžfÍš•Ê :::J…¸¸¸”ø^ãÆqëÖ­*…ƒchkkãæÍ›* å/—˱xñbXXXàã?†‘‘lmmKÿê9Š••…Ž;@…ãU4FEÛ½ú±ƒþýûcÏž=Éd°°°¾ kkkèèèÀÅŹ¹¹¥NÞXÖ:Å÷.\(¼û ø»#^=¦Šq+z x{{cÊ”)Â÷/^¼È°@E#ÃQÀ°@D Ã•Š ”ªÖ´´4Ì™3ÚÚÚÐÕÕ…““±oß>ÃÀÀ ÔG!ìììÐ¥K¥> ±qãF8:: _¼üßê§OŸ"&&FFF²ðððrÃB“&M„>{ö šššÈÌ̬ôQÑø»w©)ž/^,uLãVôX»v-ÆŒ#,Û»w/à DÃ- T*2,Pª:óòò°råJ´hÑ;wƦM›““#,/ëäC‡­ÖÉSRRдiSܺu àëë‹~ýú Ë´µµ…ËN:UˆOž:PÑxQÑv¯†…G¡uëÖèÞ½»p‚Ç5kÖàÇ„\.‡\.ǤI“°|ùòa¡¼uŠï?ð¿ËKÞ¸q .,q,·oߎ”اŠÑÑÑhÑ¢233ñüùs 2„aŠF†¢€aˆ†*(UçÏŸÇ!CФIüôÓOÈÎÎ.±üÕËMŽ97oÞDAAõ.7¹gϘššÂÄÄDRR’°lÖ¬YèÒ¥  ___ᤉ`gg‡¦M›bÛ¶m044„»»;Œ¡¯¯/œT°²cDGG—»2™¬Üñ322ЫW/càÀ8þ<ôôôàååUbü;v”:uê„þùçµãU*Ú®¬K;:88”8ydFF† }}}`ôèÑÈÉÉ)Ê[§øþß¼y?þø#à×_ʼn'JÜ®žž¢¢¢JíSEwww´k×½{÷†¿¿? ˽¯^íMˆ†"Z¨TdX Tu£}ûöppp@ttt©å±±±èÚµ+5j„qãÆ!))IXV°P]Êš8‹i|u0mÚ4øûû«{7*EQQ‘ðï?þøæææJŰ@kS†¢€aˆ†*jNÅ p*N«z?|ø³gÏFÆ ñý÷ß—z·BAAd2F…/¿üÉÉÉe>^ÔÃBÕ¸víÚ·o/œ¡.“‘‘!|LB.—còäÉpssSz<†Z›2, D´0,P©È°Psª{bLk7,$&&büøñhÓ¦ Ê\'== HMM-÷ñ¢ª;ñ¿qãºvíZ¦NNNoTXðòò‚¾¾><¨î]©4000@ûöíáèèˆÌÌL¥ÇêÞ½;ŒñÛo¿•iHH>ŒË—/#++Kí¿‡©¸eX ˆhaX R‘a¡æTçD‘(²a!** öööèÙ³'vïÞÍÇ y#éܹ3š7o^nHêÞ½;ÌÍÍakk‹É“'cÓ¦M¸{÷®ÚSqʰ@0,Ѱ@¥"ÃBÍɉ¢8Q6,DFF¢ÿþèÓ§RCøx!b K—.hݺ5œœœJ9räH 6 }ûö…¾¾>7nŒÎ;cîܹˆ‹‹Sûïd*>ˆ†"Z¨TdX¨99Q'ʆ…#GŽÀÊÊ 6668qâ/ääÃ?D·nÝð矖éÅ‹ñÇ`×®]˜7oºuë†Ö­[ÃÝÝׯ_Wûïe*.ˆ†"Z¨TdX¨99Q' „”OeŸùùùHNNF`` >úè#tèÐk׮œ'OÔþ»™ŠG†¢€aˆ†*jNNÅ Ã!åSÕçÇýû÷±páB¼÷Þ{=z4._¾¬ößÍT<2, D´0,P©È°Psr¢(N)ež§N‚:wîŒmÛ¶©ýw3 DÃ- T*2,Ôœœ(І¢ EEExøð!’’’‹ãÇ#44›7oƦM›àçç???x{{ÃÛÛ^^^ðôô„››\]]gΜ OOOÌŸ?ÞÞÞðññŸŸÖ®]‹Í›7cóæÍؽ{7Nž<‰Ë—/ãöíÛxôèäry­üœÊ}ðùçŸcÖ¬YX±b~ûí79r.\@bb"ÒÒÒ——§Öß7999HMMEBBÎ;‡ƒbÛ¶mXºt)ÜÜÜàää„Þ½{WøŽ‡Q£FaþüùضmbbbðôéÓRÇNÙçGhh(ºwïŽaÆáâÅ‹jÿýLÅ!ÃQÀ°@D ÕŠê §NÂÀññÇãÀj?ª¶®L_¼xöíÛ###?F›6mðüùs/ßÉРAáÅ[HH,--hÔ¨îܹ˜}ú”y\àáá_ýgΜQúù±oß>˜››cРA8sæŒÚ‡ DÃ- T*ª3,ÄÆÆbäÈ‘011ÁÖ­[Õ~,Tm]™(>|NNNÂ×ãÇÇîÝ»¼ :::²+W® }ûöÂ×ÅÿÇrûöí°³³P2,¤¥¥AGG}}}$%%!** mÛ¶Å¡C‡„e@És,”·Ž:Qvâô믿âý÷ßWú­ÞŠÿ5ž9s&ñÇ ==]݇C4Èår¤¤¤àرcX»VxŸ IDATv-¾þúk 8°Ü,LŸ>¾¾¾8|ø0þþûoäçç«ýw†:ÍËËÃõë×±oß>,_¾S¦L©ð#Uÿðáð´´Ä€pòäIµÿ¼T2, D´0,P©¨Î°’’‚éÓ§£Y³fðññANNŽÚGU}ôèîܹƒôôôRËêJX=z4´µµ¡«« ]]]hkkÃÁÁÀ˰жm[aÝâ_Ëår,^¼øøãadd[[[¥Ï±Ð¿ìÙ³2™ Â÷CCCamm ¸¸¸ 77·ÔÉËZG(;qòññAãÆáêêŠÔÔÔ*o?`À€r?ÿõ×_cíÚµ8vìRRRjí u•¢¢"ܾ}‡ƪU«ðÕW_•; ¶··‡››Ö­[‡cÇŽñÄU0??ÿý7> ___LŸ>]8QeUÇzÓÏ©CkF†¢€aˆ†*Õrrr°téR¼û8q"®_¿®öãQUSSS„õë×ãþýû%–Õ…°ðèÑ#´lÙRøèðòä‰zzzÈÈȨ0,ìÞ½¦¦¦xòä `Ë–-内àà`L˜0 .ÄŠ+JíGVVlmm±lÙ²r¯ Q|u¢LXHOOÇ·ß~‹FaáÂ…Jÿï÷Ý»w‰uëÖaæÌ™øôÓOËœ,[[[ã‹/¾Àüùó€ƒâòåËÈÊÊRë±S%EEEHOOÇ¥K—°gÏøùùÁÃÃãÆCß¾}Ë<.Æ Ü9sˆS§N½Qc¨KÞ¾}»ÊÛ0,PedX ˆhaX RQa¡ à/6MLLŒÇ«ý˜TÅääd¸ºº¢gÏžð÷÷GJJа¬.„…€€Œ3¦Ô÷¿øâ ¬Y³¦Â°°víZ 6 ÀË3æÛÙÙ¡wïÞJ‡…G¡uëÖèÞ½»p‚Ç5kÖàÇ„\.‡\.ǤI“°|ùòa¡¼uÔ‰2aáØ±c°¶¶F·nÝðûï¿«ô1–žžŽS§Naýúõ˜3g_{¢Á±cÇÂÝÝ«W¯FHHŽ?ŽØØX$%%ááÇ(**Rë1V\äÖ­[¸té"""‚•+WbæÌ™5j,--+<Ñ ““æÍ›‡M›6áìÙ³ÈÌÌTûïZ¾ Tˆ†"Z¨TTwXHMMÅ÷ß=== 4‡U\HNNÆ´iÓ ©©‰nݺaõêÕHNNFAAÝ ½zõÂÎ;K}ïÞ½033«0,ddd W¯^066ÆÀqþüyèééÁËË«ÌËM:88ÀÊÊJø:##C‡…¾¾> 0zôhäää” å­£Nª²²²àéé ---Œ7×®]«ñÇÝãÇqõêU9rAAAX°`\\\Ê=§@y±prrÂäÉ“áîîoooøùùá×_ÅŽ;°oß>DFFâüù󈎎~­çÎCdd$°}ûvlذ~~~X¼x1¾ûî;¸¸¸à³Ï>«ôåÍÍÍ1xð`L:‹-ÂÆqìØ1\»v ÙÙÙjîӪɰ@•‘a(`X ¢…aJEu‡…‚‚œ?£GFË–-1pà@üòË/HHHÀÓ§OÕ~|^§",hhh@SS|ð–/_ŽÛ·o׉°P›L›6 þþþêÞjS•°ððáC¬^½íÛ·‡Ö¯_¯öÇdVV®\¹‚ƒ"00‹-¬Y³àââ‚‘#G–{.‡ÚÔÂÂÂÆìÙ³±dÉlذáááHHHÀ“'OÔ~,©êdX ÊȰ@0,Ѱ@¥b] OŸ>Edd$ÆŒ===têÔ ãÆÃO?ý„7bïÞ½8tèŽ9Rçܱc† !.ÃÛÛ¦¦¦’ ×®]Cûöí…ó1ˆ™×……œœܾ}»w£#Þyç4kÖ .ÍÛñóòò––†ÄÄD\¸pAx,oذ«W¯ÆÏ?ÿŒ `Μ9puuÅ´iÓ^ë×_9sæ`áÂ…øùçŸáçç‡ 6à÷ßGDD.^¼ˆëׯ#==]òW`¢ Tˆ†"Z¨T¬ a¡ àådíâÅ‹X¸p!lmm¡§§‡&Mš S§N033ƒ¥¥%¬¬¬êœxï½÷„° °cÇŽÐ××GÏž=Õýë¬Æñòò‚¾¾><¨î]Q ÆÆÆhܸ1455_«††ôõõ±téÒ2¯ B)})ÃUF†¢€aˆ†*ëJXPøÏ?ÿàÔ©SX½z5f̘Ï?ÿööö°µµÅ€`cc 0@Pñ½âßõ{¯þ»øzå-/kÜW¿ß·o_´mÛVÒaáMÃÈÈ7.uŸ*|뭷ЦMØÚÚâçŸF\\ÿžÒ×Ȱ@•‘a(`X ¢…aJźæççãŸþA\\¢¢¢pòäIœ8q¢Î†áÇ—ø(„‘‘–,Y"©B¼I(> ‘ŸŸÿZÕý<¡T,2,PedX ˆhaX R±®†±XÖÉ—-[V'OÞ(—˱bÅ tîÜ5BË–-ñÙgŸáæÍ›µº………˜;w.455‘™™Y«·]”¹Ü$¥´b¨22, D´0,P©È°P=‹_n²k×®ðõõ­S—›,Îwß}‡N:áôéÓxþü9RSSáéé‰÷Þ{ÙÙÙ%Ö-**ª±ý>|8~üñGÔ¯_ŸaR‰È°@•‘a(`X ¢…aJE†…Ꙝœ WWWôèÑëÖ­CJJа¬.…… aƸråJ©eIII€Ë—/ãÃ?„³³3lll¡¡¡èÚµ+:wî kkk\¿~pᘘ˜cÿ:&&ݺuÃŒ3`ii CCCœ9sFXW&“Ã¥’a*#ÃQÀ°@D Ã•Š Õ355FZZZ‰eu),ìÛ·ÆÆÆ®mmmìܹœœ á£ëÖ­C¯^½Td2455±ÿ~Àʼm†J¥#ÃUF†¢€aˆ†*ªç£Gpûöí2/5X—¦M›Ð·o_áëììlèéé #>>o¿ý¶ð1ˆ 6ÀÁÁAئ  õêÕCvvök®®®°¬°°šššÈÈÈ(±O ”JG†ªŒ DÃ- T*2,Ôœu),}Š˜˜ ËÂÃÃK„…&Mš@.—ž={Væ (•Ž TÕòòò’’‚¸¸8œ>}ááá Ö-[ðË/¿`åÊ•X²d <==áææWWW¸ººbÒ¤Ipvv†³³3œœœàèèGGG 4666¯µÿþ•ZÏÆÆöööÂø£Fn×ÅÅEØ777xzzÂÛÛ«V­B`` ¶lÙ‚°°0DDDàÌ™3ˆGZZòóóký8W†"Z¨TdX¨9ëRX€E‹¡]»vˆŒŒDaa!²²²°víZèêê"""¢TXHIIAÓ¦MqëÖ-€¯¯/úõë',ÓÖÖFNN`êÔ©%ÂBƒ  غu+ºtéRj(•Ž Tk",<|ø2™ ûöíÚ5kàîîŽ &`È!°´´þHÍ>}ú`èС˜8q"<<<àïïC‡!>>?VùýPUˆhaX R‘a¡æ¬kaеkWhii¡yóæ1bbbb TX€={öÀÔÔ&&&8p p ˜5kºté‚AƒÁ××W8A£L&ƒ¡¡!ÜÝÝall }}}œ={••---hiiACCCø÷ƒj鼆JU/ÃUFU„…””ìÚµ ^^^°··íÛÊÊJ˜`Ϙ1^^^ðööÆêÕ«„mÛ¶!,, ÇŽÃÙ³gèèhÄÅÅ!11‰‰‰¸}û6RSS‘ššŠÌÌLdgg«ÔŒŒ aü¤¤$ávãââ„ý‰ŠŠBDD°uëVÁ××ÞÞÞ˜7oÜÜÜ0a <¸RAÅÁÁ ,Àþýû‘žž®‚¿¶Uƒaˆ†*jκj™L†N:©{7”†aRÕ˰@•±:a!66S§N…¹¹y‰ òÀáìì ///âèÑ£ˆ‹‹ÍGjм¼<Ü»wqqq8pàüýýáááqãÆÁÆÆ¦Ä1´°°À7ß|ƒ7nÔÚþ1,Ѱ@¥"ÃBÍɰ N(U½ T• {öì‚‚••æÏŸ½{÷"99YÅ1¤\.ÇßÿÌ;Wx‡C¯^½púôéZÙ†"Z¨TdX¨9Ä Ã¥ª·ºaAÝŸ?§ÕSÙDzaÁÚÚfff Bnn®ŠÿJììløøøÀÌÌ Ÿ}öY­Ü&Ã- T*2,ÔœR b‡aRÕ˰ m•}Ü(·ûìÙ3ÿ… ²³³kõuÃ- T*2,Ôœ â„aRÕ«ª°@Ä…ºÃÂСCñ믿âÞ½{*þɤ‰\.GRRKœ³6`X ¢…aJE†…š“/„Šåª—aAš¨;,\»v ?ýôŒO?ýóçÏGHH.\¸€{÷î¡°°PÅ?ñ›ÃóçÏq÷î]œ={Û·oÇܹsakk‹¡C‡âçŸÆ­[·© T*2,Ôœ|!,N(U½ ÒDÝa!$$€ôôt„‡‡ã¿ÿý/¦OŸXZZÂÁÁ_}õ¼¼¼°lÙ2c×®]8yò$._¾Œ›7o"55Ož<ÁóçÏUyxj•gÏžáÑ£GHMMÅõë׋ãÇ#$$AAAXºt)<==1uêT 2–––>|8¾ùæ,]º‘‘‘øçŸ¹¹¹X¿~=Ã!•aJE†…š“/„Šåª—aAš¨;,Ìœ9 Àܹs±sçNüõ×_(**Öû÷ß‘œœŒ˜˜„‡‡cçÎøå—_àíí wwwLž<ãÆƒ££#lllлwoôêÕ 666pttÄØ±cáììŒÿû¿ÿƒ««+¾ýö[xzzÂÓÓ?ýô¼½½±råJøùù©Ô+VÀÛÛ‹-nï믿†««+\\\àììŒ1cÆÀÁÁ666°°°€¥¥%lmmáèèˆñãÇcÊ”)˜;w.~þùg"$$øóÏ?‘––†/^ÇéÅ‹HHHÀ–-[0kÖ, 0^^^ „T†*jNuŸ,‹VOu?~(}“dX&ê Ïž=Ãýû÷±ÿ~,X°°¶¶ÆÄ‰1þ|#""ñññ¸ÿ> ^;ö¿ÿþ‹ììl¤¦¦â¯¿þBbb"®\¹‚èèhœ?‘‘‘ˆŒŒÄÞ½{†íÛ·cóæÍ*uÇŽ Ãþýû…Û»xñ"¢££‡ÄÄDܺu iiiÈÎÎ. Ê#//©©©¸zõ*>Œ€€Ì›7ãÇG¿~ýàääooo9rYYYÈÉÉaX ¤20,P©È°Psª{bL(­+2,Hu‡…²NÞøäÉÄÅÅáÀð÷÷‡‡‡&Nœˆ¡C‡ÂÊÊ }úôÁ°aÃðå—_bæÌ™ðòò‚··7Ö¬Yƒàà`üöÛo Cdd$Î;‡èèhDGGãêÕ«HLLDbb"îܹƒÔÔT¤¦¦"++ ÙÙÙ%ÌËË«ôÏ’——WjûÌÌLaü¤¤$ávãââ„ý9{ö,"""†­[·"((¾¾¾ðööƼyó0cÆ Lœ8C† ••úöí |ùå—ðòòB`` Ž=Šk×®áéÓ§xòFB”‚aJE†J)¥5-Â4QwXPæäùùùHKKÃÕ«WqæÌ„‡‡#,, [¶lÁ/¿ü‚•+WbÉ’%ðôô„››\]]áêêŠI“&ÁÙÙÎÎÎprr‚££#1hÐ ØØØ”°oß¾•ŽÜýúõ+µ½½½½0þèÑ£…ÛuqqögÆŒ˜7o¼½½áëë‹   lݺaaaˆˆˆ@TTžžŽ‚‚ÒïÔàÉ Q T*2,PJ)­i¤‰ºÃBqxòFÕ¼QÃ!•€aJE†J)¥5-Â4©Ka¡,xòÆÊ¼±ºÇY0,Ѱ@¥â¾}û`aaAƒáÌ™3jßJ)¥ožGEŸ>}пDFFVy{†qR×Â2¼©'oT†B*ÕŠáááèÓ§>ùä¥^ìQJ)¥¯322ýû÷GŸ>}päÈ‘*oϰ NÞİ@þÃ!•€aJÅ‹/ÂÞÞ¦¦¦Ø³gÚ÷‡RJé›ç¹sç0dÈtïÞ!!!UÞžEq°ðfð@H%`X RñîÝ»˜0aš7oäææª}Ÿ(¥”¾Y^¿~ÎÎÎhÓ¦ üýýË\'==111¸uëV©eœ(Š“š Ïž=Cnnn™oóçã¥v`X ¤0,P©˜““ƒùóç£iÓ¦puu-ó¥”RZ³²²àááÆÃÃÃYYY¥ÖILLĸqã0iÒ$\¿~½Ä2NÅIM†…þù+W®ÄÆñï¿ÿ–y»¤faX ¤0,P)©¸2D·nݰ}ûvúè#?~ “ÉЩS§jÝî¥K—*=ÆþýûѹsgèêêÂÚÚ7oÞ¬Öm×dXxðà\]]a``€   —‚Tõ„÷Õû©Y³fppp@ttt¥Ç¨ÌcH÷wm°@H%`X R2993gÎDË–-áèèˆÈÈHÆJ)¥*õÒ¥K1bZµj…Å‹ãÁƒ%–_½z;w†††Þzë-Œ5 2™ Õ nnn022ÂéÓ§ñüùsܹs#GŽ„‰‰ rss+ÜV.—COOOéÛ®h Å„5%%EøzÇŽÐÑÑAff¦J&š………ÈÌÌ|íz)))ÐÕÕÅÙ³gQTT„ùóçÃÆÆ¦Z·]aACCmÛ¶…ŸŸ JÜ®ªxõ~JOODzeËиqcœ;wîµÛWö1İP> D´0,P©yêÔ)888 eË–2dÖ¯_ëׯ#''GíûF)¥Tü>|ø~~~000@·nÝàçç‡{÷î Ë‹‡ 4jÔˆ‰‰©ÖæÞ½{hРJ|ÿÅ‹066†¿¿?.\¸aYñ¯GŒMMMtíÚD·nÝ0cÆ XZZÂÐÐgΜ)µMEcÜ»wOXçÕ «‚îÝ»cÿþý%&š>>>hÕª>üðC¡C‡J¾cA&“¡GX°`lll`llŒððp@jj*„ñ._¾Œ¶mÛVåP—¢¶Â‚††Þ{ï=¬X±¹¹¹5,X°ýúõ¾^¿~=ŒŒŒ```kkká¾~õþ/o=™LCCC¸»»C__†††8yòäkÇþü9œÑ±cG`üøñÈÏÏ8p¦¦¦èر#lmm‘‘‘¡²ã°@H%`X RóéÓ§8tèFŒ–-[ÂÄÄ'N„··76oÞŒ}ûöáСC8r䥔Rª”›7oƘ1cвeK´mÛvvvøæ›o°hÑ"¸»»£U«VÂDQCC 6„½½=>øà¥'0Û·oG×®]Ë\öÃ?ÀÉÉ©ÂI{ff&´´´¼œøijjbÿþý^NÚŒKmSÑÅ)oÂÚ­[79r¤Òa!!!ººº¸ÿ>ž={{{{a»âa!>> 4&ì!!!°´´,óØ,]ºcÆŒ)sYe©Í° ¡¡-Zà¿ÿý/zöìY+aáÎ;¨_¿>ž?ŽŒŒ 4jÔwîÜLž<Ó¦MPòþ¯h=™L† `ãÆ€Í›7ÃÈÈèµÛíÚµ vvvËå(**ÂìÙ³qöìYܽ{Mš4A\\`ÅŠ1b„ÊŽ Ã!•€aJÑœœDEEá‡~@ÿþýÑ¢E èèèÀÐÐæææ°´´„••¥”Rª´}ô:tèmmmÔ¯_ZZZhܸ1´µµQ¯^½E 4hÐM›6E·nÝ”zM·víZØÚÚ–¹, ýû÷¯RXÐÕÕÖ+,,„¦¦&222Tþý÷_ìØ±-Z´À“'O*JLÃÂÂÊ :::ÂzW®\AûöíKíWxx8ÞÿýRéªbbbhjjÖˆ¯>V444дiS´iÓ={ö¬Ö¾§¼°ðøñchhhàáÇ€§OŸ ˶oß;;;¥ïÿòÖ“ÉdÐÖÖFQQ€—ïDÐÔÔ>ÊRÞvQQQhÛ¶-:„‚‚a   6Lø:'' 6,u²KeaX ¤0,P)›žžŽÈÈH¬\¹nnn5j „ÀÆÆ†RJ)­–}ûöEÏž=Ñ©S'´nÝÍ›7‡ŽŽêׯ_j¢Ø°aC¼ûî»èÞ½»R¯évî܉>ø ÌeÿùÏðùçŸW),”C[[7oÞ¬VXPœðwÞÁÇŒóçÏ ·W™°àíí)S¦Ë.^¼XnX(þñ†W¿^NXMLLpëÖ­2YU011A“&MÊ 5eÓ¦MѺukôèÑ£Úû¯ ¼°pùòehiiáÅ‹ËåX¼x1,,,ðñÇÃÈÈHZÅïÿŠÖ“Éd¥B¶¶6þúë¯ ·€ÐÐPX[[CGG...ÈÍÍÅÒ¥K¡­­:6mÚ÷ïßWÉqaX ¤0,Púò,Þééé¸rå Μ9ƒ“'Oâĉ”RJiµŒŒÄþýû±eËbÁ‚xï½÷Êü(DçΕžÀ¤¥¥¡aƸ|ùr‰ï¡sçÎFLLŒð–sàåÿØ—š4i¹\xöì™ð?Ê•£8åMX ¿víÚ[Ø»w¯Raaß¾}055Ezzz™ûSUÏüü|¥>Û·o‡‡‡,--aff†^½záôéÓµ² D´0,PJ)¥”Ö ĉºÂÄÆÆbêÔ©B`Pد_?8;;ÃËË 8zô(®^½Š´´4äçç«ð§yyy¸wïâââpàÀøûûÃÃÃãÆCŸ>}JC |óÍ7¸qãF­íÃ- ”RJ)¥uC†q¢Î°  %%»ví‚——ìííKLËÒÊÊ C‡Åĉ1sæLxyyÁÛÛ«W¯FPP¶mÛ†°°0;v çÎCtt4¢££‡ÄÄD$&&âöíÛHMMEjj*²²²]¼¼¼Jï^^^©í333…ñ“’’„Û‹‹öçìÙ³ˆˆˆ@XX¶nÝŠ   øúúÂÛÛóæÍÃŒ30a }ú`èСøâ‹/àáá:tñññÈÎÎVùýPUˆhaX ”RJ)­ª{ÒE«§²÷{M„…ב——‡””ÄÅÅáôéÓGXX¶lÙ‚_~ù+W®Ä’%Kàéé 777¸ººÂÕÕ“&M‚³³3œáääGGG8::bРA°±±)aß¾}+}ìúõëWj{{{{aüQ£F ·ëââ"ì››<==áííU«V!00[¶lAXX"""pæÌÄÇÇ‹æ# D´0,PJ)¥”Rª>ÕHÝ„aˆ†J)¥”RJÕ'ÃQÀ°@D Ã¥”RJ)¥ê“a(`X ¢…aRJ)¥”Rõɰ@0,Ѱ@)¥”RJ©údX ˆhaX ”RJ)¥T}2, D´0,PJ)¥”Rª>ˆ†"Z(¥”RJ)UŸ DÃ- ”RJ)¥”ªO†¢€aˆ†J)¥”RJÕ'ÃQÀ°@D Ã¥”RJ)¥ê“a(`X ¢…aRJ)¥”Rõɰ@0,Ѱ@)¥”RJ©údX ˆhaX ”RJ)¥T}2, D´0,PJ)¥”Rª>ˆ†"Z(¥”RJ)UŸ DÃ- ”RJ)¥”ªO†¢€aˆ†J)¥”RJÕ'ÃQÀ°@D Ã¥”RJ)¥ê“a(`X ¢…aRJ)¥”Rõɰ@0,Ñ£GRJ)¥”RJÕ`DD„º§¤ŽÀ°@!„B!„¥aX „B!„BˆÒ0,B!„B!Di!„B!„¢4 „B!„BQ†B!„B!„( Ã!„B!„B”†aB!„B!Jð@!„B!„¥aX „B!„BˆÒ0,B!„B!Di!„B!„¢4 „B!„BQ†B!„B!„( Ã!„B!„B”†aB!„B!Jð@!„B!„¥aX „B!„BˆÒ0,B!„B!Diþ@Ë* (øÞ´IEND®B`‚hindsight-0.12.7/docs/index.md000066400000000000000000000033051301315165600161450ustar00rootroot00000000000000# Hindsight ## Overview Hindsight is a C based data processing infrastructure based on the [lua sandbox] (https://github.com/mozilla-services/lua_sandbox) project. I have received several inquiries about a lighter weight and faster data pipeline with delivery guarantees to replace [Heka](https://github.com/mozilla-services/heka). Hindsight is that light weight skeleton around the same lua sandbox offering 'at least once' delivery semantics. So how much lighter and faster? see: [Performance](performance.md) ## Table of Contents * [Architecture](architecture.md) * [Configuration](configuration.md) * [Sandbox Documentation](http://mozilla-services.github.io/lua_sandbox/heka/index.html) ### Differences from Heka 1. [Sandbox API Differences](http://mozilla-services.github.io/lua_sandbox/heka/index.html#sandbox-api-changes-from-the-go-heka-sandbox) 1. The message matcher now uses Lua string match patterns instead of RE2 expressions. 1. Looping messages in Heka (injecting messages back to an earlier point in the pipeline) has always been a bad idea so it was removed. Most looping requirements can be flattened out: i.e., alerting can be handled in the output plugins, aggregation/sessionization can be handled in the inputs. 1. Splitters/Decoders/Encoders no longer exist they simply become part of the input/output plugins respectively. The common functionality should now be broken out into Lua modules to allow for re-use in different plugins. 1. Checkpoint are all managed by the Hindsight infrastructure (so much of the burden is removed from the plugin writer, this also alters the plugin API slightly and should greatly lower the checkpoint related IOPS when compared to Heka). hindsight-0.12.7/docs/performance.md000066400000000000000000000036151301315165600173430ustar00rootroot00000000000000## Performance It is very preliminary but I setup a test using some Nginx logs. The logs were split into three files each containing 1,002,646 log lines. The test consists of parsing the Nginx logs, converting them to a Heka protobuf stream, and writing them back to disk. The configurations were setup to be as equivalent as posssible (single output, similar flush intervals, and individual readers for each file in the concurrent test). The configs can be found here: https://github.com/mozilla-services/hindsight/tree/master/benchmarks. ### Test Hardware * Lenovo x230 Thinkpad * Memory 8GB * Processor Intel Core i7-3520M CPU @ 2.90GHz x 4 * Disk SSD ### Test 1 - Processing a single log file #### Hindsight (0.8) * RSS: 3276 * VIRT: 253148 * SHR: 1140 * Processing time: 9.88 seconds * Log Lines/sec: 101482 #### Heka (0.9) * RSS: 39764 * VIRT: 662204 * SHR: 5240 * Processing time: 63 seconds * Log Lines/sec: 15,915 #### Summary * Approximately 12x less resident memory * Approximately 2.5 less virtual memory * Over 6x the throughput * Hindsight (kill -9) Caused some corruption in the stream but no messages were lost and 3 were duplicated. * Error unmarshalling message at offset: 275181251 error: proto: field/encoding mismatch: wrong type for field * Corruption detected at offset: 275181251 bytes: 317 * Processed: 1002650, matched: 1002649 messages * Heka (kill -9) record count: 1,002,595 so at least 51 records were lost. ### Test 2 - Processing all three log files concurrently #### Hindsight (0.8) * RSS: 5488 * VIRT: 327872 * SHR: 1128 * Processing time: 17.84 seconds * Log Lines/sec: 168,606 #### Heka (0.9) * RSS: 42060 * VIRT: 801432 * SHR: 5256 * Processing time: 198 second * Log Lines/sec: 15,192 # actually slower than processing them sequentially #### Summary * Approximately 7.7x less resident memory * Approximately 2.4x less virtual memory * Over 11x the throughput hindsight-0.12.7/sandboxes/000077500000000000000000000000001301315165600155515ustar00rootroot00000000000000hindsight-0.12.7/sandboxes/input/000077500000000000000000000000001301315165600167105ustar00rootroot00000000000000hindsight-0.12.7/sandboxes/input/prune_input.lua000066400000000000000000000057461301315165600217770ustar00rootroot00000000000000-- This Source Code Form is subject to the terms of the Mozilla Public -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at http://mozilla.org/MPL/2.0/. --[[ Hindsight input log file pruner Deletes the log files generated by the input and/or analysis plugins when all the analysis and output plugins are done consumining them (within ticker_interval seconds). *Example Hindsight Configuration* .. code-block:: lua filename = "prune_input.lua" ticker_interval = 60 input = true analysis = true exit_on_stall = false -- When true, causes the plugin to stop/abort when the checkpoints are no longer advancing. -- Use this option to allow hindsight_cli to exit when the inputs are finished. This plugin/option -- is typically used when streaming a large data set from something like s3 i.e., running -- a report. --]] require "io" require "os" require "string" require "math" local l = require "lpeg" l.locale(l) local output_path = read_config("output_path") -- provided by Hindsight local exit_on_stall = read_config("exit_on_stall") local input = read_config("input") and l.P"input" local analysis = read_config("analysis") and l.P"analysis" if not (input or analysis) then error("either input or analysis (or both) must be set") end local function get_min(t, n, i, o) local elt = t[n] or {min = math.huge} if i < elt.min then elt.min = i elt.off = o end t[n] = elt return t end local pair = l.P"'" * l.Cg(l.digit^1/tonumber * ":" * l.C(l.digit^1)) * "'" local ignore = (l.P(1) - "\n")^0 * "\n" local plugins if input and analysis then plugins = input + analysis else plugins = input or analysis end local line = l.P"_G['" * l.Cg(l.C(plugins) * "->" * (l.P(1) - "'")^1 * "']" * l.space^0 * "=" * l.space^0 * pair) * l.space^0 + ignore local grammar = l.Cf(l.Ct("") * line^1, get_min) local old_cp = {} function process_message() local fh = io.open(output_path .. "/hindsight.cp") if not fh then return 0 end -- checkpoint file not available yet local s = fh:read("*a") fh:close() if s then local stalled = true local t = grammar:match(s) if t then for name, elt in pairs(t) do local cp = old_cp[name] or {} if stalled and cp.min ~= elt.min or cp.off ~= elt.off then stalled = false end cp.off = elt.off if cp.min ~= elt.min then cp.min = elt.min for i = cp.min - 1, 0, -1 do local r = os.remove(string.format("%s/%s/%d.log", output_path, name, i)) if not r then break end end end old_cp[name] = cp end if stalled and exit_on_stall then error("input/analysis have stopped") end end end return 0 end hindsight-0.12.7/src/000077500000000000000000000000001301315165600143525ustar00rootroot00000000000000hindsight-0.12.7/src/CMakeLists.txt000066400000000000000000000016571301315165600171230ustar00rootroot00000000000000# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. set(HINDSIGHT_SRC hindsight.c hs_analysis_plugins.c hs_checkpoint_reader.c hs_checkpoint_writer.c hs_config.c hs_input.c hs_input_plugins.c hs_logger.c hs_output.c hs_output_plugins.c hs_util.c ) set(HINDSIGHT_LIBS ${LUASANDBOX_LIBRARIES} ${UNIX_LIBRARIES}) add_executable(hindsight ${HINDSIGHT_SRC}) set(HINDSIGHT_LIBS ${LUASANDBOX_LIBRARIES} ${UNIX_LIBRARIES}) target_link_libraries(hindsight ${HINDSIGHT_LIBS}) add_executable(hindsight_cli ${HINDSIGHT_SRC}) target_compile_definitions(hindsight_cli PUBLIC "-DHINDSIGHT_CLI") target_link_libraries(hindsight_cli ${HINDSIGHT_LIBS}) install(TARGETS hindsight DESTINATION ${CMAKE_INSTALL_BINDIR}) install(TARGETS hindsight_cli DESTINATION ${CMAKE_INSTALL_BINDIR}) add_subdirectory(test) hindsight-0.12.7/src/hindsight.c000066400000000000000000000144451301315165600165070ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** @brief Hindsight main executable @file */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hs_analysis_plugins.h" #include "hs_checkpoint_writer.h" #include "hs_config.h" #include "hs_input.h" #include "hs_input_plugins.h" #include "hs_logger.h" #include "hs_output_plugins.h" static const char g_module[] = "hindsight"; static sem_t g_shutdown; void* sig_handler(void *arg) { (void)arg; int sig; sigset_t signal_set; sigemptyset(&signal_set); sigaddset(&signal_set, SIGINT); sigaddset(&signal_set, SIGTERM); for (;;) { sigwait(&signal_set, &sig); if (sig == SIGINT || sig == SIGTERM) { hs_log(NULL, g_module, 6, "stop signal received"); sem_post(&g_shutdown); break; } else { hs_log(NULL, g_module, 6, "unexpected signal received %d", sig); } } return (void *)0; } int main(int argc, char *argv[]) { if (argc < 2 || argc > 3) { fprintf(stderr, "usage: %s [loglevel]\n", argv[0]); return EXIT_FAILURE; } if (sem_init(&g_shutdown, 0, 1)) { perror("g_shutdown sem_init failed"); exit(EXIT_FAILURE); } if (sem_wait(&g_shutdown)) { perror("g_shutdown sem_wait failed"); exit(EXIT_FAILURE); } int loglevel = 6; if (argc == 3) { loglevel = atoi(argv[2]); if (loglevel < 0 || loglevel > 7) { loglevel = 6; } } hs_init_log(loglevel); hs_config cfg; if (hs_load_config(argv[1], &cfg)) { return EXIT_FAILURE; } int watch[3] = {0}; int load = 0; if (cfg.load_path[0] != 0) { load = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); if (load < 0) { hs_free_config(&cfg); perror("inotify_init failed"); return EXIT_FAILURE; } watch[0] = inotify_add_watch(load, cfg.load_path_input, IN_CLOSE_WRITE | IN_MOVED_TO); watch[1] = inotify_add_watch(load, cfg.load_path_analysis, IN_CLOSE_WRITE | IN_MOVED_TO); watch[2] = inotify_add_watch(load, cfg.load_path_output, IN_CLOSE_WRITE | IN_MOVED_TO); } hs_checkpoint_reader cpr; hs_init_checkpoint_reader(&cpr, cfg.output_path); hs_cleanup_checkpoints(&cpr, cfg.run_path, cfg.analysis_threads); hs_log(NULL, g_module, 6, "starting"); sigset_t signal_set; sigfillset(&signal_set); if (pthread_sigmask(SIG_SETMASK, &signal_set, NULL)) { perror("pthread_sigmask failed"); exit(EXIT_FAILURE); } pthread_t sig_thread; if (pthread_create(&sig_thread, NULL, sig_handler, NULL)) { hs_log(NULL, g_module, 1, "signal handler could not be setup"); return EXIT_FAILURE; } hs_input_plugins ips; hs_init_input_plugins(&ips, &cfg, &cpr); hs_load_input_startup(&ips); hs_analysis_plugins aps; hs_init_analysis_plugins(&aps, &cfg, &cpr); hs_load_analysis_startup(&aps); hs_start_analysis_threads(&aps); hs_output_plugins ops; hs_init_output_plugins(&ops, &cfg, &cpr); hs_load_output_startup(&ops); hs_checkpoint_writer cpw; hs_init_checkpoint_writer(&cpw, &ips, &aps, &ops, cfg.output_path); struct timespec ts; const struct inotify_event *event; char inotify_buf[sizeof(struct inotify_event) + FILENAME_MAX + 1] __attribute__((aligned(__alignof__(struct inotify_event)))); while (true) { if (clock_gettime(CLOCK_REALTIME, &ts) == -1) { hs_log(NULL, g_module, 3, "clock_gettime failed"); ts.tv_sec = time(NULL); ts.tv_nsec = 0; } ts.tv_sec += 1; if (!sem_timedwait(&g_shutdown, &ts)) { sem_post(&g_shutdown); break; // shutting down } hs_write_checkpoints(&cpw, &cpr); if (load) { for (;;){ ssize_t len = read(load, inotify_buf, sizeof(inotify_buf)); if (len == -1 && errno != EAGAIN) { hs_log(NULL, g_module, 1, "inotify read failure"); sem_post(&g_shutdown); } if (len <= 0) break; for (char *ptr = inotify_buf; ptr < inotify_buf + len; ptr += sizeof(struct inotify_event) + event->len) { event = (const struct inotify_event *)ptr; if (event->len) { if (watch[1] == event->wd) { hs_load_analysis_dynamic(&aps, event->name); } else if (watch[0] == event->wd) { hs_load_input_dynamic(&ips, event->name); } else if (watch[2] == event->wd) { hs_load_output_dynamic(&ops, event->name); } } } } } #ifdef HINDSIGHT_CLI if (ips.list_cnt == 0) { hs_log(NULL, g_module, 6, "input plugins have exited; " "cascading shutdown initiated"); pthread_kill(sig_thread, SIGINT); // when all the inputs are done, exit } #endif } if (load) { for (int i = 0; i < 3; ++i) { inotify_rm_watch(load, watch[i]); } close(load); } #ifdef HINDSIGHT_CLI hs_stop_input_plugins(&ips); hs_wait_input_plugins(&ips); hs_write_checkpoints(&cpw, &cpr); hs_free_input_plugins(&ips); hs_stop_analysis_plugins(&aps); hs_wait_analysis_plugins(&aps); hs_write_checkpoints(&cpw, &cpr); hs_free_analysis_plugins(&aps); hs_stop_output_plugins(&ops); hs_wait_output_plugins(&ops); hs_write_checkpoints(&cpw, &cpr); hs_free_output_plugins(&ops); #else // non CLI mode should shut everything down immediately hs_stop_input_plugins(&ips); hs_stop_analysis_plugins(&aps); hs_stop_output_plugins(&ops); hs_wait_input_plugins(&ips); hs_wait_analysis_plugins(&aps); hs_wait_output_plugins(&ops); hs_write_checkpoints(&cpw, &cpr); hs_free_input_plugins(&ips); hs_free_analysis_plugins(&aps); hs_free_output_plugins(&ops); #endif hs_free_checkpoint_writer(&cpw); hs_free_checkpoint_reader(&cpr); hs_free_config(&cfg); pthread_join(sig_thread, NULL); hs_log(NULL, g_module, 6, "exiting"); hs_free_log(); sem_destroy(&g_shutdown); return 0; } hindsight-0.12.7/src/hs_analysis_plugins.c000066400000000000000000000543331301315165600206040ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** @brief Hindsight analysis plugin loader @file */ #include "hs_analysis_plugins.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hs_input.h" #include "hs_logger.h" #include "hs_output.h" #include "hs_util.h" static const char g_module[] = "analysis_plugins"; static int inject_message(void *parent, const char *pb, size_t pb_len) { static bool backpressure = false; static char header[14]; hs_analysis_plugin *p = parent; int rv = 1; if (p->im_limit == 0) return rv; --p->im_limit; bool bp; pthread_mutex_lock(&p->at->plugins->output.lock); int len = lsb_pb_output_varint(header + 3, pb_len); int tlen = 4 + len + pb_len; header[0] = 0x1e; header[1] = (char)(len + 1); header[2] = 0x08; header[3 + len] = 0x1f; if (fwrite(header, 4 + len, 1, p->at->plugins->output.fh) == 1 && fwrite(pb, pb_len, 1, p->at->plugins->output.fh) == 1) { p->at->plugins->output.cp.offset += tlen; if (p->at->plugins->output.cp.offset >= p->at->plugins->cfg->output_size) { ++p->at->plugins->output.cp.id; hs_open_output_file(&p->at->plugins->output); if (p->at->plugins->cfg->backpressure && p->at->plugins->output.cp.id - p->at->plugins->output.min_cp_id > p->at->plugins->cfg->backpressure) { backpressure = true; hs_log(NULL, g_module, 4, "applying backpressure (checkpoint)"); } if (!backpressure && p->at->plugins->cfg->backpressure_df) { unsigned df = hs_disk_free_ob(p->at->plugins->output.path, p->at->plugins->cfg->output_size); if (df <= p->at->plugins->cfg->backpressure_df) { backpressure = true; hs_log(NULL, g_module, 4, "applying backpressure (disk)"); } } } if (backpressure) { bool release_dfbp = true; if (p->at->plugins->cfg->backpressure_df) { unsigned df = hs_disk_free_ob(p->at->plugins->output.path, p->at->plugins->cfg->output_size); release_dfbp = (df > p->at->plugins->cfg->backpressure_df); } // even if we triggered on disk space continue to backpressure // until the queue is caught up too if (p->at->plugins->output.cp.id == p->at->plugins->output.min_cp_id && release_dfbp) { backpressure = false; hs_log(NULL, g_module, 4, "releasing backpressure"); } } rv = 0; } else { hs_log(NULL, g_module, 0, "inject_message fwrite failed: %s", strerror(ferror(p->at->plugins->output.fh))); exit(EXIT_FAILURE); } bp = backpressure; pthread_mutex_unlock(&p->at->plugins->output.lock); if (bp) { usleep(100000); // throttle to 10 messages per second } return rv; } static void destroy_analysis_plugin(hs_analysis_plugin *p) { if (!p) return; char *msg = lsb_heka_destroy_sandbox(p->hsb); if (msg) { hs_log(NULL, p->name, 3, "lsb_heka_destroy_sandbox failed: %s", msg); free(msg); } lsb_destroy_message_matcher(p->mm); free(p->name); free(p); } static hs_analysis_plugin* create_analysis_plugin(const hs_config *cfg, hs_sandbox_config *sbc) { char lua_file[HS_MAX_PATH]; if (!hs_find_lua(cfg, sbc, hs_analysis_dir, lua_file, sizeof(lua_file))) { hs_log(NULL, g_module, 3, "%s failed to find the specified lua filename: %s" , sbc->cfg_name, sbc->filename); return NULL; } hs_analysis_plugin *p = calloc(1, sizeof(hs_analysis_plugin)); if (!p) { hs_log(NULL, g_module, 2, "%s hs_analysis_plugin memory allocation failed", sbc->cfg_name); return NULL; } p->pm_im_limit = sbc->pm_im_limit; p->te_im_limit = sbc->te_im_limit; p->shutdown_terminate = sbc->shutdown_terminate; p->ticker_interval = sbc->ticker_interval; int stagger = p->ticker_interval > 60 ? 60 : p->ticker_interval; // distribute when the timer_events will fire if (stagger) { p->ticker_expires = time(NULL) + rand() % stagger; } p->mm = lsb_create_message_matcher(sbc->message_matcher); if (!p->mm) { hs_log(NULL, g_module, 3, "%s invalid message_matcher: %s", sbc->cfg_name, sbc->message_matcher); destroy_analysis_plugin(p); return NULL; } size_t len = strlen(sbc->cfg_name) + 1; p->name = malloc(len); if (!p->name) { hs_log(NULL, g_module, 2, "%s name memory allocation failed", sbc->cfg_name); destroy_analysis_plugin(p); } memcpy(p->name, sbc->cfg_name, len); char *state_file = NULL; if (sbc->preserve_data) { size_t len = strlen(cfg->output_path) + strlen(sbc->cfg_name) + 7; state_file = malloc(len); if (!state_file) { hs_log(NULL, g_module, 2, "%s state_file memory allocation failed", sbc->cfg_name); destroy_analysis_plugin(p); return NULL; } int ret = snprintf(state_file, len, "%s/%s.data", cfg->output_path, sbc->cfg_name); if (ret < 0 || ret > (int)len - 1) { hs_log(NULL, g_module, 3, "%s failed to construct the state_file path", sbc->cfg_name); destroy_analysis_plugin(p); return NULL; } } lsb_output_buffer ob; if (lsb_init_output_buffer(&ob, 8 * 1024)) { hs_log(NULL, g_module, 3, "%s configuration memory allocation failed", sbc->cfg_name); free(state_file); destroy_analysis_plugin(p); return NULL; } if (!hs_get_full_config(&ob, 'a', cfg, sbc)) { hs_log(NULL, g_module, 3, "%s hs_get_full_config failed", sbc->cfg_name); lsb_free_output_buffer(&ob); free(state_file); destroy_analysis_plugin(p); return NULL; } lsb_logger logger = { .context = NULL, .cb = hs_log }; p->hsb = lsb_heka_create_analysis(p, lua_file, state_file, ob.buf, &logger, inject_message); lsb_free_output_buffer(&ob); free(sbc->cfg_lua); sbc->cfg_lua = NULL; free(state_file); if (!p->hsb) { hs_log(NULL, g_module, 3, "%s lsb_heka_create_analysis failed", sbc->cfg_name); destroy_analysis_plugin(p); return NULL; } return p; } static void remove_plugin(hs_analysis_thread *at, int idx) { hs_log(NULL, at->list[idx]->name, 6, "removing from thread: %d", at->tid); hs_analysis_plugin *p = at->list[idx]; at->list[idx] = NULL; destroy_analysis_plugin(p); p = NULL; --at->list_cnt; } static void remove_from_analysis_plugins(hs_analysis_thread *at, const char *name) { const size_t tlen = strlen(hs_analysis_dir) + 1; pthread_mutex_lock(&at->list_lock); for (int i = 0; i < at->list_cap; ++i) { if (!at->list[i]) continue; char *pos = at->list[i]->name + tlen; if (strstr(name, pos) && strlen(pos) == strlen(name) - HS_EXT_LEN) { remove_plugin(at, i); break; } } pthread_mutex_unlock(&at->list_lock); } static void add_plugin(hs_analysis_thread *at, hs_analysis_plugin *p, int idx) { hs_log(NULL, p->name, 6, "adding to thread: %d", at->tid); at->list[idx] = p; ++at->list_cnt; } static void add_to_analysis_plugins(const hs_sandbox_config *cfg, hs_analysis_plugins *plugins, hs_analysis_plugin *p) { int idx = -1; int thread = cfg->thread % plugins->cfg->analysis_threads; hs_analysis_thread *at = &plugins->list[thread]; p->at = at; pthread_mutex_lock(&at->list_lock); // todo shrink it down if there are a lot of empty slots for (int i = 0; i < at->list_cap; ++i) { if (!at->list[i]) { idx = i; break; } } if (idx != -1) { add_plugin(at, p, idx); } else { ++at->list_cap; // todo probably don't want to grow it by 1 hs_analysis_plugin **tmp = realloc(at->list, sizeof(hs_analysis_plugin *) * at->list_cap); idx = at->list_cap - 1; if (tmp) { at->list = tmp; add_plugin(at, p, idx); } else { hs_log(NULL, g_module, 0, "plugins realloc failed"); exit(EXIT_FAILURE); } } pthread_mutex_unlock(&at->list_lock); } static void init_analysis_thread(hs_analysis_plugins *plugins, int tid) { hs_analysis_thread *at = &plugins->list[tid]; at->plugins = plugins; at->list = NULL; at->msg = NULL; if (pthread_mutex_init(&at->list_lock, NULL)) { perror("list_lock pthread_mutex_init failed"); exit(EXIT_FAILURE); } if (pthread_mutex_init(&at->cp_lock, NULL)) { perror("cp_lock pthread_mutex_init failed"); exit(EXIT_FAILURE); } at->cp.id = 0; at->cp.offset = 0; at->current_t = 0; at->list_cap = 0; at->list_cnt = 0; at->tid = tid; at->stop = false; at->sample = false; char name[255]; int n = snprintf(name, sizeof name, "%s%d", hs_analysis_dir, tid); if (n < 0 || n >= (int)sizeof name) { hs_log(NULL, g_module, 0, "name exceeded the buffer length: %s%d", hs_analysis_dir, tid); exit(EXIT_FAILURE); } hs_init_input(&at->input, plugins->cfg->max_message_size, plugins->cfg->output_path, name); } static void free_analysis_thread(hs_analysis_thread *at) { pthread_mutex_destroy(&at->cp_lock); pthread_mutex_destroy(&at->list_lock); at->plugins = NULL; for (int i = 0; i < at->list_cap; ++i) { if (!at->list[i]) continue; remove_plugin(at, i); } free(at->list); at->list = NULL; at->msg = NULL; at->cp.id = 0; at->cp.offset = 0; at->current_t = 0; at->list_cap = 0; at->list_cnt = 0; at->tid = 0; hs_free_input(&at->input); } static void terminate_sandbox(hs_analysis_thread *at, int i) { const char *err = lsb_heka_get_error(at->list[i]->hsb); hs_log(NULL, at->list[i]->name, 3, "terminated: %s", err); hs_save_termination_err(at->plugins->cfg, at->list[i]->name, err); if (at->list[i]->shutdown_terminate) { hs_log(NULL, at->list[i]->name, 6, "shutting down on terminate"); kill(getpid(), SIGTERM); } remove_plugin(at, i); } static void analyze_message(hs_analysis_thread *at, bool sample) { hs_analysis_plugin *p = NULL; int ret; pthread_mutex_lock(&at->list_lock); for (int i = 0; i < at->list_cap; ++i) { if (!at->list[i]) continue; p = at->list[i]; ret = 0; unsigned long long start; if (at->msg->raw.s) { // non idle/empty message if (sample) start = lsb_get_time(); bool matched = lsb_eval_message_matcher(p->mm, at->msg); if (sample) { lsb_update_running_stats(&p->mms, lsb_get_time() - start); } if (matched) { p->im_limit = p->pm_im_limit; ret = lsb_heka_pm_analysis(p->hsb, at->msg, sample); if (ret < 0) { const char *err = lsb_heka_get_error(p->hsb); if (strlen(err) > 0) { hs_log(NULL, p->name, 4, "received: %d msg: %s", ret, err); } } } } if (sample) { p->stats = lsb_heka_get_stats(p->hsb); } if (ret <= 0 && p->ticker_interval && at->current_t >= p->ticker_expires) { p->im_limit = p->te_im_limit; ret = lsb_heka_timer_event(p->hsb, at->current_t, false); p->ticker_expires = at->current_t + p->ticker_interval; } if (ret > 0) terminate_sandbox(at, i); } pthread_mutex_unlock(&at->list_lock); } static void shutdown_timer_event(hs_analysis_thread *at) { pthread_mutex_lock(&at->list_lock); for (int i = 0; i < at->list_cap; ++i) { if (!at->list[i]) continue; at->list[i]->im_limit = at->list[i]->te_im_limit; if (lsb_heka_timer_event(at->list[i]->hsb, at->current_t, true)) { terminate_sandbox(at, i); } } pthread_mutex_unlock(&at->list_lock); } static void* input_thread(void *arg) { hs_analysis_thread *at = (hs_analysis_thread *)arg; hs_log(NULL, g_module, 6, "starting thread: %d", at->tid); lsb_heka_message msg; lsb_init_heka_message(&msg, 8); size_t discarded_bytes; size_t bytes_read = 0; lsb_logger logger = { .context = NULL, .cb = hs_log }; bool stop = false; bool sample = false; bool next_available = false; #ifdef HINDSIGHT_CLI bool input_stop = false; while (!(stop && input_stop)) { #else while (!stop) { #endif pthread_mutex_lock(&at->cp_lock); stop = at->stop; sample = at->sample; pthread_mutex_unlock(&at->cp_lock); if (at->input.fh) { if (lsb_find_heka_message(&msg, &at->input.ib, true, &discarded_bytes, &logger)) { at->msg = &msg; at->current_t = time(NULL); analyze_message(at, sample); // advance the checkpoint pthread_mutex_lock(&at->cp_lock); at->cp.id = at->input.cp.id; at->cp.offset = at->input.cp.offset - (at->input.ib.readpos - at->input.ib.scanpos); if (sample) at->sample = false; pthread_mutex_unlock(&at->cp_lock); } else { bytes_read = hs_read_file(&at->input); } if (!bytes_read && (at->input.cp.offset >= at->plugins->cfg->output_size || next_available)) { next_available = hs_open_file(&at->input, hs_input_dir, at->input.cp.id + 1); #ifdef HINDSIGHT_CLI if (!next_available && stop) { input_stop = true; } #endif } } else { // still waiting on the first file next_available = hs_open_file(&at->input, hs_input_dir, at->input.cp.id); #ifdef HINDSIGHT_CLI if (!next_available && stop) { input_stop = true; } #endif } if (bytes_read || at->msg) { at->msg = NULL; } else { // trigger any pending timer events lsb_clear_heka_message(&msg); // create an idle/empty message at->msg = &msg; at->current_t = time(NULL); analyze_message(at, sample); if (sample) { pthread_mutex_lock(&at->cp_lock); at->sample = false; pthread_mutex_unlock(&at->cp_lock); } at->msg = NULL; sleep(1); } } shutdown_timer_event(at); lsb_free_heka_message(&msg); hs_log(NULL, g_module, 6, "exiting thread: %d", at->tid); pthread_exit(NULL); } void hs_init_analysis_plugins(hs_analysis_plugins *plugins, hs_config *cfg, hs_checkpoint_reader *cpr) { hs_init_output(&plugins->output, cfg->output_path, hs_analysis_dir); plugins->thread_cnt = cfg->analysis_threads; plugins->cfg = cfg; plugins->cpr = cpr; plugins->list = malloc(sizeof(hs_analysis_thread) * cfg->analysis_threads); for (unsigned i = 0; i < cfg->analysis_threads; ++i) { init_analysis_thread(plugins, i); } plugins->threads = malloc(sizeof(pthread_t *) * (cfg->analysis_threads)); } void hs_stop_analysis_plugins(hs_analysis_plugins *plugins) { for (int i = 0; i < plugins->thread_cnt; ++i) { hs_analysis_thread *at = &plugins->list[i]; pthread_mutex_lock(&at->cp_lock); at->stop = true; pthread_mutex_unlock(&at->cp_lock); } } void hs_wait_analysis_plugins(hs_analysis_plugins *plugins) { void *thread_result; for (int i = 0; i < plugins->thread_cnt; ++i) { if (pthread_join(plugins->threads[i], &thread_result)) { hs_log(NULL, g_module, 3, "thread could not be joined"); } } free(plugins->threads); plugins->threads = NULL; } void hs_free_analysis_plugins(hs_analysis_plugins *plugins) { for (int i = 0; i < plugins->thread_cnt; ++i) { free_analysis_thread(&plugins->list[i]); } free(plugins->list); plugins->list = NULL; hs_free_output(&plugins->output); plugins->cfg = NULL; plugins->thread_cnt = 0; } static void process_lua(hs_analysis_plugins *plugins, const char *name) { hs_config *cfg = plugins->cfg; const char *lpath = cfg->load_path_analysis; const char *rpath = cfg->run_path_analysis; char lua_lpath[HS_MAX_PATH]; char lua_rpath[HS_MAX_PATH]; char cfg_lpath[HS_MAX_PATH]; char cfg_rpath[HS_MAX_PATH]; size_t tlen = strlen(hs_analysis_dir) + 1; // move the Lua to the run directory if (hs_get_fqfn(lpath, name, lua_lpath, sizeof(lua_lpath))) { hs_log(NULL, g_module, 0, "load lua path too long"); exit(EXIT_FAILURE); } if (hs_get_fqfn(rpath, name, lua_rpath, sizeof(lua_rpath))) { hs_log(NULL, g_module, 0, "run lua path too long"); exit(EXIT_FAILURE); } if (rename(lua_lpath, lua_rpath)) { hs_log(NULL, g_module, 3, "failed to move: %s to %s errno: %d", lua_lpath, lua_rpath, errno); return; } for (int t = 0; t < plugins->thread_cnt; ++t) { // restart any plugins using this Lua code hs_analysis_thread *at = &plugins->list[t]; pthread_mutex_lock(&at->list_lock); for (int i = 0; i < at->list_cap; ++i) { if (!at->list[i]) continue; hs_analysis_plugin *p = at->list[i]; if (strcmp(lua_rpath, lsb_heka_get_lua_file(p->hsb)) == 0) { int ret = snprintf(cfg_lpath, HS_MAX_PATH, "%s/%s%s", lpath, p->name + tlen, hs_cfg_ext); if (ret < 0 || ret > HS_MAX_PATH - 1) { hs_log(NULL, g_module, 0, "load cfg path too long"); exit(EXIT_FAILURE); } ret = snprintf(cfg_rpath, HS_MAX_PATH, "%s/%s%s", rpath, p->name + tlen, hs_cfg_ext); if (ret < 0 || ret > HS_MAX_PATH - 1) { hs_log(NULL, g_module, 0, "run cfg path too long"); exit(EXIT_FAILURE); } // if no new cfg was provided, move the existing cfg to the load // directory if (!hs_file_exists(cfg_lpath)) { if (rename(cfg_rpath, cfg_lpath)) { hs_log(NULL, g_module, 3, "failed to move: %s to %s errno: %d", cfg_rpath, cfg_lpath, errno); } } } } pthread_mutex_unlock(&at->list_lock); } } static int get_thread_id(const char *lpath, const char *rpath, const char *name) { if (hs_has_ext(name, hs_cfg_ext)) { int otid = -1, ntid = -2; hs_sandbox_config sbc; if (hs_load_sandbox_config(lpath, name, &sbc, NULL, 'a')) { ntid = sbc.thread; hs_free_sandbox_config(&sbc); } if (hs_load_sandbox_config(rpath, name, &sbc, NULL, 'a')) { otid = sbc.thread; hs_free_sandbox_config(&sbc); } else { otid = ntid; } if (otid != ntid) { // mis-matched cfgs so remove the load .cfg char path[HS_MAX_PATH]; if (hs_get_fqfn(lpath, name, path, sizeof(path))) { hs_log(NULL, g_module, 0, "load off path too long"); exit(EXIT_FAILURE); } if (unlink(path)) { hs_log(NULL, g_module, 3, "failed to delete: %s errno: %d", path, errno); } return -1; } return otid; } else if (hs_has_ext(name, hs_off_ext)) { char cfg[HS_MAX_PATH]; strcpy(cfg, name); strcpy(cfg + strlen(name) - HS_EXT_LEN, hs_cfg_ext); hs_sandbox_config sbc; if (hs_load_sandbox_config(rpath, cfg, &sbc, NULL, 'a')) { hs_free_sandbox_config(&sbc); return sbc.thread; } // no config was found so remove the .off flag char path[HS_MAX_PATH]; if (hs_get_fqfn(lpath, name, path, sizeof(path))) { hs_log(NULL, g_module, 0, "load off path too long"); exit(EXIT_FAILURE); } if (unlink(path)) { hs_log(NULL, g_module, 3, "failed to delete: %s errno: %d", path, errno); } } return -2; } void hs_load_analysis_startup(hs_analysis_plugins *plugins) { hs_config *cfg = plugins->cfg; const char *dir = cfg->run_path_analysis; DIR *dp = opendir(dir); if (dp == NULL) { hs_log(NULL, g_module, 0, "%s: %s", dir, strerror(errno)); exit(EXIT_FAILURE); } struct dirent *entry; while ((entry = readdir(dp))) { hs_sandbox_config sbc; if (hs_load_sandbox_config(dir, entry->d_name, &sbc, &cfg->apd, 'a')) { hs_analysis_plugin *p = create_analysis_plugin(cfg, &sbc); if (p) { add_to_analysis_plugins(&sbc, plugins, p); } else { hs_log(NULL, g_module, 3, "%s create_analysis_plugin failed", sbc.cfg_name); } hs_free_sandbox_config(&sbc); } } closedir(dp); } void hs_load_analysis_dynamic(hs_analysis_plugins *plugins, const char *name) { hs_config *cfg = plugins->cfg; const char *lpath = cfg->load_path_analysis; const char *rpath = cfg->run_path_analysis; if (hs_has_ext(name, hs_lua_ext)) { process_lua(plugins, name); return; } int tid = get_thread_id(lpath, rpath, name); if (tid < 0) { if (tid == -1) { hs_log(NULL, g_module, 3, "plugin cannot be restarted on a different " "thread: %s", name); } hs_log(NULL, g_module, 7, "%s ignored %s", __func__, name); return; } tid %= plugins->thread_cnt; switch (hs_process_load_cfg(lpath, rpath, name)) { case 0: remove_from_analysis_plugins(&plugins->list[tid], name); break; case 1: // load remove_from_analysis_plugins(&plugins->list[tid], name); { hs_sandbox_config sbc; if (hs_load_sandbox_config(rpath, name, &sbc, &cfg->apd, 'a')) { hs_analysis_plugin *p = create_analysis_plugin(cfg, &sbc); if (p) { add_to_analysis_plugins(&sbc, plugins, p); } else { hs_log(NULL, g_module, 3, "%s create_analysis_plugin failed", sbc.cfg_name); } hs_free_sandbox_config(&sbc); } } break; default: hs_log(NULL, g_module, 7, "%s ignored %s", __func__, name); break; } } void hs_start_analysis_threads(hs_analysis_plugins *plugins) { for (int i = 0; i < plugins->thread_cnt; ++i) { hs_analysis_thread *at = &plugins->list[i]; hs_lookup_input_checkpoint(at->plugins->cpr, hs_input_dir, at->input.name, at->plugins->cfg->output_path, &at->input.cp); at->cp.id = at->input.cp.id; at->cp.offset = at->input.cp.offset; if (pthread_create(&plugins->threads[i], NULL, input_thread, (void *)at)) { perror("hs_start_analysis_threads pthread_create failed"); exit(EXIT_FAILURE); } } } hindsight-0.12.7/src/hs_analysis_plugins.h000066400000000000000000000047601301315165600206100ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** Hindsight analysis sandbox loader @file */ #ifndef hs_analysis_plugins_h_ #define hs_analysis_plugins_h_ #include #include #include #include #include #include #include #include #include "hs_config.h" #include "hs_input.h" #include "hs_output.h" typedef struct hs_analysis_plugin hs_analysis_plugin; typedef struct hs_analysis_plugins hs_analysis_plugins; typedef struct hs_analysis_thread hs_analysis_thread; struct hs_analysis_plugin { char *name; lsb_heka_sandbox *hsb; lsb_message_matcher *mm; hs_analysis_thread *at; lsb_running_stats mms; lsb_heka_stats stats; int ticker_interval; bool shutdown_terminate; unsigned char im_limit; unsigned char pm_im_limit; unsigned char te_im_limit; time_t ticker_expires; }; struct hs_analysis_plugins { hs_analysis_thread *list; pthread_t *threads; hs_config *cfg; hs_checkpoint_reader *cpr; int thread_cnt; hs_output output; }; struct hs_analysis_thread { hs_analysis_plugins *plugins; hs_analysis_plugin **list; lsb_heka_message *msg; pthread_mutex_t list_lock; pthread_mutex_t cp_lock; hs_checkpoint cp; time_t current_t; int list_cap; int list_cnt; int tid; hs_input input; bool stop; bool sample; }; void hs_init_analysis_plugins(hs_analysis_plugins *plugins, hs_config *cfg, hs_checkpoint_reader *cpr); void hs_free_analysis_plugins(hs_analysis_plugins *plugins); void hs_start_analysis_threads(hs_analysis_plugins *plugins); void hs_load_analysis_startup(hs_analysis_plugins *plugins); void hs_load_analysis_dynamic(hs_analysis_plugins *plugins, const char *name); void hs_start_analysis_input(hs_analysis_plugins *plugins, pthread_t *t); void hs_stop_analysis_plugins(hs_analysis_plugins *plugins); void hs_wait_analysis_plugins(hs_analysis_plugins *plugins); #endif hindsight-0.12.7/src/hs_checkpoint_reader.c000066400000000000000000000221021301315165600206560ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** @brief Hindsight checkpoint_reader implementation @file */ #include "hs_checkpoint_reader.h" #include #include #include #include #include #include #include #include "hs_config.h" #include "hs_logger.h" #include "hs_util.h" static const char g_module[] = "checkpoint_reader"; static bool extract_id(const char *fn, unsigned long long *id) { size_t l = strlen(fn); size_t i = 0; for (; i < l && isdigit(fn[i]); ++i); if (i > 0 && i + 4 == l && strncmp(fn + i, ".log", 4) == 0) { *id = strtoull(fn, NULL, 10); return true; } return false; } static size_t find_first_id(const char *path) { struct dirent *entry; DIR *dp = opendir(path); if (dp == NULL) { hs_log(NULL, g_module, 0, "path does not exist: %s", path); exit(EXIT_FAILURE); } unsigned long long file_id = ULLONG_MAX, current_id = 0; while ((entry = readdir(dp))) { if (extract_id(entry->d_name, ¤t_id)) { if (current_id < file_id) { file_id = current_id; } } } closedir(dp); return file_id == ULLONG_MAX ? 0 : file_id; } static void remove_checkpoint(hs_checkpoint_reader *cpr, const char *key) { lua_pushnil(cpr->values); lua_setfield(cpr->values, LUA_GLOBALSINDEX, key); hs_log(NULL, g_module, 6, "checkpoint removed: %s", key); } void hs_init_checkpoint_reader(hs_checkpoint_reader *cpr, const char *path) { char fqfn[HS_MAX_PATH]; if (hs_get_fqfn(path, "hindsight.cp", fqfn, sizeof(fqfn))) { hs_log(NULL, g_module, 0, "checkpoint name exceeds the max length: %d", sizeof(fqfn)); exit(EXIT_FAILURE); } cpr->values = luaL_newstate(); if (!cpr->values) { hs_log(NULL, g_module, 0, "checkpoint_reader luaL_newstate failed"); exit(EXIT_FAILURE); } else { lua_pushvalue(cpr->values, LUA_GLOBALSINDEX); lua_setglobal(cpr->values, "_G"); } if (hs_file_exists(fqfn)) { if (luaL_dofile(cpr->values, fqfn)) { hs_log(NULL, g_module, 0, "loading %s failed: %s", fqfn, lua_tostring(cpr->values, -1)); exit(EXIT_FAILURE); } } if (pthread_mutex_init(&cpr->lock, NULL)) { perror("checkpoint reader pthread_mutex_init failed"); exit(EXIT_FAILURE); } } void hs_free_checkpoint_reader(hs_checkpoint_reader *cpr) { if (cpr->values) lua_close(cpr->values); cpr->values = NULL; pthread_mutex_destroy(&cpr->lock); } int hs_load_checkpoint(lua_State *L, int idx, hs_ip_checkpoint *cp) { size_t len; switch (lua_type(L, idx)) { case LUA_TSTRING: pthread_mutex_lock(&cp->lock); if (cp->type == HS_CP_NUMERIC) cp->value.s = NULL; cp->type = HS_CP_STRING; const char *tmp = lua_tolstring(L, idx, &len); cp->len = (unsigned)len; ++len; if (tmp && len <= HS_MAX_IP_CHECKPOINT) { if (len > cp->cap) { free(cp->value.s); cp->value.s = malloc(len); if (!cp->value.s) { cp->len = 0; cp->cap = 0; hs_log(NULL, g_module, 0, "malloc failed"); pthread_mutex_unlock(&cp->lock); return 1; } cp->cap = len; } memcpy(cp->value.s, tmp, len); } else { pthread_mutex_unlock(&cp->lock); return 1; } pthread_mutex_unlock(&cp->lock); break; case LUA_TNUMBER: pthread_mutex_lock(&cp->lock); if (cp->type == HS_CP_STRING) { free(cp->value.s); cp->value.s = NULL; cp->len = 0; cp->cap = 0; } cp->type = HS_CP_NUMERIC; cp->value.d = lua_tonumber(L, idx); pthread_mutex_unlock(&cp->lock); break; case LUA_TNONE: case LUA_TNIL: break; default: return 1; } return 0; } void hs_lookup_checkpoint(hs_checkpoint_reader *cpr, const char *key, hs_ip_checkpoint *cp) { pthread_mutex_lock(&cpr->lock); lua_getglobal(cpr->values, key); hs_load_checkpoint(cpr->values, -1, cp); lua_pop(cpr->values, 1); pthread_mutex_unlock(&cpr->lock); } void hs_update_checkpoint(hs_checkpoint_reader *cpr, const char *key, hs_ip_checkpoint *cp) { pthread_mutex_lock(&cpr->lock); pthread_mutex_lock(&cp->lock); switch (cp->type) { case HS_CP_STRING: lua_pushlstring(cpr->values, cp->value.s, cp->len); break; case HS_CP_NUMERIC: lua_pushnumber(cpr->values, cp->value.d); break; default: lua_pushnil(cpr->values); break; } lua_setglobal(cpr->values, key); pthread_mutex_unlock(&cp->lock); pthread_mutex_unlock(&cpr->lock); } void hs_lookup_input_checkpoint(hs_checkpoint_reader *cpr, const char *subdir, const char *key, const char *path, hs_checkpoint *cp) { const char *pos = NULL; pthread_mutex_lock(&cpr->lock); if (key) { lua_pushfstring(cpr->values, "%s->%s", subdir, key); } else { lua_pushstring(cpr->values, subdir); } lua_gettable(cpr->values, LUA_GLOBALSINDEX); if (lua_type(cpr->values, -1) == LUA_TSTRING) { const char *tmp = lua_tostring(cpr->values, -1); if (tmp) { pos = strchr(tmp, ':'); if (pos) { cp->id = strtoull(tmp, NULL, 10); cp->offset = (size_t)strtoull(pos + 1, NULL, 10); } } } lua_pop(cpr->values, 1); pthread_mutex_unlock(&cpr->lock); if (!pos) { if (path) { char fqfn[HS_MAX_PATH]; if (hs_get_fqfn(path, subdir, fqfn, sizeof(fqfn))) { hs_log(NULL, g_module, 0, "checkpoint name exceeds the max length: %d", sizeof(fqfn)); exit(EXIT_FAILURE); } cp->id = find_first_id(fqfn); } else { if (key) { hs_lookup_input_checkpoint(cpr, subdir, NULL, path, cp); } } } } void hs_update_input_checkpoint(hs_checkpoint_reader *cpr, const char *subdir, const char *key, const hs_checkpoint *cp) { pthread_mutex_lock(&cpr->lock); if (key) { lua_pushfstring(cpr->values, "%s->%s", subdir, key); } else { lua_pushstring(cpr->values, subdir); } lua_pushfstring(cpr->values, "%f:%f", (lua_Number)cp->id, (lua_Number)cp->offset); lua_settable(cpr->values, LUA_GLOBALSINDEX); pthread_mutex_unlock(&cpr->lock); } int hs_output_checkpoints(hs_checkpoint_reader *cpr, FILE *fh) { int rv = 0; const char *key; pthread_mutex_lock(&cpr->lock); lua_pushnil(cpr->values); while (!rv && lua_next(cpr->values, LUA_GLOBALSINDEX) != 0) { if (lua_type(cpr->values, -2) == LUA_TSTRING) { key = lua_tostring(cpr->values, -2); switch (lua_type(cpr->values, -1)) { case LUA_TSTRING: if (fprintf(fh, "_G['%s'] = '", key) < 0) { rv = 1; break; } if (hs_output_lua_string(fh, lua_tostring(cpr->values, -1))) { rv = 1; break; } rv = (fwrite("'\n", 2, 1, fh) != 1); break; case LUA_TNUMBER: rv = (fprintf(fh, "_G['%s'] = %.17g\n", key, lua_tonumber(cpr->values, -1)) < 0); break; } } lua_pop(cpr->values, 1); } pthread_mutex_unlock(&cpr->lock); return rv; } void hs_remove_checkpoint(hs_checkpoint_reader *cpr, const char *key) { pthread_mutex_lock(&cpr->lock); remove_checkpoint(cpr, key); pthread_mutex_unlock(&cpr->lock); } void hs_cleanup_checkpoints(hs_checkpoint_reader *cpr, const char *run_path, int analysis_threads) { const char *key; const char *subkey; int analysis_thread; char type[HS_MAX_PATH]; char name[HS_MAX_PATH]; char path[HS_MAX_PATH]; pthread_mutex_lock(&cpr->lock); lua_pushnil(cpr->values); while (lua_next(cpr->values, LUA_GLOBALSINDEX) != 0) { if (lua_type(cpr->values, -2) == LUA_TSTRING) { key = lua_tostring(cpr->values, -2); subkey = strstr(key, "->"); subkey = subkey ? subkey + strlen("->") : key; if (sscanf(subkey, "analysis%d", &analysis_thread) == 1) { if (analysis_thread >= analysis_threads) { // analysis thread does not exist anymore remove_checkpoint(cpr, key); } } else if (sscanf(subkey, "%[^.].%s", type, name) == 2) { int ret = snprintf(path, HS_MAX_PATH, "%s/%s/%s%s", run_path, type, name, hs_cfg_ext); if (ret < 0 || ret > (int)sizeof(path) - 1) { hs_log(NULL, g_module, 0, "path too long"); exit(EXIT_FAILURE); } if (!hs_file_exists(path)) { // plugin does not exist anymore remove_checkpoint(cpr, key); } } } lua_pop(cpr->values, 1); } pthread_mutex_unlock(&cpr->lock); } hindsight-0.12.7/src/hs_checkpoint_reader.h000066400000000000000000000050361301315165600206720ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** Hindsight checkpoint reader functions and structures @file */ #ifndef hs_checkpoint_reader_h_ #define hs_checkpoint_reader_h_ #include #include #include #include #define HS_MAX_IP_CHECKPOINT 4096 typedef enum { HS_CP_NONE, HS_CP_NUMERIC, HS_CP_STRING } hs_ip_checkpoint_type; typedef struct hs_ip_checkpoint { pthread_mutex_t lock; hs_ip_checkpoint_type type; unsigned len; // string checkpoint length unsigned cap; // string checkpoint capacity union { double d; // numeric checkpoint char *s; // string checkpoint } value; } hs_ip_checkpoint; typedef struct hs_checkpoint { unsigned long long id; size_t offset; } hs_checkpoint; typedef struct hs_checkpoint_pair { hs_checkpoint input; hs_checkpoint analysis; } hs_checkpoint_pair; typedef struct hs_checkpoint_reader { lua_State *values; pthread_mutex_t lock; } hs_checkpoint_reader; void hs_init_checkpoint_reader(hs_checkpoint_reader *cpr, const char *path); void hs_free_checkpoint_reader(hs_checkpoint_reader *cpr); int hs_load_checkpoint(lua_State *L, int idx, hs_ip_checkpoint *cp); void hs_lookup_checkpoint(hs_checkpoint_reader *cpr, const char *key, hs_ip_checkpoint *cp); void hs_update_checkpoint(hs_checkpoint_reader *cpr, const char *key, hs_ip_checkpoint *cp); void hs_lookup_input_checkpoint(hs_checkpoint_reader *cpr, const char *key, const char *path, const char *subdir, hs_checkpoint *cp); void hs_update_input_checkpoint(hs_checkpoint_reader *cpr, const char *subdir, const char *key, const hs_checkpoint *cp); int hs_output_checkpoints(hs_checkpoint_reader *cpr, FILE *fh); void hs_remove_checkpoint(hs_checkpoint_reader *cpr, const char *key); void hs_cleanup_checkpoints(hs_checkpoint_reader *cpr, const char *run_path, int analysis_threads); #endif hindsight-0.12.7/src/hs_checkpoint_writer.c000066400000000000000000000212721301315165600207370ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** @brief Hindsight checkpoint_writer implementation @file */ #include "hs_checkpoint_writer.h" #include #include #include #include #include "hs_analysis_plugins.h" #include "hs_input_plugins.h" #include "hs_logger.h" #include "hs_output.h" #include "hs_output_plugins.h" #include "hs_util.h" static const char g_module[] = "checkpoint_writer"; static void allocate_filename(const char *path, const char *name, char **filename) { char fqfn[HS_MAX_PATH]; if (hs_get_fqfn(path, name, fqfn, sizeof(fqfn))) { hs_log(NULL, g_module, 0, "%s/%s exceeds the max length: %d", path, name, sizeof(fqfn)); exit(EXIT_FAILURE); } *filename = malloc(strlen(fqfn) + 1); if (!*filename) { hs_log(NULL, g_module, 0, "%s/%s malloc failed", path, name); exit(EXIT_FAILURE); } strcpy(*filename, fqfn); } void hs_init_checkpoint_writer(hs_checkpoint_writer *cpw, hs_input_plugins *ip, hs_analysis_plugins *ap, hs_output_plugins *op, const char *path) { cpw->input_plugins = ip; cpw->analysis_plugins = ap; cpw->output_plugins = op; allocate_filename(path, "hindsight.cp", &cpw->cp_path); allocate_filename(path, "hindsight.cp.tmp", &cpw->cp_path_tmp); allocate_filename(path, "hindsight.tsv", &cpw->tsv_path); allocate_filename(path, "hindsight.tsv.tmp", &cpw->tsv_path_tmp); } void hs_free_checkpoint_writer(hs_checkpoint_writer *cpw) { cpw->analysis_plugins = NULL; cpw->input_plugins = NULL; cpw->output_plugins = NULL; free(cpw->cp_path); cpw->cp_path = NULL; free(cpw->tsv_path); cpw->tsv_path = NULL; free(cpw->cp_path_tmp); cpw->cp_path_tmp = NULL; free(cpw->tsv_path_tmp); cpw->tsv_path_tmp = NULL; } void hs_write_checkpoints(hs_checkpoint_writer *cpw, hs_checkpoint_reader *cpr) { static int cnt = 0; static bool sample = false; static hs_checkpoint hscp = { .id = 0, .offset = 0 }; unsigned long long min_input_id = ULLONG_MAX, min_analysis_id = ULLONG_MAX; FILE *tsv = NULL; // any stat write failures are non critical and will be // ignored if (sample) { // write the stats after the sample tsv = fopen(cpw->tsv_path_tmp, "we"); if (tsv) { fprintf(tsv, "Plugin\t" "Inject Message Count\tInject Message Bytes\t" "Process Message Count\tProcess Message Failures\t" "Current Memory\t" "Max Memory\tMax Output\tMax Instructions\t" "Message Matcher Avg (ns)\tMessage Matcher SD (ns)\t" "Process Message Avg (ns)\tProcess Message SD (ns)\t" "Timer Event Avg (ns)\tTimer Event SD (ns)\n"); } } sample = (cnt % 6 == 0); // sample performance 10 times a minute if (cpw->input_plugins) { hs_input_plugin *p; pthread_mutex_lock(&cpw->input_plugins->list_lock); for (int i = 0; i < cpw->input_plugins->list_cap; ++i) { p = cpw->input_plugins->list[i]; if (p) { hs_update_checkpoint(cpr, p->name, &p->cp); pthread_mutex_lock(&p->cp.lock); if (!p->sample) p->sample = sample; if (tsv) { fprintf(tsv, "%s\t" "%llu\t%llu\t" "%llu\t%llu\t" "%llu\t%llu\t%llu\t%llu\t" "0\t0\t" "%.0f\t%.0f\t" "%.0f\t%.0f\n", p->name, p->stats.im_cnt, p->stats.im_bytes, p->stats.pm_cnt, p->stats.pm_failures, p->stats.mem_cur, p->stats.mem_max, p->stats.out_max, p->stats.ins_max, // no message matcher p->stats p->stats.pm_avg, p->stats.pm_sd, p->stats.te_avg, p->stats.te_sd); } pthread_mutex_unlock(&p->cp.lock); } } pthread_mutex_unlock(&cpw->input_plugins->list_lock); pthread_mutex_lock(&cpw->input_plugins->output.lock); if (fflush(cpw->input_plugins->output.fh)) { hs_log(NULL, g_module, 0, "input queue fflush failed"); exit(EXIT_FAILURE); } hscp = cpw->input_plugins->output.cp; pthread_mutex_unlock(&cpw->input_plugins->output.lock); hs_update_input_checkpoint(cpr, hs_input_dir, NULL, &hscp); } if (cpw->analysis_plugins) { for (int i = 0; i < cpw->analysis_plugins->thread_cnt; ++i) { hs_analysis_thread *at = &cpw->analysis_plugins->list[i]; pthread_mutex_lock(&at->cp_lock); hscp = at->cp; if (!at->sample) { at->sample = sample; } pthread_mutex_unlock(&at->cp_lock); if (hscp.id < min_input_id) { min_input_id = hscp.id; } hs_update_input_checkpoint(cpr, hs_input_dir, at->input.name, &hscp); pthread_mutex_lock(&cpw->analysis_plugins->output.lock); if (fflush(cpw->analysis_plugins->output.fh)) { hs_log(NULL, g_module, 0, "analysis queue fflush failed"); exit(EXIT_FAILURE); } hscp = cpw->analysis_plugins->output.cp; pthread_mutex_unlock(&cpw->analysis_plugins->output.lock); hs_update_input_checkpoint(cpr, hs_analysis_dir, NULL, &hscp); if (tsv) { hs_analysis_plugin *p; pthread_mutex_lock(&at->list_lock); for (int i = 0; i < at->list_cap; ++i) { p = at->list[i]; if (!p) continue; fprintf(tsv, "%s\t" "%llu\t%llu\t" "%llu\t%llu\t" "%llu\t%llu\t%llu\t%llu\t" "%.0f\t%.0f\t" "%.0f\t%.0f\t" "%.0f\t%.0f\n", p->name, p->stats.im_cnt, p->stats.im_bytes, p->stats.pm_cnt, p->stats.pm_failures, p->stats.mem_cur, p->stats.mem_max, p->stats.out_max, p->stats.ins_max, p->mms.mean, lsb_sd_running_stats(&p->mms), p->stats.pm_avg, p->stats.pm_sd, p->stats.te_avg, p->stats.te_sd); } pthread_mutex_unlock(&at->list_lock); } } } if (cpw->output_plugins) { pthread_mutex_lock(&cpw->output_plugins->list_lock); for (int i = 0; i < cpw->output_plugins->list_cap; ++i) { hs_output_plugin *p = cpw->output_plugins->list[i]; if (!p) continue; pthread_mutex_lock(&p->cp_lock); // use the current read checkpoints to prevent batching from causing // backpressure if (p->cur.input.id < min_input_id) { min_input_id = p->cur.input.id; } if (p->cur.analysis.id < min_analysis_id) { min_analysis_id = p->cur.analysis.id; } if (!p->sample) p->sample = sample; hs_update_input_checkpoint(cpr, hs_input_dir, p->name, &p->cp.input); hs_update_input_checkpoint(cpr, hs_analysis_dir, p->name, &p->cp.analysis); if (tsv) { fprintf(tsv, "%s\t" "%llu\t%llu\t" "%llu\t%llu\t" "%llu\t%llu\t%llu\t%llu\t" "%.0f\t%.0f\t" "%.0f\t%.0f\t" "%.0f\t%.0f\n", p->name, p->stats.im_cnt, p->stats.im_bytes, p->stats.pm_cnt, p->stats.pm_failures, p->stats.mem_cur, p->stats.mem_max, p->stats.out_max, p->stats.ins_max, p->mms.mean, lsb_sd_running_stats(&p->mms), p->stats.pm_avg, p->stats.pm_sd, p->stats.te_avg, p->stats.te_sd); } pthread_mutex_unlock(&p->cp_lock); } pthread_mutex_unlock(&cpw->output_plugins->list_lock); } if (tsv) { if (!fclose(tsv)) rename(cpw->tsv_path_tmp, cpw->tsv_path); } if (++cnt == 60) cnt = 0; if (cpw->input_plugins) { cpw->input_plugins->output.min_cp_id = min_input_id; } if (cpw->analysis_plugins) { cpw->analysis_plugins->output.min_cp_id = min_analysis_id; } FILE *cp = fopen(cpw->cp_path_tmp, "we"); if (!cp) { hs_log(NULL, g_module, 0, "%s: %s", cpw->cp_path_tmp, strerror(errno)); exit(EXIT_FAILURE); } int rv = hs_output_checkpoints(cpr, cp); if (fclose(cp) || rv) { hs_log(NULL, g_module, 0, "checkpoint write failure"); exit(EXIT_FAILURE); } else { rename(cpw->cp_path_tmp, cpw->cp_path); } } hindsight-0.12.7/src/hs_checkpoint_writer.h000066400000000000000000000024131301315165600207400ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** Hindsight checkpoint functions and structures @file */ #ifndef hs_checkpoint_writer_h_ #define hs_checkpoint_writer_h_ #include #include "hs_analysis_plugins.h" #include "hs_checkpoint_reader.h" #include "hs_input_plugins.h" #include "hs_output_plugins.h" #include "hs_output.h" typedef struct hs_checkpoint_writer { hs_analysis_plugins *analysis_plugins; hs_input_plugins *input_plugins; hs_output_plugins *output_plugins; char *cp_path; char *tsv_path; char *cp_path_tmp; char *tsv_path_tmp; } hs_checkpoint_writer; void hs_init_checkpoint_writer(hs_checkpoint_writer *cpw, hs_input_plugins *ip, hs_analysis_plugins *ap, hs_output_plugins *op, const char *path); void hs_free_checkpoint_writer(hs_checkpoint_writer *cpw); void hs_write_checkpoints(hs_checkpoint_writer *cpw, hs_checkpoint_reader *cpr); #endif hindsight-0.12.7/src/hs_config.c000066400000000000000000000603631301315165600164650ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** @brief Hindsight configuration implementation @file */ #include "hs_config.h" #include #include #include #include #include #include #include #include #include "hs_logger.h" #include "hs_util.h" const char *hs_input_dir = "input"; const char *hs_analysis_dir = "analysis"; const char *hs_output_dir = "output"; const char *hs_lua_ext = ".lua"; const char *hs_cfg_ext = ".cfg"; const char *hs_off_ext = ".off"; const char *hs_err_ext = ".err"; static const char g_module[] = "config_parser"; static const char *cfg_output_path = "output_path"; static const char *cfg_output_size = "output_size"; static const char *cfg_load_path = "sandbox_load_path"; static const char *cfg_run_path = "sandbox_run_path"; static const char *cfg_install_path = "sandbox_install_path"; static const char *cfg_threads = "analysis_threads"; static const char *cfg_analysis_lua_path = "analysis_lua_path"; static const char *cfg_analysis_lua_cpath = "analysis_lua_cpath"; static const char *cfg_io_lua_path = "io_lua_path"; static const char *cfg_io_lua_cpath = "io_lua_cpath"; static const char *cfg_max_message_size = "max_message_size"; static const char *cfg_hostname = "hostname"; static const char *cfg_backpressure = "backpressure"; static const char *cfg_backpressure_df = "backpressure_disk_free"; static const char *cfg_sb_ipd = "input_defaults"; static const char *cfg_sb_apd = "analysis_defaults"; static const char *cfg_sb_opd = "output_defaults"; static const char *cfg_sb_output = "output_limit"; static const char *cfg_sb_memory = "memory_limit"; static const char *cfg_sb_instruction = "instruction_limit"; static const char *cfg_sb_preserve = "preserve_data"; static const char *cfg_sb_restricted_headers = "restricted_headers"; static const char *cfg_sb_filename = "filename"; static const char *cfg_sb_ticker_interval = "ticker_interval"; static const char *cfg_sb_thread = "thread"; static const char *cfg_sb_async_buffer = "async_buffer_size"; static const char *cfg_sb_matcher = "message_matcher"; static const char *cfg_sb_shutdown_terminate = "shutdown_on_terminate"; static const char *cfg_sb_rm_cp_terminate = "remove_checkpoints_on_terminate"; static const char *cfg_sb_pm_im_limit = "process_message_inject_limit"; static const char *cfg_sb_te_im_limit = "timer_event_inject_limit"; static void init_sandbox_config(hs_sandbox_config *cfg) { cfg->dir = NULL; cfg->filename = NULL; cfg->cfg_name = NULL; cfg->cfg_lua = NULL; cfg->message_matcher = NULL; cfg->thread = 0; cfg->async_buffer_size = 0; cfg->output_limit = 1024 * 64; cfg->memory_limit = 1024 * 1024 * 8; cfg->instruction_limit = 1000000; cfg->ticker_interval = 0; cfg->preserve_data = false; cfg->restricted_headers = true; cfg->shutdown_terminate = false; cfg->rm_cp_terminate = false; cfg->pm_im_limit = 0; cfg->te_im_limit = 10; } static void init_config(hs_config *cfg) { cfg->run_path = NULL; cfg->run_path_input = NULL; cfg->run_path_analysis = NULL; cfg->run_path_output = NULL; cfg->load_path = NULL; cfg->load_path_input = NULL; cfg->load_path_analysis = NULL; cfg->load_path_output = NULL; cfg->output_path = NULL; cfg->install_path = NULL; cfg->io_lua_path = NULL; cfg->io_lua_cpath = NULL; cfg->analysis_lua_path = NULL; cfg->analysis_lua_cpath = NULL; cfg->hostname = NULL; cfg->output_size = 1024 * 1024 * 64; cfg->analysis_threads = 1; cfg->max_message_size = 1024 * 64; cfg->backpressure = 0; cfg->backpressure_df = 4; cfg->pid = (int)getpid(); init_sandbox_config(&cfg->ipd); init_sandbox_config(&cfg->apd); init_sandbox_config(&cfg->opd); cfg->ipd.restricted_headers = false; cfg->opd.restricted_headers = false; } static int check_for_unknown_options(lua_State *L, int idx, const char *parent) { lua_pushnil(L); while (lua_next(L, idx) != 0) { switch (lua_type(L, -2)) { case LUA_TSTRING: if (parent) { lua_pushfstring(L, "invalid option: '%s.%s'", parent, lua_tostring(L, -2)); } else { lua_pushfstring(L, "invalid option: '%s'", lua_tostring(L, -2)); } return 1; default: lua_pushstring(L, "non string key"); return 1;; } } return 0; } static void remove_item(lua_State *L, int idx, const char *name) { lua_pop(L, 1); lua_pushnil(L); lua_setfield(L, idx, name); } static int get_string_item(lua_State *L, int idx, const char *name, char **val, const char *dflt) { size_t len; lua_getfield(L, idx, name); const char *tmp = lua_tolstring(L, -1, &len); if (!tmp) { if (!dflt) { lua_pushfstring(L, "%s must be set to a string", name); return 1; } len = strlen(dflt); tmp = dflt; } *val = malloc(len + 1); memcpy(*val, tmp, len + 1); remove_item(L, idx, name); return 0; } static int get_unsigned_int(lua_State *L, int idx, const char *name, unsigned *val) { lua_getfield(L, idx, name); int t = lua_type(L, -1); double d; switch (t) { case LUA_TNUMBER: d = lua_tonumber(L, -1); if (d < 0 || d > UINT_MAX) { lua_pushfstring(L, "%s must be an unsigned int", name); return 1; } *val = (unsigned)d; break; case LUA_TNIL: break; // use the default default: lua_pushfstring(L, "%s must be set to a number", name); return 1; break; } remove_item(L, idx, name); return 0; } static int get_unsigned_char(lua_State *L, int idx, const char *name, unsigned char *val) { lua_getfield(L, idx, name); int t = lua_type(L, -1); double d; switch (t) { case LUA_TNUMBER: d = lua_tonumber(L, -1); if (d < 0 || d > UCHAR_MAX) { lua_pushfstring(L, "%s must be an unsigned char", name); return 1; } *val = (unsigned char)d; break; case LUA_TNIL: break; // use the default default: lua_pushfstring(L, "%s must be set to a number", name); return 1; break; } remove_item(L, idx, name); return 0; } static int get_bool_item(lua_State *L, int idx, const char *name, bool *val) { lua_getfield(L, idx, name); int t = lua_type(L, -1); switch (t) { case LUA_TBOOLEAN: *val = (bool)lua_toboolean(L, -1); break; case LUA_TNIL: break; // use the default default: lua_pushfstring(L, "%s must be set to a bool", cfg_sb_preserve); return 1; } remove_item(L, idx, name); return 0; } static int load_sandbox_defaults(lua_State *L, const char *key, hs_sandbox_config *cfg) { lua_getglobal(L, key); if (!lua_istable(L, -1)) { lua_pushfstring(L, "%s must be a table", key); return 1; } if (get_unsigned_int(L, 1, cfg_sb_output, &cfg->output_limit)) return 1; if (get_unsigned_int(L, 1, cfg_sb_memory, &cfg->memory_limit)) return 1; if (get_unsigned_int(L, 1, cfg_sb_instruction, &cfg->instruction_limit)) { return 1; } if (get_unsigned_int(L, 1, cfg_sb_ticker_interval, &cfg->ticker_interval)) { return 1; } if (get_bool_item(L, 1, cfg_sb_preserve, &cfg->preserve_data)) return 1; if (get_bool_item(L, 1, cfg_sb_restricted_headers, &cfg->restricted_headers)) { return 1; } if (get_bool_item(L, 1, cfg_sb_shutdown_terminate, &cfg->shutdown_terminate)) { return 1; } if (strcmp(key, cfg_sb_apd) == 0) { if (get_unsigned_char(L, 1, cfg_sb_pm_im_limit, &cfg->pm_im_limit)) { return 1; } if (get_unsigned_char(L, 1, cfg_sb_te_im_limit, &cfg->te_im_limit)) { return 1; } } if (strcmp(key, cfg_sb_opd) == 0) { if (get_bool_item(L, 1, cfg_sb_rm_cp_terminate, &cfg->rm_cp_terminate)) { return 1; } } if (check_for_unknown_options(L, 1, key)) return 1; remove_item(L, LUA_GLOBALSINDEX, key); return 0; } void hs_free_sandbox_config(hs_sandbox_config *cfg) { free(cfg->dir); cfg->dir = NULL; free(cfg->filename); cfg->filename = NULL; free(cfg->cfg_name); cfg->cfg_name = NULL; free(cfg->cfg_lua); cfg->cfg_lua = NULL; free(cfg->message_matcher); cfg->message_matcher = NULL; } void hs_free_config(hs_config *cfg) { free(cfg->run_path); cfg->run_path = NULL; free(cfg->run_path_input); cfg->run_path_input = NULL; free(cfg->run_path_analysis); cfg->run_path_analysis = NULL; free(cfg->run_path_output); cfg->run_path_output = NULL; free(cfg->load_path); cfg->load_path = NULL; free(cfg->load_path_input); cfg->load_path_input = NULL; free(cfg->load_path_analysis); cfg->load_path_analysis = NULL; free(cfg->load_path_output); cfg->load_path_output = NULL; free(cfg->output_path); cfg->output_path = NULL; free(cfg->install_path); cfg->install_path = NULL; free(cfg->io_lua_path); cfg->io_lua_path = NULL; free(cfg->io_lua_cpath); cfg->io_lua_cpath = NULL; free(cfg->analysis_lua_path); cfg->analysis_lua_path = NULL; free(cfg->analysis_lua_cpath); cfg->analysis_lua_cpath = NULL; free(cfg->hostname); cfg->hostname = NULL; hs_free_sandbox_config(&cfg->ipd); hs_free_sandbox_config(&cfg->apd); hs_free_sandbox_config(&cfg->opd); } static char* create_name(const char *prefix, const char *fn) { size_t ne_len = strlen(fn) - HS_EXT_LEN; size_t len = strlen(prefix) + ne_len + 2; char *name = malloc(len); if (!name) return NULL; int ret = snprintf(name, len, "%s.%.*s", prefix, (int)ne_len, fn); if (ret < 0 || ret > (int)len - 1) { return NULL; } return name; } bool hs_load_sandbox_config(const char *dir, const char *fn, hs_sandbox_config *cfg, const hs_sandbox_config *dflt, char type) { if (!cfg) return false; char fqfn[HS_MAX_PATH]; if (hs_has_ext(fn, hs_cfg_ext)) { if (hs_get_fqfn(dir, fn, fqfn, sizeof(fqfn))) { return false; } } else if (hs_has_ext(fn, hs_err_ext)) { if (!hs_get_fqfn(dir, fn, fqfn, sizeof(fqfn))) { unlink(fqfn); } return false; } else { return false; } init_sandbox_config(cfg); cfg->cfg_lua = lsb_read_file(fqfn); if (!cfg->cfg_lua) return false; lua_State *L = luaL_newstate(); if (!L) { hs_log(NULL, g_module, 3, "luaL_newstate failed: %s", fn); return false; } if (dflt) { cfg->output_limit = dflt->output_limit; cfg->memory_limit = dflt->memory_limit; cfg->instruction_limit = dflt->instruction_limit; cfg->ticker_interval = dflt->ticker_interval; cfg->preserve_data = dflt->preserve_data; cfg->restricted_headers = dflt->restricted_headers; cfg->shutdown_terminate = dflt->shutdown_terminate; cfg->rm_cp_terminate = dflt->rm_cp_terminate; cfg->pm_im_limit = dflt->pm_im_limit; cfg->te_im_limit = dflt->te_im_limit; } int ret = luaL_dostring(L, cfg->cfg_lua); if (ret) goto cleanup; size_t len = strlen(dir) + 1; cfg->dir = malloc(len); if (!cfg->dir) { ret = 1; goto cleanup; } memcpy(cfg->dir, dir, len); if (type == 'i') { cfg->cfg_name = create_name("input", fn); } else if (type == 'o') { cfg->cfg_name = create_name("output", fn); } else { cfg->cfg_name = create_name("analysis", fn); } if (!cfg->cfg_name) { lua_pushstring(L, "name allocation failed"); ret = 1; goto cleanup; } ret = get_unsigned_int(L, LUA_GLOBALSINDEX, cfg_sb_output, &cfg->output_limit); if (ret) goto cleanup; ret = get_unsigned_int(L, LUA_GLOBALSINDEX, cfg_sb_memory, &cfg->memory_limit); if (ret) goto cleanup; ret = get_unsigned_int(L, LUA_GLOBALSINDEX, cfg_sb_instruction, &cfg->instruction_limit); if (ret) goto cleanup; ret = get_unsigned_int(L, LUA_GLOBALSINDEX, cfg_sb_ticker_interval, &cfg->ticker_interval); if (ret) goto cleanup; ret = get_string_item(L, LUA_GLOBALSINDEX, cfg_sb_filename, &cfg->filename, NULL); if (!ret) { if (strpbrk(cfg->filename, "/\\")) { lua_pushfstring(L, "%s must be not contain a path component", cfg_sb_filename); ret = 1; } else if (!hs_has_ext(cfg->filename, hs_lua_ext)) { lua_pushfstring(L, "%s must have a %s extension", hs_lua_ext, cfg_sb_filename); ret = 1; } } if (ret) goto cleanup; ret = get_bool_item(L, LUA_GLOBALSINDEX, cfg_sb_preserve, &cfg->preserve_data); if (ret) goto cleanup; ret = get_bool_item(L, LUA_GLOBALSINDEX, cfg_sb_restricted_headers, &cfg->restricted_headers); if (ret) goto cleanup; ret = get_bool_item(L, LUA_GLOBALSINDEX, cfg_sb_shutdown_terminate, &cfg->shutdown_terminate); if (ret) goto cleanup; if (type == 'a' || type == 'o') { ret = get_string_item(L, LUA_GLOBALSINDEX, cfg_sb_matcher, &cfg->message_matcher, NULL); if (ret) goto cleanup; } if (type == 'a') { ret = get_unsigned_int(L, LUA_GLOBALSINDEX, cfg_sb_thread, &cfg->thread); ret = get_unsigned_char(L, LUA_GLOBALSINDEX, cfg_sb_pm_im_limit, &cfg->pm_im_limit); ret = get_unsigned_char(L, LUA_GLOBALSINDEX, cfg_sb_te_im_limit, &cfg->te_im_limit); if (ret) goto cleanup; } if (type == 'o') { ret = get_unsigned_int(L, LUA_GLOBALSINDEX, cfg_sb_async_buffer, &cfg->async_buffer_size); if (ret) goto cleanup; ret = get_bool_item(L, LUA_GLOBALSINDEX, cfg_sb_rm_cp_terminate, &cfg->rm_cp_terminate); if (ret) goto cleanup; } cleanup: if (ret) { hs_log(NULL, g_module, 3, "loading %s failed: %s", fn, lua_tostring(L, -1)); hs_free_sandbox_config(cfg); return false; } lua_close(L); return true; } int hs_load_config(const char *fn, hs_config *cfg) { if (!cfg) return 1; lua_State *L = luaL_newstate(); if (!L) { hs_log(NULL, g_module, 3, "luaL_newstate failed: %s", fn); return 1; } init_config(cfg); int ret = luaL_dofile(L, fn); if (ret) goto cleanup; ret = get_unsigned_int(L, LUA_GLOBALSINDEX, cfg_max_message_size, &cfg->max_message_size); if (cfg->max_message_size < 1024) { lua_pushfstring(L, "%s must be > 1023", cfg_max_message_size); ret = 1; goto cleanup; } ret = get_string_item(L, LUA_GLOBALSINDEX, cfg_output_path, &cfg->output_path, NULL); if (ret) goto cleanup; ret = get_unsigned_int(L, LUA_GLOBALSINDEX, cfg_output_size, &cfg->output_size); if (ret) goto cleanup; ret = get_unsigned_int(L, LUA_GLOBALSINDEX, cfg_backpressure, &cfg->backpressure); if (ret) goto cleanup; ret = get_unsigned_int(L, LUA_GLOBALSINDEX, cfg_backpressure_df, &cfg->backpressure_df); if (ret) goto cleanup; ret = get_string_item(L, LUA_GLOBALSINDEX, cfg_load_path, &cfg->load_path, ""); if (ret) goto cleanup; size_t len = strlen(cfg->load_path) + strlen(hs_input_dir) + 2; cfg->load_path_input = malloc(len); if (!cfg->load_path_input) { lua_pushfstring(L, "load_path_input malloc failed"); ret = 1; goto cleanup; } hs_get_fqfn(cfg->load_path, hs_input_dir, cfg->load_path_input, len); len = strlen(cfg->load_path) + strlen(hs_analysis_dir) + 2; cfg->load_path_analysis = malloc(len); if (!cfg->load_path_analysis) { lua_pushfstring(L, "load_path_analysis malloc failed"); ret = 1; goto cleanup; } hs_get_fqfn(cfg->load_path, hs_analysis_dir, cfg->load_path_analysis, len); len = strlen(cfg->load_path) + strlen(hs_output_dir) + 2; cfg->load_path_output = malloc(len); if (!cfg->load_path_output) { lua_pushfstring(L, "load_path_output malloc failed"); ret = 1; goto cleanup; } hs_get_fqfn(cfg->load_path, hs_output_dir, cfg->load_path_output, len); ret = get_string_item(L, LUA_GLOBALSINDEX, cfg_run_path, &cfg->run_path, NULL); if (ret) goto cleanup; len = strlen(cfg->run_path) + strlen(hs_input_dir) + 2; cfg->run_path_input = malloc(len); if (!cfg->run_path_input) { lua_pushfstring(L, "run_path_input malloc failed"); ret = 1; goto cleanup; } hs_get_fqfn(cfg->run_path, hs_input_dir, cfg->run_path_input, len); len = strlen(cfg->run_path) + strlen(hs_analysis_dir) + 2; cfg->run_path_analysis = malloc(len); if (!cfg->run_path_analysis) { lua_pushfstring(L, "run_path_analysis malloc failed"); ret = 1; goto cleanup; } hs_get_fqfn(cfg->run_path, hs_analysis_dir, cfg->run_path_analysis, len); len = strlen(cfg->run_path) + strlen(hs_output_dir) + 2; cfg->run_path_output = malloc(len); if (!cfg->run_path_output) { lua_pushfstring(L, "run_path_output malloc failed"); ret = 1; goto cleanup; } hs_get_fqfn(cfg->run_path, hs_output_dir, cfg->run_path_output, len); ret = get_string_item(L, LUA_GLOBALSINDEX, cfg_install_path, &cfg->install_path, "/usr/share/luasandbox/sandboxes/heka"); if (ret) goto cleanup; ret = get_string_item(L, LUA_GLOBALSINDEX, cfg_io_lua_path, &cfg->io_lua_path, NULL); if (ret) goto cleanup; ret = get_string_item(L, LUA_GLOBALSINDEX, cfg_io_lua_cpath, &cfg->io_lua_cpath, NULL); if (ret) goto cleanup; ret = get_string_item(L, LUA_GLOBALSINDEX, cfg_analysis_lua_path, &cfg->analysis_lua_path, NULL); if (ret) goto cleanup; ret = get_string_item(L, LUA_GLOBALSINDEX, cfg_analysis_lua_cpath, &cfg->analysis_lua_cpath, NULL); if (ret) goto cleanup; char hostname[65] = { 0 }; if (gethostname(hostname, sizeof(hostname))) { hostname[sizeof(hostname) - 1] = 0; hs_log(NULL, g_module, 4, "the system hostname was truncated to: %s", hostname); } ret = get_string_item(L, LUA_GLOBALSINDEX, cfg_hostname, &cfg->hostname, hostname); if (ret) goto cleanup; if (strlen(cfg->hostname) > sizeof(hostname) - 1) { cfg->hostname[sizeof(hostname) - 1] = 0; hs_log(NULL, g_module, 4, "the configured hostname was truncated to: %s", cfg->hostname); } ret = get_unsigned_int(L, LUA_GLOBALSINDEX, cfg_threads, &cfg->analysis_threads); if (cfg->analysis_threads < 1 || cfg->analysis_threads > 64) { lua_pushfstring(L, "%s must be 1-64", cfg_threads); ret = 1; goto cleanup; } ret = load_sandbox_defaults(L, cfg_sb_ipd, &cfg->ipd); if (ret) goto cleanup; ret = load_sandbox_defaults(L, cfg_sb_apd, &cfg->apd); if (ret) goto cleanup; ret = load_sandbox_defaults(L, cfg_sb_opd, &cfg->opd); if (ret) goto cleanup; ret = check_for_unknown_options(L, LUA_GLOBALSINDEX, NULL); if (ret) goto cleanup; cleanup: if (ret) { hs_log(NULL, g_module, 3, "loading %s failed: %s", fn, lua_tostring(L, -1)); } lua_close(L); return ret; } int hs_process_load_cfg(const char *lpath, const char *rpath, const char *name) { if (hs_has_ext(name, hs_cfg_ext)) { char cfg_lpath[HS_MAX_PATH]; if (hs_get_fqfn(lpath, name, cfg_lpath, sizeof(cfg_lpath))) { hs_log(NULL, g_module, 0, "load cfg path too long"); exit(EXIT_FAILURE); } char cfg_rpath[HS_MAX_PATH]; if (hs_get_fqfn(rpath, name, cfg_rpath, sizeof(cfg_rpath))) { hs_log(NULL, g_module, 0, "run cfg path too long"); exit(EXIT_FAILURE); } // if the plugin was off clear the flag and prepare for restart char off_rpath[HS_MAX_PATH]; strcpy(off_rpath, cfg_rpath); size_t pos = strlen(off_rpath) - HS_EXT_LEN; strcpy(off_rpath + pos, hs_off_ext); if (hs_file_exists(off_rpath)) { if (unlink(off_rpath)) { hs_log(NULL, g_module, 3, "failed to delete: %s errno: %d", off_rpath, errno); return -1; } } // if the plugin was terminated clear the error and prepare for restart strcpy(off_rpath + pos, hs_err_ext); if (hs_file_exists(off_rpath)) { if (unlink(off_rpath)) { hs_log(NULL, g_module, 3, "failed to delete: %s errno: %d", off_rpath, errno); return -1; } } // move the cfg to the run directory and prepare for start/restart if (rename(cfg_lpath, cfg_rpath)) { hs_log(NULL, g_module, 3, "failed to move: %s to %s errno: %d", cfg_lpath, cfg_rpath, errno); return -1; } return 1; } else if (hs_has_ext(name, hs_off_ext)) { char off_lpath[HS_MAX_PATH]; if (hs_get_fqfn(lpath, name, off_lpath, sizeof(off_lpath))) { hs_log(NULL, g_module, 0, "load off path too long"); exit(EXIT_FAILURE); } if (unlink(off_lpath)) { hs_log(NULL, g_module, 3, "failed to delete: %s errno: %d", off_lpath, errno); return -1; } // move the current cfg to .off and shutdown the plugin char off_rpath[HS_MAX_PATH]; if (hs_get_fqfn(rpath, name, off_rpath, sizeof(off_rpath))) { hs_log(NULL, g_module, 0, "run off path too long"); exit(EXIT_FAILURE); } char cfg_rpath[HS_MAX_PATH]; strcpy(cfg_rpath, off_rpath); strcpy(cfg_rpath + strlen(cfg_rpath) - HS_EXT_LEN, hs_cfg_ext); if (rename(cfg_rpath, off_rpath)) { hs_log(NULL, g_module, 4, "failed to move: %s to %s errno: %d", cfg_rpath, off_rpath, errno); return -1; } return 0; } return -1; } bool hs_get_full_config(lsb_output_buffer *ob, char type, const hs_config *cfg, hs_sandbox_config *sbc) { lsb_outputf(ob, "-- original configuration\n"); lsb_outputf(ob, "%s\n", sbc->cfg_lua); lsb_outputf(ob, "-- Hindsight defaults and overrides\n"); lsb_outputf(ob, "Hostname = [[%s]]\n", cfg->hostname); lsb_outputf(ob, "Pid = %d\n", cfg->pid); lsb_outputf(ob, "log_level = %d\n", hs_get_log_level()); if (type == 'a') { lsb_outputf(ob, "path = [[%s]]\n", cfg->analysis_lua_path); lsb_outputf(ob, "cpath = [[%s]]\n", cfg->analysis_lua_cpath); } else { lsb_outputf(ob, "path = [[%s]]\n", cfg->io_lua_path); lsb_outputf(ob, "cpath = [[%s]]\n", cfg->io_lua_cpath); lsb_outputf(ob, "output_path = [[%s]]\n", cfg->output_path); lsb_outputf(ob, "output_size = %u\n", cfg->output_size); lsb_outputf(ob, "max_message_size = %u\n", cfg->max_message_size); lsb_outputf(ob, "sandbox_load_path = [[%s]]\n", cfg->load_path); lsb_outputf(ob, "sandbox_run_path = [[%s]]\n", cfg->run_path); lsb_outputf(ob, "sandbox_install_path = [[%s]]\n", cfg->install_path); } lsb_outputf(ob, "\n-- Sandbox defaults and overrides\n"); lsb_outputf(ob, "Logger = [[%s]]\n", sbc->cfg_name); lsb_outputf(ob, "output_limit = %u\n", sbc->output_limit); lsb_outputf(ob, "memory_limit = %u\n", sbc->memory_limit); lsb_outputf(ob, "instruction_limit = %u\n", sbc->instruction_limit); lsb_outputf(ob, "ticker_interval = %u\n", sbc->ticker_interval); lsb_outputf(ob, "preserve_data = %s\n", sbc->preserve_data ? "true" : "false"); lsb_outputf(ob, "restricted_headers = %s\n", sbc->restricted_headers ? "true" : "false"); lsb_outputf(ob, "shutdown_on_terminate = %s\n", sbc->shutdown_terminate ? "true" : "false"); if (type == 'a') { lsb_outputf(ob, "thread = %u\n", sbc->thread); lsb_outputf(ob, "process_message_inject_limit = %hhu\n", sbc->pm_im_limit); lsb_outputf(ob, "timer_event_inject_limit = %hhu\n", sbc->te_im_limit); } if (type == 'o') { lsb_outputf(ob, "async_buffer_size = %u\n", sbc->async_buffer_size); } // just test the last write to make sure the buffer wasn't exhausted lsb_err_value ret = lsb_outputf(ob, "-- end Hindsight configuration\n"); char fcfg[] = ".fcfg"; char fn[strlen(sbc->dir) + 1 + strlen(sbc->cfg_name) + sizeof(fcfg)]; char *p = strchr(sbc->cfg_name, '.'); if (!p) return false; snprintf(fn, sizeof(fn), "%s/%s%s", sbc->dir, p + 1, fcfg); FILE *fh = fopen(fn, "we"); if (!fh) return false; fwrite(ob->buf, ob->pos, 1, fh); fclose(fh); return ret ? false : true; } hindsight-0.12.7/src/hs_config.h000066400000000000000000000063051301315165600164660ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** Hindsight main configuration module @file */ #ifndef hs_config_h_ #define hs_config_h_ #include #include #include #define HS_EXT_LEN 4 #define HS_MAX_PATH 260 extern const char *hs_input_dir; extern const char *hs_analysis_dir; extern const char *hs_output_dir; extern const char *hs_lua_ext; extern const char *hs_cfg_ext; extern const char *hs_off_ext; extern const char *hs_err_ext; typedef struct hs_sandbox_config { char *dir; char *filename; char *cfg_name; char *cfg_lua; char *message_matcher; // analysis/output sandbox only unsigned thread; // analysis sandbox only unsigned async_buffer_size; // output sandbox only unsigned output_limit; unsigned memory_limit; unsigned instruction_limit; unsigned ticker_interval; bool preserve_data; bool restricted_headers; bool shutdown_terminate; bool rm_cp_terminate; // output sandbox only unsigned char pm_im_limit; // analysis sandbox only unsigned char te_im_limit; // analysis sandbox only } hs_sandbox_config; typedef struct hs_config { char *run_path; char *run_path_input; char *run_path_analysis; char *run_path_output; char *load_path; char *load_path_input; char *load_path_analysis; char *load_path_output; char *output_path; char *install_path; char *io_lua_path; char *io_lua_cpath; char *analysis_lua_path; char *analysis_lua_cpath; char *hostname; unsigned max_message_size; unsigned output_size; unsigned analysis_threads; unsigned backpressure; unsigned backpressure_df; int pid; hs_sandbox_config ipd; // input plugin defaults hs_sandbox_config apd; // analysis plugin defaults hs_sandbox_config opd; // output plugin defaults } hs_config; /** * Free any memory allocated by the configuration * * @param cfg Configuration structure to free * */ void hs_free_sandbox_config(hs_sandbox_config *cfg); /** * Free any memory allocated by the configuration * * @param cfg Configuration structure to free * */ void hs_free_config(hs_config *cfg); /** * Loads the sandbox configuration from a file * * @param fn Filename * @param cfg Configuration structure to populate * @param dflt * * @return bool false on failure */ bool hs_load_sandbox_config(const char *dir, const char *fn, hs_sandbox_config *cfg, const hs_sandbox_config *dflt, char type); /** * Loads the Hinsight configuration from a file * * @param fn Filename * @param cfg Configuration structure to populate * * @return int 0 on success */ int hs_load_config(const char *fn, hs_config *cfg); int hs_process_load_cfg(const char *lpath, const char *rpath, const char *name); bool hs_get_full_config(lsb_output_buffer *ob, char type, const hs_config *cfg, hs_sandbox_config *sbc); #endif hindsight-0.12.7/src/hs_input.c000066400000000000000000000074571301315165600163640ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** @brief Hindsight input implementation @file */ #include "hs_input.h" #include #include #include #include #include #include #include "hs_logger.h" #include "hs_util.h" static const char g_module[] = "input_reader"; bool hs_open_file(hs_input *hsi, const char *subdir, unsigned long long id) { char fqfn[HS_MAX_PATH]; int ret = snprintf(fqfn, sizeof(fqfn), "%s/%s/%llu.log", hsi->path, subdir, id); if (ret < 0 || ret > (int)sizeof(fqfn) - 1) { hs_log(NULL, g_module, 0, "%s file: %llu.log: fully qualiifed path is" " greater than %zu", hsi->name, hsi->cp.id, sizeof(fqfn)); exit(EXIT_FAILURE); } if (hsi->fn && strcmp(hsi->fn, fqfn) == 0) return true; FILE *fh = fopen(fqfn, "re"); if (fh) { if (setvbuf(fh, NULL, _IONBF, 0)) { exit(EXIT_FAILURE); } if (hsi->cp.id == id && hsi->cp.offset) { hs_log(NULL, g_module, 7, "%s opened file: %s offset: %zu", hsi->name, fqfn, hsi->cp.offset); if (fseek(fh, hsi->cp.offset, SEEK_SET)) { hs_log(NULL, g_module, 2, "%s file: %s invalid offset: %zu error: %d", hsi->name, fqfn, hsi->cp.offset, ferror(fh)); } } else { hs_log(NULL, g_module, 7, "%s opened file: %s", hsi->name, fqfn); } if (hsi->fh) { fclose(hsi->fh); } if (hsi->cp.id != id) { hsi->cp.id = id; hsi->cp.offset = 0; } if (ret >= (int)hsi->fn_size) { free(hsi->fn); hsi->fn_size = (size_t)(ret + 1); hsi->fn = malloc(hsi->fn_size); if (!hsi->fn) { hs_log(NULL, g_module, 2, "%s file: %s malloc failed", hsi->name, fqfn); exit(EXIT_FAILURE); } } strcpy(hsi->fn, fqfn); hsi->fh = fh; return true; } return false; } size_t hs_read_file(hs_input *hsi) { lsb_input_buffer *ib = &hsi->ib; size_t need; if (ib->msglen) { need = ib->msglen + (size_t)ib->buf[ib->scanpos + 1] + LSB_HDR_FRAME_SIZE - (ib->readpos - ib->scanpos); } else { need = ib->scanpos + ib->size - ib->readpos; } if (lsb_expand_input_buffer(ib, need)) { hs_log(NULL, g_module, 0, "%s buffer reallocation failed", hsi->name); exit(EXIT_FAILURE); } size_t nread = fread(ib->buf + ib->readpos, 1, ib->size - ib->readpos, hsi->fh); hsi->cp.offset += nread; ib->readpos += nread; return nread; } void hs_init_input(hs_input *hsi, size_t max_message_size, const char *path, const char *name) { hsi->fh = NULL; hsi->fn = NULL; hsi->fn_size = 0; hsi->cp.id = 0; hsi->cp.offset = 0; if (strlen(path) > HS_MAX_PATH - 30) { hs_log(NULL, g_module, 0, "path too long"); exit(EXIT_FAILURE); } hsi->path = malloc(strlen(path) + 1); if (!hsi->path) { hs_log(NULL, g_module, 0, "path malloc failed"); exit(EXIT_FAILURE); } strcpy(hsi->path, path); hsi->name = malloc(strlen(name) + 1); if (!hsi->name) { hs_log(NULL, g_module, 0, "name malloc failed"); exit(EXIT_FAILURE); } strcpy(hsi->name, name); lsb_init_input_buffer(&hsi->ib, max_message_size); } void hs_free_input(hs_input *hsi) { if (hsi->fh) fclose(hsi->fh); hsi->fh = NULL; free(hsi->path); hsi->path = NULL; free(hsi->name); hsi->name = NULL; free(hsi->fn); hsi->fn = NULL; lsb_free_input_buffer(&hsi->ib); } hindsight-0.12.7/src/hs_input.h000066400000000000000000000020631301315165600163550ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** Hindsight input facility @file */ #ifndef hs_input_h_ #define hs_input_h_ #include #include "hs_checkpoint_reader.h" #include "hs_config.h" #include #include #include typedef struct hs_input { FILE *fh; char *path; char *name; char *fn; size_t fn_size; lsb_input_buffer ib; hs_checkpoint cp; } hs_input; void hs_init_input(hs_input *hsi, size_t max_message_size, const char *path, const char *name); void hs_free_input(hs_input *hsi); bool hs_open_file(hs_input *hsi, const char *subdir, unsigned long long id); size_t hs_read_file(hs_input *hsi); #endif hindsight-0.12.7/src/hs_input_plugins.c000066400000000000000000000460021301315165600201120ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** @brief Hindsight configuration loader @file */ #define _GNU_SOURCE #include "hs_input_plugins.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hs_logger.h" #include "hs_util.h" static const char g_module[] = "input_plugins"; static void init_ip_checkpoint(hs_ip_checkpoint *cp) { if (pthread_mutex_init(&cp->lock, NULL)) { perror("cp lock pthread_mutex_init failed"); exit(EXIT_FAILURE); } cp->type = HS_CP_NONE; cp->len = 0; cp->cap = 0; cp->value.d = 0; } static void free_ip_checkpoint(hs_ip_checkpoint *cp) { if (!cp) return; cp->type = HS_CP_NONE; if (cp->type == HS_CP_STRING) { free(cp->value.s); cp->value.s = NULL; } pthread_mutex_destroy(&cp->lock); } static bool update_checkpoint(double d, const char *s, hs_ip_checkpoint *cp) { if (!isnan(d)) { if (cp->type == HS_CP_STRING) { free(cp->value.s); cp->value.s = NULL; cp->len = 0; cp->cap = 0; } cp->type = HS_CP_NUMERIC; cp->value.d = d; } else if (s) { if (cp->type == HS_CP_NUMERIC) cp->value.s = NULL; cp->type = HS_CP_STRING; cp->len = strlen(s); cp->len++; if (cp->len <= HS_MAX_IP_CHECKPOINT) { if (cp->len > cp->cap) { free(cp->value.s); cp->value.s = malloc(cp->len); if (!cp->value.s) { cp->len = 0; cp->cap = 0; hs_log(NULL, g_module, 0, "malloc failed"); return false; } cp->cap = cp->len; } memcpy(cp->value.s, s, cp->len); } else { hs_log(NULL, g_module, 3, "checkpoint string exceeds %d", HS_MAX_IP_CHECKPOINT); return false; } } return true; } static int inject_message(void *parent, const char *pb, size_t pb_len, double cp_numeric, const char *cp_string) { static bool backpressure = false; static char header[14]; hs_input_plugin *p = parent; int rv; pthread_mutex_lock(&p->cp.lock); rv = !update_checkpoint(cp_numeric, cp_string, &p->cp); if (p->sample) { p->stats = lsb_heka_get_stats(p->hsb); p->sample = false; } pthread_mutex_unlock(&p->cp.lock); if (!pb) { // a NULL message is used as a synchronization point if (!sem_trywait(&p->shutdown)) { sem_post(&p->shutdown); lsb_heka_stop_sandbox_clean(p->hsb); } return rv; } if (rv) return rv; bool bp; pthread_mutex_lock(&p->plugins->output.lock); int len = lsb_pb_output_varint(header + 3, pb_len); int tlen = 4 + len + pb_len; header[0] = 0x1e; header[1] = (char)(len + 1); header[2] = 0x08; header[3 + len] = 0x1f; if (fwrite(header, 4 + len, 1, p->plugins->output.fh) == 1 && fwrite(pb, pb_len, 1, p->plugins->output.fh) == 1) { p->plugins->output.cp.offset += tlen; if (p->plugins->output.cp.offset >= p->plugins->cfg->output_size) { ++p->plugins->output.cp.id; hs_open_output_file(&p->plugins->output); if (p->plugins->cfg->backpressure && p->plugins->output.cp.id - p->plugins->output.min_cp_id > p->plugins->cfg->backpressure) { backpressure = true; hs_log(NULL, g_module, 4, "applying backpressure (checkpoint)"); } if (!backpressure && p->plugins->cfg->backpressure_df) { unsigned df = hs_disk_free_ob(p->plugins->output.path, p->plugins->cfg->output_size); if (df <= p->plugins->cfg->backpressure_df) { backpressure = true; hs_log(NULL, g_module, 4, "applying backpressure (disk)"); } } } if (backpressure) { bool release_dfbp = true; if (p->plugins->cfg->backpressure_df) { unsigned df = hs_disk_free_ob(p->plugins->output.path, p->plugins->cfg->output_size); release_dfbp = (df > p->plugins->cfg->backpressure_df); } // even if we triggered on disk space continue to backpressure // until the queue is caught up too if (p->plugins->output.cp.id == p->plugins->output.min_cp_id && release_dfbp) { backpressure = false; hs_log(NULL, g_module, 4, "releasing backpressure"); } } rv = 0; } else { hs_log(NULL, g_module, 0, "inject_message fwrite failed: %s", strerror(ferror(p->plugins->output.fh))); exit(EXIT_FAILURE); } bp = backpressure; pthread_mutex_unlock(&p->plugins->output.lock); if (bp) { usleep(100000); // throttle to 10 messages per second } return rv; } static void destroy_input_plugin(hs_input_plugin *p) { if (!p) return; char *msg = lsb_heka_destroy_sandbox(p->hsb); if (msg) { hs_log(NULL, p->name, 3, "lsb_heka_destroy_sandbox failed: %s", msg); free(msg); } free(p->name); free_ip_checkpoint(&p->cp); sem_destroy(&p->shutdown); free(p); } static hs_input_plugin* create_input_plugin(const hs_config *cfg, hs_sandbox_config *sbc) { char lua_file[HS_MAX_PATH]; if (!hs_find_lua(cfg, sbc, hs_input_dir, lua_file, sizeof(lua_file))) { hs_log(NULL, g_module, 3, "%s failed to find the specified lua filename: %s" , sbc->cfg_name, sbc->filename); return NULL; } hs_input_plugin *p = calloc(1, sizeof(hs_input_plugin)); if (!p) { hs_log(NULL, g_module, 2, "%s hs_input_plugin memory allocation failed", sbc->cfg_name); return NULL; } p->shutdown_terminate = sbc->shutdown_terminate; p->ticker_interval = sbc->ticker_interval; p->list_index = -1; if (sem_init(&p->shutdown, 0, 1)) { free(p); hs_log(NULL, g_module, 3, "%s sem_init failed", sbc->cfg_name); return NULL; } if (sem_wait(&p->shutdown)) { destroy_input_plugin(p); hs_log(NULL, g_module, 3, "%s sem_wait failed", sbc->cfg_name); return NULL; } size_t len = strlen(sbc->cfg_name) + 1; p->name = malloc(len); if (!p->name) { hs_log(NULL, g_module, 2, "%s name memory allocation failed", sbc->cfg_name); destroy_input_plugin(p); } memcpy(p->name, sbc->cfg_name, len); char *state_file = NULL; if (sbc->preserve_data) { size_t len = strlen(cfg->output_path) + strlen(sbc->cfg_name) + 7; state_file = malloc(len); if (!state_file) { hs_log(NULL, g_module, 2, "%s state_file memory allocation failed", sbc->cfg_name); destroy_input_plugin(p); return NULL; } int ret = snprintf(state_file, len, "%s/%s.data", cfg->output_path, sbc->cfg_name); if (ret < 0 || ret > (int)len - 1) { hs_log(NULL, g_module, 3, "%s failed to construct the state_file path", sbc->cfg_name); free(state_file); destroy_input_plugin(p); return NULL; } } lsb_output_buffer ob; if (lsb_init_output_buffer(&ob, 8 * 1024)) { hs_log(NULL, g_module, 3, "%s configuration memory allocation failed", sbc->cfg_name); free(state_file); destroy_input_plugin(p); return NULL; } if (!hs_get_full_config(&ob, 'i', cfg, sbc)) { hs_log(NULL, g_module, 3, "%s hs_get_full_config failed", sbc->cfg_name); lsb_free_output_buffer(&ob); free(state_file); destroy_input_plugin(p); return NULL; } lsb_logger logger = { .context = NULL, .cb = hs_log }; p->hsb = lsb_heka_create_input(p, lua_file, state_file, ob.buf, &logger, inject_message); lsb_free_output_buffer(&ob); free(sbc->cfg_lua); sbc->cfg_lua = NULL; free(state_file); if (!p->hsb) { destroy_input_plugin(p); hs_log(NULL, g_module, 3, "%s lsb_heka_create_input failed", sbc->cfg_name); return NULL; } init_ip_checkpoint(&p->cp); return p; } static void* input_thread(void *arg) { hs_input_plugin *p = (hs_input_plugin *)arg; struct timespec ts; int ret = 0; bool shutdown = false; bool profile = (p->ticker_interval > 0); double ncp = NAN; const char *scp = NULL; hs_log(NULL, p->name, 6, "starting"); while (true) { switch (p->cp.type) { case HS_CP_STRING: ncp = NAN; scp = p->cp.value.s; break; case HS_CP_NUMERIC: scp = NULL; ncp = p->cp.value.d; break; case HS_CP_NONE: ncp = NAN; scp = NULL; break; } ret = lsb_heka_pm_input(p->hsb, ncp, scp, profile); if (ret <= 0) { if (p->ticker_interval == 0) { // run once if (!sem_trywait(&p->shutdown)) { shutdown = true; } break; // exit } else { // poll pthread_mutex_lock(&p->cp.lock); p->stats = lsb_heka_get_stats(p->hsb); pthread_mutex_unlock(&p->cp.lock); if (clock_gettime(CLOCK_REALTIME, &ts) == -1) { hs_log(NULL, p->name, 3, "clock_gettime failed"); ts.tv_sec = time(NULL); ts.tv_nsec = 0; } ts.tv_sec += p->ticker_interval; if (!sem_timedwait(&p->shutdown, &ts)) { shutdown = true; break; // shutting down } } } else { if (!sem_trywait(&p->shutdown)) { shutdown = true; } break; // exiting due to error } } if (p->orphaned) { pthread_exit(NULL); } hs_input_plugins *plugins = p->plugins; // hold the current checkpoint in memory until we shutdown to facilitate // resuming where it left off hs_update_checkpoint(plugins->cpr, p->name, &p->cp); if (shutdown) { hs_log(NULL, p->name, 6, "shutting down"); sem_post(&p->shutdown); } else { const char *err = lsb_heka_get_error(p->hsb); hs_log(NULL, p->name, 6, "detaching received: %d msg: %s", ret, err); if (ret > 0) { hs_save_termination_err(plugins->cfg, p->name, err); } pthread_mutex_lock(&plugins->list_lock); plugins->list[p->list_index] = NULL; if (pthread_detach(p->thread)) { hs_log(NULL, p->name, 3, "thread could not be detached"); } if (ret > 0 && p->shutdown_terminate) { hs_log(NULL, p->name, 6, "shutting down on terminate"); kill(getpid(), SIGTERM); } destroy_input_plugin(p); --plugins->list_cnt; pthread_mutex_unlock(&plugins->list_lock); } pthread_exit(NULL); } static bool join_thread(hs_input_plugins *plugins, hs_input_plugin *p) { struct timespec ts; if (clock_gettime(CLOCK_REALTIME, &ts) == -1) { hs_log(NULL, p->name, 3, "clock_gettime failed"); ts.tv_sec = time(NULL); ts.tv_nsec = 0; } ts.tv_sec += 2; if (pthread_timedjoin_np(p->thread, NULL, &ts)) { lsb_heka_stop_sandbox(p->hsb); hs_log(NULL, p->name, 4, "sandbox did not respond to a clean stop"); ts.tv_sec += 2; if (pthread_timedjoin_np(p->thread, NULL, &ts)) { if (!sem_trywait(&p->shutdown)) { p->orphaned = true; hs_log(NULL, p->name, 3, "sandbox did not respond to a forced stop " "(orphaning)"); sem_post(&p->shutdown); return false; } else { ts.tv_sec += 1; if (pthread_timedjoin_np(p->thread, NULL, &ts)) { hs_log(NULL, p->name, 2, "sandbox acknowledged the stop but failed " "to stop (undefined behavior)"); return false; } } } } destroy_input_plugin(p); --plugins->list_cnt; return true; } static bool remove_plugin(hs_input_plugins *plugins, int idx) { hs_input_plugin *p = plugins->list[idx]; sem_post(&p->shutdown); lsb_heka_stop_sandbox_clean(p->hsb); if (join_thread(plugins, p)) { plugins->list[idx] = NULL; return true; } return false; } static bool remove_from_input_plugins(hs_input_plugins *plugins, const char *name) { bool removed = true; const size_t tlen = strlen(hs_input_dir) + 1; pthread_mutex_lock(&plugins->list_lock); for (int i = 0; i < plugins->list_cap; ++i) { if (!plugins->list[i]) continue; char *pos = plugins->list[i]->name + tlen; if (strstr(name, pos) && strlen(pos) == strlen(name) - HS_EXT_LEN) { removed = remove_plugin(plugins, i); break; } } pthread_mutex_unlock(&plugins->list_lock); return removed; } static void add_plugin(hs_input_plugins *plugins, hs_input_plugin *p, int idx) { plugins->list[idx] = p; p->list_index = idx; ++plugins->list_cnt; } static void add_to_input_plugins(hs_input_plugins *plugins, hs_input_plugin *p) { int idx = -1; pthread_mutex_lock(&plugins->list_lock); for (int i = 0; i < plugins->list_cap; ++i) { if (!plugins->list[i]) { idx = i; break; } } if (idx != -1) { add_plugin(plugins, p, idx); } else { // todo probably don't want to grow it by 1 ++plugins->list_cap; hs_input_plugin **tmp = realloc(plugins->list, sizeof(hs_input_plugin *) * plugins->list_cap); idx = plugins->list_cap - 1; if (tmp) { plugins->list = tmp; add_plugin(plugins, p, idx); } else { hs_log(NULL, g_module, 0, "plugins realloc failed"); exit(EXIT_FAILURE); } } pthread_mutex_unlock(&plugins->list_lock); assert(p->list_index >= 0); hs_lookup_checkpoint(p->plugins->cpr, p->name, &p->cp); int ret = pthread_create(&p->thread, NULL, input_thread, (void *)p); if (ret) { perror("pthread_create failed"); exit(EXIT_FAILURE); } } void hs_init_input_plugins(hs_input_plugins *plugins, hs_config *cfg, hs_checkpoint_reader *cpr) { hs_init_output(&plugins->output, cfg->output_path, hs_input_dir); plugins->cfg = cfg; plugins->cpr = cpr; plugins->list_cnt = 0; plugins->list = NULL; plugins->list_cap = 0; if (pthread_mutex_init(&plugins->list_lock, NULL)) { perror("list_lock pthread_mutex_init failed"); exit(EXIT_FAILURE); } } void hs_wait_input_plugins(hs_input_plugins *plugins) { pthread_mutex_lock(&plugins->list_lock); for (int i = 0; i < plugins->list_cap; ++i) { if (!plugins->list[i]) continue; hs_input_plugin *p = plugins->list[i]; plugins->list[i] = NULL; join_thread(plugins, p); } pthread_mutex_unlock(&plugins->list_lock); } void hs_free_input_plugins(hs_input_plugins *plugins) { free(plugins->list); plugins->list = NULL; pthread_mutex_destroy(&plugins->list_lock); hs_free_output(&plugins->output); plugins->cfg = NULL; plugins->list_cnt = 0; plugins->list_cap = 0; } static void process_lua(hs_input_plugins *plugins, const char *name) { hs_config *cfg = plugins->cfg; const char *lpath = cfg->load_path_input; const char *rpath = cfg->run_path_input; char lua_lpath[HS_MAX_PATH]; char lua_rpath[HS_MAX_PATH]; char cfg_lpath[HS_MAX_PATH]; char cfg_rpath[HS_MAX_PATH]; size_t tlen = strlen(hs_input_dir) + 1; // move the Lua to the run directory if (hs_get_fqfn(lpath, name, lua_lpath, sizeof(lua_lpath))) { hs_log(NULL, g_module, 0, "load lua path too long"); exit(EXIT_FAILURE); } if (hs_get_fqfn(rpath, name, lua_rpath, sizeof(lua_rpath))) { hs_log(NULL, g_module, 0, "run lua path too long"); exit(EXIT_FAILURE); } if (rename(lua_lpath, lua_rpath)) { hs_log(NULL, g_module, 3, "failed to move: %s to %s errno: %d", lua_lpath, lua_rpath, errno); return; } // restart any plugins using this Lua code pthread_mutex_lock(&plugins->list_lock); for (int i = 0; i < plugins->list_cap; ++i) { if (!plugins->list[i]) continue; hs_input_plugin *p = plugins->list[i]; if (strcmp(lua_rpath, lsb_heka_get_lua_file(p->hsb)) == 0) { int ret = snprintf(cfg_lpath, HS_MAX_PATH, "%s/%s%s", lpath, p->name + tlen, hs_cfg_ext); if (ret < 0 || ret > HS_MAX_PATH - 1) { hs_log(NULL, g_module, 0, "load cfg path too long"); exit(EXIT_FAILURE); } ret = snprintf(cfg_rpath, HS_MAX_PATH, "%s/%s%s", rpath, p->name + tlen, hs_cfg_ext); if (ret < 0 || ret > HS_MAX_PATH - 1) { hs_log(NULL, g_module, 0, "run cfg path too long"); exit(EXIT_FAILURE); } // if no new cfg was provided, move the existing cfg to the load // directory if (!hs_file_exists(cfg_lpath)) { if (rename(cfg_rpath, cfg_lpath)) { hs_log(NULL, g_module, 3, "failed to move: %s to %s errno: %d", cfg_rpath, cfg_lpath, errno); } } } } pthread_mutex_unlock(&plugins->list_lock); } void hs_load_input_startup(hs_input_plugins *plugins) { hs_config *cfg = plugins->cfg; const char *dir = cfg->run_path_input; DIR *dp = opendir(dir); if (dp == NULL) { hs_log(NULL, g_module, 0, "%s: %s", dir, strerror(errno)); exit(EXIT_FAILURE); } struct dirent *entry; while ((entry = readdir(dp))) { hs_sandbox_config sbc; if (hs_load_sandbox_config(dir, entry->d_name, &sbc, &cfg->ipd, 'i')) { hs_input_plugin *p = create_input_plugin(cfg, &sbc); hs_free_sandbox_config(&sbc); if (p) { p->plugins = plugins; add_to_input_plugins(plugins, p); } else { hs_log(NULL, g_module, 3, "%s create_inputs_plugin failed", entry->d_name); } } } closedir(dp); } void hs_load_input_dynamic(hs_input_plugins *plugins, const char *name) { hs_config *cfg = plugins->cfg; const char *lpath = cfg->load_path_input; const char *rpath = cfg->run_path_input; if (hs_has_ext(name, hs_lua_ext)) { process_lua(plugins, name); return; } switch (hs_process_load_cfg(lpath, rpath, name)) { case 0: if (!remove_from_input_plugins(plugins, name)) { hs_log(NULL, g_module, 4, "%s stop request pending", name); } break; case 1: // load { if (!remove_from_input_plugins(plugins, name)) { hs_log(NULL, g_module, 4, "%s stop request pending", name); break; } hs_sandbox_config sbc; if (hs_load_sandbox_config(rpath, name, &sbc, &cfg->ipd, 'i')) { hs_input_plugin *p = create_input_plugin(cfg, &sbc); hs_free_sandbox_config(&sbc); if (p) { p->plugins = plugins; add_to_input_plugins(plugins, p); } else { hs_log(NULL, g_module, 3, "%s create_input_plugin failed", name); } } } break; default: hs_log(NULL, g_module, 7, "%s ignored %s", __func__, name); return; } } void hs_stop_input_plugins(hs_input_plugins *plugins) { pthread_mutex_lock(&plugins->list_lock); for (int i = 0; i < plugins->list_cap; ++i) { if (!plugins->list[i]) continue; sem_post(&plugins->list[i]->shutdown); } pthread_mutex_unlock(&plugins->list_lock); } hindsight-0.12.7/src/hs_input_plugins.h000066400000000000000000000033231301315165600201160ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** Hindsight input plugins @file */ #ifndef hs_input_plugins_h_ #define hs_input_plugins_h_ #include #include #include #include #include "hs_config.h" #include "hs_checkpoint_reader.h" #include "hs_output.h" typedef struct hs_input_plugin hs_input_plugin; typedef struct hs_input_plugins hs_input_plugins; struct hs_input_plugin { char *name; lsb_heka_sandbox *hsb; hs_input_plugins *plugins; int ticker_interval; pthread_t thread; int list_index; hs_ip_checkpoint cp; lsb_heka_stats stats; sem_t shutdown; bool sample; bool orphaned; bool shutdown_terminate; }; struct hs_input_plugins { hs_input_plugin **list; hs_config *cfg; hs_checkpoint_reader *cpr; pthread_mutex_t list_lock; int list_cnt; int list_cap; hs_output output; }; void hs_init_input_plugins(hs_input_plugins *plugins, hs_config *cfg, hs_checkpoint_reader *cpr); void hs_free_input_plugins(hs_input_plugins *plugins); void hs_load_input_startup(hs_input_plugins *plugins); void hs_load_input_dynamic(hs_input_plugins *plugins, const char *name); void hs_stop_input_plugins(hs_input_plugins *plugins); void hs_wait_input_plugins(hs_input_plugins *plugins); #endif hindsight-0.12.7/src/hs_logger.c000066400000000000000000000036371301315165600165000ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** @brief Hindsight logging implementation @file */ #include "hs_logger.h" #include #include #include #include #include static pthread_mutex_t g_logger; static int g_loglevel; void hs_init_log(int loglevel) { g_loglevel = loglevel; if (pthread_mutex_init(&g_logger, NULL)) { perror("loger pthread_mutex_init failed"); exit(EXIT_FAILURE); } } int hs_get_log_level() { return g_loglevel; } void hs_free_log() { pthread_mutex_destroy(&g_logger); } void hs_log(void *context, const char *plugin, int severity, const char *fmt, ...) { (void)context; if (severity > g_loglevel) return; struct timespec ts; if (clock_gettime(CLOCK_REALTIME, &ts) == -1) { ts.tv_sec = time(NULL); fprintf(stderr, "%lld [error] hs_log clock_gettime failed\n", ts.tv_sec * 1000000000LL); ts.tv_nsec = 0; } const char *level; switch (severity) { case 7: level = "debug"; break; case 6: level = "info"; break; case 5: level = "notice"; break; case 4: level = "warning"; break; case 3: level = "error"; break; case 2: level = "crit"; break; case 1: level = "alert"; break; case 0: level = "panic"; break; default: level = "debug"; break; } va_list args; va_start(args, fmt); pthread_mutex_lock(&g_logger); fprintf(stderr, "%lld [%s] %s ", ts.tv_sec * 1000000000LL + ts.tv_nsec, level, plugin ? plugin : "unnamed"); vfprintf(stderr, fmt, args); fwrite("\n", 1, 1, stderr); pthread_mutex_unlock(&g_logger); va_end(args); } hindsight-0.12.7/src/hs_logger.h000066400000000000000000000015651301315165600165030ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** Hindsight logging facility @file */ #ifndef hs_logger_h_ #define hs_logger_h_ // todo add an option to output Heka protobuf logs /** * Initialize the log mutex * */ void hs_init_log(int loglevel); /** * Returns the current log level value * * @return int syslog severity level */ int hs_get_log_level(); /** * Destroy the log mutex * */ void hs_free_log(); /** * Hindsight log writer * * @param context * @param plugin * @param level * @param fmt */ void hs_log(void *context, const char *plugin, int level, const char *fmt, ...); #endif hindsight-0.12.7/src/hs_output.c000066400000000000000000000061511301315165600165530ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** @brief Hindsight output implementation @file */ #include "hs_output.h" #include #include #include #include #include #include #include #include "hs_logger.h" #include "hs_util.h" static const char g_module[] = "output"; static bool extract_id(const char *fn, unsigned long long *id) { size_t l = strlen(fn); size_t i = 0; for (; i < l && isdigit(fn[i]); ++i); if (i > 0 && i + 4 == l && strncmp(fn + i, ".log", 4) == 0) { *id = strtoull(fn, NULL, 10); return true; } return false; } static size_t find_last_id(const char *path) { unsigned long long file_id = 0, current_id = 0; struct dirent *entry; DIR *dp = opendir(path); if (dp == NULL) return file_id; while ((entry = readdir(dp))) { if (extract_id(entry->d_name, ¤t_id)) { if (current_id > file_id) { file_id = current_id; } } } closedir(dp); return file_id; } void hs_init_output(hs_output *output, const char *path, const char *subdir) { output->fh = NULL; output->cp.offset = 0; size_t len = strlen(path) + strlen(subdir) + 2; output->path = malloc(len); if (!output->path) { hs_log(NULL, g_module, 0, "output path malloc failed"); exit(EXIT_FAILURE); } snprintf(output->path, len, "%s/%s", path, subdir); output->cp.id = output->min_cp_id = find_last_id(output->path); int ret = mkdir(path, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP); if (ret && errno != EEXIST) { hs_log(NULL, g_module, 0, "output path could not be created: %s", path); exit(EXIT_FAILURE); } ret = mkdir(output->path, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP); if (ret && errno != EEXIST) { hs_log(NULL, g_module, 0, "output path could not be created: %s", output->path); exit(EXIT_FAILURE); } if (pthread_mutex_init(&output->lock, NULL)) { perror("output lock pthread_mutex_init failed"); exit(EXIT_FAILURE); } hs_open_output_file(output); } void hs_free_output(hs_output *output) { if (output->fh) fclose(output->fh); output->fh = NULL; free(output->path); output->path = NULL; pthread_mutex_destroy(&output->lock); } void hs_open_output_file(hs_output *output) { static char fqfn[260]; if (output->fh) { fclose(output->fh); output->fh = NULL; } int ret = snprintf(fqfn, sizeof(fqfn), "%s/%llu.log", output->path, output->cp.id); if (ret < 0 || ret > (int)sizeof(fqfn) - 1) { hs_log(NULL, g_module, 0, "output filename exceeds %zu", sizeof(fqfn)); exit(EXIT_FAILURE); } output->fh = fopen(fqfn, "a+e"); if (!output->fh) { hs_log(NULL, g_module, 0, "%s: %s", fqfn, strerror(errno)); exit(EXIT_FAILURE); } else { fseek(output->fh, 0, SEEK_END); } output->cp.offset = ftell(output->fh); } hindsight-0.12.7/src/hs_output.h000066400000000000000000000015361301315165600165620ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** Hindsight output functions and structures @file */ #ifndef hs_output_h_ #define hs_output_h_ #include "hs_checkpoint_reader.h" #include "hs_config.h" #include #include #include typedef struct hs_output { FILE *fh; char *path; unsigned long long min_cp_id; pthread_mutex_t lock; hs_checkpoint cp; } hs_output; void hs_init_output(hs_output *output, const char *path, const char *subdir); void hs_free_output(hs_output *output); void hs_open_output_file(hs_output *output); #endif hindsight-0.12.7/src/hs_output_plugins.c000066400000000000000000000576641301315165600203330ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** @brief Hindsight output plugin loader @file */ #include "hs_output_plugins.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hs_input.h" #include "hs_logger.h" #include "hs_output.h" #include "hs_util.h" static const char g_module[] = "output_plugins"; static void destroy_output_plugin(hs_output_plugin *p) { if (!p) return; hs_free_input(&p->analysis); hs_free_input(&p->input); char *msg = lsb_heka_destroy_sandbox(p->hsb); if (msg) { hs_log(NULL, p->name, 3, "lsb_heka_destroy_sandbox failed: %s", msg); free(msg); } lsb_destroy_message_matcher(p->mm); free(p->name); free(p->async_cp); pthread_mutex_destroy(&p->cp_lock); free(p); } static void update_checkpoint(hs_output_plugin *p) { pthread_mutex_lock(&p->cp_lock); p->cp.input.id = p->cur.input.id; p->cp.input.offset = p->cur.input.offset; p->cp.analysis.id = p->cur.analysis.id; p->cp.analysis.offset = p->cur.analysis.offset; pthread_mutex_unlock(&p->cp_lock); } static int update_checkpoint_callback(void *parent, void *sequence_id) { hs_output_plugin *p = parent; if (sequence_id && p->async_cp) { int i = (uintptr_t)sequence_id % p->async_len; pthread_mutex_lock(&p->cp_lock); if ((p->async_cp[i].input.id == p->cp.input.id && p->async_cp[i].input.offset > p->cp.input.offset) || p->async_cp[i].input.id > p->cp.input.id) { p->cp.input.id = p->async_cp[i].input.id; p->cp.input.offset = p->async_cp[i].input.offset; } if ((p->async_cp[i].analysis.id == p->cp.analysis.id && p->async_cp[i].analysis.offset > p->cp.analysis.offset) || p->async_cp[i].analysis.id > p->cp.analysis.id) { p->cp.analysis.id = p->async_cp[i].analysis.id; p->cp.analysis.offset = p->async_cp[i].analysis.offset; } pthread_mutex_unlock(&p->cp_lock); } else if (p->batching) { update_checkpoint(p); p->batching = false; } return 0; } static hs_output_plugin* create_output_plugin(const hs_config *cfg, hs_sandbox_config *sbc) { char lua_file[HS_MAX_PATH]; if (!hs_find_lua(cfg, sbc, hs_output_dir, lua_file, sizeof(lua_file))) { hs_log(NULL, g_module, 3, "%s failed to find the specified lua filename: %s" , sbc->cfg_name, sbc->filename); return NULL; } hs_output_plugin *p = calloc(1, sizeof(hs_output_plugin)); if (!p) { hs_log(NULL, g_module, 2, "%s hs_output_plugin memory allocation failed", sbc->cfg_name); return NULL; } if (pthread_mutex_init(&p->cp_lock, NULL)) { free(p); hs_log(NULL, g_module, 3, "%s pthread_mutex_init failed", sbc->cfg_name); return NULL; } p->list_index = -1; p->sequence_id = 1; p->ticker_interval = sbc->ticker_interval; p->rm_cp_terminate = sbc->rm_cp_terminate; p->shutdown_terminate = sbc->shutdown_terminate; int stagger = p->ticker_interval > 60 ? 60 : p->ticker_interval; // distribute when the timer_events will fire if (stagger) { p->ticker_expires = time(NULL) + rand() % stagger; } if (sbc->async_buffer_size > 0) { p->async_len = sbc->async_buffer_size; p->async_cp = calloc(p->async_len, sizeof(hs_checkpoint_pair)); if (!p->async_cp) { destroy_output_plugin(p); hs_log(NULL, g_module, 2, "%s async buffer memory allocation failed", sbc->cfg_name); return NULL; } } p->mm = lsb_create_message_matcher(sbc->message_matcher); if (!p->mm) { hs_log(NULL, g_module, 3, "%s invalid message_matcher: %s", sbc->cfg_name, sbc->message_matcher); destroy_output_plugin(p); return NULL; } size_t len = strlen(sbc->cfg_name) + 1; p->name = malloc(len); if (!p->name) { hs_log(NULL, g_module, 2, "%s name memory allocation failed", sbc->cfg_name); destroy_output_plugin(p); } memcpy(p->name, sbc->cfg_name, len); char *state_file = NULL; if (sbc->preserve_data) { size_t len = strlen(cfg->output_path) + strlen(sbc->cfg_name) + 7; state_file = malloc(len); if (!state_file) { hs_log(NULL, g_module, 2, "%s state_file memory allocation failed", sbc->cfg_name); destroy_output_plugin(p); return NULL; } int ret = snprintf(state_file, len, "%s/%s.data", cfg->output_path, sbc->cfg_name); if (ret < 0 || ret > (int)len - 1) { hs_log(NULL, g_module, 3, "%s failed to construct the state_file path", sbc->cfg_name); free(state_file); destroy_output_plugin(p); return NULL; } } lsb_output_buffer ob; if (lsb_init_output_buffer(&ob, 8 * 1024)) { hs_log(NULL, g_module, 3, "%s configuration memory allocation failed", sbc->cfg_name); free(state_file); destroy_output_plugin(p); return NULL; } if (!hs_get_full_config(&ob, 'o', cfg, sbc)) { hs_log(NULL, g_module, 3, "%s hs_get_full_config failed", sbc->cfg_name); lsb_free_output_buffer(&ob); free(state_file); destroy_output_plugin(p); return NULL; } lsb_logger logger = { .context = NULL, .cb = hs_log }; p->hsb = lsb_heka_create_output(p, lua_file, state_file, ob.buf, &logger, update_checkpoint_callback); lsb_free_output_buffer(&ob); free(sbc->cfg_lua); sbc->cfg_lua = NULL; free(state_file); if (!p->hsb) { destroy_output_plugin(p); hs_log(NULL, g_module, 3, "%s lsb_heka_create_output failed", sbc->cfg_name); return NULL; } return p; } static void shutdown_timer_event(hs_output_plugin *p) { if (lsb_heka_is_running(p->hsb)) { if (lsb_heka_timer_event(p->hsb, time(NULL), true)) { hs_log(NULL, p->name, 3, "terminated: %s", lsb_heka_get_error(p->hsb)); } } } static int output_message(hs_output_plugin *p, lsb_heka_message *msg, bool sample) { int ret = 0, te_ret = 0; time_t current_t = time(NULL); unsigned long long start; unsigned long long mmdelta = 0; if (msg->raw.s) { // non idle/empty message if (sample) start = lsb_get_time(); bool matched = lsb_eval_message_matcher(p->mm, msg); if (sample) { mmdelta = lsb_get_time() - start; } if (matched) { if (p->async_len) { int i = p->sequence_id % p->async_len; p->async_cp[i].input.id = p->cur.input.id; p->async_cp[i].input.offset = p->cur.input.offset; p->async_cp[i].analysis.id = p->cur.analysis.id; p->async_cp[i].analysis.offset = p->cur.analysis.offset; } ret = lsb_heka_pm_output(p->hsb, msg, (void *)p->sequence_id, sample); if (ret <= 0) { if (ret == LSB_HEKA_PM_SENT) { p->batching = false; } else if (ret == LSB_HEKA_PM_BATCH) { p->batching = true; } else if (ret == LSB_HEKA_PM_ASYNC) { if (!p->async_len) { lsb_heka_terminate_sandbox(p->hsb, "cannot use async checkpointing " "without a configured buffer"); ret = 1; } p->batching = true; } else if (ret == LSB_HEKA_PM_FAIL) { const char *err = lsb_heka_get_error(p->hsb); if (strlen(err) > 0) { hs_log(NULL, p->name, 4, "process_message returned: %d %s", ret, err); } } if (ret != LSB_HEKA_PM_RETRY) ++p->sequence_id; } } // advance the checkpoint if not batching/async if (ret <= 0 && !p->batching) { update_checkpoint(p); } } if (sample) { pthread_mutex_lock(&p->cp_lock); if (mmdelta) { lsb_update_running_stats(&p->mms, mmdelta); } p->stats = lsb_heka_get_stats(p->hsb); p->sample = false; pthread_mutex_unlock(&p->cp_lock); } if (ret <= 0 && p->ticker_interval && current_t >= p->ticker_expires) { te_ret = lsb_heka_timer_event(p->hsb, current_t, false); p->ticker_expires = current_t + p->ticker_interval; } if (ret > 0 || te_ret > 0) { hs_log(NULL, p->name, 3, "terminated: %s", lsb_heka_get_error(p->hsb)); return 1; } return ret; } static void* input_thread(void *arg) { lsb_heka_message *msg = NULL; lsb_heka_message im, *pim = NULL; lsb_init_heka_message(&im, 8); lsb_heka_message am, *pam = NULL; lsb_init_heka_message(&am, 8); hs_output_plugin *p = (hs_output_plugin *)arg; hs_log(NULL, p->name, 6, "starting"); size_t discarded_bytes; size_t bytes_read[2] = { 0 }; int ret = 0; bool stop = false; bool sample = false; bool next_input_available = false; bool next_analysis_available = false; lsb_logger logger = { .context = NULL, .cb = hs_log }; #ifdef HINDSIGHT_CLI bool input_stop = false; bool analysis_stop = false; while (!(stop && input_stop && analysis_stop)) { #else while (!stop) { #endif pthread_mutex_lock(&p->cp_lock); stop = p->stop; sample = p->sample; pthread_mutex_unlock(&p->cp_lock); if (p->input.fh && !pim) { if (lsb_find_heka_message(&im, &p->input.ib, true, &discarded_bytes, &logger)) { pim = &im; } else { bytes_read[0] = hs_read_file(&p->input); } // when the read gets to the end it will always check once for the next // available file just incase the output_size was increased on the last // restart if (!bytes_read[0] && (p->input.cp.offset >= p->plugins->cfg->output_size || next_input_available)) { next_input_available = hs_open_file(&p->input, hs_input_dir, p->input.cp.id + 1); #ifdef HINDSIGHT_CLI if (!next_input_available && stop) { input_stop = true; } #endif } } else if (!p->input.fh) { // still waiting on the first file next_input_available = hs_open_file(&p->input, hs_input_dir, p->input.cp.id); #ifdef HINDSIGHT_CLI if (!next_input_available && stop) { input_stop = true; } #endif } if (p->analysis.fh && !pam) { if (lsb_find_heka_message(&am, &p->analysis.ib, true, &discarded_bytes, &logger)) { pam = &am; } else { bytes_read[1] = hs_read_file(&p->analysis); } if (!bytes_read[1] && (p->analysis.cp.offset >= p->plugins->cfg->output_size || next_analysis_available)) { next_analysis_available = hs_open_file(&p->analysis, hs_analysis_dir, p->analysis.cp.id + 1); #ifdef HINDSIGHT_CLI if (!next_analysis_available && stop) { analysis_stop = true; } #endif } } else if (!p->analysis.fh) { // still waiting on the first file next_analysis_available = hs_open_file(&p->analysis, hs_analysis_dir, p->analysis.cp.id); #ifdef HINDSIGHT_CLI if (!next_analysis_available && stop) { analysis_stop = true; } #endif } // if we have one send the oldest first if (pim) { if (pam) { if (pim->timestamp <= pam->timestamp) { msg = pim; } else { msg = pam; } } else { msg = pim; } } else if (pam) { msg = pam; } if (msg) { pthread_mutex_lock(&p->cp_lock); if (msg == pim) { pim = NULL; p->cur.input.id = p->input.cp.id; p->cur.input.offset = p->input.cp.offset - (p->input.ib.readpos - p->input.ib.scanpos); } else { pam = NULL; p->cur.analysis.id = p->analysis.cp.id; p->cur.analysis.offset = p->analysis.cp.offset - (p->analysis.ib.readpos - p->analysis.ib.scanpos); } pthread_mutex_unlock(&p->cp_lock); ret = output_message(p, msg, sample); if (ret == LSB_HEKA_PM_RETRY) { while (!stop) { const char *err = lsb_heka_get_error(p->hsb); hs_log(NULL, p->name, 7, "retry message %llu err: %s", p->sequence_id, err); sleep(1); ret = output_message(p, msg, false); if (ret == LSB_HEKA_PM_RETRY) { pthread_mutex_lock(&p->cp_lock); stop = p->stop; pthread_mutex_unlock(&p->cp_lock); continue; } break; } } if (ret > 0) { break; // fatal error } msg = NULL; } else if (!bytes_read[0] && !bytes_read[1]) { // trigger any pending timer events lsb_clear_heka_message(&im); // create an idle/empty message msg = &im; output_message(p, msg, sample); if (sample) { pthread_mutex_lock(&p->cp_lock); p->sample = false; pthread_mutex_unlock(&p->cp_lock); } msg = NULL; sleep(1); } } shutdown_timer_event(p); lsb_free_heka_message(&am); lsb_free_heka_message(&im); // hold the current checkpoints in memory incase we restart it hs_output_plugins *plugins = p->plugins; hs_update_input_checkpoint(plugins->cpr, hs_input_dir, p->name, &p->cp.input); hs_update_input_checkpoint(plugins->cpr, hs_analysis_dir, p->name, &p->cp.analysis); if (stop) { hs_log(NULL, p->name, 6, "shutting down"); } else { const char *err = lsb_heka_get_error(p->hsb); hs_log(NULL, p->name, 6, "detaching received: %d msg: %s", ret, err); hs_save_termination_err(plugins->cfg, p->name, err); if (p->rm_cp_terminate) { char key[HS_MAX_PATH]; snprintf(key, HS_MAX_PATH, "%s->%s", hs_input_dir, p->name); hs_remove_checkpoint(plugins->cpr, key); snprintf(key, HS_MAX_PATH, "%s->%s", hs_analysis_dir, p->name); hs_remove_checkpoint(plugins->cpr, key); } pthread_mutex_lock(&plugins->list_lock); plugins->list[p->list_index] = NULL; if (pthread_detach(p->thread)) { hs_log(NULL, p->name, 3, "thread could not be detached"); } if (p->shutdown_terminate) { hs_log(NULL, p->name, 6, "shutting down on terminate"); kill(getpid(), SIGTERM); } destroy_output_plugin(p); --plugins->list_cnt; pthread_mutex_unlock(&plugins->list_lock); } pthread_exit(NULL); } static void remove_plugin(hs_output_plugins *plugins, int idx) { hs_output_plugin *p = plugins->list[idx]; plugins->list[idx] = NULL; pthread_mutex_lock(&p->cp_lock); p->stop = true; pthread_mutex_unlock(&p->cp_lock); if (pthread_join(p->thread, NULL)) { hs_log(NULL, p->name, 3, "remove_plugin could not pthread_join"); } destroy_output_plugin(p); --plugins->list_cnt; } static void remove_from_output_plugins(hs_output_plugins *plugins, const char *name) { const size_t tlen = strlen(hs_output_dir) + 1; hs_output_plugin *p; pthread_mutex_lock(&plugins->list_lock); for (int i = 0; i < plugins->list_cap; ++i) { p = plugins->list[i]; if (!p) continue; char *pos = p->name + tlen; if (strstr(name, pos) && strlen(pos) == strlen(name) - HS_EXT_LEN) { remove_plugin(plugins, i); char key[HS_MAX_PATH]; snprintf(key, HS_MAX_PATH, "%s->%s.%.*s", hs_input_dir, hs_output_dir, (int)strlen(name) - HS_EXT_LEN, name); hs_remove_checkpoint(plugins->cpr, key); snprintf(key, HS_MAX_PATH, "%s->%s.%.*s", hs_analysis_dir, hs_output_dir, (int)strlen(name) - HS_EXT_LEN, name); hs_remove_checkpoint(plugins->cpr, key); break; } } pthread_mutex_unlock(&plugins->list_lock); } static void add_plugin(hs_output_plugins *plugins, hs_output_plugin *p, int idx) { plugins->list[idx] = p; p->list_index = idx; ++plugins->list_cnt; } static void add_to_output_plugins(hs_output_plugins *plugins, hs_output_plugin *p, bool dynamic) { int idx = -1; pthread_mutex_lock(&plugins->list_lock); for (int i = 0; i < plugins->list_cap; ++i) { if (!plugins->list[i]) { idx = i; break; } } if (idx != -1) { add_plugin(plugins, p, idx); } else { // todo probably don't want to grow it by 1 ++plugins->list_cap; hs_output_plugin **tmp = realloc(plugins->list, sizeof(hs_output_plugin *) * plugins->list_cap); idx = plugins->list_cap - 1; if (tmp) { plugins->list = tmp; add_plugin(plugins, p, idx); } else { hs_log(NULL, g_module, 0, "plugins realloc failed"); exit(EXIT_FAILURE); } } pthread_mutex_unlock(&plugins->list_lock); assert(p->list_index >= 0); const char *path = dynamic ? NULL : p->plugins->cfg->output_path; // sync the output and read checkpoints // the read and output checkpoints can differ to allow for batching hs_lookup_input_checkpoint(p->plugins->cpr, hs_input_dir, p->name, path, &p->input.cp); p->cur.input.id = p->cp.input.id = p->input.cp.id; p->cur.input.offset = p->cp.input.offset = p->input.cp.offset; hs_lookup_input_checkpoint(p->plugins->cpr, hs_analysis_dir, p->name, path, &p->analysis.cp); p->cur.analysis.id = p->cp.analysis.id = p->analysis.cp.id; p->cur.analysis.offset = p->cp.analysis.offset = p->analysis.cp.offset; int ret = pthread_create(&p->thread, NULL, input_thread, (void *)p); if (ret) { perror("pthread_create failed"); exit(EXIT_FAILURE); } } void hs_init_output_plugins(hs_output_plugins *plugins, hs_config *cfg, hs_checkpoint_reader *cpr) { plugins->cfg = cfg; plugins->cpr = cpr; plugins->list = NULL; plugins->list_cnt = 0; plugins->list_cap = 0; if (pthread_mutex_init(&plugins->list_lock, NULL)) { perror("list_lock pthread_mutex_init failed"); exit(EXIT_FAILURE); } } void hs_wait_output_plugins(hs_output_plugins *plugins) { pthread_mutex_lock(&plugins->list_lock); for (int i = 0; i < plugins->list_cap; ++i) { if (!plugins->list[i]) continue; hs_output_plugin *p = plugins->list[i]; plugins->list[i] = NULL; if (pthread_join(p->thread, NULL)) { hs_log(NULL, p->name, 3, "thread could not be joined"); } destroy_output_plugin(p); --plugins->list_cnt; } pthread_mutex_unlock(&plugins->list_lock); } void hs_free_output_plugins(hs_output_plugins *plugins) { for (int i = 0; i < plugins->list_cap; ++i) { if (plugins->list[i]) { destroy_output_plugin(plugins->list[i]); plugins->list[i] = NULL; } } free(plugins->list); pthread_mutex_destroy(&plugins->list_lock); plugins->list = NULL; plugins->cfg = NULL; plugins->list_cnt = 0; plugins->list_cap = 0; } static void process_lua(hs_output_plugins *plugins, const char *name) { hs_config *cfg = plugins->cfg; const char *lpath = cfg->load_path_output; const char *rpath = cfg->run_path_output; char lua_lpath[HS_MAX_PATH]; char lua_rpath[HS_MAX_PATH]; char cfg_lpath[HS_MAX_PATH]; char cfg_rpath[HS_MAX_PATH]; size_t tlen = strlen(hs_output_dir) + 1; // move the Lua to the run directory if (hs_get_fqfn(lpath, name, lua_lpath, sizeof(lua_lpath))) { hs_log(NULL, g_module, 0, "load lua path too long"); exit(EXIT_FAILURE); } if (hs_get_fqfn(rpath, name, lua_rpath, sizeof(lua_rpath))) { hs_log(NULL, g_module, 0, "run lua path too long"); exit(EXIT_FAILURE); } if (rename(lua_lpath, lua_rpath)) { hs_log(NULL, g_module, 3, "failed to move: %s to %s errno: %d", lua_lpath, lua_rpath, errno); return; } // restart any plugins using this Lua code pthread_mutex_lock(&plugins->list_lock); for (int i = 0; i < plugins->list_cap; ++i) { if (!plugins->list[i]) continue; hs_output_plugin *p = plugins->list[i]; if (strcmp(lua_rpath, lsb_heka_get_lua_file(p->hsb)) == 0) { int ret = snprintf(cfg_lpath, HS_MAX_PATH, "%s/%s%s", lpath, p->name + tlen, hs_cfg_ext); if (ret < 0 || ret > HS_MAX_PATH - 1) { hs_log(NULL, g_module, 0, "load cfg path too long"); exit(EXIT_FAILURE); } ret = snprintf(cfg_rpath, HS_MAX_PATH, "%s/%s%s", rpath, p->name + tlen, hs_cfg_ext); if (ret < 0 || ret > HS_MAX_PATH - 1) { hs_log(NULL, g_module, 0, "run cfg path too long"); exit(EXIT_FAILURE); } // if no new cfg was provided, move the existing cfg to the load // directory if (!hs_file_exists(cfg_lpath)) { if (rename(cfg_rpath, cfg_lpath)) { hs_log(NULL, g_module, 3, "failed to move: %s to %s errno: %d", cfg_rpath, cfg_lpath, errno); } } } } pthread_mutex_unlock(&plugins->list_lock); } void hs_load_output_startup(hs_output_plugins *plugins) { hs_config *cfg = plugins->cfg; const char *dir = cfg->run_path_output; DIR *dp = opendir(dir); if (dp == NULL) { hs_log(NULL, g_module, 0, "%s: %s", dir, strerror(errno)); exit(EXIT_FAILURE); } struct dirent *entry; while ((entry = readdir(dp))) { hs_sandbox_config sbc; if (hs_load_sandbox_config(dir, entry->d_name, &sbc, &cfg->opd, 'o')) { hs_output_plugin *p = create_output_plugin(cfg, &sbc); if (p) { p->plugins = plugins; hs_init_input(&p->input, cfg->max_message_size, cfg->output_path, p->name); hs_init_input(&p->analysis, cfg->max_message_size, cfg->output_path, p->name); add_to_output_plugins(plugins, p, false); } else { hs_log(NULL, g_module, 3, "%s create_output_plugin failed", sbc.cfg_name); } hs_free_sandbox_config(&sbc); } } closedir(dp); } void hs_load_output_dynamic(hs_output_plugins *plugins, const char *name) { hs_config *cfg = plugins->cfg; const char *lpath = cfg->load_path_output; const char *rpath = cfg->run_path_output; if (hs_has_ext(name, hs_lua_ext)) { process_lua(plugins, name); return; } switch (hs_process_load_cfg(lpath, rpath, name)) { case 0: remove_from_output_plugins(plugins, name); break; case 1: // load remove_from_output_plugins(plugins, name); { hs_sandbox_config sbc; if (hs_load_sandbox_config(rpath, name, &sbc, &cfg->opd, 'o')) { hs_output_plugin *p = create_output_plugin(cfg, &sbc); if (p) { p->plugins = plugins; hs_init_input(&p->input, cfg->max_message_size, cfg->output_path, p->name); hs_init_input(&p->analysis, cfg->max_message_size, cfg->output_path, p->name); add_to_output_plugins(plugins, p, true); } else { hs_log(NULL, g_module, 3, "%s create_output_plugin failed", sbc.cfg_name); } hs_free_sandbox_config(&sbc); } } break; default: hs_log(NULL, g_module, 7, "%s ignored %s", __func__, name); break; } } void hs_stop_output_plugins(hs_output_plugins *plugins) { pthread_mutex_lock(&plugins->list_lock); for (int i = 0; i < plugins->list_cap; ++i) { if (!plugins->list[i]) continue; pthread_mutex_lock(&plugins->list[i]->cp_lock); plugins->list[i]->stop = true; pthread_mutex_unlock(&plugins->list[i]->cp_lock); } pthread_mutex_unlock(&plugins->list_lock); } hindsight-0.12.7/src/hs_output_plugins.h000066400000000000000000000042541301315165600203230ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** Hindsight output sandbox loader @file */ #ifndef hs_output_plugins_h_ #define hs_output_plugins_h_ #include #include #include #include #include #include #include #include #include #include #include "hs_config.h" #include "hs_input.h" #include "hs_output.h" typedef struct hs_output_plugin hs_output_plugin; typedef struct hs_output_plugins hs_output_plugins; struct hs_output_plugin { char *name; lsb_heka_sandbox *hsb; lsb_message_matcher *mm; hs_output_plugins *plugins; uintptr_t sequence_id; lsb_running_stats mms; lsb_heka_stats stats; int ticker_interval; time_t ticker_expires; pthread_t thread; int list_index; bool batching; bool stop; bool sample; bool rm_cp_terminate; bool shutdown_terminate; hs_input input; hs_input analysis; pthread_mutex_t cp_lock; hs_checkpoint_pair cp; hs_checkpoint_pair cur; hs_checkpoint_pair *async_cp; int async_len; }; struct hs_output_plugins { hs_output_plugin **list; hs_config *cfg; hs_checkpoint_reader *cpr; int list_cnt; int list_cap; pthread_mutex_t list_lock; }; void hs_init_output_plugins(hs_output_plugins *plugins, hs_config *cfg, hs_checkpoint_reader *cpr); void hs_free_output_plugins(hs_output_plugins *plugins); void hs_load_output_startup(hs_output_plugins *plugins); void hs_load_output_dynamic(hs_output_plugins *plugins, const char *name); void hs_stop_output_plugins(hs_output_plugins *plugins); void hs_wait_output_plugins(hs_output_plugins *plugins); #endif hindsight-0.12.7/src/hs_util.c000066400000000000000000000060011301315165600161620ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** @brief Hindsight util implementation @file */ #include "hs_util.h" #include #include #include #include bool hs_file_exists(const char *fn) { FILE *fh = fopen(fn, "re"); if (fh) { fclose(fh); return 1; } return 0; } int hs_get_fqfn(const char *path, const char *name, char *fqfn, size_t fqfn_len) { int rv = snprintf(fqfn, fqfn_len, "%s/%s", path, name); return (rv < 0 || rv > (int)fqfn_len - 1); } bool hs_find_lua(const hs_config *cfg, const hs_sandbox_config *sbc, const char *ptype, char *fqfn, size_t fqfn_len) { int rv = snprintf(fqfn, fqfn_len, "%s/%s", sbc->dir, sbc->filename); if (rv < 0 || rv > (int)fqfn_len - 1) return false; if (hs_file_exists(fqfn)) return true; rv = snprintf(fqfn, fqfn_len, "%s/%s/%s", cfg->install_path, ptype, sbc->filename); if (rv < 0 || rv > (int)fqfn_len - 1) return false; return hs_file_exists(fqfn); } int hs_output_lua_string(FILE *fh, const char *s) { int rv = 1; size_t len = strlen(s); for (unsigned i = 0; i < len && rv == 1; ++i) { switch (s[i]) { case '\n': rv = fwrite("\\n", 2, 1, fh); break; case '\r': rv = fwrite("\\r", 2, 1, fh); break; case '"': rv = fwrite("\\\"", 2, 1, fh); break; case '\\': fwrite("\\\\", 2, 1, fh); break; default: rv = fwrite(s + i, 1, 1, fh); break; } } return rv == 1 ? 0 : 1; } bool hs_has_ext(const char *fn, const char *ext) { size_t flen = strlen(fn); size_t elen = strlen(ext); if (flen <= elen) return false; // a fn with only an extension is invalid return strcmp(fn + flen - elen, ext) == 0 ? true : false; } unsigned hs_disk_free_ob(const char *path, unsigned ob_size) { struct statfs buf; if (ob_size == 0 || statfs(path, &buf)) return 0; return buf.f_bsize * buf.f_bavail / ob_size; } void hs_save_termination_err(const hs_config *cfg, const char *name, const char *err) { const char *pos = strchr(name, '.'); if (!pos) return; char fn[HS_MAX_PATH]; int ret = snprintf(fn, sizeof(fn), "%s/%.*s/%s.err", cfg->run_path, (int)(pos - name), name, pos + 1); if (ret < 0 || ret > (int)sizeof(fn) - 1) return; FILE *fh = fopen(fn, "we"); if (fh) { time_t t = time(NULL); struct tm tms; if (gmtime_r(&t, &tms)) { fprintf(fh, "%04d-%02d-%02dT%02d:%02d:%02d\t%s\n", tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday, tms.tm_hour, tms.tm_min, tms.tm_sec, err); } fclose(fh); } } hindsight-0.12.7/src/hs_util.h000066400000000000000000000052701301315165600161760ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** Hindsight utility functions @file */ #ifndef hs_util_h_ #define hs_util_h_ #include #include #include #include "hs_config.h" /** * Test a file exists and can be opened for reading. * * @param fn filename * * @return bool True if the file exists */ bool hs_file_exists(const char *fn); /** * Test if a filename ends with the specified extension * * @param fn filename * @param ext extension * * @return bool True if the extension matches */ bool hs_has_ext(const char *fn, const char *ext); /** * Attempts to locate the Lua file in the run_path and then the install_path. * Returns true if the file was found and the fully qualified filename was added * to the fqfn buffer. * * @param cfg Hindsight configuration * @param sbc Sandbox configuration * @param ptype Plugin type (dir name) * @param fqfn Buffer for the fully qualified file name to be returned in * @param fqfn_len Size of the buffer * * @return bool */ bool hs_find_lua(const hs_config *cfg, const hs_sandbox_config *sbc, const char *ptype, char *fqfn, size_t fqfn_len); /** * Constructs a fully qualified filename from the provided components * * @param path Base path * @param name File name * @param fqfn Buffer to construct the string in * @param fqfn_len Length of the buffer * * @return int 0 if string was successfully constructed */ int hs_get_fqfn(const char *path, const char *name, char *fqfn, size_t fqfn_len); /** * Escapes a string being written to a Lua file * * @param fh file handle write the string to * @param s string to escape * * @return int 0 if successfully escaped/output */ int hs_output_lua_string(FILE *fh, const char *s); /** * Returns the amount of free disk space as the number of output * buffers remaining. * * @param path Pathname to a file mounted on the filesystem * @param ob_size Output buffer size in bytes * * @return unsigned Number of buffers */ unsigned hs_disk_free_ob(const char *path, unsigned ob_size); /** * Writes the termination error to disk * * @param cfg Hindsight configuration * @param name Sandbox name * @param err Sandbox error message */ void hs_save_termination_err(const hs_config *cfg, const char *name, const char *err); #endif hindsight-0.12.7/src/test/000077500000000000000000000000001301315165600153315ustar00rootroot00000000000000hindsight-0.12.7/src/test/CMakeLists.txt000066400000000000000000000010531301315165600200700ustar00rootroot00000000000000# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. configure_file(test.h.in test.h ESCAPE_QUOTES) include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_executable(test_config ../hs_config.c ../hs_logger.c ../hs_checkpoint_reader.c ../hs_util.c test_config.c) target_link_libraries(test_config ${HINDSIGHT_LIBS}) add_test(NAME test_config WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND test_config) hindsight-0.12.7/src/test/cfg/000077500000000000000000000000001301315165600160705ustar00rootroot00000000000000hindsight-0.12.7/src/test/cfg/default.cfg000066400000000000000000000005141301315165600201750ustar00rootroot00000000000000output_path = "output_path" sandbox_load_path = "load" sandbox_run_path = "run" io_lua_path = "io/?.lua" io_lua_cpath = "io/?.so" analysis_lua_path = "analysis/?.lua" analysis_lua_cpath = "analysis/?.so" input_defaults = { } analysis_defaults = { } output_defaults = { } hindsight-0.12.7/src/test/cfg/extra.cfg000066400000000000000000000005321301315165600176740ustar00rootroot00000000000000output_path = "output_path" sandbox_load_path = "load" sandbox_run_path = "run" io_lua_path = "io/?.lua" io_lua_cpath = "io/?.so" analysis_lua_path = "analysis/?.lua" analysis_lua_cpath = "analysis/?.so" input_defaults = { } analysis_defaults = { } output_defaults = { } extra = true hindsight-0.12.7/src/test/cfg/valid.cfg000066400000000000000000000012041301315165600176450ustar00rootroot00000000000000output_path = "output_path" output_size = 1024 backpressure = 10 sandbox_load_path = "load" sandbox_run_path = "run" io_lua_path = "io/?.lua" io_lua_cpath = "io/?.so" analysis_lua_path = "analysis/?.lua" analysis_lua_cpath = "analysis/?.so" input_defaults = { output_limit = 1023, memory_limit = 32767, instruction_limit = 1000, preserve_data = true, } analysis_defaults = { process_message_inject_limit = 0, timer_event_inject_limit = 10, } output_defaults = { remove_checkpoints_on_terminate = true, } hindsight-0.12.7/src/test/sandbox/000077500000000000000000000000001301315165600167675ustar00rootroot00000000000000hindsight-0.12.7/src/test/sandbox/analysis.cfg000066400000000000000000000006401301315165600212730ustar00rootroot00000000000000filename = "analysis.lua" output_limit = 77777 memory_limit = 88888 instruction_limit = 99999 ticker_interval = 17 preserve_data = true message_matcher = "TRUE" thread = 1 array = {"string", 99, false, true} hash = { string = "string", number = 99, ["false"] = false, ["true"] = true} nested = { array = {"string", 99, false, true}, hash = { string = "string", number = 99, ["false"] = false, ["true"] = true}, } hindsight-0.12.7/src/test/sandbox/analysis.lua000066400000000000000000000025171301315165600213220ustar00rootroot00000000000000-- This Source Code Form is subject to the terms of the Mozilla Public -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at http://mozilla.org/MPL/2.0/. local function test_array(a) assert(type(a) == "table", type(a)) assert(#a == 4) assert(a[1] == "string") assert(a[2] == 99) assert(a[3] == false) assert(a[4] == true) end local function test_hash(h) assert(type(h) == "table") assert(h.string == "string") assert(h.number == 99) assert(h["false"] == false) assert(h["true"] == true) end function process_message() assert(read_config("cfg_name") == "analysis") assert(read_config("filename") == "analysis.lua") assert(read_config("output_limit") == 77777) assert(read_config("memory_limit") == 88888) assert(read_config("instruction_limit") == 99999) assert(read_config("ticker_interval") == 17) assert(read_config("preserve_data")) assert(read_config("message_matcher") == "TRUE") assert(read_config("thread") == 1) assert(read_config("async_buffer_size") == nil) test_array(read_config("array")) test_hash(read_config("hash")) local nested = read_config("nested") assert(type(nested) == "table") test_array(nested.array) test_hash(nested.hash) return 0 end function timer_event() end hindsight-0.12.7/src/test/sandbox/input.cfg000066400000000000000000000000271301315165600206060ustar00rootroot00000000000000filename = "input.lua" hindsight-0.12.7/src/test/sandbox/input.lua000066400000000000000000000014211301315165600206270ustar00rootroot00000000000000-- This Source Code Form is subject to the terms of the Mozilla Public -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at http://mozilla.org/MPL/2.0/. function process_message() assert(read_config("cfg_name") == "input") assert(read_config("filename") == "input.lua") assert(read_config("output_limit") == 1024 * 64) assert(read_config("memory_limit") == 1024 * 1024 * 8) assert(read_config("instruction_limit") == 1000000) assert(read_config("ticker_interval") == 0) assert(read_config("preserve_data") == false) assert(read_config("message_matcher") == nil) assert(read_config("thread") == nil) assert(read_config("async_buffer_size") == nil) return 0 end function timer_event() end hindsight-0.12.7/src/test/sandbox/invalid_fn_ext.cfg000066400000000000000000000000321301315165600224340ustar00rootroot00000000000000filename = "analysis.foo" hindsight-0.12.7/src/test/sandbox/output.cfg000066400000000000000000000001111301315165600210010ustar00rootroot00000000000000filename = "output.lua" message_matcher = "TRUE" async_buffer_size = 999 hindsight-0.12.7/src/test/sandbox/output.lua000066400000000000000000000014261301315165600210350ustar00rootroot00000000000000-- This Source Code Form is subject to the terms of the Mozilla Public -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at http://mozilla.org/MPL/2.0/. function process_message() assert(read_config("cfg_name") == "output") assert(read_config("filename") == "output.lua") assert(read_config("output_limit") == 1024 * 64) assert(read_config("memory_limit") == 1024 * 1024 * 8) assert(read_config("instruction_limit") == 1000000) assert(read_config("ticker_interval") == 0) assert(read_config("preserve_data") == false) assert(read_config("message_matcher") == "TRUE") assert(read_config("thread") == nil) assert(read_config("async_buffer_size") == 999) return 0 end function timer_event() end hindsight-0.12.7/src/test/sandbox/path_in_fn.cfg000066400000000000000000000000371301315165600215550ustar00rootroot00000000000000filename = "/tmp/analysis.lua" hindsight-0.12.7/src/test/test.h.in000066400000000000000000000045001301315165600170650ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** @brief Hindsight unit tests macros @file */ #ifndef test_h_ #define test_h_ #include #include #include #include #define TEST_LUA_PATH "${LUASANDBOX_MODULES}/modules/?.lua" #ifdef _WIN32 #define snprintf _snprintf #define TEST_LUA_CPATH "${LUASANDBOX_MODULES}/modules/?.dll" #else #define TEST_LUA_CPATH "${LUASANDBOX_MODULES}/modules/?.so" #endif #define mu_assert(cond, ...) \ do { \ if (!(cond)) { \ int cnt = snprintf(mu_err, MU_ERR_LEN, "line: %d (%s) ", __LINE__, #cond); \ if (cnt > 0 && cnt < MU_ERR_LEN) { \ cnt = snprintf(mu_err+cnt, MU_ERR_LEN-cnt, __VA_ARGS__); \ if (cnt > 0 && cnt < MU_ERR_LEN) { \ return mu_err; \ } \ } \ mu_err[MU_ERR_LEN - 1] = 0; \ return mu_err; \ } \ } while (0) #define mu_run_test(test) \ do { \ char *message = test(); \ mu_tests_run++; \ if (message) \ return message; \ } while (0) #define MU_ERR_LEN 1024 int mu_tests_run = 0; char mu_err[MU_ERR_LEN] = { 0 }; #endifhindsight-0.12.7/src/test/test_config.c000066400000000000000000000155461301315165600200140ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** @brief Hindsight unit tests @file */ #include "test.h" #include #include #include #include #include "../hs_config.h" #include "../hs_logger.h" char *e = NULL; static char* test_load_default_config() { hs_config cfg; int ret = hs_load_config("cfg/default.cfg", &cfg); mu_assert(ret == 0, "hindsight_load_config: %d", ret); mu_assert(strcmp(cfg.output_path, "output_path") == 0, "received %s", cfg.output_path); mu_assert(cfg.max_message_size == 1024 * 64, "received %d", cfg.max_message_size); mu_assert(cfg.output_size == 1024 * 1024 * 64, "received %d", cfg.output_size); mu_assert(cfg.backpressure == 0, "received %d", cfg.backpressure); mu_assert(strcmp(cfg.run_path, "run") == 0, "received %s", cfg.run_path); mu_assert(strcmp(cfg.load_path, "load") == 0, "received %s", cfg.load_path); mu_assert(strcmp(cfg.io_lua_path, "io/?.lua") == 0, "received %s", cfg.io_lua_path); mu_assert(strcmp(cfg.io_lua_cpath, "io/?.so") == 0, "received %s", cfg.io_lua_cpath); mu_assert(strcmp(cfg.analysis_lua_path, "analysis/?.lua") == 0, "received %s", cfg.analysis_lua_path); mu_assert(strcmp(cfg.analysis_lua_cpath, "analysis/?.so") == 0, "received %s", cfg.analysis_lua_cpath); mu_assert(cfg.ipd.output_limit == 1024 * 64, "received %d", cfg.ipd.output_limit); mu_assert(cfg.ipd.memory_limit == 1024 * 1024 * 8, "received %d", cfg.ipd.memory_limit); mu_assert(cfg.ipd.instruction_limit == 1000000, "received %d", cfg.ipd.instruction_limit); mu_assert(cfg.ipd.preserve_data == false, "received %d", cfg.ipd.preserve_data); hs_free_config(&cfg); return NULL; } static char* test_load_config() { hs_config cfg; int ret = hs_load_config("cfg/valid.cfg", &cfg); mu_assert(ret == 0, "hindsight_load_config: %d", ret); mu_assert(strcmp(cfg.output_path, "output_path") == 0, "received %s", cfg.output_path); mu_assert(cfg.output_size == 1024, "received %d", cfg.output_size); mu_assert(cfg.backpressure == 10, "received %d", cfg.backpressure); mu_assert(strcmp(cfg.run_path, "run") == 0, "received %s", cfg.run_path); mu_assert(strcmp(cfg.load_path, "load") == 0, "received %s", cfg.load_path); mu_assert(strcmp(cfg.io_lua_path, "io/?.lua") == 0, "received %s", cfg.io_lua_path); mu_assert(strcmp(cfg.io_lua_cpath, "io/?.so") == 0, "received %s", cfg.io_lua_cpath); mu_assert(strcmp(cfg.analysis_lua_path, "analysis/?.lua") == 0, "received %s", cfg.analysis_lua_path); mu_assert(strcmp(cfg.analysis_lua_cpath, "analysis/?.so") == 0, "received %s", cfg.analysis_lua_cpath); mu_assert(cfg.ipd.output_limit == 1023, "received %d", cfg.ipd.output_limit); mu_assert(cfg.ipd.memory_limit == 32767, "received %d", cfg.ipd.memory_limit); mu_assert(cfg.ipd.instruction_limit == 1000, "received %d", cfg.ipd.instruction_limit); mu_assert(cfg.ipd.preserve_data == true, "received %d", cfg.ipd.preserve_data); hs_free_config(&cfg); return NULL; } static char* test_load_invalid_config() { hs_config cfg; int ret = hs_load_config("cfg/extra.cfg", &cfg); mu_assert(ret == 1, "hindsight_load_config: %d", ret); hs_free_config(&cfg); return NULL; } static char* test_sandbox_input_config() { hs_sandbox_config cfg; bool ret = hs_load_sandbox_config("sandbox", "input.cfg", &cfg, NULL, 'i'); mu_assert(ret, "hs_load_sandbox_config failed"); mu_assert(strcmp(cfg.filename, "input.lua") == 0, "received %s", cfg.filename); mu_assert(cfg.message_matcher == NULL, "received %s", cfg.message_matcher); mu_assert(cfg.async_buffer_size == 0, "received %d", cfg.async_buffer_size); mu_assert(cfg.thread == 0, "received %d", cfg.thread); hs_free_sandbox_config(&cfg); return NULL; } static char* test_sandbox_analysis_config() { hs_sandbox_config cfg; bool ret = hs_load_sandbox_config("sandbox", "analysis.cfg", &cfg, NULL, 'a'); mu_assert(ret, "hs_load_sandbox_config failed"); mu_assert(strcmp(cfg.filename, "analysis.lua") == 0, "received %s", cfg.filename); mu_assert(strcmp(cfg.cfg_name, "analysis.analysis") == 0, "received %s", cfg.cfg_name); mu_assert(cfg.output_limit == 77777, "received %d", cfg.output_limit); mu_assert(cfg.memory_limit == 88888, "received %d", cfg.memory_limit); mu_assert(cfg.instruction_limit == 99999, "received %d", cfg.instruction_limit); mu_assert(cfg.ticker_interval == 17, "received %d", cfg.ticker_interval); mu_assert(cfg.preserve_data == true, "received %s", cfg.preserve_data ? "true" : "false"); mu_assert(strcmp(cfg.message_matcher, "TRUE") == 0, "received %s", cfg.message_matcher); mu_assert(cfg.thread == 1, "received %d", cfg.thread); mu_assert(cfg.async_buffer_size == 0, "received %d", cfg.async_buffer_size); hs_free_sandbox_config(&cfg); return NULL; } static char* test_sandbox_output_config() { hs_sandbox_config cfg; bool ret = hs_load_sandbox_config("sandbox", "output.cfg", &cfg, NULL, 'o'); mu_assert(ret, "hs_load_sandbox_config failed"); mu_assert(strcmp(cfg.filename, "output.lua") == 0, "received %s", cfg.filename); mu_assert(cfg.async_buffer_size == 999, "received %d", cfg.async_buffer_size); mu_assert(cfg.thread == 0, "received %d", cfg.thread); hs_free_sandbox_config(&cfg); return NULL; } static char* test_sandbox_filename_config() { hs_sandbox_config cfg; bool ret = hs_load_sandbox_config("sandbox", "path_in_fn.cfg", &cfg, NULL, 'i'); mu_assert(!ret, "accepted a filename with a path"); hs_free_sandbox_config(&cfg); ret = hs_load_sandbox_config("sandbox", "invalid_fn_ext.cfg", &cfg, NULL, 'i'); mu_assert(!ret, "accepted a filename with a invalid extension"); hs_free_sandbox_config(&cfg); return NULL; } static char* all_tests() { mu_run_test(test_load_default_config); mu_run_test(test_load_config); mu_run_test(test_load_invalid_config); mu_run_test(test_sandbox_input_config); mu_run_test(test_sandbox_analysis_config); mu_run_test(test_sandbox_output_config); mu_run_test(test_sandbox_filename_config); return NULL; } int main() { hs_init_log(7); char *result = all_tests(); if (result) { printf("%s\n", result); } else { printf("ALL TESTS PASSED\n"); } printf("Tests run: %d\n", mu_tests_run); free(e); hs_free_log(); return result != 0; }