Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ThomasMeschke committed Sep 10, 2018
0 parents commit 883f4b5
Show file tree
Hide file tree
Showing 15 changed files with 1,165 additions and 0 deletions.
52 changes: 52 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
*.gem
*.rbc
/.config
/coverage/
/InstalledFiles
/pkg/
/spec/reports/
/spec/examples.txt
/test/tmp/
/test/version_tmp/
/tmp/

# Used by dotenv library to load environment variables.
# .env

## Specific to RubyMotion:
.dat*
.repl_history
build/
*.bridgesupport
build-iPhoneOS/
build-iPhoneSimulator/

## Specific to RubyMotion (use of CocoaPods):
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# vendor/Pods/

## Documentation cache and generated files:
/.yardoc/
/_yardoc/
/doc/
/rdoc/

## Environment normalization:
/.bundle/
/vendor/bundle
/lib/bundler/man/

# for a library or gem, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# Gemfile.lock
# .ruby-version
# .ruby-gemset

# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
.rvmrc

config/config\.json
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Service-Monitor
=

The Service-Monitor tool provides the functionality to continuously watch the response time of a set of specified web services.

How to use
-

Take a look into the sample_config.json.

You will find a working configuration with a bunch of comments, explaining what you can configure.

Save your own configuration as 'config.json'. The existence of this file will cause the program to use it.

Run the program using 'ruby main.rb', and navigate your browser to the server address and port you specified in your config.json file.

You will see a webpage showing boxes for each service specified, colorized with respect to how short or long the service took to respond.

36 changes: 36 additions & 0 deletions app/clock_work.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

require_relative 'stop_watch'

# Offers the functionality to get a 'tick' after every <interval_ms> milliseconds.
# 'start' takes a lambda as callback
class ClockWork
def initialize(interval_ms)
@interval_ms = interval_ms
@running = false
end

def start(&callback)
@running = true
@thread = Thread.new {run(&callback)}
end

def run(&callback)
@sw ||= StopWatch.new
while @running do
execution_time_ms = @sw.measure {
callback.call
}
sleep_time_ms = @interval_ms - execution_time_ms
sleep(sleep_time_ms / 1000)
end
end

def stop
@running = false
@thread.kill
@thread.join
end

private :run
end
27 changes: 27 additions & 0 deletions app/config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

require 'json'

class Config

@look_for_file = 'config/config.json'
@fallback_file = 'config/sample_config.json'

def self.from_file(filename = nil)
filename ||= @fallback_file
json = File.read(filename)
from_json(json)
end

def self.from_json(json)
@settings ||= JSON.parse(json)
end

def self.[](element)
if @settings.nil?
config_file = File.exists?(@look_for_file) ? @look_for_file : nil
Config::from_file(config_file)
end
@settings[element]
end
end
24 changes: 24 additions & 0 deletions app/response_time.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

# Wrapping a given service response time
class ResponseTime
def initialize(time)
@time = time
end

def to_s
if @time.nil?
"N/A"
else
"#{@time}ms"
end
end

def to_i
if @time.nil?
Float::INFINITY
else
@time
end
end
end
67 changes: 67 additions & 0 deletions app/service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# frozen_string_literal: true

require 'net/http'
require_relative 'response_time'
require_relative 'stop_watch'

# Wrapping a specific service, offering the functionality to query it and measure its response time
class Service

attr_reader :name, :url, :path, :port, :last_response_time, :response_time_classification

def initialize(name, url, path = nil, port = nil, good_below_ms = nil, bad_above_ms = nil)
@name = name
@url = url
@path = path || '/'
@port = port || 80
@good_below_ms = good_below_ms || 200
@bad_above_ms = bad_above_ms || 500
@last_response_time = ResponseTime.new(nil)
end

def determine_response_time_async(&callback)
Thread.new {
determine_response_time
callback.call(self)
}
end

def determine_response_time
@sw ||= StopWatch.new
@sw.start
result = query
@sw.stop

if !result.nil?
result = @sw.diff
end
@last_response_time = ResponseTime.new(result)
end

def query
begin
http = Net::HTTP.new(@url, @port)
http.read_timeout = 2
http.open_timeout = 2

