Monitor the load average of the machine and display it in the api

This PR add new information in the /_node/stats api and will return the
load average of the machine in the following formats depending of the
platforms that logstash is running on:

**Linux**
```json
{
    "cpu" : {
      "percent" : 26,
      "load_average" : {
        "1m" : 2.826171875,
        "5m": 1.8261718,
        "15m": 1.56566
      }
    }
}
```

**MacOS and other platform that the OperatingMXBean understand**
```json
{
    "cpu" : {
      "percent" : 26,
      "load_average" : {
        "1m" : 2.826171875,
      }
    }
}
```

Load average is not available on Windows

Fixes: #6214

Fixes #6240
This commit is contained in:
Pier-Hugues Pellerin 2016-11-11 15:37:56 -05:00
parent 135271b37e
commit 80c2019dcf
6 changed files with 240 additions and 4 deletions

View file

@ -27,7 +27,7 @@ module LogStash
:peak_open_file_descriptors,
:max_file_descriptors,
[:mem, [:total_virtual_in_bytes]],
[:cpu, [:total_in_millis, :percent]]
[:cpu, [:total_in_millis, :percent, :load_average]]
)
end

View file

@ -59,6 +59,10 @@ module LogStash
LOGSTASH_CORE = ::File.expand_path(::File.join(::File.dirname(__FILE__), "..", ".."))
LOGSTASH_ENV = (ENV["LS_ENV"] || 'production').to_s.freeze
LINUX_OS_RE = /linux/
WINDOW_OS_RE = /mswin|msys|mingw|cygwin|bccwin|wince|emc/
MACOS_OS_RE = /darwin/
def env
LOGSTASH_ENV
end
@ -121,7 +125,11 @@ module LogStash
end
def windows?
::Gem.win_platform?
RbConfig::CONFIG['host_os'] =~ WINDOW_OS_RE
end
def linux?
RbConfig::CONFIG['host_os'] =~ LINUX_OS_RE
end
def locales_path(path)

View file

@ -1,6 +1,6 @@
# encoding: utf-8
require "logstash/instrument/periodic_poller/base"
require "logstash/environment"
require "jrmonitor"
require "set"
@ -33,11 +33,57 @@ module LogStash module Instrument module PeriodicPoller
end
end
class LoadAverage
class Windows
def self.get
nil
end
end
class Linux
LOAD_AVG_FILE = "/proc/loadavg"
TOKEN_SEPARATOR = " "
def self.get
load_average = ::File.read(LOAD_AVG_FILE).chomp.split(TOKEN_SEPARATOR)
{
:"1m" => load_average[0].to_f,
:"5m" => load_average[1].to_f,
:"15m" => load_average[2].to_f
}
end
end
class Other
def self.get()
load_average_1m = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage()
return nil if load_average_1m.nil?
{
:"1m" => load_average_1m
}
end
end
def self.create
if LogStash::Environment.windows?
Windows
elsif LogStash::Environment.linux?
Linux
else
Other
end
end
end
attr_reader :metric
def initialize(metric, options = {})
super(metric, options)
@metric = metric
@load_average = LoadAverage.create
end
def collect
@ -47,6 +93,7 @@ module LogStash module Instrument module PeriodicPoller
collect_threads_metrics
collect_process_metrics
collect_gc_stats
collect_load_average
end
private
@ -97,6 +144,18 @@ module LogStash module Instrument module PeriodicPoller
metric.gauge(cpu_path, :total_in_millis, cpu_metrics["total_in_millis"])
metric.gauge(path + [:mem], :total_virtual_in_bytes, process_metrics["mem"]["total_virtual_in_bytes"])
end
def collect_load_average
begin
load_average = @load_average.get
rescue => e
logger.debug("Can't retrieve load average", :exception => e.class.name, :message => e.message)
load_average = nil
end
metric.gauge([:jvm, :process, :cpu], :load_average, load_average) unless load_average.nil?
end
def collect_jvm_metrics(data)

View file

