Skip to content

Commit

Permalink
Merge pull request #59 from naved001/use-decimals
Browse files Browse the repository at this point in the history
Use decimals instead of floats
  • Loading branch information
naved001 authored Apr 29, 2024
2 parents ba268c8 + 1983b17 commit be997dc
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 23 deletions.
70 changes: 63 additions & 7 deletions openshift_metrics/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -854,13 +854,13 @@ def test_write_metrics_log(self, mock_gna):
}

expected_output = ("Namespace,Coldfront_PI Name,Coldfront Project ID ,Pod Start Time,Pod End Time,Duration (Hours),Pod Name,CPU Request,GPU Request,GPU Type,GPU Resource,Node,Node Model,Memory Request (GiB),Determining Resource,SU Type,SU Count\n"
"namespace1,PI1,123,1970-01-01T00:00:00,1970-01-01T00:02:00,0.0333,pod1,10,0,,,wrk-1,Dell,0.001,CPU,OpenShift CPU,10.0\n"
"namespace1,PI1,123,1970-01-01T00:02:00,1970-01-01T00:03:00,0.0167,pod1,20,0,,,wrk-2,Lenovo,0.001,CPU,OpenShift CPU,20.0\n"
"namespace1,PI1,123,1970-01-01T00:00:00,1970-01-01T00:01:00,0.0167,pod2,20,0,,,Unknown Node,Unknown Model,0.0098,CPU,OpenShift CPU,20.0\n"
"namespace1,PI1,123,1970-01-01T00:01:00,1970-01-01T00:02:00,0.0167,pod2,25,0,,,Unknown Node,Unknown Model,0.0098,CPU,OpenShift CPU,25.0\n"
"namespace1,PI1,123,1970-01-01T00:02:00,1970-01-01T00:03:00,0.0167,pod2,20,0,,,Unknown Node,Unknown Model,0.0098,CPU,OpenShift CPU,20.0\n"
"namespace2,PI2,456,1970-01-01T00:00:00,1970-01-01T00:03:00,0.05,pod3,45,0,,,Unknown Node,Unknown Model,0.0977,CPU,OpenShift CPU,45.0\n"
"namespace2,PI2,456,1970-01-01T00:00:00,1970-01-01T01:00:00,1.0,pod4,0.5,0,,,Unknown Node,Unknown Model,2.0,CPU,OpenShift CPU,0.5\n")
"namespace1,PI1,123,1970-01-01T00:00:00,1970-01-01T00:02:00,0.0333,pod1,10,0,,,wrk-1,Dell,0.0010,CPU,OpenShift CPU,10\n"
"namespace1,PI1,123,1970-01-01T00:02:00,1970-01-01T00:03:00,0.0167,pod1,20,0,,,wrk-2,Lenovo,0.0010,CPU,OpenShift CPU,20\n"
"namespace1,PI1,123,1970-01-01T00:00:00,1970-01-01T00:01:00,0.0167,pod2,20,0,,,Unknown Node,Unknown Model,0.0098,CPU,OpenShift CPU,20\n"
"namespace1,PI1,123,1970-01-01T00:01:00,1970-01-01T00:02:00,0.0167,pod2,25,0,,,Unknown Node,Unknown Model,0.0098,CPU,OpenShift CPU,25\n"
"namespace1,PI1,123,1970-01-01T00:02:00,1970-01-01T00:03:00,0.0167,pod2,20,0,,,Unknown Node,Unknown Model,0.0098,CPU,OpenShift CPU,20\n"
"namespace2,PI2,456,1970-01-01T00:00:00,1970-01-01T00:03:00,0.0500,pod3,45,0,,,Unknown Node,Unknown Model,0.0977,CPU,OpenShift CPU,45\n"
"namespace2,PI2,456,1970-01-01T00:00:00,1970-01-01T01:00:00,1.0000,pod4,0.5,0,,,Unknown Node,Unknown Model,2.0000,CPU,OpenShift CPU,0.5\n")

with tempfile.NamedTemporaryFile(mode="w+") as tmp:
utils.write_metrics_by_pod(test_metrics_dict, tmp.name)
Expand Down Expand Up @@ -963,6 +963,48 @@ def test_write_metrics_log(self, mock_gna):
self.assertEqual(tmp.read(), expected_output)


@mock.patch('openshift_metrics.utils.get_namespace_attributes')
def test_write_metrics_by_namespace_decimal(self, mock_gna):
"""This tests the inaccurate result we get when using floating
point instead of decimals.
If floating points are used then the cost is 0.45499999999999996
which is then rounded down to 0.45.
"""
mock_gna.return_value = {
'namespace1': {
'cf_pi': 'PI1',
'cf_project_id': '123',
'institution_code': '76'
},
}

duration = 35 #hours
rate = 0.013

test_metrics_dict = {
"pod1": {
"namespace": "namespace1",
"metrics": {
0: {
"cpu_request": 1,
"memory_request": 4 * 2**30,
"duration": 35*3600
},
}
}}

cost = round(duration*rate,2)
self.assertEqual(cost, 0.45)

expected_output = ("Invoice Month,Project - Allocation,Project - Allocation ID,Manager (PI),Invoice Email,Invoice Address,Institution,Institution - Specific Code,SU Hours (GBhr or SUhr),SU Type,Rate,Cost\n"
"2023-01,namespace1,namespace1,PI1,,,,76,35,OpenShift CPU,0.013,0.46\n")

with tempfile.NamedTemporaryFile(mode="w+") as tmp:
utils.write_metrics_by_namespace(test_metrics_dict, tmp.name, "2023-01")
self.assertEqual(tmp.read(), expected_output)


