snowman-io 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/README.md +4 -6
  4. data/bin/snowman +1 -1
  5. data/lib/config/mongoid.yml +5 -0
  6. data/lib/snowman-io.rb +62 -30
  7. data/lib/snowman-io/aggregate.rb +97 -0
  8. data/lib/snowman-io/api.rb +33 -65
  9. data/lib/snowman-io/api/agent.rb +27 -0
  10. data/lib/snowman-io/api/apps.rb +45 -0
  11. data/lib/snowman-io/api/auth_helpers.rb +76 -0
  12. data/lib/snowman-io/api/checks.rb +59 -0
  13. data/lib/snowman-io/api/extra/meteor.rb +45 -0
  14. data/lib/snowman-io/api/fridge.rb +14 -0
  15. data/lib/snowman-io/api/info.rb +30 -0
  16. data/lib/snowman-io/api/metrics.rb +115 -0
  17. data/lib/snowman-io/api/users.rb +231 -0
  18. data/lib/snowman-io/cli.rb +69 -0
  19. data/lib/snowman-io/launcher.rb +12 -11
  20. data/lib/snowman-io/loop/check_processor.rb +29 -0
  21. data/lib/snowman-io/loop/checks.rb +59 -0
  22. data/lib/snowman-io/loop/main.rb +43 -0
  23. data/lib/snowman-io/loop/ping.rb +25 -0
  24. data/lib/snowman-io/migration.rb +79 -0
  25. data/lib/snowman-io/models/aggregation.rb +15 -0
  26. data/lib/snowman-io/models/app.rb +61 -0
  27. data/lib/snowman-io/models/check.rb +48 -0
  28. data/lib/snowman-io/models/concerns/tokenable.rb +15 -0
  29. data/lib/snowman-io/models/data_point.rb +9 -0
  30. data/lib/snowman-io/models/deleted.rb +9 -0
  31. data/lib/snowman-io/models/following.rb +8 -0
  32. data/lib/snowman-io/models/metric.rb +36 -0
  33. data/lib/snowman-io/models/setting.rb +24 -0
  34. data/lib/snowman-io/models/user.rb +73 -0
  35. data/lib/snowman-io/options.rb +11 -3
  36. data/lib/snowman-io/report_mailer.rb +88 -0
  37. data/lib/snowman-io/reports.rb +16 -0
  38. data/lib/snowman-io/ui/AUTO_GENERATED_FOLDER +2 -0
  39. data/lib/snowman-io/ui/assets/ui-0e39dafcb798020fb855e325931c8451.css +1 -0
  40. data/lib/snowman-io/ui/assets/ui-d30809d0ae0a003d841fa95a352d624b.js +9 -0
  41. data/lib/snowman-io/ui/assets/vendor-7edfd1432c1bbd806306d5583c75b1fc.css +5 -0
  42. data/lib/snowman-io/ui/assets/vendor-c22e2ccc87c9bc7609b95939c308bc7f.js +24 -0
  43. data/lib/snowman-io/{api/public/bootstrap → ui/bootstrap-3.3.1}/css/bootstrap-theme.css +0 -0
  44. data/lib/snowman-io/{api/public/bootstrap → ui/bootstrap-3.3.1}/css/bootstrap-theme.css.map +0 -0
  45. data/lib/snowman-io/{api/public/bootstrap → ui/bootstrap-3.3.1}/css/bootstrap-theme.min.css +0 -0
  46. data/lib/snowman-io/{api/public/bootstrap → ui/bootstrap-3.3.1}/css/bootstrap.css +0 -0
  47. data/lib/snowman-io/{api/public/bootstrap → ui/bootstrap-3.3.1}/css/bootstrap.css.map +0 -0
  48. data/lib/snowman-io/{api/public/bootstrap → ui/bootstrap-3.3.1}/css/bootstrap.min.css +0 -0
  49. data/lib/snowman-io/{api/public/bootstrap → ui/bootstrap-3.3.1}/fonts/glyphicons-halflings-regular.eot +0 -0
  50. data/lib/snowman-io/{api/public/bootstrap → ui/bootstrap-3.3.1}/fonts/glyphicons-halflings-regular.svg +0 -0
  51. data/lib/snowman-io/{api/public/bootstrap → ui/bootstrap-3.3.1}/fonts/glyphicons-halflings-regular.ttf +0 -0
  52. data/lib/snowman-io/{api/public/bootstrap → ui/bootstrap-3.3.1}/fonts/glyphicons-halflings-regular.woff +0 -0
  53. data/lib/snowman-io/{api/public/bootstrap → ui/bootstrap-3.3.1}/js/bootstrap.js +0 -0
  54. data/lib/snowman-io/{api/public/bootstrap → ui/bootstrap-3.3.1}/js/bootstrap.min.js +0 -0
  55. data/lib/snowman-io/{api/public/bootstrap → ui/bootstrap-3.3.1}/js/npm.js +0 -0
  56. data/lib/snowman-io/ui/crossdomain.xml +15 -0
  57. data/lib/snowman-io/ui/fonts/glyphicons-halflings-regular.eot +0 -0
  58. data/lib/snowman-io/ui/fonts/glyphicons-halflings-regular.svg +229 -0
  59. data/lib/snowman-io/ui/fonts/glyphicons-halflings-regular.ttf +0 -0
  60. data/lib/snowman-io/ui/fonts/glyphicons-halflings-regular.woff +0 -0
  61. data/lib/snowman-io/ui/index.html +175 -0
  62. data/lib/snowman-io/ui/robots.txt +2 -0
  63. data/lib/snowman-io/utils.rb +66 -0
  64. data/lib/snowman-io/version.rb +1 -1
  65. data/lib/snowman-io/views/layouts/custom.css +42 -0
  66. data/lib/snowman-io/views/layouts/main.html.erb +38 -0
  67. data/lib/snowman-io/views/layouts/styles.css +264 -0
  68. data/lib/snowman-io/views/layouts/transactional-email-templates-LICENSE +21 -0
  69. data/lib/snowman-io/views/report_mailer/check_triggered.html.erb +16 -0
  70. data/lib/snowman-io/views/report_mailer/checks/_human_last_value_limit.html.erb +2 -0
  71. data/lib/snowman-io/views/report_mailer/checks/_human_prev_day_datapoints_limit.html.erb +2 -0
  72. data/lib/snowman-io/views/report_mailer/daily_report.html.erb +40 -0
  73. data/lib/snowman-io/views/report_mailer/restore_password.html.erb +28 -0
  74. data/lib/snowman-io/views/report_mailer/send_invite.html.erb +32 -0
  75. data/lib/snowman-io/web.rb +28 -0
  76. data/lib/snowman-io/web_server.rb +107 -0
  77. data/snowman-io.gemspec +15 -5
  78. metadata +220 -49
  79. data/lib/snowman-io/api/public/README.md +0 -36
  80. data/lib/snowman-io/api/public/css/normalize.css +0 -406
  81. data/lib/snowman-io/api/public/css/style.css +0 -4
  82. data/lib/snowman-io/api/public/js/app.js +0 -13
  83. data/lib/snowman-io/api/public/js/libs/ember-1.8.1.js +0 -49740
  84. data/lib/snowman-io/api/public/js/libs/handlebars-v1.3.0.js +0 -2746
  85. data/lib/snowman-io/api/public/js/libs/jquery-1.10.2.js +0 -9789
  86. data/lib/snowman-io/api/public/tests/runner.css +0 -14
  87. data/lib/snowman-io/api/public/tests/runner.js +0 -13
  88. data/lib/snowman-io/api/public/tests/tests.js +0 -30
  89. data/lib/snowman-io/api/public/tests/vendor/qunit-1.12.0.css +0 -244
  90. data/lib/snowman-io/api/public/tests/vendor/qunit-1.12.0.js +0 -2212
  91. data/lib/snowman-io/api/views/index.erb +0 -26
  92. data/lib/snowman-io/api/views/layout.erb +0 -24
  93. data/lib/snowman-io/api/views/login.erb +0 -21
  94. data/lib/snowman-io/api/views/unpacking.erb +0 -21
  95. data/lib/snowman-io/check.rb +0 -49
  96. data/lib/snowman-io/check_result.rb +0 -15
  97. data/lib/snowman-io/checks/hosted_graphite.rb +0 -23
  98. data/lib/snowman-io/handler.rb +0 -27
  99. data/lib/snowman-io/notifiers/slack.rb +0 -76
  100. data/lib/snowman-io/processor.rb +0 -32
  101. data/lib/snowman-io/scheduler.rb +0 -42
