Class: MetricScriptExecutor

Inherits:
Object
  • Object
show all
Defined in:
lib/instrumental_tools/metric_script_executor.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(directory) ⇒ MetricScriptExecutor

Returns a new instance of MetricScriptExecutor.



7
8
9
10
# File 'lib/instrumental_tools/metric_script_executor.rb', line 7

def initialize(directory)
  @directory   = directory
  @previous    = {}
end

Instance Attribute Details

#directoryObject (readonly)

Returns the value of attribute directory.



5
6
7
# File 'lib/instrumental_tools/metric_script_executor.rb', line 5

def directory
  @directory
end

#last_ran_atObject (readonly)

Returns the value of attribute last_ran_at.



5
6
7
# File 'lib/instrumental_tools/metric_script_executor.rb', line 5

def last_ran_at
  @last_ran_at
end

#previousObject (readonly)

Returns the value of attribute previous.



5
6
7
# File 'lib/instrumental_tools/metric_script_executor.rb', line 5

def previous
  @previous
end

Instance Method Details

#can_execute_file?(path) ⇒ Boolean

Returns:

  • (Boolean)


12
13
14
15
# File 'lib/instrumental_tools/metric_script_executor.rb', line 12

def can_execute_file?(path)
  stat = File::Stat.new(path)
  powershell_script?(path) || (stat.executable? && file_is_owner_only?(stat))
end

#can_execute_in_directory?(directory) ⇒ Boolean

Returns:

  • (Boolean)


17
18
19
20
# File 'lib/instrumental_tools/metric_script_executor.rb', line 17

def can_execute_in_directory?(directory)
  stat = File::Stat.new(directory)
  stat.directory? && file_is_owner_only?(stat)
end

#execute_custom_script(full_path) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/instrumental_tools/metric_script_executor.rb', line 57

def execute_custom_script(full_path)
  stdin_r,  stdin_w  = IO.pipe
  stdout_r, stdout_w = IO.pipe
  stderr_r, stderr_w = IO.pipe

  previous_status, previous_time, previous_output = previous[full_path]

  stdin_w.write(previous_output || "")
  stdin_w.close


  cmd = [full_path, (previous_time || 0).to_i, (previous_status && previous_status.to_i)].compact.map(&:to_s)
  if powershell_script?(full_path) && !File.executable?(full_path)
    native_path = if File::ALT_SEPARATOR
                    full_path.split(File::SEPARATOR).join(File::ALT_SEPARATOR)
                  else
                    full_path
                  end
    cmd[0] = native_path
    cmd.unshift "-File"
    cmd.unshift "powershell"
  end

  pid = Process.spawn(*cmd,
                      :chdir => File.dirname(full_path),
                      :in    => stdin_r,
                      :out   => stdout_w,
                      :err   => stderr_w)

  exit_status = nil
  exec_time   = Benchmark.realtime do
    pid, exit_status = Process.wait2(pid)
  end

  if exec_time > 1.0
    puts "[SLOW SCRIPT] Time to execute process #{full_path} took #{exec_time} seconds"
  end

  [stdin_r, stdout_w, stderr_w].each(&:close)

  output = stdout_r.read.to_s.chomp

  stderr = stderr_r.read.to_s.chomp
  unless stderr.empty?
    puts "[STDERR] #{full_path} (PID:#{pid}) [#{Time.now.to_s}]:: #{stderr}"
  end

  [stdout_r, stderr_r].each(&:close)

  [full_path, [exit_status, Time.now, output]]
end

#file_is_owner_only?(file_stat) ⇒ Boolean

Returns:

  • (Boolean)


30
31
32
33
34
35
36
37
# File 'lib/instrumental_tools/metric_script_executor.rb', line 30

def file_is_owner_only?(file_stat)
  owned_by_executor = file_stat.owned?
  unless windows?
    owned_by_executor && ((file_stat.mode & 0xFFF) ^ 0O700) == 0
  else
    owned_by_executor
  end
end

#powershell_script?(path) ⇒ Boolean

Returns:

  • (Boolean)


22
23
24
# File 'lib/instrumental_tools/metric_script_executor.rb', line 22

def powershell_script?(path)
  windows? && File.extname(path).to_s.downcase == ".ps1"
end


49
50
51
52
53
54
55
# File 'lib/instrumental_tools/metric_script_executor.rb', line 49

def print_executable_in_directory_warning(directory)
  if windows?
    puts "Directory #{directory} has gone away, not scanning for metric scripts."
  else
    puts "Directory #{directory} has gone away or does not have the correct permissions (0700), not scanning for metric scripts."
  end
end


39
40
41
42
43
44
45
46
47
# File 'lib/instrumental_tools/metric_script_executor.rb', line 39

def print_executable_warning(path)
  if windows?
    puts "[INFO] Cannot execute #{path}, must exist and be executable"
  else
    uid  = Process.uid
    user = Etc.getpwuid(uid).name
    puts "[INFO] Cannot execute #{path}, must exist, be executable and only readable/writable by #{user}/#{uid}"
  end
end

#runObject



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/instrumental_tools/metric_script_executor.rb', line 109

def run
  process_to_output = {}
  if can_execute_in_directory?(directory)
    current = Dir[File.join(directory, "*")].map do |path|
      full_path = File.expand_path(path)
      if can_execute_file?(path)
        execute_custom_script(full_path)
      else
        if !File.directory?(full_path)
          print_executable_warning(full_path)
        end
        [full_path, []]
      end
    end
    process_to_output = Hash[current]
    @previous         = process_to_output
  else
    print_executable_in_directory_warning(directory)
  end
  process_to_output.flat_map do |path, (status, time, output)|
    if status && status.success?
      prefix = File.basename(path).split(".")[0..-2].join(".").gsub(/[^\d\w\-\_\.]/i, "_")
      output.lines                                      # each line
        .map    { |line| line.chomp.split }             # split by whitespace
        .select { |data| (2..3).include?(data.size)  }  # and only valid name value time? pairs
        .map    { |(name, value, specific_time)| [[prefix, name].join("."), value.to_f, (specific_time || time).to_i] } # with value coerced to a float
    end
  end.compact
end

#windows?Boolean

Returns:

  • (Boolean)


26
27
28
# File 'lib/instrumental_tools/metric_script_executor.rb', line 26

def windows?
  RUBY_PLATFORM =~ /(win32|mswin|mingw)/
end