From 6fe93f8d79725d696e01090ab531585881636850 Mon Sep 17 00:00:00 2001 From: Victor Lu <75279620+victlu@users.noreply.github.com> Date: Thu, 28 Jan 2021 16:59:45 -0800 Subject: [PATCH] Replace DateTime.UtcNow.ToString with a zero-allocation version (#1650) * Replace DateTime.UtcNow.ToString with a zero-allocation version * Fix compiler warnings * Add Unit Test for DateTimeGetBytes * Fix dotnet-format errors * Fix style check errors * Fix compiler errors * Fix merge issues --- .../Internal/SelfDiagnosticsEventListener.cs | 108 +++++++++++++++++- .../SelfDiagnosticsEventListenerTest.cs | 39 +++++++ 2 files changed, 145 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry/Internal/SelfDiagnosticsEventListener.cs b/src/OpenTelemetry/Internal/SelfDiagnosticsEventListener.cs index b03930710c..b093fc71e4 100644 --- a/src/OpenTelemetry/Internal/SelfDiagnosticsEventListener.cs +++ b/src/OpenTelemetry/Internal/SelfDiagnosticsEventListener.cs @@ -127,8 +127,7 @@ internal void WriteEvent(string eventMessage, ReadOnlyCollection payload this.writeBuffer.Value = buffer; } - var timestamp = DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture); - var pos = Encoding.UTF8.GetBytes(timestamp, 0, timestamp.Length, buffer, 0); + var pos = this.DateTimeGetBytes(DateTime.UtcNow, buffer, 0); buffer[pos++] = (byte)':'; pos = EncodeInBuffer(eventMessage, false, buffer, pos); if (payload != null) @@ -172,6 +171,111 @@ internal void WriteEvent(string eventMessage, ReadOnlyCollection payload } } + /// + /// Write the datetime formatted string into bytes byte-array starting at byteIndex position. + /// + /// [DateTimeKind.Utc] + /// format: yyyy - MM - dd T HH : mm : ss . fffffff Z (i.e. 2020-12-09T10:20:50.4659412Z). + /// + /// + /// [DateTimeKind.Local] + /// format: yyyy - MM - dd T HH : mm : ss . fffffff +|- HH : mm (i.e. 2020-12-09T10:20:50.4659412-08:00). + /// + /// + /// [DateTimeKind.Unspecified] + /// format: yyyy - MM - dd T HH : mm : ss . fffffff (i.e. 2020-12-09T10:20:50.4659412). + /// + /// + /// + /// The bytes array must be large enough to write 27-33 charaters from the byteIndex starting position. + /// + /// DateTime. + /// Array of bytes to write. + /// Starting index into bytes array. + /// The number of bytes written. + internal int DateTimeGetBytes(DateTime datetime, byte[] bytes, int byteIndex) + { + int num; + int pos = byteIndex; + + num = datetime.Year; + bytes[pos++] = (byte)('0' + ((num / 1000) % 10)); + bytes[pos++] = (byte)('0' + ((num / 100) % 10)); + bytes[pos++] = (byte)('0' + ((num / 10) % 10)); + bytes[pos++] = (byte)('0' + (num % 10)); + + bytes[pos++] = (byte)'-'; + + num = datetime.Month; + bytes[pos++] = (byte)('0' + ((num / 10) % 10)); + bytes[pos++] = (byte)('0' + (num % 10)); + + bytes[pos++] = (byte)'-'; + + num = datetime.Day; + bytes[pos++] = (byte)('0' + ((num / 10) % 10)); + bytes[pos++] = (byte)('0' + (num % 10)); + + bytes[pos++] = (byte)'T'; + + num = datetime.Hour; + bytes[pos++] = (byte)('0' + ((num / 10) % 10)); + bytes[pos++] = (byte)('0' + (num % 10)); + + bytes[pos++] = (byte)':'; + + num = datetime.Minute; + bytes[pos++] = (byte)('0' + ((num / 10) % 10)); + bytes[pos++] = (byte)('0' + (num % 10)); + + bytes[pos++] = (byte)':'; + + num = datetime.Second; + bytes[pos++] = (byte)('0' + ((num / 10) % 10)); + bytes[pos++] = (byte)('0' + (num % 10)); + + bytes[pos++] = (byte)'.'; + + num = (int)(Math.Round(datetime.TimeOfDay.TotalMilliseconds * 10000) % 10000000); + bytes[pos++] = (byte)('0' + ((num / 1000000) % 10)); + bytes[pos++] = (byte)('0' + ((num / 100000) % 10)); + bytes[pos++] = (byte)('0' + ((num / 10000) % 10)); + bytes[pos++] = (byte)('0' + ((num / 1000) % 10)); + bytes[pos++] = (byte)('0' + ((num / 100) % 10)); + bytes[pos++] = (byte)('0' + ((num / 10) % 10)); + bytes[pos++] = (byte)('0' + (num % 10)); + + switch (datetime.Kind) + { + case DateTimeKind.Utc: + bytes[pos++] = (byte)'Z'; + break; + + case DateTimeKind.Local: + TimeSpan ts = TimeZoneInfo.Local.GetUtcOffset(datetime); + + bytes[pos++] = (byte)(ts.Hours >= 0 ? '+' : '-'); + + num = Math.Abs(ts.Hours); + bytes[pos++] = (byte)('0' + ((num / 10) % 10)); + bytes[pos++] = (byte)('0' + (num % 10)); + + bytes[pos++] = (byte)':'; + + num = ts.Minutes; + bytes[pos++] = (byte)('0' + ((num / 10) % 10)); + bytes[pos++] = (byte)('0' + (num % 10)); + break; + + case DateTimeKind.Unspecified: + default: + // Skip + break; + } + + return pos - byteIndex; + } + protected override void OnEventSourceCreated(EventSource eventSource) { if (eventSource.Name.StartsWith(EventSourceNamePrefix, StringComparison.Ordinal)) diff --git a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTest.cs b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTest.cs index eeca77e977..3d1b2b98f6 100644 --- a/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTest.cs +++ b/test/OpenTelemetry.Tests/Internal/SelfDiagnosticsEventListenerTest.cs @@ -93,6 +93,45 @@ public void SelfDiagnosticsEventListener_WriteEvent() AssertFileOutput(LOGFILEPATH, eventMessage); } + [Fact] + [Trait("Platform", "Any")] + public void SelfDiagnosticsEventListener_DateTimeGetBytes() + { + var configRefresherMock = new Mock(); + var listener = new SelfDiagnosticsEventListener(EventLevel.Error, configRefresherMock.Object); + + // Check DateTimeKind of Utc, Local, and Unspecified + DateTime[] datetimes = new DateTime[] + { + DateTime.SpecifyKind(DateTime.Parse("1996-12-01T14:02:31.1234567-08:00"), DateTimeKind.Utc), + DateTime.SpecifyKind(DateTime.Parse("1996-12-01T14:02:31.1234567-08:00"), DateTimeKind.Local), + DateTime.SpecifyKind(DateTime.Parse("1996-12-01T14:02:31.1234567-08:00"), DateTimeKind.Unspecified), + DateTime.UtcNow, + DateTime.Now, + }; + + // Expect to match output string from DateTime.ToString("O") + string[] expected = new string[datetimes.Length]; + for (int i = 0; i < datetimes.Length; i++) + { + expected[i] = datetimes[i].ToString("O"); + } + + byte[] buffer = new byte[40 * datetimes.Length]; + int pos = 0; + + // Get string after DateTimeGetBytes() write into a buffer + string[] results = new string[datetimes.Length]; + for (int i = 0; i < datetimes.Length; i++) + { + int len = listener.DateTimeGetBytes(datetimes[i], buffer, pos); + results[i] = Encoding.Default.GetString(buffer, pos, len); + pos += len; + } + + Assert.Equal(expected, results); + } + [Fact] [Trait("Platform", "Any")] public void SelfDiagnosticsEventListener_EmitEvent_OmitAsConfigured()