class TestGetServiceUnit(TestCase):

def test_cpu_only(self):
Expand Down Expand Up @@ -1066,3 +1108,17 @@ def test_known_gpu_fractional_cpu_memory(self):
self.assertEqual(su_type, utils.SU_A100_GPU)
self.assertEqual(su_count, 1)
self.assertEqual(determining_resource, "GPU")

def test_decimal_return_type(self):
from decimal import Decimal
_, su_count, _ = utils.get_service_unit(Decimal("1"), Decimal("8.1"), Decimal("0"), None, None)
self.assertIsInstance(su_count, Decimal)
self.assertEqual(su_count, Decimal('2.025'))

def test_not_decimal_return_type_when_gpu_su_type(self):
from decimal import Decimal
su_type, su_count, _ = utils.get_service_unit(Decimal("1"), Decimal("76"), Decimal("1"), utils.GPU_A100, utils.WHOLE_GPU)
# for GPU SUs, we always round up to the nearest integer
self.assertIsInstance(su_count, int)
self.assertEqual(su_count, 2)
self.assertEqual(su_type, utils.SU_A100_GPU)
36 changes: 20 additions & 16 deletions openshift_metrics/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import requests
import boto3

from decimal import Decimal
import decimal
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter

Expand Down Expand Up @@ -48,11 +50,11 @@
SU_UNKNOWN = "Openshift Unknown"

RATE = {
SU_CPU: 0.013,
SU_A100_GPU: 1.803,
SU_A100_SXM4_GPU: 2.078,
SU_V100_GPU: 1.214,
SU_UNKNOWN_GPU: 0,
SU_CPU: Decimal("0.013"),
SU_A100_GPU: Decimal("1.803"),
SU_A100_SXM4_GPU: Decimal("2.078"),
SU_V100_GPU: Decimal("1.214"),
SU_UNKNOWN_GPU: Decimal("0"),
}

STEP_MIN = 15
Expand Down Expand Up @@ -362,6 +364,8 @@ def csv_writer(rows, file_name):

def add_row(rows, report_month, namespace, pi, institution_code, hours, su_type):

hours = math.ceil(hours)
cost = (RATE.get(su_type) * hours).quantize(Decimal('.01'), rounding=decimal.ROUND_HALF_UP)
row = [
report_month,
namespace,
Expand All @@ -371,10 +375,10 @@ def add_row(rows, report_month, namespace, pi, institution_code, hours, su_type)
"", #Invoice Address
"", #Institution
institution_code,
str(math.ceil(hours)),
hours,
su_type,
RATE.get(su_type),
str(round(RATE.get(su_type) * math.ceil(hours), 2))
cost,
]
rows.append(row)

Expand Down Expand Up @@ -425,12 +429,12 @@ def write_metrics_by_namespace(condensed_metrics_dict, file_name, report_month):
}

for epoch_time, pod_metric_dict in pod_metrics_dict.items():
duration_in_hours = float(pod_metric_dict["duration"]) / 3600
cpu_request = float(pod_metric_dict.get("cpu_request", 0))
gpu_request = float(pod_metric_dict.get("gpu_request", 0))
duration_in_hours = Decimal(pod_metric_dict["duration"]) / 3600
cpu_request = Decimal(pod_metric_dict.get("cpu_request", 0))
gpu_request = Decimal(pod_metric_dict.get("gpu_request", 0))
gpu_type = pod_metric_dict.get("gpu_type")
gpu_resource = pod_metric_dict.get("gpu_resource")
memory_request = float(pod_metric_dict.get("memory_request", 0)) / 2**30
memory_request = Decimal(pod_metric_dict.get("memory_request", 0)) / 2**30

_, su_count, _ = get_service_unit(cpu_request, memory_request, gpu_request, gpu_type, gpu_resource)

Expand Down Expand Up @@ -517,16 +521,16 @@ def write_metrics_by_pod(metrics_dict, file_name):
end_time = datetime.datetime.utcfromtimestamp(
float(epoch_time + pod_metric_dict["duration"])
).strftime("%Y-%m-%dT%H:%M:%S")
duration = round(float(pod_metric_dict["duration"]) / 3600, 4)
cpu_request = pod_metric_dict.get("cpu_request", 0)
gpu_request = pod_metric_dict.get("gpu_request", 0)
duration = (Decimal(pod_metric_dict["duration"]) / 3600).quantize(Decimal(".0001"), rounding=decimal.ROUND_HALF_UP)
cpu_request = Decimal(pod_metric_dict.get("cpu_request", 0))
gpu_request = Decimal(pod_metric_dict.get("gpu_request", 0))
gpu_type = pod_metric_dict.get("gpu_type")
gpu_resource = pod_metric_dict.get("gpu_resource")
node = pod_metric_dict.get("node", "Unknown Node")
node_model = pod_metric_dict.get("node_model", "Unknown Model")
memory_request = round(float(pod_metric_dict.get("memory_request", 0)) / 2**30, 4)
memory_request = (Decimal(pod_metric_dict.get("memory_request", 0)) / 2**30).quantize(Decimal(".0001"), rounding=decimal.ROUND_HALF_UP)
su_type, su_count, determining_resource = get_service_unit(
float(cpu_request), memory_request, float(gpu_request), gpu_type, gpu_resource
cpu_request, memory_request, gpu_request, gpu_type, gpu_resource
)

info_list = [
Expand Down

0 comments on commit be997dc

Please sign in to comment.