result = http.start() { |http|
http.get(@path)
}
rescue StandardError => err
result = nil
end
result
end

def response_time_classification
if @last_response_time.nil? || (@last_response_time.to_i > @bad_above_ms)
:bad
elsif (@last_response_time.to_i < @good_below_ms)
:good
else
:medium
end
end

private :query
end
32 changes: 32 additions & 0 deletions app/service_monitor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

require_relative 'clock_work'
require_relative 'service'

class ServiceMonitor

def initialize(service_collection)
@services = service_collection
end

def on_response_time_updated
lambda { |service|
puts "<< #{service.name} --> #{service.last_response_time} (#{service.response_time_classification})"
}
end

def on_clock_tick
lambda {
@services.each do |service|
puts ">> #{service.name}"
service.determine_response_time_async(&on_response_time_updated)
end
}
end

def watch(interval_seconds = nil)
interval_seconds = interval_seconds || 10
@clock ||= ClockWork.new(interval_seconds * 1000)
@clock.start(&on_clock_tick)
end
end
31 changes: 31 additions & 0 deletions app/stop_watch.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

require_relative 'time'

# Offers the functionality to measure timings of certain actions,
# either by hand using 'start' and 'stop' or by specifying a block to 'measure'
class StopWatch
def start
@start_time = Time::now
@end_time = nil
end

def stop
@end_time = Time::now
end

def diff
if !@start_time || !@end_time
raise 'StopWatch has not been started or not been stopped!'
else
@end_time.to_ms() - @start_time.to_ms()
end
end

def measure(&block)
start
block.call
stop
diff
end
end
15 changes: 15 additions & 0 deletions app/template_engine.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

require 'erb'

# Simple wrapper around ERB
class TemplateEngine
def initialize(template_file)
@template = File.read(template_file)
@erb = ERB.new(@template)
end

def render(binding)
@erb.result(binding)
end
end
8 changes: 8 additions & 0 deletions app/time.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

# This class extends the ruby std lib Time class
class Time
def to_ms
(self.to_f * 1000.0).to_i
end
end
42 changes: 42 additions & 0 deletions app/web_server.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

require 'socket'

# Offers the functionality to receive tcp requests and calls a lambda which should handle them.
# Cann be called blocking(listen) and non-blocking(listen_async)
class WebServer
def initialize(addr, port)
@addr = addr
@port = port
@running = false
end

def listen(&callback)
@listener = TCPServer.new(@addr, @port)
@running = true
run(&callback)
end

def listen_async(&callback)
@thread = Thread.new {listen(&callback)}
end

def run(&callback)
while @running do
session = @listener.accept
Thread.new {
callback.call(session)
}
end
end

def stop
if !@thread.nil?
@running = false
@thread.kill
@thread.join
end
end

private :run
end
22 changes: 22 additions & 0 deletions config/sample_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"server-addr": "0.0.0.0", // mandatory; specify the local address on which the web server should be reachable. "0.0.0.0" means any address.
"server-port": 8080, // mandatory; specify the local port the web server should bind to.
"template-file": "templates//template.rhtml", // mandatory; specify the file which contains the ERB template for the web page.
"service-monitor": {
"watch-intervall-seconds": 5 // optional; specify the amount of seconds between two response time calculations. Defaults to 10.
},
"services":[
{
"name": "Is it Friday yet", // mandatory; specify the name that should be shown on the web page.
"url": "www.isitfridayyet.net", // mandatory; specify the url under which the service can be reached.
"path": "/", // optional; specify the request path that should be added to the url. Defaults to "/".
"port": 80, // optional; specify the port under which the service is reachable. Defaults to 80.
"good_below_ms": 100, // optional; specify the amount of milliseconds that separates a good from a medium connection. Defaults to 200.
"bad_above_ms": 300 // optional; specify the amount of milliseconds that separates a medium from a bad connection. Defaults to 500.
},
{ // this is what a minimal service config looks like
"name": "Google",
"url": "www.google.com"
}
]
}
Loading

0 comments on commit 883f4b5

Please sign in to comment.