Commit af1bc2a0 authored by Marc Gravell's avatar Marc Gravell

split profiling documentation for v1/v2

parent 4ccccdb5
...@@ -91,6 +91,27 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{00CA0876-DA9 ...@@ -91,6 +91,27 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{00CA0876-DA9
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "toys", "toys", "{E25031D3-5C64-430D-B86F-697B66816FD8}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "toys", "toys", "{E25031D3-5C64-430D-B86F-697B66816FD8}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{153A10E4-E668-41AD-9E0F-6785CE7EED66}"
ProjectSection(SolutionItems) = preProject
docs\Basics.md = docs\Basics.md
docs\Configuration.md = docs\Configuration.md
docs\Events.md = docs\Events.md
docs\ExecSync.md = docs\ExecSync.md
docs\index.md = docs\index.md
docs\KeysScan.md = docs\KeysScan.md
docs\KeysValues.md = docs\KeysValues.md
docs\PipelinesMultiplexers.md = docs\PipelinesMultiplexers.md
docs\Profiling v1.md = docs\Profiling v1.md
docs\Profiling v2.md = docs\Profiling v2.md
docs\Profiling.md = docs\Profiling.md
docs\PubSubOrder.md = docs\PubSubOrder.md
docs\ReleaseNotes.md = docs\ReleaseNotes.md
docs\Scripting.md = docs\Scripting.md
docs\Testing.md = docs\Testing.md
docs\Timeouts.md = docs\Timeouts.md
docs\Transactions.md = docs\Transactions.md
EndProjectSection
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
......
Profiling
===
StackExchange.Redis exposes a handful of methods and types to enable performance profiling. Due to its asynchronous and multiplexing
behavior profiling is a somewhat complicated topic.
Interfaces
---
The profiling interface is composed of `IProfiler`, `ConnectionMultiplexer.RegisterProfiler(IProfiler)`, `ConnectionMultiplexer.BeginProfiling(object)`,
`ConnectionMultiplexer.FinishProfiling(object)`, and `IProfiledCommand`.
You register a single `IProfiler` with a `ConnectionMultiplexer` instance, it cannot be changed. You begin profiling for a given context (ie. Thread,
Http Request, and so on) by calling `BeginProfiling(object)`, and finish by calling `FinishProfiling(object)`. `FinishProfiling(object)` returns
a collection of `IProfiledCommand`s which contain timing information for all commands sent to redis by the configured `ConnectionMultiplexer` between
the `(Begin|Finish)Profiling` calls with the given context.
What "context" object should be used is application specific.
Available Timings
---
StackExchange.Redis exposes information about:
- The redis server involved
- The redis DB being queried
- The redis command run
- The flags used to route the command
- The initial creation time of a command
- How long it took to enqueue the command
- How long it took to send the command, after it was enqueued
- How long it took the response from redis to be received, after the command was sent
- How long it took for the response to be processed, after it was received
- If the command was sent in response to a cluster ASK or MOVED response
- If so, what the original command was
`TimeSpan`s are high resolution, if supported by the runtime. `DateTime`s are only as precise as `DateTime.UtcNow`.
Choosing Context
---
Due to StackExchange.Redis's asynchronous interface, profiling requires outside assistance to group related commands together. This is achieved
by providing context objects when you start and end profiling (via the `BeginProfiling(object)` & `FinishProfiling(object)` methods), and when a
command is sent (via the `IProfiler` interface's `GetContext()` method).
A toy example of associating commands issued from many different threads together
```C#
class ToyProfiler : IProfiler
{
public ConcurrentDictionary<Thread, object> Contexts = new ConcurrentDictionary<Thread, object>();
public object GetContext()
{
object ctx;
if(!Contexts.TryGetValue(Thread.CurrentThread, out ctx)) ctx = null;
return ctx;
}
}
// ...
ConnectionMultiplexer conn = /* initialization */;
var profiler = new ToyProfiler();
var thisGroupContext = new object();
conn.RegisterProfiler(profiler);
var threads = new List<Thread>();
for (var i = 0; i < 16; i++)
{
var db = conn.GetDatabase(i);
var thread =
new Thread(
delegate()
{
var threadTasks = new List<Task>();
for (var j = 0; j < 1000; j++)
{
var task = db.StringSetAsync("" + j, "" + j);
threadTasks.Add(task);
}
Task.WaitAll(threadTasks.ToArray());
}
);
profiler.Contexts[thread] = thisGroupContext;
threads.Add(thread);
}
conn.BeginProfiling(thisGroupContext);
threads.ForEach(thread => thread.Start());
threads.ForEach(thread => thread.Join());
IEnumerable<IProfiledCommand> timings = conn.FinishProfiling(thisGroupContext);
```
At the end, `timings` will contain 16,000 `IProfiledCommand` objects - one for each command issued to redis.
If instead you did the following:
```C#
ConnectionMultiplexer conn = /* initialization */;
var profiler = new ToyProfiler();
conn.RegisterProfiler(profiler);
var threads = new List<Thread>();
var perThreadTimings = new ConcurrentDictionary<Thread, List<IProfiledCommand>>();
for (var i = 0; i < 16; i++)
{
var db = conn.GetDatabase(i);
var thread =
new Thread(
delegate()
{
var threadTasks = new List<Task>();
conn.BeginProfiling(Thread.CurrentThread);
for (var j = 0; j < 1000; j++)
{
var task = db.StringSetAsync("" + j, "" + j);
threadTasks.Add(task);
}
Task.WaitAll(threadTasks.ToArray());
perThreadTimings[Thread.CurrentThread] = conn.FinishProfiling(Thread.CurrentThread).ToList();
}
);
profiler.Contexts[thread] = thread;
threads.Add(thread);
}
threads.ForEach(thread => thread.Start());
threads.ForEach(thread => thread.Join());
```
`perThreadTimings` would end up with 16 entries of 1,000 `IProfilingCommand`s, keyed by the `Thread` the issued them.
Moving away from toy examples, here's how you can profile StackExchange.Redis in an MVC5 application.
First register the following `IProfiler` against your `ConnectionMultiplexer`:
```C#
public class RedisProfiler : IProfiler
{
const string RequestContextKey = "RequestProfilingContext";
public object GetContext()
{
var ctx = HttpContext.Current;
if (ctx == null) return null;
return ctx.Items[RequestContextKey];
}
public object CreateContextForCurrentRequest()
{
var ctx = HttpContext.Current;
if (ctx == null) return null;
object ret;
ctx.Items[RequestContextKey] = ret = new object();
return ret;
}
}
```
Then, add the following to your Global.asax.cs file:
```C#
protected void Application_BeginRequest()
{
var ctxObj = RedisProfiler.CreateContextForCurrentRequest();
if (ctxObj != null)
{
RedisConnection.BeginProfiling(ctxObj);
}
}
protected void Application_EndRequest()
{
var ctxObj = RedisProfiler.GetContext();
if (ctxObj != null)
{
var timings = RedisConnection.FinishProfiling(ctxObj);
// do what you will with `timings` here
}
}
```
This implementation will group all redis commands, including `async/await`-ed ones, with the http request that initiated them.
\ No newline at end of file
Profiling
===
StackExchange.Redis exposes a handful of methods and types to enable performance profiling. Due to its asynchronous and multiplexing
behavior profiling is a somewhat complicated topic.
Interfaces
---
The profiling API is composed of `ProfilingSession`, `ConnectionMultiplexer.RegisterProfiler(Func<ProfilingSession>)`,
`ProfilingSession.FinishProfiling()`, and `IProfiledCommand`.
You register a callback (`Func<ProfilingSession>`) that provides an ambient `ProfilingSession` with a `ConnectionMultiplexer` instance. When needed,
the library invokes this callback, and *if* a non-null session is returned: operations are attached to that session. Calling `FinishProfiling` on
a particular profiling sesssion returns a collection of `IProfiledCommand`s which contain timing information for all commands sent to redis by the
configured `ConnectionMultiplexer`. It is the callback's responsibility to maintain any state required to track individual sessions.
Available Timings
---
StackExchange.Redis exposes information about:
- The redis server involved
- The redis DB being queried
- The redis command run
- The flags used to route the command
- The initial creation time of a command
- How long it took to enqueue the command
- How long it took to send the command, after it was enqueued
- How long it took the response from redis to be received, after the command was sent
- How long it took for the response to be processed, after it was received
- If the command was sent in response to a cluster ASK or MOVED response
- If so, what the original command was
`TimeSpan`s are high resolution, if supported by the runtime. `DateTime`s are only as precise as `DateTime.UtcNow`.
Example profilers
---
Due to StackExchange.Redis's asynchronous interface, profiling requires outside assistance to group related commands together.
This is achieved by providing the desired `ProfilingSession` object via the callback, and (later) calling `FinishProfiling()` on that session.
Probably the most useful general-purpose session-provider is one that provides session automatically and works between `async` calls; this is simply:
```c#
class AsyncLocalProfiler
{
private readonly AsyncLocal<ProfilingSession> perThreadSession = new AsyncLocal<ProfilingSession>();
public ProfilingSession GetSession()
{
var val = perThreadSession.Value;
if (val == null)
{
perThreadSession.Value = val = new ProfilingSession();
}
return val;
}
}
...
var profiler = new AsyncLocalProfiler();
multiplexer.RegisterProfiler(profiler.GetSession);
```
This will automatically create a profiling session per async-context (re-using the existing session if there is one). At the end of some unit of work, the
calling code can use `var commands = profiler.GetSession().FinishProfiling();` to get the operations performed and timings data.
---
A toy example of associating commands issued from many different threads together (while still allowing unrelated work not to be profiled)
1.*
```C#
class ToyProfiler
{
// note this won't work over "await" boundaries; "AsyncLocal" would be necessary there
private readonly ThreadLocal<ProfilingSession> perThreadSession = new ThreadLocal<ProfilingSession>();
public ProfilingSession PerThreadSession
{
get => perThreadSession.Value;
set => perThreadSession.Value = value;
}
}
// ...
ConnectionMultiplexer conn = /* initialization */;
var profiler = new ToyProfiler();
var sharedSession = new ProfilingSession();
conn.RegisterProfiler(() => profiler.PerThreadSession);
var threads = new List<Thread>();
for (var i = 0; i < 16; i++)
{
var db = conn.GetDatabase(i);
var thread =
new Thread(
delegate()
{
// set each thread to share a session
profiler.PerThreadSession = sharedSession;
var threadTasks = new List<Task>();
for (var j = 0; j < 1000; j++)
{
var task = db.StringSetAsync("" + j, "" + j);
threadTasks.Add(task);
}
Task.WaitAll(threadTasks.ToArray());
}
);
threads.Add(thread);
}
threads.ForEach(thread => thread.Start());
threads.ForEach(thread => thread.Join());
var timings = sharedSession.FinishProfiling();
```
At the end, `timings` will contain 16,000 `IProfiledCommand` objects - one for each command issued to redis.
If instead you did the following:
```C#
ConnectionMultiplexer conn = /* initialization */;
var profiler = new ToyProfiler();
conn.RegisterProfiler(() => profiler.PerThreadSession);
var threads = new List<Thread>();
var perThreadTimings = new ConcurrentDictionary<Thread, List<IProfiledCommand>>();
for (var i = 0; i < 16; i++)
{
var db = conn.GetDatabase(i);
var thread =
new Thread(
delegate()
{
var threadTasks = new List<Task>();
profiler.PerThreadSession = new ProfilingSession();
for (var j = 0; j < 1000; j++)
{
var task = db.StringSetAsync("" + j, "" + j);
threadTasks.Add(task);
}
Task.WaitAll(threadTasks.ToArray());
perThreadTimings[Thread.CurrentThread] = profiler.PerThreadSession.FinishProfiling().ToList();
}
);
threads.Add(thread);
}
threads.ForEach(thread => thread.Start());
threads.ForEach(thread => thread.Join());
```
`perThreadTimings` would end up with 16 entries of 1,000 `IProfilingCommand`s, keyed by the `Thread` the issued them.
Moving away from toy examples, here's how you can profile StackExchange.Redis in an MVC5 application.
First register the following `IProfiler` against your `ConnectionMultiplexer`:
```C#
public class RedisProfiler
{
const string RequestContextKey = "RequestProfilingContext";
public ProfilingSession GetSession()
{
var ctx = HttpContext.Current;
if (ctx == null) return null;
return (ProfilingSession)ctx.Items[RequestContextKey];
}
public void CreateSessionForCurrentRequest()
{
var ctx = HttpContext.Current;
if (ctx != null)
{
ctx.Items[RequestContextKey] = new ProfilingSession();
}
}
}
```
Then, add the following to your Global.asax.cs file:
```C#
protected void Application_BeginRequest()
{
RedisProfiler.CreateSessionForCurrentRequest();
}
protected void Application_EndRequest()
{
var session = RedisProfiler.GetContext();
if (session != null)
{
var timings = session.FinishProfiling();
// do what you will with `timings` here
}
}
```
This implementation will group all redis commands, including `async/await`-ed ones, with the http request that initiated them.
\ No newline at end of file
Profiling Profiling
=== ===
StackExchange.Redis exposes a handful of methods and types to enable performance profiling. Due to its asynchronous and multiplexing The profiling API has undergone breaking changes between 1.* and 2.*; in 1.*; in particular,
behavior profiling is a somewhat complicated topic. the `object GetContext()` API was unintuitive for consumers and expensive for the library (due to book-keeping). The API in 2.* is much
simpler and more "obvious". This is a breaking change.
Interfaces [Profiling v1.md](Profiling in 1.*)
---
The profiling interface is composed of `IProfiler`, `ConnectionMultiplexer.RegisterProfiler(IProfiler)`, `ConnectionMultiplexer.BeginProfiling(object)`, [Profiling v2.md](Profiling in 2.*)
`ConnectionMultiplexer.FinishProfiling(object)`, and `IProfiledCommand`.
You register a single `IProfiler` with a `ConnectionMultiplexer` instance, it cannot be changed. You begin profiling for a given context (ie. Thread,
Http Request, and so on) by calling `BeginProfiling(object)`, and finish by calling `FinishProfiling(object)`. `FinishProfiling(object)` returns
a collection of `IProfiledCommand`s which contain timing information for all commands sent to redis by the configured `ConnectionMultiplexer` between
the `(Begin|Finish)Profiling` calls with the given context.
What "context" object should be used is application specific.
Available Timings
---
StackExchange.Redis exposes information about:
- The redis server involved
- The redis DB being queried
- The redis command run
- The flags used to route the command
- The initial creation time of a command
- How long it took to enqueue the command
- How long it took to send the command, after it was enqueued
- How long it took the response from redis to be received, after the command was sent
- How long it took for the response to be processed, after it was received
- If the command was sent in response to a cluster ASK or MOVED response
- If so, what the original command was
`TimeSpan`s are high resolution, if supported by the runtime. `DateTime`s are only as precise as `DateTime.UtcNow`.
Choosing Context
---
Due to StackExchange.Redis's asynchronous interface, profiling requires outside assistance to group related commands together. This is achieved
by providing context objects when you start and end profiling (via the `BeginProfiling(object)` & `FinishProfiling(object)` methods), and when a
command is sent (via the `IProfiler` interface's `GetContext()` method).
A toy example of associating commands issued from many different threads together
```C#
class ToyProfiler : IProfiler
{
public ConcurrentDictionary<Thread, object> Contexts = new ConcurrentDictionary<Thread, object>();
public object GetContext()
{
object ctx;
if(!Contexts.TryGetValue(Thread.CurrentThread, out ctx)) ctx = null;
return ctx;
}
}
// ...
ConnectionMultiplexer conn = /* initialization */;
var profiler = new ToyProfiler();
var thisGroupContext = new object();
conn.RegisterProfiler(profiler);
var threads = new List<Thread>();
for (var i = 0; i < 16; i++)
{
var db = conn.GetDatabase(i);
var thread =
new Thread(
delegate()
{
var threadTasks = new List<Task>();
for (var j = 0; j < 1000; j++)
{
var task = db.StringSetAsync("" + j, "" + j);
threadTasks.Add(task);
}
Task.WaitAll(threadTasks.ToArray());
}
);
profiler.Contexts[thread] = thisGroupContext;
threads.Add(thread);
}
conn.BeginProfiling(thisGroupContext);
threads.ForEach(thread => thread.Start());
threads.ForEach(thread => thread.Join());
IEnumerable<IProfiledCommand> timings = conn.FinishProfiling(thisGroupContext);
```
At the end, `timings` will contain 16,000 `IProfiledCommand` objects - one for each command issued to redis.
If instead you did the following:
```C#
ConnectionMultiplexer conn = /* initialization */;
var profiler = new ToyProfiler();
conn.RegisterProfiler(profiler);
var threads = new List<Thread>();
var perThreadTimings = new ConcurrentDictionary<Thread, List<IProfiledCommand>>();
for (var i = 0; i < 16; i++)
{
var db = conn.GetDatabase(i);
var thread =
new Thread(
delegate()
{
var threadTasks = new List<Task>();
conn.BeginProfiling(Thread.CurrentThread);
for (var j = 0; j < 1000; j++)
{
var task = db.StringSetAsync("" + j, "" + j);
threadTasks.Add(task);
}
Task.WaitAll(threadTasks.ToArray());
perThreadTimings[Thread.CurrentThread] = conn.FinishProfiling(Thread.CurrentThread).ToList();
}
);
profiler.Contexts[thread] = thread;
threads.Add(thread);
}
threads.ForEach(thread => thread.Start());
threads.ForEach(thread => thread.Join());
```
`perThreadTimings` would end up with 16 entries of 1,000 `IProfilingCommand`s, keyed by the `Thread` the issued them.
Moving away from toy examples, here's how you can profile StackExchange.Redis in an MVC5 application.
First register the following `IProfiler` against your `ConnectionMultiplexer`:
```C#
public class RedisProfiler : IProfiler
{
const string RequestContextKey = "RequestProfilingContext";
public object GetContext()
{
var ctx = HttpContext.Current;
if (ctx == null) return null;
return ctx.Items[RequestContextKey];
}
public object CreateContextForCurrentRequest()
{
var ctx = HttpContext.Current;
if (ctx == null) return null;
object ret;
ctx.Items[RequestContextKey] = ret = new object();
return ret;
}
}
```
Then, add the following to your Global.asax.cs file:
```C#
protected void Application_BeginRequest()
{
var ctxObj = RedisProfiler.CreateContextForCurrentRequest();
if (ctxObj != null)
{
RedisConnection.BeginProfiling(ctxObj);
}
}
protected void Application_EndRequest()
{
var ctxObj = RedisProfiler.GetContext();
if (ctxObj != null)
{
var timings = RedisConnection.FinishProfiling(ctxObj);
// do what you will with `timings` here
}
}
```
This implementation will group all redis commands, including `async/await`-ed ones, with the http request that initiated them.
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment