Monthly Archives: November 2012

Как отчеты TFS выглядят изнутри

Я недавно писал о том, как пользоваться отчетом Burndown and Burn Rate в TFS.

Сейчас хочу рассказать немного про внутреннюю кухню. Как же данные из issue tracker, например значения полей remaining work и completed work у элементов task, превращаются красивую картинку отчета?

На самом деле, никакой магии здесь нет. Всё предельно просто.

Данные issue tracker хранятся в обычной БД SQL Server с именем TFS_имяколлекциипроектов. Обычная такая транзакционная БД. С немного страшной структурой, ну с кем не бывает, и в MS страшные вещи проектируют.

Например work items скорее всего лежат в какой-то из этих таблиц (а может и во всех сразу): WorkItemsAre, WorkItemsWere, WorkItemsLatest, WorkItemsDestroyed, с любопытными полями вроде “Not A Field” и “Fld10013”.

Нет, данные из этих таблиц не идут напрямую на картинку отчета.

Раз в 2 минуты (по умолчанию) TFS перекидывает данные в data warehouse (склад данных). БД TFS_Warehouse. Обычный такой data warehouse, с таблицами фактов, таблицами измерений. Зачем это делается? Профиль нагрузки на транзакционную БД и на data warehouse очень разный. Транзакционная БД должна хранить только текущее состояние данных, и обеспечивать обработку большого количества запросов чтения/записи. Data warehouse должен хранить огромные объемы (в идеале – всю историю изменений рабочих элементов TFS), при этом данные там не изменяются и не удаляются, только добавляются. Структура базы данных оптимизирована для использования в отчетах и аналитике.

Далее раз в 2 часа (по умолчанию) данные попадают в OLAP-кубы SQL Server Analysis Services (база данных Tfs_Analysis в SSAS). При этом там производятся всякие расчеты хитрые, например предварительный расчет различных агрегированных показателей, для того чтобы потом быстро отвечать на аналитические запросы.

И только после этого, SQL Server Reporting Services на основе данных из SQL Server Analysis Services строит отчеты. Они строятся тоже не в real-time по запросу пользователя, а периодически обновляются в фоновом режиме. Как часто – уже не помню, но чаще чем 1 раз в час.

Время последнего обновления данных в SSAS обычно указывается в отчете снизу справа с подписью “Data updated”. Время последнего обновления отчета в SQL Server Reporting Services – там же, с подписью “Generated”.

В разработке собственных отчетов (или правке стандартных) тоже нет никакой магии. Это обычные отчеты SQL Server Reporting Services.

Если зайти в веб-интерфейс SQL Server Reporting Services, найти там список отчетов, у каждого отчета в контекстном меню есть пункт “Edit in report builder”.

Он открывает report builderGUI-приложение, которое инсталлируется через Click once, и позволяет полноценно редактировать отчет.

Тут мы можем настраивать источники данных, привязывать их к элементам отчета, стили всякие настраивать.

Посмотрим на то, откуда берутся данные для отчета Burn Rate. Он строится на основе набора данных dsVelocity, который берет данные из SSAS с помощью вот такого MDX-запроса:

WITH
	MEMBER [Measures].[Remaining Work] AS [Measures].[FactCurrentWorkItem Microsoft_VSTS_Scheduling_RemainingWork]
	MEMBER [Measures].[Completed Work] AS [Measures].[FactCurrentWorkItem Microsoft_VSTS_Scheduling_CompletedWork]
SELECT
{
	[Measures].[Work Item Count],
	[Measures].[Remaining Work],
	[Measures].[Completed Work]
} ON COLUMNS,
{
	NonEmpty(
		[Work Item].[System_State].[System_State],
		[Measures].[Work Item Count]
	)
} ON ROWS
FROM
(
	SELECT
		CrossJoin(
			StrToMember("[Team Project].[Project Node GUID].&[{" + @ProjectGuid + "}]"),
			StrToSet(@StateParam),
			StrToSet(@AreaParam),
			StrToSet(@IterationParam),
			Except(
				Descendants(StrToSet(@WorkItemTypeParam)),
				[Work Item].[System_WorkItemType].[All] + StrToSet(@WorkItemsToExclude)
			)
		) ON COLUMNS
	FROM [Team System]
)

Просто взяли данные по рабочим элементам, отфильтровали по параметрам, из мер (measure) нас интересуют количество рабочих элементов, оставшаяся и выполненная работа. Я не до конца этот запрос понимаю, но при желании разобраться можно.

Дальше для вычисления собственно current velocity и required velocity используются вот такие нехитрые выражения:

CurrentVelocity =IIF(
	Parameters!YAxis.Value = "hours",
	Fields!Completed_Work.Value,
	IIF(
		Fields!System_State.Value=Parameters!ClosedName.Value,
		Fields!Cumulative_Count.Value,
		0
		)
	) / Code.DaysCompleted(Parameters!StartDateParam.Value, Parameters!EndDateParam.Value, Parameters!NonWorkDays.Value)

RequiredVelocity =IIF(
	Parameters!YAxis.Value = "hours",
	Fields!Remaining_Work.Value,
	IIF(
		Fields!System_State.Value<>Parameters!ClosedName.Value,
		Fields!Cumulative_Count.Value,
		0
		)
	) / Code.DaysRemaining(Parameters!StartDateParam.Value, Parameters!EndDateParam.Value, Parameters!NonWorkDays.Value)

Если отбросить кучерявые конструкции IIF, видно, что здесь работа делится на рабочие дни. Нерабочие дни при этом вычисляются с использованием параметра “NonWorkDays”, значение по умолчанию у которого равно “0,6” – подозреваю, что это индексы воскресенья и субботы соответственно.

Домашнее задание: отредактируйте график Burndown так, чтобы в нём не было выходных дней. И еще придумайте, как нам быть с тем, что сейчас в России заканчиваются 3-дневные выходные, а значит, график Burn Rate будет врать.

Павел Сурменок

http://surmenok.ru/

http://pavel.surmenok.com/