├── .gitignore
├── README.md
├── client
├── Components
│ ├── DropdownMenu.jsx
│ ├── NodeID.jsx
│ ├── UsageDetails.jsx
│ └── UsageDisplay.jsx
├── Containers
│ ├── DropDownContainer.jsx
│ ├── LogContainer.jsx
│ ├── MainContainer.jsx
│ └── NodeIDContainer.jsx
├── app.css
├── app.jsx
├── images
│ ├── KlusterBud-Logo-NoBG.png
│ ├── KlusterGif-Animated Image (Large).gif
│ └── logo.png
├── index.html
├── index.jsx
└── main.js
├── package-lock.json
├── package.json
├── server
├── controller
│ ├── RSController.js
│ └── queryController.js
└── server.js
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env
77 | .env.development.local
78 | .env.test.local
79 | .env.production.local
80 | .env.local
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Gatsby files
95 | .cache/
96 | # Comment in the public line in if your project uses Gatsby and not Next.js
97 | # https://nextjs.org/blog/next-9-1#public-directory-support
98 | # public
99 |
100 | # vuepress build output
101 | .vuepress/dist
102 |
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 |
107 | # Docusaurus cache and generated files
108 | .docusaurus
109 |
110 | # Serverless directories
111 | .serverless/
112 |
113 | # FuseBox cache
114 | .fusebox/
115 |
116 | # DynamoDB Local files
117 | .dynamodb/
118 |
119 | # TernJS port file
120 | .tern-port
121 |
122 | # Stores VSCode versions used for testing VSCode extensions
123 | .vscode-test
124 |
125 | # yarn v2
126 | .yarn/cache
127 | .yarn/unplugged
128 | .yarn/build-state.yml
129 | .yarn/install-state.gz
130 | .pnp.*
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | KlusterBud
5 |
6 | Klusterbud is a Kubernetes developers tool used to detect anomalies in the performance of nodes within a replica set.
7 |
8 |
9 |
10 | ## Built Using
11 |
12 | 
13 | 
14 | 
15 | 
16 | 
17 | 
18 | 
19 | 
20 | 
21 | 
22 | 
23 |
24 |
25 |
26 | ## Table of Contents
27 |
28 | - [About KlusterBud](#about-klusterbud)
29 | - [Vision Timeline](#vision-timeline)
30 | - [Getting Started](#getting-started-using-klusterbud)
31 | - [Demo](#demo)
32 | - [Publications](#publications)
33 | - [Website](#website)
34 | - [Contributors](#contributors)
35 |
36 | ## About Klusterbud
37 |
38 | KlusterBud makes PromQL queries to the user's running instance of prometheus, collecting metrics from nodes and comparing them to other nodes within the same replica set. When an anomaly is found, the identifying information of the node is served back to the user. Users can choose from a dropdown list of replica sets in their cluster, and start watching for any anomalies.
39 |
40 | ## Vision Timeline
41 |
42 | - ✅ Develop application to pull CPU resonse time from a replica set
43 | - ✅ Consolidate data from a replica set to isolate a single outlier node within it
44 | - ⏳ Containerize application to be deployed directly on monitored cluster
45 | - ⏳ Remove need for the user to manually initialize their own Prometheus server
46 | - ⏳ Develop functionality for monitoring multiple replica sets concurrently
47 | - 🤝 Allow users to input their own margins and metrics to define outlier nodes
48 | - 🤝 Develop live monitoring features for a production workflow
49 | - 🤝 Generate live monitoring visualization
50 |
51 | ✅ = Completed
52 | ⏳ = Coming soon
53 | 🤝 = Seeking Support
54 |
55 | ## Getting Started Using KlusterBud
56 |
57 | - Have Prometheus installed on Kubernetes cluster
58 | - Have Prometheus port-forwarded to `localhost:9090`
59 | - Run these commands
60 |
61 | - ```js
62 | npm install
63 | npm run build
64 | npm start
65 | ```
66 |
67 | - Select node from drop down menu
68 |
69 | ## Demo
70 |
71 |
72 |
73 |
74 |
75 | ## Publications
76 |
77 | [](https://medium.com/@AMitchell-Smith/klusterbud-k8s-anomaly-detection-made-simple-060ca496aebc)
78 |
79 | ## Website
80 |
81 | ### Check out our [Website](https://www.klusterbud.com) and [](https://www.linkedin.com/company/klusterbud)
82 |
83 | ## Contributors:
84 |
85 | ###
Asher Mitchell-Smith
[](https://github.com/The-Onion-Man) [](https://www.linkedin.com/in/asher-mitchell-smith-507683154/)
86 |
87 | ### Christopher Lu
[](https://github.com/christopher-lu) [](https://www.linkedin.com/in/christopher-lu19/)
88 |
89 | ### Fredy Rubio
[](https://github.com/FredyRubio) [](https://www.linkedin.com/in/fredy-rubio/)
90 |
91 | ### Michael Wang
[](https://github.com/mwang934) [](www.linkedin.com/in/michael-w-9475412a2)
92 |
93 | [Back to top of page](#klusterbud)
94 |
--------------------------------------------------------------------------------
/client/Components/DropdownMenu.jsx:
--------------------------------------------------------------------------------
1 | import React, {useState, useEffect} from "react";
2 | import { Dropdown, DropdownButton } from "react-bootstrap";
3 | import axios from "axios";
4 |
5 | export const DropdownMenu = (props) => {
6 | const handleSelect = (event) =>{
7 | const selectedValue = event.target.value;
8 | props.setSelectedRS(selectedValue)
9 | }
10 | return (
11 |
12 |
15 | {/* map our array of replica sets to an array of 'option' elements to populate our dropdown*/}
16 | {props.replicaSets.map((value, index) => (
17 |
18 | {value}
19 |
20 | ))}
21 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/client/Components/NodeID.jsx:
--------------------------------------------------------------------------------
1 | import React, {useState, useEffect} from "react";
2 |
3 | export const NodeID = (props) => {
4 | return(
5 | //insert logic to pull node ID
6 | <>
7 | {props.abnormalNode !== null && (
8 | Abnormal Node: {props.abnormalNode}
9 | )}
10 | >
11 | )
12 | }
--------------------------------------------------------------------------------
/client/Components/UsageDetails.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const UsageDetails = ({value, responseData}) => {
4 | return(
5 | //insert logic for retrieving the node details to be displayed
6 |
7 | 'Usage Details Inserted here'
8 |
9 |
10 | )
11 | }
--------------------------------------------------------------------------------
/client/Components/UsageDisplay.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 |
3 | export const UsageDisplay = (props) => {
4 | return (
5 | <>
6 | {props.abnormalNode !== null && (
7 |
8 |
9 |
10 | )}
11 | >
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/client/Containers/DropDownContainer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Container } from 'react-bootstrap';
3 | import ReplicaSetSelector, { DropdownMenu } from '../Components/DropdownMenu';
4 | import { Dropdown } from 'react-bootstrap';
5 |
6 | export const DropDownContainer = (props) => {
7 | return(
8 |
9 |
10 |
12 |
13 | )
14 | }
--------------------------------------------------------------------------------
/client/Containers/LogContainer.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { NodeSelector } from "../Components/DropdownMenu";
3 | import { Container } from "react-bootstrap";
4 | import { UsageDisplay } from "../Components/UsageDisplay";
5 | import { UsageDetails } from "../Components/UsageDetails";
6 |
7 |
8 |
9 | export const LogContainer = (props) => {
10 | return(
11 |
12 |
13 |
14 | {/* */}
15 |
16 |
17 | )
18 | }
--------------------------------------------------------------------------------
/client/Containers/MainContainer.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useState, useEffect } from "react";
3 | import axios from "axios"; // not using axios just yet, possible iteration
4 | import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
5 | import { Container, Button } from "react-bootstrap";
6 | import { ReplicaSetSelector } from "../Components/DropdownMenu";
7 | import { LogContainer } from "./LogContainer";
8 | import { DropDownContainer } from "./DropDownContainer";
9 | import { NodeIDContainer } from "./NodeIDContainer";
10 |
11 |
12 |
13 | const MainContainer = () => {
14 | const [replicaSets, setReplicaSets] = useState([]); //array of names of replica sets in our cluster
15 | const [selectedRS, setSelectedRS] = useState(''); //the name of the replica set selected by the user from the dropdown
16 | const [abnormalNode, setAbnormalNode] = useState(null); //the name of the node behaving abnormally(within our selected replica set)
17 | const [medianTime, setMedianTime] = useState(''); //the median cpu response time from nodes in our selected replica set
18 | const [watchInterval, setWatchInterval] = useState(null); //indicates whether we should be actively watching (we watch by fetching repeatedly at an interval)
19 |
20 |
21 | useEffect(() => {
22 | //the getRS function sends a request to /loadRS, and expects an array of replica set names as a response.
23 | function getRS() {
24 | fetch('/loadRS')
25 | .then(response => response.json())
26 | .then(data => {
27 | //we then use the setReplicaSet setter function to set this piece of state to that array of replica set names.
28 | setReplicaSets(data.result);
29 | document.getElementById('info').innerText = JSON.stringify(data.result, null, 2);
30 | })
31 | .catch(err => console.log('ERROR:', err))
32 | }
33 | getRS();
34 | }, [])
35 |
36 |
37 | const handleWatchButtonClick = () => {
38 | if(watchInterval){ // if detects the anomaly stops repeating
39 | clearInterval(watchInterval); // to stop repeating
40 | setWatchInterval(null) // set the interval back to null
41 | setAbnormalNode(null)
42 | setMedianTime("")
43 | } else {
44 | handleAbnormalNode();
45 | const intervalId = setInterval(()=> {
46 | handleAbnormalNode();
47 | },3000); // every 3s its repeating the functionallity
48 | setWatchInterval(intervalId); // set the state of watchInterval to intervalId so it stops iterating
49 | }
50 | };
51 |
52 | const handleAbnormalNode = () => {
53 | fetch(`/query/${selectedRS}`)
54 | .then(response => response.json())
55 | .then(data => {
56 | if(data.abnormalNodeFound === true){
57 | setAbnormalNode(data.result) // sets the result state to the abnormal it found
58 | setMedianTime(data.median)
59 | clearInterval(watchInterval); // stops repeating reading through
60 | setWatchInterval(null); // set state back to null (false)
61 | }else{
62 | setAbnormalNode(data.result)
63 | setMedianTime(data.median)}
64 | })
65 | .catch(err => console.log('ERROR:', err))
66 | }
67 |
68 | return (
69 |
70 |
71 |
72 | {watchInterval ? "Stop Watching" : "Start Watching"}
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | )
82 | }
83 |
84 | export default MainContainer;
--------------------------------------------------------------------------------
/client/Containers/NodeIDContainer.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Container } from "react-bootstrap";
3 | import { NodeID } from "../Components/NodeID";
4 |
5 | export const NodeIDContainer = (props) => {
6 | return(
7 | <>
8 |
9 |
10 |
11 | >
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/client/app.css:
--------------------------------------------------------------------------------
1 | /* Global Styles */
2 | * {
3 | font-family: 'Ubuntu Light', 'Century Gothic', sans-serif;
4 | color: #1b1b1b;
5 | }
6 |
7 | /* html {
8 | background-color: #f0f0f0;
9 | } */
10 |
11 | body {
12 | background-color: rgb(255, 248, 238);
13 | }
14 |
15 | /* Container Styles */
16 | .container {
17 | background-color: transparent;
18 | padding-top: 10px;
19 | margin-top: 10px;
20 | }
21 |
22 | #MainContainer {
23 | background-color: #e4eaff;
24 | padding: 20px;
25 | border-radius: 8px;
26 | box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.1);
27 | }
28 | #klusterbud{
29 | display: flex;
30 | justify-content: center;
31 | align-self: center;
32 | color: black;
33 | font-family: 'Brush Script MT', cursive;
34 | font-size: 700%;
35 | }
36 |
37 | #TitleContainer {
38 | display: flex;
39 | align-items: center;
40 | justify-content: center;
41 | }
42 |
43 | #title {
44 | text-align: center;
45 | color: #333;
46 | }
47 |
48 | /* Watch Container Styles */
49 | #watchContainer {
50 | text-align: center;
51 | background-color: transparent;
52 | /* border: 2px solid #333; */
53 | padding: 15px;
54 | margin-top: 20px;
55 | border-radius: 5px;
56 | transition: border-color 0.3s ease-in-out;
57 | }
58 |
59 | .watchButton {
60 | background-color: #3498db;
61 | color: #fff;
62 | padding: 10px 20px;
63 | border: none;
64 | border-radius: 5px;
65 | cursor: pointer;
66 | transition: background-color 0.3s ease-in-out;
67 | }
68 |
69 | .watchButton:hover {
70 | background-color: #2980b9;
71 | }
72 |
73 | /* Dropdown Styles */
74 | #replicaset-select,
75 | #replica-menu,
76 | #default-dropdown {
77 | background-color: #3498db;
78 | /* color: #fff; */
79 | border-radius: 5px;
80 | padding: 10px;
81 | margin-top: 10px;
82 | transition: background-color 0.3s ease-in-out;
83 | }
84 |
85 | #default-dropdown {
86 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
87 | }
88 |
89 | /* Log Container Styles */
90 | #logcontainer {
91 | display: flex;
92 | justify-content: center;
93 | }
94 |
95 | #dropdown-container {
96 | text-align: center;
97 | justify-content: center;
98 | }
99 |
100 | /* Usage Styles */
101 | #usage {
102 | display: flex;
103 | justify-content: space-between;
104 | background-color: transparent;
105 | margin-top: 20px;
106 | }
107 |
108 | #nodeid,
109 | #cpu-header,
110 | #usage-details,
111 | #pulled-usage-details {
112 | background-color: transparent;
113 | }
114 |
115 | /* Modern Card Styles */
116 | .card {
117 | background-color: #fff;
118 | border-radius: 8px;
119 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
120 | padding: 20px;
121 | margin-top: 20px;
122 | transition: box-shadow 0.3s ease-in-out;
123 | }
124 |
125 | #usage-details{
126 | display: flex;
127 | background-color: rgba(0,0,0,0);
128 | }
129 | #pulled-usage-details{
130 | background-color: rgba(0,0,0,0);
131 | }
132 |
133 | #replica-menu{
134 | background-color: #296de8;
135 | color: white;
136 | border-radius: 5%;
137 | }
138 |
--------------------------------------------------------------------------------
/client/app.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ReactDOM } from "react-dom/client";
3 | import './app.css'
4 | import MainContainer from './Containers/MainContainer';
5 | import { Container } from "react-bootstrap";
6 | import logo from './images/Logo.png';
7 |
8 | // const root = ReactDOM.createRoot(document.getElementById('root'));
9 | // root.render( )
10 | const App = () => {
11 | return(
12 | <>
13 |
14 |
15 |
16 |
KlusterBud
17 |
18 |
19 |
20 |
21 |
22 | >)
23 | }
24 | export default App;
--------------------------------------------------------------------------------
/client/images/KlusterBud-Logo-NoBG.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/KlusterBud/88d6ae1859f6e41dda3c4f3080189c691614eb39/client/images/KlusterBud-Logo-NoBG.png
--------------------------------------------------------------------------------
/client/images/KlusterGif-Animated Image (Large).gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/KlusterBud/88d6ae1859f6e41dda3c4f3080189c691614eb39/client/images/KlusterGif-Animated Image (Large).gif
--------------------------------------------------------------------------------
/client/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/KlusterBud/88d6ae1859f6e41dda3c4f3080189c691614eb39/client/images/logo.png
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
16 |
17 | KlusterBud
18 |
19 |
20 |
21 |
22 |
23 |
27 |
28 |
29 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/client/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import { createRoot } from 'react-dom/client';
4 | import App from './app';
5 | import MainContainer from './Containers/MainContainer';
6 |
7 |
8 | const root = ReactDOM.createRoot(document.getElementById("root"));
9 | root.render( );
10 |
--------------------------------------------------------------------------------
/client/main.js:
--------------------------------------------------------------------------------
1 | // const queryHandler = function (string) {
2 | // const address = 'localhost:9090/api/v1/query?query=' + string;
3 | // console.log(address);
4 | // }
5 | document.addEventListener('DOMContentLoaded', () => {
6 | const submitButton = document.getElementById('submit');
7 | submitButton.addEventListener('click', () => {
8 | const url = '/query/' + document.getElementById('queryString').value;
9 | console.log(url)
10 | //http://localhost:3000/query/up
11 | fetch(url)
12 | .then(response => response.json())
13 | .then(data => {
14 | document.getElementById('info').innerText = JSON.stringify(data, null, 2);
15 | })
16 | .catch(err => console.log('ERROR:', err))
17 | })
18 | })
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "klusterbud",
3 | "version": "1.0.0",
4 | "description": "OSP Project",
5 | "main": "webpack.config.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "NODE_ENV=production nodemon server/index.js",
9 | "build": "NODE_ENV=production webpack",
10 | "dev": "NODE_ENV=development nodemon server/server.js & NODE_ENV=development webpack serve --open",
11 | "dev-server": "NODE_ENV=development webpack-dev-server --open --hot & NODE_ENV=development nodemon server/server.js"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/oslabs-beta/KlusterBud.git"
16 | },
17 | "author": "Christopher Lu, Michael Wang, Asher Mitchell-Smith, Fredy Rubio",
18 | "license": "ISC",
19 | "bugs": {
20 | "url": "https://github.com/oslabs-beta/KlusterBud/issues"
21 | },
22 | "homepage": "https://github.com/oslabs-beta/KlusterBud#readme",
23 | "devDependencies": {
24 | "babel-loader": "^9.1.3",
25 | "css-loader": "^6.8.1",
26 | "electron": "^27.0.3",
27 | "file-loader": "^6.2.0",
28 | "react-dom": "^18.2.0",
29 | "style-loader": "^3.3.3",
30 | "ts-loader": "^9.5.0",
31 | "typescript": "^5.2.2",
32 | "webpack": "^5.89.0"
33 | },
34 | "dependencies": {
35 | "@babel/core": "^7.23.3",
36 | "@babel/preset-env": "^7.23.3",
37 | "@babel/preset-react": "^7.23.3",
38 | "axios": "^1.6.2",
39 | "babel-loader": "^9.1.3",
40 | "body-parser": "^1.20.2",
41 | "bootstrap": "^5.3.2",
42 | "cors": "^2.8.5",
43 | "dotenv": "^16.3.1",
44 | "express": "^4.18.2",
45 | "file": "^0.2.2",
46 | "html-webpack-plugin": "^5.5.3",
47 | "loader": "^2.1.1",
48 | "nodemon": "^3.0.1",
49 | "react": "^18.2.0",
50 | "react-bootstrap": "^2.9.1",
51 | "react-dom": "^18.2.0",
52 | "react-router-dom": "^6.20.0",
53 | "sass-loader": "^13.3.2",
54 | "webpack": "^5.89.0",
55 | "webpack-cli": "^5.1.4",
56 | "webpack-dev-server": "^4.15.1"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/server/controller/RSController.js:
--------------------------------------------------------------------------------
1 | const { response } = require("express");
2 |
3 | const RSController = {};
4 |
5 | RSController.fetch = (req, res, next) => {
6 | fetch('http://localhost:9090/api/v1/query?query=kube_deployment_spec_replicas')
7 | .then(response => response.json())
8 | .then(data => {
9 | const replicaSets = [];
10 | // populate the replicaSets array with the name(stored under 'deployment') of every replica set
11 | data.data.result.forEach(element => {
12 | replicaSets.push(element.metric.deployment);
13 | });
14 | console.log(replicaSets);
15 | // console.log(data.data.result);
16 | // Send only the data.data.result part to the client
17 | res.locals.replicaSets = replicaSets;
18 | return next();
19 | })
20 | .catch(err => {console.log(err)})
21 | }
22 |
23 | module.exports = RSController;
--------------------------------------------------------------------------------
/server/controller/queryController.js:
--------------------------------------------------------------------------------
1 | const queryController = {};
2 |
3 |
4 | //get the cpu response time of all nodes in our cluster from the prometheus server
5 | queryController.fetch = (req, res, next) => {
6 | console.log('/QUERY ROUTE IS RUNNING', req.params.string)
7 | fetch('http://localhost:9090/api/v1/query?query=container_cpu_usage_seconds_total')
8 | .then(response => {
9 | return response.json()})
10 | .then(data => {
11 | const items = {};
12 | data.data.result.forEach(element => {
13 | items[element.metric.pod] = element.value;
14 | });
15 | console.log(items);
16 | // console.log(data.data.result);
17 | // Send only the data.data.result part to the client
18 | res.locals.data = items;
19 | return next();
20 | })
21 | }
22 |
23 | //reduces information down to array of only the nodes in our chosen replica set, then returns an anomaly node OR 'no anomalies found yet'
24 | queryController.sort = (req, res, next) => {
25 | console.log(req.params.string)
26 | const RSPods = {};
27 | Object.keys(res.locals.data).forEach(element => {
28 | if(element.includes(req.params.string)) {
29 | RSPods[element] = res.locals.data[element];
30 | }
31 | })
32 | res.locals[req.params.string] = RSPods;
33 |
34 | //function acceps an array of values, returns the median value
35 | function medianArr(arr){
36 | arr.sort(function(a, b) {
37 | return a - b;
38 | });
39 |
40 | const n = arr.length;
41 | let median;
42 |
43 | if (n % 2 === 0) {
44 | const midLeft = arr[n / 2 - 1];
45 | const midRight = arr[n / 2];
46 | median = (midLeft + midRight) / 2;
47 | } else {
48 | median = arr[Math.floor(n / 2)];
49 | }
50 |
51 | return median;
52 | }
53 | //find the median of the cpu response times of our nodes
54 | const median = medianArr(Object.values(RSPods).map(ele => ele[1]))
55 | console.log(median)
56 |
57 |
58 | console.log(RSPods)
59 | //go through our pods, compare their cpu usage to the median
60 | for(const key in RSPods){
61 | //if its > 2x the median we assign it to res.locals for our response
62 | if(RSPods[key][1] > (2*median) ){
63 | res.locals.pod = key
64 | res.locals.medianSec = median
65 | res.locals.errorFound = true
66 | console.log(res.locals.pod)
67 | return next();
68 | }
69 | }
70 | //if we get to this point, just send back 'no anomalies found yet'
71 | res.locals.pod = 'no anomalies found yet...'
72 | res.locals.medianSec = median
73 | res.locals.errorFound = false
74 | return next();
75 | };
76 |
77 | module.exports = queryController;
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const express = require('express');
3 | const bodyParser = require('body-parser');
4 | const queryController = require('./controller/queryController')
5 | const RSController = require('./controller/RSController')
6 |
7 | // const queryController = require('./controllers/queryController');
8 |
9 | const PORT = 3000;
10 | const app = express();
11 |
12 |
13 |
14 | app.listen(PORT, console.log(`Listening on PORT: ${PORT}`));
15 |
16 | app.use(express.json());
17 | app.use(express.urlencoded())
18 |
19 | // app.use(bodyParser.urlencoded());
20 | // app.use(bodyParser.text());
21 |
22 | app.use('/', express.static(path.resolve(__dirname, '../dist')))
23 |
24 | app.get('/', (req, res) => {
25 | return res.status(200).sendFile(path.resolve(__dirname, '../client/index.html'));
26 | })
27 | //gets names of replica sets from prometheus server, sends them to front end in an array
28 | app.get('/loadRS', RSController.fetch, (req, res) => {
29 | res.status(200).json({result: res.locals.replicaSets})
30 | })
31 |
32 | app.get('/query/:string', queryController.fetch, queryController.sort, (req, res) => {
33 | res.status(200).json({ result: res.locals.pod, median: res.locals.medianSec, abnormalNodeFound: res.locals.errorFound })
34 | })
35 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HTMLWebpackPlugin = require('html-webpack-plugin');
3 | const loader = require('sass-loader');
4 |
5 | module.exports = {
6 | entry: './client/index.jsx',
7 |
8 | devServer: {
9 | host: 'localhost',
10 | port: 8080,
11 | compress: true,
12 | hot: true,
13 | static: {
14 | directory: path.resolve(__dirname, 'dist'),
15 | publicPath: '/',
16 | },
17 | proxy: {
18 | '/': 'http://localhost:3000',
19 | },
20 | },
21 |
22 | output: {
23 | path: path.resolve(__dirname, 'dist'),
24 | filename: 'bundle.js',
25 | },
26 |
27 | plugins: [
28 | new HTMLWebpackPlugin({
29 | template: './client/index.html',
30 | })
31 | ],
32 |
33 | mode: process.env.NODE_ENV,
34 |
35 | module: {
36 | rules: [
37 | {
38 | test: /\.jsx?$/,
39 | exclude: /node_modules/,
40 | use: {
41 | loader: 'babel-loader',
42 | options: {
43 | presets: ['@babel/preset-env', '@babel/preset-react'],
44 | },
45 | },
46 | },
47 | {
48 | test: /\.css$/i,
49 | exclude: /node_modules/,
50 | use: ['style-loader', 'css-loader'],
51 | },
52 | {
53 | test: /\.(png|jpe?g|gif)$/i,
54 | use: [
55 | {
56 | loader: 'file-loader',
57 | },
58 | ],
59 | },
60 | ],
61 | },
62 | resolve: {
63 | extensions: ['.js', '.jsx'],
64 | },
65 | devtool: 'eval-source-map',
66 | };
67 |
--------------------------------------------------------------------------------