diff --git a/Gemfile b/Gemfile index 903d0d08..3e6af523 100644 --- a/Gemfile +++ b/Gemfile @@ -95,3 +95,7 @@ gem "httparty", "~> 0.22.0" gem "redcarpet", "~> 3.6" gem "rubyzip", "~> 2.3" + +gem "chartkick", "~> 5.1" + +gem "groupdate", "~> 6.5" diff --git a/Gemfile.lock b/Gemfile.lock index 293cf19a..7524b1cc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -111,6 +111,7 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) + chartkick (5.1.2) chronic (0.10.2) chunky_png (1.4.0) coderay (1.1.3) @@ -177,6 +178,8 @@ GEM activerecord (>= 4.0.0) globalid (1.2.1) activesupport (>= 6.1) + groupdate (6.5.1) + activesupport (>= 7) hashdiff (1.0.1) hashie (5.0.0) http (5.2.0) @@ -518,11 +521,13 @@ DEPENDENCIES bootsnap brakeman capybara + chartkick (~> 5.1) cssbundling-rails debug devise (~> 4.9) dotenv (~> 3.1) friendly_id (~> 5.4) + groupdate (~> 6.5) httparty (~> 0.22.0) image_processing (~> 1.13) importmap-rails diff --git a/Procfile.dev b/Procfile.dev index a43b0e8b..61641ddc 100644 --- a/Procfile.dev +++ b/Procfile.dev @@ -1,4 +1,4 @@ web: bin/rails server -p 3000 worker: bundle exec sidekiq -js: yarn build --reload +js: yarn build --watch css: bin/rails tailwindcss:watch diff --git a/app/javascript/apex/index.js b/app/javascript/apex/index.js deleted file mode 100644 index 8b6da8fe..00000000 --- a/app/javascript/apex/index.js +++ /dev/null @@ -1,75 +0,0 @@ -import ApexCharts from "apexcharts"; -const ORDERED_COLORS = [ - "#a25772", - "#3e5eff" -]; - - -class Apex { - constructor(element, datasets) { - this.element = element; - this.datasets = datasets; - this.xAxis = this.datasets.map(d => d.metrics.map(m => new Date(m.created_at))).flat().sort(); - } - - options() { - return { - chart: { - events: { - mounted: (c) => c.windowResizeHandler(), - }, - type: "line", - height: 400, - background: "transparent", - toolbar: { - show: true, - tools: { - download: true, - zoom: false, - zoomin: false, - zoomout: false, - pan: false, - reset: false, - }, - }, - group: "metrics", - }, - theme: { - mode: 'dark', - }, - stroke: { - width: 2, - }, - xaxis: { - categories: this.xAxis, - }, - series: this.datasets.map((dataset) => { - let data = dataset.metrics - if (dataset.type === "size") { - data = data.map(m => m.value) - } else { - data = data.map(m => m.value) - } - return { - name: dataset.name, - data, - } - }) - } - }; - - createSeriesData(data, index) { - return [{ - name: data.name, - data: data.data, - color: ORDERED_COLORS[index], - }]; - } - - render() { - var chart = new ApexCharts(this.element, this.options()); - chart.render() - return chart; - } -} -export default Apex; diff --git a/app/javascript/application.js b/app/javascript/application.js index 2ae7ca54..9392b1c3 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -12,4 +12,4 @@ require("@rails/ujs").start() import './channels/**/*_channel.js' import "./controllers" -import "./apex" +import "chartkick/chart.js" diff --git a/app/javascript/controllers/chart_controller.js b/app/javascript/controllers/chart_controller.js deleted file mode 100644 index 0405cc26..00000000 --- a/app/javascript/controllers/chart_controller.js +++ /dev/null @@ -1,12 +0,0 @@ -import Apex from "../apex"; -import { Controller } from "@hotwired/stimulus" - -export default class extends Controller { - static values = { - dataset: Array, - } - - connect() { - new Apex(this.element, this.datasetValue).render(); - } -} diff --git a/app/javascript/controllers/refresh_turbo_frame_controller.js b/app/javascript/controllers/refresh_turbo_frame_controller.js new file mode 100644 index 00000000..1d3ab8ce --- /dev/null +++ b/app/javascript/controllers/refresh_turbo_frame_controller.js @@ -0,0 +1,21 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static values = { + frequency: Number + } + + connect() { + console.log(this.frequencyValue) + this.refreshInterval = setInterval(() => { + console.log("Updating src"); + this.element.setAttribute("src", window.location.href); + }, this.frequencyValue) + } + + disconnect() { + if (this.refreshInterval) { + clearInterval(this.refreshInterval) + } + } +} diff --git a/app/services/k8/metrics/metrics.rb b/app/services/k8/metrics/metrics.rb index 2c5e5ec9..911eaf84 100644 --- a/app/services/k8/metrics/metrics.rb +++ b/app/services/k8/metrics/metrics.rb @@ -6,42 +6,46 @@ class K8::Metrics::Metrics nodes = K8::Metrics::Api::Node.ls(cluster) metrics = [] nodes.each do |node| + tags = [ "node:#{node.name}" ] metrics << { metric_type: :cpu, - tags: [ node.name ], + tags:, metadata: { cpu: node.cpu_cores } } metrics << { metric_type: :memory, - tags: [ node.name ], + tags:, metadata: { memory: node.used_memory } } metrics << { metric_type: :total_cpu, - tags: [ node.name ], + tags:, metadata: { total_cpu: node.total_cpu } } metrics << { metric_type: :total_memory, - tags: [ node.name ], + tags:, metadata: { total_memory: node.total_memory } } node.namespaces.each do |namespace, pods| pods.each do |pod| + tags = [ "node:#{node.name}", "namespace:#{namespace}", "pod:#{pod.name}" ] metrics << { metric_type: :cpu, - tags: [ node.name, namespace, pod.name ], + tags:, metadata: { cpu: pod.cpu } } metrics << { metric_type: :memory, - tags: [ node.name, namespace, pod.name ], + tags:, metadata: { memory: pod.memory } } end end end - cluster.metrics.create(attributes) + metrics.each do |metric| + cluster.metrics.create(**metric) + end end end diff --git a/app/views/charts/_chart.html.erb b/app/views/charts/_chart.html.erb deleted file mode 100644 index ab4ea075..00000000 --- a/app/views/charts/_chart.html.erb +++ /dev/null @@ -1,6 +0,0 @@ -
-
-

