puny-monitor 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5563bcd36e5c112a41100ba5138ca04eb9c2d8ad4cdf7782f9b3847d4d7cc44c
4
- data.tar.gz: dcab8591a17194d2a92e0ff23f052d6680348d3925a36f45cef69caf8e5c2130
3
+ metadata.gz: b39f83645fed8b173fe448a4c9f93dfc886e15a0c368b0137e315caa001bf414
4
+ data.tar.gz: 19f2757bb81d07c6d132dc9570e4f4724ad5f69ad8ca06f930a302db6f47ec43
5
5
  SHA512:
6
- metadata.gz: e1f2e834bb72b0918f50d80c7a842d8bcdf685d3caddcb28e7aea60808836e90371330cfc2f31d8f479b4414f6db85bdd2883c17dce6cb44db6a47351245a7d5
7
- data.tar.gz: ba927bfbd53adfe1aab34ed2b2c0e5cb7452a6128a99cdf61c52d1e6ab2876b59673c24357cf5d6a75defd1830d5a1ad700c2b2124fdbf019d805b796e76bf31
6
+ metadata.gz: 5d76d92559a392db462a141e0350535cbb3dbf3052a17983ca54cb85c7c54d2e980c4d555194616aa0cf6d6031b7091e43d999db171d67f5e2157f1ae1b5886c
7
+ data.tar.gz: 8017307c65f35ca42c653bfb9a3231af8df5334134b558c1935b9ac8ff34faeee8b2ad7f7c1d116934e2fa5f3ab3b02923d5883c29bad20892095c02cac6a844
data/app/authorization.rb CHANGED
@@ -5,17 +5,17 @@ module PunyMonitor
5
5
  def self.included(base)
6
6
  return unless authorize?
7
7
 
8
- base.use Rack::Auth::Basic, 'Puny Monitor' do |username, password|
8
+ base.use Rack::Auth::Basic, "Puny Monitor" do |username, password|
9
9
  username == self.username && password == self.password
10
10
  end
11
11
  end
12
12
 
13
13
  def self.username
14
- @username ||= ENV.fetch('PUNY_USERNAME', nil)
14
+ @username ||= ENV.fetch("PUNY_USERNAME", nil)
15
15
  end
16
16
 
17
17
  def self.password
18
- @password ||= ENV.fetch('PUNY_PASSWORD', nil)
18
+ @password ||= ENV.fetch("PUNY_PASSWORD", nil)
19
19
  end
20
20
 
21
21
  def self.authorize?
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationModel < ActiveRecord::Base
4
+ self.abstract_class = true
5
+
6
+ scope :group_by_time, ->(minutes) {
7
+ interval_seconds = minutes * 60
8
+ group("strftime('%s', created_at) / #{interval_seconds} * #{interval_seconds}")
9
+ }
10
+
11
+ def self.group_format(minutes)
12
+ interval_seconds = minutes * 60
13
+ "strftime('%s', created_at) / #{interval_seconds} * #{interval_seconds}"
14
+ end
15
+ end
@@ -1,21 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Bandwidth < ActiveRecord::Base
4
- class << self
5
- def average_usage(start_time, group_by)
6
- [
7
- {name: "Incoming Mbps", data: average_for_period(:incoming_mbps, start_time, group_by)},
8
- {name: "Outgoing Mbps", data: average_for_period(:outgoing_mbps, start_time, group_by)}
9
- ]
10
- end
3
+ class Bandwidth < ApplicationModel
4
+ def self.average_usage(start_time, minutes)
5
+ data = where(created_at: start_time..)
6
+ .group_by_time(minutes)
7
+ .pluck(
8
+ Arel.sql("datetime(#{group_format(minutes)}, 'unixepoch') as period"),
9
+ Arel.sql("ROUND(AVG(incoming_mbps), 2) as avg_incoming"),
10
+ Arel.sql("ROUND(AVG(outgoing_mbps), 2) as avg_outgoing")
11
+ )
11
12
 
12
- private
13
-
14
- def average_for_period(column, start_time, group_by)
15
- where(created_at: start_time..)
16
- .group_by_period(group_by, :created_at)
17
- .average(column)
18
- .transform_values { |value| value&.round(2) }
19
- end
13
+ incoming = data.to_h { |period, incoming, _| [period, incoming] }
14
+ outgoing = data.to_h { |period, _, outgoing| [period, outgoing] }
15
+ [
16
+ {name: "Incoming Mbps", data: incoming},
17
+ {name: "Outgoing Mbps", data: outgoing}
18
+ ]
20
19
  end
21
20
  end
