# Licensed to Elasticsearch B.V. under one or more contributor # license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright # ownership. Elasticsearch B.V. 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. # frozen_string_literal: true module ElasticAPM module Metrics # @api private class CpuMemSet < Set include Logging # @api private class Sample # rubocop:disable Metrics/ParameterLists def initialize( page_size:, process_cpu_usage:, process_memory_rss:, process_memory_size:, system_cpu_total:, system_cpu_usage:, system_memory_free:, system_memory_total: ) @page_size = page_size @process_cpu_usage = process_cpu_usage @process_memory_rss = process_memory_rss @process_memory_size = process_memory_size @system_cpu_total = system_cpu_total @system_cpu_usage = system_cpu_usage @system_memory_free = system_memory_free @system_memory_total = system_memory_total end # rubocop:enable Metrics/ParameterLists attr_accessor( :page_size, :process_cpu_usage, :process_memory_rss, :process_memory_size, :system_cpu_total, :system_cpu_usage, :system_memory_free, :system_memory_total ) end def initialize(config) super @sampler = sampler_for_platform(Metrics.platform) read! # set @previous on boot end attr_reader :config def collect read! super end private def sampler_for_platform(platform) case platform when :linux then Linux.new else warn "Unsupported platform '#{platform}' - Disabling system metrics" disable! nil end end def read! return if disabled? current = @sampler.sample unless @previous @previous = current return end cpu_usage_pct, cpu_process_pct = calculate_deltas(current, @previous) gauge(:'system.cpu.total.norm.pct').value = cpu_usage_pct gauge(:'system.memory.actual.free').value = current.system_memory_free gauge(:'system.memory.total').value = current.system_memory_total gauge(:'system.process.cpu.total.norm.pct').value = cpu_process_pct gauge(:'system.process.memory.size').value = current.process_memory_size gauge(:'system.process.memory.rss.bytes').value = current.process_memory_rss * current.page_size @previous = current end def calculate_deltas(current, previous) system_cpu_total = current.system_cpu_total - previous.system_cpu_total system_cpu_usage = current.system_cpu_usage - previous.system_cpu_usage process_cpu_usage = current.process_cpu_usage - previous.process_cpu_usage cpu_usage_pct = system_cpu_usage.to_f / system_cpu_total cpu_process_pct = process_cpu_usage.to_f / system_cpu_total [cpu_usage_pct, cpu_process_pct] end # @api private class Linux def sample proc_stat = ProcStat.new.read! proc_self_stat = ProcSelfStat.new.read! meminfo = Meminfo.new.read! Sample.new( system_cpu_total: proc_stat.total, system_cpu_usage: proc_stat.usage, system_memory_total: meminfo.total, system_memory_free: meminfo.available, process_cpu_usage: proc_self_stat.total, process_memory_size: proc_self_stat.vsize, process_memory_rss: proc_self_stat.rss, page_size: meminfo.page_size ) end # @api private class ProcStat attr_reader :total, :usage CPU_FIELDS = %i[ user nice system idle iowait irq softirq steal guest guest_nice ].freeze def read! stat = IO.readlines('/proc/stat') .lazy .find { |sp| sp.start_with?('cpu ') } .split .map(&:to_i)[1..-1] values = CPU_FIELDS.each_with_index.each_with_object({}) do |(key, i), v| v[key] = stat[i] || 0 end @total = values[:user] + values[:nice] + values[:system] + values[:idle] + values[:iowait] + values[:irq] + values[:softirq] + values[:steal] @usage = @total - (values[:idle] + values[:iowait]) self end end UTIME_POS = 13 STIME_POS = 14 VSIZE_POS = 22 RSS_POS = 23 # @api private class ProcSelfStat attr_reader :total, :vsize, :rss def read! stat = IO.readlines('/proc/self/stat') .lazy .first .split .map(&:to_i) @total = stat[UTIME_POS] + stat[STIME_POS] @vsize = stat[VSIZE_POS] @rss = stat[RSS_POS] self end end # @api private class Meminfo attr_reader :total, :available, :page_size # rubocop:disable Metrics/PerceivedComplexity # rubocop:disable Metrics/CyclomaticComplexity def read! # rubocop:disable Style/RescueModifier @page_size = `getconf PAGESIZE`.chomp.to_i rescue 4096 # rubocop:enable Style/RescueModifier info = IO.readlines('/proc/meminfo') .lazy .each_with_object({}) do |line, hsh| if line.start_with?('MemTotal:') hsh[:total] = line.split[1].to_i * 1024 elsif line.start_with?('MemAvailable:') hsh[:available] = line.split[1].to_i * 1024 elsif line.start_with?('MemFree:') hsh[:free] = line.split[1].to_i * 1024 elsif line.start_with?('Buffers:') hsh[:buffers] = line.split[1].to_i * 1024 elsif line.start_with?('Cached:') hsh[:cached] = line.split[1].to_i * 1024 end break hsh if hsh[:total] && hsh[:available] end @total = info[:total] @available = info[:available] || info[:free] + info[:buffers] + info[:cached] self end # rubocop:enable Metrics/CyclomaticComplexity # rubocop:enable Metrics/PerceivedComplexity end end end end end