added live reloading metrics

This commit is contained in:
Chris Zhu
2024-11-09 11:45:10 -08:00
parent 3f7ea0848f
commit fd7b73e59d
14 changed files with 199 additions and 214 deletions

View File

@@ -95,3 +95,7 @@ gem "httparty", "~> 0.22.0"
gem "redcarpet", "~> 3.6" gem "redcarpet", "~> 3.6"
gem "rubyzip", "~> 2.3" gem "rubyzip", "~> 2.3"
gem "chartkick", "~> 5.1"
gem "groupdate", "~> 6.5"

View File

@@ -111,6 +111,7 @@ GEM
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0) regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2) xpath (~> 3.2)
chartkick (5.1.2)
chronic (0.10.2) chronic (0.10.2)
chunky_png (1.4.0) chunky_png (1.4.0)
coderay (1.1.3) coderay (1.1.3)
@@ -177,6 +178,8 @@ GEM
activerecord (>= 4.0.0) activerecord (>= 4.0.0)
globalid (1.2.1) globalid (1.2.1)
activesupport (>= 6.1) activesupport (>= 6.1)
groupdate (6.5.1)
activesupport (>= 7)
hashdiff (1.0.1) hashdiff (1.0.1)
hashie (5.0.0) hashie (5.0.0)
http (5.2.0) http (5.2.0)
@@ -518,11 +521,13 @@ DEPENDENCIES
bootsnap bootsnap
brakeman brakeman
capybara capybara
chartkick (~> 5.1)
cssbundling-rails cssbundling-rails
debug debug
devise (~> 4.9) devise (~> 4.9)
dotenv (~> 3.1) dotenv (~> 3.1)
friendly_id (~> 5.4) friendly_id (~> 5.4)
groupdate (~> 6.5)
httparty (~> 0.22.0) httparty (~> 0.22.0)
image_processing (~> 1.13) image_processing (~> 1.13)
importmap-rails importmap-rails

View File

@@ -1,4 +1,4 @@
web: bin/rails server -p 3000 web: bin/rails server -p 3000
worker: bundle exec sidekiq worker: bundle exec sidekiq
js: yarn build --reload js: yarn build --watch
css: bin/rails tailwindcss:watch css: bin/rails tailwindcss:watch

View File

@@ -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;

View File

@@ -12,4 +12,4 @@ require("@rails/ujs").start()
import './channels/**/*_channel.js' import './channels/**/*_channel.js'
import "./controllers" import "./controllers"
import "./apex" import "chartkick/chart.js"

View File

@@ -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();
}
}

View File

@@ -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)
}
}
}

View File

@@ -6,42 +6,46 @@ class K8::Metrics::Metrics
nodes = K8::Metrics::Api::Node.ls(cluster) nodes = K8::Metrics::Api::Node.ls(cluster)
metrics = [] metrics = []
nodes.each do |node| nodes.each do |node|
tags = [ "node:#{node.name}" ]
metrics << { metrics << {
metric_type: :cpu, metric_type: :cpu,
tags: [ node.name ], tags:,
metadata: { cpu: node.cpu_cores } metadata: { cpu: node.cpu_cores }
} }
metrics << { metrics << {
metric_type: :memory, metric_type: :memory,
tags: [ node.name ], tags:,
metadata: { memory: node.used_memory } metadata: { memory: node.used_memory }
} }
metrics << { metrics << {
metric_type: :total_cpu, metric_type: :total_cpu,
tags: [ node.name ], tags:,
metadata: { total_cpu: node.total_cpu } metadata: { total_cpu: node.total_cpu }
} }
metrics << { metrics << {
metric_type: :total_memory, metric_type: :total_memory,
tags: [ node.name ], tags:,
metadata: { total_memory: node.total_memory } metadata: { total_memory: node.total_memory }
} }
node.namespaces.each do |namespace, pods| node.namespaces.each do |namespace, pods|
pods.each do |pod| pods.each do |pod|
tags = [ "node:#{node.name}", "namespace:#{namespace}", "pod:#{pod.name}" ]
metrics << { metrics << {
metric_type: :cpu, metric_type: :cpu,
tags: [ node.name, namespace, pod.name ], tags:,
metadata: { cpu: pod.cpu } metadata: { cpu: pod.cpu }
} }
metrics << { metrics << {
metric_type: :memory, metric_type: :memory,
tags: [ node.name, namespace, pod.name ], tags:,
metadata: { memory: pod.memory } metadata: { memory: pod.memory }
} }
end end
end end
end end
cluster.metrics.create(attributes) metrics.each do |metric|
cluster.metrics.create(**metric)
end
end end
end end

