linux_process_memory 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
+ SHA256:
3
+ metadata.gz: 31ab74b4c6400c74e5b44f8e566b3814f4967aad8f6921c243c9d6cc9c8cec6b
4
+ data.tar.gz: 9fad9bf95d085146dcdb427f09c5a1670e49b65f56c611f726caa2e003ac9d7b
5
+ SHA512:
6
+ metadata.gz: 34e6e1eb617e34c402355a13441585cb0f8c38bf239da87be5f04257815ef9d4ce1342f4c9ab9c1531fd85247a379ab88d1a040e4b37c3e91293930024147b7e
7
+ data.tar.gz: 90e4549cb6d9d3428343d7cdc0d4f0e01193b8b5a0b5df626390816367ceff839fd69f9e91c2f25edcc4e48188344b40a6101fe3431e6d141a685b1cc8133db4
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## 1.0.0
8
+
9
+ ### Added
10
+ - Initial release.
data/MIT-LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2023 Brian Durand
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # Linux Process Memory Ruby Gem
2
+
3
+ [![Continuous Integration](https://github.com/bdurand/linux_process_memory/actions/workflows/continuous_integration.yml/badge.svg)](https://github.com/bdurand/linux_process_memory/actions/workflows/continuous_integration.yml)
4
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
5
+
6
+ Ruby gem to get a breakdown of the memory being used by a Linux process. It is specific to Linux and will not work on other operating systems even if they are Linux-like (i.e. MacOS, Windows, FreeBSD, etc.). The breakdown takes into account shared memory and swap memory. It is most useful for monitoring memory usage of processes that use shared memory.
7
+
8
+ If you need similar functionality like this on other platforms, you can use the [get_process_mem gem](https://github.com/zombocom/get_process_mem).
9
+
10
+ ## Usage
11
+
12
+ Pass in a process pid to get a breakdown of the memory being used by that process.
13
+
14
+ ```ruby
15
+ memory = LinuxProcessMemory.new(1234)
16
+ ```
17
+
18
+ If you don't pass in a pid, it will get the memory for the current process.
19
+
20
+ ```ruby
21
+ memory = LinuxProcessMemory.new
22
+ ```
23
+
24
+ The memory breakdown is captured at the time the object is created. To get the memory breakdown at a different time, create a new object.
25
+
26
+ Memory is complicated in Linux and there are many different ways to measure it depending on how you want to count shared memory and swap. This gem provides a few different ways to measure memory usage. The following methods are available:
27
+
28
+ ```ruby
29
+ memory = LinuxProcessMemory.new
30
+ memory.total # => total memory used by the process (resident + swap)
31
+ memory.swap # => swap memory used
32
+ memory.shared # => shared memory used
33
+ memory.rss # => resident set size (i.e. non-swap memory allocated)
34
+ memory.resident # same as rss
35
+ memory.pss # => proportional set size (resident size + shared memory / number of processes)
36
+ memory.proportional # same as pss
37
+ memory.uss # => unique set size (resident memory not shared with other processes)
38
+ memory.unique # same as uss
39
+ memory.referenced # => memory actively referenced by the process (i.e. non-freeable memory)
40
+ ```
41
+
42
+ These measurements tend to be the mose useful ones especially if your processes are using shared memory:
43
+
44
+ - [Resident Set Size](https://en.wikipedia.org/wiki/Resident_set_size)
45
+ - [Proportional Set Size](https://en.wikipedia.org/wiki/Proportional_set_size)
46
+ - [Unique Set Size](https://en.wikipedia.org/wiki/Unique_set_size)
47
+
48
+ Values are returned in bytes, but you can request different units by passing in an optional argument to indicate the unit. Note that requesting a unit other than bytes will return a `Float` instead of an `Integer`.
49
+
50
+ ```ruby
51
+ memory = LinuxProcessMemory.new
52
+ memory.total(:kb) # => total memory used by the process in kilobytes
53
+ memory.total(:mb) # => total memory used by the process in megabytes
54
+ memory.total(:gb) # => total memory used by the process in gigabytes
55
+ ```
56
+
57
+ This gem is specific to Linux. If you try to use it on a non-Linux platform then memory values will always be returned as -1. If you want to check if the gem is supported on your platform, you can use the `supported?` method.
58
+
59
+ ```ruby
60
+ if LinuxProcessMemory.supported?
61
+ memory = LinuxProcessMemory.new
62
+ end
63
+ ```
64
+
65
+ ### Example
66
+
67
+ Here's an example of how you might use this gem to collect memory information on your processes by logging resident memory every minute.
68
+
69
+ ```ruby
70
+ if LinuxProcessMemory.supported?
71
+ logger = Logger.new($stderr)
72
+ Thread.new do
73
+ loop do
74
+ memory = LinuxProcessMemory.new
75
+ logger.info("Proportional memory: #{memory.pss(:mb).round} MB (pid: #{Process.pid})")
76
+ sleep(60)
77
+ end
78
+ end
79
+ end
80
+ ```
81
+
82
+ ## Installation
83
+
84
+ Add this line to your application's Gemfile:
85
+
86
+ ```ruby
87
+ gem "linux_process_memory"
88
+ ```
89
+
90
+ Then execute:
91
+ ```bash
92
+ $ bundle
93
+ ```
94
+
95
+ Or install it yourself as:
96
+ ```bash
97
+ $ gem install linux_process_memory
98
+ ```
99
+
100
+ ## Contributing
101
+
102
+ Open a pull request on [GitHub](https://github.com/bdurand/linux_process_memory).
103
+
104
+ Please use the [standardrb](https://github.com/testdouble/standard) syntax and lint your code with `standardrb --fix` before submitting.
105
+
106
+ ## License
107
+
108
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This class will read the smap files for a process on a Linux system and report
4
+ # the memory usage for that process.
5
+ class LinuxProcessMemory
6
+ VERSION = File.read(File.expand_path("../VERSION", __dir__)).strip.freeze
7
+
8
+ LINUX_MATCHER = /linux/i
9
+ private_constant :LINUX_MATCHER
10
+
11
+ UNIT_CONVERSION = {
12
+ "bytes" => 1,
13
+ "kilobytes" => 1024,
14
+ "megabytes" => 1024 * 1024,
15
+ "gigabytes" => 1024 * 1024 * 1024,
16
+ "kb" => 1024,
17
+ "mb" => 1024 * 1024,
18
+ "gb" => 1024 * 1024 * 1024,
19
+ "k" => 1024,
20
+ "m" => 1024 * 1024,
21
+ "g" => 1024 * 1024 * 1024
22
+ }.freeze
23
+ private_constant :UNIT_CONVERSION
24
+
25
+ attr_reader :pid
26
+
27
+ class << self
28
+ # Returns true if the current platform is Linux.
29
+ #
30
+ # @return [Boolean]
31
+ def supported?
32
+ RUBY_PLATFORM.match?(LINUX_MATCHER)
33
+ end
34
+ end
35
+
36
+ # Create a memory snapshot for the specified process.
37
+ #
38
+ # @param pid [Integer] The process ID to snapshot. Defaults to the current process.
39
+ def initialize(pid = Process.pid)
40
+ @pid = pid
41
+ @stats = (self.class.supported? ? read_smaps : Hash.new(-1))
42
+ end
43
+
44
+ # Returns the total memory usage for the process.
45
+ #
46
+ # @param units [Symbol] The units to return the memory usage in.
47
+ # Valid values are :bytes, :kilobytes, :megabytes, :gigabytes, :kb, :mb, :gb, :k, :m, :g.
48
+ # Defaults to :bytes.
49
+ # @return [Numberic]
50
+ def total(units = :bytes)
51
+ convert_units(@stats[:Rss] + @stats[:Swap], units)
52
+ end
53
+
54
+ # Returns the resident set size for the process.
55
+ #
56
+ # @param units [Symbol] The units to return the memory usage in.
57
+ # Valid values are :bytes, :kilobytes, :megabytes, :gigabytes, :kb, :mb, :gb, :k, :m, :g.
58
+ # Defaults to :bytes.
59
+ # @return [Numberic]
60
+ def rss(units = :bytes)
61
+ convert_units(@stats[:Rss], units)
62
+ end
63
+
64
+ alias_method :resident, :rss
65
+
66
+ # Returns the proportional set size for the process.
67
+ #
68
+ # @param units [Symbol] The units to return the memory usage in.
69
+ # Valid values are :bytes, :kilobytes, :megabytes, :gigabytes, :kb, :mb, :gb, :k, :m, :g.
70
+ # Defaults to :bytes.
71
+ # @return [Numberic]
72
+ def pss(units = :bytes)
73
+ convert_units(@stats[:Pss], units)
74
+ end
75
+
76
+ alias_method :proportional, :pss
77
+
78
+ # Returns the uniq set size for the process.
79
+ #
80
+ # @param units [Symbol] The units to return the memory usage in.
81
+ # Valid values are :bytes, :kilobytes, :megabytes, :gigabytes, :kb, :mb, :gb, :k, :m, :g.
82
+ # Defaults to :bytes.
83
+ # @return [Numberic]
84
+ def uss(units = :bytes)
85
+ convert_units(@stats[:Private_Clean] + @stats[:Private_Dirty], units)
86
+ end
87
+
88
+ alias_method :unique, :uss
89
+
90
+ # Returns the swap used by the process.
91
+ #
92
+ # @param units [Symbol] The units to return the memory usage in.
93
+ # Valid values are :bytes, :kilobytes, :megabytes, :gigabytes, :kb, :mb, :gb, :k, :m, :g.
94
+ # Defaults to :bytes.
95
+ # @return [Numberic]
96
+ def swap(units = :bytes)
97
+ convert_units(@stats[:Swap], units)
98
+ end
99
+
100
+ # Returns the shared memory used by the process.
101
+ #
102
+ # @param units [Symbol] The units to return the memory usage in.
103
+ # Valid values are :bytes, :kilobytes, :megabytes, :gigabytes, :kb, :mb, :gb, :k, :m, :g.
104
+ # Defaults to :bytes.
105
+ # @return [Numberic]
106
+ def shared(units = :bytes)
107
+ convert_units(@stats[:Shared_Clean] + @stats[:Shared_Dirty], units)
108
+ end
109
+
110
+ # Returns the referenced memory size for the process (i.e. memory that is actively being used)
111
+ # that cannot be reclaimed.
112
+ #
113
+ # @param units [Symbol] The units to return the memory usage in.
114
+ # Valid values are :bytes, :kilobytes, :megabytes, :gigabytes, :kb, :mb, :gb, :k, :m, :g.
115
+ # Defaults to :bytes.
116
+ # @return [Numberic]
117
+ def referenced(units = :bytes)
118
+ convert_units(@stats[:Referenced], units)
119
+ end
120
+
121
+ private
122
+
123
+ def read_smaps
124
+ stats = Hash.new(0)
125
+ return stats unless File.exist?(smap_rollup_file)
126
+
127
+ data = File.read(smap_rollup_file).split("\n")
128
+ data.shift # remove header
129
+ data.each do |line|
130
+ key, value, unit = line.split
131
+ key = key.chomp(":").to_sym
132
+
133
+ multiplier = UNIT_CONVERSION.fetch(unit.to_s.downcase, 1)
134
+ numeric_value = (value.to_f * multiplier).round
135
+ stats[key] += numeric_value if numeric_value > 0
136
+ end
137
+
138
+ stats
139
+ end
140
+
141
+ def convert_units(value, units)
142
+ return -1 if value < 0
143
+
144
+ divisor = UNIT_CONVERSION[units.to_s.downcase]
145
+ raise ArgumentError.new("Unknown units: #{units}") unless divisor
146
+
147
+ return value if divisor == 1
148
+
149
+ value.to_f / divisor
150
+ end
151
+
152
+ def smap_rollup_file
153
+ "/proc/#{pid}/smaps_rollup"
154
+ end
155
+ end
@@ -0,0 +1,35 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "linux_process_memory"
3
+ spec.version = File.read(File.expand_path("../VERSION", __FILE__)).strip
4
+ spec.authors = ["Brian Durand"]
5
+ spec.email = ["bbdurand@gmail.com"]
6
+
7
+ spec.summary = "Get a breakdown of the memory being used by a Linux process including resident, shared, private, and swap memory."
8
+
9
+ spec.homepage = "https://github.com/bdurand/linux_process_memory"
10
+ spec.license = "MIT"
11
+
12
+ # Specify which files should be added to the gem when it is released.
13
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
14
+ ignore_files = %w[
15
+ .
16
+ Appraisals
17
+ Gemfile
18
+ Gemfile.lock
19
+ Rakefile
20
+ config.ru
21
+ assets/
22
+ bin/
23
+ gemfiles/
24
+ spec/
25
+ ]
26
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
27
+ `git ls-files -z`.split("\x0").reject { |f| ignore_files.any? { |path| f.start_with?(path) } }
28
+ end
29
+
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.required_ruby_version = ">= 2.5"
33
+
34
+ spec.add_development_dependency "bundler"
35
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: linux_process_memory
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Brian Durand
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-08-17 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: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description:
28
+ email:
29
+ - bbdurand@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - CHANGELOG.md
35
+ - MIT-LICENSE.txt
36
+ - README.md
37
+ - VERSION
38
+ - lib/linux_process_memory.rb
39
+ - linux_process_memory.gemspec
40
+ homepage: https://github.com/bdurand/linux_process_memory
41
+ licenses:
42
+ - MIT
43
+ metadata: {}
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '2.5'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubygems_version: 3.2.22
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: Get a breakdown of the memory being used by a Linux process including resident,
63
+ shared, private, and swap memory.
64
+ test_files: []