Skip to content

Commit d12a7c5

Browse files
committed
trace2: add region_enter and region_leave events
Add supporting infrastructure for regions. This includes the normal `Write` methods in Trace2 and corresponding Trace2 messages with a few notable distinctions: 1. There is an abstract parent RegionMessage that inherits from Trace2Message. The RegionEnterMessage and RegionLeave messages inherit from this parent (rather than Trace2Message) to maximize reuse of shared fields. 2. We create a new Region class that implements DisposableObject and call it using a CreateRegion method in Trace2. This ensures that we call WriteRegionLeave before a Region object is disposed - a key requirement of Git's Trace2 API for regions. Additionally, it allows us to manage getting the executing thread name and region timings within the class, thus removing this burden from consumers. An additional note is that the region_enter and region_leave events will always have the same line number with this model. This was deemed to be acceptable, however, given that knowing where a region begins executing provides enough information for inspection of the region if it is needed.
1 parent f90830f commit d12a7c5

File tree

3 files changed

+267
-13
lines changed

3 files changed

+267
-13
lines changed

src/shared/Core/Trace2.cs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.ComponentModel;
34
using System.IO;
45
using System.IO.Pipes;
6+
using System.Reflection.Emit;
57
using System.Text;
68
using System.Threading;
79

@@ -19,6 +21,8 @@ public enum Trace2Event
1921
ChildStart = 3,
2022
ChildExit = 4,
2123
Error = 5,
24+
RegionEnter = 6,
25+
RegionLeave = 7,
2226
}
2327

2428
/// <summary>
@@ -54,6 +58,42 @@ public class PerformanceFormatSpan
5458
public int EndPadding { get; set; }
5559
}
5660

61+
/// <summary>
62+
/// Class that manages regions.
63+
/// </summary>
64+
public class Region : DisposableObject
65+
{
66+
private readonly ITrace2 _trace2;
67+
private readonly string _category;
68+
private readonly string _label;
69+
private readonly string _filePath;
70+
private readonly int _lineNumber;
71+
private readonly string _message;
72+
private readonly string _thread;
73+
private readonly DateTimeOffset _startTime;
74+
75+
public Region(ITrace2 trace2, string category, string label, string filePath, int lineNumber, string message = "")
76+
{
77+
_trace2 = trace2;
78+
_category = category;
79+
_label = label;
80+
_filePath = filePath;
81+
_lineNumber = lineNumber;
82+
_message = message;
83+
84+
_startTime = DateTimeOffset.UtcNow;
85+
_thread = ThreadHelpers.BuildThreadName();
86+
87+
_trace2.WriteRegionEnter(_category, _label, _thread, _message, _filePath, _lineNumber);
88+
}
89+
90+
protected override void ReleaseManagedResources()
91+
{
92+
double relativeTime = (DateTimeOffset.UtcNow - _startTime).TotalSeconds;
93+
_trace2.WriteRegionLeave(relativeTime, _category, _label, _thread, _message, _filePath, _lineNumber);
94+
}
95+
}
96+
5797
/// <summary>
5898
/// Represents the application's TRACE2 tracing system.
5999
/// </summary>
@@ -150,6 +190,51 @@ void WriteError(
150190
string filePath = "",
151191
[System.Runtime.CompilerServices.CallerLineNumber]
152192
int lineNumber = 0);
193+
194+
Region CreateRegion(
195+
string category,
196+
string label,
197+
[System.Runtime.CompilerServices.CallerFilePath]
198+
string filePath = "",
199+
[System.Runtime.CompilerServices.CallerLineNumber]
200+
int lineNumber = 0,
201+
string message = "");
202+
203+
/// <summary>
204+
/// Writes a region enter message to the trace writer.
205+
/// </summary>
206+
/// <param name="category">Category of region.</param>
207+
/// <param name="label">Description of region.</param>
208+
/// <param name="threadName">Name of the currently-executing thread.</param>
209+
/// <param name="message">Message associated with entering region.</param>
210+
/// <param name="filePath">Path of the file this method is called from.</param>
211+
/// <param name="lineNumber">Line number of file this method is called from.</param>
212+
void WriteRegionEnter(
213+
string category,
214+
string label,
215+
string threadName,
216+
string message = "",
217+
string filePath = "",
218+
int lineNumber = 0);
219+
220+
/// <summary>
221+
/// Writes a region leave message to the trace writer.
222+
/// </summary>
223+
/// <param name="relativeTime">Time of region execution.</param>
224+
/// <param name="category">Category of region.</param>
225+
/// <param name="label">Description of region.</param>
226+
/// <param name="threadName">Name of the currently-executing thread.</param>
227+
/// <param name="message">Message associated with entering region.</param>
228+
/// <param name="filePath">Path of the file this method is called from.</param>
229+
/// <param name="lineNumber">Line number of file this method is called from.</param>
230+
void WriteRegionLeave(
231+
double relativeTime,
232+
string category,
233+
string label,
234+
string threadName,
235+
string message = "",
236+
string filePath = "",
237+
int lineNumber = 0);
153238
}
154239