@@ -0,0 +1,69 @@
1
+ require 'digest/sha1'
2
+
3
+ module SnowmanIO
4
+ # The command line interface for SnowmanIO.
5
+ class CLI
6
+ attr_reader :options
7
+
8
+ def self.run
9
+ options = Options.new.parse!(ARGV)
10
+ new(options).run
11
+ end
12
+
13
+ def initialize(options = {})
14
+ @options = options
15
+ end
16
+
17
+ def run
18
+ setup_logger
19
+
20
+ # Self-pipe for deferred signal-handling (http://cr.yp.to/docs/selfpipe.html)
21
+ self_read, self_write = IO.pipe
22
+
23
+ %w(INT TERM USR1 USR2 TTIN).each do |sig|
24
+ begin
25
+ trap sig do
26
+ self_write.puts(sig)
27
+ end
28
+ rescue ArgumentError
29
+ puts "Signal #{sig} not supported"
30
+ end
31
+ end
32
+
33
+ launcher = Launcher.new(options)
34
+
35
+ begin
36
+ launcher.start
37
+ while readable_io = IO.select([self_read])
38
+ signal = readable_io.first[0].gets.strip
39
+ handle_signal(signal)
40
+ end
41
+ rescue Interrupt
42
+ SnowmanIO.logger.info 'Shutting down'
43
+ launcher.stop
44
+ exit(0)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def handle_signal(sig)
51
+ SnowmanIO.logger.debug "Received #{sig} signal"
52
+ case sig
53
+ when 'TERM'
54
+ raise Interrupt
55
+ when 'INT'
56
+ raise Interrupt
57
+ end
58
+ end
59
+
60
+ def setup_logger
61
+ Celluloid.logger = (options[:verbose] ? SnowmanIO.logger : nil)
62
+ if options[:verbose]
63
+ SnowmanIO.logger.level = ::Logger::DEBUG
64
+ else
65
+ SnowmanIO.logger.level = ::Logger::INFO
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,24 +1,25 @@
1
- require 'snowman-io/scheduler'
2
- require 'snowman-io/handler'
3
-
4
1
  module SnowmanIO