<%= title %>

-
-
-
diff --git a/app/views/clusters/metrics/_live_metrics.html.erb b/app/views/clusters/metrics/_live_metrics.html.erb new file mode 100644 index 00000000..dc9b9746 --- /dev/null +++ b/app/views/clusters/metrics/_live_metrics.html.erb @@ -0,0 +1,119 @@ +
+
+
• LIVE
+
+ Updated at: <%= Time.now.strftime("%H:%M:%S") %> +
+
+ + + + + + + + + + + + <% @nodes.each do |node| %> + + + + + + + + <% end %> + +
+ Node Name + + CPU Cores + + + CPU Usage + + + + Memory Capacity + + + + Memory Usage + +
+
+
+ <%= node.name %> +
+
+
+
+ <%= integer_to_size(node.total_cpu) %> +
+
+
+ <%= node.cpu_percent %>% +
+
+
+ <%= integer_to_size(node.total_memory) %> +
+
+
+ <%= node.memory_percent %>% +
+
+ + + + + + + + + + + + <% @nodes.each do |node| %> + <% node.namespaces.each do |namespace, pods| %> + <% pods.each do |pod| %> + + + + + + + <% end %> + <% end %> + <% end %> + +
+ Namespace + + Pod Name + + CPU + + + Memory + +
+
+ <%= namespace %> +
+
+
+ <%= pod.name %> +
+
+
+ <%= pod.cpu %> +
+
+
+ <%= integer_to_size(pod.memory) %> +
+
+
\ No newline at end of file diff --git a/app/views/clusters/metrics/show.html.erb b/app/views/clusters/metrics/show.html.erb index 5311d4b5..ce08ec62 100644 --- a/app/views/clusters/metrics/show.html.erb +++ b/app/views/clusters/metrics/show.html.erb @@ -1,114 +1,6 @@ <%= cluster_layout(@cluster) do %> - - - - - - - - - - - - - <% @nodes.each do |node| %> - - - - - - - - <% end %> - -
- Node Name - - CPU Cores - - - CPU Usage - - - - Memory Capacity - - - - Memory Usage - -
-
-
- <%= node.name %> -
-
-
-
- <%= integer_to_size(node.total_cpu) %> -
-
-
- <%= node.cpu_percent %>% -
-
-
- <%= integer_to_size(node.total_memory) %> -
-
-
- <%= node.memory_percent %>% -
-
+ <%= turbo_frame_tag "metrics", data: { controller: "refresh-turbo-frame", "refresh-turbo-frame-frequency-value": 5000 } do %> + <%= render "live_metrics" %> + <% end %> - - - - - - - - - - - <% @nodes.each do |node| %> - <% node.namespaces.each do |namespace, pods| %> - <% pods.each do |pod| %> - - - - - - - <% end %> - <% end %> - <% end %> - -
- Namespace - - Pod Name - - CPU - - - Memory - -
-
- <%= namespace %> -
-
-
- <%= pod.name %> -
-
-
- <%= pod.cpu %> -
-
-
- <%= integer_to_size(pod.memory) %> -
-
<% end %> diff --git a/lib/tasks/metrics.rake b/lib/tasks/metrics.rake index 3111b7bb..d1b9a95a 100644 --- a/lib/tasks/metrics.rake +++ b/lib/tasks/metrics.rake @@ -21,7 +21,7 @@ namespace :metrics do end desc "Poll Kubernetes cluster metrics" - task nodes: :environment do + task fetch: :environment do Cluster.running.each do |cluster| nodes = K8::Metrics::Metrics.call(cluster) end diff --git a/package.json b/package.json index 0b66a357..494d807c 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,8 @@ "@tailwindcss/typography": "^0.5.15", "apexcharts": "^3.54.0", "autoprefixer": "^10.4.20", + "chart.js": "^4.4.6", + "chartkick": "^5.0.1", "chokidar": "^4.0.1", "daisyui": "^4.12.10", "esbuild-rails": "^1.0.7", diff --git a/yarn.lock b/yarn.lock index 43d5b874..fc6deb19 100644 --- a/yarn.lock +++ b/yarn.lock @@ -210,6 +210,11 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@kurkle/color@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f" + integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw== + "@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0": version "1.2.1" resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz#2f3a8f1d688935c704dbc89132394a41029acbb8" @@ -449,6 +454,27 @@ caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001663: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001663.tgz#1529a723505e429fdfd49532e9fc42273ba7fed7" integrity sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA== +chart.js@4, chart.js@^4.4.6: + version "4.4.6" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.6.tgz#da39b84ca752298270d4c0519675c7659936abec" + integrity sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA== + dependencies: + "@kurkle/color" "^0.3.0" + +chartjs-adapter-date-fns@>=3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-3.0.0.tgz#c25f63c7f317c1f96f9a7c44bd45eeedb8a478e5" + integrity sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg== + +chartkick@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/chartkick/-/chartkick-5.0.1.tgz#f557ff8560f974343dc65c7fc34ce1e8326d8ee7" + integrity sha512-4F3tWI3eBQgnjCYZIZ+fHOaJuNyxeyhDE2Tm+voOWB19hDjSJceys/spzN52DOn8bWepNESGXvPVTGU1jeFsbA== + optionalDependencies: + chart.js "4" + chartjs-adapter-date-fns ">=3" + date-fns ">=2" + chokidar@^3.3.0, chokidar@^3.5.2, chokidar@^3.5.3: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" @@ -539,6 +565,11 @@ daisyui@^4.12.10: picocolors "^1" postcss-js "^4" +date-fns@>=2: + version "4.1.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14" + integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg== + debug@^4: version "4.3.7" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"