health_status 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .*.swp
19
+ vendor/bundle
20
+ *.db
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in health_status.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 riywo
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # HealthStatus
2
+
3
+ API server to store and visualize applications' health status.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'health_status'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install health_status
18
+
19
+ ## Usage
20
+
21
+ $ health_status_server -d /path/to/sqlite.db
22
+
23
+ $ curl -v -d "status=1" localhost:5678/service1/application1/metric1
24
+ $ curl -v -d "status=2" localhost:5678/service1/application1/metric2
25
+
26
+ ## Contributing
27
+
28
+ 1. Fork it
29
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
30
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
31
+ 4. Push to the branch (`git push origin my-new-feature`)
32
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'sinatra/activerecord/rake'
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.expand_path("../../lib", __FILE__)
3
+ begin
4
+ require 'vegas'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'vegas'
8
+ end
9
+ require "health_status/app"
10
+ require "health_status/model"
11
+
12
+ Vegas::Runner.new(HealthStatus::App, 'health_status_server') do |runner, opts, app|
13
+ opts.on("-d", "--database DATABASE_FILE", "path/to/sqlite.db") do |database|
14
+ runner.logger.info "Using database #{database}"
15
+ HealthStatus::App.database_path = database
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ class HealthStatusMigration < ActiveRecord::Migration
2
+ def up
3
+ create_table :applications do |t|
4
+ t.string :name, :null => false
5
+ t.integer :status, :null => false
6
+ t.datetime :datetime, :null => false
7
+ t.datetime :saved_at, :null => false
8
+ end
9
+ add_index :applications, [ :name ], :unique => true
10
+
11
+ create_table :half_hour_statuses do |t|
12
+ t.integer :application_id, :null => false
13
+ t.integer :status, :null => false
14
+ t.datetime :datetime, :null => false
15
+ t.datetime :saved_at, :null => false
16
+ end
17
+ add_index :half_hour_statuses, [:application_id]
18
+ end
19
+
20
+ def down
21
+ end
22
+ end
@@ -0,0 +1,9 @@
1
+ class RemoveDatetime < ActiveRecord::Migration
2
+ def up
3
+ remove_column(:applications, :datetime)
4
+ end
5
+
6
+ def down
7
+ add_column(:applications, :datetime, :datetime, { :null => false })
8
+ end
9
+ end
@@ -0,0 +1,64 @@
1
+ class AddServiceMetric < ActiveRecord::Migration
2
+ def up
3
+ rename_table :applications, :v1_applications
4
+ rename_table :half_hour_statuses, :v1_half_hour_statuses
5
+
6
+ create_table :services do |t|
7
+ t.string :name, :null => false
8
+ t.integer :status, :null => false
9
+ t.datetime :saved_at, :null => false
10
+ end
11
+ add_index :services, [ :name ], :unique => true
12
+
13
+ create_table :service_half_hour_statuses do |t|
14
+ t.integer :service_id, :null => false
15
+ t.integer :status, :null => false
16
+ t.datetime :datetime, :null => false
17
+ t.datetime :saved_at, :null => false
18
+ end
19
+ add_index :service_half_hour_statuses, [ :service_id ]
20
+
21
+ create_table :applications do |t|
22
+ t.integer :service_id, :null => false
23
+ t.string :name, :null => false
24
+ t.integer :status, :null => false
25
+ t.datetime :saved_at, :null => false
26
+ end
27
+ add_index :applications, [ :service_id, :name ], :unique => true
28
+
29
+ create_table :application_half_hour_statuses do |t|
30
+ t.integer :application_id, :null => false
31
+ t.integer :status, :null => false
32
+ t.datetime :datetime, :null => false
33
+ t.datetime :saved_at, :null => false
34
+ end
35
+ add_index :application_half_hour_statuses, [ :application_id ]
36
+
37
+ create_table :metrics do |t|
38
+ t.integer :application_id, :null => false
39
+ t.string :name, :null => false
40
+ t.integer :status, :null => false
41
+ t.datetime :saved_at, :null => false
42
+ end
43
+ add_index :metrics, [ :application_id, :name ], :unique => true
44
+
45
+ create_table :metric_half_hour_statuses do |t|
46
+ t.integer :metric_id, :null => false
47
+ t.integer :status, :null => false
48
+ t.datetime :datetime, :null => false
49
+ t.datetime :saved_at, :null => false
50
+ end
51
+ add_index :metric_half_hour_statuses, [ :metric_id ]
52
+ end
53
+
54
+ def down
55
+ drop_table :services
56
+ drop_table :service_half_hour_statuses
57
+ drop_table :applications
58
+ drop_table :application_half_hour_statuses
59
+ drop_table :metrics
60
+ drop_table :metric_half_hour_statuses
61
+ rename_table :v1_applications, :applications
62
+ rename_table :v1_half_hour_statuses, :half_hour_statuses
63
+ end
64
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'health_status/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "health_status"
8
+ spec.version = HealthStatus::VERSION
9
+ spec.authors = ["Ryosuke IWANAGA"]
10
+ spec.email = ["riywo.jp@gmail.com"]
11
+ spec.description = %q{Health status API server}
12
+ spec.summary = %q{API server to store and visualize applications' health status}
13
+ spec.homepage = "https://github.com/riywo/health_status"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'sinatra'
22
+ spec.add_dependency 'sinatra-contrib'
23
+ spec.add_dependency 'activerecord'
24
+ spec.add_dependency 'sinatra-activerecord'
25
+ spec.add_dependency 'sqlite3'
26
+ spec.add_dependency 'vegas'
27
+
28
+ spec.add_development_dependency "bundler", "~> 1.3"
29
+ spec.add_development_dependency 'tapp'
30
+ spec.add_development_dependency 'rake'
31
+ end
@@ -0,0 +1,5 @@
1
+ require "health_status/version"
2
+
3
+ module HealthStatus
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,83 @@
1
+ require "health_status"
2
+ require "health_status/model"
3
+ require "health_status/web"
4
+ require "sinatra/base"
5
+ require "sinatra/activerecord"
6
+ require "sinatra/cookies"
7
+
8
+ class HealthStatus::App < Sinatra::Base
9
+
10
+ class << self
11
+ attr_accessor :database_path
12
+ end
13
+
14
+ helpers Sinatra::Cookies, ERB::Util
15
+ register Sinatra::ActiveRecordExtension
16
+
17
+ set :public_folder, File.expand_path("../../../public", __FILE__)
18
+ set :views, File.expand_path("../../../views", __FILE__)
19
+
20
+ before do
21
+ settings.database = "sqlite3:///#{HealthStatus::App.database_path}"
22
+ ActiveRecord::Base.logger = nil
23
+ HealthStatus::Model::Migrate.migrate
24
+ end
25
+
26
+ get '/' do
27
+ @timezone = params["timezone"] || cookies[:timezone] || HealthStatus::Web.system_timezone
28
+ @zones = HealthStatus::Web.timezones
29
+ erb :index
30
+ end
31
+
32
+ get '/api/v2/' do
33
+ HealthStatus::Model::Service.readonly.fetch_all_info().to_json
34
+ end
35
+
36
+ get '/api/v2/:service' do |service_name|
37
+ args = {}
38
+ args[:end_time] = Time.now.in_time_zone(params["timezone"])
39
+ args[:with_status] = true
40
+ service = HealthStatus::Model::Service.find_by_name(service_name)
41
+ service.fetch_info(args).to_json
42
+ end
43
+
44
+ get '/api/v2/:service/:application' do |service_name, application_name|
45
+ Time.zone = params["timezone"]
46
+ args = {}
47
+ args[:end_time] = Time.now.in_time_zone(params["timezone"])
48
+ args[:with_status] = true
49
+ application = HealthStatus::Model::Service.find_by_name(service_name).applications.find_by_name(application_name)
50
+ application.fetch_info(args).to_json
51
+ end
52
+
53
+ get '/api/v2/:service/:application/:metric' do |service_name, application_name, metric_name|
54
+ Time.zone = params["timezone"]
55
+ args = {}
56
+ args[:end_time] = Time.now.in_time_zone(params["timezone"])
57
+ args[:with_status] = true
58
+ metric = HealthStatus::Model::Service.find_by_name(service_name).applications.find_by_name(application_name).metrics.find_by_name(metric_name)
59
+ metric.fetch_info(args).to_json
60
+ end
61
+
62
+ post '/api/v2/:service/:application/:metric' do |service_name, application_name, metric_name|
63
+ raise unless params.has_key? "status"
64
+ HealthStatus::Model::Service.save_metric(service_name, application_name, metric_name, params["status"])
65
+ "OK"
66
+ end
67
+
68
+ delete '/api/v2/:service' do |service_name|
69
+ HealthStatus::Model::Service.find_by_name(service_name).destroy
70
+ "OK"
71
+ end
72
+
73
+ delete '/api/v2/:service/:application' do |service_name, application_name|
74
+ HealthStatus::Model::Service.find_by_name(service_name).applications.find_by_name(application_name).destroy
75
+ "OK"
76
+ end
77
+
78
+ delete '/api/v2/:service/:application/:metric' do |service_name, application_name, metric_name|
79
+ HealthStatus::Model::Service.find_by_name(service_name).applications.find_by_name(application_name).metrics.find_by_name(metric_name).destroy
80
+ "OK"
81
+ end
82
+
83
+ end
@@ -0,0 +1,284 @@
1
+ require "health_status"
2
+ require 'sinatra/activerecord'
3
+ require "sinatra/activerecord/rake"
4
+
5
+ class HealthStatus::Model
6
+
7
+ module AggregateStatus
8
+ @@default_timezone = :utc
9
+ @@half_hour = 30 * 60
10
+ @@hour = 60 * 60
11
+ @@day = 24 * @@hour
12
+
13
+ def fetch_current_status
14
+ if saved_at < floor_half_hour(Time.now.utc)
15
+ nil
16
+ else
17
+ status
18
+ end
19
+ end
20
+
21
+ def fetch_saved_at(args)
22
+ end_time = args[:end_time]
23
+ end_time ||= Time.now
24
+ Time.at(saved_at.to_i).localtime(end_time.utc_offset)
25
+ end
26
+
27
+ def fetch_status_with_interval(interval, args = {})
28
+ validate_args = validate_fetch_status(interval, args)
29
+ interval_status = []
30
+ time = validate_args[:start_time]
31
+ while time <= validate_args[:end_time]
32
+ data = {
33
+ :datetime => time,
34
+ :status => self_half_hour_statuses.where(:datetime => time..(time + interval - 1)).maximum(:status),
35
+ }
36
+ interval_status << data
37
+ time += interval
38
+ end
39
+ interval_status
40
+ end
41
+
42
+ def fetch_hourly_status(args = {})
43
+ fetch_status_with_interval(@@hour, args)
44
+ end
45
+
46
+ def fetch_daily_status(args = {})
47
+ fetch_status_with_interval(@@day, args)
48
+ end
49
+
50
+ def fetch_info(args = {})
51
+ depth = args[:depth].nil? ? 0 : args[:depth]
52
+ data = {
53
+ :id => id,
54
+ :name => name,
55
+ :current_status => fetch_current_status,
56
+ :saved_at => fetch_saved_at(args),
57
+ }
58
+
59
+ if args[:with_status]
60
+ data[:hourly_status] = fetch_hourly_status(args)
61
+ data[:daily_status] = fetch_daily_status(args)
62
+ end
63
+
64
+ if args[:with_id]
65
+ data[:service_id] = service.id if respond_to? :service
66
+ if respond_to? :application
67
+ data[:service_id] = application.service.id
68
+ data[:application_id] = application.id
69
+ end
70
+ end
71
+
72
+ if children.respond_to? :map and depth > 0
73
+ data[children_name] = children.map do |child|
74
+ args[:depth] = depth - 1
75
+ child.fetch_info(args)
76
+ end
77
+ end
78
+ data
79
+ end
80
+
81
+ def save_status(new_status)
82
+ children_status = children.maximum(:status)
83
+ if !children_status.nil? and children_status <= new_status.to_i
84
+ self.status = new_status.to_i
85
+ end
86
+ save!
87
+ end
88
+
89
+ private
90
+
91
+ def validate_fetch_status(interval, args)
92
+ end_time = args[:end_time]
93
+ end_time ||= Time.now
94
+ start_time = args[:start_time]
95
+ start_time ||= end_time - (interval == @@day ? 7 * @@day : @@day)
96
+
97
+ offset = start_time.utc_offset
98
+ raise if offset != end_time.utc_offset
99
+ raise if offset % @@half_hour != 0
100
+
101
+ start_time = floor(start_time, interval)
102
+ end_time = floor(end_time, interval)
103
+ { :start_time => start_time, :end_time => end_time }
104
+ end
105
+
106
+ def floor_half_hour(datetime)
107
+ floor(datetime, @@half_hour)
108
+ end
109
+
110
+ def floor_hour(datetime)
111
+ floor(datetime, @@hour)
112
+ end
113
+
114
+ def floor_day(datetime)
115
+ floor(datetime, @@day)
116
+ end
117
+
118
+ def floor(datetime, seconds)
119
+ offset = datetime.utc_offset
120
+ Time.at(datetime.to_i - ((datetime.to_i + offset) % seconds).floor).localtime(offset)
121
+ end
122
+
123
+ def update_half_hour_status
124
+ half_hour = floor_half_hour(self.saved_at)
125
+ current = self.self_half_hour_statuses.find(:all, :conditions => { :datetime => half_hour } ).first
126
+ if current
127
+ current.status = status if current.status < status
128
+ current.saved_at = self.saved_at
129
+ current.save
130
+ else
131
+ self_half_hour_statuses.create(
132
+ :status => self.status,
133
+ :datetime => half_hour,
134
+ :saved_at => self.saved_at,
135
+ )
136
+ end
137
+ end
138
+ end
139
+
140
+ class Service < ActiveRecord::Base
141
+ include AggregateStatus
142
+
143
+ has_many :applications, :dependent => :destroy
144
+ has_many :service_half_hour_statuses, :order => "datetime ASC", :dependent => :destroy
145
+
146
+ before_validation do self.saved_at = Time.now.utc end
147
+ after_save :update_half_hour_status
148
+ validates_inclusion_of :status, :in => 1..3
149
+
150
+ def self.fetch_all_info(args = {})
151
+ all.map do |service|
152
+ args[:depth] = 2
153
+ service.fetch_info(args)
154
+ end
155
+ end
156
+
157
+ def self.save_metric(service_name, application_name, metric_name, new_status)
158
+ ActiveRecord::Base.transaction do
159
+ service = find_or_initialize_by_name(service_name)
160
+ application = service.applications.find_or_initialize_by_name(application_name)
161
+ metric = application.metrics.find_or_initialize_by_name(metric_name)
162
+
163
+ if service.new_record?
164
+ service.status = new_status
165
+ application.status = new_status
166
+ elsif application.new_record?
167
+ application.status = new_status
168
+ end
169
+ metric.status = new_status
170
+ service.save!
171
+ application.save!
172
+ metric.save!
173
+
174
+ application.save_status(new_status)
175
+ service.save_status(new_status)
176
+ end
177
+ end
178
+
179
+ def self_half_hour_statuses
180
+ service_half_hour_statuses
181
+ end
182
+
183
+ def children_name
184
+ :applications
185
+ end
186
+
187
+ def children
188
+ applications
189
+ end
190
+ end
191
+
192
+ class Application < ActiveRecord::Base
193
+ include AggregateStatus
194
+
195
+ belongs_to :service
196
+ has_many :metrics, :dependent => :destroy
197
+ has_many :application_half_hour_statuses, :order => "datetime ASC", :dependent => :destroy
198
+
199
+ before_validation do self.saved_at = Time.now.utc end
200
+ after_save :update_half_hour_status
201
+ validates_inclusion_of :status, :in => 1..3
202
+
203
+ def self_half_hour_statuses
204
+ application_half_hour_statuses
205
+ end
206
+
207
+ def children_name
208
+ :metrics
209
+ end
210
+
211
+ def children
212
+ metrics
213
+ end
214
+ end
215
+
216
+ class Metric < ActiveRecord::Base
217
+ include AggregateStatus
218
+
219
+ belongs_to :application
220
+ has_many :metric_half_hour_statuses, :order => "datetime ASC", :dependent => :destroy
221
+
222
+ before_validation do self.saved_at = Time.now.utc end
223
+ after_save :update_half_hour_status
224
+ validates_inclusion_of :status, :in => 1..3
225
+
226
+ def self_half_hour_statuses
227
+ metric_half_hour_statuses
228
+ end
229
+
230
+ def children
231
+ self
232
+ end
233
+
234
+ end
235
+
236
+ class ServiceHalfHourStatus < ActiveRecord::Base
237
+ @@default_timezone = :utc
238
+ belongs_to :service
239
+ validates_inclusion_of :status, :in => 1..3
240
+ end
241
+
242
+ class ApplicationHalfHourStatus < ActiveRecord::Base
243
+ @@default_timezone = :utc
244
+ belongs_to :application
245
+ validates_inclusion_of :status, :in => 1..3
246
+ end
247
+
248
+ class MetricHalfHourStatus < ActiveRecord::Base
249
+ @@default_timezone = :utc
250
+ belongs_to :metric
251
+ validates_inclusion_of :status, :in => 1..3
252
+ end
253
+
254
+ module Sort
255
+ def self.names_sort_by_status
256
+ all.sort do |a, b|
257
+ a_status = a.fetch_current_status
258
+ b_status = b.fetch_current_status
259
+ if !a_status and !b_status
260
+ a.name <=> b.name
261
+ elsif !a_status
262
+ 1
263
+ elsif !b_status
264
+ -1
265
+ else
266
+ (b.fetch_current_status <=> a.fetch_current_status).nonzero? or
267
+ a.name <=> b.name
268
+ end
269
+ end.map { |app| app.name }
270
+ end
271
+ end
272
+
273
+ module Migrate
274
+ extend Sinatra::ActiveRecordTasks
275
+ extend self
276
+
277
+ private
278
+
279
+ def migrations_dir
280
+ File.expand_path("../../../db/migrate", __FILE__)
281
+ end
282
+ end
283
+
284
+ end