D6927: ci: report cost to run each job
indygreg (Gregory Szorc)
phabricator at mercurial-scm.org
Mon Sep 30 23:58:23 EDT 2019
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.
REVISION SUMMARY
The spot instance request contains details on the cost to run
the instance. Let's record the hourly cost to run an instance
in DynamoDB so we can use it to calculate the total compute cost
to run a single job.
And now that we store it, expose it in the web interface so there
is visibility.
REPOSITORY
rHG Mercurial
REVISION DETAIL
https://phab.mercurial-scm.org/D6927
AFFECTED FILES
contrib/ci/lambda_functions/ci.py
contrib/ci/lambda_functions/web.py
contrib/ci/terraform/job_executor.tf
CHANGE DETAILS
diff --git a/contrib/ci/terraform/job_executor.tf b/contrib/ci/terraform/job_executor.tf
--- a/contrib/ci/terraform/job_executor.tf
+++ b/contrib/ci/terraform/job_executor.tf
@@ -142,6 +142,7 @@
"ec2:CreateTags",
"ec2:DescribeInstanceAttribute",
"ec2:DescribeInstances",
+ "ec2:DescribeSpotInstanceRequests",
]
resources = ["*"]
}
diff --git a/contrib/ci/lambda_functions/web.py b/contrib/ci/lambda_functions/web.py
--- a/contrib/ci/lambda_functions/web.py
+++ b/contrib/ci/lambda_functions/web.py
@@ -115,6 +115,7 @@
'<th>Scheduled At</th>',
'<th>Start Delay</th>',
'<th>Execution Time</th>',
+ '<th>Cost</th>',
'<th>Total Tests</th>',
'<th>Passed</th>',
'<th>Failed</th>',
@@ -136,14 +137,28 @@
start_time = datetime.datetime.utcfromtimestamp(job_info['start_time'])
start_delay = '%ds' % (start_time - schedule_time).total_seconds()
else:
+ start_time = None
start_delay = 'n/a'
if 'end_time' in job_info:
end_time = datetime.datetime.utcfromtimestamp(job_info['end_time'])
execution_time = '%ds' % (end_time - start_time).total_seconds()
+
+ instance_time = (end_time - start_time).total_seconds()
else:
execution_time = 'n/a'
+ if start_time is not None:
+ instance_time = (datetime.datetime.utcnow() - start_time).total_seconds()
+ else:
+ instance_time = None
+
+ if 'instance_hourly_cost' in job_info and instance_time is not None:
+ total_cost = float(job_info['instance_hourly_cost'] )/ 3600.0 * instance_time
+ total_cost = '$%.3f' % total_cost
+ else:
+ total_cost = 'n/a'
+
if 'test_count' in job_info:
test_count = '%d' % job_info['test_count']
else:
@@ -207,6 +222,7 @@
'<td>%s</td>' % schedule_time.isoformat(),
'<td>%s</td>' % start_delay,
'<td>%s</td>' % execution_entry,
+ '<td>%s</td>' % e(total_cost),
'<td>%s</td>' % test_count,
'<td>%s</td>' % pass_count,
'<td>%s</td>' % fail_entry,
diff --git a/contrib/ci/lambda_functions/ci.py b/contrib/ci/lambda_functions/ci.py
--- a/contrib/ci/lambda_functions/ci.py
+++ b/contrib/ci/lambda_functions/ci.py
@@ -116,6 +116,7 @@
state = event['detail']['state']
print('received %s for %s' % (state, instance_id))
+ ec2_client = boto3.client('ec2')
ec2 = boto3.resource('ec2')
dynamodb = boto3.resource('dynamodb')
@@ -132,7 +133,7 @@
job_table = dynamodb.Table(os.environ['DYNAMODB_JOB_TABLE'])
- react_to_instance_state_change(job_table, instance, state)
+ react_to_instance_state_change(ec2_client, job_table, instance, state)
def handle_try_server_upload(event, context):
@@ -644,7 +645,7 @@
)
-def react_to_instance_state_change(job_table, instance, state):
+def react_to_instance_state_change(ec2, job_table, instance, state):
"""React to a CI worker instance state change."""
now = decimal.Decimal(time.time())
@@ -689,17 +690,32 @@
# New instance/job seen. Record that.
if state == 'pending':
print('recording running state for job %s' % job_id)
+
+ # Try to record the cost to running this instance.
+ hourly_cost = None
+
+ if instance.spot_instance_request_id:
+ spot_instance_requests = ec2.describe_spot_instance_requests(
+ SpotInstanceRequestIds=[instance.spot_instance_request_id],
+ )['SpotInstanceRequests']
+
+ if spot_instance_requests:
+ hourly_cost = decimal.Decimal(
+ spot_instance_requests[0]['ActualBlockHourlyPrice'])
+
job_table.update_item(
Key={'job_id': job_id},
UpdateExpression=(
'set execution_state = :state, '
'instance_id = :instance_id, '
+ 'instance_hourly_cost = :hourly_cost, '
'start_time = :start_time, '
'exit_clean = :exit_clean'
),
ExpressionAttributeValues={
':state': 'running',
':instance_id': instance.instance_id,
+ ':hourly_cost': hourly_cost,
':start_time': now,
':exit_clean': False,
},
To: indygreg, #hg-reviewers
Cc: mercurial-devel
More information about the Mercurial-devel
mailing list