diff --git a/src/Squidex.Infrastructure/Log/Profiler.cs b/src/Squidex.Infrastructure/Log/Profiler.cs index 581fa1620..85c19bb2b 100644 --- a/src/Squidex.Infrastructure/Log/Profiler.cs +++ b/src/Squidex.Infrastructure/Log/Profiler.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; using Squidex.Infrastructure.Tasks; @@ -56,14 +55,13 @@ namespace Squidex.Infrastructure.Log return NoopDisposable.Instance; } - var startTime = Stopwatch.GetTimestamp(); + var watch = ValueStopwatch.StartNew(); return new DelegateDisposable(() => { - var endTime = Stopwatch.GetTimestamp(); - var elapsed = endTime - startTime; + var elapsedMs = watch.Stop(); - session.Measured(key, elapsed); + session.Measured(key, elapsedMs); }); } } diff --git a/src/Squidex.Infrastructure/Log/SemanticLogExtensions.cs b/src/Squidex.Infrastructure/Log/SemanticLogExtensions.cs index 816d4fdfd..414bf298c 100644 --- a/src/Squidex.Infrastructure/Log/SemanticLogExtensions.cs +++ b/src/Squidex.Infrastructure/Log/SemanticLogExtensions.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using System.Diagnostics; namespace Squidex.Infrastructure.Log { @@ -94,18 +93,17 @@ namespace Squidex.Infrastructure.Log private static IDisposable Measure(this ISemanticLog log, SemanticLogLevel logLevel, Action objectWriter) { - var startTime = Stopwatch.GetTimestamp(); + var watch = ValueStopwatch.StartNew(); return new DelegateDisposable(() => { - var endTime = Stopwatch.GetTimestamp(); - var elapsed = endTime - startTime; + var elapsedMs = watch.Stop(); log.Log(logLevel, writer => { objectWriter?.Invoke(writer); - writer.WriteProperty("elapsedMs", elapsed); + writer.WriteProperty("elapsedMs", elapsedMs); }); }); } diff --git a/src/Squidex.Infrastructure/Orleans/J{T}.cs b/src/Squidex.Infrastructure/Orleans/J{T}.cs index a85c29dd4..e9d2fd633 100644 --- a/src/Squidex.Infrastructure/Orleans/J{T}.cs +++ b/src/Squidex.Infrastructure/Orleans/J{T}.cs @@ -59,7 +59,7 @@ namespace Squidex.Infrastructure.Orleans { var jsonSerializer = GetSerializer(context); - var stream = new MemoryStream(); + var stream = new StreamWriterWrapper(context.StreamWriter); using (var writer = new JsonTextWriter(new StreamWriter(stream))) { @@ -67,11 +67,6 @@ namespace Squidex.Infrastructure.Orleans writer.Flush(); } - - var outBytes = stream.ToArray(); - - context.StreamWriter.Write(outBytes.Length); - context.StreamWriter.Write(outBytes); } } @@ -82,10 +77,7 @@ namespace Squidex.Infrastructure.Orleans { var jsonSerializer = GetSerializer(context); - var outLength = context.StreamReader.ReadInt(); - var outBytes = context.StreamReader.ReadBytes(outLength); - - var stream = new MemoryStream(outBytes); + var stream = new StreamReaderWrapper(context.StreamReader); using (var reader = new JsonTextReader(new StreamReader(stream))) { diff --git a/src/Squidex.Infrastructure/Orleans/StreamReaderWrapper.cs b/src/Squidex.Infrastructure/Orleans/StreamReaderWrapper.cs new file mode 100644 index 000000000..9284b2a9f --- /dev/null +++ b/src/Squidex.Infrastructure/Orleans/StreamReaderWrapper.cs @@ -0,0 +1,88 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.IO; +using Orleans.Serialization; + +namespace Squidex.Infrastructure.Orleans +{ + internal sealed class StreamReaderWrapper : Stream + { + private readonly IBinaryTokenStreamReader reader; + + public override bool CanRead + { + get { return true; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return false; } + } + + public override long Length + { + get { return reader.Length; } + } + + public override long Position + { + get + { + return reader.CurrentPosition; + } + set + { + throw new NotSupportedException(); + } + } + + public StreamReaderWrapper(IBinaryTokenStreamReader reader) + { + this.reader = reader; + } + + public override void Flush() + { + } + + public override int Read(byte[] buffer, int offset, int count) + { + var bytesLeft = reader.Length - reader.CurrentPosition; + + if (bytesLeft < count) + { + count = bytesLeft; + } + + reader.ReadByteArray(buffer, offset, count); + + return count; + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Squidex.Infrastructure/Orleans/StreamWriterWrapper.cs b/src/Squidex.Infrastructure/Orleans/StreamWriterWrapper.cs new file mode 100644 index 000000000..fe9707a47 --- /dev/null +++ b/src/Squidex.Infrastructure/Orleans/StreamWriterWrapper.cs @@ -0,0 +1,79 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.IO; +using Orleans.Serialization; + +namespace Squidex.Infrastructure.Orleans +{ + internal sealed class StreamWriterWrapper : Stream + { + private readonly IBinaryTokenStreamWriter writer; + + public override bool CanRead + { + get { return false; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return true; } + } + + public override long Length + { + get { return writer.CurrentOffset; } + } + + public override long Position + { + get + { + return writer.CurrentOffset; + } + set + { + throw new NotSupportedException(); + } + } + + public StreamWriterWrapper(IBinaryTokenStreamWriter writer) + { + this.writer = writer; + } + + public override void Flush() + { + } + + public override void Write(byte[] buffer, int offset, int count) + { + writer.Write(buffer, offset, count); + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/Squidex.Infrastructure/ValueStopwatch.cs b/src/Squidex.Infrastructure/ValueStopwatch.cs new file mode 100644 index 000000000..86c4b50c0 --- /dev/null +++ b/src/Squidex.Infrastructure/ValueStopwatch.cs @@ -0,0 +1,56 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Diagnostics; + +namespace Squidex.Infrastructure +{ + public struct ValueStopwatch + { + private const long TicksPerMillisecond = 10000; + private const long TicksPerSecond = TicksPerMillisecond * 1000; + + private static double tickFrequency; + + private readonly long startTime; + + static ValueStopwatch() + { + if (Stopwatch.IsHighResolution) + { + tickFrequency = (double)TicksPerSecond / Stopwatch.Frequency; + } + } + + public ValueStopwatch(long startTime) + { + this.startTime = startTime; + } + + public static ValueStopwatch StartNew() + { + return new ValueStopwatch(Stopwatch.GetTimestamp()); + } + + public long Stop() + { + var elapsed = Stopwatch.GetTimestamp() - startTime; + + if (elapsed < 0) + { + return elapsed; + } + + if (Stopwatch.IsHighResolution) + { + elapsed = unchecked((long)(elapsed * tickFrequency)); + } + + return elapsed / TicksPerMillisecond; + } + } +} diff --git a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index 165dbe519..f5abee611 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs @@ -19,7 +19,6 @@ using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.GraphQL; using Squidex.Infrastructure.Commands; -using Squidex.Infrastructure.Reflection; using Squidex.Pipeline; namespace Squidex.Areas.Api.Controllers.Contents diff --git a/src/Squidex/Config/Orleans/SiloWrapper.cs b/src/Squidex/Config/Orleans/SiloWrapper.cs index 85ad4f0b9..81077508c 100644 --- a/src/Squidex/Config/Orleans/SiloWrapper.cs +++ b/src/Squidex/Config/Orleans/SiloWrapper.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using System.Diagnostics; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -144,19 +143,18 @@ namespace Squidex.Config.Orleans public void Initialize() { - var startTime = Stopwatch.GetTimestamp(); + var watch = ValueStopwatch.StartNew(); try { silo.Value.StartAsync().Wait(); } finally { - var endTime = Stopwatch.GetTimestamp(); - var elapsed = endTime - startTime; + var elapsedMs = watch.Stop(); log.LogInformation(w => w .WriteProperty("message", "Silo started") - .WriteProperty("elapsedMs", elapsed)); + .WriteProperty("elapsedMs", elapsedMs)); } } diff --git a/src/Squidex/Pipeline/ApiCostsFilter.cs b/src/Squidex/Pipeline/ApiCostsFilter.cs index 24b377630..1f9797b2c 100644 --- a/src/Squidex/Pipeline/ApiCostsFilter.cs +++ b/src/Squidex/Pipeline/ApiCostsFilter.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Squidex.Domain.Apps.Entities.Apps.Services; +using Squidex.Infrastructure; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.UsageTracking; @@ -61,7 +62,7 @@ namespace Squidex.Pipeline } } - var startTime = Stopwatch.GetTimestamp(); + var watch = ValueStopwatch.StartNew(); try { @@ -69,10 +70,9 @@ namespace Squidex.Pipeline } finally { - var endTime = Stopwatch.GetTimestamp(); - var elapsed = endTime - startTime; + var elapsedMs = watch.Stop(); - await usageTracker.TrackAsync(appFeature.App.Id.ToString(), FilterDefinition.Weight, elapsed); + await usageTracker.TrackAsync(appFeature.App.Id.ToString(), FilterDefinition.Weight, elapsedMs); } } else diff --git a/src/Squidex/Pipeline/LocalCacheMiddleware.cs b/src/Squidex/Pipeline/LocalCacheMiddleware.cs index 52567997a..064e6a520 100644 --- a/src/Squidex/Pipeline/LocalCacheMiddleware.cs +++ b/src/Squidex/Pipeline/LocalCacheMiddleware.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Squidex.Infrastructure; diff --git a/src/Squidex/Pipeline/RequestLogPerformanceMiddleware.cs b/src/Squidex/Pipeline/RequestLogPerformanceMiddleware.cs index 7092a185c..19e706467 100644 --- a/src/Squidex/Pipeline/RequestLogPerformanceMiddleware.cs +++ b/src/Squidex/Pipeline/RequestLogPerformanceMiddleware.cs @@ -5,9 +5,9 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Diagnostics; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Squidex.Infrastructure; using Squidex.Infrastructure.Log; namespace Squidex.Pipeline @@ -23,7 +23,7 @@ namespace Squidex.Pipeline public async Task InvokeAsync(HttpContext context, RequestDelegate next) { - var startTime = Stopwatch.GetTimestamp(); + var watch = ValueStopwatch.StartNew(); using (Profiler.StartSession()) { @@ -33,14 +33,13 @@ namespace Squidex.Pipeline } finally { - var endTime = Stopwatch.GetTimestamp(); - var elapsed = endTime - startTime; + var elapsedMs = watch.Stop(); log.LogInformation(w => { Profiler.Session?.Write(w); - w.WriteProperty("elapsedRequestMs", elapsed); + w.WriteProperty("elapsedRequestMs", elapsedMs); }); } } diff --git a/tests/Squidex.Infrastructure.Tests/Orleans/JsonExternalSerializerTests.cs b/tests/Squidex.Infrastructure.Tests/Orleans/JsonExternalSerializerTests.cs index 236e0c25b..526d3ffc9 100644 --- a/tests/Squidex.Infrastructure.Tests/Orleans/JsonExternalSerializerTests.cs +++ b/tests/Squidex.Infrastructure.Tests/Orleans/JsonExternalSerializerTests.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; +using System.IO; using FakeItEasy; using Orleans.Serialization; using Xunit; @@ -48,28 +49,29 @@ namespace Squidex.Infrastructure.Orleans { var value = new J>(new List { 1, 2, 3 }); - var writtenLength = 0; - var writtenBuffer = (byte[])null; + var buffer = new MemoryStream(); var writer = A.Fake(); var writerContext = new SerializationContext(null) { StreamWriter = writer }; - A.CallTo(() => writer.Write(A.Ignored)) - .Invokes(new Action(x => writtenLength = x)); - - A.CallTo(() => writer.Write(A.Ignored)) - .Invokes(new Action(x => writtenBuffer = x)); + A.CallTo(() => writer.Write(A.Ignored, A.Ignored, A.Ignored)) + .Invokes(new Action(buffer.Write)); + A.CallTo(() => writer.CurrentOffset) + .ReturnsLazily(x => (int)buffer.Position); J.Serialize(value, writerContext, value.GetType()); + buffer.Position = 0; + var reader = A.Fake(); var readerContext = new DeserializationContext(null) { StreamReader = reader }; - A.CallTo(() => reader.ReadInt()) - .Returns(writtenLength); - - A.CallTo(() => reader.ReadBytes(writtenLength)) - .Returns(writtenBuffer); + A.CallTo(() => reader.ReadByteArray(A.Ignored, A.Ignored, A.Ignored)) + .Invokes(new Action((b, o, l) => buffer.Read(b, o, l))); + A.CallTo(() => reader.CurrentPosition) + .ReturnsLazily(x => (int)buffer.Position); + A.CallTo(() => reader.Length) + .ReturnsLazily(x => (int)buffer.Length); var copy = (J>)J.Deserialize(value.GetType(), readerContext);