|
| 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