Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use decimals instead of floats #59

Merged
merged 2 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading