focuslight 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.env +13 -0
  3. data/.gitignore +21 -0
  4. data/.travis.yml +9 -0
  5. data/CHANGELOG.md +21 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/Procfile +3 -0
  9. data/Procfile-gem +3 -0
  10. data/README.md +162 -0
  11. data/Rakefile +37 -0
  12. data/bin/focuslight +7 -0
  13. data/config.ru +6 -0
  14. data/focuslight.gemspec +41 -0
  15. data/lib/focuslight.rb +6 -0
  16. data/lib/focuslight/cli.rb +56 -0
  17. data/lib/focuslight/config.rb +27 -0
  18. data/lib/focuslight/data.rb +258 -0
  19. data/lib/focuslight/graph.rb +240 -0
  20. data/lib/focuslight/init.rb +13 -0
  21. data/lib/focuslight/logger.rb +89 -0
  22. data/lib/focuslight/rrd.rb +393 -0
  23. data/lib/focuslight/validator.rb +220 -0
  24. data/lib/focuslight/version.rb +3 -0
  25. data/lib/focuslight/web.rb +614 -0
  26. data/lib/focuslight/worker.rb +97 -0
  27. data/public/css/bootstrap.min.css +7 -0
  28. data/public/favicon.ico +0 -0
  29. data/public/fonts/glyphicons-halflings-regular.eot +0 -0
  30. data/public/fonts/glyphicons-halflings-regular.svg +229 -0
  31. data/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  32. data/public/fonts/glyphicons-halflings-regular.woff +0 -0
  33. data/public/js/bootstrap.min.js +7 -0
  34. data/public/js/jquery-1.10.2.min.js +6 -0
  35. data/public/js/jquery-1.10.2.min.map +0 -0
  36. data/public/js/jquery.storageapi.min.js +2 -0
  37. data/public/js/site.js +214 -0
  38. data/spec/spec_helper.rb +3 -0
  39. data/spec/syntax_spec.rb +9 -0
  40. data/spec/validator_predefined_rules_spec.rb +177 -0
  41. data/spec/validator_result_spec.rb +27 -0
  42. data/spec/validator_rule_spec.rb +68 -0
  43. data/spec/validator_spec.rb +121 -0
  44. data/view/add_complex.erb +143 -0
  45. data/view/base.erb +200 -0
  46. data/view/docs.erb +125 -0
  47. data/view/edit.erb +102 -0
  48. data/view/edit_complex.erb +158 -0
  49. data/view/index.erb +19 -0
  50. data/view/list.erb +22 -0
  51. data/view/view.erb +42 -0
  52. data/view/view_graph.erb +16 -0
  53. metadata +345 -0
@@ -0,0 +1,27 @@
1
+ require "focuslight"
2
+
3
+ module Focuslight::Config
4
+ DEFAULT_DATADIR = File.expand_path('data', "#{__dir__}/../..")
5
+ DEFAULT_LOG_PATH = File.expand_path('log/application.log', "#{__dir__}/../..")
6
+
7
+ def self.get(name)
8
+ case name
9
+ when :datadir
10
+ ENV.fetch('DATADIR', DEFAULT_DATADIR)
11
+ when :float_support
12
+ ENV.fetch('FLOAT_SUPPORT', false)
13
+ when :dburl
14
+ ENV.fetch('DBURL', 'sqlite://data/gforecast.db')
15
+ when :log_path
16
+ ENV.fetch('LOG_PATH', DEFAULT_LOG_PATH)
17
+ when :log_level
18
+ ENV.fetch('LOG_LEVEL', 'info')
19
+ when :log_shift_age
20
+ ENV.fetch('LOG_SHIFT_AGE', '0')
21
+ when :log_shift_size
22
+ ENV.fetch('LOG_SHIFT_SIZE', '1048576')
23
+ else
24
+ raise ArgumentError, 'unknown configuration keyword'
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,258 @@
1
+ require "focuslight"
2
+ require "focuslight/config"
3
+ require "focuslight/logger"
4
+ require "focuslight/graph"
5
+ require "sequel"
6
+
7
+ DB = Sequel.connect(Focuslight::Config.get(:dburl), logger: Focuslight.logger)
8
+
9
+ class Focuslight::Data
10
+ include Focuslight::Logger
11
+
12
+ def initialize
13
+ @datadir = Focuslight::Config.get(:datadir)
14
+ @floatings = Focuslight::Config.get(:float_support) == "y"
15
+
16
+ if DB.database_type == :sqlite
17
+ DB.run 'PRAGMA journal_mode = WAL'
18
+ DB.run 'PRAGMA synchronous = NORMAL'
19
+ end
20
+ @graphs = DB.from(:graphs)
21
+ @complex_graphs = DB.from(:complex_graphs)
22
+ end
23
+
24
+ def number_type
25
+ @floatings ? Float : Bignum
26
+ end
27
+
28
+ def create_tables
29
+ ntype = number_type
30
+ DB.transaction do
31
+
32
+ DB.create_table :graphs do
33
+ primary_key :id, Bignum # Notice that SQLite actually creates integer primary key
34
+ column :service_name, String, null: false
35
+ column :section_name, String, null: false
36
+ column :graph_name, String, null: false
37
+ column :number, ntype, default: 0
38
+ column :mode, String, default: "gauge", null: false
39
+ column :description, String, default: "", null: false
40
+ column :sort, Bignum, default: 0, null: false
41
+ column :color, String, default: "#00CC00", null: false
42
+ column :ulimit, ntype, default: 1000000000000000, null: false
43
+ column :llimit, ntype, default: 0, null: false
44
+ column :type, String, default: "AREA", null: false
45
+ String :meta, text: true
46
+ column :created_at, Bignum, null: false
47
+ column :updated_at, Bignum, null: false
48
+ unique [:service_name, :section_name, :graph_name]
49
+ end
50
+
51
+ DB.create_table :complex_graphs do
52
+ primary_key :id, Bignum # Notice that SQLite actually creates integer primary key
53
+ column :service_name, String, null: false
54
+ column :section_name, String, null: false
55
+ column :graph_name, String, null: false
56
+ column :number, ntype, default: 0
57
+ column :description, String, default: "", null: false
58
+ column :sort, Bignum, default: 0, null: false
59
+ String :meta, text: true
60
+ column :created_at, Bignum, null: false
61
+ column :updated_at, Bignum, null: false
62
+ unique [:service_name, :section_name, :graph_name]
63
+ end
64
+ end
65
+ end
66
+
67
+ def execute(*args)
68
+ DB.run(*args)
69
+ end
70
+
71
+ def transaction
72
+ DB.transaction do
73
+ yield DB
74
+ end
75
+ end
76
+
77
+ def get(service, section, graph)
78
+ data = @graphs.where(service_name: service, section_name: section, graph_name: graph).first
79
+ data && Focuslight::Graph.concrete(data)
80
+ end
81
+
82
+ def get_by_id(id)
83
+ data = @graphs[id: id]
84
+ data && Focuslight::Graph.concrete(data)
85
+ end
86
+
87
+ def get_by_id_for_rrdupdate(id, target=:normal) # get_by_id_for_rrdupdate_short == get_by_id_for_rrdupdate(id, :short)
88
+ data = @graphs[id: id]
89
+ return nil unless data
90
+ graph = Focuslight::Graph.concrete(data)
91
+ end
92
+
93
+ def update(service_name, section_name, graph_name, number, mode, color)
94
+ data = nil
95
+ DB.transaction do
96
+ data = @graphs.where(service_name: service_name, section_name: section_name, graph_name: graph_name).first
97
+ if data
98
+ graph = Focuslight::Graph.concrete(data)
99
+ if mode == 'count'
100
+ number += graph.number
101
+ end
102
+ if mode != 'modified' || (mode == 'modified' && graph.number != number)
103
+ color = graph.color if color.empty?
104
+ @graphs.where(id: graph.id).update(number: number, mode: mode, color: color, updated_at: Time.now.to_i)
105
+ end
106
+ else
107
+ color = '#' + ['33', '66', '99', 'cc'].shuffle.slice(0,3).join if color.empty?
108
+ # COLUMNS = %w(service_name section_name graph_name number mode color llimit created_at updated_at)
109
+ columns = Focuslight::SimpleGraph::COLUMNS.join(',')
110
+ # PLACEHOLDERS = COLUMNS.map{|c| '?'}
111
+ placeholders = Focuslight::SimpleGraph::PLACEHOLDERS.join(',')
112
+ current_time = Time.now.to_i
113
+ @graphs.insert(
114
+ service_name: service_name,
115
+ section_name: section_name,
116
+ graph_name: graph_name,
117
+ number: number,
118
+ mode: mode,
119
+ color: color,
120
+ llimit: -1000000000,
121
+ created_at: current_time,
122
+ updated_at: current_time)
123
+ end
124
+
125
+ data = @graphs.where(service_name: service_name, section_name: section_name, graph_name: graph_name).first
126
+ end # transaction
127
+
128
+ Focuslight::Graph.concrete(data)
129
+ end
130
+
131
+ def update_graph(id, args)
132
+ graph = get_by_id(id)
133
+ return nil unless graph
134
+
135
+ graph.update(args)
136
+ @graphs.where(id: graph.id)
137
+ .update(
138
+ service_name: graph.service,
139
+ section_name: graph.section,
140
+ graph_name: graph.graph,
141
+ description: graph.description,
142
+ sort: graph.sort,
143
+ color: graph.color,
144
+ type: graph.type,
145
+ llimit: graph.llimit,
146
+ ulimit: graph.ulimit,
147
+ meta: graph.meta
148
+ )
149
+ true
150
+ end
151
+
152
+ def update_graph_description(id, description)
153
+ @graphs.where(id: id).update(description: description)
154
+ true
155
+ end
156
+
157
+ def get_services
158
+ rows1 = @graphs.order(:service_name).all
159
+ rows2 = @complex_graphs.order(:service_name).all
160
+ (rows1 + rows2).map{|row| row[:service_name]}.uniq.sort
161
+ end
162
+
163
+ def get_sections(service)
164
+ rows1 = @graphs.select(:section_name).order(:section_name).where(service_name: service).all
165
+ rows2 = @complex_graphs.select(:section_name).order(:section_name).where(service_name: service).all
166
+ (rows1 + rows2).map{|row| row[:section_name]}.uniq.sort
167
+ end
168
+
169
+ def get_graphs(service, section)
170
+ rows1 = @graphs.order(Sequel.desc(:sort)).where(service_name: service, section_name: section).all
171
+ rows2 = @complex_graphs.order(Sequel.desc(:sort)).where(service_name: service, section_name: section).all
172
+ (rows1 + rows2).map{|row| Focuslight::Graph.concrete(row)}.sort{|a,b| b.sort <=> a.sort}
173
+ end
174
+
175
+ def get_all_graph_id
176
+ @graphs.select(:id).all
177
+ end
178
+
179
+ def get_all_graph_name
180
+ @graphs.select(:id, :service_name, :section_name, :graph_name).reverse_order(:service_name, :section_name, :graph_name).all
181
+ end
182
+
183
+ def get_all_graph_all
184
+ rows = @graphs.reverse_order(:service_name, :section_name, :graph_name).all
185
+ rows.map{|row| Focuslight::Graph.concrete(row)}
186
+ end
187
+
188
+ def remove(id)
189
+ DB.transaction do
190
+ @graphs.where(id: id).delete
191
+ end
192
+ end
193
+
194
+ def get_complex(service, section, graph)
195
+ data = @complex_graphs.where(service_name: service, section_name: section, graph_name: graph).first
196
+ data && Focuslight::Graph.concrete(data)
197
+ end
198
+
199
+ def get_complex_by_id(id)
200
+ data = @complex_graphs.where(id: id).first
201
+ data && Focuslight::Graph.concrete(data)
202
+ end
203
+
204
+ def create_complex(service, section, graph, args)
205
+ description = args[:description]
206
+ sort = args[:sort]
207
+ meta = Focuslight::ComplexGraph.meta_clean(args).to_json
208
+ now = Time.now.to_i
209
+ @complex_graphs.insert(
210
+ service_name: service,
211
+ section_name: section,
212
+ graph_name: graph,
213
+ description: description,
214
+ sort: sort.to_i,
215
+ meta: meta,
216
+ created_at: now,
217
+ updated_at: now,
218
+ )
219
+ get_complex(service, section, graph)
220
+ end
221
+
222
+ def update_complex(id, args)
223
+ graph = get_complex_by_id(id)
224
+ return nil unless graph
225
+
226
+ graph.update(args)
227
+ @complex_graphs.where(id: graph.id)
228
+ .update(
229
+ service_name: graph.service,
230
+ section_name: graph.section,
231
+ graph_name: graph.graph,
232
+ description: graph.description,
233
+ sort: graph.sort,
234
+ meta: graph.meta,
235
+ updated_at: Time.now.to_i,
236
+ )
237
+
238
+ get_complex_by_id(id)
239
+ end
240
+
241
+ def remove_complex(id)
242
+ @complex_graphs.where(id: id).delete
243
+ end
244
+
245
+ def get_all_complex_graph_id
246
+ @complex_graphs.select(:id).all
247
+ end
248
+
249
+ def get_all_complex_graph_name
250
+ @complex_graphs.select(:id, :service_name, :section_name, :graph_name).reverse_order(:service_name, :section_name, :graph_name).all
251
+ end
252
+
253
+ def get_all_complex_graph_all
254
+ rows = @complex_graphs.reverse_order(:service_name, :section_name, :graph_name)
255
+ return [] unless rows
256
+ rows.map{|row| Focuslight::Graph.concrete(row)}
257
+ end
258
+ end
@@ -0,0 +1,240 @@
1
+ require "focuslight"
2
+ require "focuslight/logger"
3
+
4
+ require "digest"
5
+ require "json"
6
+
7
+ module Focuslight
8
+ class Graph
9
+ include Focuslight::Logger
10
+
11
+ def self.concrete(row)
12
+ if row.has_key?(:mode) && row.has_key?(:type)
13
+ Focuslight::SimpleGraph.new(row)
14
+ else
15
+ Focuslight::ComplexGraph.new(row)
16
+ end
17
+ end
18
+
19
+ attr_accessor :id, :service, :section, :graph, :number, :description, :sort
20
+ attr_accessor :meta
21
+ attr_accessor :created_at_time, :updated_at_time
22
+
23
+ attr_accessor :c_type, :stack # for complex graph construction
24
+
25
+ def initialize(row)
26
+ @row_hash = row
27
+
28
+ @id = row[:id]
29
+ @service = row[:service_name]
30
+ @section = row[:section_name]
31
+ @graph = row[:graph_name]
32
+ @number = row[:number].to_i # NOT NULL DEFAULT 0
33
+ @description = row[:description] || ''
34
+ @sort = row[:sort].to_i # NOT NULL DEFAULT 0
35
+
36
+ @meta = row[:meta]
37
+ @parsed_meta = JSON.parse(@meta || '{}', :symbolize_names => true)
38
+
39
+ @created_at_time = Time.at(row[:created_at].to_i)
40
+ @updated_at_time = Time.at(row[:updated_at].to_i)
41
+ end
42
+
43
+ def path
44
+ [@service, @section, @graph]
45
+ end
46
+
47
+ def created_at
48
+ @created_at_time.strftime('%Y/%m/%d %T')
49
+ end
50
+
51
+ def updated_at
52
+ @updated_at_time.strftime('%Y/%m/%d %T')
53
+ end
54
+
55
+ def to_hash
56
+ {
57
+ id: @id, service_name: @service, section_name: @section, graph_name: @graph,
58
+ number: @number, description: @description, sort: @sort,
59
+ created_at: self.created_at(), updated_at: self.updated_at(),
60
+ }
61
+ end
62
+
63
+ def self.hash2request(hash)
64
+ hash = hash.dup
65
+ is_complex = hash.delete(:complex)
66
+
67
+ hash.delete(:id)
68
+ hash.delete(:created_at)
69
+ hash.delete(:updated_at)
70
+
71
+ return hash unless is_complex ##TODO concrete?
72
+
73
+ hash.delete(:number)
74
+ hash[:sumup] = (hash[:sumup] ? '1' : '0')
75
+
76
+ data_rows = hash.delete(:data)
77
+
78
+ first = data_rows.shift
79
+ hash['path-1'.to_sym] = first[:graph_id]
80
+ hash['type-1'.to_sym] = first[:type]
81
+
82
+ p2 = 'path-2'.to_sym
83
+ t2 = 'type-2'.to_sym
84
+ s2 = 'stack-2'.to_sym
85
+ hash[p2] = []
86
+ hash[t2] = []
87
+ hash[s2] = []
88
+ data_rows.each do |row|
89
+ hash[p2] << row[:graph_id]
90
+ hash[t2] << row[:type]
91
+ hash[s2] << (row[:stack] ? '1' : '0')
92
+ end
93
+
94
+ hash ##TODO concrete?
95
+ end
96
+ end
97
+
98
+ class SimpleGraph < Graph
99
+ COLUMNS = %w(service_name section_name graph_name number mode color llimit created_at updated_at)
100
+ PLACEHOLDERS = COLUMNS.map{|c| '?'}
101
+
102
+ attr_accessor :mode, :color, :ulimit, :llimit, :type
103
+
104
+ attr_reader :md5
105
+ attr_accessor :adjust, :adjustval, :unit
106
+
107
+ def initialize(row)
108
+ super
109
+
110
+ @mode = row[:mode] || 'gauge' # NOT NULL DEFAULT 'gauge'
111
+ @color = row[:color] || '#00CC00' # NOT NULL DEFAULT '#00CC00'
112
+ @ulimit = row[:ulimit] || 1000000000000000 # NOT NULL DEFAULT 1000000000000000
113
+ @llimit = row[:llimit] || 0
114
+ @type = row[:type] || 'AREA'
115
+
116
+ @md5 = Digest::MD5.hexdigest(@id.to_s)
117
+
118
+ @adjust = @parsed_meta.fetch(:adjust, '*')
119
+ @adjustval = @parsed_meta.fetch(:adjustval, '1')
120
+ @unit = @parsed_meta.fetch(:unit, '')
121
+ end
122
+
123
+ def to_hash
124
+ simple = {
125
+ mode: @mode, color: @color,
126
+ ulimit: @ulimit, llimit: @llimit,
127
+ type: @type,
128
+ adjust: @adjust, adjustval: @adjustval, unit: @unit,
129
+ complex: false,
130
+ md5: @md5, meta: @meta,
131
+ }
132
+ hash = super
133
+ hash.merge(simple)
134
+ end
135
+
136
+ def complex?
137
+ false
138
+ end
139
+
140
+ def update(args={})
141
+ meta = @parsed_meta.dup
142
+ args.each do |k, v|
143
+ case k.to_sym
144
+ when :number then @number = v
145
+ when :description then @description = v
146
+ when :sort then @sort = v
147
+ when :mode then @mode = v
148
+ when :color then @color = v
149
+ when :ulimit then @ulimit = v
150
+ when :llimit then @llimit = v
151
+ when :type then @type = v
152
+ else
153
+ meta[k.to_sym] = v
154
+ end
155
+ end
156
+ @parsed_meta = self.class.meta_clean(@parsed_meta.merge(meta))
157
+ @meta = @parsed_meta.to_json
158
+ end
159
+
160
+ def self.meta_clean(args={})
161
+ args.delete_if do |k,v|
162
+ %w(id service_name section_name graph_name number
163
+ description sort mode color ulimit llimit type).include?(k.to_s)
164
+ end
165
+ end
166
+ end
167
+
168
+ class ComplexGraph < Graph
169
+ attr_accessor :sumup, :data_rows
170
+ attr_reader :complex_graph
171
+
172
+ def initialize(row)
173
+ super
174
+
175
+ uri = [:'type-1', :'path-1'].map{|k| @parsed_meta[k]}.join(':') + ':0' # stack
176
+
177
+ data_rows = []
178
+
179
+ first_row = {
180
+ type: @parsed_meta[:'type-1'],
181
+ path: @parsed_meta[:'path-1'].to_i,
182
+ stack: false,
183
+ graph_id: @parsed_meta[:'path-1'].to_i,
184
+ }
185
+ data_rows << first_row
186
+
187
+ unless @parsed_meta[:'type-2'].is_a?(Array)
188
+ [:'type-2', :'path-2', :'stack-2'].each do |key|
189
+ @parsed_meta[key] = [@parsed_meta[key]].flatten
190
+ end
191
+ end
192
+
193
+ @parsed_meta[:'type-2'].each_with_index do |type, i|
194
+ t = @parsed_meta[:'type-2'][i]
195
+ p = @parsed_meta[:'path-2'][i].to_i
196
+ s = @parsed_meta[:'stack-2'][i].is_a?(String) ? !!(@parsed_meta[:'stack-2'][i] =~ /^(1|true)$/i) : !!@parsed_meta[:'stack-2'][i]
197
+ uri += ':' + [t, p, (s ? '1' : '0')].join(':')
198
+ data_rows << {type: t, path: p, stack: s, graph_id: p}
199
+ end
200
+
201
+ @sumup = @parsed_meta.fetch(:sumup, '0') != '0' # '0' is false
202
+ @data_rows = data_rows
203
+ @complex_graph = uri
204
+ end
205
+
206
+ def to_hash
207
+ complex = {
208
+ sumup: @sumup, data: @data_rows,
209
+ complex: true
210
+ }
211
+ hash = super
212
+ hash.merge(complex)
213
+ end
214
+
215
+ def complex?
216
+ true
217
+ end
218
+
219
+ def update(args={})
220
+ meta = @parsed_meta.dup
221
+ args.each do |k, v|
222
+ case k.to_sym
223
+ when :number then @number = v
224
+ when :description then @description = v
225
+ when :sort then @sort = v
226
+ else
227
+ meta[k.to_sym] = v
228
+ end
229
+ end
230
+ @parsed_meta = self.class.meta_clean(@parsed_meta.merge(meta))
231
+ @meta = @parsed_meta.to_json
232
+ end
233
+
234
+ def self.meta_clean(args={})
235
+ args.delete_if do |k,v|
236
+ %w(id service_name section_name graph_name number description sort).include?(k.to_s)
237
+ end
238
+ end
239
+ end
240
+ end