@@ -1,18 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class CpuLoad < ActiveRecord::Base
4
- def self.average_load(start_time, end_time, group_by)
3
+ class CpuLoad < ApplicationModel
4
+ def self.average_load(start_time, minutes)
5
+ data = where(created_at: start_time..)
6
+ .group_by_time(minutes)
7
+ .pluck(
8
+ Arel.sql("datetime(#{group_format(minutes)}, 'unixepoch') as period"),
9
+ Arel.sql("ROUND(AVG(one_minute), 2) as avg_one"),
10
+ Arel.sql("ROUND(AVG(five_minutes), 2) as avg_five"),
11
+ Arel.sql("ROUND(AVG(fifteen_minutes), 2) as avg_fifteen")
12
+ )
13
+
14
+ one = data.to_h { |period, one, _, _| [period, one] }
15
+ five = data.to_h { |period, _, five, _| [period, five] }
16
+ fifteen = data.to_h { |period, _, _, fifteen| [period, fifteen] }
5
17
  [
6
- {name: "1 minute", data: average_for_period(:one_minute, start_time, end_time, group_by)},
7
- {name: "5 minutes", data: average_for_period(:five_minutes, start_time, end_time, group_by)},
8
- {name: "15 minutes", data: average_for_period(:fifteen_minutes, start_time, end_time, group_by)}
18
+ {name: "1 minute", data: one},
19
+ {name: "5 minutes", data: five},
20
+ {name: "15 minutes", data: fifteen}
9
21
  ]
10
22
  end
11
-
12
- def self.average_for_period(column, start_time, end_time, group_by)
13
- where(created_at: start_time..end_time)
14
- .group_by_period(group_by, :created_at)
15
- .average(column)
16
- .transform_values { |value| value&.round(2) }
17
- end
18
23
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class CpuUsage < ActiveRecord::Base
4
- def self.average_usage(start_time, group_by)
3
+ class CpuUsage < ApplicationModel
4
+ def self.average_usage(start_time, minutes)
5
5
  where(created_at: start_time..)
6
- .group_by_period(group_by, :created_at, expand_range: true)
7
- .average(:used_percent)
8
- .transform_values { |value| value&.round(2) }
6
+ .group_by_time(minutes)
7
+ .pluck(
8
+ Arel.sql("datetime(#{group_format(minutes)}, 'unixepoch') as period"),
9
+ Arel.sql("ROUND(AVG(used_percent), 2) as avg_usage")
10
+ )
9
11
  end
10
12
  end
@@ -1,21 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class DiskIO < ActiveRecord::Base
4
- class << self
5
- def average_io(start_time, group_by)
6
- [
7
- {name: "Read MB/s", data: average_for_period(:read_mb_per_sec, start_time, group_by)},
8
- {name: "Write MB/s", data: average_for_period(:write_mb_per_sec, start_time, group_by)}
9
- ]
10
- end
3
+ class DiskIO < ApplicationModel
4
+ def self.average_io(start_time, minutes)
5
+ data = where(created_at: start_time..)
6
+ .group_by_time(minutes)
7
+ .pluck(
8
+ Arel.sql("datetime(#{group_format(minutes)}, 'unixepoch') as period"),
9
+ Arel.sql("ROUND(AVG(read_mb_per_sec), 2) as avg_read"),
10
+ Arel.sql("ROUND(AVG(write_mb_per_sec), 2) as avg_write")
11
+ )
11
12
 
12
- private
13
-
14
- def average_for_period(column, start_time, group_by)
15
- where(created_at: start_time..)
16
- .group_by_period(group_by, :created_at)
17
- .average(column)
18
- .transform_values { |value| value&.round(2) }
19
- end
13
+ read = data.to_h { |period, read, _| [period, read] }
14
+ write = data.to_h { |period, _, write| [period, write] }
15
+ [
16
+ {name: "Read MB/s", data: read},
17
+ {name: "Write MB/s", data: write}
18
+ ]
20
19
  end
21
20
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class FilesystemUsage < ActiveRecord::Base
4
- def self.average_usage(start_time, group_by)
3
+ class FilesystemUsage < ApplicationModel
4
+ def self.average_usage(start_time, minutes)
5
5
  where(created_at: start_time..)
6
- .group_by_period(group_by, :created_at)
7
- .average(:used_percent)
8
- .transform_values { |value| value&.round(2) }
6
+ .group_by_time(minutes)
7
+ .pluck(
8
+ Arel.sql("datetime(#{group_format(minutes)}, 'unixepoch') as period"),
9
+ Arel.sql("ROUND(AVG(used_percent), 2) as avg_usage")
10
+ )
9
11
  end