View File

@@ -1,6 +0,0 @@
<div class="card">
<div class="card-body">
<h2 class="font-medium"><%= title %></h2>
<div data-controller="chart" data-chart-dataset-value="<%= dataset.to_json %>"></div>
</div>
</div>

View File

@@ -0,0 +1,119 @@
<div>
<div class="flex space-x-2">
<div class="badge badge-success">• LIVE</div>
<div class="text-sm text-gray-500">
Updated at: <%= Time.now.strftime("%H:%M:%S") %>
</div>
</div>
<table class="table mt-2 rounded-box" id="order_table" data-component="table">
<thead>
<tr>
<th>
<span class="text-sm font-medium text-base-content/80">Node Name</span>
</th>
<th>
<span class="text-sm font-medium text-base-content/80">CPU Cores</span>
</th>
<th>
<span class="text-sm font-medium text-base-content/80">
CPU Usage
</span>
</th>
<th>
<span class="text-sm font-medium text-base-content/80">
Memory Capacity
</span>
</th>
<th>
<span class="text-sm font-medium text-base-content/80">
Memory Usage
</span>
</th>
</tr>
</thead>
<tbody>
<% @nodes.each do |node| %>
<tr class="cursor-pointer hover:bg-base-200/40">
<td>
<div class="flex items-center space-x-3 truncate">
<div class="font-medium">
<%= node.name %>
</div>
</div>
</td>
<td>
<div class="font-medium">
<%= integer_to_size(node.total_cpu) %>
</div>
</td>
<td>
<div class="font-medium">
<%= node.cpu_percent %>%
</div>
</td>
<td>
<div class="font-medium">
<%= integer_to_size(node.total_memory) %>
</div>
</td>
<td>
<div class="font-medium">
<%= node.memory_percent %>%
</div>
</td>
</tr>
<% end %>
</tbody>
</table>
<table class="table mt-2 rounded-box" data-component="table">
<thead>
<tr>
<th>
<span class="text-sm font-medium text-base-content/80">Namespace</span>
</th>
<th>
<span class="text-sm font-medium text-base-content/80">Pod Name</span>
</th>
<th>
<span class="text-sm font-medium text-base-content/80">CPU</span>
</th>
<th>
<span class="text-sm font-medium text-base-content/80">
Memory
</span>
</th>
</tr>
</thead>
<tbody>
<% @nodes.each do |node| %>
<% node.namespaces.each do |namespace, pods| %>
<% pods.each do |pod| %>
<tr class="cursor-pointer hover:bg-base-200/40">
<td>
<div class="flex items-center space-x-3 truncate font-medium">
<%= namespace %>
</div>
</td>
<td>
<div class="flex items-center space-x-3 truncate font-medium">
<%= pod.name %>
</div>
</td>
<td>
<div class="font-medium">
<%= pod.cpu %>
</div>
</td>
<td>
<div class="font-medium">
<%= integer_to_size(pod.memory) %>
</div>
</td>
</tr>
<% end %>
<% end %>
<% end %>
</tbody>
</table>
</div>

View File

