Verifying current calls and sessions during runtime

 

One of the WCF strengths is the flexibility to allow different configuration combinations. One of the WCF weakness is this configuration flexibility that may lead to a final results different from the expected. Some of the problems we see with our customers are related to actual configuration versus expected configuration. Let me give a very common issue: you configure your service to have per call instance mode which would have you to believe you would not have to set the max concurrent sessions values to a high value. You have no problem when using your http binding. However, you decide to use net.tcp binding and start to see “the socket connection was aborted” errors and you have to wait 10 minutes to have the service responding again. For this scenario, using a channel that is sessionful like net.tcp in a “per call” service will still create sessions and as the receive timeout is 10 minutes you will have to wait the 10 minutes.

There is no easy way to monitor the throttling during runtime (calls and sessions). No performance counter to give you current number of calls and sessions in a process.

Some people use a Ping method to test whether the service is online or not. This Ping method normally is implemented to respond using some combination based on the string parameter set. I put together a more useful Ping-like method that will return the runtime values like user, current calls and sessions and garbage collection mode.

This would be the result for GetProcessObjInfo when there are 8 current calls:

image

 

It quite simple to add this monitoring to your service. For example, using the default Service template project, you just need to make two changes,:

1. Make the service interface inherit from IWCFMonitor:

 

     [ServiceContract]
    public interface IService1: IWCFMonitor // To add monitoring, make the service interface inherit from IWCFMonitor
    {

        [OperationContract]
        string GetData(int value);

        [OperationContract]
        CompositeType GetDataUsingDataContract(CompositeType composite);

        // TODO: Add your service operations here
    }

2. Make the service class implement WCFMonitor:

     public class Service1 : WCFMonitor, IService1
    {
        public string GetData(int value)
        {
            return string.Format("You entered: {0}", value);
        }

        public CompositeType GetDataUsingDataContract(CompositeType composite)
        {
            if (composite == null)
            {
                throw new ArgumentNullException("composite");
            }
            if (composite.BoolValue)
            {
                composite.StringValue += "Suffix";
            }
            return composite;
        }
    }
}

WCFMonitor.cs:

Download a sample project including the class and interface here: Monitoring Services Class and Sample

 using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Dispatcher;

//The code samples are provided AS IS without warranty of any kind. 
// Microsoft disclaims all implied warranties including, without limitation, 
// any implied warranties of merchantability or of fitness for a particular purpose. 

/* 

The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. 
In no event shall Microsoft, its authors, 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 sample scripts 
or documentation, even if Microsoft has been advised of the possibility of such damages. 

*/ 


namespace Microsoft.Sample.MonitoringService
{

    /// <summary>
    /// Interface with WCF Runtime values operations
    /// </summary>
    /// <remarks>More details in https://blogs.msdn.com/rodneyviana</remarks>
    [ServiceContract]
    public interface IWCFMonitor
    {
        [OperationContract]
        string GetProcessInfo();
        [OperationContract]
        ProcessInfoData GetProcessObjInfo();
    }

    /// <summary>
    /// Contains the implementation of WCF Monitoring
    /// </summary>
    /// <remarks>More details in https://blogs.msdn.com/rodneyviana</remarks>
    public class WCFMonitor: IWCFMonitor
    {
        /// <summary>
        /// Return a string with WCF Runtime values
        /// </summary>
        public string GetProcessInfo()
        {
            ProcessInfoData pi = new ProcessInfoData(OperationContext.Current, OperationContext.Current.Host);
            return String.Format("Process Name: {0}{10}GC Mode: {1}{10}Bitness: {2}{10}Max Calls: {3}{10}Max Sessions: {4}{10}Calls: {5}{10}Sessions: {6}{10}User Name: {7}{10}Auth Type: {8}{10}LastError: {9}{10}",
                pi.ProcessName,
                pi.GCMode,
                pi.Bitness,
                pi.MaxCalls,
                pi.MaxSessions,
                pi.Calls,
                pi.Sessions,
                pi.UserName,
                pi.AuthType,
                pi.LastError,
                Environment.NewLine);
        }

        /// <summary>
        /// Return a structure with fields containing WCF Runtime values
        /// </summary>
        public ProcessInfoData GetProcessObjInfo()
        {
            ProcessInfoData pi = new ProcessInfoData(OperationContext.Current, OperationContext.Current.Host);
            return pi;
        }
    }

    /// <summary>
    /// It is populated with runtime values
    /// </summary>
    /// <remarks>Structure with WCF Process Info</remarks>
    [DataContract]
    public class ProcessInfoData
    {
        public ProcessInfoData()
        { }
        protected FieldInfo GetField(Type type, string FieldName)
        {
            return type.GetField(FieldName, BindingFlags.Instance | BindingFlags.NonPublic);
        }