10
12
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class MemoryUsage < ActiveRecord::Base
4
- def self.average_usage(start_time, group_by)
3
+ class MemoryUsage < ApplicationModel
4
+ def self.average_usage(start_time, minutes)
5
5
  where(created_at: start_time..)
6
- .group_by_period(group_by, :created_at)
7
- .average(:used_percent)
8
- .transform_values { |value| value&.round(2) }
6
+ .group_by_time(minutes)
7
+ .pluck(
8
+ Arel.sql("datetime(#{group_format(minutes)}, 'unixepoch') as period"),
9
+ Arel.sql("ROUND(AVG(used_percent), 2) as avg_usage")
10
+ )
9
11
  end
10
12
  end
data/app/puny_monitor.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rufus-scheduler'
4
- require_relative 'scheduler'
5
- require_relative 'authorization'
6
- require_relative '../lib/system_utils'
3
+ require "rufus-scheduler"
4
+ require_relative "scheduler"
5
+ require_relative "authorization"
6
+ require_relative "../lib/system_utils"
7
7
 
8
8
  module PunyMonitor
9
9
  class App < Sinatra::Base
@@ -12,8 +12,8 @@ module PunyMonitor
12
12
  configure do
13
13
  register Sinatra::ActiveRecordExtension
14
14
  @scheduler = Rufus::Scheduler.new
15
- @scheduler.every('5s') { Scheduler.collect_data }
16
- @scheduler.every('1h') { Scheduler.cleanup_old_data }
15
+ @scheduler.every("10s") { Scheduler.collect_data }
16
+ @scheduler.every("1h") { Scheduler.cleanup_old_data }
17
17
  end
18
18
 
19
19
  configure :development do
@@ -21,75 +21,77 @@ module PunyMonitor
21
21
  end
22
22
 
23
23
  set :erb, layout: :layout
24
- set :public_folder, File.join(__dir__, '..', 'public')
25
- set :database_file, '../config/database.yml'
24
+ set :public_folder, File.join(__dir__, "..", "public")
25
+ set :database_file, "../config/database.yml"
26
26
 
27
- get '/' do
28
- erb :index, locals: { params:, logo: }
27
+ get "/" do
28
+ erb :index, locals: {params:, logo:}
29
29
  end
30
30
 
31
- get '/up' do
31
+ get "/up" do
32
32
  200
33
33
  end
34
34
 
35
- get '/data/cpu_usage' do
35
+ get "/data/cpu_usage" do
36
36
  content_type :json
37
- CpuUsage.average_usage(start_time, group_by).to_json
37
+ CpuUsage.average_usage(start_time, interval).to_json
38
38
  end
39
39
 
40
- get '/data/cpu_load' do
40
+ get "/data/cpu_load" do
41
41
  content_type :json
42
- CpuLoad.average_load(start_time, Time.now, group_by).to_json
42
+ CpuLoad.average_load(start_time, interval).to_json
43
43
  end
44
44
 
45
- get '/data/memory_usage' do
45
+ get "/data/memory_usage" do
46
46
  content_type :json
47
- MemoryUsage.average_usage(start_time, group_by).to_json
47
+ MemoryUsage.average_usage(start_time, interval).to_json
48
48
  end
49
49
 
50
- get '/data/filesystem_usage' do
50
+ get "/data/filesystem_usage" do
51
51
  content_type :json
52
- FilesystemUsage.average_usage(start_time, group_by).to_json
52
+ FilesystemUsage.average_usage(start_time, interval).to_json
53
53
  end
54
54
 
55
- get '/data/disk_io' do
55
+ get "/data/disk_io" do
56
56
  content_type :json
57
- DiskIO.average_io(start_time, group_by).to_json
57
+ DiskIO.average_io(start_time, interval).to_json
58
58
  end
59
59
 
60
- get '/data/bandwidth' do
60
+ get "/data/bandwidth" do
61
61
  content_type :json
62
- Bandwidth.average_usage(start_time, group_by).to_json
62
+ Bandwidth.average_usage(start_time, interval).to_json
63
63
  end
64
64
 
65
65
  private
66
66
 
67
67
  def logo
68
68
  @logo ||= begin
69
- file = File.open('public/icon.svg')
69
+ file = File.open("public/icon.svg")
70
70
  file.read
71
71
  end
72
72
  end
73
73
 
74
- def duration
75
- params[:duration] || '1d'
76
- end
77
-
78
74
  def start_time
75
+ duration = params[:duration] || "1d"
79
76
  case duration
