vcap_common 1.0.10

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.
@@ -0,0 +1,164 @@
1
+ #a priority queue with the added twist of FIFO behavior for elements with equal priorities
2
+ #implementation using binary max-heap on top of a ruby array.
3
+ #the FIFO behavior is implemented by storing a FIFO bucket of same-priority values
4
+
5
+ #The implementation is not meant to be high-performance, just decent, with two goals:
6
+ #1. clean interface
7
+ #2. proper time/space complexity of a binary heap
8
+ #3. no silly memory leaks (Ah, three weapons of the Spanish Inquisition)
9
+
10
+ #additionally, we implement a PrioritySet, that is a PriorityQueue
11
+ #into which an element can only be inserted once. This PrioritySet
12
+ #allows specifying identity for the object with a separate object.
13
+ #The identity is for determining whether an object being inserted is a
14
+ #duplicate, e.g.
15
+
16
+ # q.insert("boo", 1, "key")
17
+ # q.insert("zah", 1, "key")
18
+
19
+ #will result in just one object in the queue, "boo"
20
+ #
21
+ #See spec/unit/priority_queue_set for
22
+ #other examples
23
+
24
+ require 'set'
25
+ require 'pp'
26
+
27
+ module VCAP
28
+ class PriorityQueueFIFO
29
+
30
+ attr_reader :size
31
+
32
+ def initialize
33
+ @heap_arr = []
34
+ @p2b = {} #hash mapping priorities to buckets
35
+ @size = 0
36
+ end
37
+
38
+ def empty?
39
+ size == 0
40
+ end
41
+
42
+ def insert(item, priority = 0)
43
+ raise ArgumentError, "priority can not be negative: #{priority}" if priority < 0
44
+
45
+ unless append_to_existing_priority_bucket(item, priority)
46
+ add_bucket_at_the_end_and_shift_up(item, priority)
47
+ end
48
+ @size += 1
49
+ end
50
+
51
+ def remove
52
+ return nil if empty?
53
+ bucket = top_bucket
54
+ priority = top_priority
55
+ elem = bucket.shift
56
+ @size -= 1
57
+ if empty?
58
+ @heap_arr.clear
59
+ @p2b.clear
60
+ elsif bucket.empty?
61
+ @heap_arr[0] = @heap_arr.pop
62
+ @p2b.delete(priority)
63
+ shift_down
64
+ else
65
+ #do nothing, we just shifted a value from a bucket and it still isn't empty, so no rearrangement is needed
66
+ end
67
+ elem
68
+ end
69
+
70
+ private
71
+
72
+ def add_bucket_at_the_end_and_shift_up(item, priority)
73
+ bucket = [item]
74
+ @p2b[priority] = bucket
75
+
76
+ #normal binary heap operation
77
+ @heap_arr.push priority
78
+ shift_up
79
+ end
80
+
81
+ def append_to_existing_priority_bucket(item, priority)
82
+ return false unless @p2b[priority]
83
+ @p2b[priority] << item
84
+ return true
85
+ end
86
+
87
+ def top_bucket
88
+ @p2b[top_priority]
89
+ end
90
+
91
+ def top_priority
92
+ priority_at(0)
93
+ end
94
+
95
+ def priority_at(index)
96
+ return -1 if index >= @heap_arr.size
97
+ @heap_arr[index]
98
+ end
99
+
100
+ def parent_index(index)
101
+ (index+1) / 2 - 1
102
+ end
103
+
104
+ def left_child_index(index)
105
+ (index+1) * 2 - 1
106
+ end
107
+
108
+ def right_child_index(index)
109
+ (index+1) * 2
110
+ end
111
+
112
+ def any_children_at?(index)
113
+ left_child_index(index) < @heap_arr.length
114
+ end
115
+
116
+ def shift_up
117
+ cur_index = @heap_arr.length - 1
118
+ while cur_index > 0 && priority_at(cur_index) > priority_at(parent_index(cur_index)) do
119
+ next_cur_index = parent_index cur_index
120
+ swap_at(cur_index, next_cur_index)
121
+ cur_index = next_cur_index
122
+ end
123
+ end
124
+
125
+ def index_of_max_priority_child_at(index)
126
+ #raise(ArgumentError, "no children at #{index}") unless any_children_at?(index)
127
+ l = left_child_index(index)
128
+ r = right_child_index(index)
129
+ return r if priority_at(r) > priority_at(l) #this is safe since priority will return -1 for non-existent right child
130
+ return l
131
+ end
132
+
133
+ def shift_down
134
+ cur_index = 0
135
+ while any_children_at?(cur_index) && priority_at(cur_index) < priority_at(index_of_max_priority_child_at(cur_index)) do
136
+ next_cur_index = index_of_max_priority_child_at cur_index
137
+ swap_at(cur_index, next_cur_index)
138
+ cur_index = next_cur_index
139
+ end
140
+ end
141
+
142
+ def swap_at(i,j)
143
+ @heap_arr[i], @heap_arr[j] = @heap_arr[j], @heap_arr[i]
144
+ end
145
+ end
146
+
147
+
148
+ class PrioritySet < PriorityQueueFIFO
149
+ def initialize
150
+ super
151
+ @set = Set.new #the set is used to check for duplicates
152
+ end
153
+
154
+ def insert(elem, priority = 0, key = nil)
155
+ super([elem,key], priority) if @set.add?(key || elem)
156
+ end
157
+
158
+ def remove
159
+ elem, key = super
160
+ @set.delete(key || elem)
161
+ elem
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,43 @@
1
+ require 'thread'
2
+
3
+ require 'vcap/subprocess'
4
+
5
+ module VCAP
6
+ module ProcessUtils
7
+ STAT_FIELDS = [
8
+ {:name => :rss, :parse_method => :to_i},
9
+ {:name => :vsize, :parse_method => :to_i},
10
+ {:name => :pcpu, :parse_method => :to_f},
11
+ ]
12
+
13
+ class << self
14
+
15
+ def get_stats(pid=nil)
16
+ pid ||= Process.pid
17
+
18
+ flags = STAT_FIELDS.map {|f| "-o #{f[:name]}=" }.join(' ')
19
+ begin
20
+ stdout, stderr, status = VCAP::Subprocess.run("ps #{flags} -p #{pid}")
21
+ rescue VCAP::SubprocessStatusError => se
22
+ # Process not running
23
+ if se.status.exitstatus == 1
24
+ return nil
25
+ else
26
+ raise se
27
+ end
28
+ end
29
+
30
+ ret = {}
31
+ stdout.split.each_with_index do |val, ii|
32
+ field = STAT_FIELDS[ii]
33
+ ret[field[:name]] = val.send(field[:parse_method])
34
+ end
35
+
36
+ ret
37
+ end
38
+
39
+ end # class << self
40
+ end # ProcessUtils
41
+ end # VCAP
42
+
43
+
data/lib/vcap/quota.rb ADDED
@@ -0,0 +1,152 @@
1
+ module VCAP
2
+ module Quota
3
+
4
+ class Command
5
+ class ValidationError < StandardError; end
6
+
7
+ def run
8
+ validate
9
+ command = build_command
10
+ result = execute(command)
11
+ parse_result(result)
12
+ end
13
+
14
+ def validate
15
+ nil
16
+ end
17
+
18
+ def build_command
19
+ raise NotImplementedError
20
+ end
21
+
22
+ def execute(command)
23
+ stdout = `#{command}`
24
+ [$?, stdout]
25
+ end
26
+
27
+ def parse_result(result)
28
+ result
29
+ end
30
+
31
+ private
32
+
33
+ def assert_at_least_one_of(*fields)
34
+ for field in fields
35
+ has_value = send(field.to_sym)
36
+ return if has_value
37
+ end
38
+ raise ValidationError, "At least one of {#{fields.join(', ')}} must be set"
39
+ end
40
+
41
+ def assert_at_most_one_of(*fields)
42
+ existing_fields = fields.inject([]) do |accum, field|
43
+ accum << field unless send(field.to_sym)
44
+ accum
45
+ end
46
+ unless existing_fields.length == 1
47
+ raise ValidationError, "At most one of #{fields.join(', ')} must be set"
48
+ end
49
+ end
50
+ end
51
+
52
+ class SetQuota < Command
53
+ attr_accessor :user
54
+ attr_accessor :group
55
+ attr_accessor :filesystem
56
+ attr_accessor :quotas
57
+
58
+ def initialize
59
+ @quotas = {
60
+ :block => {
61
+ :soft => 0,
62
+ :hard => 0,
63
+ },
64
+ :inode => {
65
+ :soft => 0,
66
+ :hard => 0,
67
+ },
68
+ }
69
+ end
70
+
71
+ def validate
72
+ assert_at_least_one_of(:user)
73
+ assert_at_least_one_of(:filesystem)
74
+ assert_at_least_one_of(:quotas)
75
+ end
76
+
77
+ private
78
+
79
+ def build_command
80
+ cmd = ['setquota']
81
+ cmd << ['-u', self.user] if self.user
82
+ cmd << ['-g', self.group] if self.group
83
+ cmd << [self.quotas[:block][:soft], self.quotas[:block][:hard],
84
+ self.quotas[:inode][:soft], self.quotas[:inode][:hard]]
85
+ cmd << self.filesystem
86
+ cmd.flatten.join(' ')
87
+ end
88
+ end
89
+
90
+ class RepQuota < Command
91
+ attr_accessor :report_groups
92
+ attr_accessor :report_users
93
+ attr_accessor :ids_only
94
+ attr_accessor :filesystem
95
+
96
+ def initialize
97
+ @report_users = true
98
+ end
99
+
100
+ def validate
101
+ assert_at_least_one_of(:report_groups, :report_users)
102
+ assert_at_least_one_of(:filesystem)
103
+ end
104
+
105
+ def build_command
106
+ cmd = ['repquota', '-p'] # -p reports grace as 0 when unset
107
+ cmd << '-u' if self.report_users
108
+ cmd << '-g' if self.report_groups
109
+ cmd << '-n' if self.ids_only
110
+ cmd << self.filesystem
111
+ cmd = cmd.flatten.join(' ')
112
+ cmd
113
+ end
114
+
115
+ def parse_result(result)
116
+ if result[0] == 0
117
+ quota_info = {}
118
+ result[1].lines.each do |line|
119
+ next unless line.match(/[^\s]+\s+[+-]+\s+\d+/)
120
+ fields = line.split(/\s+/)
121
+ if self.ids_only
122
+ match = fields[0].match(/^#(\d+)$/)
123
+ uid = match[1].to_i
124
+ else
125
+ uid = fields[0]
126
+ end
127
+ quota_info[uid] = {
128
+ :usage => {
129
+ :block => fields[2].to_i,
130
+ :inode => fields[6].to_i
131
+ },
132
+ :quotas => {
133
+ :block => {
134
+ :soft => fields[3].to_i,
135
+ :hard => fields[4].to_i,
136
+ },
137
+ :inode => {
138
+ :soft => fields[7].to_i,
139
+ :hard => fields[8].to_i,
140
+ },
141
+ }
142
+ }
143
+ end
144
+ [true, quota_info]
145
+ else
146
+ [false, result]
147
+ end
148
+ end
149
+ end
150
+
151
+ end # VCAP::Quota
152
+ end
@@ -0,0 +1,74 @@
1
+ require "monitor"
2
+
3
+ module VCAP
4
+
5
+ class RollingMetric
6
+
7
+ def initialize(duration, num_buckets = 60)
8
+ @duration = duration
9
+ num_buckets = [@duration, num_buckets].min
10
+ @bucket_duration = (@duration / num_buckets).to_i
11
+ @eviction_duration = @bucket_duration * 2
12
+ @buckets = []
13
+ num_buckets.times do
14
+ @buckets << {:timestamp => 0, :value => 0, :samples => 0}
15
+ end
16
+ end
17
+
18
+ def <<(value)
19
+ timestamp = Time.now.to_i
20
+ bucket = @buckets[(timestamp / @bucket_duration) % @buckets.length]
21
+ if timestamp - bucket[:timestamp] > @eviction_duration
22
+ bucket[:timestamp] = timestamp
23
+ bucket[:value] = value
24
+ bucket[:samples] = 1
25
+ else
26
+ bucket[:value] += value
27
+ bucket[:samples] += 1
28
+ end
29
+ end
30
+
31
+ def value
32
+ timestamp = Time.now.to_i
33
+ min_timestamp = timestamp - @duration
34
+
35
+ value = 0
36
+ samples = 0
37
+
38
+ @buckets.each do |bucket|
39
+ if bucket[:timestamp] > min_timestamp
40
+ value += bucket[:value]
41
+ samples += bucket[:samples]
42
+ end
43
+ end
44
+
45
+ {
46
+ :value => value,
47
+ :samples => samples
48
+ }
49
+ end
50
+
51
+ def to_json
52
+ Yajl::Encoder.encode(value)
53
+ end
54
+
55
+ end
56
+
57
+ class ThreadSafeRollingMetric < RollingMetric
58
+
59
+ def initialize(*args)
60
+ super(*args)
61
+ @lock = Monitor.new
62
+ end
63
+
64
+ def <<(*args)
65
+ @lock.synchronize { super(*args) }
66
+ end
67
+
68
+ def value(*args)
69
+ @lock.synchronize { super(*args) }
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,32 @@
1
+ # Copyright (c) 2009-2011 VMware, Inc.
2
+ module VCAP
3
+
4
+ module Spec
5
+
6
+ module EM
7
+
8
+ def em(options = {})
9
+ raise "no block given" unless block_given?
10
+ timeout = options[:timeout] ||= 1.0
11
+
12
+ ::EM.run {
13
+ quantum = 0.005
14
+ ::EM.set_quantum(quantum * 1000) # Lowest possible timer resolution
15
+ ::EM.set_heartbeat_interval(quantum) # Timeout connections asap
16
+ ::EM.add_timer(timeout) { raise "timeout" }
17
+ yield
18
+ }
19
+ end
20
+
21
+ def done
22
+ raise "reactor not running" if !::EM.reactor_running?
23
+
24
+ ::EM.next_tick {
25
+ # Assert something to show a spec-pass
26
+ :done.should == :done
27
+ ::EM.stop_event_loop
28
+ }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,87 @@
1
+ require 'vcap/common'
2
+
3
+
4
+ module VCAP
5
+ module Spec
6
+ module ForkedComponent
7
+ end
8
+ end
9
+ end
10
+
11
+ class VCAP::Spec::ForkedComponent::Base
12
+ attr_reader :pid, :pid_filename, :output_basedir, :name, :cmd
13
+
14
+ attr_accessor :reopen_stdio
15
+
16
+ # @param cmd String Command to run
17
+ # @param name String Short name for this component (e.g. 'redis')
18
+ # @param output_basedir String Stderr/stdout will be placed under this directory
19
+ # @param pid_filename String If not nil, we ready the pid from this file instead
20
+ # of using the pid returned from fork
21
+ def initialize(cmd, name, output_basedir='/tmp', pid_filename=nil)
22
+ @cmd = cmd
23
+ @name = name
24
+ @output_basedir = output_basedir
25
+ @pid_filename = pid_filename
26
+
27
+ @reopen_stdio = true
28
+
29
+ end
30
+
31
+ def start
32
+ pid = fork do
33
+
34
+ if @reopen_stdio
35
+ fn = File.join(@output_basedir, "#{@name}.#{Process.pid}.out")
36
+ outfile = File.new(fn, 'w+')
37
+ $stderr.reopen(outfile)
38
+ $stdout.reopen(outfile)
39
+ end
40
+
41
+ exec(@cmd)
42
+ end
43
+
44
+ if @pid_filename
45
+ wait_for(5) { File.exists?(@pid_filename) }
46
+ @pid = File.read(@pid_filename).chomp.to_i
47
+ else
48
+ @pid = pid
49
+ end
50
+
51
+ self
52
+ end
53
+
54
+ def stop
55
+ return unless @pid && VCAP.process_running?(@pid)
56
+ Process.kill('TERM', @pid)
57
+ Process.waitpid(@pid, 0)
58
+ FileUtils.rm_f(@pid_filename) if @pid_filename
59
+ @pid = nil
60
+
61
+ self
62
+ end
63
+
64
+ def running?
65
+ VCAP.process_running?(@pid)
66
+ end
67
+
68
+ def ready?
69
+ raise NotImplementedError
70
+ end
71
+
72
+ def wait_ready(timeout=1)
73
+ wait_for { ready? }
74
+ end
75
+
76
+ private
77
+
78
+ def wait_for(timeout=1, &predicate)
79
+ start = Time.now()
80
+ cond_met = predicate.call()
81
+ while !cond_met && ((Time.new() - start) < timeout)
82
+ cond_met = predicate.call()
83
+ sleep(0.2)
84
+ end
85
+ cond_met
86
+ end
87
+ end
@@ -0,0 +1,28 @@
1
+ require 'nats/client'
2
+ require 'uri'
3
+
4
+ require 'vcap/spec/forked_component/base'
5
+
6
+ module VCAP
7
+ module Spec
8
+ module ForkedComponent
9
+ end
10
+ end
11
+ end
12
+
13
+ class VCAP::Spec::ForkedComponent::NatsServer < VCAP::Spec::ForkedComponent::Base
14
+
15
+ attr_reader :uri, :port, :parsed_uri
16
+
17
+ def initialize(pid_filename, port, output_basedir='tmp')
18
+ cmd = "ruby -S bundle exec nats-server -p #{port} -P #{pid_filename} -V -D"
19
+ super(cmd, 'nats', output_basedir, pid_filename)
20
+ @port = port
21
+ @uri = "nats://127.0.0.1:#{@port}"
22
+ @parsed_uri = URI.parse(@uri)
23
+ end
24
+
25
+ def ready?
26
+ running? && NATS.server_running?(@parsed_uri)
27
+ end
28
+ end
@@ -0,0 +1,2 @@
1
+ require 'vcap/spec/forked_component/base'
2
+ require 'vcap/spec/forked_component/nats_server'