Skip to content

Commit

Permalink
Enhanced the allocation of further amounts (#20)
Browse files Browse the repository at this point in the history
* Fixed log

* Enhanced the allocation of further amounts and added unit test
  • Loading branch information
marc-perreaut authored Sep 27, 2023
1 parent f8860ea commit c9bd8a2
Show file tree
Hide file tree
Showing 12 changed files with 207 additions and 30 deletions.
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,11 @@ In theory, an even more general type of cost allocation could be considered:

### Allocation of further amounts [experimental]

Further amounts (on top of cloud costs) like emitted CO2 and electricity power.
After cost allocation, custom amounts, which are called *further amounts*, can be allocated.
Examples of *further amounts* are CO2 and electricity power.
The *further amounts* to allocate must be first loaded into the cost items, before the allocation is run for them.
The *further amounts* can be loaded into cloud cost items, or even into consumer cost items: in both cases, the further amounts are allocated along the cost allocation graph.
Custom allocation keys can be used to allocate *further amounts*.

### Configuration

Expand Down Expand Up @@ -233,13 +237,13 @@ MaxBreaks = 10
[FurtherAmounts]
# Further amounts to allocate
Amounts = Scope1Co2,Scope2Co2,Scope3Co2,ElectricityPower
Amounts = Co2,ElectricityPower
# Allocation keys for further amounts
AllocationKeys = ElectricityPowerAllocationKey
# Allocation keys used for further amounts
AllocationKeys = Co2Key
# The allocation keys to use for further amounts
AmountAllocationKeys = Scope1Co2:ProviderCostAllocationKey,Scope2Co2:ProviderCostAllocationKey,Scope3Co2:ProviderCostAllocationKey,ElectricityPower:ElectricityPowerAllocationKey
AmountAllocationKeys = Co2:Co2Key,ElectricityPower:ProviderCostAllocationKey
```

## Test
Expand Down
12 changes: 8 additions & 4 deletions cloud_cost_allocation/cloud_cost_allocator.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def allocate(self, consumer_cost_items: list[ConsumerCostItem], cloud_cost_items
if not config.build_amount_to_allocation_key_indexes(amount_to_allocation_key_indexes, amounts):
return False
info("Allocating costs, ignoring keys that are costs, for date " + self.date_str)
self.visit_for_allocation(True, amount_to_allocation_key_indexes)
self.visit_for_allocation(True, False, amount_to_allocation_key_indexes)
for service_instance in self.service_instances.values():
service_instance_amortized_cost = 0.0
for cost_item in service_instance.cost_items:
Expand All @@ -131,7 +131,7 @@ def allocate(self, consumer_cost_items: list[ConsumerCostItem], cloud_cost_items

# Allocate amortized costs for services
info("Allocating costs, for date " + self.date_str)
self.visit_for_allocation(False, amount_to_allocation_key_indexes)
self.visit_for_allocation(False, False, amount_to_allocation_key_indexes)

except CycleException:
return False
Expand All @@ -153,7 +153,7 @@ def allocate_further_amounts(self, cost_items: list[CostItem], amounts: list[str

# Visit, by protecting against unexpected cycles
try:
self.visit_for_allocation(False, amount_to_allocation_key_indexes)
self.visit_for_allocation(False, True, amount_to_allocation_key_indexes)
except CycleException:
return False

Expand Down Expand Up @@ -450,11 +450,15 @@ def process_cloud_tag_selectors(self,
cloud_tag_dict.clear()
new_consumer_cost_items.clear()

def visit_for_allocation(self, ignore_cost_as_key: bool, amount_allocation_to_key_indexes: dict[int]) -> None:
def visit_for_allocation(self,
ignore_cost_as_key: bool,
increment_amounts: bool,
amount_allocation_to_key_indexes: dict[int]) -> None:
for service_instance in self.service_instances.values():
service_instance.reset_visit()
for service_instance in self.service_instances.values():
visited_service_instance_list = []
service_instance.visit_for_allocation(visited_service_instance_list,
ignore_cost_as_key,
increment_amounts,
amount_allocation_to_key_indexes)
24 changes: 19 additions & 5 deletions cloud_cost_allocation/cost_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def visit_for_allocation(self,
visited_service_instance_list: list['ServiceInstance'],
ignore_cost_as_key: bool,
process_self_consumption: bool,
increment_amounts: bool,
amount_to_allocation_key_indexes: dict[int]) -> None:
pass

Expand All @@ -117,6 +118,7 @@ def visit_for_allocation(self,
visited_service_instance_list: list['ServiceInstance'],
ignore_cost_as_key: bool,
process_self_consumption: bool,
increment_amounts: bool,
amount_to_allocation_key_indexes: dict[int]) -> None:
# Nothing to do
return
Expand Down Expand Up @@ -223,6 +225,7 @@ def visit_for_allocation(self,
visited_service_instance_list: list['ServiceInstance'],
ignore_cost_as_key: bool,
process_self_consumption: bool,
increment_amounts: bool,
amount_to_allocation_key_indexes: dict[int]) -> None:

# Check self consumption
Expand All @@ -232,6 +235,7 @@ def visit_for_allocation(self,
if not self.is_self_consumption():
self.provider_service_instance.visit_for_allocation(visited_service_instance_list,
ignore_cost_as_key,
increment_amounts,
amount_to_allocation_key_indexes)

# Ignore cost as keys
Expand Down Expand Up @@ -264,12 +268,19 @@ def visit_for_allocation(self,
amount = provider_amount / nb_keys
product_amount = provider_product_amount / nb_keys

# Update amounts
self.amounts[amount_index] = amount
if self.product:
self.product_amounts[amount_index] = product_amount
# Set or increment amounts
if increment_amounts:
self.amounts[amount_index] += amount
if self.product:
self.product_amounts[amount_index] += product_amount
else:
self.unallocated_product_amounts[amount_index] += product_amount
else:
self.unallocated_product_amounts[amount_index] = product_amount
self.amounts[amount_index] = amount
if self.product:
self.product_amounts[amount_index] = product_amount
else:
self.unallocated_product_amounts[amount_index] = product_amount

# Next amount
index += 1
Expand Down Expand Up @@ -767,6 +778,7 @@ def reset_visit(self) -> None:
def visit_for_allocation(self,
visited_service_instance_list: list['ServiceInstance'],
ignore_cost_as_key: bool,
increment_amounts: bool,
amount_to_allocation_key_indexes: dict[int]) -> None:

# Already visited?
Expand All @@ -788,6 +800,7 @@ def visit_for_allocation(self,
cost_item.visit_for_allocation(visited_service_instance_list,
ignore_cost_as_key,
False,
increment_amounts,
amount_to_allocation_key_indexes)

# Compute provider tag selector amounts
Expand All @@ -798,6 +811,7 @@ def visit_for_allocation(self,
item.visit_for_allocation(visited_service_instance_list,
ignore_cost_as_key,
True,
increment_amounts,
amount_to_allocation_key_indexes)

# Set visited
Expand Down
2 changes: 1 addition & 1 deletion cloud_cost_allocation/reader/csv_allocated_cost_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def initialize_cost_item(self, cost_item: CostItem, line):
value = key_value_match.group(2).strip().lower()
cost_item.tags[key] = value
else:
error("Unexpected consumer tag format: '" + tag + "'")
error("Unexpected tag format: '" + tag + "'")

# Amounts
index = 0
Expand Down
15 changes: 12 additions & 3 deletions cloud_cost_allocation/reader/csv_cost_allocation_keys_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ def read_item(self, line) -> ConsumerCostItem:
"' for ProviderService '" + consumer_cost_item.provider_service + "'")
return None
if consumer_cost_item.provider_cost_allocation_type == 'Key':
key_str = ""
if 'ProviderCostAllocationKey' in line and line['ProviderCostAllocationKey']:
if 'ProviderCostAllocationKey' in line:
key_str = line['ProviderCostAllocationKey']
if utils.is_float(key_str):
consumer_cost_item.allocation_keys[0] = float(key_str)
Expand All @@ -67,8 +66,18 @@ def read_item(self, line) -> ConsumerCostItem:
consumer_cost_item.provider_cost_allocation_cloud_tag_selector = \
line['ProviderCostAllocationCloudTagSelector']

# Provider meters
# Further allocation keys
config = self.cost_item_factory.config
if config.nb_allocation_keys > 1:
key_index = 1
for further_allocation_key in config.allocation_keys[1:]:
if further_allocation_key in line and line[further_allocation_key]:
key_str = line[further_allocation_key]
if utils.is_float(key_str):
consumer_cost_item.allocation_keys[key_index] = float(key_str)
key_index += 1

# Provider meters
if config.nb_provider_meters:
for i in range(1, config.nb_provider_meters + 1):
provider_meter_name = None
Expand Down
2 changes: 1 addition & 1 deletion cloud_cost_allocation/writer/csv_allocated_cost_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def get_headers(self) -> list[str]:
headers.extend(['Product' + amount])

# Add further allocation keys
if self.config.nb_allocation_keys > 2:
if self.config.nb_allocation_keys > 1:
headers.extend(self.config.allocation_keys[1:])

return headers
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# Setup
setup(
name='cloud-cost-allocation',
version='1.0.6',
version='1.0.7',
description='Python library for shared, hierarchical cost allocation based on user-defined usage metrics.',
long_description=readme,
long_description_content_type='text/markdown',
Expand Down
Loading

0 comments on commit c9bd8a2

Please sign in to comment.