80
- when '1h' then 1.hour.ago
81
- when '3d' then 3.days.ago
82
- when '1w' then 1.week.ago
83
- when '1m' then 1.month.ago
77
+ when "1h" then 1.hour.ago
78
+ when "3d" then 3.days.ago
79
+ when "1w" then 1.week.ago
80
+ when "1m" then 1.month.ago
84
81
  else 1.day.ago
85
82
  end
86
83
  end
87
84
 
88
- def group_by
89
- case duration
90
- when '1h', '1d' then :minute
91
- else :hour
92
- end
85
+ def interval
86
+ (params[:interval] || "15").to_i
87
+ end
88
+
89
+ def chart_url(endpoint)
90
+ query_params = []
91
+ query_params << "duration=#{params[:duration]}" if params[:duration]
92
+ query_params << "interval=#{params[:interval]}" if params[:interval]
93
+ query_string = query_params.empty? ? "" : "?#{query_params.join("&")}"
94
+ "#{endpoint}#{query_string}"
93
95
  end
94
96
  end
95
97
  end
data/app/scheduler.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../lib/system_utils'
3
+ require_relative "../lib/system_utils"
4
4
 
5
5
  module PunyMonitor
6
6
  class Scheduler
@@ -9,8 +9,8 @@ module PunyMonitor
9
9
  CpuUsage.create(used_percent: SystemUtils.cpu_usage_percent)
10
10
  cpu_load_averages = SystemUtils.cpu_load_average
11
11
  CpuLoad.create(one_minute: cpu_load_averages[0],
12
- five_minutes: cpu_load_averages[1],
13
- fifteen_minutes: cpu_load_averages[2])
12
+ five_minutes: cpu_load_averages[1],
13
+ fifteen_minutes: cpu_load_averages[2])
14
14
  MemoryUsage.create(used_percent: SystemUtils.memory_usage_percent)
15
15
  FilesystemUsage.create(used_percent: SystemUtils.filesystem_usage_percent)
16
16
 
@@ -24,7 +24,7 @@ module PunyMonitor
24
24
  def cleanup_old_data
25
25
  one_month_ago = 1.month.ago
26
26
  [CpuUsage, CpuLoad, MemoryUsage, FilesystemUsage, DiskIO, Bandwidth].each do |model|
27
- model.where('created_at < ?', one_month_ago).delete_all
27
+ model.where("created_at < ?", one_month_ago).delete_all
28
28
  end
29
29
  end
30
30
  end
data/app/views/index.erb CHANGED
@@ -1,15 +1,31 @@
1
1
  <section class="controls">
2
- <form action="/" method="get">
3
- <select name="duration" onchange="this.form.submit()">
4
- <option value="1h" <%= params[:duration] == '1h' ? 'selected' : '' %>>1 Hour</option>
5
- <option
6
- value="1d"
7
- <%= params[:duration] == '1d' || params[:duration].nil? ? 'selected' : '' %>
8
- >1 Day</option>
9
- <option value="3d" <%= params[:duration] == '3d' ? 'selected' : '' %>>3 Days</option>
10
- <option value="1w" <%= params[:duration] == '1w' ? 'selected' : '' %>>1 Week</option>
11
- <option value="1m" <%= params[:duration] == '1m' ? 'selected' : '' %>>1 Month</option>
12
- </select>
2
+ <form class="controls__form" action="/" method="get">
3
+
4
+ <label for="duration">Duration
5
+ <select id="duration" name="duration" onchange="this.form.submit()">
6
+ <option value="1h" <%= params[:duration] == '1h' ? 'selected' : '' %>>1 Hour</option>
7
+ <option
8
+ value="1d"
9
+ <%= params[:duration] == '1d' || params[:duration].nil? ? 'selected' : '' %>
10
+ >1 Day</option>
11
+ <option value="3d" <%= params[:duration] == '3d' ? 'selected' : '' %>>3 Days</option>
12
+ <option value="1w" <%= params[:duration] == '1w' ? 'selected' : '' %>>1 Week</option>
13
+ <option value="1m" <%= params[:duration] == '1m' ? 'selected' : '' %>>1 Month</option>
14
+ </select>
15
+ </label>
16
+
17
+ <label for="interval">Aggregation Interval
18
+ <select id="interval" name="interval" onchange="this.form.submit()">
19
+ <option value="5" <%= params[:interval] == '5' ? 'selected' : '' %>>5 minutes</option>
20
+ <option
21
+ value="15"
22
+ <%= params[:interval] == '15' || params[:interval].nil? ? 'selected' : '' %>
23
+ >15 minutes</option>
24
+ <option value="60" <%= params[:interval] == '60' ? 'selected' : '' %>>1 hour</option>
25
+ <option value="120" <%= params[:interval] == '120' ? 'selected' : '' %>>2 hours</option>
26
+ <option value="1440" <%= params[:interval] == '1440' ? 'selected' : '' %>>1 day</option>
27
+ </select>
28
+ </label>
13
29
  </form>
