summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWesley Haakman <25249425+whaakman@users.noreply.github.com>2024-03-18 17:00:26 +0100
committerGitHub <noreply@github.com>2024-03-18 18:00:26 +0200
commit0251ffc903efff22b965503ed2db92d5404d5529 (patch)
tree5b7c3a85ce81bf7b6ac7e51bd64cdfbf476bddb9
parent4fe49d139de02a61c2ad979c3da8c6241e78fdc2 (diff)
CSV Exporter (#227)
* CSV Exporter * CSV Exporter * updated readme --------- Co-authored-by: Pavel Zhukov <33721692+LeaveMyYard@users.noreply.github.com>
-rw-r--r--README.md1
-rw-r--r--robusta_krr/formatters/__init__.py1
-rw-r--r--robusta_krr/formatters/csv.py111
3 files changed, 113 insertions, 0 deletions
diff --git a/README.md b/README.md
index 3a34a1a..908310d 100644
--- a/README.md
+++ b/README.md
@@ -285,6 +285,7 @@ Currently KRR ships with a few formatters to represent the scan data:
- `json`
- `yaml`
- `pprint` - data representation from python's pprint library
+- `csv_export` - export data to a csv file in the current directory
To run a strategy with a selected formatter, add a `-f` flag:
diff --git a/robusta_krr/formatters/__init__.py b/robusta_krr/formatters/__init__.py
index 325cf01..e34a25f 100644
--- a/robusta_krr/formatters/__init__.py
+++ b/robusta_krr/formatters/__init__.py
@@ -2,3 +2,4 @@ from .json import json
from .pprint import pprint
from .table import table
from .yaml import yaml
+from .csv import csv
diff --git a/robusta_krr/formatters/csv.py b/robusta_krr/formatters/csv.py
new file mode 100644
index 0000000..792c061
--- /dev/null
+++ b/robusta_krr/formatters/csv.py
@@ -0,0 +1,111 @@
+import itertools
+import csv
+
+import logging
+
+
+from robusta_krr.core.abstract import formatters
+from robusta_krr.core.models.allocations import RecommendationValue
+from robusta_krr.core.models.result import ResourceScan, ResourceType, Result
+from robusta_krr.utils import resource_units
+import datetime
+
+logger = logging.getLogger("krr")
+
+NONE_LITERAL = "unset"
+NAN_LITERAL = "?"
+
+
+def _format(value: RecommendationValue) -> str:
+ if value is None:
+ return NONE_LITERAL
+ elif isinstance(value, str):
+ return NAN_LITERAL
+ else:
+ return resource_units.format(value)
+
+
+def __calc_diff(allocated, recommended, selector, multiplier=1) -> str:
+ if recommended is None or isinstance(recommended.value, str) or selector != "requests":
+ return ""
+ else:
+ reccomended_val = recommended.value if isinstance(recommended.value, (int, float)) else 0
+ allocated_val = allocated if isinstance(allocated, (int, float)) else 0
+ diff_val = reccomended_val - allocated_val
+ diff_sign = "+" if diff_val >= 0 else "-"
+ return f"{diff_sign}{_format(abs(diff_val) * multiplier)}"
+
+
+def _format_request_str(item: ResourceScan, resource: ResourceType, selector: str) -> str:
+ allocated = getattr(item.object.allocations, selector)[resource]
+ recommended = getattr(item.recommended, selector)[resource]
+
+ if allocated is None and recommended.value is None:
+ return f"{NONE_LITERAL}"
+
+ diff = __calc_diff(allocated, recommended, selector)
+ if diff != "":
+ diff = f"({diff}) "
+
+ return (
+ diff
+ + _format(allocated)
+ + " -> "
+ + _format(recommended.value)
+ )
+
+
+def _format_total_diff(item: ResourceScan, resource: ResourceType, pods_current: int) -> str:
+ selector = "requests"
+ allocated = getattr(item.object.allocations, selector)[resource]
+ recommended = getattr(item.recommended, selector)[resource]
+
+ return __calc_diff(allocated, recommended, selector, pods_current)
+
+
+@formatters.register()
+def csv_export(result: Result) -> str:
+
+ current_datetime = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
+ file_path = f"krr-{current_datetime}.csv"
+
+ # We need to order the resource columns so that they are in the format of Namespace,Name,Pods,Old Pods,Type,Container,CPU Diff,CPU Requests,CPU Limits,Memory Diff,Memory Requests,Memory Limits
+ resource_columns = []
+ for resource in ResourceType:
+ resource_columns.append(f"{resource.name} Diff")
+ resource_columns.append(f"{resource.name} Requests")
+ resource_columns.append(f"{resource.name} Limits")
+
+ with open(file_path, 'w+', newline='') as csvfile:
+ csv_writer = csv.writer(csvfile)
+ csv_writer.writerow([
+ "Namespace", "Name", "Pods", "Old Pods", "Type", "Container",
+ *resource_columns
+
+ ])
+
+ for _, group in itertools.groupby(
+ enumerate(result.scans), key=lambda x: (x[1].object.cluster, x[1].object.namespace, x[1].object.name)
+ ):
+ group_items = list(group)
+
+ for j, (i, item) in enumerate(group_items):
+ full_info_row = j == 0
+
+ row = [
+ item.object.namespace if full_info_row else "",
+ item.object.name if full_info_row else "",
+ f"{item.object.current_pods_count}" if full_info_row else "",
+ f"{item.object.deleted_pods_count}" if full_info_row else "",
+ item.object.kind if full_info_row else "",
+ item.object.container,
+ ]
+
+ for resource in ResourceType:
+ row.append(_format_total_diff(item, resource, item.object.current_pods_count))
+ row += [_format_request_str(item, resource, selector) for selector in ["requests", "limits"]]
+
+ csv_writer.writerow(row)
+
+ logger.info("CSV File: %s", file_path)
+ return "" \ No newline at end of file