|
23 | 23 | materialization_result_total, |
24 | 24 | online_features_entity_count, |
25 | 25 | online_features_request_count, |
| 26 | + online_features_status_total, |
26 | 27 | online_store_read_duration_seconds, |
27 | 28 | push_request_count, |
28 | 29 | request_count, |
29 | 30 | request_latency, |
| 31 | + track_feature_statuses, |
30 | 32 | track_materialization, |
31 | 33 | track_online_features_entities, |
32 | 34 | track_online_store_read, |
@@ -435,6 +437,71 @@ def test_records_entity_count(self): |
435 | 437 | assert online_features_entity_count._sum.get() >= before_count + 42 |
436 | 438 |
|
437 | 439 |
|
| 440 | +class TestTrackFeatureStatuses: |
| 441 | + def test_increments_present_and_not_found(self): |
| 442 | + before_present = online_features_status_total.labels( |
| 443 | + feature_view="fv_status", status="present" |
| 444 | + )._value.get() |
| 445 | + before_not_found = online_features_status_total.labels( |
| 446 | + feature_view="fv_status", status="not_found" |
| 447 | + )._value.get() |
| 448 | + |
| 449 | + track_feature_statuses("fv_status", present_count=3, not_found_count=2) |
| 450 | + |
| 451 | + assert ( |
| 452 | + online_features_status_total.labels( |
| 453 | + feature_view="fv_status", status="present" |
| 454 | + )._value.get() |
| 455 | + == before_present + 3 |
| 456 | + ) |
| 457 | + assert ( |
| 458 | + online_features_status_total.labels( |
| 459 | + feature_view="fv_status", status="not_found" |
| 460 | + )._value.get() |
| 461 | + == before_not_found + 2 |
| 462 | + ) |
| 463 | + |
| 464 | + @patch("feast.metrics.track_feature_statuses") |
| 465 | + def test_populate_response_passes_correct_counts(self, mock_track): |
| 466 | + """Integration: _populate_response_from_feature_data computes and |
| 467 | + forwards the right present/not_found counts. |
| 468 | +
|
| 469 | + 3 entities, 2 features each: |
| 470 | + Entity 0 — both present (2 PRESENT) |
| 471 | + Entity 1 — missing (2 NOT_FOUND) |
| 472 | + Entity 2 — partial (1 PRESENT, 1 NOT_FOUND) |
| 473 | + """ |
| 474 | + from feast.protos.feast.serving.ServingService_pb2 import ( |
| 475 | + GetOnlineFeaturesResponse, |
| 476 | + ) |
| 477 | + from feast.protos.feast.types.Value_pb2 import Value as ValueProto |
| 478 | + from feast.utils import _populate_response_from_feature_data |
| 479 | + |
| 480 | + now = datetime.now(tz=timezone.utc) |
| 481 | + val = ValueProto(int64_val=1) |
| 482 | + table = MagicMock() |
| 483 | + table.name = "driver_fv" |
| 484 | + table.projection.name_to_use.return_value = "driver_fv" |
| 485 | + table.projection.name_alias = None |
| 486 | + table.projection.name = "driver_fv" |
| 487 | + |
| 488 | + _populate_response_from_feature_data( |
| 489 | + requested_features=["feat_a", "feat_b"], |
| 490 | + read_rows=[ |
| 491 | + (now, {"feat_a": val, "feat_b": val}), |
| 492 | + (None, None), |
| 493 | + (now, {"feat_a": val}), |
| 494 | + ], |
| 495 | + indexes=([0], [1], [2]), |
| 496 | + online_features_response=GetOnlineFeaturesResponse(), |
| 497 | + full_feature_names=False, |
| 498 | + table=table, |
| 499 | + output_len=3, |
| 500 | + ) |
| 501 | + |
| 502 | + mock_track.assert_called_once_with("driver_fv", 3, 3) |
| 503 | + |
| 504 | + |
438 | 505 | class TestTrackPush: |
439 | 506 | def test_increments_push_counter(self): |
440 | 507 | before = push_request_count.labels( |
|
0 commit comments