14
30
  </section>
15
31
 
@@ -17,7 +33,7 @@
17
33
 
18
34
  <div class="tile">
19
35
  <h2>CPU Usage</h2>
20
- <%= area_chart "/data/cpu_usage?duration=#{params[:duration] || "1d"}",
36
+ <%= area_chart chart_url("/data/cpu_usage"),
21
37
  ytitle: "CPU Usage (%)",
22
38
  min: 0,
23
39
  max: 100,
@@ -31,7 +47,7 @@
31
47
 
32
48
  <div class="tile">
33
49
  <h2>CPU Load</h2>
34
- <%= line_chart "/data/cpu_load?duration=#{params[:duration] || "1d"}",
50
+ <%= line_chart chart_url("/data/cpu_load"),
35
51
  ytitle: "Load Average",
36
52
  library: {
37
53
  title: {
@@ -43,7 +59,7 @@
43
59
 
44
60
  <div class="tile">
45
61
  <h2>Memory Usage</h2>
46
- <%= area_chart "/data/memory_usage?duration=#{params[:duration] || "1d"}",
62
+ <%= area_chart chart_url("/data/memory_usage"),
47
63
  ytitle: "Memory Usage (%)",
48
64
  min: 0,
49
65
  max: 100,
@@ -57,7 +73,7 @@
57
73
 
58
74
  <div class="tile">
59
75
  <h2>Filesystem Usage</h2>
60
- <%= area_chart "/data/filesystem_usage?duration=#{params[:duration] || "1d"}",
76
+ <%= area_chart chart_url("/data/filesystem_usage"),
61
77
  ytitle: "Used Space (%)",
62
78
  min: 0,
63
79
  max: 100,
@@ -71,7 +87,7 @@
71
87
 
72
88
  <div class="tile">
73
89
  <h2>Disk I/O</h2>
74
- <%= area_chart "/data/disk_io?duration=#{params[:duration] || "1d"}",
90
+ <%= area_chart chart_url("/data/disk_io"),
75
91
  ytitle: "MB/s",
76
92
  library: {
77
93
  title: {
@@ -83,7 +99,7 @@
83
99
 
84
100
  <div class="tile">
85
101
  <h2>Bandwidth</h2>
86
- <%= area_chart "/data/bandwidth?duration=#{params[:duration] || "1d"}",
102
+ <%= area_chart chart_url("/data/bandwidth"),
87
103
  ytitle: "Bandwidth (Mbps)",
88
104
  library: {
89
105
  title: {
@@ -8,7 +8,6 @@ Bundler.require(:default, ENV.fetch("RACK_ENV", nil))
8
8
  require "sinatra/contrib"
9
9
  require "sinatra/activerecord"
10
10
  require "rufus-scheduler"
11
- require "groupdate"
12
11
  require "chartkick"
13
12
  require "sqlite3"
14
13
  require "sys-filesystem"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PunyMonitor
4
- VERSION = '0.2.0'
4
+ VERSION = "0.3.0"
5
5
  end
data/public/style.css CHANGED
@@ -116,6 +116,20 @@ footer {
116
116
  gap: var(--space-md);
117
117
  margin-bottom: var(--space-sm);
118
118
 
119
+ form {
120
+ display: flex;
121
+ justify-content: center;
122
+ align-items: center;
123
+ gap: var(--space-md);
124
+ }
125
+
126
+ label {
127
+ display: flex;
128
+ justify-content: center;
129
+ align-items: center;
130
+ gap: var(--space-xs);
131
+ }
132
+
119
133
  select {
120
134
  font-size: var(--font-size-md);
121
135
  padding: var(--space-xxs) var(--space-xs);
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puny-monitor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hans Schnedlitz
@@ -23,20 +23,6 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '5.1'
26
- - !ruby/object:Gem::Dependency
27
- name: groupdate
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '6.4'
33
- type: :runtime
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '6.4'
40
26
  - !ruby/object:Gem::Dependency
41
27
  name: puma
42
28
  requirement: !ruby/object:Gem::Requirement
@@ -160,6 +146,7 @@ files:
160
146
  - LICENSE
161
147
  - README.md
162
148
  - app/authorization.rb
149
+ - app/models/application_model.rb
163
150
  - app/models/bandwidth.rb
164
151
  - app/models/cpu_load.rb
165
152
  - app/models/cpu_usage.rb