        protected ServiceHostBase host = null;

        private string lastError = null;

        [DataMember]
        public string LastError
        {
            get { return lastError; }
            set { lastError = value; }
        }

        [DataMember]
        public bool IsError
        {
            get
            {
                return !String.IsNullOrEmpty(LastError);
            }
            set { }
        }

        private int maxCalls;

        [DataMember]
        public int MaxCalls
        {
            get { return maxCalls; }
            set { maxCalls = value; }
        }

        private int maxSessions;

        [DataMember]
        public int MaxSessions
        {
            get { return maxSessions; }
            set { maxSessions = value; }
        }

        private int maxInstances;

        [DataMember]
        public int MaxInstances
        {
            get { return maxInstances; }
            set { maxInstances = value; }
        }

        private int calls;

        [DataMember]
        public int Calls
        {
            get { return calls; }
            set { calls = value; }
        }

        private int sessions;

        [DataMember]
        public int Sessions
        {
            get { return sessions; }
            set { sessions = value; }
        }

        private string userName;

        [DataMember]
        public string UserName
        {
            get { return userName; }
            set { userName = value; }
        }

        private string authType;

        [DataMember]
        public string AuthType
        {
            get { return authType; }
            set { authType = value; }
        }

        internal ProcessInfoData(OperationContext Context, ServiceHostBase Host)
        {
            if (Context == null)
            {
                lastError = "FATALERROR: Context cannot be null (ProcessInfo)";
                return;
            }
            if (Host == null)
            {
                lastError = "FATALERROR: Context cannot be null (ProcessInfo)";
                return;
            }
            maxCalls = 0;
            maxInstances = 0;
            calls = 0;
            sessions = 0;
            ServiceHostBase host = Host;
            
            try
            {
                if (Context.ServiceSecurityContext != null)
                    userName = Context.ServiceSecurityContext.PrimaryIdentity.Name;
                else
                    userName = "Anonymous";
            }
            catch (Exception ex)
            {
                userName = "Anonymous/Unknown";
                lastError = String.Format("NONFATAL: {0} ({1}) (ProcessInfo) (username) ", ex.Message, ex.ToString());
            }
            try
            {
                if (Context.ServiceSecurityContext != null)
                    authType = Context.ServiceSecurityContext.PrimaryIdentity.AuthenticationType;
                else
                    authType = "None";

            }
            catch (Exception ex)
            {
                lastError = String.Format("NONFATAL: {0} ({1}) (ProcessInfo) (authentication)", ex.Message, ex.ToString());
            }

            if (host != null)
            {
                foreach (object obj in host.Description.Behaviors)
                {
                    System.ServiceModel.Description.ServiceThrottlingBehavior throttle = obj as System.ServiceModel.Description.ServiceThrottlingBehavior;

                    if (throttle != null)
                    {
                        maxCalls = throttle.MaxConcurrentCalls;
                        maxSessions = throttle.MaxConcurrentSessions;
                        maxInstances = throttle.MaxConcurrentInstances;
                    }
                }



                Type t = typeof(ServiceHostBase);
                FieldInfo fi = GetField(t, "serviceThrottle");
                ServiceThrottle rtThrottle = null;
                if (fi != null)
                {
                    rtThrottle = fi.GetValue(host) as ServiceThrottle;
                    t = typeof(ServiceThrottle);

                    fi = GetField(t, "calls");
                    if (fi != null)
                    {
                        object o = fi.GetValue(rtThrottle);
                        if (o != null)
                        {
                            fi = GetField(o.GetType(), "count");
                            if (fi != null)
                                calls = (int)fi.GetValue(o);
                        }
                    }
                    fi = GetField(t, "sessions");
                    if (fi != null)
                    {
                        object o = fi.GetValue(rtThrottle);
                        if (o != null)
                        {
                            fi = GetField(o.GetType(), "count");
                            if (fi != null)
                                sessions = (int)fi.GetValue(o);
                        }
                    }
                }

            }

        }

        [DataMember]
        public string ProcessName
        {
            get
            {
                return Process.GetCurrentProcess().ProcessName;
            }
            set { }
        }

        [DataMember]
        public string GCMode
        {
            get
            {
                return System.Runtime.GCSettings.IsServerGC ? "Server" : "Workstation";
            }
            set { }
        }

        [DataMember]
        public byte Bitness
        {
            get
            {
                return sizeof(ulong) * 8;

            }
            set { }
        }


    }
}