Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit b5d9448

Browse files
committed
Merge branch 'master' of github.com:ServiceStack/ServiceStack.Text
2 parents c56d3a6 + 8e83be0 commit b5d9448

File tree

9 files changed

+1271
-2
lines changed

9 files changed

+1271
-2
lines changed

benchmarks/ServiceStack.Text.VersionCompareBenchmarks/JsonSerializationBenchmarks.cs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using ServiceStack.Text;
66
using ServiceStack.Text.Tests.DynamicModels;
77
using ServiceStack.Text.Json;
8+
using StackExchange.Profiling;
89

910
namespace ServiceStack.Text.Benchmarks
1011
{
@@ -101,18 +102,46 @@ static JsonSerializationBenchmarks()
101102
stringTypeJson = JsonSerializer.SerializeToString<StringType>(StringType.Create());
102103
}
103104

104-
[Benchmark]
105+
[Benchmark(Description = "Deserialize Json: class with builtin types")]
105106
public void DeserializeJsonCommonTypes()
106107
{
107108
var result = JsonSerializer.DeserializeFromString<ModelWithCommonTypes>(commonTypesModelJson);
108109
}
109110

110-
[Benchmark]
111+
[Benchmark(Description = "Deserialize Json: class with 10 string properties")]
111112
public void DeserializeStringType()
112113
{
113114
var result = JsonSerializer.DeserializeFromString<StringType>(stringTypeJson);
114115
}
115116

117+
[Benchmark(Description = "Deserialize Json: Complex MiniProfiler")]
118+
public MiniProfiler ComplexDeserializeServiceStack() => ServiceStack.Text.JsonSerializer.DeserializeFromString<MiniProfiler>(_complexProfilerJson);
116119

