.NET Library instrumentation SDK
Get started
Two different NuGet packages can be used to make the custom SDK available to your application. However, only one should be added to your project. Which NuGet package you should use depends on the installation method used for the .NET APM library. For an overview of the installation options, see Add a .NET service.
The SDK namespace provided by both NuGet packages is AppOptics.Instrumentation
.
SolarWindsAPM.Agent.Api NuGet package
If you install the .NET APM library using the Windows installer (SolarWindsAPM_DotNetAgent_Setup.exe
) or a Linux compressed archive (for example, solarwinds-apm-dotnet-agent-package.linux.x86_64.tgz
), SolarWinds recommends you use the SolarWindsAPM.Agent.Api NuGet package to add custom instrumentation to your .NET Framework or .NET 6+ application.
To use the custom SDK in your application code, add the SolarWindsAPM.Agent.Api NuGet package to your application's project file and reference the AppOptics.Instrumentation
namespace.
If you have custom SDK calls in your application, the .NET Library must be installed and enabled when your application is executed.
SolarWindsAPM.Agent NuGet package
The custom SDK is also part of the SolarWindsAPM.Agent NuGet package , which provides both manual and automatic instrumentation. If you install the .NET APM Library by adding the SolarWindsAPM.Agent
NuGet package to your project, the custom SDK will be available to use in your application immediately. Add custom instrumentation to your application by referencing the AppOptics.Instrumentation
namespace.
Configure the APM Library
If the application you are adding the custom instrumentation to is a Windows service or stand-alone application (for example, NON-IIS) you must ensure that you have enabled instrumentation for your application. For detailed instructions see Configure the .NET Library.
Customize out-of-box instrumentation
Customization involves adding hooks from our public SDK to your code so that you can take advantage of additional filtering capabilities on the dashboard, change how requests are traced, or capture additional information during the trace.
Starting a trace
Sometimes the default instrumentation might not capture traces on certain systems (custom web framework or console applications, for example), but traces can still be created by using the Trace.StartTrace() and Trace.EndTrace() SDK methods, and the optional Trace.WaitUntilAgentReady() call to ensure library readiness.
Also see the section tracing across external spans on how to use the Trace.StartTrace() method to continue a trace from an upstream service.
Custom spans
By default, the .NET Framework instrumentation creates a span for the code
itself. For example, IIS
, WCF-Service
, ado-mssql
and the .NET Core SDK Middleware
create a root span for requests, aspnet-core
. If these spans do not provide enough visibility, you can further divide your code into
sub-spans. How you segment your application modules and sub-system into spans is entirely up to you. For example, the default instrumentation might not recognize calls to external processes, so you
could use the SDK to manually create spans for them. Similarly,
a multi-threaded app can have individual spans for each of its
asynchronous operations. To create a custom span, identify the section
of code you would like to appear as a span, and wrap it in a pair of
entry and exit events as shown.
EntryTraceEvent event = Trace.CreateEntryEvent("my_span_name");
event.AddInfo("something", "interesting");
event.Report();
...
ExitTraceEvent event = Trace.CreateExitEvent("my_span_name");
event.Report();
Two ways to create a span
The attribute method of adding a custom span is only applicable to the .NET Library SDK.
If you want to create a custom span for a particular method, there are
two ways to do this. The first is to wrap it in a pair of entry and exit
events. But this technique becomes burdensome if the method is called
more than once and you want to trace each of those calls. Instead, you
can use the second method, which is the TraceAttribute
attribute class.
Attribute classes are a convenient way of associating declarative
information with program entities, in this case the method you want to
trace. The syntax for associating the TraceAttribute
with a method is
shown below. Try to declare the association as close to the method as possible, for example within the class just above the method
definition. TraceAttribute
requires one positional parameter, which must
be the method name. The method name also becomes the custom span
name. TraceAttribute
accepts to two optional named parameters. For more information, see
Backtraces and return values.
class MyClass {
[TraceAttribute("AnnotatedMethodcharRef", Backtrace = true, ReturnValue = true)]
public void AnnotatedMethodcharRef(ref char value)
{
// your method definition goes here
}
}
Tracing multi-threaded applications
The following steps are only applicable for .NET Framework 3.5 applications. For .NET Framework 4.6.2 and .NET 6.0+, the context is automatically propagated to child threads.
You might want to report events from background or child threads and associate them with the thread that spawned them. Assuming that a trace was already started in the parent thread, complete the following steps:
- Pass the trace context to the child thread using
TraceContext.GetCurrent()
. - Set the context within the child thread using
Trace.SetAsCurrent()
. - Mark the thread as asynchronous by calling
EntryTraceEvent.SetAsync()
on the entry event of the child span. - Clean up the thread by calling
TraceContext.ClearCurrentContext()
before returning it to the threadpool.
If the context needs to be passed to the exit event creation, TraceContext.GetCurrent()
must be called after the entry event is reported to
get the latest context. The latest context can then be passed to the exit event creation. SetAsCurrent()
must be called on the passed
context, and then the exit event can be created.
System.Threading.Thread myThread = new System.Threading.Thread(this.DoSomeWork1);
myThread.Start(TraceContext.GetCurrent());
public void DoSomeWork1(object data)
{
TraceContext currentContext = data as TraceContext;
if(currentContext != null)
{
currentContext.SetAsCurrent();
var asyncEntryEvent = Trace.CreateEntryEvent("my_span_name");
asyncEntryEvent.AddInfo("something", "interesting");
asyncEntryEvent.SetAsync();
asyncEntryEvent.Report();
// Code that needs to be traced
var exitEvent = Trace.CreateExitEvent("my_span_name");
exitEvent.AddInfo("something", "interesting");
exitEvent.Report();
TraceContext.ClearCurrent();
}
}
Tracing across external spans
While instrumenting your code, you might want to trace a request across processes or hosts. This can be done by passing the traceparent and tracestate between them.
- The client should call
Trace.GetCurrent()
, which returns the current context as a TraceContext object. If the request has been sampled, a TraceContext object is returned regardless of whether the request is being traced or not. If a request is being traced, the Traceparent property of the TraceContext object ends in01
. The .NET Library uses the trace context headers defined by the W3C Trace Context (https://www.w3.org/TR/trace-context/#trace-context-http-headers-format). - The client should then send the Traceparent and Tracestate values to the remote process by any means, but probably through the traceparent and tracestate HTTP header.
- The remote process should read the identifiers (traceparent and tracestate) and input it to
Trace.StartTrace()
. - When the remote process is done, it should end the trace using
Trace.EndTrace()
.
Collecting additional trace details
Any additional information you collect is presented on the trace details page.
Backtraces and return values
Backtraces and return values can be included when you are creating a
custom span for an individual function by way of the TraceAttribute
class. To include a backtrace in the entry event of the span, set the
backtrace parameter of the TraceAttribute
class to true
. To include a
return value in the exit event of the span, set ReturnValue
to true
.
Add info events
There are two reasons you might want to create an info event. The first
is to attach any meta data that might be of interest to you
during later analysis of a trace. The other reason is to take full
advantage of Traces Explorer filtering capability. You can classify
spans by attaching a pre-defined set of specified key/value pairs.
Then from the Traces Explorer, you can filter for traces with that span
type. See Special span types for more information. To add info to an
span, after creating the info event, call .AddInfo()
on it one or more
times as shown. In this case the information events are displayed on the
raw span data tab on the trace details page. If all of the key/value
pairs for special interpretation are present, the span type is
reflected in the span details tab.
InfoTraceEvent event = Trace.CreateInfoEvent("my_span_name");
event.AddInfo("something", "interesting");
event.AddInfo("something_else", "also_interesting");
event.Report();
Report errors
Create an error event for an exception, including a backtrace.
try {
// your code that might throw an exception goes here ...
} catch(exceptionType exception) {
Trace.ReportException(exception);
// the rest of your exception handler ...
}
Complete examples
The following examples use the .NET instrumentation SDK to illustrate custom instrumentation concepts such as starting a trace and adding spans for an application.
This demonstrates instrumenting a simple console application written in C#. Note that once the trace is started via SDK, any execution path through supported components will be auto-instrumented without needing extra SDK calls. The example code can be used in either a .NET Framework, .NET Core, or .NET application.
using AppOptics.Instrumentation;
namespace ConsoleExampleApp
{
class Program
{
private const string CONSOLE_SPAN_NAME = "console-example-app";
static void Main(string[] args)
{
// The Trace.WaitUntilAgentReady method is used to ensure that the library is ready before the
// console application is ran so that a trace will be generated.
// The timeout parameter is set to 2000 milliseconds, if the library is not ready in 2000 milliseconds
// the method will stop waiting and return false.
var agentReady = Trace.WaitUntilAgentReady(2000, out int statusCode);
// Debug logging if library ready returns false
if (!agentReady)
{
System.Console.WriteLine("Agent isn't ready. Status code returned: {0}", statusCode);
}
// Start the trace by creating a StartTraceEvent and reporting it
StartTraceEvent startTraceEvent = Trace.StartTrace(CONSOLE_SPAN_NAME);
// The optional key "TransactionName" can be added to the start trace event
// If a transaction name isn't specified, the transaction name is set to "custom_<start trace span name>"
startTraceEvent.AddInfo("TransactionName", "console_test_command");
startTraceEvent.Report();
// The transaction name can also be set anytime before the end of the trace using the Trace.SetTransactionName method
// Example: Trace.SetTransactionName("transaction_name");
// Note: Thread.Sleep is used in the console example application so that spans with different timings
// are displayed when viewing traces in SolarWinds Observability SaaS
System.Threading.Thread.Sleep(200);
ProcessCommand(args);
System.Threading.Thread.Sleep(100);
// End the trace by creating a EndTraceEvent and reporting it
// Note: Span name used for start trace event must match the span name for the end trace event
EndTraceEvent endTraceEvent = Trace.EndTrace(CONSOLE_SPAN_NAME);
endTraceEvent.Report();
}
public static void ProcessCommand(string[] args)
{
// The EntryTraceEvent and ExitTraceEvent can be used to create additional spans in the trace
// The span name of the EntryTraceEvent must match the span name of the ExitTraceEvent
EntryTraceEvent entryTraceEvent = Trace.CreateEntryEvent("command_one");
entryTraceEvent.Report();
// do command work
System.Threading.Thread.Sleep(500);
ExitTraceEvent exitTraceEvent = Trace.CreateExitEvent("command_one");
exitTraceEvent.Report();
}
}
}
This demonstrates propagating trace context between two simple console applications written in C# for .NET Core. The calling application uses StartTrace
to start tracing and TraceContext.GetCurrent
to get the current trace context. Then the caller passes the Traceparent and Tracestate from the TraceContext to the called external application.
using System;
using AppOptics.Instrumentation;
using System.Diagnostics;
using System.IO;
namespace coretestapp
{
public class Example
{
string spanName = "example-trace";
public void StartTrace()
{
System.Console.WriteLine("Starting trace");
StartTraceEvent startEvent = AppOptics.Instrumentation.Trace.StartTrace(spanName);
startEvent.Report();
}
public void EndTrace()
{
System.Console.WriteLine("Ending trace");
EndTraceEvent endEvent = AppOptics.Instrumentation.Trace.EndTrace(spanName);
endEvent.Report();
}
public void ExternalServiceSpan()
{
System.Console.WriteLine("\n****** Enter ExternalServiceSpan ***** \n");
System.Console.WriteLine("Starting span for ExternalServiceSpan");
string spanName = "example-span";
EntryTraceEvent eventEntry = AppOptics.Instrumentation.Trace.CreateEntryEvent(spanName);
eventEntry.Report();
// Launch external app and pass the current trace id
var traceContext = AppOptics.Instrumentation.Trace.GetCurrentTraceContext();
string traceparent;
string tracestate;
if (traceContext != null)
{
traceparent = traceContext.Traceparent;
tracestate = traceContext.Tracestate;
}
LaunchExternalApp(traceparent, tracestate);
System.Console.WriteLine("Ending span");
ExitTraceEvent eventExit = AppOptics.Instrumentation.Trace.CreateExitEvent(spanName);
eventExit.Report();
System.Console.WriteLine("\n****** Exit ExternalServiceSpan ***** \n");
}
public bool LaunchExternalApp(string traceparent, string tracestate)
{
// Path to external application directory
string path = Directory.GetCurrentDirectory();
#if DEBUG
path += @"\..\..\..\coretestappexternal\bin\Debug\netcoreapp3.1\";
#else
path += @"\..\..\..\coretestappexternal\bin\Release\netcoreapp3.1\";
#endif
ProcessStartInfo processStartInfo = new ProcessStartInfo();
processStartInfo.Arguments = path + "coretestappexternal.dll" + " " + traceparent + " " + tracestate;
processStartInfo.FileName = "dotnet.exe";
processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
processStartInfo.CreateNoWindow = true;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.WorkingDirectory = path;
int exitCode;
// Run the external process & wait for it to finish
try
{
Process process = Process.Start(processStartInfo);
// wait for process to exit
process.WaitForExit();
// retrieve application exit code
exitCode = process.ExitCode;
// when application exit with success process std output
if(exitCode == 0)
{
return true;
}
}
catch (Exception e)
{
Console.WriteLine("Exception: {0}.", e);
}
return false;
}
public void Run()
{
StartTrace();
ExternalServiceSpan();
EndTrace();
}
}
public class Program
{
private const int WAIT_AGENT_TIMEOUT = 5000; // Wait timeout for library to be ready
public static void Main(string[] args)
{
System.Console.WriteLine("Running application...");
// Wait until library is ready
System.Console.WriteLine("Waiting until agent is ready...");
bool bIsReady = AppOptics.Instrumentation.Trace.WaitUntilAgentReady(WAIT_AGENT_TIMEOUT);
if (!bIsReady)
{
System.Console.WriteLine("Agent is not ready. Exit application.");
return;
}
System.Console.WriteLine("Agent is ready.");
// Run example
Example test = new Example();
test.Run();
System.Console.WriteLine("Application has stopped.");
}
}
}
The called external application passes the received trace context to StartTrace
to add more spans to the trace in progress. On exit, it returns the updated trace context to the caller by writing it to standard output.
using System;
using System.Threading;
using AppOptics.Instrumentation;
namespace coretestappexternal
{
public class Program
{
private const int WAIT_AGENT_TIMEOUT = 5000; // Wait timeout for library to be ready
private static string spanName = "example-trace-external";
public static int Main(string[] args)
{
if (args.Length != 1)
{
System.Console.WriteLine("Invalid number of arguments. Expecting x-trace-id passed as argument.");
return -1;
}
System.Console.WriteLine("Running external application...");
// Extract x-trace-id as first argument for the program
string xTraceId = args[0];
System.Console.WriteLine("External application launched with trace id {0}.", xTraceId);
// Wait until library is ready
System.Console.WriteLine("Waiting until agent is ready...");
bool bIsReady = Trace.WaitUntilAgentReady(WAIT_AGENT_TIMEOUT);
if (!bIsReady)
{
System.Console.WriteLine("Agent is not ready. Exit external application.");
return -1;
}
System.Console.WriteLine("Agent is ready.");
// Continue trace using the provided x-trace-id
ContinueTraceEvent continueTraceEvent = Trace.ContinueTrace(spanName, xTraceId);
continueTraceEvent.Report();
// Do some work
int count = 5;
while(--count > 0)
{
Thread.Sleep(1000);
}
// End trace
EndTraceEvent endTraceEvent = Trace.EndTrace(spanName);
// Retrieve the x-trace-id to be returned to launching process
string returnXTraceId = endTraceEvent.ReportAndReturnTraceId();
// Write x-trace-id to standard output to be extracted by launching process
if (!string.IsNullOrEmpty(returnXTraceId))
{
System.Console.WriteLine("x-trace-id={0}", returnXTraceId);
}
System.Console.WriteLine("External application has stopped.");
return 0;
}
}
}
.NET SDK reference
The following methods are provided by the .NET instrumentation public
SDK. The SDK namespace is AppOptics.Instrumentation
.
Trace.StartTrace()
Method |
|
||||||||
---|---|---|---|---|---|---|---|---|---|
Description |
If a trace is already in progress, reporting the StartTraceEvent adds a span to the trace already started. If a trace isn’t already in progress, reporting the StartTraceEvent either starts a new trace, or if a valid traceparent and tracestate are provided the trace is continued. The reporting of the StartTraceEvent should typically be done when a new request enters your system. For the .NET Framework and .NET 6+, your incoming requests are usually fielded by a web server and so traces are initiated there by our instrumentation. If you do need to start or continue traces within your application code, the first span is created by the reporting of StartTraceEvent automatically. You don't need to add a separate entry event for it. |
||||||||
Returns |
An event that can be populated with key-value pairs and reported. |
||||||||
Parameters |
|
||||||||
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
||||||||
Example |
|
||||||||
Notes |
|
||||||||
Related |
Trace.EndTrace()
Method |
|
||||
---|---|---|---|---|---|
Description |
Ends a trace. |
||||
Returns |
An event that can be populated with key/value pairs and reported.
Similar to |
||||
Parameters |
|
||||
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
||||
Example |
|
||||
Notes |
|
||||
Related |
Trace.EndTraceHttpService()
Method | Trace.EndTraceHttpService() |
||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
Description | Ends a trace. | ||||||||||
Returns | An event that can be populated with key/value pairs and reported. Similar to Trace.StartTrace() , requests usually egress your application stack at your web server and so our instrumentation will take care of ending traces. But this method is needed for tracing across external spans. |
||||||||||
Parameters |
|
||||||||||
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
||||||||||
Example |
|
||||||||||
Notes |
|
||||||||||
Related |
Trace.CreateEntryEvent()
Method |
|
||
---|---|---|---|
Description |
Starts a new span. It's up to you, the application developer, to decide how to segment your application modules and sub-systems into spans. |
||
Returns |
An event that can be populated with key/value pairs and reported. |
||
Parameters |
|
||
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
||
Example |
|
||
Notes |
|
||
Related |
|
Trace.CreateExitEvent()
Method |
|
||
---|---|---|---|
Description |
Completes the specified span. |
||
Returns |
An event that can be populated with key/value pairs and reported. |
||
Parameters |
|
||
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
||
Example |
|
||
Notes |
|
||
Related |
Trace.CreateInfoEvent()
Method |
|
||
---|---|---|---|
Description |
Info events can be used to report information in between the entry and exit events of a span, particularly for special interpretation. |
||
Returns |
An event that can be populated with key/value pairs and reported. |
||
Parameters |
|
||
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
||
Example |
|
||
Notes |
|
||
Related |
Trace.ReportException()
Method |
|
---|---|
Description |
Reports an exception, including a backtrace. |
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
Example |
See report errors. |
Notes |
|
Trace.SetTransactionName()
Method |
|
||
---|---|---|---|
Description |
The .NET Library out-of-the-box instrumentation assigns a transaction name based on URL and Controller/Action values detected. However, you might want to override the transaction name to better describe your instrumented operation. If multiple transaction names are set on the same trace, then the last one is used. |
||
Returns |
A Boolean value indicating whether the setting of the transaction name was successful. |
||
Parameters |
|
||
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
||
Example |
|
||
Notes |
|
Trace.IsAgentAvailable()
Method | Trace.IsAgentAvailable()
|
---|---|
Description | Used to determine if the .NET Library is available. |
Returns | A Boolean value indicating whether the .NET Library is available. |
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
Trace.WaitUntilAgentReady(timeout)
Method |
|
||
---|---|---|---|
Description |
Blocks until the library is ready (has established a connection with the data collector) or the timeout is reached. |
||
Parameters |
|
||
Returns |
A Boolean value indicating if the .NET Library is ready. |
||
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
Trace.WaitUntilAgentReady(timeout, statusCode)
Method |
|
||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Description |
Blocks until the library is ready (has established a connection with the data collector) or the timeout is reached. |
||||||||||||||||
Parameters |
|
||||||||||||||||
Returns |
A Boolean value indicating whether the .NET Library is ready. |
||||||||||||||||
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
EntryTraceEvent.SetAsync()
Method |
|
---|---|
Description |
Declares the span initiated by the entry event as asynchronous. See Tracing multi-threaded applications. |
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
Example |
|
ExitTraceEvent.AddEdge()
Method |
|
||
---|---|---|---|
Description |
Associates an external span with the current trace. See Tracing across external spans. |
||
Parameters |
|
||
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
||
Example |
|
.AddInfo()
Method | .AddInfo()
|
---|---|
Description | Attaches information to events in the form of key/value pairs particularly for special interpretation, but also to attach any other desired information. |
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
Example | See add info events. |
Notes | Duplicate keys are ignored, and keys cannot be null. |
.AddBackTrace()
Method |
|
---|---|
Description |
Use this method to report the backtraces of a certain time frame of the application. This adds the backtrace of the current thread to the event. |
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
Example |
|
.Report()
Method |
|
---|---|
Description |
Use this method to report events, because they are not automatically reported upon creation. |
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
Example |
|
.ReportAndReturnTraceContext()
Method |
|
---|---|
Description |
Reports the end trace event and return the trace context as a TraceContext object. See Tracing across external spans. |
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
Example |
|
Related |
.SummaryMetric()
Method |
|
||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
Description |
Use this method to report a metric value. The metric values reported are aggregated and flushed every 60 seconds. |
||||||||||
Parameters |
|
||||||||||
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
||||||||||
Example |
|
.IncrementMetric()
Method |
|
||||||||
---|---|---|---|---|---|---|---|---|---|
Description |
Use this method to report the number of times an action occurs. The metric counts reported are summed and flushed every 60 seconds. |
||||||||
Parameters |
|
||||||||
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
||||||||
Example |
|
Trace.GetCurrentLogTraceId()
Method |
|
||
---|---|---|---|
Description |
Gets a current trace context in the following format: |
||
Parameters |
|
||
Returns |
A string representing a compact form of the current trace ID, or an empty string if no trace is active. |
||
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
TraceContext.GetCurrent()
Method | TraceContext.GetCurrent()
|
---|---|
Description |
Returns the current tracing context as a TraceContext object. The traceContext can be passed to a background or child thread. See tracing multi-threaded applications for usage information. |
Returns | A TraceContext object representing the current trace. A TraceContext should always be returned even if the request isn't being sampled. The TraceContext object contains the fields Traceparent, Tracestate, and Sampled. |
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
TraceContext.ClearCurrentContext()
Method | TraceContext.ClearCurrentContext()
|
---|---|
Description | Used to unset the trace context before returning a thread back to the threadpool. See tracing multi-threaded applications for usage information. |
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
TraceContext.SetAsCurrent()
Method | TraceContext.SetAsCurrent()
|
---|---|
Description | Sets the context contained in the TraceContext object as the current trace context. This setting of the context can be done for a background or child thread. See tracing multi-threaded applications for usage information. |
History |
Introduced with version 5.0.0 of the .NET Instrumentation Library. |
Troubleshooting
See the Troubleshoot the .NET Library for more information.
The scripts are not supported under any SolarWinds support program or service. The scripts are provided AS IS without warranty of any kind. SolarWinds further disclaims all warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The risk arising out of the use or performance of the scripts and documentation stays with you. In no event shall SolarWinds or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the scripts or documentation.