Skip to content

Commit 718165a

Browse files
authored
Fix measuring time during work item migration (#2736)
A have already fixed this in PR #2706 but it was broken (basically reverted) back in #2712. Now the processing of 10 000 items says, it will finish in 4 minutes and this time rapidly decreases – after processing 1000 items, the remainig time is only only 1 minute. There are two main problems. The duration of activity is not measured, but set to fixed time of 10 seconds using `activity?.SetEndTime(activity.StartTimeUtc.AddSeconds(10));`. I assume, that you did it because the `Duration` was zero when you accessed it when calculating `average`. This is because the `Duration` is not dynamic – it will not retur actual duration of activity. It is possible to set it directly to some fixed time with `SetEndTime`. But in our case when we need real duration of activity, we must stop it. When `activity.Stop()` is called, `Duration` is set to real time it has taken and so we need to stop the activity before we access `Duration`. The second problem si calculating average and remaining time. In description of #2712 you wrote: > Removed the `ProgressTimer` class and associated logic, replacing it with simpler calculations for average and remaining time during work item processing. It is not possible to count average and remaining time during work item processing, because that is processing of just single one work item – that means, the duration of activity you count with is just the duration of processing 1 item. There is no information about how long all the processing already took. So say the processing is stable and to process one item always takes 1 second. This value is `activity.Duration`. Now you calculate average as: `var average = new TimeSpan(0, 0, 0, 0, (int)(activity.Duration.TotalMilliseconds / _current));` So if first item was processed, average time is 1 / 1 that means 1 s. If second item is processed, average time is 1 / 2 = 0,5 s. After 10 processed item the average is 1 / 10 = 0,1 s. So every processed item decreases average time even if the processing takes always the same time. We really need something _above_ the one processed item and that is the `ProgressTimer` class. Because we need to track cumulative processing time of every item and calculate average as this cumulative time divided by processed items. And this class also handles retrying, so if the item is processed three times (two times failed), it is taken into account.
2 parents 2fbb97d + 4614742 commit 718165a

File tree

1 file changed

+40
-11
lines changed

1 file changed

+40
-11
lines changed

src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessor.cs

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,35 @@ namespace MigrationTools.Processors
3737
/// <processingtarget>Work Items</processingtarget>
3838
public class TfsWorkItemMigrationProcessor : TfsProcessor
3939
{
40+
private class ProgressTimer
41+
{
42+
private TimeSpan _totalProcessedTime = TimeSpan.Zero;
43+
44+
public ProgressTimer(int totalCount)
45+
{
46+
TotalCount = totalCount;
47+
}
48+
49+
public int TotalCount { get; private set; }
50+
public int ProcessedCount { get; private set; }
51+
public TimeSpan TotalProcessedTime => _totalProcessedTime;
52+
53+
public void AddProcessedItem(TimeSpan processDuration, bool addToTotal)
54+
{
55+
if (addToTotal)
56+
{
57+
TotalCount++;
58+
}
59+
ProcessedCount++;
60+
_totalProcessedTime += processDuration;
61+
}
62+
63+
public TimeSpan AverageTime => ProcessedCount > 0
64+
? TimeSpan.FromSeconds(TotalProcessedTime.TotalSeconds / ProcessedCount)
65+
: TotalProcessedTime;
66+
67+
public TimeSpan RemainingTime => TimeSpan.FromTicks(AverageTime.Ticks * (TotalCount - ProcessedCount));
68+
}
4069

4170
private static int _count = 0;
4271
private static int _current = 0;
@@ -147,6 +176,8 @@ protected override void InternalExecute()
147176
_current = 1;
148177
_count = sourceWorkItems.Count;
149178
_totalWorkItem = sourceWorkItems.Count;
179+
ProgressTimer progressTimer = new ProgressTimer(_totalWorkItem);
180+
150181
ProcessorActivity.SetTag("source_workitems_to_process", sourceWorkItems.Count);
151182
foreach (WorkItemData sourceWorkItemData in sourceWorkItems)
152183
{
@@ -163,7 +194,7 @@ protected override void InternalExecute()
163194
{
164195
try
165196
{
166-
ProcessWorkItemAsync(sourceWorkItemData, Options.WorkItemCreateRetryLimit).Wait();
197+
ProcessWorkItemAsync(sourceWorkItemData, progressTimer, Options.WorkItemCreateRetryLimit).Wait();
167198

168199
stopwatch.Stop();
169200
var processingTime = stopwatch.Elapsed.TotalMilliseconds;
@@ -407,7 +438,6 @@ private WorkItemData CreateWorkItem_Shell(ProjectData destProject, WorkItemData
407438
activity?.SetTagsFromOptions(Options);
408439
activity?.SetTag("http.request.method", "GET");
409440
activity?.SetTag("migrationtools.client", "TfsObjectModel");
410-
activity?.SetEndTime(activity.StartTimeUtc.AddSeconds(10));
411441

412442
WorkItem newwit;
413443
if (destProject.ToProject().WorkItemTypes.Contains(destType))
@@ -580,14 +610,13 @@ private void ProcessWorkItemEmbeddedLinks(WorkItemData sourceWorkItem, WorkItemD
580610
}
581611
}
582612

583-
private async Task ProcessWorkItemAsync(WorkItemData sourceWorkItem, int retryLimit = 5, int retries = 0)
613+
private async Task ProcessWorkItemAsync(WorkItemData sourceWorkItem, ProgressTimer progressTimer, int retryLimit = 5, int retries = 0)
584614
{
585615
using (var activity = ActivitySourceProvider.ActivitySource.StartActivity("ProcessWorkItemAsync", ActivityKind.Client))
586616
{
587617
activity?.SetTagsFromOptions(Options);
588618
activity?.SetTag("http.request.method", "GET");
589619
activity?.SetTag("migrationtools.client", "TfsObjectModel");
590-
activity?.SetEndTime(activity.StartTimeUtc.AddSeconds(10));
591620
activity?.SetTag("SourceURL", Source.Options.Collection.ToString());
592621
activity?.SetTag("SourceWorkItem", sourceWorkItem.Id);
593622
activity?.SetTag("TargetURL", Target.Options.Collection.ToString());
@@ -676,7 +705,7 @@ private async Task ProcessWorkItemAsync(WorkItemData sourceWorkItem, int retryLi
676705
{"Retrys", retries },
677706
{"RetryLimit", retryLimit }
678707
});
679-
await ProcessWorkItemAsync(sourceWorkItem, retryLimit, retries);
708+
await ProcessWorkItemAsync(sourceWorkItem, progressTimer, retryLimit, retries);
680709
}
681710
else
682711
{
@@ -692,15 +721,15 @@ private async Task ProcessWorkItemAsync(WorkItemData sourceWorkItem, int retryLi
692721
Telemetry.TrackException(ex, activity.Tags);
693722
throw ex;
694723
}
695-
var average = new TimeSpan(0, 0, 0, 0, (int)(activity.Duration.TotalMilliseconds / _current));
696-
var remaining = new TimeSpan(0, 0, 0, 0, (int)(average.TotalMilliseconds * _count));
724+
activity?.Stop();
725+
progressTimer.AddProcessedItem(activity.Duration, retries > 0);
697726
TraceWriteLine(LogEventLevel.Information,
698-
"Average time of {average:%s}.{average:%fff} per work item and {remaining:%h} hours {remaining:%m} minutes {remaining:%s}.{remaining:%fff} seconds estimated to completion",
727+
"Average time of {average:%s}.{average:%fff} per work item and {remaining:%h} hours {remaining:%m} minutes {remaining:%s} seconds estimated to completion.",
699728
new Dictionary<string, object>() {
700-
{"average", average},
701-
{"remaining", remaining}
729+
{ "average", progressTimer.AverageTime },
730+
{ "remaining", progressTimer.RemainingTime }
702731
});
703-
activity?.Stop();
732+
704733
activity?.SetStatus(ActivityStatusCode.Error);
705734
activity?.SetTag("http.response.status_code", "200");
706735

0 commit comments

Comments
 (0)