Skip to content

Commit c2f875f

Browse files
Copilot4n4nd
andcommitted
Replace numpy functions with NaN-safe variants in metric aggregations
- Replace numpy.sum with numpy.nansum - Replace numpy.max with numpy.nanmax - Replace numpy.min with numpy.nanmin - Replace numpy.average with numpy.nanmean - Replace numpy.percentile with numpy.nanpercentile - Replace numpy.std with numpy.nanstd - Replace numpy.var with numpy.nanvar - Add comprehensive test for NaN handling Co-authored-by: 4n4nd <22333506+4n4nd@users.noreply.github.com>
1 parent 2c1e925 commit c2f875f

2 files changed

Lines changed: 68 additions & 7 deletions

File tree

prometheus_api_client/prometheus_connect.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -597,22 +597,22 @@ def get_metric_aggregation(
597597
np_array = numpy.array(query_values)
598598
for operation in operations:
599599
if operation == "sum":
600-
aggregated_values["sum"] = numpy.sum(np_array)
600+
aggregated_values["sum"] = numpy.nansum(np_array)
601601
elif operation == "max":
602-
aggregated_values["max"] = numpy.max(np_array)
602+
aggregated_values["max"] = numpy.nanmax(np_array)
603603
elif operation == "min":
604-
aggregated_values["min"] = numpy.min(np_array)
604+
aggregated_values["min"] = numpy.nanmin(np_array)
605605
elif operation == "average":
606-
aggregated_values["average"] = numpy.average(np_array)
606+
aggregated_values["average"] = numpy.nanmean(np_array)
607607
elif operation.startswith("percentile"):
608608
percentile = float(operation.split("_")[1])
609-
aggregated_values["percentile_" + str(percentile)] = numpy.percentile(
609+
aggregated_values["percentile_" + str(percentile)] = numpy.nanpercentile(
610610
query_values, percentile
611611
)
612612
elif operation == "deviation":
613-
aggregated_values["deviation"] = numpy.std(np_array)
613+
aggregated_values["deviation"] = numpy.nanstd(np_array)
614614
elif operation == "variance":
615-
aggregated_values["variance"] = numpy.var(np_array)
615+
aggregated_values["variance"] = numpy.nanvar(np_array)
616616
else:
617617
raise TypeError("Invalid operation: " + operation)
618618
return aggregated_values

tests/test_prometheus_connect.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,5 +395,66 @@ def test_get_label_values_method(self): # noqa D102
395395
request = handler.requests[0]
396396
self.assertEqual(request.path_url, "/api/v1/label/label_name/values")
397397

398+
def test_get_metric_aggregation_with_nan_values(self): # noqa D102
399+
"""Test that aggregation functions handle NaN values correctly."""
400+
# Mock response with NaN values in the data
401+
query_response_with_nan = {
402+
"status": "success",
403+
"data": {
404+
"result": [
405+
{
406+
"metric": {"__name__": "test_metric"},
407+
"value": [1638360000, "1.0"]
408+
},
409+
{
410+
"metric": {"__name__": "test_metric"},
411+
"value": [1638360015, "NaN"]
412+
},
413+
{
414+
"metric": {"__name__": "test_metric"},
415+
"value": [1638360030, "2.0"]
416+
},
417+
{
418+
"metric": {"__name__": "test_metric"},
419+
"value": [1638360045, "3.0"]
420+
}
421+
]
422+
}
423+
}
424+
425+
operations = ["sum", "max", "min", "variance", "percentile_50", "deviation", "average"]
426+
427+
with self.mock_response(query_response_with_nan) as handler:
428+
aggregated_values = self.pc.get_metric_aggregation(
429+
query="test_metric", operations=operations
430+
)
431+
432+
# With NaN-handling functions, we should get valid results
433+
# sum should be 6.0 (1 + 2 + 3, ignoring NaN)
434+
self.assertIsNotNone(aggregated_values)
435+
self.assertIn("sum", aggregated_values)
436+
self.assertIn("max", aggregated_values)
437+
self.assertIn("min", aggregated_values)
438+
self.assertIn("average", aggregated_values)
439+
self.assertIn("variance", aggregated_values)
440+
self.assertIn("deviation", aggregated_values)
441+
self.assertIn("percentile_50.0", aggregated_values)
442+
443+
# Verify that results are not NaN
444+
import math
445+
self.assertFalse(math.isnan(aggregated_values["sum"]), "Sum should not be NaN")
446+
self.assertFalse(math.isnan(aggregated_values["max"]), "Max should not be NaN")
447+
self.assertFalse(math.isnan(aggregated_values["min"]), "Min should not be NaN")
448+
self.assertFalse(math.isnan(aggregated_values["average"]), "Average should not be NaN")
449+
self.assertFalse(math.isnan(aggregated_values["variance"]), "Variance should not be NaN")
450+
self.assertFalse(math.isnan(aggregated_values["deviation"]), "Deviation should not be NaN")
451+
self.assertFalse(math.isnan(aggregated_values["percentile_50.0"]), "Percentile should not be NaN")
452+
453+
# Verify expected values (approximately)
454+
self.assertAlmostEqual(aggregated_values["sum"], 6.0, places=5)
455+
self.assertAlmostEqual(aggregated_values["max"], 3.0, places=5)
456+
self.assertAlmostEqual(aggregated_values["min"], 1.0, places=5)
457+
self.assertAlmostEqual(aggregated_values["average"], 2.0, places=5)
458+
398459
if __name__ == "__main__":
399460
unittest.main()

0 commit comments

Comments
 (0)