Display overall duration in buidset page in zuul web

The overall duration is from a user (developer) point of view, how much
time it takes from the trigger of the build (e.g. a push, a comment,
etc.), till the last build is finished.

It takes into account also the time spent in waiting in queue, launching
nodes, preparing the nodes, etc.

Technically it measures between the event timestamp and the end time of
the last build in the build set.

This duration reflects the user experience of how much time the user needs
to wait.

Change-Id: I253d023146c696d0372197e599e0df3c217ef344
This commit is contained in:
Dong Zhang
2022-01-03 10:46:36 +08:00
committed by James E. Blair
parent 02efa8fb28
commit 8a01c61991
6 changed files with 82 additions and 7 deletions

View File

@@ -73,7 +73,7 @@ class TestSQLConnectionMysql(ZuulTestCase):
buildset_table = table_prefix + 'zuul_buildset'
build_table = table_prefix + 'zuul_build'
self.assertEqual(16, len(insp.get_columns(buildset_table)))
self.assertEqual(17, len(insp.get_columns(buildset_table)))
self.assertEqual(13, len(insp.get_columns(build_table)))
def test_sql_tables_created(self):
@@ -146,6 +146,7 @@ class TestSQLConnectionMysql(ZuulTestCase):
'https://review.example.com/%d' % buildset0['change'],
buildset0['ref_url'])
self.assertNotEqual(None, buildset0['event_id'])
self.assertNotEqual(None, buildset0['event_timestamp'])
buildset0_builds = conn.execute(
sa.sql.select([reporter.connection.zuul_build_table]).where(

View File

@@ -62,6 +62,13 @@ function Buildset({ buildset, timezone, tenant, user }) {
(moment.utc(lastEndBuild.end_time).tz(timezone) -
moment.utc(firstStartBuild.start_time).tz(timezone)) /
1000
const overallDuration =
(moment.utc(lastEndBuild.end_time).tz(timezone) -
moment.utc(
buildset.event_timestamp!=null
? buildset.event_timestamp : firstStartBuild.start_time
).tz(timezone)
) / 1000
const buildLink = (build) => (
<Link to={`${tenant.linkPrefix}/build/${build.uuid}`}>
@@ -109,7 +116,7 @@ function Buildset({ buildset, timezone, tenant, user }) {
icon={<OutlinedClockIcon />}
value={
<>
<strong>Total duration </strong>
<strong>Total build duration </strong>
{moment
.duration(totalDuration, 'seconds')
.format('h [hr] m [min] s [sec]')}{' '}
@@ -126,6 +133,18 @@ function Buildset({ buildset, timezone, tenant, user }) {
</>
}
/>
<IconProperty
WrapElement={ListItem}
icon={<OutlinedClockIcon />}
value={
<>
<strong>Overall duration </strong>
{moment
.duration(overallDuration, 'seconds')
.format('h [hr] m [min] s [sec]')}
</>
}
/>
</List>
</FlexItem>
</Flex>

View File

@@ -0,0 +1,39 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""add event timestamp column
Revision ID: eca077de5e1b
Revises: 40c49b6fc2e3
Create Date: 2022-01-03 13:52:37.262934
"""
# revision identifiers, used by Alembic.
revision = 'eca077de5e1b'
down_revision = '40c49b6fc2e3'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
def upgrade(table_prefix=''):
op.add_column(
table_prefix + "zuul_buildset",
sa.Column("event_timestamp", sa.DateTime, nullable=True)
)
def downgrade():
raise Exception("Downgrades not supported")

View File

@@ -59,7 +59,7 @@ class DatabaseSession(object):
def getBuilds(self, tenant=None, project=None, pipeline=None,
change=None, branch=None, patchset=None, ref=None,
newrev=None, event_id=None, uuid=None,
newrev=None, event_id=None, event_timestamp=None, uuid=None,
job_name=None, voting=None, nodeset=None,
result=None, provides=None, final=None, held=None,
complete=None, sort_by_buildset=False, limit=50,
@@ -100,6 +100,8 @@ class DatabaseSession(object):
q = self.listFilter(q, buildset_table.c.ref, ref)
q = self.listFilter(q, buildset_table.c.newrev, newrev)
q = self.listFilter(q, buildset_table.c.event_id, event_id)
q = self.listFilter(
q, buildset_table.c.event_timestamp, event_timestamp)
q = self.listFilter(q, build_table.c.uuid, uuid)
q = self.listFilter(q, build_table.c.job_name, job_name)
q = self.listFilter(q, build_table.c.voting, voting)
@@ -331,6 +333,7 @@ class SQLConnection(BaseConnection):
branch = sa.Column(sa.String(255))
uuid = sa.Column(sa.String(36))
event_id = sa.Column(sa.String(255), nullable=True)
event_timestamp = sa.Column(sa.DateTime, nullable=True)
sa.Index(self.table_prefix + 'project_pipeline_idx',
project, pipeline)

View File

@@ -46,9 +46,12 @@ class SQLReporter(BaseReporter):
if not buildset.uuid:
return
event_id = None
event_timestamp = None
item = buildset.item
if item.event is not None:
event_id = getattr(item.event, "zuul_event_id", None)
event_timestamp = datetime.datetime.fromtimestamp(
item.event.timestamp, tz=datetime.timezone.utc)
with self.connection.getSession() as db:
db_buildset = db.createBuildSet(
@@ -65,6 +68,7 @@ class SQLReporter(BaseReporter):
zuul_ref=buildset.ref,
ref_url=item.change.url,
event_id=event_id,
event_timestamp=event_timestamp,
)
return db_buildset

View File

@@ -1222,15 +1222,16 @@ class ZuulWebAPI(object):
resp.headers['Content-Type'] = 'text/plain'
return key
def _datetimeToString(self, my_datetime):
return my_datetime.strftime('%Y-%m-%dT%H:%M:%S')
def buildToDict(self, build, buildset=None):
start_time = build.start_time
if build.start_time:
start_time = start_time.strftime(
'%Y-%m-%dT%H:%M:%S')
start_time = self._datetimeToString(start_time)
end_time = build.end_time
if build.end_time:
end_time = end_time.strftime(
'%Y-%m-%dT%H:%M:%S')
end_time = self._datetimeToString(end_time)
if build.start_time and build.end_time:
duration = (build.end_time -
build.start_time).total_seconds()
@@ -1256,6 +1257,9 @@ class ZuulWebAPI(object):
}
if buildset:
event_timestamp = buildset.event_timestamp
if event_timestamp:
event_timestamp = self._datetimeToString(event_timestamp)
ret.update({
'project': buildset.project,
'branch': buildset.branch,
@@ -1266,6 +1270,7 @@ class ZuulWebAPI(object):
'newrev': buildset.newrev,
'ref_url': buildset.ref_url,
'event_id': buildset.event_id,
'event_timestamp': event_timestamp,
'buildset': {
'uuid': buildset.uuid,
},
@@ -1340,6 +1345,9 @@ class ZuulWebAPI(object):
return data
def buildsetToDict(self, buildset, builds=[]):
event_timestamp = buildset.event_timestamp
if event_timestamp:
event_timestamp = self._datetimeToString(event_timestamp)
ret = {
'_id': buildset.id,
'uuid': buildset.uuid,
@@ -1354,6 +1362,7 @@ class ZuulWebAPI(object):
'newrev': buildset.newrev,
'ref_url': buildset.ref_url,
'event_id': buildset.event_id,
'event_timestamp': event_timestamp,
}
if builds:
ret['builds'] = []