bookie_accounting 0.0.1

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.
Files changed (42) hide show
  1. data/.gitignore +19 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +22 -0
  4. data/README.md +29 -0
  5. data/Rakefile +15 -0
  6. data/bin/bookie-create-tables +52 -0
  7. data/bin/bookie-data +102 -0
  8. data/bin/bookie-send +110 -0
  9. data/bookie_accounting.gemspec +28 -0
  10. data/lib/bookie.rb +11 -0
  11. data/lib/bookie/config.rb +101 -0
  12. data/lib/bookie/database.rb +656 -0
  13. data/lib/bookie/formatter.rb +149 -0
  14. data/lib/bookie/formatters/comma_dump.rb +24 -0
  15. data/lib/bookie/formatters/spreadsheet.rb +45 -0
  16. data/lib/bookie/formatters/stdout.rb +32 -0
  17. data/lib/bookie/sender.rb +108 -0
  18. data/lib/bookie/senders/standalone.rb +37 -0
  19. data/lib/bookie/senders/torque_cluster.rb +166 -0
  20. data/lib/bookie/version.rb +4 -0
  21. data/snapshot/config.json +12 -0
  22. data/snapshot/default.json +11 -0
  23. data/snapshot/pacct +0 -0
  24. data/snapshot/pacct_large +0 -0
  25. data/snapshot/pacct_test_config.json +14 -0
  26. data/snapshot/test_config.json +13 -0
  27. data/snapshot/torque +3 -0
  28. data/snapshot/torque_invalid_lines +5 -0
  29. data/snapshot/torque_invalid_lines_2 +4 -0
  30. data/snapshot/torque_invalid_lines_3 +3 -0
  31. data/snapshot/torque_large +100 -0
  32. data/spec/comma_dump_formatter_spec.rb +56 -0
  33. data/spec/config_spec.rb +55 -0
  34. data/spec/database_spec.rb +625 -0
  35. data/spec/formatter_spec.rb +93 -0
  36. data/spec/sender_spec.rb +104 -0
  37. data/spec/spec_helper.rb +121 -0
  38. data/spec/spreadsheet_formatter_spec.rb +112 -0
  39. data/spec/standalone_sender_spec.rb +40 -0
  40. data/spec/stdout_formatter_spec.rb +66 -0
  41. data/spec/torque_cluster_sender_spec.rb +111 -0
  42. metadata +227 -0
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+
3
+ module Bookie
4
+ module Formatters
5
+ module Mock
6
+ def open(filename)
7
+
8
+ end
9
+
10
+ def do_print_summary(field_values)
11
+ #A bit of an ugly hack, but .should doesn't work here.
12
+ $field_values = field_values
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ describe Bookie::Formatter do
19
+ before(:all) do
20
+ Bookie::Database::Migration.up
21
+ Helpers::generate_database
22
+ Bookie::Formatter.any_instance.stubs(:require)
23
+ @formatter = Bookie::Formatter.new(:mock)
24
+ @jobs = Bookie::Database::Job
25
+ end
26
+
27
+ after(:all) do
28
+ FileUtils.rm('test.sqlite')
29
+ end
30
+
31
+ it "correctly formats durations" do
32
+ Bookie::Formatter.format_duration(3600 * 6 + 60 * 5 + 4).should eql '06:05:04'
33
+ end
34
+
35
+ it "correctly calculates fields for jobs" do
36
+ @formatter.send(:fields_for_each_job, @jobs.limit(1).all) do |fields|
37
+ fields.should eql [
38
+ 'root',
39
+ 'root',
40
+ 'test1',
41
+ 'Standalone',
42
+ "2012-01-01 00:00:00",
43
+ "2012-01-01 01:00:00",
44
+ '01:00:00',
45
+ '00:01:40',
46
+ '200kb (avg)',
47
+ 0,
48
+ ]
49
+ end
50
+ jobs = [Bookie::Database::Job.first]
51
+ jobs[0].system.system_type.memory_stat_type = :unknown
52
+ @formatter.send(:fields_for_each_job, jobs) do |fields|
53
+ fields[8].should eql '200kb'
54
+ end
55
+ end
56
+
57
+ describe "#print_summary" do
58
+ it "prints the correct summary fields" do
59
+ Time.expects(:now).returns(Time.local(2012) + 3600 * 40).at_least_once
60
+ @formatter.print_summary(@jobs.order(:start_time).limit(5), Bookie::Database::System)
61
+ @formatter.flush
62
+ $field_values.should eql [5, "05:00:00", "00:08:20", "60.0000%", "140:00:00", "0.0992%", "1750000 kb", "0.0014%"]
63
+ Bookie::Database::System.expects(:summary).returns(
64
+ :avail_cpu_time => 0,
65
+ :avail_memory_time => 0,
66
+ :avail_memory_avg => 0
67
+ )
68
+ @formatter.print_summary(@jobs.order(:start_time).limit(1), Bookie::Database::System, Time.local(2012), Time.local(2012))
69
+ @formatter.flush
70
+ $field_values.should eql [0, "00:00:00", "00:00:00", "0.0000%", "00:00:00", "0.0000%", "0 kb", "0.0000%"]
71
+ end
72
+
73
+ it "returns the summary objects" do
74
+ s1, s2 = @formatter.print_summary(@jobs.order(:start_time).limit(1), Bookie::Database::System.limit(0))
75
+ s1[:jobs].length.should eql 1
76
+ s2[:avail_memory_time].should eql 0
77
+ end
78
+ end
79
+
80
+ it "forwards print_jobs to do_print_jobs" do
81
+ @formatter.expects(:do_print_jobs)
82
+ @formatter.print_jobs(nil)
83
+ end
84
+
85
+ it "forwards flush to do_flush" do
86
+ @formatter.expects(:'respond_to?').with(:do_flush).returns(false)
87
+ @formatter.expects(:do_flush).never
88
+ @formatter.flush
89
+ @formatter.unstub(:'respond_to?')
90
+ @formatter.expects(:do_flush)
91
+ @formatter.flush
92
+ end
93
+ end
@@ -0,0 +1,104 @@
1
+ require 'spec_helper'
2
+
3
+ class JobStub
4
+ attr_accessor :user_name
5
+ attr_accessor :group_name
6
+ attr_accessor :command_name
7
+ attr_accessor :start_time
8
+ attr_accessor :end_time
9
+ attr_accessor :wall_time
10
+ attr_accessor :cpu_time
11
+ attr_accessor :memory
12
+ attr_accessor :exit_code
13
+ end
14
+
15
+ describe Bookie::Sender do
16
+ before(:all) do
17
+ base_time = Time.new(2012)
18
+ Bookie::Database::Migration.up
19
+ Bookie::Database::System.create!(
20
+ :name => 'localhost',
21
+ :system_type => Bookie::Sender.new(@config).system_type,
22
+ :start_time => base_time,
23
+ :end_time => nil,
24
+ :cores => @config.cores,
25
+ :memory => @config.memory
26
+ )
27
+ end
28
+
29
+ after(:all) do
30
+ FileUtils.rm('test.sqlite')
31
+ end
32
+
33
+ before(:each) do
34
+ @sender = Bookie::Sender.new(@config)
35
+ @job = JobStub.new
36
+ @job.user_name = "root"
37
+ @job.group_name = "root"
38
+ @job.command_name = "ls"
39
+ @job.start_time = Time.new
40
+ @job.wall_time = 3
41
+ @job.cpu_time = 2
42
+ @job.memory = 300
43
+ @job.exit_code = 0
44
+ end
45
+
46
+ it "correctly filters jobs" do
47
+ job = JobStub.new
48
+ job.user_name = "root"
49
+ @sender.filtered?(job).should eql true
50
+ job.user_name = "test"
51
+ @sender.filtered?(job).should eql false
52
+ end
53
+
54
+ it "correctly sends jobs" do
55
+ old_excluded = @config.excluded_users
56
+ @config.excluded_users = Set.new
57
+ begin
58
+ @sender.send_data('snapshot/torque_large')
59
+ count = 0
60
+ Bookie::Database::Job.all_with_relations.each do |job|
61
+ job.system.name.should eql @config.hostname
62
+ count += 1
63
+ end
64
+ count.should eql 100
65
+ ensure
66
+ @config.excluded_users = old_excluded
67
+ end
68
+ end
69
+
70
+ it "refuses to send jobs when jobs already have been sent from a file" do
71
+ exception = nil
72
+ expect {
73
+ @sender.send_data('snapshot/torque_large')
74
+ }.to raise_error(/^Jobs already exist in the database for the date [\d]{4}-[\d]{2}-[\d]{2}.$/)
75
+ end
76
+
77
+ it "handles missing files" do
78
+ expect { @sender.send_data('snapshot/abc') }.to raise_error("File 'snapshot/abc' does not exist.")
79
+ end
80
+ end
81
+
82
+ describe Bookie::ModelHelpers do
83
+ before(:all) do
84
+ @job = JobStub.new
85
+ @job.user_name = "root"
86
+ @job.group_name = "root"
87
+ @job.start_time = Time.new
88
+ @job.wall_time = 3
89
+ @job.cpu_time = 2
90
+ @job.memory = 300
91
+ @job.extend(Bookie::ModelHelpers)
92
+ end
93
+
94
+ it "correctly converts jobs to database objects" do
95
+ Bookie::Database::Job.stubs(:new).returns(JobStub.new)
96
+ djob = @job.to_model
97
+ djob.start_time.should eql @job.start_time
98
+ djob.end_time.should eql @job.start_time + @job.wall_time
99
+ djob.wall_time.should eql @job.wall_time
100
+ djob.cpu_time.should eql @job.cpu_time
101
+ djob.memory.should eql @job.memory
102
+ djob.exit_code.should eql @job.exit_code
103
+ end
104
+ end
@@ -0,0 +1,121 @@
1
+ if ENV['COVERAGE']
2
+ require 'simplecov'
3
+ SimpleCov.start
4
+ end
5
+
6
+ $LOAD_PATH.concat Dir.glob(File.join(Dir.pwd, "../*/lib"))
7
+
8
+ require 'fileutils'
9
+ require 'mocha/api'
10
+
11
+ require 'bookie'
12
+
13
+ #Predefined here so config can see it
14
+ module Helpers
15
+
16
+ end
17
+
18
+ RSpec.configure do |config|
19
+ config.include Helpers
20
+
21
+ config.mock_with(:mocha)
22
+
23
+ config.before(:all) do
24
+ @config = Bookie::Config.new('snapshot/test_config.json')
25
+ @config.connect
26
+ end
27
+ end
28
+
29
+ class IOMock
30
+ def initialize
31
+ @buf = ""
32
+ end
33
+
34
+ def puts(str)
35
+ @buf << str.to_s
36
+ @buf << "\n"
37
+ end
38
+
39
+ def write(str)
40
+ @buf << str.to_s
41
+ end
42
+
43
+ def printf(format, *args)
44
+ @buf << sprintf(format, *args)
45
+ end
46
+
47
+ def buf
48
+ @buf
49
+ end
50
+ end
51
+
52
+ module Helpers
53
+ extend self
54
+
55
+ def generate_database
56
+ base_time = Time.local(2012)
57
+ #Create test database
58
+ groups = {}
59
+ group_names = ['root', 'default', 'admin', 'admin']
60
+ group_names.each do |name|
61
+ unless groups[name]
62
+ group = Bookie::Database::Group.new
63
+ group.name = name
64
+ group.save!
65
+ groups[name] = group
66
+ end
67
+ end
68
+ users = {}
69
+ user_names = ['root', 'test', 'test', 'blm768']
70
+ user_names.each_index do |i|
71
+ name = user_names[i]
72
+ unless users[[name, group_names[i]]]
73
+ user = Bookie::Database::User.new
74
+ user.name = name
75
+ user.group = groups[group_names[i]]
76
+ user.save!
77
+ users[name] ||= {}
78
+ users[[name, group_names[i]]] = user
79
+ end
80
+ end
81
+ system_types = [
82
+ Bookie::Database::SystemType.create!(
83
+ :name => 'Standalone',
84
+ :memory_stat_type => :avg),
85
+ Bookie::Database::SystemType.create!(
86
+ :name => 'TORQUE cluster',
87
+ :memory_stat_type => :max)]
88
+ systems = []
89
+ system_names = ['test1', 'test1', 'test2', 'test3']
90
+ system_names.each_index do |i|
91
+ name = system_names[i]
92
+ unless systems.include?name
93
+ system = Bookie::Database::System.create!(
94
+ :name => name,
95
+ :system_type => system_types[i & 1],
96
+ :start_time => base_time + (36000 * i),
97
+ :cores => 2,
98
+ :memory => 1000000)
99
+ systems << system
100
+ end
101
+ end
102
+ systems[0].end_time = base_time + 36000
103
+ systems[0].save!
104
+ for i in 0 ... 40 do
105
+ job = Bookie::Database::Job.new
106
+ job.user = users[[user_names[i % user_names.length], group_names[i % user_names.length]]]
107
+ job.system = systems[i / 10]
108
+ if i & 1 == 0
109
+ job.command_name = 'vi'
110
+ else
111
+ job.command_name = 'emacs'
112
+ end
113
+ job.start_time = base_time + 3600 * i
114
+ job.wall_time = 3600
115
+ job.cpu_time = 100
116
+ job.memory = 200
117
+ job.exit_code = i & 1
118
+ job.save!
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ module Bookie
4
+ module Formatters
5
+ module Spreadsheet
6
+
7
+ end
8
+ end
9
+ end
10
+
11
+ class MockWorkbook
12
+ def initialize
13
+ @worksheets = {}
14
+ end
15
+
16
+ def worksheet(name)
17
+ @worksheets[name] ||= MockWorksheet.new
18
+ end
19
+ end
20
+
21
+ class MockWorksheet
22
+ def initialize
23
+ @mock_rows = []
24
+ @mock_columns = []
25
+ end
26
+
27
+ def row(num)
28
+ @mock_rows[num] ||= []
29
+ end
30
+
31
+ def mock_rows
32
+ @mock_rows
33
+ end
34
+
35
+ def column(num)
36
+ @mock_columns[num] ||= MockColumn.new
37
+ end
38
+
39
+ def mock_columns
40
+ @mock_columns
41
+ end
42
+
43
+ def last_row_index
44
+ @mock_rows.length - 1
45
+ end
46
+ end
47
+
48
+ class MockColumn
49
+ def width=(value)
50
+ @width = value
51
+ end
52
+
53
+ def width
54
+ @width
55
+ end
56
+ end
57
+
58
+ describe Bookie::Formatters::Spreadsheet do
59
+ before(:all) do
60
+ Bookie::Database::Migration.up
61
+ Helpers::generate_database
62
+ @jobs = Bookie::Database::Job
63
+ end
64
+
65
+ before(:each) do
66
+ @m = MockWorkbook.new
67
+ Spreadsheet::Workbook.expects(:new).returns(@m)
68
+ @formatter = Bookie::Formatter.new(:spreadsheet, 'test.xls')
69
+ end
70
+
71
+ after(:all) do
72
+ FileUtils.rm('test.sqlite')
73
+ end
74
+
75
+ it "correctly formats jobs" do
76
+ @formatter.print_jobs(@jobs.limit(2))
77
+ w = @m.worksheet('Details')
78
+ w.last_row_index.should eql 2
79
+ w.mock_columns.length.should eql Bookie::Formatter::DETAILS_FIELD_LABELS.length
80
+ w.mock_columns.each do |col|
81
+ col.width.should_not eql nil
82
+ end
83
+ w.row(0).should eql Bookie::Formatter::DETAILS_FIELD_LABELS
84
+ w.row(1).should eql ["root", "root", "test1", "Standalone", "2012-01-01 00:00:00",
85
+ "2012-01-01 01:00:00", "01:00:00", "00:01:40", "200kb (avg)", 0]
86
+ w.row(2).should eql ["test", "default", "test1", "Standalone", "2012-01-01 01:00:00",
87
+ "2012-01-01 02:00:00", "01:00:00", "00:01:40", "200kb (avg)", 1]
88
+ end
89
+
90
+ it "correctly formats summaries" do
91
+ Time.expects(:now).returns(Time.local(2012) + 3600 * 40).at_least_once
92
+ @formatter.print_summary(@jobs.order(:start_time).limit(5), Bookie::Database::System)
93
+ w = @m.worksheet('Summary')
94
+ w.column(0).width.should_not eql nil
95
+ w.last_row_index.should eql 7
96
+ w.mock_rows.should eql [
97
+ ["Number of jobs", 5],
98
+ ["Total wall time", "05:00:00"],
99
+ ["Total CPU time", "00:08:20"],
100
+ ["Successful", "60.0000%"],
101
+ ["Available CPU time", "140:00:00"],
102
+ ["CPU time used", "0.0992%"],
103
+ ["Available memory (average)", "1750000 kb"],
104
+ ["Memory used (average)", "0.0014%"],
105
+ ]
106
+ end
107
+
108
+ it "correctly flushes output" do
109
+ @m.expects(:write).with('test.xls')
110
+ @formatter.do_flush
111
+ end
112
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ require 'pacct'
4
+
5
+ #Stubbed out for now so the 'describe' line works
6
+ module Bookie
7
+ module Senders
8
+ module Standalone
9
+
10
+ end
11
+ end
12
+ end
13
+
14
+ describe Bookie::Senders::Standalone do
15
+ before(:all) do
16
+ config = Bookie::Config.new('snapshot/pacct_test_config.json')
17
+ @sender = Bookie::Sender.new(config)
18
+ end
19
+
20
+ it "correctly yields jobs" do
21
+ @sender.each_job('snapshot/pacct') do |job|
22
+ job.class.should eql Pacct::Entry
23
+ job.user_name.should eql 'root'
24
+ end
25
+ end
26
+
27
+ it "has the correct system type name" do
28
+ @sender.system_type_name.should eql 'Standalone'
29
+ end
30
+
31
+ it "has the correct memory stat type" do
32
+ @sender.memory_stat_type.should eql :avg
33
+ end
34
+ end
35
+
36
+ describe Pacct::Entry do
37
+ it "has a to_model method" do
38
+ Pacct::Entry.new.respond_to?(:to_model).should eql true
39
+ end
40
+ end