2018-10-30 15:05:07 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-04-06 12:20:27 -04:00
|
|
|
module Gitlab
|
|
|
|
module Ci
|
|
|
|
class Trace
|
|
|
|
class Stream
|
|
|
|
BUFFER_SIZE = 4096
|
2017-04-18 08:27:10 -04:00
|
|
|
LIMIT_SIZE = 500.kilobytes
|
2017-04-06 12:20:27 -04:00
|
|
|
|
2020-09-16 14:09:47 -04:00
|
|
|
attr_reader :stream, :metrics
|
2017-04-06 12:20:27 -04:00
|
|
|
|
2018-04-05 15:41:45 -04:00
|
|
|
delegate :close, :tell, :seek, :size, :url, :truncate, to: :stream, allow_nil: true
|
2017-04-06 12:20:27 -04:00
|
|
|
|
2020-09-16 14:09:47 -04:00
|
|
|
def initialize(metrics = Trace::Metrics.new)
|
2017-04-06 12:20:27 -04:00
|
|
|
@stream = yield
|
2017-04-18 05:33:17 -04:00
|
|
|
@stream&.binmode
|
2020-09-16 14:09:47 -04:00
|
|
|
@metrics = metrics
|
2017-04-06 12:20:27 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def valid?
|
|
|
|
self.stream.present?
|
|
|
|
end
|
2022-01-17 13:16:07 -05:00
|
|
|
alias_method :present?, :valid?
|
2017-04-06 12:20:27 -04:00
|
|
|
|
|
|
|
def file?
|
|
|
|
self.path.present?
|
|
|
|
end
|
|
|
|
|
2018-04-03 08:52:23 -04:00
|
|
|
def path
|
|
|
|
self.stream.path if self.stream.respond_to?(:path)
|
2018-04-05 15:41:45 -04:00
|
|
|
end
|
|
|
|
|
2017-04-06 12:20:27 -04:00
|
|
|
def limit(last_bytes = LIMIT_SIZE)
|
2017-04-13 10:07:37 -04:00
|
|
|
if last_bytes < size
|
|
|
|
stream.seek(-last_bytes, IO::SEEK_END)
|
|
|
|
stream.readline
|
2017-04-06 12:20:27 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def append(data, offset)
|
2018-04-18 02:19:53 -04:00
|
|
|
data = data.force_encoding(Encoding::BINARY)
|
|
|
|
|
2020-09-16 14:09:47 -04:00
|
|
|
metrics.increment_trace_operation(operation: :streamed)
|
|
|
|
metrics.increment_trace_bytes(data.bytesize)
|
|
|
|
|
2018-11-23 11:25:11 -05:00
|
|
|
stream.seek(offset, IO::SEEK_SET)
|
2017-04-06 12:20:27 -04:00
|
|
|
stream.write(data)
|
2018-11-23 11:25:11 -05:00
|
|
|
stream.truncate(offset + data.bytesize)
|
2019-01-24 08:43:02 -05:00
|
|
|
stream.flush
|
2017-04-06 12:20:27 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def set(data)
|
2018-11-23 11:25:11 -05:00
|
|
|
append(data, 0)
|
2017-04-06 12:20:27 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def raw(last_lines: nil)
|
|
|
|
return unless valid?
|
|
|
|
|
|
|
|
if last_lines.to_i > 0
|
|
|
|
read_last_lines(last_lines)
|
|
|
|
else
|
|
|
|
stream.read
|
2017-04-17 02:30:42 -04:00
|
|
|
end.force_encoding(Encoding.default_external)
|
2017-04-06 12:20:27 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def html(last_lines: nil)
|
|
|
|
text = raw(last_lines: last_lines)
|
2017-04-18 05:03:02 -04:00
|
|
|
buffer = StringIO.new(text)
|
2017-09-06 07:08:06 -04:00
|
|
|
::Gitlab::Ci::Ansi2html.convert(buffer).html
|
2017-04-06 12:20:27 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def extract_coverage(regex)
|
|
|
|
return unless valid?
|
2017-07-21 08:04:18 -04:00
|
|
|
return unless regex.present?
|
2017-04-06 12:20:27 -04:00
|
|
|
|
2017-06-22 11:33:17 -04:00
|
|
|
regex = Gitlab::UntrustedRegexp.new(regex)
|
2017-04-06 12:20:27 -04:00
|
|
|
|
|
|
|
match = ""
|
|
|
|
|
2017-05-05 14:16:39 -04:00
|
|
|
reverse_line do |line|
|
2017-07-21 17:08:23 -04:00
|
|
|
line.chomp!
|
2017-06-22 11:33:17 -04:00
|
|
|
matches = regex.scan(line)
|
2017-04-06 12:20:27 -04:00
|
|
|
next unless matches.is_a?(Array)
|
2017-04-11 15:10:12 -04:00
|
|
|
next if matches.empty?
|
2017-04-06 12:20:27 -04:00
|
|
|
|
|
|
|
match = matches.flatten.last
|
|
|
|
coverage = match.gsub(/\d+(\.\d+)?/).first
|
2018-04-18 05:19:40 -04:00
|
|
|
return coverage if coverage.present? # rubocop:disable Cop/AvoidReturnFromBlocks
|
2017-04-06 12:20:27 -04:00
|
|
|
end
|
2017-04-11 15:10:12 -04:00
|
|
|
|
|
|
|
nil
|
2021-04-26 08:09:44 -04:00
|
|
|
rescue StandardError
|
2017-04-06 12:20:27 -04:00
|
|
|
# if bad regex or something goes wrong we dont want to interrupt transition
|
2017-05-17 04:53:31 -04:00
|
|
|
# so we just silently ignore error for now
|
2017-04-06 12:20:27 -04:00
|
|
|
end
|
|
|
|
|
2017-09-25 12:54:08 -04:00
|
|
|
def extract_sections
|
|
|
|
return [] unless valid?
|
|
|
|
|
|
|
|
lines = to_enum(:each_line_with_pos)
|
|
|
|
parser = SectionParser.new(lines)
|
|
|
|
|
|
|
|
parser.parse!
|
|
|
|
parser.sections
|
|
|
|
end
|
|
|
|
|
2017-04-06 12:20:27 -04:00
|
|
|
private
|
|
|
|
|
2017-09-25 12:54:08 -04:00
|
|
|
def each_line_with_pos
|
|
|
|
stream.seek(0, IO::SEEK_SET)
|
|
|
|
stream.each_line do |line|
|
|
|
|
yield [line, stream.pos - line.bytesize]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-05-17 05:18:16 -04:00
|
|
|
def read_last_lines(limit)
|
2017-05-17 05:26:38 -04:00
|
|
|
to_enum(:reverse_line).first(limit).reverse.join
|
2017-04-06 12:20:27 -04:00
|
|
|
end
|
2017-05-05 14:16:39 -04:00
|
|
|
|
|
|
|
def reverse_line
|
2017-05-24 07:20:20 -04:00
|
|
|
stream.seek(0, IO::SEEK_END)
|
2017-05-09 13:40:34 -04:00
|
|
|
debris = ''
|
2017-05-05 14:16:39 -04:00
|
|
|
|
2017-05-24 12:35:40 -04:00
|
|
|
until (buf = read_backward(BUFFER_SIZE)).empty?
|
2018-10-30 15:05:07 -04:00
|
|
|
debris, *lines = (buf + debris).each_line.to_a
|
2017-05-17 12:44:15 -04:00
|
|
|
lines.reverse_each do |line|
|
2018-04-18 02:19:53 -04:00
|
|
|
yield(line.force_encoding(Encoding.default_external))
|
2017-05-09 13:40:34 -04:00
|
|
|
end
|
2017-05-05 14:16:39 -04:00
|
|
|
end
|
2017-05-06 04:38:56 -04:00
|
|
|
|
2018-04-18 02:19:53 -04:00
|
|
|
yield(debris.force_encoding(Encoding.default_external)) unless debris.empty?
|
2017-05-17 07:21:13 -04:00
|
|
|
end
|
|
|
|
|
2017-05-24 07:20:20 -04:00
|
|
|
def read_backward(length)
|
|
|
|
cur_offset = stream.tell
|
|
|
|
start = cur_offset - length
|
|
|
|
start = 0 if start < 0
|
|
|
|
|
|
|
|
stream.seek(start, IO::SEEK_SET)
|
|
|
|
stream.read(cur_offset - start).tap do
|
|
|
|
stream.seek(start, IO::SEEK_SET)
|
|
|
|
end
|
2017-05-05 14:16:39 -04:00
|
|
|
end
|
2017-04-06 12:20:27 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|