Skip to content

Commit 0ecfc86

Browse files
committed
Include function_size in license fee event metric types
1 parent 74e059e commit 0ecfc86

2 files changed

Lines changed: 97 additions & 2 deletions

File tree

gateway/core/ibm_cloud/event_streams/kafka_event_streams_client.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,14 @@ def _emit_job_completed(self, job, metric_type: str | None = None) -> None:
162162
)
163163

164164
def _emit_license_fee(self, job: Job) -> None:
165-
metric_type = "_".join([LICENSE_FEE_METRIC_TYPE, job.program.provider.name, job.program.title])
165+
parts = [LICENSE_FEE_METRIC_TYPE, job.program.provider.name, job.program.title]
166+
167+
# Include function size if available
168+
function_size = self._resolve_function_size(job)
169+
if function_size:
170+
parts.append(function_size)
171+
172+
metric_type = "_".join(parts)
166173
self._publish(
167174
job,
168175
metric_type=metric_type,
@@ -172,6 +179,20 @@ def _emit_license_fee(self, job: Job) -> None:
172179
business_model=billing_name_for(job.business_model),
173180
)
174181

182+
def _resolve_function_size(self, job: Job) -> str | None:
183+
"""Resolve the function size for the job.
184+
185+
Returns the job's explicit size if set, otherwise falls back to the function's
186+
default size. Returns None if neither is set.
187+
"""
188+
if job.function_size:
189+
return job.function_size.function_size
190+
191+
if job.program and job.program.default_size:
192+
return job.program.default_size.function_size
193+
194+
return None
195+
175196
def _build_classical_metric_type(self, job: Job) -> str:
176197
"""Build classical metric type from job attributes: classical_PROVIDER_FUNCTION_COMPUTE_PROFILE."""
177198
parts = [CLASSICAL_TIME_METRIC_TYPE_PREFIX]

gateway/tests/core/services/ibm_cloud/event_streams/test_event_streams_client.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,11 +297,13 @@ def test_emit_job_in_progress_returns_zero_usage_when_running_started_at_is_none
297297
assert published["data"]["metric_value"] == 0
298298
assert published["data"]["job_started_at"] is None
299299

300-
def test_emit_license_fee_publishes_correct_payload(self):
300+
def test_emit_license_fee_publishes_correct_payload_without_size(self):
301301
job = _make_job()
302302
job.program = MagicMock()
303303
job.program.provider.name = "ibm"
304304
job.program.title = "test-program"
305+
job.function_size = None
306+
job.program.default_size = None
305307

306308
with patch(f"{_CLIENT_MOD}.Producer") as mock_producer_cls:
307309
with patch(f"{_CLIENT_MOD}.uuid") as mock_uuid_mod:
@@ -343,6 +345,76 @@ def test_emit_license_fee_publishes_correct_payload(self):
343345
assert call_kwargs["key"] == str(job.id).encode("utf-8")
344346
mock_producer.flush.assert_called_once()
345347

348+
def test_emit_license_fee_includes_size_from_job_function_size(self):
349+
job = _make_job()
350+
job.program = MagicMock()
351+
job.program.provider.name = "ibm"
352+
job.program.title = "test-program"
353+
job.function_size = MagicMock()
354+
job.function_size.function_size = "S"
355+
356+
with patch(f"{_CLIENT_MOD}.Producer") as mock_producer_cls:
357+
with patch(f"{_CLIENT_MOD}.uuid") as mock_uuid_mod:
358+
with patch(f"{_CLIENT_MOD}.datetime") as mock_dt:
359+
with patch.dict(
360+
os.environ,
361+
{
362+
"EVENT_STREAMS_BOOTSTRAP_SERVERS": "b:9093",
363+
"EVENT_STREAMS_API_KEY": "k",
364+
"ENVIRONMENT": "production",
365+
},
366+
):
367+
fake_event_id = uuid_module.UUID("00000000-0000-0000-0000-000000000003")
368+
mock_uuid_mod.uuid4.return_value = fake_event_id
369+
fake_now = datetime(2026, 1, 1, 12, 0, 1, tzinfo=timezone.utc)
370+
mock_dt.now.return_value = fake_now
371+
372+
client = KafkaEventStreamsClient()
373+
mock_producer = mock_producer_cls.return_value
374+
mock_producer.flush.return_value = 0
375+
client.emit_license_fee(job)
376+
377+
call_kwargs = mock_producer.produce.call_args[1]
378+
published = json.loads(call_kwargs["value"])
379+
assert published["data"]["metric_type"] == "license_ibm_test-program_S"
380+
assert published["data"]["business_model"] == "licensed"
381+
382+
def test_emit_license_fee_falls_back_to_program_default_size(self):
383+
job = _make_job()
384+
job.program = MagicMock()
385+
job.program.provider.name = "ibm"
386+
job.program.title = "test-program"
387+
job.function_size = None
388+
default_size = MagicMock()
389+
default_size.function_size = "M"
390+
job.program.default_size = default_size
391+
392+
with patch(f"{_CLIENT_MOD}.Producer") as mock_producer_cls:
393+
with patch(f"{_CLIENT_MOD}.uuid") as mock_uuid_mod:
394+
with patch(f"{_CLIENT_MOD}.datetime") as mock_dt:
395+
with patch.dict(
396+
os.environ,
397+
{
398+
"EVENT_STREAMS_BOOTSTRAP_SERVERS": "b:9093",
399+
"EVENT_STREAMS_API_KEY": "k",
400+
"ENVIRONMENT": "production",
401+
},
402+
):
403+
fake_event_id = uuid_module.UUID("00000000-0000-0000-0000-000000000004")
404+
mock_uuid_mod.uuid4.return_value = fake_event_id
405+
fake_now = datetime(2026, 1, 1, 12, 0, 1, tzinfo=timezone.utc)
406+
mock_dt.now.return_value = fake_now
407+
408+
client = KafkaEventStreamsClient()
409+
mock_producer = mock_producer_cls.return_value
410+
mock_producer.flush.return_value = 0
411+
client.emit_license_fee(job)
412+
413+
call_kwargs = mock_producer.produce.call_args[1]
414+
published = json.loads(call_kwargs["value"])
415+
assert published["data"]["metric_type"] == "license_ibm_test-program_M"
416+
assert published["data"]["business_model"] == "licensed"
417+
346418
@pytest.mark.parametrize(
347419
"business_model,expected",
348420
[
@@ -356,6 +428,8 @@ def test_emit_license_fee_maps_business_model(self, business_model, expected):
356428
job.program = MagicMock()
357429
job.program.provider.name = "ibm"
358430
job.program.title = "test-program"
431+
job.function_size = None
432+
job.program.default_size = None
359433

360434
with patch(f"{_CLIENT_MOD}.Producer") as mock_producer_cls:
361435
with patch(f"{_CLIENT_MOD}.uuid"):

0 commit comments

Comments
 (0)