AppBeat Blog

Perfect Monitoring for Your Cloud

Migration of our monitoring stack to .NET Core 2.0 and .NET Standard 2.0

After successful migration of our monitoring front-end, back-end and microservices to .NET Core 2.0 and .NET Standard 2.0, here is quick summary of this process.

Migration decision

Although we were very very satisfied with .NET Core 1.0, we decided for migration to newest version for following reasons:

  • bigger API and more libraries available (thanks to .NET Standard 2.0)
  • much easier to share common internal projects between .NET Core 2.0 and Full .NET Framework on Windows (.NET Framework 4.6.1 and newer)
  • security and performance improvements

Issues

We started official migration from .NET Core 1.0 as soon as Microsoft released RTM version of .NET Core 2.0 and Visual Studio tooling. Entire process went quite smoothly and without major issues. We followed official instructions published on Microsoft Docs (very useful).

During migration, most time was spent for resolving NuGet conflicts and we also encountered one runtime problem with Entity Framework Core 2.0 - we resolved this by doing simple workaround and rewriting this one problematic query. Everything else was - perfect!

Results

Our migrated solution is now deployed on Windows and Linux production servers and everything runs without any issues. Improvements on web front-end are very noticeable (Kestrel, compiled MVC views, smaller deployments). Mission accomplished!

Thanks to Microsoft and all open source contributors for making such a great product!

Implementing WebDriverIO support in .NET Core 2.0 for efficient Selenium testing

We are currently implementing experimental subset of WebDriverIO API (higher level API on top of Selenium WebDriver) in .NET Core so we could run synthetic (transaction) monitoring very efficiently from different geographic locations.

Implementation goals:

  • cross-platform, efficient and lightweight framework
  • easy to use (REST API / web interface)
  • high compatiblity with WebDriverIO because users are already familiar with it
  • for security reasons we will probably implement simple JavaScript interpreter for WebDriverIO commands (instead of running tests in Node.js)

Currently we have basic prototype but it looks very promising:

If you have any ideas or suggestions, please let us know!

How to build .NET Microservices

We would like to share with you very interesting NDC Conference session from Sydney which describes pros and cons of a microservices solution and how you can build one using .NET and a number of open source tools like EventStore, RabbitMq and Redis.

Presentation was done by Richard Banks and you can watch it on YouTube:

Robust and affordable Browser as a Service (BaaS)

That is our goal for next year!

We want to offer you real browsers, for example Chrome and Firefox, so you can periodically check if your web application always behaves as it should.

This kind of service will be very useful for your continuous integration and testing your website after you deploy new version into production environment.

If you would like to participate in closed beta, please contact us.

C# method for very fast and efficient traceroute (network diagnostic tool)

We like C#, .NET and .NET Core!

Last time we played a little bit with traceroute utility and thought, could we write something fast, efficient, and since we love Asynchronous Programming with async and await (C#), something that would include this pattern as well?

Here is what we came up with :)

using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;

namespace AppBeat.Core.Utility
{
    public class TraceRoute
    {
        private const string DATA = "AppBeat Monitoring - https://appbeat.io - AppBeat Monitoring"; //60 Bytes
        private static readonly byte[] _buffer = Encoding.ASCII.GetBytes(DATA);
        private const int MAX_HOPS = 15;
        private const string STR_REQUEST_TIMEOUT = "Request timed out.";
        private const string STR_REQUEST_TIME_NA = "*";
        private const int REQUEST_TIMEOUT = 4000;

        /// <summary>
        /// Runs traceroute and writes result to console.
        /// </summary>
        public static async Task TryTraceRouteAsync(string hostNameOrAddress)
        {
            EnsureCommonArguments(hostNameOrAddress);
            Contract.EndContractBlock();

            using (var console = Console.OpenStandardOutput())
            using (var sw = new StreamWriter(console))
            {
                await TryTraceRouteAsync(hostNameOrAddress, sw);
            }
        }

        /// <summary>
        /// Runs traceroute and writes result to provided stream.
        /// </summary>
        public static async Task TryTraceRouteAsync(string hostNameOrAddress, StreamWriter outputStreamWriter)
        {
            EnsureCommonArguments(hostNameOrAddress);
            if (outputStreamWriter == null)
            {
                throw new ArgumentNullException(nameof(outputStreamWriter));
            }
            Contract.EndContractBlock();

            await outputStreamWriter.WriteLineAsync($"traceroute to {hostNameOrAddress}, {MAX_HOPS} hops max, {_buffer.Length} byte packets");

            //dispatch parallel tasks for each hop
            var arrTraceRouteTasks = new Task<TraceRouteResult>[MAX_HOPS];
            for (int zeroBasedHop = 0; zeroBasedHop < MAX_HOPS; zeroBasedHop++)
            {
                arrTraceRouteTasks[zeroBasedHop] = TryTraceRouteInternalAsync(hostNameOrAddress, zeroBasedHop);
            }

            //and wait for them to finish
            await Task.WhenAll(arrTraceRouteTasks);

            //now just collect all results and write them to output stream
            for (int hop = 0; hop < MAX_HOPS; hop++)
            {
                var traceTask = arrTraceRouteTasks[hop];
                if (traceTask.Status == TaskStatus.RanToCompletion)
                {
                    var res = traceTask.Result;
                    await outputStreamWriter.WriteLineAsync(res.Message);

                    if (res.IsComplete)
                    {
                        //trace complete
                        break;
                    }
                }
                else
                {
                    await outputStreamWriter.WriteLineAsync($"Could not get result for hop #{hop + 1}");
                }
            }
        }

