heap_periscope_agent 0.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
+ SHA256:
3
+ metadata.gz: 4c859a494c5e24980e456f5de068960eb7f7d823ea3c7e5f0dc11008962d564e
4
+ data.tar.gz: 05d5c1bf1886fec7c7eff1f73de7210c6580ce7be679bd9173d46fda1c99a632
5
+ SHA512:
6
+ metadata.gz: 24d398857915e014a307cb9580a4e4464827ec8f10180d3f826a486a6bf3d3f29667e1c80b2bee9383cddfcb1eccb1d1436d0f5bc71488a13f144e16ebe1e0b8
7
+ data.tar.gz: e94d5b7f0b7fe0ed16b0b92a83be81c34a94e27d4fbddb46f66c7e51880fdca72a523ae7223b3666e991eb1928b56bdd422ae6b9b900401ce3cfe006088ee830
@@ -0,0 +1,34 @@
1
+ require 'time'
2
+ require 'json'
3
+ require 'socket'
4
+ require 'objspace'
5
+ require 'concurrent/atomic/atomic_boolean'
6
+ require 'thread'
7
+
8
+ module HeapPeriscopeAgent
9
+ class Collector
10
+ def self.collect_snapshot(detailed_mode = false)
11
+ data = {
12
+ gc_stats: GC.stat,
13
+ object_space_summary: ObjectSpace.count_objects
14
+ }
15
+
16
+ if detailed_mode
17
+ data[:living_objects_by_class] = collect_detailed_living_objects(HeadPeriscopeAgent.configuration.detailed_objects_limit)
18
+ end
19
+
20
+ data
21
+ end
22
+
23
+ private
24
+
25
+ def self.collect_detailed_living_objects(limit)
26
+ counts = Hash.new(0)
27
+ ObjectSpace.each_object do |obj|
28
+ class_name = obj.class.name rescue 'Anonymous/Singleton Class'
29
+ counts[class_name] += 1
30
+ end
31
+ counts.sort_by { |_, count| -count }.first(limit).to_h
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,14 @@
1
+ module HeapPeriscopeAgent
2
+ class Configuration
3
+ attr_accessor :interval, :host, :port, :verbose, :enable_detailed_objects, :detailed_objects_limit
4
+
5
+ def initialize
6
+ @interval = 10 # seconds
7
+ @host = '127.0.0.1'
8
+ @port = 9000
9
+ @verbose = true
10
+ @enable_detailed_objects = false
11
+ @detailed_objects_limit = 20
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,122 @@
1
+ require 'time'
2
+ require 'json'
3
+ require 'socket'
4
+ require 'objspace'
5
+ require 'concurrent/atomic/atomic_boolean'
6
+ require 'thread'
7
+
8
+ module HeapPeriscopeAgent
9
+ class Reporter
10
+ def self.start
11
+ @config = HeapPeriscopeAgent.configuration
12
+ @running = Concurrent::AtomicBoolean.new(false)
13
+
14
+ return if @running.true?
15
+ @running.make_true
16
+
17
+ log("Reporter starting with interval: #{@config.interval}s.")
18
+
19
+ # Enable the standard GC Profiler
20
+ GC::Profiler.enable
21
+ # Store the initial total time to calculate deltas later
22
+ @last_gc_total_time = GC::Profiler.total_time
23
+
24
+ @thread = Thread.new do
25
+ @socket = UDPSocket.new
26
+ last_snapshot_time = Time.now
27
+
28
+ while @running.true?
29
+ # Periodically send a full snapshot
30
+ if Time.now - last_snapshot_time >= @config.interval
31
+ send_snapshot_report
32
+ last_snapshot_time = Time.now
33
+ end
34
+
35
+ # Check for new GC activity since the last check
36
+ send_gc_profiler_report
37
+
38
+ sleep(1) # Main loop delay
39
+ end
40
+
41
+ log("Reporter loop finished.")
42
+ GC::Profiler.disable # Clean up the profiler
43
+ @socket.close
44
+ end
45
+
46
+ @thread.report_on_exception = true
47
+ log("Reporter thread initiated.")
48
+ end
49
+
50
+ def self.stop
51
+ return unless @running&.true?
52
+
53
+ log("Stopping reporter...")
54
+ @running.make_false
55
+ if @thread&.join(5)
56
+ log("Reporter thread stopped gracefully.")
57
+ else
58
+ log("Reporter thread did not stop in time, killing.", level: :warn)
59
+ @thread&.kill
60
+ end
61
+ @thread = nil
62
+ log("Reporter stop process complete.")
63
+ end
64
+
65
+ def self.report_once!
66
+ @config = HeapPeriscopeAgent.configuration
67
+ @socket = UDPSocket.new
68
+ send_snapshot_report
69
+ @socket.close
70
+ end
71
+
72
+ private
73
+
74
+ def self.send_snapshot_report
75
+ log("Collecting periodic snapshot...")
76
+ stats = HeapPeriscopeAgent.Collector.collect_snapshot(@config.enable_detailed_objects)
77
+ send_payload(stats, "snapshot")
78
+ end
79
+
80
+ def self.send_gc_profiler_report
81
+ current_gc_total_time = GC::Profiler.total_time
82
+ # Calculate time spent in GC since our last check
83
+ gc_time_delta = current_gc_total_time - @last_gc_total_time
84
+
85
+ # If there was GC activity, report it
86
+ if gc_time_delta > 0
87
+ log("Detected GC activity. Sending GC profiler report.")
88
+ payload = {
89
+ # Convert from seconds to milliseconds
90
+ gc_duration_since_last_check_ms: (gc_time_delta * 1000).round(2),
91
+ gc_invocation_count: GC.count, # Total number of GCs so far
92
+ latest_gc_info: GC.latest_gc_info,
93
+ }
94
+ send_payload(payload, "gc_profiler_report")
95
+
96
+ # Update the last known time
97
+ @last_gc_total_time = current_gc_total_time
98
+ end
99
+ end
100
+
101
+ def self.send_payload(data, type)
102
+ payload = {
103
+ type: type,
104
+ process_id: Process.pid,
105
+ reported_at: Time.now.utc.iso8601,
106
+ payload: data
107
+ }.to_json
108
+
109
+ @socket.send(payload, 0, @config.host, @config.port)
110
+ log("Sent #{type} payload.")
111
+ rescue => e
112
+ log("Failed to send payload: #{e.message}", level: :error)
113
+ end
114
+
115
+ def self.log(message, level: :info)
116
+ return unless @config&.verbose
117
+ timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
118
+ output = "[HeapPeriscopeAgent][#{level.to_s.upcase}] #{timestamp}: #{message}"
119
+ level == :error ? warn(output) : puts(output)
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,13 @@
1
+ require_relative './heap_periscope_agent/configuration.rb'
2
+ require_relative './heap_periscope_agent/collector.rb'
3
+ require_relative './heap_periscope_agent/reporter.rb'
4
+
5
+ module HeapPeriscopeAgent
6
+ def self.configuration
7
+ @configuration ||= HeapPeriscopeAgent::Configuration.new
8
+ end
9
+
10
+ def self.configure
11
+ yield(configuration)
12
+ end
13
+ end
metadata ADDED
@@ -0,0 +1,43 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: heap_periscope_agent
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jonathan Natanael Siahaan
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Real-time Garbage Collection & Object Allocation Metrics
13
+ email: js.jonathan.n@gmail.com
14
+ executables: []
15
+ extensions: []
16
+ extra_rdoc_files: []
17
+ files:
18
+ - lib/heap_periscope_agent.rb
19
+ - lib/heap_periscope_agent/collector.rb
20
+ - lib/heap_periscope_agent/configuration.rb
21
+ - lib/heap_periscope_agent/reporter.rb
22
+ homepage: https://rubygems.org/gems/heap_periscope_agent
23
+ licenses:
24
+ - MIT
25
+ metadata: {}
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ requirements: []
40
+ rubygems_version: 3.6.9
41
+ specification_version: 4
42
+ summary: Real-time Garbage Collection & Object Allocation Metrics
43
+ test_files: []