5
2
  class Launcher
6
3
  include Celluloid
7
4
 
8
- attr_reader :scheduler, :handler
9
-
10
- def initialize(checks)
11
- @handler = Handler.new_link
12
- @scheduler = Scheduler.new_link(checks)
13
- @scheduler.handler = handler
5
+ def initialize(options)
6
+ @options = options
14
7
  end
15
8
 
16
9
  def start
17
- scheduler.async.start
10
+ Migration.new.migrate
11
+ app = Rack::Cascade.new [API::Root, Web]
12
+ @web_server = WebServer.supervise_as(:web_server, app, @options.slice(:port, :host, :verbose))
13
+ @ping = Loop::Ping.supervise_as(:ping)
14
+ @main = Loop::Main.supervise_as(:main)
15
+ @checks = Loop::Checks.supervise_as(:checks)
18
16
  end
19
17
 
20
18
  def stop
21
- #TODO
19
+ @checks.terminate
20
+ @main.terminate
21
+ @ping.terminate
22
+ @web_server.terminate # TODO: shutdown blocking?
22
23
  end
23
24
  end
24
25
  end
@@ -0,0 +1,29 @@
1
+ module SnowmanIO
2
+ module Loop
3
+ class CheckProcessor
4
+ def initialize(check)
5
+ @check = check
6
+ @metric = check.metric
7
+ end
8
+
9
+ def process
10
+ if @check.template == Check::TEMPLATE_LAST_VALUE_LIMIT
11
+ @check.cmp_fn.call(@metric.last_value, @check.value)
12
+
13
+ elsif @check.template == Check::TEMPLATE_PREV_DAY_DATAPOINTS_LIMIT
14
+ # use aggregation for prev day
15
+ at = Time.now.beginning_of_day - 1.day
16
+ if @metric.created_at < at # require full gather day
17
+ count = @metric.aggregations.where("precision" => "daily", "at" => at).first.try(&:count).to_i
18
+ @check.cmp_fn.call(count, @check.value)
19
+ else
20
+ false
21
+ end
22
+
23
+ else
24
+ raise "Unknown check template: #{@check.template}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,59 @@
1
+ require 'snowman-io/loop/check_processor'
2
+
3
+ module SnowmanIO
4
+ module Loop
5
+ class Checks
6
+ include Celluloid
7
+
8
+ def initialize
9
+ after(1) { tick }
10
+ end
11
+
12
+ def tick
13
+ perform
14
+ after(3) { tick }
15
+ end
16
+
17
+ private
18
+
19
+ def perform
20
+ Check.each do |check|
21
+ result = CheckProcessor.new(check).process
22
+ send_mail = false
23
+ check.last_run_at = DateTime.now
24
+ if result
25
+ puts "Check for #{check.metric.name} triggered"
26
+ unless check.triggered
27
+ send_mail = true
28
+ end
29
+ check.triggered = true
30
+ check.last_status = Check::STATUS_FAILED
31
+ else
32
+ check.last_status = Check::STATUS_OK
33
+ end
34
+ check.save!
35
+
36
+ if send_mail
37
+ ReportMailer.check_triggered(
38
+ check,
39
+ check.last_run_at,
40
+ Setting.get(SnowmanIO::BASE_URL_KEY),
41
+ check.user.email,
42
+ true
43
+ ).deliver_now
44
+
45
+ check.user.followers.each do |user|
46
+ ReportMailer.check_triggered(
47
+ check,
48
+ check.last_run_at,
49
+ Setting.get(SnowmanIO::BASE_URL_KEY),
50
+ user.email,
51
+ false
52
+ ).deliver_now
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,43 @@
1
+ module SnowmanIO
2
+ module Loop
3
+ class Main
4
+ include Celluloid
5
+
6
+ def initialize
7
+ after(1) { tick }
8
+ end
9
+
10
+ def tick
11
+ perform
12
+ after(3) { tick }
13
+ end
14
+
15
+ private
16
+
17
+ def perform
18
+ now = Time.now
19
+
20
+ # next time let send report for today
21
+ next_report_date = Time.now.beginning_of_day
22
+
23
+ # aggregate
24
+ start = Time.now.to_f
25
+ Aggregate.metrics_aggregate_5min
26
+ Aggregate.metrics_aggregate_hour
27
+ Aggregate.metrics_aggregate_daily
28
+ Aggregate.metrics_clean_old
29
+
30
+ # init report time
31
+ unless Setting.get(SnowmanIO::NEXT_REPORT_DATE_KEY)
32
+ Setting.set(SnowmanIO::NEXT_REPORT_DATE_KEY, next_report_date.to_i)
33
+ end
34
+
35
+ # send report at 7:00 next day
36
+ if now.to_i > Setting.get(SnowmanIO::NEXT_REPORT_DATE_KEY) + 1.day + 7.hours
37
+ Reports.report_send(Time.at(Setting.get(SnowmanIO::NEXT_REPORT_DATE_KEY)))
38
+ Setting.set(SnowmanIO::NEXT_REPORT_DATE_KEY, next_report_date.to_i)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,25 @@
1
+ module SnowmanIO
2
+ module Loop
3
+ # Ping itself every 5 minutes to be in fit
4
+ class Ping
5
+ include Celluloid
6
+
7
+ def initialize
8
+ after(10) { tick }
9
+ end
10
+
11
+ def tick
12
+ perform
13
+ after(5*60) { tick }
14
+ end
15
+
16
+ private
17
+
18
+ def perform
19
+ if base_url = Setting.get(SnowmanIO::BASE_URL_KEY)
20
+ open(base_url + "/ping")
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,79 @@
1
+ module SnowmanIO
2
+ # Helpfull class to seed data and update DB
3
+ class Migration
4
+ def initialize
5
+ @db = Mongoid::Sessions.default
6
+ puts "[DEBUG] #{@db[:snowman_io_schema].find.to_a.inspect}"
7
+ @done_versions = @db[:snowman_io_schema].find.map { |c| c["version"] }
8
+ @migration_versions = []
9
+ end
10
+
11
+ def migrate
12
+ SnowmanIO.logger.info "Migration start"
13
+
14
+ migration "Set boundary template for all checks" do
15
+ @db[:snowman_io_checks].find().update_all("$set" => {"template" => "boundary"})
16
+ end
17
+
18
+ migration "boundary => last_value_limit" do
19
+ @db[:snowman_io_checks].find({template: "boundary"}).update_all("$set" => {"template" => "last_value_limit"})
20
+ end
21
+
22
+ migration "daily_datapoints => prev_day_datapoints_limit" do
23
+ @db[:snowman_io_checks].find({template: "daily_datapoints"}).update_all("$set" => {"template" => "prev_day_datapoints_limit"})
24
+ end
25
+
26
+ migration "set owner to each check" do
27
+ if user = @db[:snowman_io_users].find.first
28
+ @db[:snowman_io_checks].find.update_all("$set" => {"user_id" => user["_id"]})
29
+ end
30
+ end
31
+
32
+ migration "users timestamps" do
33
+ @db[:snowman_io_users].find.update_all("$set" => {"created_at" => Time.now})
34
+ @db[:snowman_io_users].find.update_all("$set" => {"updated_at" => Time.now})
35
+ end
36
+
37
+ migration "users daily_report" do
38
+ @db[:snowman_io_users].find.update_all("$set" => {"daily_report" => true})
39
+ end
40
+
41
+ migration "users invite_token" do
42
+ @db[:snowman_io_users].find.update_all("$set" => {"invite_token" => ""})
43
+ end
44
+
45
+ migration "users invite_send_count" do
46
+ @db[:snowman_io_users].find.update_all("$set" => {"invite_send_count" => 0})
47
+ end
48
+
49
+ migration "users status" do
50
+ @db[:snowman_io_users].find.update_all("$set" => {"status" => "wait_invite"})
51
+ @db[:snowman_io_users].where(invite_token: "").update_all("$set" => {"status" => "active"})
52
+ end
53
+
54
+ migration "users name" do
55
+ @db[:snowman_io_users].where(status: "active").each do |record|
56
+ @db[:snowman_io_users].where(_id: record["_id"]).update_all("$set" => {"name" => record["email"]})
57
+ end
58
+ end
59
+
60
+ SnowmanIO.logger.info "Migration done"
61
+ end
62
+
63
+ private
64
+
65
+ def migration(version, &block)
66
+ if @migration_versions.include?(version)
67
+ raise "Duplicate migration version '#{version}'"
68
+ end
69
+ @migration_versions.push version
70
+
71
+ return if @done_versions.include?(version)
72
+
73
+ SnowmanIO.logger.info "Apply: #{version} ..."
74
+ yield
75
+ @db[:snowman_io_schema].insert(version: version)
76
+ SnowmanIO.logger.info "Apply: #{version} ... OK"
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,15 @@
1
+ module SnowmanIO
2
+ class Aggregation
3
+ include Mongoid::Document
4
+ belongs_to :metric
5
+
6
+ field :precision, type: String
7
+ field :at, type: DateTime
8
+ field :min, type: Float
9
+ field :avg, type: Float
10
+ field :up, type: Float
11
+ field :max, type: Float
12
+ field :sum, type: Float
13
+ field :count, type: Integer
14
+ end
15
+ end
@@ -0,0 +1,61 @@
1
+ module SnowmanIO
2
+ class App
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+ include Concerns::Tokenable
6
+ has_many :metrics, dependent: :destroy
7
+
8
+ field :name, type: String
9
+ field :token, type: String
10
+
11
+ validates :name, :token, presence: true
12
+
13
+ before_validation on: :create do
14
+ self.token = generate_token(:token)
15
+ end
16
+
17
+ def as_json(options = {})
18
+ super(options.merge(methods: [:datapoints, :metric_ids])).tap do |o|
19
+ o["id"] = o.delete("_id").to_s
20
+ o["metric_ids"] = o["metric_ids"].map(&:to_s)
21
+ end
22
+ end
23
+
24
+ # Returns amount of datapoints for `at` and day before
25
+ def daily_metrics(at)
26
+ json = {}
27
+
28
+ today = at.beginning_of_day
29
+ yesterday = at.beginning_of_day - 1.day
30
+ json["today"] = {"at" => today.strftime("%Y-%m-%d"), "count" => 0}
31
+ json["yesterday"] = {"at" => yesterday.strftime("%Y-%m-%d"), "count" => 0}
32
+ json["total"] = {"count" => 0}
33
+
34
+ # TODO: impl it more performable
35
+ metrics.each do |metric|
36
+ if aggr = metric.aggregations.where(precision: "daily", at: today).first
37
+ json["today"]["count"] += aggr.count.to_i
38
+ end
39
+
40
+ if aggr = metric.aggregations.where(precision: "daily", at: yesterday).first
41
+ json["yesterday"]["count"] += aggr.count.to_i
42
+ end
43
+
44
+ json["total"]["count"] += metric.aggregations.where(precision: "daily").sum(:count).to_i
45
+ end
46
+
47
+ json
48
+ end
49
+
50
+ def register_metric_value(name, kind, value, at = Time.now)
51
+ metric = metrics.where(name: name, kind: kind).first_or_create!
52
+ metric.update_attributes!(last_value: value, last_value_updated_at: Time.now)
53
+ metric.data_points.create!(at: at, value: value)
54
+ touch
55
+ end
56
+
57
+ def datapoints
58
+ daily_metrics(Time.now)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,48 @@
1
+ module SnowmanIO
2
+ class Check
3
+ include Mongoid::Document
4
+ include Mongoid::Timestamps
5
+
6
+ STATUS_NEVER_RUNNED = "NEVER RUNNED"
7
+ STATUS_OK = "OK"
8
+ STATUS_FAILED = "FAILED"
9
+
10
+ TEMPLATE_LAST_VALUE_LIMIT = "last_value_limit"
11
+ TEMPLATE_PREV_DAY_DATAPOINTS_LIMIT = "prev_day_datapoints_limit"
12
+
13
+ belongs_to :metric
14
+ belongs_to :user
15
+
16
+ # Each check should be one of available templates. Each metric is compatible only
17
+ # with some templates.
18
+ field :template, type: String
19
+
20
+ # TEMPLATE_LAST_VALUE_LIMIT, TEMPLATE_PREV_DAY_DATAPOINTS_LIMIT
21
+ field :cmp, type: String
22
+ field :value, type: Float
23
+
24
+ # COMMON
25
+ field :triggered, type: Boolean, default: false
26
+ field :last_run_at, type: DateTime
27
+ field :last_status, type: String, default: STATUS_NEVER_RUNNED
28
+
29
+ def as_json(options = {})
30
+ super(options).tap do |o|
31
+ o["id"] = o.delete("_id").to_s
32
+ o["metric_id"] = o["metric_id"].to_s
33
+ o["user_id"] = o["user_id"].to_s
34
+ end
35
+ end
36
+
37
+ def cmp_fn
38
+ case cmp
39
+ when "more"
40
+ -> (a, b) { a ? a > b : false }
41
+ when "less"
42
+ -> (a, b) { a ? a < b : false }
43
+ else
44
+ raise "unreachable"
45
+ end
46
+ end
47
+ end
48
+ end