        private static void EnsureCommonArguments(string hostNameOrAddress)
        {
            if (hostNameOrAddress == null)
            {
                throw new ArgumentNullException(nameof(hostNameOrAddress));
            }

            if (string.IsNullOrWhiteSpace(hostNameOrAddress))
            {
                throw new ArgumentException("Hostname or address is required", nameof(hostNameOrAddress));
            }
        }

        public class TraceRouteResult
        {
            public TraceRouteResult(string message, bool isComplete)
            {
                Message = message;
                IsComplete = isComplete;
            }

            public string Message
            {
                get; private set;
            }

            public bool IsComplete
            {
                get;private set;
            }
        }

        public static async Task<TraceRouteResult> TryTraceRouteInternalAsync(string hostNameOrAddress, int zeroBasedHop)
        {
            using (Ping pingSender = new Ping())
            {
                var hop = zeroBasedHop + 1;

                PingOptions pingOptions = new PingOptions();
                Stopwatch stopWatch = new Stopwatch();
                pingOptions.DontFragment = true;
                pingOptions.Ttl = hop;

                stopWatch.Start();

                PingReply pingReply = await pingSender.SendPingAsync(
                    hostNameOrAddress,
                    REQUEST_TIMEOUT,
                    _buffer,
                    pingOptions
                );

                stopWatch.Stop();

                var elapsedMilliseconds = stopWatch.ElapsedMilliseconds;

                string pingReplyAddress;
                string strElapsedMilliseconds;

                if (pingReply.Status == IPStatus.TimedOut)
                {
                    pingReplyAddress = STR_REQUEST_TIMEOUT;
                    strElapsedMilliseconds = STR_REQUEST_TIME_NA;
                }
                else
                {
                    pingReplyAddress = pingReply.Address.ToString();
                    strElapsedMilliseconds = $"{elapsedMilliseconds.ToString(System.Globalization.CultureInfo.InvariantCulture)} ms";
                }

                var traceResults = new StringBuilder();
                traceResults.Append(hop.ToString(System.Globalization.CultureInfo.InvariantCulture).PadRight(4, ' '));
                traceResults.Append(strElapsedMilliseconds.PadRight(10, ' '));
                traceResults.Append(pingReplyAddress);

                return new TraceRouteResult(traceResults.ToString(), pingReply.Status == IPStatus.Success);
            }
        }
    }
}

Method above basically dispatches 15 parallel (asynchronous) tasks and waits for their result. For this reason it is really fast compared to standard implementation, which would do only one step at a time, and wait for each step (15 times in a row).

Hopefully you will find this useful :)

Coming soon: multi expression rules for better monitoring experience

Very soon we will add multi expression support for Warning and Error notification rules. Syntax will be as following:

[trigger_rule: YOUR_RULE_NAME_1]
EXPRESSION(S)_1

[trigger_rule: YOUR_RULE_NAME_2]
EXPRESSION(S)_2

[trigger_rule: YOUR_RULE_NAME_N]
EXPRESSION(S)_N

On user interface this will look something like this:

This example rule then triggers "Warning" notification which is sent to you and it is also visible in monitor log:

 

Source code for our website monitoring command line tool

As we have previously promised, we published full source code of our cross-platform website monitoring command line tool. You can get it from https://appbeat.io/automation/cli/source-code

Tool is written in C# and targets cross-platform .NET Core framework. This means you can run it on Windows, Linux or Mac.

Tool currently supports following commands:


AppBeat Command Line Interface for monitoring automation, version 1.0.0
Usage: dotnet AppBeat.CLI.dll [command] [options]

command:
  help                Displays this help.

  link                Links this command line tool with your AppBeat account by providing secret access key.

  unlink              Unlinks this command line tool from your AppBeat account.

  status              Returns current status overview of your system with all services and checks.

  list                Lists active checks or services and returns unique identifier for each resource (resource identifiers).

  pause               Pauses check(s) and/or service(s) by using resource identifiers.

  resume              Resumes check(s) and/or service(s) by using resource identifiers.

  new-check           Creates new AppBeat check (periodic monitor) from json settings provided by UTF-8 encoded input file or standard input.

  delete              Permanently deletes check(s) and/or service(s) by using resource identifiers.

If you have any questions or ideas for improvement, please contact us at any time.