github.com/anycable/anycable-go@v1.5.1/features/runner.rb (about) 1 # frozen_string_literal: true 2 3 retried = false 4 require "bundler/inline" 5 6 begin 7 gemfile(retried, quiet: true) do 8 source "https://rubygems.org" 9 10 gem "childprocess", "~> 4.1" 11 gem "jwt" 12 gem "activesupport", "~> 7.0.0" 13 end 14 rescue 15 raise if retried 16 retried = true 17 retry 18 end 19 20 require "socket" 21 require "time" 22 require "json" 23 24 require "active_support/message_verifier" 25 26 class BenchRunner 27 LOG_LEVEL_TO_NUM = { 28 error: 0, 29 warn: 1, 30 info: 2, 31 debug: 3 32 }.freeze 33 34 # CI machines could be slow 35 RUN_TIMEOUT = (ENV["CI"] || ENV["CODESPACES"]) ? 120 : 30 36 37 def initialize 38 @processes = {} 39 @pipes = {} 40 @log_level = ENV["DEBUG"] == "true" ? LOG_LEVEL_TO_NUM[:debug] : LOG_LEVEL_TO_NUM[:info] 41 end 42 43 def load(script, path) 44 instance_eval script, path, 0 45 end 46 47 def launch(name, cmd, env: {}, debug: ENV["DEBUG"] == "true", capture_output: false) 48 log(:info) { "Launching background process: #{cmd}"} 49 50 process = ChildProcess.build(*cmd.split(/\s+/)) 51 # set process environment variables 52 process.environment.merge!(env) 53 54 if capture_output 55 r, w = IO.pipe 56 process.io.stdout = w 57 process.io.stderr = w 58 pipes[name] = {r: r, w: w} 59 else 60 process.io.inherit! if debug 61 end 62 63 process.detach = true 64 65 processes[name] = process 66 process.start 67 end 68 69 def run(name, cmd, timeout: RUN_TIMEOUT) 70 log(:info) { "Running command: #{cmd}" } 71 72 r, w = IO.pipe 73 74 process = ChildProcess.build(*cmd.split(/\s+/)) 75 process.io.stdout = w 76 process.io.stderr = w 77 78 processes[name] = process 79 pipes[name] = {r: r, w: w} 80 81 process.start 82 83 w.close 84 85 begin 86 process.poll_for_exit(timeout) 87 rescue ChildProcess::TimeoutError 88 process.stop 89 log(:debug) { "Output:\n#{stdout(name)}" } 90 fail "Command expected to finish in #{timeout}s but is still running" 91 end 92 93 log(:info) { "Finished" } 94 log(:debug) { "Output:\n#{stdout(name)}" } 95 end 96 97 def gops(pid) 98 log(:info) { "Fetching Go process #{pid} stats... "} 99 100 `gops stats #{pid}`.lines.each_with_object({}) do |line, acc| 101 key, val = line.split(/:\s+/) 102 acc[key] = val.to_i 103 end 104 end 105 106 def wait_tcp(port, host: "127.0.0.1", timeout: 10) 107 log(:info) { "Waiting for TCP server to start at #{port}" } 108 109 listening = false 110 while timeout > 0 111 begin 112 Socket.tcp(host, port, connect_timeout: 1).close 113 listening = true 114 log(:info) { "TCP server is listening at #{port}" } 115 break 116 rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError 117 end 118 119 Kernel.sleep 0.5 120 timeout -= 0.5 121 end 122 123 fail "No server is listening at #{port}" unless listening 124 end 125 126 def pid(name) 127 processes.fetch(name).pid 128 end 129 130 def stop(name) 131 processes.fetch(name).stop 132 pipes[name]&.fetch(:w)&.close 133 end 134 135 def stdout(name) 136 pipes.fetch(name).then do |pipe| 137 pipe[:data] ||= pipe[:r].read 138 end 139 end 140 141 def sleep(time) 142 log(:info) { "Wait for #{time}s" } 143 Kernel.sleep time 144 end 145 146 def shutdown 147 processes.each_value do |process| 148 process.stop 149 end 150 end 151 152 def retrying(delay: 1, attempts: 2, &block) 153 begin 154 block.call 155 rescue => e 156 attempts -= 1 157 raise if attempts <= 0 158 159 log(:info) { "Retrying after error: #{e.message}" } 160 161 sleep delay 162 retry 163 end 164 end 165 166 private 167 168 attr_reader :processes, :pipes, :log_level 169 170 def log(level, &block) 171 return unless log_level >= LOG_LEVEL_TO_NUM[level] 172 173 $stdout.puts "[#{level}] [#{Time.now.iso8601}] #{block.call}" 174 end 175 end 176 177 if ARGF 178 begin 179 scripts = ARGF.each.group_by { ARGF.filename } 180 scripts.each do |filename, lines| 181 puts "\n--- RUN: #{filename} ---\n\n" if scripts.size > 1 182 script = lines.join 183 runner = BenchRunner.new 184 185 begin 186 runner.load(script, filename) 187 puts "All OK 👍" 188 rescue => e 189 $stderr.puts e.message 190 exit(1) 191 ensure 192 runner.shutdown 193 end 194 end 195 rescue Errno::ENOENT 196 puts "\n--- NOTHINIG TO EXECUTE ---\n\n" 197 end 198 end