#!/usr/bin/env ruby # psched.rb # Created by Paolo Bosetti on 2011-04-21. # Copyright (c) 2011 University of Trento. All rights reserved. require 'ffi' require 'psched/version' # A module and a class ({PSched::Operation}) for implementing a recurring # call as deterministic as possible. # # See {PSched::Operation} for usage details. Module functions are FFI # wrappers and are not intended to be used directly. # @author Paolo Bosetti # @todo Are there better ways? module PSched extend FFI::Library ffi_lib FFI::Library::LIBC # @!method ualarm(useconds, interval) # The `ualarm()` function waits a count of useconds before asserting the # terminating signal `SIGALRM`. System activity or time used in processing # the call may cause a slight delay. # If the interval argument is non-zero, the `SIGALRM` signal will be sent to # the process every interval microseconds after the timer expires # (e.g.,after useconds number of microseconds have passed). # @param [Fixnum] useconds initial delay in microseconds # @param [Fixnum] interval recurring interval in microseconds # @return [Fixnum] the amount of time left on the clock attach_function :ualarm, [:uint, :uint], :uint # @!method alarm(seconds) # Set a timer to deliver the signal `SIGALRM` to the calling process after # the specified number of seconds # @param [Fixnum] seconds delay in microseconds # @return [Fixnum] the amount of time left on the clock from a previous # call to alarm() attach_function :alarm, [:uint], :uint enum :which, [ :prio_process, 0, :prio_pgrp, :prio_user, :prio_darwin_thread, :prio_darwin_process ] # @!method setpriority(which, who, prio) # Set the scheduling priority of the current process # @param [Fixnum] which The type of priority, as for the enum `which` # @param [Fixnum] who The process, group, user or thread # @param [Fixnum] prio The priority level, from -20 to 20 # @return [0|-1] 0 if no error, -1 if error attach_function :setpriority, [:which, :uint, :int], :int # Implements a recurring operation. # @example General usage. Note the `sleep` call. # op = PSched::Operation.new(0.5) # op.start(10) do |i| # puts "Ping #{i}" # end # # Signal.trap("SIGINT") do # print "Stopping recurring process..." # op.stop # puts "done!" # end # # sleep(0.2) while op.active? # THIS IS IMPORTANT!!! # # @author Paolo Bosetti class Operation # Changes scheduling priority of the current process # @param [Fixnum] nice sets the nice level (-20..+20) # @todo better return message and error management def self.prioritize(nice = -20) result = PSched.setpriority(:prio_process,0,nice) puts "Setting max priority: #{result == 0 ? 'success' : 'failure (missing sudo?)'}" end # @!attribute [rw] strict_timing # If true, raise a {RealTimeError} when `TET >= step` # (default to `false`) attr_accessor :strict_timing # @!attribute [r] tet # The Task Execution Time # @!attribute [r] step # The time step in seconds attr_reader :step, :tet # Initializer # @param [Numeric] step the timestep in seconds def initialize(step) @step = step @active = false @lock = false @strict_timing = false @tet = 0 end # Sets the time step (in seconds) and reschedule pending alarms. # @param [Numeric] secs Timestep in seconds def step=(secs) @step = secs self.schedule end # Updates scheduling of pending alarms. # @note Usually, there's no need to call this, since {#step=} automatically # calls it after having set the +@step+ attribute. def schedule usecs = @step * 1E6 PSched::ualarm(usecs, usecs) end # Tells id the recurring operation is active or not. # @return [Boolean] the status of the recurring alarm operation def active?; @active; end # Starts a recurring operation, described by the passed block. # If the block returns the symbol :stop, the recurrin operation gets # disabled. # @param [Fixnum,nil] n_iter the maximum number of iterations. # If nil it loops indefinitedly # @yieldparam [Fixnum] i the number of elapsed iterations # @yieldparam [Numeric] tet the Task Execution Time of previous step # @raise [ArgumentError] unless a block is given # @raise [RealTimeError] if TET exceeds @step def start(n_iter=nil) @active = true i = 0 raise ArgumentError, "Need a block!" unless block_given? Signal.trap(:ALRM) do # If there is still a pending step, raises an error containing # information about the CURRENT step if @lock then if @strict_timing @lock = false raise RealTimeError.new({:tet => @tet, :step => @step, :i => i, :time => Time.now}) end else start = Time.now @lock = true result = yield(i, @tet) i += 1 self.stop if (n_iter and i >= n_iter) self.stop if (result.kind_of? Symbol and result == :stop) @tet = Time.now - start @lock = false end end self.schedule end # Stops the recurring process by resetting alarm and disabling management # of `SIGALRM` signal. def stop PSched::ualarm(0, 0) Signal.trap(:ALRM, "DEFAULT") @active = false @lock = false end end end class RealTimeError < RuntimeError attr_reader :status def initialize(status) @status = status end def ratio (@status[:tet] / @status[:step]) * 100 end end if $0 == __FILE__ then PSched::Operation.prioritize op = PSched::Operation.new(0.01) op.start(10) do |i| puts "Ping #{i}" end Signal.trap("SIGINT") do print "Stopping recurring process..." op.stop puts "done!" end sleep while op.active? end