Shatl's home

Some thoughts on life and programming

Mega switch

note line numbers Smile

// line num: 74
#region Switch Statement
switch (key) {
  case 10: {
// ...

// line num: 1767
  case 430: {
// ...
}

 

Using EQATEC Analytics to monitor .NET application feature usage

 

Often developers need to know how their application is being used. There is number of different approaches and tools to achieve this. I would like to share my experience of using EQATEC Analytics for this.

Analytics allows you to collect detailed information about usage of you application. Full list of data you can collect is available at Analytics home page, but I would like to highlight some of them which appeared most interesting for me.

  • Collect exceptions;
  • Collect feature usage (number of times particular function was used);
  • Feature execution time;
  • Email notification about exception;
  • Application version and location info.

While Analytics supports lot of different programming environments (like .NET, Java, C++, etc..), I used it only with .NET framework.

Analytics Monitor API and usage scenarios are well documented at www.eqatec.com site, so I will not repeat it here and will describe what was missing from my point of view and extra steps I did to integrate it to my project.

Distribution

Analytics monitor can be downloaded from EQATEC site as zip archive. I would rather have it as nuget package. Hope this will be done soon Smile

Feature hierarchy

Currently Analytics supports only 2 level hierarchy, where category and feature are divided by dot. Like: Order.Created, Order.Approved, etc… I would like to have more than 2 level hierarchy.

API

Analytics Monitor has quite simple and clear API. The only thing I was not so happy with is Track Feature Time. In order to get it working properly you have to write something similar to this: 

TrackFeatureStart(featureName);
try
{
  // some code here
  TrackFeatureStop(featureName);
}
catch
{
  TrackFeatureCancel(featureName);
  throw;
}

Too much code for me… Smile I prefer more concise code, either using lambda or using() construct..

TrackFeatureTiming("message.send", () => mailer.send(message));

Or, if there is more than one line of code, something like this (transaction-like approach)

using (var ft = monitor.TrackFeatureTime("mail.send")) {
  var message = new MailMessage();
  // other code here
  ft.Complete();
}

Integration

Another thing which I always try to avoid is introducing additional dependencies in project. This is actually nothing to do with Analytics Monitor itself, but also worth to note.

First of all, referencing Analytics directly from the code will make it depend on another third-party library, which in fact does not solve any business critical problems.

Secondly, if code is executed by unit-test, not real user, this will affect actual usage statistics.

This is quite simple to overcome, we need introduce another level of abstraction. So we just creating new interface or abstract class and 2 implementations: one for delegating job to Analytics Monitor and another one to represent NullObject.

Now we can register in out favorite dependency container and use read Monitor when running the application and switch to NullObject in unit-tests.

Below is implementation of API helpers and Analytics monitor wrapper.

    [PublicAPI]
    public class FeatureTimingTracker : IDisposable
    {
        #region Data

        readonly string _featureName;
        bool _isCompleted;
        bool _isDisposed;
        UsageMonitor _monitor;

        #endregion

        #region .ctor

        internal FeatureTimingTracker([NotNull] UsageMonitor monitor, [NotNull] string featureName)
        {
            if (monitor == null)
                throw new ArgumentNullException("monitor");
            if (featureName == null)
                throw new ArgumentNullException("featureName");

            _featureName = featureName;
            _isCompleted = false;
            _isDisposed = false;
            _monitor = monitor;
            _monitor.TrackFeatureStart(_featureName);
        }

        #endregion

        /// <summary>
        ///   Mark feature as completed.
        /// </summary>
        /// <remarks>
        ///   Actual feature usage will be tracked on Dispose
        /// </remarks>
        public void Complete()
        {
            _isCompleted = true;
        }


        ~FeatureTimingTracker()
        {
            if (_monitor != null)
                _monitor.SendLog("FeatureTimingTracker was not disposed for [{0}]", _featureName);

            Dispose(false);
        }

        #region Implementation of IDisposable

        /// <summary>
        ///   Report Feature usage.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        void Dispose(bool disposing)
        {
            if (_isDisposed)
                return;

            _isDisposed = true;
            if (_isCompleted)
                _monitor.TrackFeatureStop(_featureName);
            else
                _monitor.TrackFeatureCancel(_featureName);

            if (disposing)
                _monitor = null; // we do not need it anymore
        }

        #endregion
    }


    [PublicAPI]
    public abstract class UsageMonitor
    {
        public abstract void Start();
        public abstract void Stop();


        public abstract void SendLog(string message);


        [StringFormatMethod("fmt")]
        public virtual void SendLog(string fmt, params object[] args)
        {
            SendLog(fmt.ApplyArgs(args));
        }


        public abstract void TrackFeature(string feature);


        public abstract void TrackFeatureValue(string feature, long value);


        public virtual void TrackException(Exception ex)
        {
            TrackException(ex, string.Empty);
        }


        public abstract void TrackException(Exception ex, string context);
        public abstract void TrackFeatureStart(string featureName);
        public abstract void TrackFeatureStop(string featureName);
        public abstract void TrackFeatureCancel(string featureName);


        public virtual void TrackFeatureTiming([NotNull] Action action, string featureName)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            TrackFeatureStart(featureName);
            try
            {
                action();
                TrackFeatureStop(featureName);
            }
            catch
            {
                TrackFeatureCancel(featureName);
                throw;
            }
        }


        public virtual T TrackFeatureTiming<T>([NotNull] Func<T> operation, string featureName)
        {
            if (operation == null)
                throw new ArgumentNullException("operation");
            TrackFeatureStart(featureName);
            try
            {
                var result = operation();
                TrackFeatureStop(featureName);
                return result;
            }
            catch
            {
                TrackFeatureCancel(featureName);
                throw;
            }
        }


        public FeatureTimingTracker CreateFeatureTimingTracker(string featureName)
        {
            return new FeatureTimingTracker(this, featureName);
        }


        public abstract void Save();
    }


    public class NullUsageMonitor : UsageMonitor
    {
        #region Overrides of UsageMonitor

        public override void Start()
        {}


        public override void Stop()
        {}


        public override void SendLog(string message)
        {}


        public override void TrackFeature(string feature)
        {}


        public override void TrackFeatureValue(string feature, long value)
        {}


        public override void TrackException(Exception ex, string context)
        {}


        public override void TrackFeatureStart(string featureName)
        {}


        public override void TrackFeatureStop(string featureName)
        {}


        public override void TrackFeatureCancel(string featureName)
        {}

        #region Overrides of UsageMonitor

        public override void TrackFeatureTiming([NotNull] Action action, string featureName)
        {
            if (action == null)
                throw new ArgumentNullException("action");
            // since we do not doing any tracking, just execute code
            action();
        }


        public override void Save()
        {}

        #endregion

        #endregion
    }

 

Analytics Monitor wrapper

    public class EqatecAnalyticsMonitor : UsageMonitor
    {
        readonly IAnalyticsMonitor _monitor;

        #region .ctor

        public EqatecAnalyticsMonitor(ConfigurationData data)
        {
            if (data == null)
                throw new ArgumentNullException("data");
            var settings = new AnalyticsMonitorSettings(data.FeatureUsageMonitorKey) {
                StorageSaveInterval = TimeSpan.FromMinutes(2), SynchronizeAutomatically = true
            };
            _monitor = AnalyticsMonitorFactory.Create(settings);
            _monitor.SetInstallationInfo(data.InstanceId);
        }

        #endregion

        #region Overrides of UsageMonitor

        public override void Start()
        {
            _monitor.Start();
        }


        public override void Stop()
        {
            _monitor.Stop();
        }


        public override void SendLog(string message)
        {
            _monitor.SendLog(message);
        }


        public override void TrackFeature(string feature)
        {
            _monitor.TrackFeature(feature);
        }


        public override void TrackFeatureValue(string feature, long value)
        {
            _monitor.TrackFeatureValue(feature, value);
        }


        public override void TrackException(Exception ex, string context)
        {
            _monitor.TrackException(ex, context);
        }


        public override void TrackFeatureStart(string featureName)
        {
            _monitor.TrackFeatureStart(featureName);
        }


        public override void TrackFeatureStop(string featureName)
        {
            _monitor.TrackFeatureStop(featureName);
        }


        public override void TrackFeatureCancel(string featureName)
        {
            _monitor.TrackFeatureCancel(featureName);
        }


        public override void Save()
        {
            _monitor.ForceSync();
        }


        public override void SendLog(string messageFormat, params object[] args)
        {
            _monitor.SendLog(messageFormat, args);
        }

        #endregion

Путешествуем с GoogleMaps

image

Over net-worked :)

image

Google+ epic fail :)

image

Mind shift


function callback(route) {
  map.add(route);
  // display trip distance in Km
  $("#tripDistance").text(Globalize.format((route.distance() / 1024, "n0" )));
  $("#tripDistance").attr("title", "Approximate distance");
  map.repaint();
}

			

Enterprise Library 5 on nuget

Enterprise library 5 is available as set of NuGet packages. See post from Grigory Melnik http://blogs.msdn.com/b/agile/archive/2011/05/11/nuget-for-the-win.aspx

Removed Reflector

Finally I removed the Reflector and replaced it with ILSpy Smile

Red Gate, shame on you Devil! Long live ILSpy Thumbs up

Coding with future changes in mind

if (e.TableCellIdentity.TableCellType == GridTableCellType.ColumnHeaderCell)
{
  ...
}
else
{
  // .. some other condition we can cater for here
}

Ass Initialization :)

 

// Associations Tab 
TpAssociations.Visible = true; 
crlAssociations.Visible = true; 
var ass = crlAssociations.Presenter; 
if ( ass != null ) { 
  _presenter.AddChildPresenter( ass ); 
  ass.Init(); 
}
Technorati Tags:
Follow

Get every new post delivered to your Inbox.