class LogTools::PostProcessing
Main class to build post-processing pipelines for log datasets
Attributes
Objects that define the parts of the dataset that should be processed
Public Class Methods
# File lib/log_tools/post_processing.rb, line 32 def initialize @region_matchers = Array.new end
Public Instance Methods
Excludes regions of the log file for post-processing
See {include} for more information about inclusions and exclusions
# File lib/log_tools/post_processing.rb, line 369 def exclude(matcher = LogStreamMatcher.new) region_matchers << [false, matcher] matcher end
# File lib/log_tools/post_processing.rb, line 336 def exclude_all region_matchers.clear @exclude_all = true end
Gets the set of streams that should be post-processed
@param [{Pocolog::Logfiles=>}] the set of logfiles
along with the set of matchers that should be used to filter this
logfile's streams. This is the same format returned by
{open_logfiles}
@return [{Pocolog::DataStream=>}] the set of streams along
with the set of matchers that should be used to further filter them.
# File lib/log_tools/post_processing.rb, line 61 def find_all_included_streams(logfiles) streams = Hash.new logfiles.each do |file, matchers| file.streams.map do |s| if related_matchers = perform_simple_match(!@exclude_all, matchers, "stream", s) streams[s] = related_matchers end end end streams end
# File lib/log_tools/post_processing.rb, line 184 def find_all_logfiles_in_dir(dir) Dir.enum_for(:glob, File.join(dir, '*.log')). find_all { |file| file =~ /\.\d+\.log$/ } end
Given a set of streams and annotations, outputs a per-stream set of regions that have to be postprocessed
The regions are sorted by time, and are guaranteed to not overlap
@param [{Pocolog::DataStream=>}] streams the set of streams
along with the set of matchers that should be used to determine the
regions. The method performs an union of the returned regions. This
is the same format as the value returned by
{find_all_included_streams}
@param [Array<Orocos::Log::Annotation>] array of annotations sorted in
time
@return [{Pocolog::DataStream=>}] a per-stream list of regions
that are sorted in time, and non-overlapping
# File lib/log_tools/post_processing.rb, line 109 def find_all_matching_regions(streams, annotations) regions_by_stream = Hash.new streams.each do |s, matchers| if s.empty? regions_by_stream[s] = [] next end initial_region = if @exclude_all then [] else [Region.new(*s.time_interval)] end raw_regions = matchers.inject(initial_region) do |whole_region, (inclusive, matcher)| r = matcher.matching_regions(s, annotations) if inclusive normalize_regions(whole_region + r) else substract_regions(whole_region, normalize_regions(r)) end end regions_by_stream[s] = raw_regions end regions_by_stream end
Gets all annotations stored in the provided streams
# File lib/log_tools/post_processing.rb, line 135 def gather_annotations(streams) raw_annotations = streams.keys.inject(Array.new) do |a, s| if s.type.name == "/logger/Annotations" a.concat(s.samples.enum_for(:raw_each).to_a) end a end raw_annotations.map(&:last).sort_by { |s| s.raw_time.microseconds } end
Includes regions of the log file for post-processing
By default, all the data is included
Inclusions and exclusions are processed in declaration order. This means that e.g. calling {include_logfile} and then {exclude_logstream} will exclude streams from this logfile. The other way around, the log stream exclusion would have no effect on the logfile inclusion (i.e. the log file would be completely included)
@return [LogStreamMatcher]
@example match a log file
processor.include_regions_matching. logfile(/camera/)
@example match all Time streams
processor.include_regions_matching. stream_type("/base/Time")
# File lib/log_tools/post_processing.rb, line 361 def include(matcher = LogStreamMatcher.new) region_matchers << [true, matcher] matcher end
# File lib/log_tools/post_processing.rb, line 331 def include_all region_matchers.clear @exclude_all = false end
Ensures that an array of Region objects is sorted and non-overlapping
@param [Array<Region>] non-empty array of Region @return [Array<Region>]
# File lib/log_tools/post_processing.rb, line 77 def normalize_regions(regions) regions = regions.sort_by { |r| r.start_time } filtered_regions = Array.new last_region = regions.shift regions.each do |r| if r.start_time < last_region.end_time last_region.end_time = [last_region.end_time, r.end_time].max else filtered_regions << last_region last_region = r end end if last_region filtered_regions << last_region end filtered_regions end
Opens the log files that are included by the configured matchers
@param [String] dir the dataset's directory @return [{Pocolog::Logfiles=>}] the set of opened logfiles
along with the set of matchers that should be used to further filter the logfile's streams
# File lib/log_tools/post_processing.rb, line 42 def open_logfiles(paths, matchers = region_matchers) logfiles = Hash.new paths.each do |path| basename = File.basename(path) if related_matchers = perform_simple_match(!@exclude_all, matchers, "logfile", basename) logfiles[Pocolog::Logfiles.open(path)] = related_matchers end end logfiles end
# File lib/log_tools/post_processing.rb, line 263 def perform_simple_match(initial_value, matchers, type, object, *args) related_matchers = Array.new does_match = matchers.inject(initial_value) do |v, (inclusion_matcher, matcher)| if !matcher.send("filters_#{type}s?") related_matchers << [inclusion_matcher, matcher] next(v || inclusion_matcher) end result = matcher.send("matches_#{type}?", object, *args) if result.nil? related_matchers << [inclusion_matcher, matcher] v || inclusion_matcher elsif result related_matchers << [inclusion_matcher, matcher] inclusion_matcher else v end end if does_match related_matchers end end
# File lib/log_tools/post_processing.rb, line 231 def run(dir, target_dir) files = Dir.enum_for(:glob, File.join(dir, '*.*.log')).to_a logfiles = open_logfiles(files) streams = find_all_included_streams(logfiles) annotations = gather_annotations(streams) regions_by_stream = find_all_matching_regions(streams, annotations) data_buffer = String.new target_logfiles = Hash.new regions_by_stream.each do |stream, regions| logfile_path = stream.logfile.io.path logfile_basename = File.basename(logfile_path).gsub(/\.\d+\.log$/, '') logfile = target_logfiles[logfile_basename] if !logfile target_path = File.join(target_dir, logfile_basename) target_logfiles[logfile_basename] = Pocolog::Logfiles. create(target_path) end target_stream = logfile.create_stream(stream.name, stream.type, stream.metadata) regions.each do |r| header = stream.seek(r.start_time) while header && header.lg < r.end_time logfile.data(header, data_buffer) target_stream.write_raw(header.rt, header.lg, data_buffer) header = stream.advance end end end end
# File lib/log_tools/post_processing.rb, line 196 def show(dir) files = find_all_logfiles_in_dir(dir) logfiles = open_logfiles(files) streams = find_all_included_streams(logfiles) annotations = gather_annotations(streams) regions_by_stream = find_all_matching_regions(streams, annotations) puts "Available logfiles:" files.sort.each do |f| puts " #{f}" end puts "Matchers:" region_matchers.each do |inclusive, m| show_matcher(inclusive, m, " ") end puts "Matched logfiles:" logfiles.each do |file, matchers| puts " #{file.io.first.path}, by" matchers.each do |inclusive, m| show_matcher(inclusive, m, " ") end end puts "Matched streams:" streams.each do |stream, matchers| puts " #{stream.name}, by" matchers.each do |inclusive, m| show_matcher(inclusive, m, " ") end end puts "Processed Regions" regions_by_stream.each do |stream, regions| puts " #{stream.name}: #{regions.map { |r| "[#{r.start_time}:#{r.end_time}]" }.join(" ")}" end end
# File lib/log_tools/post_processing.rb, line 189 def show_matcher(inclusive, m, indent) flag = if inclusive then '+' else '-' end puts " (#{flag}) #{m}" end
Substracts a set of regions from another set
@param [Array<Region>] regions a sorted, non-overlapping list of regions @param [Array<Region>] removed_regions a sorted, non-overlapping list of regions @return [Array<Region>] the set of regions that contain all intervals
in regions with the intervals in removed_regions removed
# File lib/log_tools/post_processing.rb, line 151 def substract_regions(regions, removed_regions) regions = regions.dup removed_regions = removed_regions.dup result = Array.new current = regions.shift while current && !removed_regions.empty? current = current.dup current_removal = removed_regions.first while current_removal && (current_removal.end_time <= current.start_time) current_removal = removed_regions.shift end if !current_removal break elsif current.end_time <= current_removal.start_time result << current current = regions.shift next end if current.start_time < current_removal.start_time result << Region.new(current.start_time, current_removal.start_time) end if current.end_time > current_removal.end_time current = Region.new(current_removal.end_time, current.end_time) else current = regions.shift end end result + [current].compact + regions end