120+
private static readonly MiniProfiler _complexProfiler = GetComplexProfiler();
121+
private static readonly string _complexProfilerJson = _complexProfiler.ToJson();
122+
123+
private static MiniProfiler GetComplexProfiler()
124+
{
125+
var mp = new MiniProfiler("Complex");
126+
for (var i = 0; i < 50; i++)
127+
{
128+
using (mp.Step("Step " + i))
129+
{
130+
for (var j = 0; j < 50; j++)
131+
{
132+
using (mp.Step("SubStep " + j))
133+
{
134+
for (var k = 0; k < 50; k++)
135+
{
136+
using (mp.CustomTiming("Custom " + k, "YOLO!"))
137+
{
138+
}
139+
}
140+
}
141+
}
142+
}
143+
}
144+
return mp;
145+
}
117146
}
118147
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Runtime.Serialization;
3+
4+
namespace StackExchange.Profiling
5+
{
6+
/// <summary>
7+
/// A client timing probe
8+
/// </summary>
9+
[DataContract]
10+
public class ClientTiming
11+
{
12+
/// <summary>
13+
/// Gets or sets the name.
14+
/// </summary>
15+
[DataMember(Order = 1)]
16+
public string Name { get; set; }
17+
18+
/// <summary>
19+
/// Gets or sets the start.
20+
/// </summary>
21+
[DataMember(Order = 2)]
22+
public decimal Start { get; set; }
23+
24+
/// <summary>
25+
/// Gets or sets the duration.
26+
/// </summary>
27+
[DataMember(Order = 3)]
28+
public decimal Duration { get; set; }
29+
30+
/// <summary>
31+
/// Unique Identifier used for sql storage.
32+
/// </summary>
33+
/// <remarks>Not set unless storing in Sql</remarks>
34+
public Guid Id { get; set; }
35+
36+
/// <summary>
37+
/// Used for sql storage
38+
/// </summary>
39+
/// <remarks>Not set unless storing in Sql</remarks>
40+
public Guid MiniProfilerId { get; set; }
41+
}
42+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Runtime.Serialization;
5+
using System.Text;
6+
7+
namespace StackExchange.Profiling
8+
{
9+
/// <summary>
10+
/// Times collected from the client
11+
/// </summary>
12+
[DataContract]
13+
public class ClientTimings
14+
{
15+
private const string TimingPrefix = "clientPerformance[timing][";
16+
private const string ProbesPrefix = "clientProbes[";
17+
18+
/// <summary>
19+
/// Gets or sets the list of client side timings
20+
/// </summary>
21+
[DataMember(Order = 2)]
22+
public List<ClientTiming> Timings { get; set; }
23+
24+
/// <summary>
25+
/// Gets or sets the redirect count.
26+
/// </summary>
27+
[DataMember(Order = 1)]
28+
public int RedirectCount { get; set; }
29+
30+
/// <summary>
31+
/// Returns null if there is not client timing stuff
32+
/// </summary>
33+
/// <param name="form">The form to transform to a <see cref="ClientTiming"/>.</param>
34+
public static ClientTimings FromForm(IDictionary<string, string> form)
35+
{
36+
ClientTimings timing = null;
37+
// AJAX requests won't have client timings
38+
if (!form.ContainsKey(TimingPrefix + "navigationStart]")) return timing;
39+
long.TryParse(form[TimingPrefix + "navigationStart]"], out long navigationStart);
40+
if (navigationStart > 0)
41+
{
42+
var timings = new List<ClientTiming>();
43+
timing = new ClientTimings();
44+
int.TryParse(form["clientPerformance[navigation][redirectCount]"], out int redirectCount);
45+
timing.RedirectCount = redirectCount;
46+
47+
var clientPerf = new Dictionary<string, ClientTiming>();
48+
var clientProbes = new Dictionary<int, ClientTiming>();
49+
50+
foreach (string key in
51+
form.Keys.OrderBy(i => i.IndexOf("Start]", StringComparison.Ordinal) > 0 ? "_" + i : i))
52+
{
53+
if (key.StartsWith(TimingPrefix, StringComparison.Ordinal))
54+
{
55+
long.TryParse(form[key], out long val);
56+
val -= navigationStart;
57+
58+
string parsedName = key.Substring(
59+
TimingPrefix.Length, (key.Length - 1) - TimingPrefix.Length);
60+
61+
// just ignore stuff that is negative ... not relevant
62+
if (val > 0)
63+
{
64+
if (parsedName.EndsWith("Start", StringComparison.Ordinal))
65+
{
66+
var shortName = parsedName.Substring(0, parsedName.Length - 5);
67+
clientPerf[shortName] = new ClientTiming
68+
{
69+
Duration = -1,
70+
Name = parsedName,
71+
Start = val
72+
};
73+
}
74+
else if (parsedName.EndsWith("End", StringComparison.Ordinal))
75+
{
76+
var shortName = parsedName.Substring(0, parsedName.Length - 3);
77+
if (clientPerf.TryGetValue(shortName, out var t))
78+
{
79+
t.Duration = val - t.Start;
80+
t.Name = shortName;
81+
}
82+
}
83+
else
84+
{
85+
clientPerf[parsedName] = new ClientTiming { Name = parsedName, Start = val, Duration = -1 };
86+
}
87+
}
88+
}
89+
90+
if (key.StartsWith(ProbesPrefix, StringComparison.Ordinal))
91+
{
92+
int endBracketIndex = key.IndexOf(']');
93+
if (endBracketIndex > 0 && int.TryParse(key.Substring(ProbesPrefix.Length, endBracketIndex - ProbesPrefix.Length), out int probeId))
94+
{
95+
if (!clientProbes.TryGetValue(probeId, out var t))
96+
{
97+
t = new ClientTiming();
98+
clientProbes.Add(probeId, t);
99+
}
100+
101+
if (key.EndsWith("[n]", StringComparison.Ordinal))
102+
{
103+
t.Name = form[key];
104+
}
105+
106+
if (key.EndsWith("[d]", StringComparison.Ordinal))
107+
{
108+
long.TryParse(form[key], out long val);
109+
if (val > 0)
110+
{
111+
t.Start = val - navigationStart;
112+
}
113+
}
114+
}
115+
}
116+
}
117+
118+
foreach (var group in clientProbes
119+
.Values.OrderBy(p => p.Name)
120+
.GroupBy(p => p.Name))
121+
{
122+
ClientTiming current = null;
123+
foreach (var item in group)
124+
{
125+
if (current == null)
126+
{
127+
current = item;
128+
}
129+
else
130+
{
131+
current.Duration = item.Start - current.Start;
132+
timings.Add(current);
133+
current = null;
134+
}
135+
}
136+
}
137+
138+
foreach (var item in clientPerf.Values)
139+
{
140+
item.Name = SentenceCase(item.Name);
141+
}
142+
143+
timings.AddRange(clientPerf.Values);
144+
timing.Timings = timings.OrderBy(t => t.Start).ToList();
145+
}
146+
147+
return timing;
148+
}
149+
150+
private static string SentenceCase(string value)
151+
{
152+
var sb = new StringBuilder();
153+
for (int i = 0; i < value.Length; i++)
154+
{
155+
if (i == 0)
156+
{
157+
sb.Append(char.ToUpper(value[0]));
158+
continue;
159+
}
160+
161+
if (value[i] == char.ToUpper(value[i]))
162+
{
163+
sb.Append(' ');
164+
}
165+
166+
sb.Append(value[i]);
167+
}
168+
169+
return sb.ToString();
170+
}
171+
}
172+
}

0 commit comments

Comments
 (0)