benchcc 0.0.11 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 661158a9d7af70f1063d55e5848506701c1b497e
4
- data.tar.gz: 1e2b3c71ecf1c3bc84b214d212ab8033e66428a1
3
+ metadata.gz: 1639266252ed6664cf61f275325fcb29e864341a
4
+ data.tar.gz: 3659e30ee50cfec284f7837ea705e778647c8493
5
5
  SHA512:
6
- metadata.gz: f0f1c6647a9a77860f83908b5bd102f9f468ee3e6fc968625cb33693c6a803c4ec304666f2b81031e535c6df15674e465c7c1a18646ce6d9575667e1b0fedc41
7
- data.tar.gz: ee7d8500e0fbbbecf20cb776ef75c7259c5c9d357a4aa35d44311e8799c16513ab40366294ee78f00d10e8e70fe7d475dfa2dc09df1cd95fa58bfe4cffbeeeaa
6
+ metadata.gz: 41b54dc111a54b67ceed9d434c8e3b3b39ef3074f0883666444a32ace49070a5c04510bac6e51656768836e48168be1f909f1651de3ffce0be22b436eac5310b
7
+ data.tar.gz: 9c1c1d2557408c9cccf48bfd730dc29fa074d22f1d9e0baeb07c42b15cf60b614a67abf8ec57958ac9b0ef08f7d479c17ee6a8d5193cbace8d0673153d1c40f9
@@ -1,9 +1,9 @@
1
1
  require_relative 'compiler'
2
+
2
3
  require 'csv'
3
4
  require 'pathname'
4
5
  require 'ruby-progressbar'
5
6
  require 'tilt'
6
- require 'timeout'
7
7
 
8
8
 
9
9
  module Benchcc
@@ -20,31 +20,45 @@ module Benchcc
20
20
  end
21
21
  end
22
22
 
23
- def benchmark(erb_file, environments, timeout: 10,
24
- evaluate_erb_relative_to: File.dirname(erb_file), &bench)
23
+ def benchmark(
24
+ erb_file:, # String or Pathname
25
+ environments:, # Array of Hash
26
+ compilation_timeout:, # Int
27
+ execution_timeout:, # Int
28
+ evaluate_erb_relative_to:, # String or Pathname
29
+ features:, # Array of Symbol
30
+ compiler_executable:, # String
31
+ compiler_id:, # String
32
+ compiler_options: # Array of String
33
+ )
25
34
  erb_file = Pathname.new(erb_file)
26
35
  progress = ProgressBar.create(format: '%p%% | %B |', total: environments.size)
36
+ compiler = Benchcc::which(compiler_id)
27
37
 
28
38
  data = CSV.generate({headers: :first_row}) do |csv|
29
- csv << [:input_size, :compilation_time, :memory_usage, :run_time]
39
+ csv << [:input_size] + features
30
40
 
31
41
  environments.each do |env|
32
42
  code = Renderer.new(evaluate_erb_relative_to).render(erb_file, **env)
33
43
  begin
44
+ row = {input_size: env[:input_size]}
34
45
  Tempfile.create([erb_file.basename, '.cpp']) do |tmp|
35
46
  tmp.write(code) && tmp.close
36
- bench_data = Timeout::timeout(timeout) {
37
- bench.call(tmp.path, env)
38
- }
39
- row = {
40
- input_size: env[:input_size],
41
- compilation_time: bench_data[:compilation_time],
42
- memory_usage: bench_data[:memory_usage],
43
- run_time: bench_data[:run_time] # TODO: implement this
44
- }
45
- csv << row
47
+
48
+ row.merge!(
49
+ compiler.call(
50
+ input_file: tmp.path,
51
+ features: features,
52
+ compiler_executable: compiler_executable,
53
+ compiler_options: compiler_options,
54
+ compilation_timeout: compilation_timeout,
55
+ execution_timeout: execution_timeout
56
+ )
57
+ )
46
58
  end
47
- rescue CompilationError, Timeout::Error => e
59
+
60
+ csv << row
61
+ rescue ExecutionError, CompilationError, Timeout::Error => e
48
62
  $stderr << e
49
63
  break
50
64
  end
@@ -3,121 +3,108 @@ require_relative 'ext/string'
3
3
  require 'open3'
4
4
  require 'pathname'
5
5
  require 'tempfile'
6
+ require 'timeout'
6
7
 
7
8
 
8
9
  module Benchcc
