vcap_common 1.0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -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'