From a26727279685ba980c800ff78939b540c7e139bc Mon Sep 17 00:00:00 2001 From: Christian Lohmann Date: Fri, 23 Dec 2016 12:00:40 +0100 Subject: [PATCH] added sorting feature --- app.py | 32 +++++++++++++++------ app/src/app.js | 23 ++++++++------- app/src/node.js | 30 ++++++++++--------- app/src/pod.js | 67 +++++++++++++++++++++++++++++-------------- app/src/selectbox.js | 27 ++++++++++------- app/src/utils.js | 10 ++++++- app/webpack.config.js | 1 + 7 files changed, 124 insertions(+), 66 deletions(-) diff --git a/app.py b/app.py index 555d975..65c3c4d 100755 --- a/app.py +++ b/app.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import gevent.monkey + gevent.monkey.patch_all() import flask @@ -17,7 +18,6 @@ from flask import Flask, redirect from flask_oauthlib.client import OAuth, OAuthRemoteApp from urllib.parse import urljoin - DEFAULT_CLUSTERS = 'http://localhost:8001/' CREDENTIALS_DIR = os.getenv('CREDENTIALS_DIR', '') AUTHORIZE_URL = os.getenv('AUTHORIZE_URL') @@ -119,15 +119,20 @@ def generate_mock_cluster_data(index: int): phase = pod_phases[hash_int((index + 1) * (i + 1) * (j + 1)) % len(pod_phases)] containers = [] for k in range(1): - containers.append({'name': 'myapp', 'image': 'foo/bar/{}'.format(j), 'resources': {'requests': {'cpu': '100m', 'memory': '100Mi'}}}) + containers.append({'name': 'myapp', 'image': 'foo/bar/{}'.format(j), + 'resources': {'requests': {'cpu': '100m', 'memory': '100Mi'}}}) status = {'phase': phase} if phase == 'Running': if j % 13 == 0: - status['containerStatuses'] = [{'ready': False, 'state': {'waiting': {'reason': 'CrashLoopBackOff'}}}] + status['containerStatuses'] = [ + {'ready': False, 'state': {'waiting': {'reason': 'CrashLoopBackOff'}}}] elif j % 7 == 0: status['containerStatuses'] = [{'ready': True, 'state': {'running': {}}, 'restartCount': 3}] - pods.append({'name': 'my-pod-{}'.format(j), 'namespace': 'kube-system' if j < 3 else 'default', 'labels': labels, 'status': status, 'containers': containers}) - nodes.append({'name': 'node-{}'.format(i), 'labels': labels, 'status': {'capacity': {'cpu': '4', 'memory': '32Gi', 'pods': '110'}}, 'pods': pods}) + pods.append( + {'name': 'my-pod-{}'.format(j), 'namespace': 'kube-system' if j < 3 else 'default', 'labels': labels, + 'status': status, 'containers': containers}) + nodes.append({'name': 'node-{}'.format(i), 'labels': labels, + 'status': {'capacity': {'cpu': '4', 'memory': '32Gi', 'pods': '110'}}, 'pods': pods}) unassigned_pods = [] return { 'api_server_url': 'https://kube-{}.example.org'.format(index), @@ -156,7 +161,8 @@ def get_kubernetes_clusters(): pods_by_namespace_name = {} unassigned_pods = [] for node in response.json()['items']: - obj = {'name': node['metadata']['name'], 'labels': node['metadata']['labels'], 'status': node['status'], 'pods': []} + obj = {'name': node['metadata']['name'], 'labels': node['metadata']['labels'], 'status': node['status'], + 'pods': []} nodes.append(obj) nodes_by_name[obj['name']] = obj response = session.get(urljoin(api_server_url, '/api/v1/pods'), timeout=5) @@ -164,9 +170,15 @@ def get_kubernetes_clusters(): for pod in response.json()['items']: obj = {'name': pod['metadata']['name'], 'namespace': pod['metadata']['namespace'], - 'labels': pod['metadata'].get('labels', {}), 'status': pod['status'], 'containers': []} + 'labels': pod['metadata'].get('labels', {}), + 'status': pod['status'], + 'startTime': pod['status']['startTime'] if 'startTime' in pod['status'] else '', + 'containers': [] + } if 'deletionTimestamp' in pod['metadata']: - obj['deleted'] = datetime.datetime.strptime(pod['metadata']['deletionTimestamp'], '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=datetime.timezone.utc).timestamp() + obj['deleted'] = datetime.datetime.strptime(pod['metadata']['deletionTimestamp'], + '%Y-%m-%dT%H:%M:%SZ').replace( + tzinfo=datetime.timezone.utc).timestamp() for cont in pod['spec']['containers']: obj['containers'].append({'name': cont['name'], 'image': cont['image'], 'resources': cont['resources']}) pods_by_namespace_name[(obj['namespace'], obj['name'])] = obj @@ -183,7 +195,9 @@ def get_kubernetes_clusters(): except: logging.exception('Failed to get metrics') try: - response = session.get(urljoin(api_server_url, '/api/v1/namespaces/kube-system/services/heapster/proxy/apis/metrics/v1alpha1/pods'), timeout=5) + response = session.get(urljoin(api_server_url, + '/api/v1/namespaces/kube-system/services/heapster/proxy/apis/metrics/v1alpha1/pods'), + timeout=5) response.raise_for_status() for metrics in response.json()['items']: pod = pods_by_namespace_name.get((metrics['metadata']['namespace'], metrics['metadata']['name'])) diff --git a/app/src/app.js b/app/src/app.js index 8edd0c4..5b26d37 100644 --- a/app/src/app.js +++ b/app/src/app.js @@ -1,9 +1,10 @@ import Tooltip from './tooltip.js' import Cluster from './cluster.js' -import {Pod, ALL_PODS} from './pod.js' +import {Pod, ALL_PODS, sortByName, sortByMemory, sortByCPU, sortByAge} from './pod.js' import SelectBox from './selectbox' import { PRIMARY_VIOLET } from './colors.js' import 'pixi-display' + const PIXI = require('pixi.js') @@ -12,6 +13,7 @@ export default class App { constructor() { this.filterString = '' this.seenPods = {} + this.sorterFn = '' } filter() { @@ -54,13 +56,13 @@ export default class App { const menuBar = new PIXI.Graphics() menuBar.beginFill(PRIMARY_VIOLET, 1) - menuBar.drawRect(0, 0, window.innerWidth, 25) + menuBar.drawRect(0, 0, window.innerWidth, 35) menuBar.endFill() stage.addChild(menuBar) const searchPrompt = new PIXI.Text('>', {fontFamily: 'ShareTechMono', fontSize: 18}) searchPrompt.x = 20 - searchPrompt.y = 5 + searchPrompt.y = 10 PIXI.ticker.shared.add(function (_) { var v = Math.sin((PIXI.ticker.shared.lastTime % 2000) / 2000. * Math.PI) searchPrompt.alpha = v @@ -74,22 +76,23 @@ export default class App { const items = [ { - text: 'Name', sorterFn: () => {} + text: 'Name', sorterFn: sortByName }, { - text: 'Age', sorterFn: () => {} + text: 'Age', sorterFn: sortByAge }, { - text: 'Memory', sorterFn: () => {} + text: 'Memory', sorterFn: sortByMemory }, { - text: 'CPU', sorterFn: () => {} - }, - + text: 'CPU', sorterFn: sortByCPU + } ] + //setting default sort + this.sorterFn = items[0].sorterFn const selectBox = new SelectBox(items) selectBox.x = 265 - selectBox.y = 5 + selectBox.y = 3 const mainLayer = new PIXI.DisplayGroup(1, true) selectBox.displayGroup = mainLayer stage.addChild(selectBox.draw()) diff --git a/app/src/node.js b/app/src/node.js index 03572ac..9a077f4 100644 --- a/app/src/node.js +++ b/app/src/node.js @@ -1,6 +1,7 @@ import {Pod} from './pod.js' import Bars from './bars.js' import {parseResource} from './utils.js' +import App from './app' const PIXI = require('pixi.js') export default class Node extends PIXI.Graphics { @@ -43,9 +44,9 @@ export default class Node extends PIXI.Graphics { return resources } - draw () { - var nodeBox = this - var topHandle = new PIXI.Graphics() + draw() { + const nodeBox = this + const topHandle = new PIXI.Graphics() topHandle.beginFill(0xaaaaff, 1) topHandle.drawRect(0, 0, 105, 15) topHandle.endFill() @@ -62,8 +63,8 @@ export default class Node extends PIXI.Graphics { nodeBox.lineStyle(2, 0xaaaaaa, 1) topHandle.interactive = true topHandle.on('mouseover', function () { - var s = nodeBox.node.name - for (var key of Object.keys(nodeBox.node.labels)) { + let s = nodeBox.node.name + for (const key of Object.keys(nodeBox.node.labels)) { s += '\n' + key + ': ' + nodeBox.node.labels[key] } nodeBox.tooltip.setText(s) @@ -79,9 +80,16 @@ export default class Node extends PIXI.Graphics { bars.y = 1 nodeBox.addChild(bars.draw()) - var px = 24 - var py = 20 - for (const pod of this.node.pods) { + nodeBox.addPods(App.sorterFn) + return nodeBox + } + + addPods(sorterFn) { + const nodeBox = this + let px = 24 + let py = 20 + const pods = sorterFn !== 'undefined' ? this.node.pods.sort(sorterFn) : this.node.pods + for (const pod of pods) { if (pod.namespace != 'kube-system') { const podBox = Pod.getOrCreate(pod, this.cluster, this.tooltip) //new Pod(pod, this.tooltip) podBox.x = px @@ -93,11 +101,10 @@ export default class Node extends PIXI.Graphics { py += 13 } } - } px = 24 py = 100 - for (const pod of this.node.pods) { + for (const pod of pods) { if (pod.namespace == 'kube-system') { const podBox = Pod.getOrCreate(pod, this.cluster, this.tooltip) //new Pod(pod, this.tooltip) podBox.x = px @@ -109,9 +116,6 @@ export default class Node extends PIXI.Graphics { py -= 13 } } - } - return nodeBox } - } diff --git a/app/src/pod.js b/app/src/pod.js index 4ab1386..df47a23 100644 --- a/app/src/pod.js +++ b/app/src/pod.js @@ -1,7 +1,36 @@ const PIXI = require('pixi.js') -import {FACTORS, parseResource, getBarColor} from './utils.js' +import {FACTORS, getBarColor, podResource} from './utils.js' -export const ALL_PODS = {} +const ALL_PODS = {} + +const sortByName = (a, b) => { + return a.name.localeCompare(b.name) +} + +const sortByAge = (a, b) => { + const dateA = new Date(a.status.startTime) + const dateB = new Date(b.status.startTime) + if (dateA.getTime() < dateB.getTime()) { + return -1 + } else if (dateA.getTime() === dateB.getTime()) + return 0 + else + return 1 +} + +const sortByMemory = (a, b) => { + const aMem = podResource('memory')(a.containers, 'usage') + const bMem = podResource('memory')(b.containers, 'usage') + return bMem - aMem +} + +const sortByCPU = (a, b) => { + const aCpu = podResource('cpu')(a.containers, 'usage') + const bCpu = podResource('cpu')(b.containers, 'usage') + return bCpu - aCpu +} + +export {ALL_PODS, sortByAge, sortByCPU, sortByMemory, sortByName} export class Pod extends PIXI.Graphics { @@ -18,13 +47,6 @@ export class Pod extends PIXI.Graphics { } getResourceUsage() { - const metric = (metric, type) => - metric ? (metric[type] ? parseResource(metric[type]) : 0) : 0 - - const podResource = type => (containers, resource) => - containers - .map(({resources}) => metric(resources[resource], type)) - .reduce((a, b) => a + b, 0) const podCpu = podResource('cpu') const podMem = podResource('memory') @@ -63,7 +85,7 @@ export class Pod extends PIXI.Graphics { } pulsate(time) { - const v = Math.sin((PIXI.ticker.shared.lastTime % 1000)/1000.* Math.PI) + const v = Math.sin((PIXI.ticker.shared.lastTime % 1000) / 1000. * Math.PI) this.alpha = v * this._progress } @@ -82,9 +104,9 @@ export class Pod extends PIXI.Graphics { // pod.status.containerStatuses might be undefined! const containerStatuses = this.pod.status.containerStatuses || [] - var ready = 0 - var running = 0 - var restarts = 0 + let ready = 0 + let running = 0 + let restarts = 0 for (const containerStatus of containerStatuses) { if (containerStatus.ready) { ready++ @@ -98,17 +120,18 @@ export class Pod extends PIXI.Graphics { const allRunning = running >= containerStatuses.length const resources = this.getResourceUsage() - var newTick = null + let newTick = null const podBox = this podBox.interactive = true - podBox.on('mouseover', function() { + podBox.on('mouseover', function () { const filter = new PIXI.filters.ColorMatrixFilter() filter.brightness(1.3) podBox.filters = [filter] let s = this.pod.name - s += '\nStatus: ' + this.pod.status.phase + ' (' + ready + '/' + containerStatuses.length + ' ready)' - s += '\nLabels:' + s += '\nStatus : ' + this.pod.status.phase + ' (' + ready + '/' + containerStatuses.length + ' ready)' + s += '\nStart Time: ' + this.pod.status.startTime + s += '\nLabels :' for (var key of Object.keys(this.pod.labels)) { if (key !== 'pod-template-hash') { s += '\n ' + key + ': ' + this.pod.labels[key] @@ -144,8 +167,8 @@ export class Pod extends PIXI.Graphics { this.tooltip.visible = false }) podBox.lineStyle(2, 0xaaaaaa, 1) - var i = 0 - var w = 10 / this.pod.containers.length + let i = 0 + const w = 10 / this.pod.containers.length for (const container of this.pod.containers) { podBox.drawRect(i * w, 0, w, 10) i++ @@ -190,9 +213,9 @@ export class Pod extends PIXI.Graphics { if (restarts) { this.lineStyle(2, 0xff9999, 1) - for (let i=0; i + metric ? (metric[type] ? parseResource(metric[type]) : 0) : 0 + +const podResource = type => (containers, resource) => + containers + .map(({resources}) => metric(resources[resource], type)) + .reduce((a, b) => a + b, 0) + +export {FACTORS, hsvToRgb, getBarColor, parseResource, metric, podResource} diff --git a/app/webpack.config.js b/app/webpack.config.js index 09e2b42..65427ed 100644 --- a/app/webpack.config.js +++ b/app/webpack.config.js @@ -33,6 +33,7 @@ module.exports = { {test: /\.html$/, exclude: /node_modules/, loader: 'file-loader?name=[path][name].[ext]'}, {test: /\.jpe?g$|\.svg$|\.png$/, exclude: /node_modules/, loader: 'file-loader?name=[path][name].[ext]'}, {test: /\.json$/, exclude: /node_modules/, loader: 'json'}, + {test: /\.(otf|eot|svg|ttf|woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=8192&mimetype=application/font-woff'}, {test: /\.json$/, include: path.join(__dirname, 'node_modules', 'pixi.js'), loader: 'json'} ], postLoaders: [{