9
10
  class CompilationError < RuntimeError
10
11
  end
11
12
 
12
- # Basic interface to compiler frontends.
13
- class Compiler
14
- # Show the name and the version of the compiler.
15
- def to_s
16
- raise NotImplementedError
17
- end
18
-
19
- # compile: Path -> Hash
20
- #
21
- # Compile the given file and return compilation statistics.
22
- #
23
- # Additional compiler-specific arguments may be specified. A
24
- # `CompilationError` is raised if the compilation fails for
25
- # whatever reason.
26
- def compile(file, *args)
27
- raise NotImplementedError
28
- end
29
-
30
- def self.guess_from_binary(binary)
31
- stdout, stderr, status = Open3.capture3("#{binary} --version")
32
- case stdout
33
- when /\(GCC\)/
34
- return GCC.new(binary)
35
- when /clang/
36
- return Clang.new(binary)
37
- else
38
- raise ArgumentError("unknown compiler #{binary}")
39
- end
40
- end
41
- end # class Compiler
42
-
43
- class Clang < Compiler
44
- def initialize(binary)
45
- @exe = `which #{binary}`.strip
46
- raise "#{binary} not found" unless $?.success?
47
- end
13
+ class ExecutionError < RuntimeError
14
+ end
48
15
 
49
- def compile(file, *args)
50
- file = Pathname.new(file).expand_path
51
- command = "/usr/bin/time -l #{@exe} #{args.join(' ')} -ftime-report #{file}"
52
- stdout, stderr, status = Open3.capture3(command)
53
-
54
- if status.success?
55
- memusg = stderr.match(/(\d+)\s+maximum/)[1].to_i
56
-
57
- section = -> (title) {
58
- title.gsub!(' ', '\s')
59
- /(
60
- ===-+===\n
61
- .*#{title}.*\n
62
- ===-+===\n
63
- (.|\n)+?(?====)
64
- )|(
65
- ===-+===\n
66
- .*#{title}.*\n
67
- ===-+===\n
68
- (.|\n)+
69
- )/x
70
- }
71
- time = stderr.match(section["Miscellaneous Ungrouped Timers"]).to_s
72
- .match(/(\d+\.?\d+).+?Total/)[1]
73
- return {
74
- compilation_time: time,
75
- memory_usage: memusg
16
+ class Clang
17
+ def call(input_file:, features:, compiler_executable:, compiler_options:,
18
+ compilation_timeout:, execution_timeout:)
19
+ stats = {}
20
+ Dir.mktmpdir do |tmp_dir|
21
+ if features.include?(:compilation_time)
22
+ compiler_options << '-ftime-report'
23
+ end
24
+
25
+ if features.include?(:execution_time)
26
+ compiler_options << "-o#{tmp_dir}/a.out"
27
+ end
28
+
29
+ if features.include?(:memory_usage)
30
+ compiler_executable = "/usr/bin/time -l #{compiler_executable}"
31
+ end
32
+
33
+ command = "#{compiler_executable} #{compiler_options.join(' ')} #{input_file}"
34
+ stdout, stderr, status = Timeout::timeout(compilation_timeout) {
35
+ Open3.capture3(command)
76
36
  }
77
37
 
78
- else
79
- err_string = <<-EOS.strip_heredoc
80
- > #{command}
81
- #{stderr}
82
-
83
- [compiling:
84
- #{file.read}
85
- ]
86
- EOS
87
- raise CompilationError.new(err_string)
38
+ if not status.success?
39
+ raise CompilationError.new(<<-EOS.strip_heredoc
40
+ > #{command}
41
+ #{stderr}
42
+
43
+ [compiling:
44
+ #{input_file.read}
45
+ ]
46
+ EOS
47
+ )
48
+ end
49
+
50
+ if features.include?(:compilation_time)
51
+ section = -> (title) {
52
+ title.gsub!(' ', '\s')
53
+ /(
54
+ ===-+===\n
55
+ .*#{title}.*\n
56
+ ===-+===\n
57
+ (.|\n)+?(?====)
58
+ )|(
59
+ ===-+===\n
60
+ .*#{title}.*\n
61
+ ===-+===\n
62
+ (.|\n)+
63
+ )/x
64
+ }
65
+ time = stderr.match(section["Miscellaneous Ungrouped Timers"]).to_s
66
+ .match(/(\d+\.?\d+).+?Total/)[1]
67
+ stats.merge!({compilation_time: time})
68
+ end
69
+
70
+ if features.include?(:memory_usage)
71
+ memusg = stderr.match(/(\d+)\s+maximum/)[1]
72
+ stats.merge!({memory_usage: memusg})
73
+ end
74
+
75
+ if features.include?(:execution_time)
76
+ command = "/usr/bin/time #{tmp_dir}/a.out"
77
+ stdout, stderr, status = Timeout::timeout(execution_timeout) {
78
+ Open3.capture3(command)
79
+ }
80
+ if status.success?
81
+ time = stderr.match(/(\d+\.?\d*)\s+real/)[1]
82
+ stats.merge!({execution_time: time})
83
+ else
84
+ raise ExecutionError.new(<<-EOS.strip_heredoc
85
+ > #{command}
86
+ #{stderr}
87
+
88
+ [running:
89
+ #{input_file.read}
90
+ ]
91
+ EOS
92
+ )
93
+ end
94
+ end
95
+
96
+ return stats
88
97
  end
89
98
  end
90
99
  end
91
100
 
92
- class GCC < Compiler
93
- def initialize(binary)
94
- @exe = `which #{binary}`.strip
95
- raise "#{binary} not found" unless $?.success?
96
- end
97
-
98
- def compile(file, *args)
99
- file = Pathname.new(file).expand_path
100
- command = "/usr/bin/time -l #{@exe} #{args.join(' ')} -ftime-report #{file}"
101
- stdout, stderr, status = Open3.capture3(command)
102
-
103
- if status.success?
104
- time = stderr.match(/TOTAL.+/).to_s.split[-3].to_f
105
- memusg = stderr.match(/(\d+)\s+maximum/)[1].to_i
106
- return {
107
- compilation_time: time,
108
- memory_usage: memusg
109
- }
110
- else
111
- err_string = <<-EOS.strip_heredoc
112
- > #{command}
113
- #{stderr}
114
-
115
- [compiling:
116
- #{file.read}
117
- ]
118
- EOS
119
- raise CompilationError.new(err_string)
120
- end
101
+ def which(compiler_id)
102
+ case compiler_id
103
+ when 'Clang'
104
+ return Clang.new
105
+ else
106
+ raise ArgumentError.new("Unsupported compiler id: #{compiler_id}")
121
107
  end
122
108
  end
109
+ module_function :which
123
110
  end
@@ -1,3 +1,3 @@
1
1
  module Benchcc
2
- VERSION = "0.0.11"
2
+ VERSION = "0.0.12"
3
3
  end
@@ -10,35 +10,17 @@ describe Benchcc.method(:benchmark) do
10
10
  envs = [{input_size: 1}, {input_size: 2}]
11
11
  Tempfile.create('') do |file|
12
12
  expect {
13
- data = Benchcc.benchmark(file.path, envs) do |file, env|
14
- {
15
- compilation_time: 'foo',
16
- memory_usage: 'bar',
17
- run_time: 'baz'
18
- }
19
- end
20
- }.not_to raise_error
21
- end
22
- }
23
-
24
- it('should stop on timeout') {
25
- envs = [{input_size: 1}, {input_size: 2}]
26
- Tempfile.create('') do |file|
27
- expect {
28
- data = Benchcc.benchmark(file.path, envs, timeout: 0.1) do |file, env|
29
- sleep(0.2)
30
- end
31
- }.not_to raise_error
32
- end
33
- }
34
-
35
- it('should stop on CompilationError') {
36
- envs = [{input_size: 1}, {input_size: 2}]
37
- Tempfile.create('') do |file|
38
- expect {
39
- data = Benchcc.benchmark(file.path, envs, timeout: 0.1) do |file, env|
40
- raise Benchcc::CompilationError.new
41
- end
13
+ data = Benchcc.benchmark(
14
+ erb_file: file.path,
15
+ environments: [],
16
+ compilation_timeout: 10,
17
+ execution_timeout: 10,
18
+ evaluate_erb_relative_to: '',
19
+ features: [],
20
+ compiler_executable: `which clang++`,
21
+ compiler_id: 'Clang',
22
+ compiler_options: []
23
+ )
42
24
  }.not_to raise_error
43
25
  end
44
26
  }