155240
public class Trace2 : DisposableObject, ITrace2
@@ -322,6 +407,66 @@ public void WriteError(
322407
});
323408
}
324409

410+
public Region CreateRegion(
411+
string category,
412+
string label,
413+
string filePath,
414+
int lineNumber,
415+
string message = "")
416+
{
417+
return new Region(this, category, label, filePath, lineNumber, message);
418+
}
419+
420+
public void WriteRegionEnter(
421+
string category,
422+
string label,
423+
string threadName,
424+
string message = "",
425+
string filePath = "",
426+
int lineNumber = 0)
427+
{
428+
WriteMessage(new RegionEnterMessage()
429+
{
430+
Event = Trace2Event.RegionEnter,
431+
Sid = _sid,
432+
Time = DateTimeOffset.UtcNow,
433+
Category = category,
434+
Label = label,
435+
Message = message == "" ? label : message,
436+
Thread = threadName,
437+
File = Path.GetFileName(filePath),
438+
Line = lineNumber,
439+
ElapsedTime = (DateTimeOffset.UtcNow - _applicationStartTime).TotalSeconds,
440+
Depth = ProcessManager.Depth
441+
});
442+
}
443+
444+
public void WriteRegionLeave(
445+
double relativeTime,
446+
string category,
447+
string label,
448+
string threadName,
449+
string message = "",
450+
string filePath = "",
451+
int lineNumber = 0)
452+
{
453+
WriteMessage(new RegionLeaveMessage()
454+
{
455+
Event = Trace2Event.RegionLeave,
456+
Sid = _sid,
457+
Time = DateTimeOffset.UtcNow,
458+
Category = category,
459+
Label = label,
460+
Message = message == "" ? label : message,
461+
Thread = threadName,
462+
File = Path.GetFileName(filePath),
463+
Line = lineNumber,
464+
ElapsedTime = (DateTimeOffset.UtcNow - _applicationStartTime).TotalSeconds,
465+
RelativeTime = relativeTime,
466+
Depth = ProcessManager.Depth
467+
});
468+
}
469+
325470
protected override void ReleaseManagedResources()
326471
{
327472
lock (_writersLock)

src/shared/Core/Trace2Message.cs

Lines changed: 95 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,7 @@
77

88
namespace GitCredentialManager;
99

10-
public interface ITrace2Message
11-
{
12-
Trace2Event Event { get; set; }
13-
string Sid { get; set; }
14-
string Thread { get; set; }
15-
DateTimeOffset Time { get; set; }
16-
string File { get; set; }
17-
int Line { get; set; }
18-
int Depth { get; set; }
19-
}
20-
21-
public abstract class Trace2Message : ITrace2Message
10+
public abstract class Trace2Message
2211
{
2312
private const int SourceColumnMaxWidth = 23;
2413
private const string NormalPerfTimeFormat = "HH:mm:ss.ffffff";
@@ -173,7 +162,8 @@ private static string BuildSpan(PerformanceFormatSpan component, string data)
173162

174163
if (data.Length < dataLimit)
175164
{
176-
component.EndPadding += Math.Abs(sizeDifference);
165+
// Increase end padding for short values
166+
component.EndPadding += sizeDifference;
177167
}
178168

179169
var beginPadding = new string(' ', component.BeginPadding);
@@ -447,3 +437,95 @@ protected override string GetEventMessage(Trace2FormatTarget formatTarget)
447437
return Message;
448438
}
449439
}
440+
441+
public abstract class RegionMessage : Trace2Message
442+
{
443+
[JsonProperty("repo")]
444+
445+
// Defaults to 1, as does Git.
446+
// See https://git-scm.com/docs/api-trace2#Documentation/technical/api-trace2.txt-codeltrepo-idgtcode for details.
447+
public int Repo { get; set; } = 1;
448+
449+
// Nested regions are not currently supported in GCM, so this value should always be 1.
450+
[JsonProperty("nesting")]
451+
public int Nesting { get; set; } = 1;
452+
453+
[JsonProperty("category")]
454+
public string Category { get; set; }
455+
456+
[JsonProperty("label")]
457+
public string Label { get; set; }
458+
459+
[JsonProperty("msg")]
460+
public string Message { get; set; }
461+
462+
public double ElapsedTime { get; set; }
463+
}
464+
465+
public class RegionEnterMessage : RegionMessage
466+
{
467+
public override string ToJson()
468+
{
469+
return JsonConvert.SerializeObject(this,
470+
new StringEnumConverter(typeof(SnakeCaseNamingStrategy)),
471+
new IsoDateTimeConverter()
472+
{
473+
DateTimeFormat = TimeFormat
474+
});
475+
}
476+
477+
public override string ToNormalString()
478+
{
479+
return BuildNormalString();
480+
}
481+
482+
public override string ToPerformanceString()
483+
{
484+
return BuildPerformanceString();
485+
}
486+
487+
protected override string BuildPerformanceSpan()
488+
{
489+
return $"|{BuildRepoSpan(Repo)}|{BuildTimeSpan(ElapsedTime)}| |{BuildCategorySpan(Category)}";
490+
}
491+
492+
protected override string GetEventMessage(Trace2FormatTarget formatTarget)
493+
{
494+
return Message;
495+
}
496+
}
497+
498+
public class RegionLeaveMessage : RegionMessage
499+
{
500+
public double RelativeTime { get; set; }
501+
502+
public override string ToJson()
503+
{
504+
return JsonConvert.SerializeObject(this,
505+
new StringEnumConverter(typeof(SnakeCaseNamingStrategy)),
506+
new IsoDateTimeConverter()
507+
{
508+
DateTimeFormat = TimeFormat
509+
});
510+
}
511+
512+
public override string ToNormalString()
513+
{
514+
return BuildNormalString();
515+
}
516+
517+
public override string ToPerformanceString()
518+
{
519+
return BuildPerformanceString();
520+
}
521+
522+
protected override string BuildPerformanceSpan()
523+
{
524+
return $"|{BuildRepoSpan(Repo)}|{BuildTimeSpan(ElapsedTime)}|{BuildTimeSpan(RelativeTime)}|{BuildCategorySpan(Category)}";
525+
}
526+
527+
protected override string GetEventMessage(Trace2FormatTarget formatTarget)
528+
{
529+
return Message;
530+
}
531+
}

src/shared/TestInfrastructure/Objects/NullTrace.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,33 @@ public void WriteError(
8989
string filePath = "",
9090
int lineNumber = 0) { }
9191

92+
public Region CreateRegion(
93+
string category,
94+
string label,
95+
string filePath = "",
96+
int lineNumber = 0,
97+
string message = "")
98+
{
99+
return new Region(this, category, label, filePath, lineNumber, message);
100+
}
101+
102+
public void WriteRegionEnter(
103+
string category,
104+
string label,
105+
string threadName,
106+
string message = "",
107+
string filePath = "",
108+
int lineNumber = 0) { }
109+
110+
public void WriteRegionLeave(
111+
double relativeTime,
112+
string category,
113+
string label,
114+
string threadName,
115+
string message = "",
116+
string filePath = "",
117+
int lineNumber = 0) { }
118+
92119
#endregion
93120

94121
#region IDisposable

0 commit comments

Comments
 (0)