├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── build-dockerimage-on-pr.yml │ ├── build-dockerimage-on-tag.yml │ ├── build.yml │ └── codeql-analysis.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── NOTICE ├── README.md ├── backend.py ├── css ├── bootstrap.min.css ├── bootswatch.min.css ├── britecharts │ ├── britecharts.css │ ├── britecharts.min.css │ ├── charts │ │ ├── bar.css │ │ ├── bar.min.css │ │ ├── brush.css │ │ ├── brush.min.css │ │ ├── bullet.css │ │ ├── bullet.min.css │ │ ├── donut.css │ │ ├── donut.min.css │ │ ├── grouped-bar.css │ │ ├── grouped-bar.min.css │ │ ├── line.css │ │ ├── line.min.css │ │ ├── scatter-plot.css │ │ ├── scatter-plot.min.css │ │ ├── sparkline.css │ │ ├── sparkline.min.css │ │ ├── stacked-area.css │ │ ├── stacked-area.min.css │ │ ├── stacked-bar.css │ │ ├── stacked-bar.min.css │ │ ├── step.css │ │ └── step.min.css │ └── common │ │ ├── common.css │ │ └── common.min.css └── frontend.css ├── docs ├── built-in-dashboards-and-charts.md ├── configuration-settings.md ├── design-fundamentals.md ├── installation-on-docker.md ├── installation-on-kubernetes-and-openshift.md └── prometheus-exporter-grafana-dashboard.md ├── entrypoint.sh ├── index.html ├── js ├── britecharts │ └── umd │ │ ├── bar.min.js │ │ ├── bar.min.js.map │ │ ├── brush.min.js │ │ ├── brush.min.js.map │ │ ├── bullet.min.js │ │ ├── bullet.min.js.map │ │ ├── colors.min.js │ │ ├── colors.min.js.map │ │ ├── donut.min.js │ │ ├── donut.min.js.map │ │ ├── groupedBar.min.js │ │ ├── groupedBar.min.js.map │ │ ├── heatmap.min.js │ │ ├── heatmap.min.js.map │ │ ├── legend.min.js │ │ ├── legend.min.js.map │ │ ├── line.min.js │ │ ├── line.min.js.map │ │ ├── loading.min.js │ │ ├── loading.min.js.map │ │ ├── miniTooltip.min.js │ │ ├── miniTooltip.min.js.map │ │ ├── scatterPlot.min.js │ │ ├── scatterPlot.min.js.map │ │ ├── sparkline.min.js │ │ ├── sparkline.min.js.map │ │ ├── stackedArea.min.js │ │ ├── stackedArea.min.js.map │ │ ├── stackedBar.min.js │ │ ├── stackedBar.min.js.map │ │ ├── step.min.js │ │ ├── step.min.js.map │ │ ├── tooltip.min.js │ │ └── tooltip.min.js.map ├── d3-selection │ ├── .eslintrc.json │ └── dist │ │ ├── d3-selection.js │ │ └── d3-selection.min.js ├── frontend.js ├── lib │ ├── bootstrap.min.js │ ├── bootswatch.js │ ├── d3-selection.min.js │ └── jquery-1.11.0.min.js └── require-v2.3.6.min.js ├── kube-opex-analytics.png ├── manifests ├── helm │ ├── Chart.yaml │ ├── templates │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── deployment.yaml │ │ ├── ingress.yaml │ │ ├── service.yaml │ │ ├── serviceaccount.yaml │ │ ├── servicemonitor.yaml │ │ └── tests │ │ │ └── test-connection.yaml │ └── values.yaml └── kustomize │ ├── kustomization.yaml │ └── resources │ ├── kube-opex-analytics-config.yaml │ ├── kube-opex-analytics-namespace.yaml │ ├── kube-opex-analytics-rbac.yaml │ ├── kube-opex-analytics-secrets.yaml │ ├── kube-opex-analytics-service.yaml │ ├── kube-opex-analytics-sts.yaml │ └── kube-opex-analytics-tests.yaml ├── requirements.txt ├── run-debug-docker.sh ├── run-debug.sh ├── screenshots ├── dataset-export-options.png ├── export-menu.png ├── krossboard-current-usage-overview.png ├── kube-opex-analytics-demo.gif ├── kube-opex-analytics-grafana.png ├── kube-opex-analytics-hourly-consolidated-usage-trends.png ├── kube-opex-analytics-overview.png ├── kube-opex-analytics-usage-requests-efficiency.png ├── sample-last-nodes-occupation-by-pods.png ├── sample-one-week-hourly-usage.png ├── sample-one-year-monthly-usage.png ├── sample-two-weeks-daily-usage.png ├── thumbnail-header.png └── thumbnail.png ├── static └── images │ ├── favicon.ico │ └── kube-opex-analytics.png ├── test_backend.py ├── tests ├── k8s │ ├── busybox.yml │ ├── consumer-mem.yaml │ ├── nginx.yaml │ ├── redis-master-deployment.yaml │ └── redis-slave-deployment.yaml └── prometheus │ └── prometheus.yml ├── third-parties ├── grafana │ └── kube-opex-analytics.json └── prometheus │ └── prometheus.yml └── tox.ini /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | Screenshot and/or a clear and concise description of what you expected to happen. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | assignees: 9 | - rchakode 10 | reviewers: 11 | - rchakode 12 | - package-ecosystem: "docker" 13 | directory: "/" 14 | schedule: 15 | interval: "daily" 16 | - package-ecosystem: github-actions 17 | directory: "/" 18 | schedule: 19 | interval: daily 20 | open-pull-requests-limit: 5 21 | assignees: 22 | - rchakode 23 | reviewers: 24 | - rchakode 25 | -------------------------------------------------------------------------------- /.github/workflows/build-dockerimage-on-pr.yml: -------------------------------------------------------------------------------- 1 | name: Build Docker image on PR 2 | on: 3 | pull_request: 4 | branches: [main] 5 | 6 | jobs: 7 | build_dev_image: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: Build and push Docker image 12 | env: 13 | DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} 14 | DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} 15 | run: | 16 | export IMAGE_TAG="$(date +%F)-$(git rev-parse --short HEAD)" 17 | docker build . --file Dockerfile --tag rchakode/kube-opex-analytics:$IMAGE_TAG 18 | echo "$DOCKERHUB_PASSWORD" | docker login -u $DOCKERHUB_USERNAME --password-stdin 19 | docker push rchakode/kube-opex-analytics:$IMAGE_TAG 20 | -------------------------------------------------------------------------------- /.github/workflows/build-dockerimage-on-tag.yml: -------------------------------------------------------------------------------- 1 | name: Docker image 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | jobs: 7 | build_release_image: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: Build and push Docker image 12 | env: 13 | DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} 14 | DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} 15 | run: | 16 | export GIT_TAG=$(git describe --tags `git rev-list --tags --max-count=1`) 17 | export IMAGE_TAG=$(echo $GIT_TAG | sed 's/v//') 18 | docker build . --file Dockerfile --tag rchakode/kube-opex-analytics:$IMAGE_TAG 19 | docker tag rchakode/kube-opex-analytics:$IMAGE_TAG rchakode/kube-opex-analytics:latest 20 | echo "$DOCKERHUB_PASSWORD" | docker login -u $DOCKERHUB_USERNAME --password-stdin 21 | docker push rchakode/kube-opex-analytics:$IMAGE_TAG 22 | docker push rchakode/kube-opex-analytics 23 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | strategy: 10 | max-parallel: 4 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Set up Python ${{ matrix.python-version }} 15 | uses: actions/setup-python@v4 16 | with: 17 | python-version: ${{ matrix.python-version }} 18 | - name: Install dependencies 19 | run: | 20 | sudo apt install -y librrd-dev libpython3-dev python3-pip 21 | python -m pip install --upgrade pip 22 | pip install -r requirements.txt 23 | - name: Lint with flake8 24 | run: | 25 | pip install flake8 tox 26 | # stop the build if there are Python syntax errors or undefined names 27 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 28 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 29 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 30 | - name: Test with pytest 31 | run: | 32 | pip install pytest 33 | tox 34 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [main] 9 | schedule: 10 | - cron: '0 23 * * 4' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | # Override automatic language detection by changing the below list 21 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 22 | language: ['python', 'javascript'] 23 | # Learn more... 24 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | with: 30 | # We must fetch at least the immediate parents so that if this is 31 | # a pull request then we can checkout the head. 32 | fetch-depth: 2 33 | 34 | # If this run was triggered by a pull request event, then checkout 35 | # the head of the pull request instead of the merge commit. 36 | - run: git checkout HEAD^2 37 | if: ${{ github.event_name == 'pull_request' }} 38 | 39 | # Initializes the CodeQL tools for scanning. 40 | - name: Initialize CodeQL 41 | uses: github/codeql-action/init@v2 42 | with: 43 | languages: ${{ matrix.language }} 44 | # If you wish to specify custom queries, you can do so here or in a config file. 45 | # By default, queries listed here will override any specified in a config file. 46 | # Prefix the list here with "+" to use these queries and those in the config file. 47 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 48 | 49 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 50 | # If this step fails, then you should remove it and run the build manually (see below) 51 | - name: Autobuild 52 | uses: github/codeql-action/autobuild@v2 53 | 54 | # ℹ️ Command-line programs to run using the OS shell. 55 | # 📚 https://git.io/JvXDl 56 | 57 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 58 | # and modify them (or add more) to build your code if your project 59 | # uses a compiled language 60 | 61 | #- run: | 62 | # make bootstrap 63 | # make release 64 | 65 | - name: Perform CodeQL Analysis 66 | uses: github/codeql-action/analyze@v2 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | .idea/ 3 | .tox/ 4 | venv/ 5 | __pycache__/ 6 | *.s[q-z]p 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | ARG RUNTIME_USER="koa" 4 | ARG RUNTIME_USER_UID=4583 5 | ARG APP_HOME="/koa" 6 | ARG DEBIAN_FRONTEND=noninteractive 7 | 8 | RUN apt update && \ 9 | apt install -y python3 librrd-dev libpython3-dev python3-pip && \ 10 | pip3 install --no-cache-dir flask flask_cors requests rrdtool prometheus_client waitress && \ 11 | rm -rf /var/lib/apt/lists/* && \ 12 | mkdir /data && \ 13 | mkdir -p $APP_HOME/static && \ 14 | useradd $RUNTIME_USER -u $RUNTIME_USER_UID && \ 15 | usermod $RUNTIME_USER -d $APP_HOME 16 | 17 | COPY css $APP_HOME/css 18 | COPY js $APP_HOME/js 19 | COPY static/images $APP_HOME/static/images 20 | COPY entrypoint.sh \ 21 | backend.py \ 22 | index.html \ 23 | LICENSE \ 24 | NOTICE \ 25 | $APP_HOME/ 26 | 27 | RUN chown -R $RUNTIME_USER:$RUNTIME_USER $APP_HOME && \ 28 | chown -R $RUNTIME_USER:$RUNTIME_USER /data 29 | 30 | WORKDIR $APP_HOME 31 | VOLUME ["/data"] 32 | ENTRYPOINT ["sh", "./entrypoint.sh"] 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Kubernetes Opex Analytics 2 | Copyright © 2019 Rodrigue Chakode and contributors. 3 | 4 | Kubernetes Opex Analytics is licensed under the Apache License 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. You may obtain a 6 | copy of the License at: 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | This product is bound to the following third-party librairies. Please read their license terms for more details. 17 | 18 | * [Britecharts](https://britecharts.github.io/britecharts/). 19 | * [D3.js](https://d3js.org/). 20 | * [RRDtool](https://oss.oetiker.ch/rrdtool/) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo-thumbnail](screenshots/thumbnail-header.png) 2 | 3 | ![Apache License](https://img.shields.io/github/license/rchakode/kube-opex-analytics.svg?label=License&style=for-the-badge) 4 | [![Calendar Versioning](https://img.shields.io/badge/calver-YY.MM.MICRO-bb8fce.svg?style=for-the-badge)](http://calver.org) 5 | ![Docker pulls](https://img.shields.io/docker/pulls/rchakode/kube-opex-analytics.svg?label=Docker%20Pulls&style=for-the-badge) 6 | 7 | --- 8 | 9 | - [Overview](#overview) 10 | - [Getting Started](#getting-started) 11 | - [Design Fundamentals](#design-fundamentals) 12 | - [License](#license) 13 | - [Support & Contributions](#support--contributions) 14 | 15 | # Overview 16 | `kube-opex-analytics` (literally *Kubernetes Opex Analytics*) is a Kubernetes usage accounting and analytics tool to help organizations track the resources being consumed by their Kubernetes clusters over time (hourly, daily, monthly). The purpose of `kube-opex-analytics` is to help prevent overpaying. Indeed, it provides insightful usage analytics metrics and charts, that engineering and financial teams can use as key indicators to take appropriate cost optimization decisions. 17 | 18 | Key features: 19 | 20 | * **Hourly consumption trends, daily and monthly accounting per namespace.** This feature provides analytics metrics _tracking both actual usage and requested capacities_ over time. Metrics are namespaced-based, collected every 5 minutes, consolidated on a hourly basis for trends, from which daily and monthly accounting is processed. 21 | * **Accounting of non-allocatable capacities.** At node and cluster levels, `kube-opex-analytics` tracks and consolidates the share of non-allocatable capacities and highlights them against usable capacities (i.e. capacities used by actual application workloads). In contrary to usable capacities, non-allocatable capacities are dedicated to Kubernetes operations (OS, kubelets, etc). 22 | * **Cluster usage accounting and capacity planning.** This feature makes it easy to account and visualize capacities consumed on a cluster, globally, instantly and over time. 23 | * **Usage/requests efficiency.** Based on hourly-consolidated trends, this functionality helps know how efficient resource requests set on Kubernetes workloads are, compared against the actual resource usage over time. 24 | * **Cost allocation and charge back analytics:** automatic processing and visualization of resource usage accounting per namespace on daily and monthly periods. 25 | * **Insightful and extensible visualization.** `kube-opex-analytics` enables built-in analytics dashboards, as well as a native Prometheus exporter that exposes its analytics metrics for third-party visualization tools like Grafana. 26 | 27 | 28 | ![kube-opex-analytics-overview](screenshots/kube-opex-analytics-demo.gif) 29 | 30 | Read the [design fundamentals](./docs/design-fundamentals.md) documentation to learn more concepts and implementation decisions. 31 | 32 | > **Multi-cluster Integration:** `kube-opex-analytics` tracks the usage for a single Kubernetes cluster. For a centralized multi-Kubernetes usage analytics, use our [Krossboard Kubernetes Operator](https://github.com/2-alchemists/krossboard-kubernetes-operator) product. Watch a [demo video](https://youtu.be/lfkUIREDYDY). 33 | 34 | # Getting Started 35 | * [Installation on Kubernetes and OpenShift](./docs/installation-on-kubernetes-and-openshift.md) 36 | * [Installation on Docker](./docs/installation-on-docker.md) 37 | * [Built-in dashboards and charts](./docs/built-in-dashboards-and-charts.md) 38 | * [Prometheus Exporter and Grafana Dashboards](./docs/prometheus-exporter-grafana-dashboard.md) 39 | * [Configuration Settings](./docs/configuration-settings.md) 40 | 41 | # Design Fundamentals 42 | Checkout the [Design Fundamentals](./docs/design-fundamentals.md) documentation to learn more about `kube-opex-analytics`, it introduces concepts and implementation decisions. 43 | 44 | # License 45 | `kube-opex-analytics` (code and documentation) is licensed under the terms of Apache License 2.0; read the [LICENSE](./LICENSE). Besides, it's bound to third-party libraries each with its specific license terms; read the [NOTICE](./NOTICE) for additional information. 46 | 47 | # Support & Contributions 48 | We encourage feedback and always make our best to handle any troubles you may encounter when using `kube-opex-analytics`. 49 | 50 | Use this [link to submit issues or improvement ideas](https://github.com/rchakode/kube-opex-analytics/issues). 51 | 52 | To contribute bug patches or new features, please submit a [Pull Request](https://github.com/rchakode/kube-opex-analytics/pulls). 53 | 54 | Contributions are accepted subject that the code and documentation be released under the terms of Apache 2.0 License. 55 | 56 | -------------------------------------------------------------------------------- /css/bootswatch.min.css: -------------------------------------------------------------------------------- 1 | body{padding-top:50px} 2 | #banner{border-bottom:none} 3 | .page-header h1{font-size:4em} 4 | .bs-docs-section{margin-top:8em} 5 | footer{margin:5em 0}footer li{float:left;margin-right:1.5em;margin-bottom:1.5em} 6 | footer p{clear:left;margin-bottom:0} 7 | .splash{background-color:#1c2533;background:-webkit-linear-gradient(70deg, #080f1f 30%, #2b4b5a 87%, #435e67 100%);background:-o-linear-gradient(70deg, #080f1f 30%, #2b4b5a 87%, #435e67 100%);background:-ms-linear-gradient(70deg, #080f1f 30%, #2b4b5a 87%, #435e67 100%);background:-moz-linear-gradient(70deg, #080f1f 30%, #2b4b5a 87%, #435e67 100%);background:linear-gradient(20deg, #080f1f 30%, #2b4b5a 87%, #435e67 100%);background-attachment:fixed;padding:6em 0 2em;color:#fff;text-align:center}.splash .alert{margin:4em 0 2em} 8 | .splash h1{font-size:4em} 9 | .splash #social{margin-top:6em} 10 | .section-tout{padding:4em 0 3em;border-top:1px solid rgba(255,255,255,0.1);border-bottom:1px solid rgba(0,0,0,0.1);background-color:#eaf1f1}.section-tout .fa{margin-right:.5em} 11 | .section-tout p{margin-bottom:3em} 12 | .section-preview{padding:4em 0 4em}.section-preview .preview{margin-bottom:4em;background-color:#eaf1f1;border:1px solid rgba(0,0,0,0.1);border-radius:6px}.section-preview .preview .image{padding:5px}.section-preview .preview .image img{border:1px solid rgba(0,0,0,0.1)} 13 | .section-preview .preview .options{text-align:center;padding:0 2em 2em}.section-preview .preview .options p{margin-bottom:2em} 14 | .section-preview .dropdown-menu{text-align:left} 15 | .section-preview .lead{margin-bottom:2em} 16 | @media (max-width:767px){.section-preview .image img{width:100%}} 17 | .bsa .one .bsa_it_ad{border:none !important;background-color:transparent !important}.bsa .one .bsa_it_ad .bsa_it_t,.bsa .one .bsa_it_ad .bsa_it_d{color:inherit !important} 18 | .bsa .one .bsa_it_p{display:none} 19 | -------------------------------------------------------------------------------- /css/britecharts/britecharts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Karla', sans-serif; 3 | src: url("https://fonts.googleapis.com/css?family=Karla") format("opentype"); 4 | font-weight: 300; } 5 | 6 | .britechart { 7 | font-family: 'Karla', sans-serif; 8 | -webkit-font-smoothing: antialiased; } 9 | 10 | .select-disable { 11 | -webkit-touch-callout: none; 12 | /* iOS Safari */ 13 | -webkit-user-select: none; 14 | /* Safari */ 15 | -khtml-user-select: none; 16 | /* Konqueror HTML */ 17 | -moz-user-select: none; 18 | /* Firefox */ 19 | -ms-user-select: none; 20 | /* Internet Explorer/Edge */ 21 | user-select: none; 22 | /* Non-prefixed version, currently supported by Chrome and Opera */ } 23 | 24 | .line-load-state .chart-filter, 25 | .stacked-area-load-state .chart-filter, 26 | .donut-load-state .chart-filter, 27 | .bar-load-state .chart-filter { 28 | will-change: transform; 29 | animation: swipe 1.5s linear infinite forwards; } 30 | 31 | @keyframes swipe { 32 | from { 33 | transform: translateX(-100%); } 34 | to { 35 | transform: translateX(100%); } } 36 | 37 | .vertical-grid-line, 38 | .horizontal-grid-line { 39 | fill: none; 40 | shape-rendering: crispEdges; 41 | stroke: #EFF2F5; 42 | stroke-width: 1; 43 | stroke-dasharray: 4, 4; } 44 | 45 | .horizontal-grid-line--highlighted { 46 | stroke: #45494E; 47 | stroke-dasharray: 2, 2; } 48 | 49 | .extended-y-line, 50 | .extended-x-line { 51 | fill: none; 52 | shape-rendering: crispEdges; 53 | stroke: #D2D6DF; 54 | stroke-width: 1; } 55 | 56 | .tick line { 57 | fill: none; 58 | stroke: #ADB0B6; 59 | stroke-width: 1; 60 | shape-rendering: crispEdges; } 61 | 62 | .tick text { 63 | font-size: 1rem; 64 | fill: #666A73; 65 | padding: 12px; } 66 | 67 | .y-axis-label, 68 | .x-axis-label { 69 | font-size: 1rem; 70 | fill: #ADB0B6; } 71 | 72 | .vertical-marker-container .vertical-marker { 73 | stroke: #D2D6DF; 74 | stroke-width: 1; 75 | fill: none; } 76 | 77 | .vertical-marker-container .data-point-highlighter { 78 | fill: #ffffff; 79 | stroke-width: 2; } 80 | 81 | .tooltip-background { 82 | fill: rgba(255, 255, 255, 0.97); 83 | stroke: #D2D6DF; 84 | stroke-width: 1; 85 | border-radius: 2px; } 86 | 87 | .britechart-legend .legend-entry.is-faded .legend-entry-name, 88 | .britechart-legend .legend-entry.is-faded .legend-entry-value, 89 | .britechart-legend .legend-entry.is-faded .legend-circle { 90 | opacity: 0.97; 91 | transition: opacity .2s ease-out; 92 | -moz-transition: opacity .2s ease-out; 93 | -webkit-transition: opacity .2s ease-out; } 94 | 95 | .britechart-legend .legend-entry.is-faded .legend-entry-name, 96 | .britechart-legend .legend-entry.is-faded .legend-entry-value, 97 | .britechart-legend .legend-entry.is-faded .legend-circle { 98 | opacity: 0.2; } 99 | 100 | .bar-chart .bar { 101 | shape-rendering: crispEdges; } 102 | 103 | .bar-chart .y-axis-group .tick text { 104 | font-size: 14px; } 105 | 106 | .bar-chart .axis path { 107 | display: none; } 108 | 109 | .bar-chart .tick line { 110 | display: none; } 111 | 112 | .bar-chart .adjust-upwards { 113 | transform: translate(0, -10px); } 114 | 115 | .bar-chart .percentage-label { 116 | fill: #666A73; } 117 | 118 | .line-chart .data-point-mark { 119 | fill: #ffffff; } 120 | 121 | .line-chart .topic .line { 122 | stroke-width: 2; 123 | stroke-linecap: round; 124 | stroke-linejoin: round; } 125 | 126 | .line-chart .x.axis path, 127 | .line-chart .y.axis path { 128 | display: none; } 129 | 130 | .line-chart .month-axis path { 131 | display: none; } 132 | 133 | .line-chart .custom-line { 134 | shape-rendering: crispEdges; 135 | stroke-width: 1; } 136 | 137 | .line-chart .custom-line-annotation { 138 | font-size: 0.75rem; 139 | line-height: 0.75rem; 140 | fill: #666A73; } 141 | 142 | .line-chart .masking-rectangle { 143 | fill: #ffffff; } 144 | 145 | .scatter-plot .y-axis-group .tick line { 146 | display: none; } 147 | 148 | .scatter-plot .y-axis-group .axis path { 149 | display: none; } 150 | 151 | .scatter-plot .x.axis path { 152 | display: none; } 153 | 154 | .scatter-plot .data-point-highlighter { 155 | stroke-width: 1.2; } 156 | 157 | .sparkline { 158 | stroke: #ADB0B6; 159 | stroke-width: 1; 160 | stroke-linecap: round; 161 | stroke-linejoin: round; } 162 | .sparkline .line { 163 | stroke-width: 2; } 164 | .sparkline .sparkline-circle { 165 | fill: #ff584c; 166 | stroke-width: 0; 167 | display: none; } 168 | .sparkline .sparkline-area { 169 | stroke: none; } 170 | 171 | .stacked-area .dot { 172 | display: none; } 173 | 174 | .stacked-area .y-axis-group path { 175 | display: none; } 176 | 177 | .stacked-area .x-axis-group path { 178 | display: none; } 179 | 180 | .stacked-area .area-outline { 181 | shape-rendering: geometricPrecision; 182 | stroke-width: 1.2; } 183 | 184 | .stacked-area .data-point-highlighter { 185 | stroke-width: 1.2; } 186 | 187 | .stacked-area .empty-data-line { 188 | stroke-width: 2px; 189 | stroke-linecap: round; } 190 | 191 | .stacked-bar .x-axis-group path, 192 | .stacked-bar .y-axis-group path { 193 | display: none; } 194 | 195 | .stacked-bar .y-axis-group .tick text { 196 | font-size: 14px; } 197 | 198 | .stacked-bar .tick line, 199 | .stacked-bar .tick line { 200 | display: none; } 201 | 202 | .grouped-bar .x-axis-group path, 203 | .grouped-bar .y-axis-group path { 204 | display: none; } 205 | 206 | .grouped-bar .y-axis-group .tick text { 207 | font-size: 14px; } 208 | 209 | .grouped-bar .tick line, 210 | .grouped-bar .tick line { 211 | display: none; } 212 | 213 | .step-chart .step { 214 | fill: #8FDAD8; 215 | stroke-width: 0; 216 | shape-rendering: crispEdges; } 217 | .step-chart .step:hover { 218 | fill: #39C2C9; } 219 | 220 | .step-chart .axis path { 221 | display: none; } 222 | 223 | .step-chart .tick line { 224 | display: none; } 225 | 226 | .brush-chart .brush-area { 227 | fill: #EFF2F5; } 228 | 229 | .brush-chart rect.brush-rect.selection { 230 | fill-opacity: 0.08; 231 | stroke-linejoin: round; } 232 | 233 | .brush-chart rect.brush-rect.handle { 234 | fill: #00d8d2; 235 | width: 0.2rem; } 236 | 237 | .brush-chart .axis path { 238 | display: none; } 239 | 240 | .bullet-chart .marker-line { 241 | shape-rendering: crispEdges; } 242 | 243 | .bullet-chart .axis-group path { 244 | display: none; } 245 | 246 | .bullet-chart .bullet-title { 247 | font-size: 16px; } 248 | -------------------------------------------------------------------------------- /css/britecharts/britecharts.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Karla,sans-serif;src:url(https://fonts.googleapis.com/css?family=Karla) format("opentype");font-weight:300}.britechart{font-family:Karla,sans-serif;-webkit-font-smoothing:antialiased}.select-disable{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.bar-load-state .chart-filter,.donut-load-state .chart-filter,.line-load-state .chart-filter,.stacked-area-load-state .chart-filter{will-change:transform;animation:swipe 1.5s linear infinite forwards}@keyframes swipe{from{transform:translateX(-100%)}to{transform:translateX(100%)}}.horizontal-grid-line,.vertical-grid-line{fill:none;shape-rendering:crispEdges;stroke:#eff2f5;stroke-width:1;stroke-dasharray:4,4}.horizontal-grid-line--highlighted{stroke:#45494e;stroke-dasharray:2,2}.extended-x-line,.extended-y-line{fill:none;shape-rendering:crispEdges;stroke:#d2d6df;stroke-width:1}.tick line{fill:none;stroke:#adb0b6;stroke-width:1;shape-rendering:crispEdges}.tick text{font-size:1rem;fill:#666a73;padding:12px}.x-axis-label,.y-axis-label{font-size:1rem;fill:#adb0b6}.vertical-marker-container .vertical-marker{stroke:#d2d6df;stroke-width:1;fill:none}.vertical-marker-container .data-point-highlighter{fill:#fff;stroke-width:2}.tooltip-background{fill:rgba(255,255,255,.97);stroke:#d2d6df;stroke-width:1;border-radius:2px}.britechart-legend .legend-entry.is-faded .legend-circle,.britechart-legend .legend-entry.is-faded .legend-entry-name,.britechart-legend .legend-entry.is-faded .legend-entry-value{opacity:.97;transition:opacity .2s ease-out;-moz-transition:opacity .2s ease-out;-webkit-transition:opacity .2s ease-out}.britechart-legend .legend-entry.is-faded .legend-circle,.britechart-legend .legend-entry.is-faded .legend-entry-name,.britechart-legend .legend-entry.is-faded .legend-entry-value{opacity:.2}.bar-chart .bar{shape-rendering:crispEdges}.bar-chart .y-axis-group .tick text{font-size:14px}.bar-chart .axis path{display:none}.bar-chart .tick line{display:none}.bar-chart .adjust-upwards{transform:translate(0,-10px)}.bar-chart .percentage-label{fill:#666a73}.line-chart .data-point-mark{fill:#fff}.line-chart .topic .line{stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.line-chart .x.axis path,.line-chart .y.axis path{display:none}.line-chart .month-axis path{display:none}.line-chart .custom-line{shape-rendering:crispEdges;stroke-width:1}.line-chart .custom-line-annotation{font-size:.75rem;line-height:.75rem;fill:#666a73}.line-chart .masking-rectangle{fill:#fff}.scatter-plot .y-axis-group .tick line{display:none}.scatter-plot .y-axis-group .axis path{display:none}.scatter-plot .x.axis path{display:none}.scatter-plot .data-point-highlighter{stroke-width:1.2}.sparkline{stroke:#adb0b6;stroke-width:1;stroke-linecap:round;stroke-linejoin:round}.sparkline .line{stroke-width:2}.sparkline .sparkline-circle{fill:#ff584c;stroke-width:0;display:none}.sparkline .sparkline-area{stroke:none}.stacked-area .dot{display:none}.stacked-area .y-axis-group path{display:none}.stacked-area .x-axis-group path{display:none}.stacked-area .area-outline{shape-rendering:geometricPrecision;stroke-width:1.2}.stacked-area .data-point-highlighter{stroke-width:1.2}.stacked-area .empty-data-line{stroke-width:2px;stroke-linecap:round}.stacked-bar .x-axis-group path,.stacked-bar .y-axis-group path{display:none}.stacked-bar .y-axis-group .tick text{font-size:14px}.stacked-bar .tick line{display:none}.grouped-bar .x-axis-group path,.grouped-bar .y-axis-group path{display:none}.grouped-bar .y-axis-group .tick text{font-size:14px}.grouped-bar .tick line{display:none}.step-chart .step{fill:#8fdad8;stroke-width:0;shape-rendering:crispEdges}.step-chart .step:hover{fill:#39c2c9}.step-chart .axis path{display:none}.step-chart .tick line{display:none}.brush-chart .brush-area{fill:#eff2f5}.brush-chart rect.brush-rect.selection{fill-opacity:.08;stroke-linejoin:round}.brush-chart rect.brush-rect.handle{fill:#00d8d2;width:.2rem}.brush-chart .axis path{display:none}.bullet-chart .marker-line{shape-rendering:crispEdges}.bullet-chart .axis-group path{display:none}.bullet-chart .bullet-title{font-size:16px} -------------------------------------------------------------------------------- /css/britecharts/charts/bar.css: -------------------------------------------------------------------------------- 1 | .bar-chart .bar { 2 | shape-rendering: crispEdges; } 3 | 4 | .bar-chart .y-axis-group .tick text { 5 | font-size: 14px; } 6 | 7 | .bar-chart .axis path { 8 | display: none; } 9 | 10 | .bar-chart .tick line { 11 | display: none; } 12 | 13 | .bar-chart .adjust-upwards { 14 | transform: translate(0, -10px); } 15 | 16 | .bar-chart .percentage-label { 17 | fill: #666A73; } 18 | -------------------------------------------------------------------------------- /css/britecharts/charts/bar.min.css: -------------------------------------------------------------------------------- 1 | .bar-chart .bar{shape-rendering:crispEdges}.bar-chart .y-axis-group .tick text{font-size:14px}.bar-chart .axis path{display:none}.bar-chart .tick line{display:none}.bar-chart .adjust-upwards{transform:translate(0,-10px)}.bar-chart .percentage-label{fill:#666a73} -------------------------------------------------------------------------------- /css/britecharts/charts/brush.css: -------------------------------------------------------------------------------- 1 | .brush-chart .brush-area { 2 | fill: #EFF2F5; } 3 | 4 | .brush-chart rect.brush-rect.selection { 5 | fill-opacity: 0.08; 6 | stroke-linejoin: round; } 7 | 8 | .brush-chart rect.brush-rect.handle { 9 | fill: #00d8d2; 10 | width: 0.2rem; } 11 | 12 | .brush-chart .axis path { 13 | display: none; } 14 | -------------------------------------------------------------------------------- /css/britecharts/charts/brush.min.css: -------------------------------------------------------------------------------- 1 | .brush-chart .brush-area{fill:#eff2f5}.brush-chart rect.brush-rect.selection{fill-opacity:.08;stroke-linejoin:round}.brush-chart rect.brush-rect.handle{fill:#00d8d2;width:.2rem}.brush-chart .axis path{display:none} -------------------------------------------------------------------------------- /css/britecharts/charts/bullet.css: -------------------------------------------------------------------------------- 1 | .bullet-chart .marker-line { 2 | shape-rendering: crispEdges; } 3 | 4 | .bullet-chart .axis-group path { 5 | display: none; } 6 | 7 | .bullet-chart .bullet-title { 8 | font-size: 16px; } 9 | -------------------------------------------------------------------------------- /css/britecharts/charts/bullet.min.css: -------------------------------------------------------------------------------- 1 | .bullet-chart .marker-line{shape-rendering:crispEdges}.bullet-chart .axis-group path{display:none}.bullet-chart .bullet-title{font-size:16px} -------------------------------------------------------------------------------- /css/britecharts/charts/donut.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/css/britecharts/charts/donut.css -------------------------------------------------------------------------------- /css/britecharts/charts/donut.min.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/css/britecharts/charts/donut.min.css -------------------------------------------------------------------------------- /css/britecharts/charts/grouped-bar.css: -------------------------------------------------------------------------------- 1 | .grouped-bar .x-axis-group path, 2 | .grouped-bar .y-axis-group path { 3 | display: none; } 4 | 5 | .grouped-bar .y-axis-group .tick text { 6 | font-size: 14px; } 7 | 8 | .grouped-bar .tick line, 9 | .grouped-bar .tick line { 10 | display: none; } 11 | -------------------------------------------------------------------------------- /css/britecharts/charts/grouped-bar.min.css: -------------------------------------------------------------------------------- 1 | .grouped-bar .x-axis-group path,.grouped-bar .y-axis-group path{display:none}.grouped-bar .y-axis-group .tick text{font-size:14px}.grouped-bar .tick line{display:none} -------------------------------------------------------------------------------- /css/britecharts/charts/line.css: -------------------------------------------------------------------------------- 1 | .line-chart .data-point-mark { 2 | fill: #ffffff; } 3 | 4 | .line-chart .topic .line { 5 | stroke-width: 2; 6 | stroke-linecap: round; 7 | stroke-linejoin: round; } 8 | 9 | .line-chart .x.axis path, 10 | .line-chart .y.axis path { 11 | display: none; } 12 | 13 | .line-chart .month-axis path { 14 | display: none; } 15 | 16 | .line-chart .custom-line { 17 | shape-rendering: crispEdges; 18 | stroke-width: 1; } 19 | 20 | .line-chart .custom-line-annotation { 21 | font-size: 0.75rem; 22 | line-height: 0.75rem; 23 | fill: #666A73; } 24 | 25 | .line-chart .masking-rectangle { 26 | fill: #ffffff; } 27 | -------------------------------------------------------------------------------- /css/britecharts/charts/line.min.css: -------------------------------------------------------------------------------- 1 | .line-chart .data-point-mark{fill:#fff}.line-chart .topic .line{stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.line-chart .x.axis path,.line-chart .y.axis path{display:none}.line-chart .month-axis path{display:none}.line-chart .custom-line{shape-rendering:crispEdges;stroke-width:1}.line-chart .custom-line-annotation{font-size:.75rem;line-height:.75rem;fill:#666a73}.line-chart .masking-rectangle{fill:#fff} -------------------------------------------------------------------------------- /css/britecharts/charts/scatter-plot.css: -------------------------------------------------------------------------------- 1 | .scatter-plot .y-axis-group .tick line { 2 | display: none; } 3 | 4 | .scatter-plot .y-axis-group .axis path { 5 | display: none; } 6 | 7 | .scatter-plot .x.axis path { 8 | display: none; } 9 | 10 | .scatter-plot .data-point-highlighter { 11 | stroke-width: 1.2; } 12 | -------------------------------------------------------------------------------- /css/britecharts/charts/scatter-plot.min.css: -------------------------------------------------------------------------------- 1 | .scatter-plot .y-axis-group .tick line{display:none}.scatter-plot .y-axis-group .axis path{display:none}.scatter-plot .x.axis path{display:none}.scatter-plot .data-point-highlighter{stroke-width:1.2} -------------------------------------------------------------------------------- /css/britecharts/charts/sparkline.css: -------------------------------------------------------------------------------- 1 | .sparkline { 2 | stroke: #ADB0B6; 3 | stroke-width: 1; 4 | stroke-linecap: round; 5 | stroke-linejoin: round; } 6 | .sparkline .line { 7 | stroke-width: 2; } 8 | .sparkline .sparkline-circle { 9 | fill: #ff584c; 10 | stroke-width: 0; 11 | display: none; } 12 | .sparkline .sparkline-area { 13 | stroke: none; } 14 | -------------------------------------------------------------------------------- /css/britecharts/charts/sparkline.min.css: -------------------------------------------------------------------------------- 1 | .sparkline{stroke:#adb0b6;stroke-width:1;stroke-linecap:round;stroke-linejoin:round}.sparkline .line{stroke-width:2}.sparkline .sparkline-circle{fill:#ff584c;stroke-width:0;display:none}.sparkline .sparkline-area{stroke:none} -------------------------------------------------------------------------------- /css/britecharts/charts/stacked-area.css: -------------------------------------------------------------------------------- 1 | .stacked-area .dot { 2 | display: none; } 3 | 4 | .stacked-area .y-axis-group path { 5 | display: none; } 6 | 7 | .stacked-area .x-axis-group path { 8 | display: none; } 9 | 10 | .stacked-area .area-outline { 11 | shape-rendering: geometricPrecision; 12 | stroke-width: 1.2; } 13 | 14 | .stacked-area .data-point-highlighter { 15 | stroke-width: 1.2; } 16 | 17 | .stacked-area .empty-data-line { 18 | stroke-width: 2px; 19 | stroke-linecap: round; } 20 | -------------------------------------------------------------------------------- /css/britecharts/charts/stacked-area.min.css: -------------------------------------------------------------------------------- 1 | .stacked-area .dot{display:none}.stacked-area .y-axis-group path{display:none}.stacked-area .x-axis-group path{display:none}.stacked-area .area-outline{shape-rendering:geometricPrecision;stroke-width:1.2}.stacked-area .data-point-highlighter{stroke-width:1.2}.stacked-area .empty-data-line{stroke-width:2px;stroke-linecap:round} -------------------------------------------------------------------------------- /css/britecharts/charts/stacked-bar.css: -------------------------------------------------------------------------------- 1 | .stacked-bar .x-axis-group path, 2 | .stacked-bar .y-axis-group path { 3 | display: none; } 4 | 5 | .stacked-bar .y-axis-group .tick text { 6 | font-size: 14px; } 7 | 8 | .stacked-bar .tick line, 9 | .stacked-bar .tick line { 10 | display: none; } 11 | -------------------------------------------------------------------------------- /css/britecharts/charts/stacked-bar.min.css: -------------------------------------------------------------------------------- 1 | .stacked-bar .x-axis-group path,.stacked-bar .y-axis-group path{display:none}.stacked-bar .y-axis-group .tick text{font-size:14px}.stacked-bar .tick line{display:none} -------------------------------------------------------------------------------- /css/britecharts/charts/step.css: -------------------------------------------------------------------------------- 1 | .step-chart .step { 2 | fill: #8FDAD8; 3 | stroke-width: 0; 4 | shape-rendering: crispEdges; } 5 | .step-chart .step:hover { 6 | fill: #39C2C9; } 7 | 8 | .step-chart .axis path { 9 | display: none; } 10 | 11 | .step-chart .tick line { 12 | display: none; } 13 | -------------------------------------------------------------------------------- /css/britecharts/charts/step.min.css: -------------------------------------------------------------------------------- 1 | .step-chart .step{fill:#8fdad8;stroke-width:0;shape-rendering:crispEdges}.step-chart .step:hover{fill:#39c2c9}.step-chart .axis path{display:none}.step-chart .tick line{display:none} -------------------------------------------------------------------------------- /css/britecharts/common/common.css: -------------------------------------------------------------------------------- 1 | .vertical-grid-line, 2 | .horizontal-grid-line { 3 | fill: none; 4 | shape-rendering: crispEdges; 5 | stroke: #EFF2F5; 6 | stroke-width: 1; 7 | stroke-dasharray: 4, 4; } 8 | 9 | .horizontal-grid-line--highlighted { 10 | stroke: #45494E; 11 | stroke-dasharray: 2, 2; } 12 | 13 | .extended-y-line, 14 | .extended-x-line { 15 | fill: none; 16 | shape-rendering: crispEdges; 17 | stroke: #D2D6DF; 18 | stroke-width: 1; } 19 | 20 | .tick line { 21 | fill: none; 22 | stroke: #ADB0B6; 23 | stroke-width: 1; 24 | shape-rendering: crispEdges; } 25 | 26 | .tick text { 27 | font-size: 1rem; 28 | fill: #666A73; 29 | padding: 12px; } 30 | 31 | .y-axis-label, 32 | .x-axis-label { 33 | font-size: 1rem; 34 | fill: #ADB0B6; } 35 | 36 | .vertical-marker-container .vertical-marker { 37 | stroke: #D2D6DF; 38 | stroke-width: 1; 39 | fill: none; } 40 | 41 | .vertical-marker-container .data-point-highlighter { 42 | fill: #ffffff; 43 | stroke-width: 2; } 44 | 45 | .tooltip-background { 46 | fill: rgba(255, 255, 255, 0.97); 47 | stroke: #D2D6DF; 48 | stroke-width: 1; 49 | border-radius: 2px; } 50 | 51 | .britechart-legend .legend-entry.is-faded .legend-entry-name, 52 | .britechart-legend .legend-entry.is-faded .legend-entry-value, 53 | .britechart-legend .legend-entry.is-faded .legend-circle { 54 | opacity: 0.97; 55 | transition: opacity .2s ease-out; 56 | -moz-transition: opacity .2s ease-out; 57 | -webkit-transition: opacity .2s ease-out; } 58 | 59 | .britechart-legend .legend-entry.is-faded .legend-entry-name, 60 | .britechart-legend .legend-entry.is-faded .legend-entry-value, 61 | .britechart-legend .legend-entry.is-faded .legend-circle { 62 | opacity: 0.2; } 63 | -------------------------------------------------------------------------------- /css/britecharts/common/common.min.css: -------------------------------------------------------------------------------- 1 | .horizontal-grid-line,.vertical-grid-line{fill:none;shape-rendering:crispEdges;stroke:#eff2f5;stroke-width:1;stroke-dasharray:4,4}.horizontal-grid-line--highlighted{stroke:#45494e;stroke-dasharray:2,2}.extended-x-line,.extended-y-line{fill:none;shape-rendering:crispEdges;stroke:#d2d6df;stroke-width:1}.tick line{fill:none;stroke:#adb0b6;stroke-width:1;shape-rendering:crispEdges}.tick text{font-size:1rem;fill:#666a73;padding:12px}.x-axis-label,.y-axis-label{font-size:1rem;fill:#adb0b6}.vertical-marker-container .vertical-marker{stroke:#d2d6df;stroke-width:1;fill:none}.vertical-marker-container .data-point-highlighter{fill:#fff;stroke-width:2}.tooltip-background{fill:rgba(255,255,255,.97);stroke:#d2d6df;stroke-width:1;border-radius:2px}.britechart-legend .legend-entry.is-faded .legend-circle,.britechart-legend .legend-entry.is-faded .legend-entry-name,.britechart-legend .legend-entry.is-faded .legend-entry-value{opacity:.97;transition:opacity .2s ease-out;-moz-transition:opacity .2s ease-out;-webkit-transition:opacity .2s ease-out}.britechart-legend .legend-entry.is-faded .legend-circle,.britechart-legend .legend-entry.is-faded .legend-entry-name,.britechart-legend .legend-entry.is-faded .legend-entry-value{opacity:.2} -------------------------------------------------------------------------------- /css/frontend.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | width: 98%; 3 | } 4 | .navbar-header{ 5 | min-height: 55px; 6 | } 7 | .navbar-brand { 8 | padding: 5px; 9 | } 10 | .chart-container { 11 | min-height: 400px; 12 | margin-top: 16px; 13 | } 14 | 15 | .btn.menu-export { 16 | border: 0; 17 | } 18 | 19 | .tricolon:after { 20 | content: '⁝'; 21 | } 22 | 23 | .chart-download:before { 24 | content: '\2193 '; 25 | } 26 | 27 | .chart-download:after { 28 | content: ' \2193'; 29 | } 30 | 31 | legend { 32 | /* margin-top: .5rem; */ 33 | color: rgba(0, 0, 0, 0.65); 34 | } 35 | 36 | 37 | .britecharts-tooltip { 38 | width: 64rem !important; /* Adjust the value as needed */ 39 | max-width: 100rem; 40 | } -------------------------------------------------------------------------------- /docs/built-in-dashboards-and-charts.md: -------------------------------------------------------------------------------- 1 | # Built-in Dashboards and Charts of kube-opex-analytics 2 | This section describes the built-in dashboards and charts provided by `kube-opex-analytics`. 3 | 4 | - [Built-in Dashboards and Charts of kube-opex-analytics](#built-in-dashboards-and-charts-of-kube-opex-analytics) 5 | - [Hourly Consolidated Usage Trends (7 days)](#hourly-consolidated-usage-trends-7-days) 6 | - [Hourly Usage/Requests Efficiency (7 days)](#hourly-usagerequests-efficiency-7-days) 7 | - [Daily Consumption Accounting (14 days)](#daily-consumption-accounting-14-days) 8 | - [Monthly Consumption Accounting (12 months)](#monthly-consumption-accounting-12-months) 9 | - [Nodes' Occupation by Pods](#nodes-occupation-by-pods) 10 | - [Export Charts and Datasets (PNG, CSV, JSON)](#export-charts-and-datasets-png-csv-json) 11 | - [Dashboards and Visualization with Grafana](#dashboards-and-visualization-with-grafana) 12 | 13 | 14 | ## Hourly Consolidated Usage Trends (7 days) 15 | For each namespace discovered in the target Kubernetes cluster, this dashboard section displays hourly usage trends for CPU and memory resources during the last week (7 days). The aim of these charts is to help understand trends of actual resource usage by each namespace, but also globally thanks to the stacked-area charts. 16 | 17 | ![](../screenshots/kube-opex-analytics-hourly-consolidated-usage-trends.png) 18 | 19 | 20 | The date filter can be used to zoom out/in on a specific time range. 21 | 22 | These charts are based on data consolidated hourly thanks to sample metrics collected every five minutes from Kubernetes. 23 | 24 | ## Hourly Usage/Requests Efficiency (7 days) 25 | For each namespace discovered in the target Kubernetes cluster, this dashboard section displays hourly usage/requests efficiency trends for CPU and memory resources during the last week (7 days). The aim of these charts is to help understand how efficient resource requests set on Kubernetes workloads are, compared against the actual resource usage over time. 26 | 27 | ![](../screenshots/kube-opex-analytics-usage-requests-efficiency.png) 28 | 29 | The date filter can be used to zoom out/in on a specific time range. 30 | 31 | These charts are based on data consolidated hourly thanks to sample metrics collected every five minutes from Kubernetes. 32 | 33 | ## Daily Consumption Accounting (14 days) 34 | The daily accounting charts are provided per namespace for CPU and Memory resources and cover the last 14 days (2 weeks). 35 | 36 | According to the [selected accounting model (](design-fundamentals.md#usage-accounting-models), the charts display the following metrics. The chart and the backed-data can be easily exported as an image or a CSV file (see [Export Charts and Datasets (PNG, CSV, JSON)](#export-charts-and-datasets-png-csv-json)). 37 | 38 | * Daily cumulative sum of actual hourly consumption per namespace. 39 | * Daily cumulative sum of the maximum between the actual hourly consumption and the requested capacities. 40 | * Daily cumulative sum of hourly cost computed from an actual cluster cost set statically based on a fixed hourly rate, or determinated dynamically from allocated resources on public clouds (nodes, storage, etc.). 41 | 42 | ![](../screenshots/sample-two-weeks-daily-usage.png) 43 | 44 | 45 | ## Monthly Consumption Accounting (12 months) 46 | 47 | The monthly accounting charts are provided per namespace for CPU and Memory resources and cover the last 12 months (1 year). 48 | 49 | According to the [selected accounting model (](design-fundamentals.md#usage-accounting-models), the charts display the following metrics. Each chart and/or the backed-data can be easily exported as an image or a CSV file (see [Export Charts and Datasets (PNG, CSV, JSON)](#export-charts-and-datasets-png-csv-json)). 50 | 51 | * Monthly cumulative sum of actual hourly consumption per namespace. 52 | * Monthly cumulative sum of the maximum between the actual hourly consumption and the requested capacities. 53 | * Monthly cumulative sum of hourly cost computed from an actual cluster cost set statically based on a fixed hourly rate, or determinated dynamically from allocated resources on public clouds (nodes, storage, etc.). 54 | 55 | ![](../screenshots/sample-one-year-monthly-usage.png) 56 | 57 | 58 | ## Nodes' Occupation by Pods 59 | For each node discovered in the Kubernetes cluster, this dashboard section displays the CPU and the memory resources currently consumed by running pods. The data are refreshed every five minutes. 60 | 61 | ![](../screenshots/sample-last-nodes-occupation-by-pods.png) 62 | 63 | 64 | ## Export Charts and Datasets (PNG, CSV, JSON) 65 | Any chart provided by kube-opex-analytics can be exported, either as PNG image, CSV or JSON data files. 66 | 67 | * Go to the target chart section. 68 | * Click on the link `export`, then select the target exportion format. This action shall download the artifact instantly. 69 | 70 | ![](../screenshots/export-menu.png) 71 | 72 | # Dashboards and Visualization with Grafana 73 | In addition or alternatively to the built-in dashboards, it's also possible to [use Grafana for visualization](./prometheus-exporter-grafana-dashboard.md) thanks to the Prometheus exporter natively enabled by `kube-opex-analytics`. 74 | -------------------------------------------------------------------------------- /docs/configuration-settings.md: -------------------------------------------------------------------------------- 1 | # kube-opex-analytics Configuration Variables 2 | This section provides an exhaustive list of configuration variables used by `kube-opex-analytics`. 3 | 4 | > These are **startup environment variables** that require to restart the service when they are updated. 5 | 6 | * `KOA_DB_LOCATION` sets the path to use to store internal data. Typically when you consider to set a volume to store those data, you should also take care to set this path to belong to the mounting point. 7 | * `KOA_K8S_API_ENDPOINT` sets the endpoint to the Kubernetes API. 8 | * `KOA_K8S_API_VERIFY_SSL` boolean to set whether to check validate Kubernetes certificate (defaut is `true`). 9 | * `KOA_K8S_CACERT` sets the path to CA file for a self-signed certificate. 10 | * `KOA_K8S_AUTH_TOKEN` sets a Bearer token to authenticate against the Kubernetes API. 11 | * `KOA_K8S_AUTH_CLIENT_CERT` sets the path to the X509 client certificate to authenticate against the Kubernetes API. 12 | * `KOA_K8S_AUTH_CLIENT_CERT_KEY` sets the path to the X509 client certificate key. 13 | * `KOA_K8S_AUTH_USERNAME` sets the username to authenticate against the Kubernetes API using Basic Authentication. 14 | * `KOA_K8S_AUTH_PASSWORD` sets the password for Basic Authentication. 15 | * `KOA_COST_MODEL` (version >= `0.2.0`): sets the model of cost allocation to use. Possible values are: _CUMULATIVE_RATIO_ (default) indicates to compute cost as cumulative resource usage for each period of time (daily, monthly); _CHARGE_BACK_ calculates cost based on a given cluster hourly rate (see `KOA_BILLING_HOURLY_RATE`); _RATIO_ indicates to compute cost as a normalized percentage of resource usage during each period of time. 16 | * `KOA_BILLING_HOURLY_RATE` (required if cost model is _CHARGE_BACK_): defines a positive floating number corresponding to an estimated hourly rate for the Kubernetes cluster. For example if your cluster cost is $5,000 dollars a month (i.e. `~30*24` hours), its estimated hourly cost would be `6.95 = 5000/(30*24)`. 17 | * `KOA_BILLING_CURRENCY_SYMBOL` (optional, default is '`$`'): sets a currency string to use to annotate costs on reports. 18 | * `KOA_INCLUDED_NAMESPACES` (optional, default is '`*`'): comma-separated list of namespaces to monitore. If any value is provided, only these namespaces will be accounted. 19 | * `KOA_EXCLUDED_NAMESPACES` (optional, default is ''): comma-separated list of namespaces to ignore. If any value is provided, these namespaces will be discarded. This options takes precedence over `KOA_INCLUDED_NAMESPACES`. 20 | -------------------------------------------------------------------------------- /docs/design-fundamentals.md: -------------------------------------------------------------------------------- 1 | 2 | # Design Fundamentals 3 | This section highlights fundamental assumptions and decisions made when designing `kube-opex-analytics`. 4 | 5 | - [Design Fundamentals](#design-fundamentals) 6 | - [Data Collection and Analytics](#data-collection-and-analytics) 7 | - [Usage Accounting Models](#usage-accounting-models) 8 | 9 | 10 | ## Data Collection and Analytics 11 | `kube-opex-analytics` periodically collects CPU and memory usage metrics from Kubernetes's API, processes and consolidates them over various time-aggregation perspectives (hourly, daily, monthly), to produce resource **usage reports covering up to a year**. The reports focus on namespace level, while a special care is taken to also account and highlight **shares of non-allocatable capacities**. 12 | 13 | * **Namespace-focused:** Means that consolidated resource usage metrics consider individual namespaces as fundamental units for resource sharing. A special care is taken to also account and highlight `non-allocatable` resources . 14 | * **Hourly Usage & Trends:** Like on public clouds, resource consumption for each namespace is consolidated on a hourly-basic. This actually corresponds to the ratio (%) of resource used per namespace during each hour. It's the foundation for cost allocation and also allows to get over time trends about resources being consuming per namespace and also at the Kubernetes cluster scale. 15 | * **Daily and Monthly Usage Costs:** Provides for each period (daily/monthly), namespace, and resource type (CPU/memory), consolidated cost computed given one of the following ways: (i) accumulated hourly usage over the period; (ii) actual costs computed based on resource usage and a given hourly billing rate; (iii) normalized ratio of usage per namespace compared against the global cluster usage. 16 | * **Utilization of Nodes by Pods:** Highlights for each node the share of resources used by active pods labelled by their namespace. 17 | 18 | 19 | ## Usage Accounting Models 20 | `kube-opex-analytics` support various usage accounting models with the aim to address use cases like cost allocation, usage show back, capacity planning, etc. 21 | 22 | The target accounting model must be selected using the startup configuration variable `KOA_COST_MODEL` using one of the following values: 23 | 24 | * `CUMULATIVE_RATIO`: (default value) compute costs as cumulative resource usage for each period of time (daily, monthly). 25 | * `RATIO`: compute costs as normalized ratios (`%`) of resource usage during each period of time. 26 | * `CHARGE_BACK`: compute actual costs using a given cluster hourly rate and the cumulative resource usage during each period of time. 27 | 28 | Read the [Configuration Section](#configuration-variables) for more details. -------------------------------------------------------------------------------- /docs/installation-on-docker.md: -------------------------------------------------------------------------------- 1 | # Deploying kube-opex-analytics using Docker 2 | 3 | - [Requirements](#requirements) 4 | - [Deployment](#deployment) 5 | - [Access the web UI](#access-the-web-ui) 6 | 7 | ## Requirements 8 | `kube-opex-analytics` requires read-only access to the following Kubernetes API endpoints. 9 | * `/api/v1` 10 | * `/apis/metrics.k8s.io/v1beta1` (provided by [Kubernetes Metrics Server](https://github.com/kubernetes-sigs/metrics-server), which shall be installed on the cluster if it's not yet the case). 11 | 12 | You need to provide the base URL of the Kubernetes API when starting the program. 13 | As aforementioned endpoints would typically require authentication, you have to proceed with one of the following options to authenticated with Kubernetes API: 14 | * Provide a proxied access to Kubernetes API (e.g. `http://127.0.0.1:8001`, get through command `kubectl proxy` with sufficient credentials to get access to the above endpoints). 15 | * Provide a base Kubernetes API (e.g. https://1.2.3.4:6443), plus additional credentials information to authenticate to the Kubernetes API with sufficient permission to read through the above endpoints (see the list of [configuration variables](./docs/../configuration-settings.md)). 16 | 17 | ## Deployment 18 | It assumes that you have a proxied access to your Kubernetes cluster from the local machine. 19 | 20 | As `kube-opex-analytics` is released as a Docker image, you can quickly start an instance of the service by running the following command: 21 | 22 | ``` 23 | docker run -d \ 24 | --net="host" \ 25 | --name 'kube-opex-analytics' \ 26 | -v /var/lib/kube-opex-analytics:/data \ 27 | -e KOA_DB_LOCATION=/data/db \ 28 | -e KOA_K8S_API_ENDPOINT=http://127.0.0.1:8001 \ 29 | rchakode/kube-opex-analytics 30 | ``` 31 | 32 | In this command: 33 | 34 | * We provide a local path `/var/lib/kube-opex-analytics` as data volume for the container. That's where `kube-opex-analytics` will store its internal analytics data. You can change this local path to another location, but please keep the container volume `/data` as is. 35 | * The environment variable `KOA_DB_LOCATION` points to the container path to store data. You may note that this directory belongs to the data volume atached to the container. 36 | * The environment variable `KOA_K8S_API_ENDPOINT` set the address of the Kubernetes API endpoint. 37 | 38 | ## Access the web UI 39 | Once the container started you can open access the `kube-opex-analytics`'s web interface at `http://:5483/` (e.g. http://127.0.0.1:5483/). * 40 | 41 | `` should be actually replaced by the IP address or the hostmane of the Docker server. 42 | 43 | > You typically need to wait almost an hour to have all charts filled. This is a normal operations of `kube-opex-analytics` which is an hourly-based analytics tool. 44 | -------------------------------------------------------------------------------- /docs/installation-on-kubernetes-and-openshift.md: -------------------------------------------------------------------------------- 1 | # Deploying kube-opex-analytics on Kubernetes and OpenShift 2 | 3 | - [Requirements](#requirements) 4 | - [Deployment Methods](#deployment-methods) 5 | - [Installation with Kustomize (Default Settings)](#installing-with-kustomize-default-settings) 6 | - [Customizing the Installation with Helm](#customizing-the-installation-with-helm) 7 | - [Accessing the Web Interface](#accessing-the-web-interface) 8 | 9 | ## Requirements 10 | 11 | `kube-opex-analytics` requires read-only access to the following Kubernetes API endpoints: 12 | 13 | - `/api/v1` 14 | - `/apis/metrics.k8s.io/v1beta1` (provided by the [Kubernetes Metrics Server](https://github.com/kubernetes-sigs/metrics-server)). 15 | 16 | 17 | ### Installing Kubernetes Metrics Server (Skip if using OpenShift) 18 | 19 | To install the Kubernetes Metrics Server, run the following command: 20 | 21 | ```shell 22 | # This step is not needed if using OpenShift. 23 | kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml 24 | ``` 25 | 26 | ## Deployment methods for Kubernetes 27 | `kube-opex-analytics` can be installed on a Kubernetes cluster using one of the following methods: 28 | 29 | * [kubectl + Kustomize](#Installing-with-Kustomize-Default-Settings) - Quick installation with default settings. 30 | * [Helm](#Customizing-the-Installation-with-Helm) - For more advanced customization. 31 | 32 | ## Downloading the Deployment Artifacts 33 | Clone the repository and navigate to the main folder: 34 | 35 | ```shell 36 | git clone https://github.com/rchakode/kube-opex-analytics.git --depth=1 37 | cd kube-opex-analytics 38 | ``` 39 | 40 | ## Installing with Kustomize (Default Settings) 41 | 42 | This approach provides a quick way to deploy kube-opex-analytics using the default settings (You can review the Kustomize resources located in the `./manifests/kustomize/resources/` folder). 43 | 44 | If these default settings do not meet your requirements, consider using the [Helm-based installation](#customizing-the-installation-with-helm) described below, which offers greater customization options. 45 | 46 | ### Default Settings: 47 | - A persistent volume with a `1Gi` storage request. 48 | - The Kubernetes distribution is **not OpenShift**, as OpenShift requires additional SCC settings. 49 | - The pod is configured with security contexts that allow UID `4583` and GID `4583` inside the container. 50 | 51 | ### Installation Steps 52 | Follow these steps to install kube-opex-analytics using Kustomize: 53 | 54 | ```shell 55 | # Create the installation namespace if it does not exist 56 | kubectl create ns kube-opex-analytics 57 | 58 | # Create kube-opex-analytics resources 59 | kubectl -n kube-opex-analytics apply -k ./manifests/kustomize 60 | 61 | # Check that the status of the pod and wait that it starts 62 | kubectl -n kube-opex-analytics get po -w 63 | 64 | # If not pod is found, check events for additional information. 65 | kubectl -n kube-opex-analytics get ev 66 | ``` 67 | 68 | ## Customizing the Installation with Helm 69 | 70 | This approach is recommended when deploying on **OpenShift** or when advanced configuration is required. 71 | 72 | Customization is done by modifying the Helm values file at the following location `./manifests/helm/values.yaml`. 73 | 74 | ### Common Customizations 75 | 76 | Below are some frequently used customizations: 77 | 78 | - **Use `emptyDir` for local testing** 79 | Set `.dataVolume.persist` to `false` (it's set to `true` by default). 80 | 81 | - **Set specific persistent volume** 82 | * Set `.dataVolume.persist` to `true` (default value). 83 | * Set `.dataVolume.capacity` with the persistent volume size (it's set to `1Gi` by default). 84 | * Set `.dataVolume.storageClass` with the storage class name (if not set, the default storage class of the cluster will be used). 85 | 86 | - **Enable deployment on OpenShift** 87 | Set `.securityContext.openshift` to `true`. This binds the SCC `nonroot-v2` to the `kube-opex-analytics` service account. 88 | If `emptyDir` is enabled, the SCC `hostaccess` is also bound to the service account. 89 | 90 | - **Customize CPU and memory requests** 91 | Adjust `.resources.requests.cpu` and `.resources.requests.memory` as needed. 92 | 93 | - **Customize the integration with Kubernetes** 94 | Set the different environment variables under the value `.envs` section. See [kube-opex-analytics configuration variables](./configuration-settings.md) 95 | 96 | ### Installation Steps 97 | 98 | The installation consists of the following steps. The installation namespace (`kube-opex-analytics`) is created it does not exist. 99 | 100 | ```shell 101 | # Create the installation namespace if it does not exist 102 | kubectl create ns kube-opex-analytics 103 | 104 | # Create kube-opex-analytics resources 105 | helm upgrade -n kube-opex-analytics --install kube-opex-analytics manifests/helm/ 106 | 107 | # Check that the status of the pod and wait that it starts 108 | kubectl -n kube-opex-analytics get po -w 109 | 110 | # If not pod is found, check events for additional information. 111 | kubectl -n kube-opex-analytics get ev 112 | ``` 113 | 114 | ## Get Access to the kube-opex-anakytics's Web Interface 115 | The deployment create an HTTP service named `kube-opex-analytics` on port `80` in the selected namespace. This service provides access to the built-in `kube-opex-analytics` dashboard. 116 | -------------------------------------------------------------------------------- /docs/prometheus-exporter-grafana-dashboard.md: -------------------------------------------------------------------------------- 1 | # Promehtheus Exporter and Grafana dashboards 2 | This section describes how to set up Grafana dashboards thanks to `kube-opex-analytics`' Prometheus exporter. 3 | 4 | - [Promehtheus Exporter and Grafana dashboards](#promehtheus-exporter-and-grafana-dashboards) 5 | - [Prometheus Exporter](#prometheus-exporter) 6 | - [Default Grafana Dashboard](#default-grafana-dashboard) 7 | - [Importation and configuration](#importation-and-configuration) 8 | - [Overview of the default dashboard](#overview-of-the-default-dashboard) 9 | 10 | ![](../screenshots/kube-opex-analytics-grafana.png) 11 | 12 | ## Prometheus Exporter 13 | `kube-opex-analytics` enables a Prometheus exporter through the endpoint `/metrics`. 14 | 15 | The exporter exposes the following metrics: 16 | 17 | * `koa_namespace_hourly_usage` exposes for each namespace its current hourly resource usage for both CPU and memory. 18 | * `koa_namespace_daily_usage` exposes for each namespace and for the ongoing day, its current resource usage for both CPU and memory. 19 | * `koa_namespace_monthly_usage` exposes for each namespace and for the ongoing month, its current resource usage for both CPU and memory. 20 | 21 | The Prometheus scraping job can be configured like below (adapt the target URL if needed). A scraping interval less than 5 minutes (i.e. `300s`) is useless as `kube-opex-analytics` would not generate any new metrics in the meantime. 22 | 23 | 24 | ``` 25 | scrape_configs: 26 | - job_name: 'kube-opex-analytics' 27 | scrape_interval: 300s 28 | static_configs: 29 | - targets: ['kube-opex-analytics:5483'] 30 | ``` 31 | 32 | > When the paramater `prometheusOperator` is enabled during the deployment (see Helm [values.yaml](./helm/kube-opex-analytics/values.yaml) file), you have nothing to do as the scraping should be automatically configured by the deployed `Prometheus ServiceMonitor`. 33 | 34 | ## Default Grafana Dashboard 35 | There is an official Grafana dashboard for kube-opex-analytics. It can be downloaded from the folder [./third-parties/grafana/](https://github.com/rchakode/kube-opex-analytics/tree/main/third-parties/grafana) or via [Grafana Dashboard Hub](https://grafana.com/grafana/dashboards/10282-kube-opex-analytics/). 36 | 37 | The dashboard looks as below and is designed to work out-of-the box with the [Prometheus exporter](#prometheus-exporter). It would just require a couple of minutes to make it work. 38 | 39 | ### Importation and configuration 40 | 41 | * Download the dashboard and import it in Grafana. 42 | * Create a Grafana variable named `KOA_DS_PROMETHEUS` and point to your Prometheus server data source. 43 | * You're done. 44 | 45 | ### Overview of the default dashboard 46 | The dashboard currently provides the following charts (see screenshot below): 47 | 48 | * Hourly resource usage over time. 49 | * Current day's ongoing resource usage. 50 | * Current month's ongoing resource usage. 51 | 52 | > This default Grafana dashboard has less features compared against the ones enabled by the built-in dashboards. In particular, the daily and the monthly usage for the different namespaces are not stacked, neither than we have no analytics charts for past days and months. These limitations are inherent to how Grafana handles timeseries and bar charts. 53 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # File: entrypoint.sh # 4 | # Author: Rodrigue Chakode # 5 | # # 6 | # Copyright © 2019 Rodrigue Chakode and contributors. # 7 | # # 8 | # This file is part of Kubernetes Opex Analytics software. # 9 | # # 10 | # Kubernetes Opex Analytics is licensed under the Apache License 2.0 (the "License"); # 11 | # you may not use this file except in compliance with the License. You may obtain # 12 | # a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 # 13 | # # 14 | # Unless required by applicable law or agreed to in writing, software distributed # 15 | # under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR # 16 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the # 17 | # specific language governing permissions and limitations under the License. # 18 | 19 | if [ ! -z "${KOA_ENABLE_PROMETHEUS_EXPORTER}" ]; then 20 | echo "WARNING: as of version 0.4.8, startup variable KOA_ENABLE_PROMETHEUS_EXPORTER is now depracated and ignored" 21 | fi 22 | 23 | LC_ALL='C.UTF-8' LANG='C.UTF-8' \ 24 | python3 -u ./backend.py 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | Kubernetes Opex Analytics | Hourly, Daily and Monthly Analytics of Kubernetes Cluster Usage 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 35 | 45 | 46 | 49 | 50 | 51 | 68 | 69 | 212 | 213 | 214 | 215 | 216 | 217 | 260 | 261 | 294 | 295 | 296 | -------------------------------------------------------------------------------- /js/britecharts/umd/colors.min.js: -------------------------------------------------------------------------------- 1 | !function(e,r){"object"==typeof exports&&"object"==typeof module?module.exports=r():"function"==typeof define&&define.amd?define([],r):"object"==typeof exports?exports.colors=r():(e.britecharts=e.britecharts||{},e.britecharts.colors=r())}(window,(function(){return function(e){var r={};function o(f){if(r[f])return r[f].exports;var n=r[f]={i:f,l:!1,exports:{}};return e[f].call(n.exports,n,n.exports,o),n.l=!0,n.exports}return o.m=e,o.c=r,o.d=function(e,r,f){o.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:f})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,r){if(1&r&&(e=o(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var f=Object.create(null);if(o.r(f),Object.defineProperty(f,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)o.d(f,n,function(r){return e[r]}.bind(null,n));return f},o.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(r,"a",r),r},o.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},o.p="",o(o.s=110)}({110:function(e,r,o){e.exports=o(66)},66:function(e,r,o){"use strict";var f;void 0===(f=function(){return{colorSchemas:{britecharts:["#6aedc7","#39c2c9","#ffce00","#ffa71a","#f866b9","#998ce3"],grey:["#F8F8FA","#EFF2F5","#D2D6DF","#C3C6CF","#ADB0B6","#666A73","#45494E","#363A43","#282C35"],orange:["#fcc870","#ffa71a","#fb8825","#f6682f","#db5a2c","#bf4c28","#a43b1c","#892a10","#f9e9c5"],blueGreen:["#ccf7f6","#70e4e0","#00d8d2","#00acaf","#007f8c","#005e66","#003c3f","#002d2f","#0d2223"],teal:["#ccfffe","#94f7f4","#00fff8","#1de1e1","#39c2c9","#2e9a9d","#227270","#1a5957","#133f3e"],green:["#edfff7","#d7ffef","#c0ffe7","#95f5d7","#6aedc7","#59c3a3","#479980","#34816a","#206953"],yellow:["#f9f2b3","#fbe986","#fce05a","#fed72d","#ffce00","#fcc11c","#f9b438","#eda629","#e09819"],pink:["#fdd1ea","#fb9cd2","#f866b9","#fc40b6","#ff1ab3","#e3239d","#c62c86","#a62073","#85135f"],purple:["#ddd6fc","#bbb1f0","#998ce3","#8e6bc1","#824a9e","#77337f","#6b1c60","#591650","#470f3f"],red:["#ffd8d4","#ffb5b0","#ff938c","#ff766c","#ff584c","#f04b42","#e03d38","#be2e29","#9c1e19"]},colorSchemasHuman:{britecharts:"Britecharts Default",grey:"Britecharts Grey",orange:"Orange",blueGreen:"Blue",teal:"Light Blue",green:"Green",yellow:"Yellow",pink:"Pink",purple:"Purple",red:"Red"},colorGradients:{greenBlue:["#39C7EA","#4CDCBA"],orangePink:["#FBC670","#F766B8"],bluePurple:["#3DC3C9","#824a9e"]},colorGradientsHuman:{greenBlue:"Green to Blue",orangePink:"Orange to Pink",bluePurple:"Blue to Purple"},singleColors:{aloeGreen:["#7bdcc0"],greenColor:["#6aedc7"],blueColor:["#39c2c9"],yellowColor:["#ffce00"],orangeColor:["#ffa71a"],pinkColor:["#f866b9"],purpleColor:["#998ce3"]},singleColorsHuman:{aloeGreen:"Aloe Green",greenColor:"Green",blueColor:"Blue",yellowColor:"Yellow",orangeColor:"Orange",pinkColor:"Pink",purpleColor:"Purple"}}}.call(r,o,r,e))||(e.exports=f)}})})); 2 | //# sourceMappingURL=colors.min.js.map -------------------------------------------------------------------------------- /js/britecharts/umd/colors.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack://britecharts.[name]/webpack/universalModuleDefinition","webpack://britecharts.[name]/webpack/bootstrap","webpack://britecharts.[name]/./src/charts/helpers/color.js"],"names":["root","factory","exports","module","define","amd","window","installedModules","__webpack_require__","moduleId","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","colorSchemas","britecharts","grey","orange","blueGreen","teal","green","yellow","pink","purple","red","colorSchemasHuman","colorGradients","greenBlue","orangePink","bluePurple","colorGradientsHuman","singleColors","aloeGreen","greenColor","blueColor","yellowColor","orangeColor","pinkColor","purpleColor","singleColorsHuman"],"mappings":"CAAA,SAA2CA,EAAMC,GAC1B,iBAAZC,SAA0C,iBAAXC,OACxCA,OAAOD,QAAUD,IACQ,mBAAXG,QAAyBA,OAAOC,IAC9CD,OAAO,GAAIH,GACe,iBAAZC,QACdA,QAAgB,OAAID,KAEpBD,EAAkB,YAAIA,EAAkB,aAAK,GAAIA,EAAkB,YAAU,OAAIC,KARnF,CASGK,QAAQ,WACX,O,YCTE,IAAIC,EAAmB,GAGvB,SAASC,EAAoBC,GAG5B,GAAGF,EAAiBE,GACnB,OAAOF,EAAiBE,GAAUP,QAGnC,IAAIC,EAASI,EAAiBE,GAAY,CACzCC,EAAGD,EACHE,GAAG,EACHT,QAAS,IAUV,OANAU,EAAQH,GAAUI,KAAKV,EAAOD,QAASC,EAAQA,EAAOD,QAASM,GAG/DL,EAAOQ,GAAI,EAGJR,EAAOD,QA0Df,OArDAM,EAAoBM,EAAIF,EAGxBJ,EAAoBO,EAAIR,EAGxBC,EAAoBQ,EAAI,SAASd,EAASe,EAAMC,GAC3CV,EAAoBW,EAAEjB,EAASe,IAClCG,OAAOC,eAAenB,EAASe,EAAM,CAAEK,YAAY,EAAMC,IAAKL,KAKhEV,EAAoBgB,EAAI,SAAStB,GACX,oBAAXuB,QAA0BA,OAAOC,aAC1CN,OAAOC,eAAenB,EAASuB,OAAOC,YAAa,CAAEC,MAAO,WAE7DP,OAAOC,eAAenB,EAAS,aAAc,CAAEyB,OAAO,KAQvDnB,EAAoBoB,EAAI,SAASD,EAAOE,GAEvC,GADU,EAAPA,IAAUF,EAAQnB,EAAoBmB,IAC/B,EAAPE,EAAU,OAAOF,EACpB,GAAW,EAAPE,GAA8B,iBAAVF,GAAsBA,GAASA,EAAMG,WAAY,OAAOH,EAChF,IAAII,EAAKX,OAAOY,OAAO,MAGvB,GAFAxB,EAAoBgB,EAAEO,GACtBX,OAAOC,eAAeU,EAAI,UAAW,CAAET,YAAY,EAAMK,MAAOA,IACtD,EAAPE,GAA4B,iBAATF,EAAmB,IAAI,IAAIM,KAAON,EAAOnB,EAAoBQ,EAAEe,EAAIE,EAAK,SAASA,GAAO,OAAON,EAAMM,IAAQC,KAAK,KAAMD,IAC9I,OAAOF,GAIRvB,EAAoB2B,EAAI,SAAShC,GAChC,IAAIe,EAASf,GAAUA,EAAO2B,WAC7B,WAAwB,OAAO3B,EAAgB,SAC/C,WAA8B,OAAOA,GAEtC,OADAK,EAAoBQ,EAAEE,EAAQ,IAAKA,GAC5BA,GAIRV,EAAoBW,EAAI,SAASiB,EAAQC,GAAY,OAAOjB,OAAOkB,UAAUC,eAAe1B,KAAKuB,EAAQC,IAGzG7B,EAAoBgC,EAAI,GAIjBhC,EAAoBA,EAAoBiC,EAAI,K,kFClFrD,KAAArC,aA0LI,MAAO,CACHsC,aAtDiB,CACjBC,YAtHgB,CACZ,UACA,UACA,UACA,UACA,UACA,WAiHJC,KA9GS,CACL,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WAsGJC,OAnGW,CACP,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WA2FJC,UAxFc,CACV,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WAgFJC,KA7ES,CACL,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WAqEJC,MAlEU,CACN,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WA0DJC,OAvDW,CACP,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WA+CJC,KA5CS,CACL,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WAoCJC,OAjCW,CACP,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,WAyBJC,IAtBQ,CACJ,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,YA0DJC,kBA3CsB,CACtB,YAAe,sBACf,KAAQ,mBACR,OAAU,SACV,UAAa,OACb,KAAQ,aACR,MAAS,QACT,OAAU,SACV,KAAQ,OACR,OAAU,SACV,IAAO,OAkCPC,eA1LmB,CACnBC,UAAW,CAAC,UAAW,WACvBC,WAAY,CAAC,UAAW,WACxBC,WAAY,CAAC,UAAW,YAwLxBC,oBAtLwB,CACxBH,UAAW,gBACXC,WAAY,iBACZC,WAAY,kBAoLZE,aAxBiB,CACjBC,UATc,CAAC,WAUfC,WATe,CAAC,WAUhBC,UATc,CAAC,WAUfC,YATgB,CAAC,WAUjBC,YATgB,CAAC,WAUjBC,UATc,CAAC,WAUfC,YATgB,CAAC,YA2BjBC,kBAhBsB,CACtBP,UAAW,aACXC,WAAY,QACZC,UAAW,OACXC,YAAa,SACbC,YAAa,SACbC,UAAW,OACXC,YAAa,YAvLrB","file":"colors.min.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"colors\"] = factory();\n\telse\n\t\troot[\"britecharts\"] = root[\"britecharts\"] || {}, root[\"britecharts\"][\"colors\"] = factory();\n})(window, function() {\nreturn "," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 110);\n","define(function() {\n\n // Color Gradients\n const colorGradients = {\n greenBlue: ['#39C7EA', '#4CDCBA'],\n orangePink: ['#FBC670', '#F766B8'],\n bluePurple: ['#3DC3C9', '#824a9e']\n };\n const colorGradientsHuman = {\n greenBlue: 'Green to Blue',\n orangePink: 'Orange to Pink',\n bluePurple: 'Blue to Purple'\n };\n\n // Color Schemas\n // Standard Color Schema for Britecharts\n const britecharts = [\n '#6aedc7', //green\n '#39c2c9', //blue\n '#ffce00', //yellow\n '#ffa71a', //orange\n '#f866b9', //pink\n '#998ce3' //purple\n ];\n // Grey Palette\n const grey = [\n '#F8F8FA',\n '#EFF2F5',\n '#D2D6DF',\n '#C3C6CF',\n '#ADB0B6',\n '#666A73',\n '#45494E',\n '#363A43',\n '#282C35'\n ];\n // Orange Palette\n const orange = [\n '#fcc870',\n '#ffa71a',\n '#fb8825',\n '#f6682f',\n '#db5a2c',\n '#bf4c28',\n '#a43b1c',\n '#892a10',\n '#f9e9c5'\n ];\n // Blue Palette\n const blueGreen = [\n '#ccf7f6',\n '#70e4e0',\n '#00d8d2',\n '#00acaf',\n '#007f8c',\n '#005e66',\n '#003c3f',\n '#002d2f',\n '#0d2223'\n ];\n // LightBlue Palette\n const teal = [\n '#ccfffe',\n '#94f7f4',\n '#00fff8',\n '#1de1e1',\n '#39c2c9',\n '#2e9a9d',\n '#227270',\n '#1a5957',\n '#133f3e'\n ];\n // Green Palette\n const green = [\n '#edfff7',\n '#d7ffef',\n '#c0ffe7',\n '#95f5d7',\n '#6aedc7',\n '#59c3a3',\n '#479980',\n '#34816a',\n '#206953'\n ];\n // Yellow Palette\n const yellow = [\n '#f9f2b3',\n '#fbe986',\n '#fce05a',\n '#fed72d',\n '#ffce00',\n '#fcc11c',\n '#f9b438',\n '#eda629',\n '#e09819'\n ];\n // Pink Palette\n const pink = [\n '#fdd1ea',\n '#fb9cd2',\n '#f866b9',\n '#fc40b6',\n '#ff1ab3',\n '#e3239d',\n '#c62c86',\n '#a62073',\n '#85135f'\n ];\n // Purple Palette\n const purple = [\n '#ddd6fc',\n '#bbb1f0',\n '#998ce3',\n '#8e6bc1',\n '#824a9e',\n '#77337f',\n '#6b1c60',\n '#591650',\n '#470f3f'\n ];\n // Red Palette\n const red = [\n '#ffd8d4',\n '#ffb5b0',\n '#ff938c',\n '#ff766c',\n '#ff584c',\n '#f04b42',\n '#e03d38',\n '#be2e29',\n '#9c1e19'\n ];\n\n const colorSchemas = {\n britecharts,\n grey,\n orange,\n blueGreen,\n teal,\n green,\n yellow,\n pink,\n purple,\n red\n };\n const colorSchemasHuman = {\n 'britecharts': 'Britecharts Default',\n 'grey': 'Britecharts Grey',\n 'orange': 'Orange',\n 'blueGreen': 'Blue',\n 'teal': 'Light Blue',\n 'green': 'Green',\n 'yellow': 'Yellow',\n 'pink': 'Pink',\n 'purple': 'Purple',\n 'red': 'Red'\n };\n\n // Single Colors\n const aloeGreen = ['#7bdcc0']; // To Deprecate\n const greenColor = ['#6aedc7'];\n const blueColor = ['#39c2c9'];\n const yellowColor = ['#ffce00'];\n const orangeColor = ['#ffa71a'];\n const pinkColor = ['#f866b9'];\n const purpleColor = ['#998ce3'];\n\n const singleColors = {\n aloeGreen,\n greenColor,\n blueColor,\n yellowColor,\n orangeColor,\n pinkColor,\n purpleColor,\n };\n const singleColorsHuman = {\n aloeGreen: 'Aloe Green',\n greenColor: 'Green',\n blueColor: 'Blue',\n yellowColor: 'Yellow',\n orangeColor: 'Orange',\n pinkColor: 'Pink',\n purpleColor: 'Purple',\n };\n\n return {\n colorSchemas,\n colorSchemasHuman,\n colorGradients,\n colorGradientsHuman,\n singleColors,\n singleColorsHuman,\n };\n});\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /js/britecharts/umd/loading.min.js: -------------------------------------------------------------------------------- 1 | !function(t,o){"object"==typeof exports&&"object"==typeof module?module.exports=o():"function"==typeof define&&define.amd?define([],o):"object"==typeof exports?exports.loading=o():(t.britecharts=t.britecharts||{},t.britecharts.loading=o())}(window,(function(){return function(t){var o={};function n(f){if(o[f])return o[f].exports;var e=o[f]={i:f,l:!1,exports:{}};return t[f].call(e.exports,e,e.exports,n),e.l=!0,e.exports}return n.m=t,n.c=o,n.d=function(t,o,f){n.o(t,o)||Object.defineProperty(t,o,{enumerable:!0,get:f})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,o){if(1&o&&(t=n(t)),8&o)return t;if(4&o&&"object"==typeof t&&t&&t.__esModule)return t;var f=Object.create(null);if(n.r(f),Object.defineProperty(f,"default",{enumerable:!0,value:t}),2&o&&"string"!=typeof t)for(var e in t)n.d(f,e,function(o){return t[o]}.bind(null,e));return f},n.n=function(t){var o=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(o,"a",o),o},n.o=function(t,o){return Object.prototype.hasOwnProperty.call(t,o)},n.p="",n(n.s=109)}({109:function(t,o,n){t.exports=n(83)},83:function(t,o,n){"use strict";var f;void 0===(f=function(){return{bar:'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ',donut:'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ',line:'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n ',stackedArea:'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n '}}.call(o,n,o,t))||(t.exports=f)}})})); 2 | //# sourceMappingURL=loading.min.js.map -------------------------------------------------------------------------------- /js/britecharts/umd/loading.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack://britecharts.[name]/webpack/universalModuleDefinition","webpack://britecharts.[name]/webpack/bootstrap","webpack://britecharts.[name]/./src/charts/helpers/load.js"],"names":["root","factory","exports","module","define","amd","window","installedModules","__webpack_require__","moduleId","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","bar","donut","line","stackedArea"],"mappings":"CAAA,SAA2CA,EAAMC,GAC1B,iBAAZC,SAA0C,iBAAXC,OACxCA,OAAOD,QAAUD,IACQ,mBAAXG,QAAyBA,OAAOC,IAC9CD,OAAO,GAAIH,GACe,iBAAZC,QACdA,QAAiB,QAAID,KAErBD,EAAkB,YAAIA,EAAkB,aAAK,GAAIA,EAAkB,YAAW,QAAIC,KARpF,CASGK,QAAQ,WACX,O,YCTE,IAAIC,EAAmB,GAGvB,SAASC,EAAoBC,GAG5B,GAAGF,EAAiBE,GACnB,OAAOF,EAAiBE,GAAUP,QAGnC,IAAIC,EAASI,EAAiBE,GAAY,CACzCC,EAAGD,EACHE,GAAG,EACHT,QAAS,IAUV,OANAU,EAAQH,GAAUI,KAAKV,EAAOD,QAASC,EAAQA,EAAOD,QAASM,GAG/DL,EAAOQ,GAAI,EAGJR,EAAOD,QA0Df,OArDAM,EAAoBM,EAAIF,EAGxBJ,EAAoBO,EAAIR,EAGxBC,EAAoBQ,EAAI,SAASd,EAASe,EAAMC,GAC3CV,EAAoBW,EAAEjB,EAASe,IAClCG,OAAOC,eAAenB,EAASe,EAAM,CAAEK,YAAY,EAAMC,IAAKL,KAKhEV,EAAoBgB,EAAI,SAAStB,GACX,oBAAXuB,QAA0BA,OAAOC,aAC1CN,OAAOC,eAAenB,EAASuB,OAAOC,YAAa,CAAEC,MAAO,WAE7DP,OAAOC,eAAenB,EAAS,aAAc,CAAEyB,OAAO,KAQvDnB,EAAoBoB,EAAI,SAASD,EAAOE,GAEvC,GADU,EAAPA,IAAUF,EAAQnB,EAAoBmB,IAC/B,EAAPE,EAAU,OAAOF,EACpB,GAAW,EAAPE,GAA8B,iBAAVF,GAAsBA,GAASA,EAAMG,WAAY,OAAOH,EAChF,IAAII,EAAKX,OAAOY,OAAO,MAGvB,GAFAxB,EAAoBgB,EAAEO,GACtBX,OAAOC,eAAeU,EAAI,UAAW,CAAET,YAAY,EAAMK,MAAOA,IACtD,EAAPE,GAA4B,iBAATF,EAAmB,IAAI,IAAIM,KAAON,EAAOnB,EAAoBQ,EAAEe,EAAIE,EAAK,SAASA,GAAO,OAAON,EAAMM,IAAQC,KAAK,KAAMD,IAC9I,OAAOF,GAIRvB,EAAoB2B,EAAI,SAAShC,GAChC,IAAIe,EAASf,GAAUA,EAAO2B,WAC7B,WAAwB,OAAO3B,EAAgB,SAC/C,WAA8B,OAAOA,GAEtC,OADAK,EAAoBQ,EAAEE,EAAQ,IAAKA,GAC5BA,GAIRV,EAAoBW,EAAI,SAASiB,EAAQC,GAAY,OAAOjB,OAAOkB,UAAUC,eAAe1B,KAAKuB,EAAQC,IAGzG7B,EAAoBgC,EAAI,GAIjBhC,EAAoBA,EAAoBiC,EAAI,K,kFClFrD,KAAArC,aAsGI,MAAO,CACHsC,IA1FQA,+mFA2FRC,MA/DUA,i3CAgEVC,KAnDSA,ijDAoDTC,YA9BgBA,02FA5ExB","file":"loading.min.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"loading\"] = factory();\n\telse\n\t\troot[\"britecharts\"] = root[\"britecharts\"] || {}, root[\"britecharts\"][\"loading\"] = factory();\n})(window, function() {\nreturn "," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 109);\n","define(function() {\n\n const linearGradient = `\n \n \n \n \n \n \n \n \n \n `;\n const bar = `\n \n ${linearGradient}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n `;\n const donut = `\n \n ${linearGradient}\n \n \n \n \n \n \n \n \n \n `;\n const line = `\n \n ${linearGradient}\n \n \n \n \n `;\n const stackedArea = `\n \n ${linearGradient}\n \n \n \n \n \n \n \n `;\n\n return {\n bar,\n donut,\n line,\n stackedArea\n };\n});\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /js/d3-selection/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parserOptions": { 4 | "sourceType": "module", 5 | "ecmaVersion": 8 6 | }, 7 | "env": { 8 | "es6": true, 9 | "node": true, 10 | "browser": true 11 | }, 12 | "rules": { 13 | "no-cond-assign": 0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /js/d3-selection/dist/d3-selection.min.js: -------------------------------------------------------------------------------- 1 | // https://d3js.org/d3-selection/ v3.0.0 Copyright 2010-2021 Mike Bostock 2 | !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).d3=t.d3||{})}(this,(function(t){"use strict";var n="http://www.w3.org/1999/xhtml",e={svg:"http://www.w3.org/2000/svg",xhtml:n,xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};function r(t){var n=t+="",r=n.indexOf(":");return r>=0&&"xmlns"!==(n=t.slice(0,r))&&(t=t.slice(r+1)),e.hasOwnProperty(n)?{space:e[n],local:t}:t}function i(t){return function(){var e=this.ownerDocument,r=this.namespaceURI;return r===n&&e.documentElement.namespaceURI===n?e.createElement(t):e.createElementNS(r,t)}}function o(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function u(t){var n=r(t);return(n.local?o:i)(n)}function s(){}function c(t){return null==t?s:function(){return this.querySelector(t)}}function l(t){return null==t?[]:Array.isArray(t)?t:Array.from(t)}function a(){return[]}function f(t){return null==t?a:function(){return this.querySelectorAll(t)}}function h(t){return function(){return this.matches(t)}}function p(t){return function(n){return n.matches(t)}}var _=Array.prototype.find;function d(){return this.firstElementChild}var y=Array.prototype.filter;function m(){return Array.from(this.children)}function v(t){return new Array(t.length)}function g(t,n){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=n}function w(t){return function(){return t}}function A(t,n,e,r,i,o){for(var u,s=0,c=n.length,l=o.length;sn?1:t>=n?0:NaN}function N(t){return function(){this.removeAttribute(t)}}function C(t){return function(){this.removeAttributeNS(t.space,t.local)}}function L(t,n){return function(){this.setAttribute(t,n)}}function P(t,n){return function(){this.setAttributeNS(t.space,t.local,n)}}function T(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttribute(t):this.setAttribute(t,e)}}function B(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,e)}}function M(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}function q(t){return function(){this.style.removeProperty(t)}}function D(t,n,e){return function(){this.style.setProperty(t,n,e)}}function O(t,n,e){return function(){var r=n.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,e)}}function V(t,n){return t.style.getPropertyValue(n)||M(t).getComputedStyle(t,null).getPropertyValue(n)}function j(t){return function(){delete this[t]}}function R(t,n){return function(){this[t]=n}}function H(t,n){return function(){var e=n.apply(this,arguments);null==e?delete this[t]:this[t]=e}}function I(t){return t.trim().split(/^|\s+/)}function U(t){return t.classList||new X(t)}function X(t){this._node=t,this._names=I(t.getAttribute("class")||"")}function G(t,n){for(var e=U(t),r=-1,i=n.length;++r=0&&(n=t.slice(e+1),t=t.slice(0,e)),{type:t,name:n}}))}function st(t){return function(){var n=this.__on;if(n){for(var e,r=0,i=-1,o=n.length;r=0&&(this._names.splice(n,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var ht=[null];function pt(t,n){this._groups=t,this._parents=n}function _t(){return new pt([[document.documentElement]],ht)}function dt(t){return"string"==typeof t?new pt([[document.querySelector(t)]],[document.documentElement]):new pt([[t]],ht)}pt.prototype=_t.prototype={constructor:pt,select:function(t){"function"!=typeof t&&(t=c(t));for(var n=this._groups,e=n.length,r=new Array(e),i=0;i=N&&(N=E+1);!(g=y[N])&&++N<_;);v._next=g||null}}return(u=new pt(u,r))._enter=s,u._exit=c,u},enter:function(){return new pt(this._enter||this._groups.map(v),this._parents)},exit:function(){return new pt(this._exit||this._groups.map(v),this._parents)},join:function(t,n,e){var r=this.enter(),i=this,o=this.exit();return"function"==typeof t?(r=t(r))&&(r=r.selection()):r=r.append(t+""),null!=n&&(i=n(i))&&(i=i.selection()),null==e?o.remove():e(o),r&&i?r.merge(i).order():i},merge:function(t){for(var n=t.selection?t.selection():t,e=this._groups,r=n._groups,i=e.length,o=r.length,u=Math.min(i,o),s=new Array(i),c=0;c=0;)(r=i[o])&&(u&&4^r.compareDocumentPosition(u)&&u.parentNode.insertBefore(r,u),u=r);return this},sort:function(t){function n(n,e){return n&&e?t(n.__data__,e.__data__):!n-!e}t||(t=E);for(var e=this._groups,r=e.length,i=new Array(r),o=0;o1?this.each((null==n?q:"function"==typeof n?O:D)(t,n,null==e?"":e)):V(this.node(),t)},property:function(t,n){return arguments.length>1?this.each((null==n?j:"function"==typeof n?H:R)(t,n)):this.node()[t]},classed:function(t,n){var e=I(t+"");if(arguments.length<2){for(var r=U(this.node()),i=-1,o=e.length;++iwt(t,n)))},t.select=dt,t.selectAll=function(t){return"string"==typeof t?new pt([document.querySelectorAll(t)],[document.documentElement]):new pt([l(t)],ht)},t.selection=_t,t.selector=c,t.selectorAll=f,t.style=V,t.window=M,Object.defineProperty(t,"__esModule",{value:!0})})); 3 | -------------------------------------------------------------------------------- /js/lib/bootswatch.js: -------------------------------------------------------------------------------- 1 | $('[data-toggle="tooltip"]').tooltip(); -------------------------------------------------------------------------------- /js/lib/d3-selection.min.js: -------------------------------------------------------------------------------- 1 | // https://d3js.org/d3-selection/ v1.4.0 Copyright 2019 Mike Bostock 2 | !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n(t.d3=t.d3||{})}(this,function(t){"use strict";var n="http://www.w3.org/1999/xhtml",e={svg:"http://www.w3.org/2000/svg",xhtml:n,xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};function r(t){var n=t+="",r=n.indexOf(":");return r>=0&&"xmlns"!==(n=t.slice(0,r))&&(t=t.slice(r+1)),e.hasOwnProperty(n)?{space:e[n],local:t}:t}function i(t){var e=r(t);return(e.local?function(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}:function(t){return function(){var e=this.ownerDocument,r=this.namespaceURI;return r===n&&e.documentElement.namespaceURI===n?e.createElement(t):e.createElementNS(r,t)}})(e)}function o(){}function u(t){return null==t?o:function(){return this.querySelector(t)}}function c(){return[]}function s(t){return null==t?c:function(){return this.querySelectorAll(t)}}function a(t){return function(){return this.matches(t)}}function l(t){return new Array(t.length)}function f(t,n){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=n}f.prototype={constructor:f,appendChild:function(t){return this._parent.insertBefore(t,this._next)},insertBefore:function(t,n){return this._parent.insertBefore(t,n)},querySelector:function(t){return this._parent.querySelector(t)},querySelectorAll:function(t){return this._parent.querySelectorAll(t)}};var h="$";function p(t,n,e,r,i,o){for(var u,c=0,s=n.length,a=o.length;cn?1:t>=n?0:NaN}function d(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}function y(t,n){return t.style.getPropertyValue(n)||d(t).getComputedStyle(t,null).getPropertyValue(n)}function m(t){return t.trim().split(/^|\s+/)}function g(t){return t.classList||new w(t)}function w(t){this._node=t,this._names=m(t.getAttribute("class")||"")}function A(t,n){for(var e=g(t),r=-1,i=n.length;++r=0&&(this._names.splice(n,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var B={};(t.event=null,"undefined"!=typeof document)&&("onmouseenter"in document.documentElement||(B={mouseenter:"mouseover",mouseleave:"mouseout"}));function q(t,n,e){return t=D(t,n,e),function(n){var e=n.relatedTarget;e&&(e===this||8&e.compareDocumentPosition(this))||t.call(this,n)}}function D(n,e,r){return function(i){var o=t.event;t.event=i;try{n.call(this,this.__data__,e,r)}finally{t.event=o}}}function M(t){return function(){var n=this.__on;if(n){for(var e,r=0,i=-1,o=n.length;r=S&&(S=x+1);!(A=g[S])&&++S=0;)(r=i[o])&&(u&&4^r.compareDocumentPosition(u)&&u.parentNode.insertBefore(r,u),u=r);return this},sort:function(t){function n(n,e){return n&&e?t(n.__data__,e.__data__):!n-!e}t||(t=v);for(var e=this._groups,r=e.length,i=new Array(r),o=0;o1?this.each((null==n?function(t){return function(){this.style.removeProperty(t)}}:"function"==typeof n?function(t,n,e){return function(){var r=n.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,e)}}:function(t,n,e){return function(){this.style.setProperty(t,n,e)}})(t,n,null==e?"":e)):y(this.node(),t)},property:function(t,n){return arguments.length>1?this.each((null==n?function(t){return function(){delete this[t]}}:"function"==typeof n?function(t,n){return function(){var e=n.apply(this,arguments);null==e?delete this[t]:this[t]=e}}:function(t,n){return function(){this[t]=n}})(t,n)):this.node()[t]},classed:function(t,n){var e=m(t+"");if(arguments.length<2){for(var r=g(this.node()),i=-1,o=e.length;++i=0&&(n=t.slice(e+1),t=t.slice(0,e)),{type:t,name:n}})}(t+""),u=o.length;if(!(arguments.length<2)){for(c=n?O:M,null==e&&(e=!1),r=0;r \n" 7 | exit 1 8 | fi 9 | 10 | CLUSTER_NAME=$1 11 | DOCKER_IMAGE=$2 12 | KOA_K8S_API_ENDPOINT=$(kubectl config view -o jsonpath="{.clusters[?(@.name==\"$CLUSTER_NAME\")].cluster.server}") 13 | if [ "$KOA_K8S_API_ENDPOINT" != "" ]; then 14 | KOA_K8S_AUTH_TOKEN=$(kubectl get secrets -o jsonpath="{.items[?(@.metadata.annotations['kubernetes\.io/service-account\.name']=='default')].data.token}"|base64 -d) 15 | else 16 | echo "cannot find KOA_K8S_API_ENDPOINT" 17 | fi 18 | 19 | if [ "$KOA_K8S_AUTH_TOKEN" != "" ]; then 20 | fuser -k 5483/tcp || true 21 | 22 | export KOA_K8S_API_ENDPOINT 23 | export KOA_K8S_AUTH_TOKEN 24 | export KOA_ENABLE_DEBUG=true 25 | export KOA_K8S_API_VERIFY_SSL=false 26 | export KOA_BILLING_HOURLY_RATE=9.92 27 | export KOA_BILLING_CURRENCY_SYMBOL='$' 28 | export KOA_COST_MODEL='CHARGE_BACK' 29 | 30 | docker run -d \ 31 | --net="host" \ 32 | --name 'kube-opex-analytics' \ 33 | -v /var/lib/kube-opex-analytics:/data \ 34 | -e KOA_DB_LOCATION=/data/db \ 35 | -e KOA_K8S_API_ENDPOINT=$KOA_K8S_API_ENDPOINT \ 36 | -e KOA_K8S_AUTH_TOKEN=$KOA_K8S_AUTH_TOKEN \ 37 | -e KOA_ENABLE_DEBUG=$KOA_ENABLE_DEBUG \ 38 | -e KOA_K8S_API_VERIFY_SSL=$KOA_K8S_API_VERIFY_SSL \ 39 | -e KOA_BILLING_HOURLY_RATE=$KOA_BILLING_HOURLY_RATE \ 40 | -e KOA_BILLING_CURRENCY_SYMBOL=$KOA_BILLING_CURRENCY_SYMBOL \ 41 | -e KOA_COST_MODEL=$KOA_COST_MODEL \ 42 | $DOCKER_IMAGE 43 | else 44 | echo "cannot find KOA_K8S_AUTH_TOKEN" 45 | fi 46 | -------------------------------------------------------------------------------- /run-debug.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -u 3 | set -e 4 | 5 | if [ $# -ne 1 ]; then 6 | echo -e "usage: \n\t`basename $0` \n" 7 | exit 1 8 | fi 9 | 10 | CLUSTER_NAME=$1 11 | KOA_K8S_API_ENDPOINT=$(kubectl config view -o jsonpath="{.clusters[?(@.name==\"$CLUSTER_NAME\")].cluster.server}") 12 | if [ "$KOA_K8S_API_ENDPOINT" == "" ]; then 13 | echo "cannot find KOA_K8S_API_ENDPOINT" 14 | exit 1 15 | fi 16 | 17 | KOA_K8S_AUTH_TOKEN=$(kubectl get secrets -n kube-opex-analytics -o jsonpath="{.items[?(@.metadata.annotations['kubernetes\.io/service-account\.name']=='kube-opex-analytics')].data.token}"|base64 -d) 18 | if [ "$KOA_K8S_AUTH_TOKEN" == "" ]; then 19 | echo "cannot find KOA_K8S_AUTH_TOKEN" 20 | exit 1 21 | fi 22 | 23 | export KOA_DB_LOCATION=${KOA_DB_LOCATION:-$PWD/db} 24 | export KOA_K8S_API_ENDPOINT 25 | export KOA_K8S_AUTH_TOKEN 26 | export KOA_ENABLE_DEBUG=true 27 | export KOA_K8S_API_VERIFY_SSL=false 28 | export KOA_BILLING_HOURLY_RATE=${KOA_BILLING_HOURLY_RATE:-9.92} 29 | export KOA_BILLING_CURRENCY_SYMBOL=${KOA_BILLING_CURRENCY_SYMBOL:-'$'} 30 | export KOA_COST_MODEL=${KOA_COST_MODEL:-CUMULATIVE_RATIO} 31 | export KOA_POLLING_INTERVAL_SEC=${KOA_POLLING_INTERVAL_SEC:-300} 32 | 33 | fuser -k 5483/tcp || true 34 | ./entrypoint.sh 35 | -------------------------------------------------------------------------------- /screenshots/dataset-export-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/screenshots/dataset-export-options.png -------------------------------------------------------------------------------- /screenshots/export-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/screenshots/export-menu.png -------------------------------------------------------------------------------- /screenshots/krossboard-current-usage-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/screenshots/krossboard-current-usage-overview.png -------------------------------------------------------------------------------- /screenshots/kube-opex-analytics-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/screenshots/kube-opex-analytics-demo.gif -------------------------------------------------------------------------------- /screenshots/kube-opex-analytics-grafana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/screenshots/kube-opex-analytics-grafana.png -------------------------------------------------------------------------------- /screenshots/kube-opex-analytics-hourly-consolidated-usage-trends.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/screenshots/kube-opex-analytics-hourly-consolidated-usage-trends.png -------------------------------------------------------------------------------- /screenshots/kube-opex-analytics-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/screenshots/kube-opex-analytics-overview.png -------------------------------------------------------------------------------- /screenshots/kube-opex-analytics-usage-requests-efficiency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/screenshots/kube-opex-analytics-usage-requests-efficiency.png -------------------------------------------------------------------------------- /screenshots/sample-last-nodes-occupation-by-pods.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/screenshots/sample-last-nodes-occupation-by-pods.png -------------------------------------------------------------------------------- /screenshots/sample-one-week-hourly-usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/screenshots/sample-one-week-hourly-usage.png -------------------------------------------------------------------------------- /screenshots/sample-one-year-monthly-usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/screenshots/sample-one-year-monthly-usage.png -------------------------------------------------------------------------------- /screenshots/sample-two-weeks-daily-usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/screenshots/sample-two-weeks-daily-usage.png -------------------------------------------------------------------------------- /screenshots/thumbnail-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/screenshots/thumbnail-header.png -------------------------------------------------------------------------------- /screenshots/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/screenshots/thumbnail.png -------------------------------------------------------------------------------- /static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/static/images/favicon.ico -------------------------------------------------------------------------------- /static/images/kube-opex-analytics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rchakode/kube-opex-analytics/af064920952ecb021bccd3d1371f6a75946a317a/static/images/kube-opex-analytics.png -------------------------------------------------------------------------------- /test_backend.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | __author__ = "Rodrigue Chakode" 3 | __copyright__ = "Copyright 2019 Rodrigue Chakode and contributors" 4 | __credits__ = ["Rodrigue Chakode and contributors"] 5 | __license__ = "Apache" 6 | __version__ = "2.0" 7 | __maintainer__ = "Rodrigue Chakode" 8 | __email__ = "Rodrigue Chakode ` to any timeseries scraped from this config. 13 | - job_name: 'prometheus' 14 | 15 | # Override the global default and scrape targets from this job every 5 seconds. 16 | scrape_interval: 30s 17 | 18 | static_configs: 19 | - targets: ['localhost:9090'] 20 | - job_name: 'kube-opex-analytics' 21 | 22 | # Override the global default and scrape targets from this job every 5 seconds. 23 | scrape_interval: 300s 24 | 25 | static_configs: 26 | - targets: ['localhost:5483'] -------------------------------------------------------------------------------- /third-parties/grafana/kube-opex-analytics.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [], 3 | "__requires": [ 4 | { 5 | "type": "grafana", 6 | "id": "grafana", 7 | "name": "Grafana", 8 | "version": "6.2.1" 9 | }, 10 | { 11 | "type": "panel", 12 | "id": "graph", 13 | "name": "Graph", 14 | "version": "" 15 | }, 16 | { 17 | "type": "datasource", 18 | "id": "prometheus", 19 | "name": "Prometheus", 20 | "version": "1.0.0" 21 | } 22 | ], 23 | "annotations": { 24 | "list": [ 25 | { 26 | "builtIn": 1, 27 | "datasource": "-- Grafana --", 28 | "enable": true, 29 | "hide": true, 30 | "iconColor": "rgba(0, 211, 255, 1)", 31 | "name": "Annotations & Alerts", 32 | "type": "dashboard" 33 | } 34 | ] 35 | }, 36 | "editable": true, 37 | "gnetId": null, 38 | "graphTooltip": 0, 39 | "id": null, 40 | "iteration": 1559464401566, 41 | "links": [], 42 | "panels": [ 43 | { 44 | "aliasColors": {}, 45 | "bars": false, 46 | "cacheTimeout": null, 47 | "dashLength": 10, 48 | "dashes": false, 49 | "datasource": "$KOA_DS_PROMETHEUS", 50 | "fill": 1, 51 | "gridPos": { 52 | "h": 9, 53 | "w": 12, 54 | "x": 0, 55 | "y": 0 56 | }, 57 | "id": 2, 58 | "legend": { 59 | "avg": false, 60 | "current": false, 61 | "max": false, 62 | "min": false, 63 | "show": true, 64 | "total": false, 65 | "values": false 66 | }, 67 | "lines": true, 68 | "linewidth": 1, 69 | "links": [], 70 | "nullPointMode": "null", 71 | "options": {}, 72 | "percentage": false, 73 | "pluginVersion": "6.2.1", 74 | "pointradius": 2, 75 | "points": false, 76 | "renderer": "flot", 77 | "seriesOverrides": [], 78 | "spaceLength": 10, 79 | "stack": true, 80 | "steppedLine": false, 81 | "targets": [ 82 | { 83 | "expr": "koa_namespace_hourly_usage{resource=\"CPU\"}", 84 | "format": "time_series", 85 | "intervalFactor": 1, 86 | "legendFormat": "{{ namespace }}", 87 | "refId": "A" 88 | } 89 | ], 90 | "thresholds": [], 91 | "timeFrom": null, 92 | "timeRegions": [], 93 | "timeShift": null, 94 | "title": "Hourly CPU Usage", 95 | "tooltip": { 96 | "shared": true, 97 | "sort": 0, 98 | "value_type": "individual" 99 | }, 100 | "type": "graph", 101 | "xaxis": { 102 | "buckets": null, 103 | "mode": "time", 104 | "name": null, 105 | "show": true, 106 | "values": [] 107 | }, 108 | "yaxes": [ 109 | { 110 | "format": "short", 111 | "label": null, 112 | "logBase": 1, 113 | "max": null, 114 | "min": null, 115 | "show": true 116 | }, 117 | { 118 | "format": "short", 119 | "label": null, 120 | "logBase": 1, 121 | "max": null, 122 | "min": null, 123 | "show": true 124 | } 125 | ], 126 | "yaxis": { 127 | "align": false, 128 | "alignLevel": null 129 | } 130 | }, 131 | { 132 | "aliasColors": {}, 133 | "bars": false, 134 | "cacheTimeout": null, 135 | "dashLength": 10, 136 | "dashes": false, 137 | "datasource": "$KOA_DS_PROMETHEUS", 138 | "fill": 1, 139 | "gridPos": { 140 | "h": 9, 141 | "w": 12, 142 | "x": 12, 143 | "y": 0 144 | }, 145 | "id": 3, 146 | "legend": { 147 | "avg": false, 148 | "current": false, 149 | "max": false, 150 | "min": false, 151 | "show": true, 152 | "total": false, 153 | "values": false 154 | }, 155 | "lines": true, 156 | "linewidth": 1, 157 | "links": [], 158 | "nullPointMode": "null", 159 | "options": {}, 160 | "percentage": false, 161 | "pluginVersion": "6.2.1", 162 | "pointradius": 2, 163 | "points": false, 164 | "renderer": "flot", 165 | "seriesOverrides": [], 166 | "spaceLength": 10, 167 | "stack": true, 168 | "steppedLine": false, 169 | "targets": [ 170 | { 171 | "expr": "koa_namespace_hourly_usage{resource=\"MEMORY\"}", 172 | "format": "time_series", 173 | "intervalFactor": 1, 174 | "legendFormat": "{{ namespace }}", 175 | "refId": "A" 176 | } 177 | ], 178 | "thresholds": [], 179 | "timeFrom": null, 180 | "timeRegions": [], 181 | "timeShift": null, 182 | "title": "Hourly Memory Usage", 183 | "tooltip": { 184 | "shared": true, 185 | "sort": 0, 186 | "value_type": "individual" 187 | }, 188 | "type": "graph", 189 | "xaxis": { 190 | "buckets": null, 191 | "mode": "time", 192 | "name": null, 193 | "show": true, 194 | "values": [] 195 | }, 196 | "yaxes": [ 197 | { 198 | "format": "short", 199 | "label": null, 200 | "logBase": 1, 201 | "max": null, 202 | "min": null, 203 | "show": true 204 | }, 205 | { 206 | "format": "short", 207 | "label": null, 208 | "logBase": 1, 209 | "max": null, 210 | "min": null, 211 | "show": true 212 | } 213 | ], 214 | "yaxis": { 215 | "align": false, 216 | "alignLevel": null 217 | } 218 | }, 219 | { 220 | "aliasColors": {}, 221 | "bars": true, 222 | "cacheTimeout": null, 223 | "dashLength": 10, 224 | "dashes": false, 225 | "datasource": "$KOA_DS_PROMETHEUS", 226 | "fill": 1, 227 | "gridPos": { 228 | "h": 9, 229 | "w": 12, 230 | "x": 0, 231 | "y": 9 232 | }, 233 | "id": 9, 234 | "legend": { 235 | "alignAsTable": false, 236 | "avg": false, 237 | "current": false, 238 | "max": false, 239 | "min": false, 240 | "rightSide": false, 241 | "show": true, 242 | "total": false, 243 | "values": false 244 | }, 245 | "lines": false, 246 | "linewidth": 1, 247 | "links": [], 248 | "nullPointMode": "null", 249 | "options": {}, 250 | "percentage": false, 251 | "pluginVersion": "6.2.1", 252 | "pointradius": 2, 253 | "points": false, 254 | "renderer": "flot", 255 | "seriesOverrides": [], 256 | "spaceLength": 10, 257 | "stack": false, 258 | "steppedLine": false, 259 | "targets": [ 260 | { 261 | "expr": "koa_namespace_daily_usage{resource=\"CPU\"}", 262 | "format": "time_series", 263 | "intervalFactor": 1, 264 | "legendFormat": "{{ namespace }}", 265 | "refId": "A" 266 | } 267 | ], 268 | "thresholds": [], 269 | "timeFrom": null, 270 | "timeRegions": [], 271 | "timeShift": null, 272 | "title": "Current Day's CPU Usage", 273 | "tooltip": { 274 | "shared": false, 275 | "sort": 0, 276 | "value_type": "individual" 277 | }, 278 | "type": "graph", 279 | "xaxis": { 280 | "buckets": null, 281 | "mode": "series", 282 | "name": null, 283 | "show": true, 284 | "values": [ 285 | "current" 286 | ] 287 | }, 288 | "yaxes": [ 289 | { 290 | "format": "short", 291 | "label": null, 292 | "logBase": 1, 293 | "max": null, 294 | "min": null, 295 | "show": true 296 | }, 297 | { 298 | "format": "short", 299 | "label": null, 300 | "logBase": 1, 301 | "max": null, 302 | "min": null, 303 | "show": true 304 | } 305 | ], 306 | "yaxis": { 307 | "align": false, 308 | "alignLevel": null 309 | } 310 | }, 311 | { 312 | "aliasColors": {}, 313 | "bars": true, 314 | "cacheTimeout": null, 315 | "dashLength": 10, 316 | "dashes": false, 317 | "datasource": "$KOA_DS_PROMETHEUS", 318 | "fill": 1, 319 | "gridPos": { 320 | "h": 9, 321 | "w": 12, 322 | "x": 12, 323 | "y": 9 324 | }, 325 | "id": 8, 326 | "legend": { 327 | "alignAsTable": false, 328 | "avg": false, 329 | "current": false, 330 | "max": false, 331 | "min": false, 332 | "rightSide": false, 333 | "show": true, 334 | "total": false, 335 | "values": false 336 | }, 337 | "lines": false, 338 | "linewidth": 1, 339 | "links": [], 340 | "nullPointMode": "null", 341 | "options": {}, 342 | "percentage": false, 343 | "pluginVersion": "6.2.1", 344 | "pointradius": 2, 345 | "points": false, 346 | "renderer": "flot", 347 | "seriesOverrides": [], 348 | "spaceLength": 10, 349 | "stack": false, 350 | "steppedLine": false, 351 | "targets": [ 352 | { 353 | "expr": "koa_namespace_daily_usage{resource=\"MEMORY\"}", 354 | "format": "time_series", 355 | "intervalFactor": 1, 356 | "legendFormat": "{{ namespace }}", 357 | "refId": "A" 358 | } 359 | ], 360 | "thresholds": [], 361 | "timeFrom": null, 362 | "timeRegions": [], 363 | "timeShift": null, 364 | "title": "Current Day's Memory Usage", 365 | "tooltip": { 366 | "shared": false, 367 | "sort": 0, 368 | "value_type": "individual" 369 | }, 370 | "type": "graph", 371 | "xaxis": { 372 | "buckets": null, 373 | "mode": "series", 374 | "name": null, 375 | "show": true, 376 | "values": [ 377 | "current" 378 | ] 379 | }, 380 | "yaxes": [ 381 | { 382 | "format": "short", 383 | "label": null, 384 | "logBase": 1, 385 | "max": null, 386 | "min": null, 387 | "show": true 388 | }, 389 | { 390 | "format": "short", 391 | "label": null, 392 | "logBase": 1, 393 | "max": null, 394 | "min": null, 395 | "show": true 396 | } 397 | ], 398 | "yaxis": { 399 | "align": false, 400 | "alignLevel": null 401 | } 402 | }, 403 | { 404 | "aliasColors": {}, 405 | "bars": true, 406 | "cacheTimeout": null, 407 | "dashLength": 10, 408 | "dashes": false, 409 | "datasource": "$KOA_DS_PROMETHEUS", 410 | "fill": 1, 411 | "gridPos": { 412 | "h": 9, 413 | "w": 12, 414 | "x": 0, 415 | "y": 18 416 | }, 417 | "id": 6, 418 | "legend": { 419 | "alignAsTable": false, 420 | "avg": false, 421 | "current": false, 422 | "max": false, 423 | "min": false, 424 | "rightSide": false, 425 | "show": true, 426 | "total": false, 427 | "values": false 428 | }, 429 | "lines": false, 430 | "linewidth": 1, 431 | "links": [], 432 | "nullPointMode": "null", 433 | "options": {}, 434 | "percentage": false, 435 | "pluginVersion": "6.2.1", 436 | "pointradius": 2, 437 | "points": false, 438 | "renderer": "flot", 439 | "seriesOverrides": [], 440 | "spaceLength": 10, 441 | "stack": false, 442 | "steppedLine": false, 443 | "targets": [ 444 | { 445 | "expr": "koa_namespace_monthly_usage{resource=\"CPU\"}", 446 | "format": "time_series", 447 | "intervalFactor": 1, 448 | "legendFormat": "{{ namespace }}", 449 | "refId": "A" 450 | } 451 | ], 452 | "thresholds": [], 453 | "timeFrom": null, 454 | "timeRegions": [], 455 | "timeShift": null, 456 | "title": "Current Month's CPU Usage", 457 | "tooltip": { 458 | "shared": false, 459 | "sort": 0, 460 | "value_type": "individual" 461 | }, 462 | "type": "graph", 463 | "xaxis": { 464 | "buckets": null, 465 | "mode": "series", 466 | "name": null, 467 | "show": true, 468 | "values": [ 469 | "current" 470 | ] 471 | }, 472 | "yaxes": [ 473 | { 474 | "format": "short", 475 | "label": null, 476 | "logBase": 1, 477 | "max": null, 478 | "min": null, 479 | "show": true 480 | }, 481 | { 482 | "format": "short", 483 | "label": null, 484 | "logBase": 1, 485 | "max": null, 486 | "min": null, 487 | "show": true 488 | } 489 | ], 490 | "yaxis": { 491 | "align": false, 492 | "alignLevel": null 493 | } 494 | }, 495 | { 496 | "aliasColors": {}, 497 | "bars": true, 498 | "cacheTimeout": null, 499 | "dashLength": 10, 500 | "dashes": false, 501 | "datasource": "$KOA_DS_PROMETHEUS", 502 | "fill": 1, 503 | "gridPos": { 504 | "h": 9, 505 | "w": 12, 506 | "x": 12, 507 | "y": 18 508 | }, 509 | "id": 7, 510 | "legend": { 511 | "alignAsTable": false, 512 | "avg": false, 513 | "current": false, 514 | "max": false, 515 | "min": false, 516 | "rightSide": false, 517 | "show": true, 518 | "total": false, 519 | "values": false 520 | }, 521 | "lines": false, 522 | "linewidth": 1, 523 | "links": [], 524 | "nullPointMode": "null", 525 | "options": {}, 526 | "percentage": false, 527 | "pluginVersion": "6.2.1", 528 | "pointradius": 2, 529 | "points": false, 530 | "renderer": "flot", 531 | "seriesOverrides": [], 532 | "spaceLength": 10, 533 | "stack": false, 534 | "steppedLine": false, 535 | "targets": [ 536 | { 537 | "expr": "koa_namespace_monthly_usage{resource=\"MEMORY\"}", 538 | "format": "time_series", 539 | "intervalFactor": 1, 540 | "legendFormat": "{{ namespace }}", 541 | "refId": "A" 542 | } 543 | ], 544 | "thresholds": [], 545 | "timeFrom": null, 546 | "timeRegions": [], 547 | "timeShift": null, 548 | "title": "Current Month's Memory Usage", 549 | "tooltip": { 550 | "shared": false, 551 | "sort": 0, 552 | "value_type": "individual" 553 | }, 554 | "type": "graph", 555 | "xaxis": { 556 | "buckets": null, 557 | "mode": "series", 558 | "name": null, 559 | "show": true, 560 | "values": [ 561 | "current" 562 | ] 563 | }, 564 | "yaxes": [ 565 | { 566 | "format": "short", 567 | "label": null, 568 | "logBase": 1, 569 | "max": null, 570 | "min": null, 571 | "show": true 572 | }, 573 | { 574 | "format": "short", 575 | "label": null, 576 | "logBase": 1, 577 | "max": null, 578 | "min": null, 579 | "show": true 580 | } 581 | ], 582 | "yaxis": { 583 | "align": false, 584 | "alignLevel": null 585 | } 586 | } 587 | ], 588 | "schemaVersion": 18, 589 | "style": "dark", 590 | "tags": [], 591 | "templating": { 592 | "list": [ 593 | { 594 | "current": { 595 | "text": "Prometheus", 596 | "value": "Prometheus" 597 | }, 598 | "hide": 0, 599 | "includeAll": false, 600 | "label": null, 601 | "multi": false, 602 | "name": "KOA_DS_PROMETHEUS", 603 | "options": [], 604 | "query": "prometheus", 605 | "refresh": 1, 606 | "regex": "", 607 | "skipUrlSync": false, 608 | "type": "datasource" 609 | } 610 | ] 611 | }, 612 | "time": { 613 | "from": "now-7d", 614 | "to": "now" 615 | }, 616 | "timepicker": { 617 | "refresh_intervals": [ 618 | "5s", 619 | "10s", 620 | "30s", 621 | "1m", 622 | "5m", 623 | "15m", 624 | "30m", 625 | "1h", 626 | "2h", 627 | "1d" 628 | ], 629 | "time_options": [ 630 | "5m", 631 | "15m", 632 | "1h", 633 | "6h", 634 | "12h", 635 | "24h", 636 | "2d", 637 | "7d", 638 | "30d" 639 | ] 640 | }, 641 | "timezone": "", 642 | "title": "kube-opex-analytics", 643 | "uid": "mO-3kMGWk", 644 | "version": 14 645 | } -------------------------------------------------------------------------------- /third-parties/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s # By default, scrape targets every 15 seconds. 3 | 4 | # Attach these labels to any time series or alerts when communicating with 5 | # external systems (federation, remote storage, Alertmanager). 6 | external_labels: 7 | monitor: 'codelab-monitor' 8 | 9 | # A scrape configuration containing exactly one endpoint to scrape: 10 | # Here it's Prometheus itself. 11 | scrape_configs: 12 | # The job name is added as a label `job=` to any timeseries scraped from this config. 13 | - job_name: 'prometheus' 14 | 15 | # Override the global default and scrape targets from this job every 5 seconds. 16 | scrape_interval: 30s 17 | 18 | static_configs: 19 | - targets: ['localhost:9090'] 20 | - job_name: 'kube-opex-analytics' 21 | 22 | # Override the global default and scrape targets from this job every 5 seconds. 23 | scrape_interval: 300s 24 | 25 | static_configs: 26 | - targets: ['localhost:5483'] -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = lint,testenv 3 | skipsdist = true 4 | 5 | [testenv] 6 | deps = -rrequirements.txt 7 | pytest 8 | commands = pytest 9 | 10 | [testenv:lint] 11 | deps = flake8 12 | flake8-import-order 13 | flake8-blind-except 14 | flake8-builtins 15 | flake8-docstrings 16 | flake8-rst-docstrings 17 | flake8-logging-format 18 | pydocstyle < 4.0.0 19 | pytest 20 | 21 | commands = flake8 {posargs} 22 | 23 | [flake8] 24 | show-source = true 25 | 26 | ignore = 27 | # B001 do not use bare except, specify exception instead 28 | B001, 29 | # B901 blind except: statement 30 | B901, 31 | # D100 Missing docstring in public module 32 | D100, 33 | # D101 Missing docstring in public class 34 | D101, 35 | # D102 Missing docstring in public method 36 | D102, 37 | # D103 Missing docstring in public function 38 | D103, 39 | # D104 Missing docstring in public package 40 | D104, 41 | # D105 Missing docstring in magic method 42 | D105, 43 | # D107 Missing docstring in __init__ 44 | D107, 45 | # E722 do not use bare except, specify exception instead 46 | E722, 47 | # G200 Logging statement uses exception in arguments 48 | G200 49 | # C901 Method is too complex 50 | C901 51 | # B902 blind except 52 | B902 53 | 54 | max-line-length = 120 55 | max-complexity = 16 56 | 57 | exclude = 58 | .tox 59 | .git 60 | build 61 | venv/* 62 | doc 63 | *.eff 64 | __pycache__ 65 | 66 | enable-extensions=G 67 | application-import-names = kube-opex-analytics 68 | --------------------------------------------------------------------------------