blob: 3aba09f56153955484cd93e47d472bff89dffe4b [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'get_process_mem'
require_relative '../base'
module Skywalking
module Meter
module Runtime
# DataSource for collecting memory usage metrics
class MemDataSource < DataSource
def initialize
@process_mem = GetProcessMem.new
@cached_total_memory = nil
@cache_time = 0
@cache_duration = 60
@ffi_loaded = false
load_ffi_if_available
end
# Process RSS (Resident Set Size) in MB
def memory_rss_mb_generator
@process_mem.bytes / (1024.0 * 1024.0)
rescue
0.0
end
# Process memory usage percentage
def memory_usage_percent_generator
rss_bytes = @process_mem.bytes
total_memory = get_total_memory
if total_memory && total_memory > 0
(rss_bytes.to_f / total_memory * 100).round(2)
end
rescue
nil
end
private
# Load FFI if available for more efficient system calls
def load_ffi_if_available
require 'ffi'
@ffi_loaded = true
rescue LoadError
@ffi_loaded = false
end
# Get total system memory with caching to avoid frequent system calls
def get_total_memory
current_time = Time.now.to_i
if @cached_total_memory.nil? || (current_time - @cache_time) > @cache_duration
@cached_total_memory = fetch_total_memory
@cache_time = current_time
end
@cached_total_memory
end
# Fetch total memory using platform-specific methods
def fetch_total_memory
if RUBY_PLATFORM.include?('linux')
fetch_linux_total_memory
elsif RUBY_PLATFORM.include?('darwin')
fetch_macos_total_memory
end
end
# Fetch total memory on Linux using /proc/meminfo
def fetch_linux_total_memory
meminfo = File.read('/proc/meminfo')
total = (meminfo.match(/MemTotal:\s+(\d+)/)&.captures&.first || '0').to_i * 1024
total > 0 ? total : nil
rescue
nil
end
# Fetch total memory on macOS using sysctl
def fetch_macos_total_memory
if @ffi_loaded
result = fetch_macos_total_memory_ffi
return result if result
end
fetch_macos_total_memory_sysctl
end
# Use FFI to call sysctl directly
def fetch_macos_total_memory_ffi
extend FFI::Library
ffi_lib 'c'
attach_function :sysctl, [:pointer, :uint, :pointer, :pointer, :pointer, :size_t], :int
# sysctlnametomib for hw.memsize
mib = FFI::MemoryPointer.new(:int, 2)
mib.put_int(0, 6) # CTL_HW
mib.put_int(4, 24) # HW_MEMSIZE
size = FFI::MemoryPointer.new(:size_t)
size.put_size_t(0, 8)
result = FFI::MemoryPointer.new(:uint64_t)
if sysctl(mib, 2, result, size, nil, 0) == 0
value = result.read_uint64
value > 0 ? value : nil
end
rescue
nil
end
# Fallback to sysctl command (still forks but cached)
def fetch_macos_total_memory_sysctl
result = `sysctl -n hw.memsize 2>/dev/null`.strip.to_i
result > 0 ? result : nil
rescue
nil
end
end
end
end
end