@@ -2,52 +2,54 @@ require 'benchcc/compiler'
2
2
 
3
3
  require 'pathname'
4
4
  require 'rspec'
5
- require 'tempfile'
6
5
 
7
6
 
8
- describe Benchcc::Compiler do
9
- describe :guess_from_binary do
10
- it('can guess clang') {
11
- expect(
12
- Benchcc::Compiler.guess_from_binary(`which clang`.strip)
13
- ).to be_instance_of(Benchcc::Clang)
7
+ [['Clang', 'clang++']].each do |compiler_id, executable|
8
+ describe compiler_id do
9
+ before(:each) {
10
+ @cc = Benchcc::which(compiler_id)
11
+ @invalid = Pathname.new('src/invalid.cpp').expand_path(File.dirname(__FILE__))
12
+ @invalid_runtime = Pathname.new('src/invalid_runtime.cpp').expand_path(File.dirname(__FILE__))
13
+ @valid = Pathname.new('src/valid.cpp').expand_path(File.dirname(__FILE__))
14
14
  }
15
15
 
16
- # GCC is aliased to clang on OSX, so this test fails.
17
- # it('can guess gcc') {
18
- # expect(
19
- # Benchcc::Compiler.guess_from_binary(`which gcc`.strip)
20
- # ).to be_instance_of(Benchcc::GCC)
21
- # }
22
-
23
- it('fails otherwise') {
16
+ it('fails cleanly on invalid input') {
24
17
  expect {
25
- Benchcc::Compiler.guess_from_binary('not_a_compiler')
26
- }.to raise_error
18
+ @cc.call(
19
+ input_file: @invalid,
20
+ features: [],
21
+ compiler_executable: executable,
22
+ compiler_options: [],
23
+ compilation_timeout: 10,
24
+ execution_timeout: 10
25
+ )
26
+ }.to raise_error(Benchcc::CompilationError)
27
27
  }
28
- end
29
- end
30
28
 
31
- [[Benchcc::Clang, 'clang++'], [Benchcc::GCC, 'g++']].each do |compiler, which|
32
- describe compiler do
33
- before(:each) {
34
- @cc = compiler.new(which)
35
- @invalid = Pathname.new('src/invalid.cpp').expand_path(File.dirname(__FILE__))
36
- @valid = Pathname.new('src/valid.cpp').expand_path(File.dirname(__FILE__))
29
+ it('fails cleanly on runtime error') {
30
+ expect {
31
+ @cc.call(
32
+ input_file: @invalid_runtime,
33
+ features: [:execution_time],
34
+ compiler_executable: executable,
35
+ compiler_options: [],
36
+ compilation_timeout: 10,
37
+ execution_timeout: 10
38
+ )
39
+ }.to raise_error(Benchcc::ExecutionError)
37
40
  }
38
41
 
39
- describe :compile do
40
- it('fails cleanly on invalid input') {
41
- expect {
42
- @cc.compile(@invalid)
43
- }.to raise_error(Benchcc::CompilationError)
44
- }
45
-
46
- it('returns statistics on valid input') {
47
- expect(
48
- @cc.compile(@valid, '-o /dev/null')
49
- ).to be_instance_of(Hash)
50
- }
51
- end
42
+ it('returns statistics on valid input') {
43
+ expect(
44
+ @cc.call(
45
+ input_file: @valid,
46
+ features: [:execution_time, :compilation_time, :memory_usage],
47
+ compiler_executable: executable,
48
+ compiler_options: ['-o/dev/null'],
49
+ compilation_timeout: 10,
50
+ execution_timeout: 10
51
+ )
52
+ ).to be_instance_of(Hash)
53
+ }
52
54
  end
53
55
  end
@@ -0,0 +1,5 @@
1
+ #include <cassert>
2
+
3
+ int main() {
4
+ assert(false);
5
+ }
data/spec/src/valid.cpp CHANGED
@@ -1 +1,7 @@
1
- int main() {}
1
+ #include <chrono>
2
+ #include <thread>
3
+
4
+
5
+ int main() {
6
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
7
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: benchcc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.11
4
+ version: 0.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Louis Dionne
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-20 00:00:00.000000000 Z
11
+ date: 2014-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gnuplot
@@ -115,6 +115,7 @@ files:
115
115
  - spec/numeric_spec.rb
116
116
  - spec/plot_spec.rb
117
117
  - spec/src/invalid.cpp
118
+ - spec/src/invalid_runtime.cpp
118
119
  - spec/src/plot.csv
119
120
  - spec/src/valid.cpp
120
121
  homepage: https://github.com/ldionne/benchcc