Skip to content

Commit bc2ef21

Browse files
authored
feat: add new csv-raw export format for calculation (#422)
I added a new export format to avoid breaking existing functionality. Please let me know if there's anything that needs improvement. Close #293 <details><summary>Result example</summary> <p> ```csv Namespace,Name,Pods,Old Pods,Type,Container,Severity,CPU Requests Current,CPU Requests Recommended,CPU Limits Current,CPU Limits Recommended,Memory Requests Current,Memory Requests Recommended,Memory Limits Current,Memory Limits Recommended prometheus,prometheus-stack-grafana,1,0,Deployment,grafana-sc-dashboard,WARNING,unset,0.01,unset,unset,unset,110100480,unset,110100480 prometheus,prometheus-stack-grafana,1,0,Deployment,grafana-sc-datasources,WARNING,unset,0.01,unset,unset,unset,113246208,unset,113246208 prometheus,prometheus-stack-grafana,1,0,Deployment,grafana,WARNING,unset,0.01,unset,unset,unset,106954752,unset,106954752 prometheus,prometheus-stack-kube-prom-operator,1,0,Deployment,kube-prometheus-stack,WARNING,unset,0.01,unset,unset,unset,104857600,unset,104857600 prometheus,prometheus-stack-kube-state-metrics,1,0,Deployment,kube-state-metrics,WARNING,unset,0.01,unset,unset,unset,104857600,unset,104857600 prometheus,alertmanager-prometheus-stack-kube-prom-alertmanager,1,0,StatefulSet,alertmanager,WARNING,unset,0.01,unset,unset,209715200,104857600,unset,104857600 prometheus,alertmanager-prometheus-stack-kube-prom-alertmanager,1,0,StatefulSet,config-reloader,WARNING,unset,0.01,unset,unset,unset,104857600,unset,104857600 prometheus,prometheus-prometheus-stack-kube-prom-prometheus,1,0,StatefulSet,prometheus,WARNING,unset,0.057,unset,unset,unset,834666496,unset,834666496 prometheus,prometheus-prometheus-stack-kube-prom-prometheus,1,0,StatefulSet,config-reloader,WARNING,unset,0.01,unset,unset,unset,104857600,unset,104857600 prometheus,prometheus-stack-prometheus-node-exporter,3,0,DaemonSet,node-exporter,WARNING,unset,0.01,unset,unset,unset,104857600,unset,104857600 ``` </p> </details>
1 parent 0e1cc68 commit bc2ef21

File tree

4 files changed

+113
-1
lines changed

4 files changed

+113
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ Currently KRR ships with a few formatters to represent the scan data:
369369
- `yaml`
370370
- `pprint` - data representation from python's pprint library
371371
- `csv` - export data to a csv file in the current directory
372+
- `csv-raw` - csv with raw data for calculation
372373
- `html`
373374

374375
To run a strategy with a selected formatter, add a `-f` flag. Usually this should be combined with `--fileoutput <filename>` to write clean output to file without logs:

robusta_krr/core/runner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def _process_result(self, result: Result) -> None:
121121

122122
with open(file_name, "w") as target_file:
123123
# don't use rich when writing a csv or html to avoid line wrapping etc
124-
if settings.format == "csv" or settings.format == "html" or settings.format == "json" or settings.format == "yaml":
124+
if settings.format in ("csv", "csv-raw", "html", "json", "yaml"):
125125
target_file.write(formatted)
126126
else:
127127
console = Console(file=target_file, width=settings.width)

robusta_krr/formatters/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
from .table import table
44
from .yaml import yaml
55
from .csv import csv
6+
from .csv_raw import csv_raw
67
from .html import html

robusta_krr/formatters/csv_raw.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import csv
2+
import io
3+
import logging
4+
from typing import Any, Union
5+
6+
from robusta_krr.core.abstract import formatters
7+
from robusta_krr.core.models.allocations import NAN_LITERAL, NONE_LITERAL
8+
from robusta_krr.core.models.config import settings
9+
from robusta_krr.core.models.result import ResourceScan, ResourceType, Result
10+
11+
logger = logging.getLogger("krr")
12+
13+
14+
NAMESPACE_HEADER = "Namespace"
15+
NAME_HEADER = "Name"
16+
PODS_HEADER = "Pods"
17+
OLD_PODS_HEADER = "Old Pods"
18+
TYPE_HEADER = "Type"
19+
CONTAINER_HEADER = "Container"
20+
CLUSTER_HEADER = "Cluster"
21+
SEVERITY_HEADER = "Severity"
22+
23+
RESOURCE_REQUESTS_CURRENT_HEADER = "{resource_name} Requests Current"
24+
RESOURCE_REQUESTS_RECOMMENDED_HEADER = '{resource_name} Requests Recommended'
25+
26+
RESOURCE_LIMITS_CURRENT_HEADER = "{resource_name} Limits Current"
27+
RESOURCE_LIMITS_RECOMMENDED_HEADER = '{resource_name} Limits Recommended'
28+
29+
30+
def _format_value(val: Union[float, int]) -> str:
31+
if isinstance(val, int):
32+
return str(val)
33+
elif isinstance(val, float):
34+
return str(int(val)) if val.is_integer() else str(val)
35+
elif val is None:
36+
return NONE_LITERAL
37+
elif isinstance(val, str):
38+
return NAN_LITERAL
39+
else:
40+
raise ValueError(f'unknown value: {val}')
41+
42+
43+
def _format_request_current(item: ResourceScan, resource: ResourceType, selector: str) -> str:
44+
allocated = getattr(item.object.allocations, selector)[resource]
45+
if allocated is None:
46+
return NONE_LITERAL
47+
return _format_value(allocated)
48+
49+
50+
def _format_request_recommend(item: ResourceScan, resource: ResourceType, selector: str) -> str:
51+
recommended = getattr(item.recommended, selector)[resource]
52+
if recommended is None:
53+
return NONE_LITERAL
54+
return _format_value(recommended.value)
55+
56+
57+
@formatters.register("csv-raw")
58+
def csv_raw(result: Result) -> str:
59+
# We need to order the resource columns so that they are in the format of
60+
# Namespace, Name, Pods, Old Pods, Type, Container,
61+
# CPU Requests Current, CPU Requests Recommend, CPU Limits Current, CPU Limits Recommend,
62+
# Memory Requests Current, Memory Requests Recommend, Memory Limits Current, Memory Limits Recommend,
63+
csv_columns = ["Namespace", "Name", "Pods", "Old Pods", "Type", "Container"]
64+
65+
if settings.show_cluster_name:
66+
csv_columns.insert(0, "Cluster")
67+
68+
if settings.show_severity:
69+
csv_columns.append("Severity")
70+
71+
for resource in ResourceType:
72+
csv_columns.append(RESOURCE_REQUESTS_CURRENT_HEADER.format(resource_name=resource.name))
73+
csv_columns.append(RESOURCE_REQUESTS_RECOMMENDED_HEADER.format(resource_name=resource.name))
74+
csv_columns.append(RESOURCE_LIMITS_CURRENT_HEADER.format(resource_name=resource.name))
75+
csv_columns.append(RESOURCE_LIMITS_RECOMMENDED_HEADER.format(resource_name=resource.name))
76+
77+
output = io.StringIO()
78+
csv_writer = csv.DictWriter(output, csv_columns, extrasaction="ignore")
79+
csv_writer.writeheader()
80+
81+
for item in result.scans:
82+
row: dict[str, Any] = {
83+
NAMESPACE_HEADER: item.object.namespace,
84+
NAME_HEADER: item.object.name,
85+
PODS_HEADER: f"{item.object.current_pods_count}",
86+
OLD_PODS_HEADER: f"{item.object.deleted_pods_count}",
87+
TYPE_HEADER: item.object.kind,
88+
CONTAINER_HEADER: item.object.container,
89+
SEVERITY_HEADER: item.severity,
90+
CLUSTER_HEADER: item.object.cluster,
91+
}
92+
93+
for resource in ResourceType:
94+
resource: ResourceType
95+
row[RESOURCE_REQUESTS_CURRENT_HEADER.format(resource_name=resource.name)] = _format_request_current(
96+
item, resource, "requests"
97+
)
98+
row[RESOURCE_REQUESTS_RECOMMENDED_HEADER.format(resource_name=resource.name)] = _format_request_recommend(
99+
item, resource, "requests"
100+
)
101+
row[RESOURCE_LIMITS_CURRENT_HEADER.format(resource_name=resource.name)] = _format_request_current(
102+
item, resource, "limits"
103+
)
104+
row[RESOURCE_LIMITS_RECOMMENDED_HEADER.format(resource_name=resource.name)] = _format_request_recommend(
105+
item, resource, "limits"
106+
)
107+
108+
csv_writer.writerow(row)
109+
110+
return output.getvalue()

0 commit comments

Comments
 (0)