@ -69,7 +69,8 @@ describe LogStash::Api::Modules::NodeStats do
},
"cpu"=>{
"total_in_millis"=>Numeric,
"percent"=>Numeric
"percent"=>Numeric,
"load_average" => { "1m" => Numeric }
}
},
"pipeline" => {

View file

@ -53,4 +53,42 @@ describe LogStash::Environment do
expect($LOAD_PATH).to include(path)
end
end
describe "OS detection" do
windows_host_os = %w(bccwin cygwin mingw mswin wince)
linux_host_os = %w(linux)
context "windows" do
windows_host_os.each do |host|
it "#{host} returns true" do
expect(RbConfig::CONFIG).to receive(:[]).with("host_os").and_return(host)
expect(LogStash::Environment.windows?).to be_truthy
end
end
linux_host_os.each do |host|
it "#{host} returns false" do
expect(RbConfig::CONFIG).to receive(:[]).with("host_os").and_return(host)
expect(LogStash::Environment.windows?).to be_falsey
end
end
end
context "Linux" do
windows_host_os.each do |host|
it "#{host} returns true" do
expect(RbConfig::CONFIG).to receive(:[]).with("host_os").and_return(host)
expect(LogStash::Environment.linux?).to be_falsey
end
end
linux_host_os.each do |host|
it "#{host} returns false" do
expect(RbConfig::CONFIG).to receive(:[]).with("host_os").and_return(host)
expect(LogStash::Environment.linux?).to be_truthy
end
end
end
end
end

View file

@ -2,6 +2,7 @@
require "spec_helper"
require "logstash/instrument/periodic_poller/jvm"
require "logstash/instrument/collector"
require "logstash/environment"
describe LogStash::Instrument::PeriodicPoller::JVM::GarbageCollectorName do
subject { LogStash::Instrument::PeriodicPoller::JVM::GarbageCollectorName }
@ -27,6 +28,97 @@ describe LogStash::Instrument::PeriodicPoller::JVM::GarbageCollectorName do
end
end
describe LogStash::Instrument::PeriodicPoller::JVM::LoadAverage do
subject { described_class.create }
context "on mocked system" do
context "on Linux" do
before do
expect(LogStash::Environment).to receive(:windows?).and_return(false)
expect(LogStash::Environment).to receive(:linux?).and_return(true)
end
context "when it can read the file" do
let(:proc_loadavg) { "0.00 0.01 0.05 3/180 29727" }
before do
expect(::File).to receive(:read).with("/proc/loadavg").and_return(proc_loadavg)
end
it "return the 3 load average from `/proc/loadavg`" do
avg_1m, avg_5m, avg_15m = proc_loadavg.chomp.split(" ")
expect(subject.get).to include(:"1m" => avg_1m.to_f, :"5m" => avg_5m.to_f, :"15m" => avg_15m.to_f)
end
end
end
context "on windows" do
before do
expect(LogStash::Environment).to receive(:windows?).and_return(true)
end
it "Xreturns nil" do
expect(subject.get).to be_nil
end
end
context "on other" do
before do
expect(LogStash::Environment).to receive(:windows?).and_return(false)
expect(LogStash::Environment).to receive(:linux?).and_return(false)
end
context "when 'OperatingSystemMXBean.getSystemLoadAverage' return something" do
let(:load_avg) { 5 }
before do
expect(ManagementFactory).to receive(:getOperatingSystemMXBean).and_return(double("OperatingSystemMXBean", :getSystemLoadAverage => load_avg))
end
it "returns the value" do
expect(subject.get).to include(:"1m" => 5)
end
end
context "when 'OperatingSystemMXBean.getSystemLoadAverage' doesn't return anything" do
before do
expect(ManagementFactory).to receive(:getOperatingSystemMXBean).and_return(double("OperatingSystemMXBean", :getSystemLoadAverage => nil))
end
it "returns nothing" do
expect(subject.get).to be_nil
end
end
end
end
# Since we are running this on macos and linux I think it make sense to have real test
# insteadof only mock
context "real system" do
if LogStash::Environment.linux?
context "Linux" do
it "returns the load avg" do
expect(subject.get).to include(:"1m" => a_kind_of(Numeric), :"5m" => a_kind_of(Numeric), :"15m" => a_kind_of(Numeric))
end
end
elsif LogStash::Environment.windows?
context "window" do
it "returns nothing" do
expect(subject.get).to be_nil
end
end
else
context "Other" do
it "returns 1m only" do
expect(subject.get).to include(:"1m" => a_kind_of(Numeric))
end
end
end
end
end
describe LogStash::Instrument::PeriodicPoller::JVM do
let(:metric) { LogStash::Instrument::Metric.new(LogStash::Instrument::Collector.new) }
let(:options) { {} }
@ -36,6 +128,22 @@ describe LogStash::Instrument::PeriodicPoller::JVM do
expect { jvm }.not_to raise_error
end
describe "load average" do
context "on linux" do
context "when an exception occur reading the file" do
before do
expect(LogStash::Environment).to receive(:windows?).and_return(false)
expect(LogStash::Environment).to receive(:linux?).and_return(true)
expect(::File).to receive(:read).with("/proc/loadavg").and_raise("Didnt work out so well")
end
it "doesn't raise an exception" do
expect { subject.collect }.not_to raise_error
end
end
end
end
describe "collections" do
subject(:collection) { jvm.collect }
it "should run cleanly" do
@ -70,6 +178,28 @@ describe LogStash::Instrument::PeriodicPoller::JVM do
expect(mval(*path)).to be_a(Numeric)
end
end
context "real system" do
if LogStash::Environment.linux?
context "Linux" do
it "returns the load avg" do
expect(subject[:process][:cpu][:load_average].value).to include(:"1m" => a_kind_of(Numeric), :"5m" => a_kind_of(Numeric), :"15m" => a_kind_of(Numeric))
end
end
elsif LogStash::Environment.windows?
context "Window" do
it "returns nothing" do
expect(subject[:process][:cpu].has_key?(:load_average)).to be_falsey
end
end
else
context "Other" do
it "returns 1m only" do
expect(subject[:process][:cpu][:load_average].value).to include(:"1m" => a_kind_of(Numeric))
end
end
end
end
end
end
end