@@ -1,114 +1,6 @@
<%= cluster_layout(@cluster) do %> <%= cluster_layout(@cluster) do %>
<script src="https://cdnjs.cloudflare.com/ajax/libs/apexcharts/3.53.0/apexcharts.min.js"></script> <%= turbo_frame_tag "metrics", data: { controller: "refresh-turbo-frame", "refresh-turbo-frame-frequency-value": 5000 } do %>
<table class="table mt-2 rounded-box" id="order_table" data-component="table"> <%= render "live_metrics" %>
<thead> <% end %>
<tr>
<th>
<span class="text-sm font-medium text-base-content/80">Node Name</span>
</th>
<th>
<span class="text-sm font-medium text-base-content/80">CPU Cores</span>
</th>
<th>
<span class="text-sm font-medium text-base-content/80">
CPU Usage
</span>
</th>
<th>
<span class="text-sm font-medium text-base-content/80">
Memory Capacity
</span>
</th>
<th>
<span class="text-sm font-medium text-base-content/80">
Memory Usage
</span>
</th>
</tr>
</thead>
<tbody>
<% @nodes.each do |node| %>
<tr class="cursor-pointer hover:bg-base-200/40">
<td>
<div class="flex items-center space-x-3 truncate">
<div class="font-medium">
<%= node.name %>
</div>
</div>
</td>
<td>
<div class="font-medium">
<%= integer_to_size(node.total_cpu) %>
</div>
</td>
<td>
<div class="font-medium">
<%= node.cpu_percent %>%
</div>
</td>
<td>
<div class="font-medium">
<%= integer_to_size(node.total_memory) %>
</div>
</td>
<td>
<div class="font-medium">
<%= node.memory_percent %>%
</div>
</td>
</tr>
<% end %>
</tbody>
</table>
<table class="table mt-2 rounded-box" data-component="table">
<thead>
<tr>
<th>
<span class="text-sm font-medium text-base-content/80">Namespace</span>
</th>
<th>
<span class="text-sm font-medium text-base-content/80">Pod Name</span>
</th>
<th>
<span class="text-sm font-medium text-base-content/80">CPU</span>
</th>
<th>
<span class="text-sm font-medium text-base-content/80">
Memory
</span>
</th>
</tr>
</thead>
<tbody>
<% @nodes.each do |node| %>
<% node.namespaces.each do |namespace, pods| %>
<% pods.each do |pod| %>
<tr class="cursor-pointer hover:bg-base-200/40">
<td>
<div class="flex items-center space-x-3 truncate font-medium">
<%= namespace %>
</div>
</td>
<td>
<div class="flex items-center space-x-3 truncate font-medium">
<%= pod.name %>
</div>
</td>
<td>
<div class="font-medium">
<%= pod.cpu %>
</div>
</td>
<td>
<div class="font-medium">
<%= integer_to_size(pod.memory) %>
</div>
</td>
</tr>
<% end %>
<% end %>
<% end %>
</tbody>
</table>
<% end %> <% end %>

View File

@@ -21,7 +21,7 @@ namespace :metrics do
end end
desc "Poll Kubernetes cluster metrics" desc "Poll Kubernetes cluster metrics"
task nodes: :environment do task fetch: :environment do
Cluster.running.each do |cluster| Cluster.running.each do |cluster|
nodes = K8::Metrics::Metrics.call(cluster) nodes = K8::Metrics::Metrics.call(cluster)
end end

View File

@@ -23,6 +23,8 @@
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.15",
"apexcharts": "^3.54.0", "apexcharts": "^3.54.0",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"chart.js": "^4.4.6",
"chartkick": "^5.0.1",
"chokidar": "^4.0.1", "chokidar": "^4.0.1",
"daisyui": "^4.12.10", "daisyui": "^4.12.10",
"esbuild-rails": "^1.0.7", "esbuild-rails": "^1.0.7",

View File

@@ -210,6 +210,11 @@
"@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14" "@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": "@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0":
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz#2f3a8f1d688935c704dbc89132394a41029acbb8" 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" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001663.tgz#1529a723505e429fdfd49532e9fc42273ba7fed7"
integrity sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA== 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: chokidar@^3.3.0, chokidar@^3.5.2, chokidar@^3.5.3:
version "3.6.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
@@ -539,6 +565,11 @@ daisyui@^4.12.10:
picocolors "^1" picocolors "^1"
postcss-js "^4" 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: debug@^4:
version "4.3.7" version "4.3.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"