I've written a custom HttpModule that implements the Application_PreRequestHandlerExecute and Application_PostRequestHandlerExecute events in order to log all request times. I'm trying to instrument an MVC web application with this HttpModule to log the performance of all requests into it. The MVC web application uses Http Session with the timeout set to the default 20 minutes. We have a suite of JMeter load tests that, prior to my HttpModule, were running fine. However, when I instrument the application, after 20 minutes of JMeter running its tests, it starts throwing errors all over the place that indicate that all the session variables have been dropped.
Why is this happening? I'm not accessing session state at all in my HttpModule, so I'm at a loss for what might be causing this. Here's the full code for the custom HttpModule. It seems that the mere presence of this HttpModule is causing session timeout problems for any apps configured to use it.
public class LoggerModule : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication context)
{
if (context == null)
{
return;
}
// Using PreRequestHandlerExecute/PostRequestHandlerExecute instead of BeginRequest/EndRequest
// from: http://stackoverflow.com/questions/12981298/how-to-analyze-the-performance-of-requests-in-asp-net-mvc-application
// NOTE: The Web.config for each application should be configured with preCondition="managedHandler". See
// http://stackoverflow.com/questions/18989449/how-does-precondition-managedhandler-work-for-modules.
// TL;DR is that this HttpModule will only run for managed resources like MVC endpoints, .aspx requests, etc. and won't run for static files.
context.PreRequestHandlerExecute += this.Application_PreRequestHandlerExecute;
context.PostRequestHandlerExecute += this.Application_PostRequestHandlerExecute;
}
private void Application_PreRequestHandlerExecute(object sender, EventArgs e)
{
// Store the current time in order to determine the time taken to process each request.
(sender as HttpApplication).Context.Items["PerformanceLoggerStart"] = DateTime.Now;
}
private void Application_PostRequestHandlerExecute(object sender, EventArgs e)
{
HttpContext context = (sender as HttpApplication).Context;
if (context == null)
{
return;
}
// Calculate the time taken to process the request
DateTime start = (DateTime)context.Items["PerformanceLoggerStart"];
double millisecondsTaken = (DateTime.Now - start).TotalMilliseconds;
// Asynchronously log performance. Note that this also insulates the profiled application from any exceptions that may be thrown by the task being done.
Task.Run(() => Logger.LogPerformance(new LogEntry
{
// Get the full URL as it appears in the browser MINUS any MVC route-specific parameter like a GUID
Endpoint = ApplicationInformation.TrimIdFromMVCRoute(context.Request.Url),
// GET, POST, etc.
Action = context.Request.HttpMethod,
// Name of the machine executing this code
MachineName = Environment.MachineName,
// Get the application name from the URL - depends on whether running locally or remotely
ApplicationName = ApplicationInformation.GetApplicationName(context.Request.Url, context.Request.ApplicationPath),
// Time taken to process the request
MillisecondsTaken = millisecondsTaken
}));
}
}
Related
I have a Web API C# project. I noticed that when sending few requests together (few can be even 7-10) to the controller, some of the requests are taking very long time (5-7 seconds). When sending each request separately, each request takes less than 200ms. I'm sending requests to my localhost (dev environment), so there shouldn't be any latency or heavy usage on the server.
I added this code inside global.asax in order to view how long it takes for each request.
//Global asax
private Dictionary<string, StopWatch> urlTimers = new Dictionary<string, StopWatch>();
void Application_BeginRequest(object sender, EventArgs e)
{
HttpContextBase currentContext = new HttpContextWrapper(HttpContext.Current);
var requestedUrl = currentContext.Request.RequestContext.HttpContext.Request.RawUrl;
var urlWithoutQueryParams = requestedUrl.Split('?')[0];
if (urlWithoutQueryParams.StartsWith("/controller"))
{
var stopWatch = new StopWatch();
stopWatch.Start(urlWithoutQueryParams);
urlTimers[urlWithoutQueryParams] = stopWatch;
}
}
void Application_EndRequest(object sender, EventArgs e)
{
HttpContextBase currentContext = new HttpContextWrapper(HttpContext.Current);
var requestedUrl = currentContext.Request.RequestContext.HttpContext.Request.RawUrl;
var urlWithoutQueryParams = requestedUrl.Split('?')[0];
if (urlWithoutQueryParams.StartsWith("/controller"))
{
var stopWatch = urlTimers[urlWithoutQueryParams];
if (stopWatch != null)
{
stopWatch.Stop();
}
}
}
When submitting 10 requests using fiddler I receive the following
All requests return a tiny amount of data, so it also shouldn't be the problem.
It doesn't make sense that 10 requests take so much time. Any help will be appreciated.
I will assume this is not yet a MVC 6 Web Api (presence of Global.asax and all) but MVC 5 or lower. One of the things that people forget in a MVC 5 Web API is the SessionState.
A couple of short blogs on this:
http://www.compilemode.com/2015/12/sessionstate-controller-attribute-in.html
https://www.codeproject.com/Articles/624883/Performance-Tip-A-few-words-on-ASP-NET-session-sta
In short it comes down to this:
With SessionState enable, only one request is processed per User session at one time.
In your case this means:
GetAsKeyValuePairs is processed
then GetTranslationManagmentData is processed
then IsCaptionsExist is processed
...
They are all requested at the same time, so they have the same start time.
The end time of a request however, depends on the time needed by the previous requests + it's own processing time. This causes the increase in time per call in your sample.
If your actions do not access the Session information, you can 'safely' add the [SessionState(SessionStateBehavior.ReadOnly)] attribute on your controllers (see blogs).
That will result in the calls being processed at the same time. Just don't use this on controllers that change your session information.
I have an Asp.NET Web API 2. My client calls a particular action method but I need someway to set the timeout value.
I need the timeout value because I want the client to take appropriate action if the action method hasn't returned anything back in say 40 seconds. (Basically that's an arbitrary limit I've chosen...so if it hasn't completed it's job..i.e. hasn't returned back the valid JSON in 40 seconds, we're going to have to assume that something is taking way too long on Azure and we're going to perform a rollback).
Also, if the timeout has occurred I want someone way to Rollback the transaction.
If it helps, I'm using the UnitOfWorkActionFilter along with NHibernate.
The controller and action method are both asynchronous, and I'm explicitly catching the ExecuteAsync method along with the CancellationToken variable.
However, I'm unaware of a way to cancel this call OR use the CancellationToken variable.
I can post code if necessary.
I did read in a few places that since WebApi2 is asynchronous that I may not be able to cancel this!
Any recommendations on how to go about solving this?
I think setting a timeout on the request is the wrong approach as you will have no visibility of what is going on during the 40 seconds.
Rather make a ajax web request and then subsequent web requests to see if the process has completed.
For example,
Queue the request somehow with the initial request.
Write something to pick up and process the item from the queue. This also means if something goes wrong, you can just roll back at this point. You also need to store the status of the item somewhere.
Write a periodic poll in Javascript that makes another ajax request every 5 seconds to see if the request has been processed or not.
Depending on what kind of method is running on your WebApi service you could try the following:
Start a StopWatch at the start of your action
Check periodically if the elapsed time is greater than your arbitrary limit. When that happens throw an Exception (I called mine
CalculationTimeLimitExceededException)
Catch the exception and perform a rollback (assuming you want to do a rollback on the server)
Return a response (e.g. HTTP 500 with some useful information, e.g. server timeout)
Since the client gets a response within your time limit you can then handle the error on the client side.
Update (added code for PerformanceWatcher):
public class PerformanceWatcher : IDisposable
{
private System.Diagnostics.Stopwatch _sw;
private Timer _timer;
private int _maxSeconds;
public bool TimeLimitExceeded { get; private set; }
public PerformanceWatcher(int maxSeconds)
{
_maxSeconds = maxSeconds;
// start the StopWatch
_sw = System.Diagnostics.Stopwatch.StartNew();
// check every second
_timer = new Timer(1000);
_timer.AutoReset = true;
// set event-handler
_timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
_timer.Enabled = true;
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
// check if time limit was reached
if (this._sw.Elapsed.TotalSeconds > _maxSeconds)
{
this.TimeLimitExceeded = true;
}
}
public void Dispose()
{
this._timer.Dispose();
}
}
You can use this class in your action:
[HttpGet]
public HttpResponseMessage GetResultFromLongRunningMethod()
{
using (PerformanceWatcher watcher = new PerformanceWatcher(10))
{
try
{
// begin long-running operation
for (int i = 0; i < 20; i++)
{
if (watcher.TimeLimitExceeded)
{
throw new TimeLimitExceededException();
}
Thread.Sleep(1000);
}
// end long-running operation
} catch (TimeLimitExceededException e)
{
return this.Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Time limit exceeded");
}
}
return this.Request.CreateResponse(HttpStatusCode.OK, "everything ok");
}
The above code isn't tested; I just copied some elements from my own classes.
Update 2: Fixed a bug in the code (Exception thrown from event handler wasn't caught)
I'm using a Webservice coded in C# using .Net Framework 3.5.
The WebMethods of this Webservice besides their main workload also connect to Salesforce to retrieve and write data, so I'm using a static variable for keeping the Salesforce api login object alive between requests, this way I only log in once (on Salesforce) and then reuse this variable for subsequent calls, avoiding the need to login on each request.
This static object is evaluated on the main ctor of the Webservice, to check if some properties inside the login object are valid (for example, the validity of the session), if not, the login method is called again.
This works with no problems at all on my development environment (Windows 7 and VS2012 IIS7.5? Dev server) and in a test server (Win Server 2003, IIS6), but it doesn't work at all on the production box which is also a Windows Server 2003 with IIS6, because the static variable value is null on each request, logging in Salesforce on every request, giving long response times for each call and also hitting some limits on Salesforce, locking the account and blocking any following logins.
Sadly both machines (test and production) don't have the same configuration and the production box is currently unreachable for me, so the recycling time of the App Pool and other specifics are unknown to me at the moment.
I think this is a configuration issue, but anyway here is my code, firstly i started only by having a static variable inside the Webservice main class and then (current version) creating a whole static class with static variables.
All of the following code is under the same namespace
Static class (Salesforce login logic):
public static class Srv
{
public static SFHelpers helper = new SFHelpers(); // own class, Holds Salesforce logic and data related to this requirement
public static SforceService SFserv = new SforceService(); // Salesforce class that handles login (and other Salesforce data manipulation methods)
public static DateTime NextLoginSF = DateTime.MinValue; // Determines when does the Salesforce session expires
public static void LoginSalesforce()
{ // Simplified salesforce login steps, removed try-catch and other conditions to facilitate comprehension
SFserv.Url = helper.URLSalesforce;
LoginResult loginResult = SFserv.login(SFuser, SFpass);
NextLoginSF = DateTime.Now.AddSeconds(loginResult.userInfo.sessionSecondsValid);
SFserv.Url = loginResult.serverUrl;
SFserv.SessionHeaderValue = new SessionHeader { sessionId = loginResult.sessionId };
}
}
Main ctor and sample Webmethod:
[WebService(Namespace = "http://helloSO.com/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class WsSFTest : System.Web.Services.WebService
{
public WsSFTest()
{
try
{
if (Srv.SFserv.SessionHeaderValue == null || DateTime.Now >= Srv.NextLoginSF) // any of this will trigger a login to renew Salesforce session
{
Srv.LoginSalesforce();
}
}
catch (SoapException se)
{
if (se.Code.Name == ExceptionCode.INVALID_SESSION_ID.ToString())
{ // Login again because Salesforce invalidated my session somehow
Srv.LoginSalesforce();
}
else
{
// Irrelevant
}
}
catch (Exception e)
{
// Irrelevant
}
}
[WebMethod]
public SampleResult SampleMethod(int param)
{
try
{
//irrelevant code gathers values here
var something = Srv.helper.Method(param, anotherParam);
return something;
}
catch (Exception e)
{
// Irrelevant
}
}
}
Serializing the login object to a local file or persistance database are my last options since quick response times are a must.
I think what's left for me to try are Session variables but given this behavior odds that something similar could happen seem high.
Any clues? Thanks in advance
At the end I realized some evaluation value in this line (Specifically Srv.NextLoginSF)
if (Srv.SFserv.SessionHeaderValue == null || DateTime.Now >= Srv.NextLoginSF) // any of this will trigger a login to renew Salesforce session
Was returning with a different value than expected, and that the static variable wasn't losing its value
I'm working with .NET 3.5 with a simple handler for http requests. Right now, on each http request my handler opens a tcp connection with 3 remote servers in order to receive some information from them. Then closes the sockets and writes the server status back to Context.Response.
However, I would prefer to have a separate object that every 5 minutes connects to the remote servers via tcp, gets the information and keeps it. So the HttpRequest, on each request would be much faster just asking this object for the information.
So my questions here are, how to keep a shared global object in memory all the time that can also "wake" an do those tcp connections even when no http requests are coming and have the object accesible to the http request handler.
A service may be overkill for this.
You can create a global object in your application start and have it create a background thread that does the query every 5 minutes. Take the response (or what you process from the response) and put it into a separate class, creating a new instance of that class with each response, and use System.Threading.Interlocked.Exchange to replace a static instance each time the response is retrieved. When you want to look the the response, simply copy a reference the static instance to a stack reference and you will have the most recent data.
Keep in mind, however, that ASP.NET will kill your application whenever there are no requests for a certain amount of time (idle time), so your application will stop and restart, causing your global object to get destroyed and recreated.
You may read elsewhere that you can't or shouldn't do background stuff in ASP.NET, but that's not true--you just have to understand the implications. I have similar code to the following example working on an ASP.NET site that handles over 1000 req/sec peak (across multiple servers).
For example, in global.asax.cs:
public class BackgroundResult
{
public string Response; // for simplicity, just use a public field for this example--for a real implementation, public fields are probably bad
}
class BackgroundQuery
{
private BackgroundResult _result; // interlocked
private readonly Thread _thread;
public BackgroundQuery()
{
_thread = new Thread(new ThreadStart(BackgroundThread));
_thread.IsBackground = true; // allow the application to shut down without errors even while this thread is still running
_thread.Name = "Background Query Thread";
_thread.Start();
// maybe you want to get the first result here immediately?? Otherwise, the first result may not be available for a bit
}
/// <summary>
/// Gets the latest result. Note that the result could change at any time, so do expect to reference this directly and get the same object back every time--for example, if you write code like: if (LatestResult.IsFoo) { LatestResult.Bar }, the object returned to check IsFoo could be different from the one used to get the Bar property.
/// </summary>
public BackgroundResult LatestResult { get { return _result; } }
private void BackgroundThread()
{
try
{
while (true)
{
try
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("http://example.com/samplepath?query=query");
request.Method = "GET";
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (StreamReader reader = new StreamReader(response.GetResponseStream(), System.Text.Encoding.UTF8))
{
// get what I need here (just the entire contents as a string for this example)
string result = reader.ReadToEnd();
// put it into the results
BackgroundResult backgroundResult = new BackgroundResult { Response = result };
System.Threading.Interlocked.Exchange(ref _result, backgroundResult);
}
}
}
catch (Exception ex)
{
// the request failed--cath here and notify us somehow, but keep looping
System.Diagnostics.Trace.WriteLine("Exception doing background web request:" + ex.ToString());
}
// wait for five minutes before we query again. Note that this is five minutes between the END of one request and the start of another--if you want 5 minutes between the START of each request, this will need to change a little.
System.Threading.Thread.Sleep(5 * 60 * 1000);
}
}
catch (Exception ex)
{
// we need to get notified of this error here somehow by logging it or something...
System.Diagnostics.Trace.WriteLine("Error in BackgroundQuery.BackgroundThread:" + ex.ToString());
}
}
}
private static BackgroundQuery _BackgroundQuerier; // set only during application startup
protected void Application_Start(object sender, EventArgs e)
{
// other initialization here...
_BackgroundQuerier = new BackgroundQuery();
// get the value here (it may or may not be set quite yet at this point)
BackgroundResult result = _BackgroundQuerier.LatestResult;
// other initialization here...
}
so I have succeeded in connecting my Windows Phone 8 Application to the Live API, I also succeeded in reading data from my hotmail account.
I have access to the needed client ID and the live access token.
But when I quit and restart my application, I lose all references to the session and the client objects and I have to start the process anew.
I don't want to annoy the user with the web mask in which he has to agree again that he provides me with the needed permissions every time he is starting the application. But I also haven't found a way to get a reference to a session object without this step.
The login mask is only shown the first time after installing the application, after that, only the screen mentioned above is shown.
But it is still quite annoying for the user to accept this every time.
I already tried serializing the session object, which is not possible, because the class does not have a standard constructor.
Maybe it is possible to create a new session by using the live access token, but I haven't found a way to do so.
Any ideas? What am I doing wrong, I know that there is a way to login again without prompting the user.
I'm thankful for every idea.
Some code I use:
/// <summary>
/// This method is responsible for authenticating an user asyncronesly to Windows Live.
/// </summary>
public void InitAuth()
{
this.authClient.LoginCompleted +=
new EventHandler<LoginCompletedEventArgs>(this.AuthClientLoginCompleted);
this.authClient.LoginAsync(someScopes);
}
/// <summary>
/// This method is called when the Login process has been completed (successfully or with error).
/// </summary>
private void AuthClientLoginCompleted(object sender, LoginCompletedEventArgs e)
{
if (e.Status == LiveConnectSessionStatus.Connected)
{
LiveConnector.ConnectSession = e.Session; // where do I save this session for reuse?
this.connectClient = new LiveConnectClient(LiveConnector.ConnectSession);
// can I use this access token to create a new session later?
LiveConnector.LiveAccessToken = LiveConnector.ConnectSession.AccessToken;
Debug.WriteLine("Logged in.");
}
else if (e.Error != null)
{
Debug.WriteLine("Error signing in: " + e.Error.ToString());
}
}
I have tried to use the LiveAuthClient.InitializeAsync - method to login in background after restarting the application, but the session object stays empty:
// this is called after application is restarted
private void ReLogin()
{
LiveAuthClient client = new LiveAuthClient(LiveConnector.ClientID);
client.InitializeCompleted += OnInitializeCompleted;
client.InitializeAsync(someScopes);
}
private void OnInitializeCompleted(object sender, LoginCompletedEventArgs e)
{
Debug.WriteLine("***************** Inititalisation completed **********");
Debug.WriteLine(e.Status); // is undefined
Debug.WriteLine(e.Session); // is empty
}
Does anyone have an idea how I could get access to a new session after restarting the application?
After two full days searching for the mistake I was making, I finally found out what I was doing wrong: I have to use the wl.offline_access scope to make this work!
Let me quote another user here:
"If your app uses wl.offline_access scope than the live:SignInButton control saves it for you and loads it automatically. Just use the SessionChanged event to capture the session object. This way the user will need to sign in only once."
(see WP7 how to store LiveConnectSession during TombStoning?)
Now everything is fun again. Can't believe that this was the problem. Tested & working. Nice!
Been struggling to get this working on a Windows Live + Azure Mobile Service app myself so thought I would post a complete working code sample here now that I've got it working.
The key parts are the wl.offline_access scope and the call to InitializeAsync.
Note: this sample also connects with Windows Azure Mobile Services. Just remove the stuff related to MobileService if you're not using that.
private static LiveConnectSession _session;
private static readonly string[] scopes = new[] {"wl.signin", "wl.offline_access", "wl.basic"};
private async System.Threading.Tasks.Task Authenticate()
{
try
{
var liveIdClient = new LiveAuthClient("YOUR_CLIENT_ID");
var initResult = await liveIdClient.InitializeAsync(scopes);
_session = initResult.Session;
if (null != _session)
{
await MobileService.LoginWithMicrosoftAccountAsync(_session.AuthenticationToken);
}
if (null == _session)
{
LiveLoginResult result = await liveIdClient.LoginAsync(scopes);
if (result.Status == LiveConnectSessionStatus.Connected)
{
_session = result.Session;
await MobileService.LoginWithMicrosoftAccountAsync(result.Session.AuthenticationToken);
}
else
{
_session = null;
MessageBox.Show("Unable to authenticate with Windows Live.", "Login failed :(", MessageBoxButton.OK);
}
}
}
finally
{
}
}