busy-administrator 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 883afd8faafc52f568eea6a1393d2a945654bcf7
4
+ data.tar.gz: a08973fcbc8caddb7db15d787751ebdc5a151235
5
+ SHA512:
6
+ metadata.gz: 5ad360f51265c9d1a901327d1a2577ea6ae44504f0a5647b7deeba2753112bbfd093723cbbe1f38ee45cca743cddf35df1ef7a1b7e56e756a278a67f51bd40f4
7
+ data.tar.gz: a4ea6628c47693c4b551a4e58cea763de0ba9a7ca1897237222e5e491440b7be64ddaa04249e5df9a12985a149de0e050e00bf1fff89ebb83f61fbbee3e35a75
@@ -0,0 +1,41 @@
1
+ module BusyAdministrator
2
+ module Display
3
+ def self.debug(target, indent: 0)
4
+ if target.is_a?(Hash)
5
+ indent_space = " " * (indent * 4)
6
+ puts indent_space + "{"
7
+
8
+ indent += 1
9
+
10
+ target.each do |key, value|
11
+ if value.is_a?(Hash) || value.is_a?(Array)
12
+ indent_space = " " * (indent * 4)
13
+ puts indent_space + "#{ key }:"
14
+
15
+ self.debug(value, indent: indent + 1)
16
+ else
17
+ indent_space = " " * (indent * 4)
18
+ puts indent_space + "#{ key }: #{ value }"
19
+ end
20
+ end
21
+
22
+ indent -= 1
23
+ indent_space = " " * (indent * 4)
24
+ puts indent_space + "}"
25
+ elsif target.is_a?(Array)
26
+ indent_space = " " * (indent * 4)
27
+ puts indent_space + "["
28
+
29
+ target.each do |element|
30
+ self.debug(element, indent: indent + 1)
31
+ end
32
+
33
+ indent_space = " " * (indent * 4)
34
+ puts indent_space + "]"
35
+ else
36
+ indent_space = " " * (indent * 4)
37
+ puts indent_space + "#{ target }"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,13 @@
1
+ require 'securerandom'
2
+
3
+ module BusyAdministrator
4
+ module ExampleGenerator
5
+ class << self
6
+ def generate_string_with_specified_memory_size(memory_size)
7
+ raise Exception unless memory_size.respond_to?(:mebibytes)
8
+
9
+ SecureRandom.random_bytes(memory_size.bytes)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,143 @@
1
+ class Numeric
2
+ [:mebibytes, :megabytes, :kibibytes, :kilobytes].each do |method|
3
+ define_method(method) do
4
+ BusyAdministrator::MemorySize.send(method, self)
5
+ end
6
+ end
7
+
8
+ alias_method :mebibyte, :mebibytes
9
+ alias_method :megabyte, :megabytes
10
+ alias_method :kibibyte, :kibibytes
11
+ alias_method :kilobyte, :kilobytes
12
+ end
13
+
14
+ module BusyAdministrator
15
+ class MemorySize
16
+ def initialize(bytes:)
17
+ @bytes = bytes
18
+ end
19
+
20
+ def bytes
21
+ @bytes
22
+ end
23
+
24
+ def kibibytes
25
+ @bytes / 1024
26
+ end
27
+
28
+ def kilobytes
29
+ @bytes / 1000
30
+ end
31
+
32
+ def mebibytes
33
+ @bytes / 1024 / 1024
34
+ end
35
+
36
+ def megabytes
37
+ @bytes / 1000 / 1000
38
+ end
39
+
40
+ def +(other)
41
+ self.class.new(bytes: @bytes + other.bytes)
42
+ end
43
+
44
+ def -(other)
45
+ self.class.new(bytes: @bytes - other.bytes)
46
+ end
47
+
48
+ def *(number)
49
+ self.class.new(bytes: @bytes * number)
50
+ end
51
+
52
+ def /(number)
53
+ self.class.new(bytes: @bytes / number)
54
+ end
55
+
56
+ def ==(other_object)
57
+ self.bytes == other_object.bytes
58
+ end
59
+
60
+ def >(other_object)
61
+ self.bytes > other_object.bytes
62
+ end
63
+
64
+ def >=(other_object)
65
+ self.bytes >= other_object.bytes
66
+ end
67
+
68
+ def <(other_object)
69
+ self.bytes < other_object.bytes
70
+ end
71
+
72
+ def <=(other_object)
73
+ self.bytes <= other_object.bytes
74
+ end
75
+
76
+ def to_s
77
+ if @bytes < 1024
78
+ "#{ bytes } Bytes"
79
+ elsif @bytes < 1024 * 1024
80
+ "#{ kibibytes } KiB"
81
+ else
82
+ "#{ mebibytes } MiB"
83
+ end
84
+ end
85
+
86
+ def inspect
87
+ to_s
88
+ end
89
+
90
+ class << self
91
+ def kibibytes(num)
92
+ new(bytes: num * 1024)
93
+ end
94
+
95
+ def kilobytes(num)
96
+ new(bytes: num * 1000)
97
+ end
98
+
99
+ def mebibytes(num)
100
+ new(bytes: num * 1024 * 1024)
101
+ end
102
+
103
+ def megabytes(num)
104
+ new(bytes: num * 1000 * 1000)
105
+ end
106
+
107
+ def of(target, checked_objects: [])
108
+ total_size = 0
109
+
110
+ unless checked_objects.include?(target.object_id)
111
+ total_size += ObjectSpace.memsize_of(target)
112
+
113
+ checked_objects << target.object_id
114
+
115
+ (ObjectSpace.reachable_objects_from(target) || []).each do |reachable_object|
116
+ return new(bytes: 0) if reachable_object.is_a?(ObjectSpace::InternalObjectWrapper)
117
+
118
+ total_size += self.of(reachable_object, checked_objects: checked_objects).bytes
119
+ end
120
+ end
121
+
122
+ new(bytes: total_size)
123
+ end
124
+
125
+ def of_all_objects_from(target)
126
+ total_memory_size = 0
127
+
128
+ # ensure that we don't double count duplicates
129
+ checked_objects = []
130
+
131
+ if target.is_a?(Class) || target.is_a?(Module)
132
+ ObjectSpace.each_object(target).each do |instance|
133
+ total_memory_size += self.of(instance, checked_objects: checked_objects).bytes
134
+ end
135
+ else
136
+ raise Exception, "target should be a Class or Module"
137
+ end
138
+
139
+ new(bytes: total_memory_size)
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,201 @@
1
+ module BusyAdministrator
2
+ module MemoryUtils
3
+ class MemoryAnalyzer
4
+ attr_reader :pairs
5
+
6
+ def initialize
7
+ @pairs = []
8
+ end
9
+
10
+ def include(key:, value:)
11
+ @pairs << [key, value.object_id]
12
+ end
13
+
14
+ def analyze(verbose: false)
15
+ output = {}
16
+
17
+ @pairs.each do |pair|
18
+ key, value = pair
19
+
20
+ target = ObjectSpace._id2ref(value)
21
+
22
+ if target.is_a?(Class) || target.is_a?(Module)
23
+ output[key.to_sym] = MemorySize.of_all_objects_from(target)
24
+ else
25
+ output[key.to_sym] = MemorySize.of(target)
26
+ end
27
+
28
+ log("#{ key } = #{ output[key.to_sym] } { #{ target.class } }", verbose: verbose)
29
+ end
30
+
31
+ output
32
+ end
33
+
34
+ private
35
+
36
+ def log(message, verbose: false)
37
+ if verbose
38
+ puts "[BusyAdministrator::MemoryUtils::Analyzer.analyze] #{ message }"
39
+ end
40
+ end
41
+ end
42
+
43
+ class << self
44
+ def trigger_gc
45
+ GC.start(full_mark:true, immediate_sweep: true, immediate_mark: false)
46
+ end
47
+
48
+ def get_created_object_classes
49
+ ObjectSpace.each_object.to_a.map(&:class).uniq.select do |target|
50
+ target.is_a?(Class) || target.is_a?(Module)
51
+ end
52
+ end
53
+
54
+ def profile(gc_enabled: true, verbose: false)
55
+ analyzer = MemoryAnalyzer.new
56
+ excluded_object_id_storage = []
57
+ all_object_ids_after_execution = []
58
+ gc_count_before = 0
59
+
60
+ output_container = {
61
+ memory_usage: {},
62
+ total_time: 0.0,
63
+ gc: {
64
+ count: 0,
65
+ enabled: false
66
+ }
67
+ }
68
+
69
+ action("GC Settings") do
70
+ output_container[:gc][:enabled] = gc_enabled
71
+
72
+ if gc_enabled
73
+ GC.enable
74
+ log("[Before] Enable GC", verbose: verbose)
75
+ else
76
+ GC.disable
77
+ log("[Before] Disable GC", verbose: verbose)
78
+ end
79
+ end
80
+
81
+ action("Prepare Object ID Storage Before Execution") do
82
+ excluded_object_id_storage = get_created_object_ids
83
+ log("[Before] Object Count: #{ excluded_object_id_storage.size }", verbose: verbose)
84
+ end
85
+
86
+ action("Get Memory Usage Before Execution") do
87
+ memory_usage_before = ProcessUtils.get_memory_usage(:rss)
88
+ output_container[:memory_usage][:before] = memory_usage_before
89
+
90
+ log("[Before] Memory Usage: #{ memory_usage_before }", verbose: verbose)
91
+ end
92
+
93
+ action("Execute Main Action") do
94
+ gc_count_before = GC.stat[:count]
95
+ log("[Before] GC Count: #{ gc_count_before }", verbose: verbose)
96
+
97
+ log("[Before] Block Start", verbose: verbose)
98
+
99
+ total_time = Benchmark.realtime do
100
+ yield(analyzer) if block_given?
101
+ end
102
+
103
+ output_container[:total_time] = total_time.round(6)
104
+
105
+ log("[After] Block End", verbose: verbose)
106
+ log("[After] Total Time: #{ total_time } seconds", verbose: verbose)
107
+ end
108
+
109
+ action("Compute Specific Stats") do
110
+ output_container[:specific] = analyzer.analyze(verbose: verbose)
111
+ end
112
+
113
+ action("Run GC if enabled") do
114
+ if gc_enabled
115
+ self.trigger_gc
116
+ log("[After] Run GC", verbose: verbose)
117
+ end
118
+
119
+ gc_count_after = GC.stat[:count]
120
+ log("[After] GC Count: #{ gc_count_after }", verbose: verbose)
121
+
122
+ gc_count_diff = gc_count_after - gc_count_before
123
+ output_container[:gc][:count] = gc_count_diff
124
+
125
+ log("[Diff] GC Count: #{ gc_count_diff }", verbose: verbose)
126
+ end
127
+
128
+ action("Get Memory Usage After Execution") do
129
+ memory_usage_after = ProcessUtils.get_memory_usage(:rss)
130
+ log("[After] Memory Usage: #{ memory_usage_after }", verbose: verbose)
131
+
132
+ output_container[:memory_usage][:after] = memory_usage_after
133
+ output_container[:memory_usage][:diff] = output_container[:memory_usage][:after] - output_container[:memory_usage][:before]
134
+ log("[Diff] Memory Usage: #{ output_container[:memory_usage] }", verbose: verbose)
135
+ end
136
+
137
+ action("Get Object Count After Execution") do
138
+ all_object_ids_after_execution = get_created_object_ids
139
+ log("[After] Object Count: #{ all_object_ids_after_execution.size }", verbose: verbose)
140
+
141
+ all_object_ids_after_execution -= excluded_object_id_storage
142
+ log("[Diff] Object Count: #{ all_object_ids_after_execution.size }", verbose: verbose)
143
+ output_container[:object_count] = all_object_ids_after_execution.size
144
+ end
145
+
146
+ action("Compute General Stats") do
147
+ general = {}
148
+
149
+ all_object_ids_after_execution.each do |object_id|
150
+ target = ObjectSpace._id2ref(object_id)
151
+ target_class = target.class.name
152
+
153
+ general[target_class.to_sym] ||= 0
154
+ general[target_class.to_sym] += ObjectSpace.memsize_of(target)
155
+ end
156
+
157
+ general.each do |key, value|
158
+ general[key] = MemorySize.new(bytes: value)
159
+ log("[Diff] Class #{ key }: #{ general[key] } ")
160
+ end
161
+
162
+ output_container[:general] = general
163
+ end
164
+
165
+ action("Run GC") do
166
+ GC.enable
167
+ self.trigger_gc
168
+ end
169
+
170
+ output_container
171
+ end
172
+
173
+ private
174
+
175
+ def action(label)
176
+ log("Action: #{ label }")
177
+
178
+ yield if block_given?
179
+ end
180
+
181
+ def log(message, verbose: false)
182
+ if verbose
183
+ puts "[BusyAdministrator::MemoryUtils.profile] #{ message }"
184
+ end
185
+ end
186
+
187
+ def get_created_object_ids
188
+ output = ObjectSpace.each_object.to_a.map(&:object_id)
189
+ output << output.object_id
190
+
191
+ output
192
+ end
193
+
194
+ def get_unique_classes_from_object_ids(object_ids_array)
195
+ object_ids_array.map { |object_id| ObjectSpace._id2ref(object_id) }.map(&:class).uniq.select do |target|
196
+ target.is_a?(Class) || target.is_a?(Module)
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,30 @@
1
+ module BusyAdministrator
2
+ module ProcessUtils
3
+ class << self
4
+ # http://stackoverflow.com/questions/7880784/what-is-rss-and-vsz-in-linux-memory-management
5
+ def get_memory_usage(metric = :rss)
6
+ available_options = [:rss, :vsz]
7
+
8
+ if available_options.include?(metric)
9
+ bytes = `ps -o #{ metric.to_s }= -p #{ Process.pid }`.to_i * 1024
10
+
11
+ MemorySize.new(bytes: bytes)
12
+ else
13
+ raise Exception, "available_options: #{ available_options.inspect }"
14
+ end
15
+ end
16
+
17
+ # Does not work in OSX
18
+ # Will raise NoMemoryError: failed to allocate memory
19
+ def set_max_memory_usage(mebibytes:)
20
+ Process.setrlimit(Process::RLIMIT_AS, mebibytes * 1024 * 1024)
21
+ end
22
+
23
+ def get_max_memory_usage
24
+ bytes = Process.getrlimit(Process::RLIMIT_AS)[1]
25
+
26
+ MemorySize.new(bytes: bytes)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module BusyAdministrator
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,55 @@
1
+ require 'objspace'
2
+ require 'benchmark'
3
+ require 'busy-administrator/display'
4
+ require 'busy-administrator/example_generator'
5
+ require 'busy-administrator/memory_size'
6
+ require 'busy-administrator/memory_utils'
7
+ require 'busy-administrator/process_utils'
8
+ require 'busy-administrator/version'
9
+
10
+ """
11
+ require 'busy-administrator'
12
+
13
+ BusyAdministrator::MemoryUtils.get_created_object_classes
14
+ BusyAdministrator::MemoryUtils.profile(verbose: true) do
15
+ puts 'something'
16
+ end
17
+
18
+ class Testing
19
+ attr_accessor :large_value
20
+ end
21
+
22
+ BusyAdministrator::MemoryUtils.profile(verbose: true, gc_enabled: false) do |analyzer|
23
+ data = BusyAdministrator::ExampleGenerator.generate_string_with_specified_memory_size(10.mebibytes)
24
+
25
+ testing_a = Testing.new
26
+ testing_a.large_value = BusyAdministrator::ExampleGenerator.generate_string_with_specified_memory_size(5.mebibytes)
27
+
28
+ testing_b = Testing.new
29
+ testing_b.large_value = BusyAdministrator::ExampleGenerator.generate_string_with_specified_memory_size(10.mebibytes)
30
+
31
+ analyzer.include(key: 'data', value: data)
32
+ analyzer.include(key: 'testing_a', value: testing_a)
33
+ analyzer.include(key: 'testing_b', value: testing_b)
34
+ analyzer.include(key: 'Testing', value: Testing)
35
+ end
36
+
37
+ data = BusyAdministrator::ExampleGenerator.generate_string_with_specified_memory_size(10.mebibytes)
38
+ BusyAdministrator::MemorySize.of(data).mebibytes
39
+ BusyAdministrator::MemorySize.of_all_objects_from(SampleClass).mebibytes
40
+
41
+ BusyAdministrator::ProcessUtils.get_memory_usage(:rss).mebibytes
42
+ BusyAdministrator::ProcessUtils.get_memory_usage(:vsz).mebibytes
43
+
44
+ # set max memory usage will check vsz
45
+ BusyAdministrator::ProcessUtils.set_max_memory_usage(100.mebibytes)
46
+ BusyAdministrator::ProcessUtils.get_max_memory_usage.mebibytes
47
+
48
+ TODO:
49
+ Create gem and add tests and documentation
50
+ Add option to set recursion level for MemorySize
51
+ Add advanced analyzer for objects with references to other objects
52
+ """
53
+
54
+ module BusyAdministrator
55
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: busy-administrator
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Joshua Arvin Lat
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-09-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Handy Ruby Dev Tools for the Busy Administrator
42
+ email:
43
+ - joshua.arvin.lat@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - lib/busy-administrator.rb
49
+ - lib/busy-administrator/display.rb
50
+ - lib/busy-administrator/example_generator.rb
51
+ - lib/busy-administrator/memory_size.rb
52
+ - lib/busy-administrator/memory_utils.rb
53
+ - lib/busy-administrator/process_utils.rb
54
+ - lib/busy-administrator/version.rb
55
+ homepage: ''
56
+ licenses:
57
+ - MIT
58
+ metadata: {}
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubyforge_project:
75
+ rubygems_version: 2.4.8
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: Handy Ruby Dev Tools for the Busy Administrator
79
+ test_files: []
80
+ has_rdoc: