AppBeat Blog

Perfect Monitoring for Your Cloud

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.

New scheduling algorithm

In last few days we upgraded our core scheduling algorithm which assigns agent resources (CPU, network) to your monitoring tasks.

New version now handles agent resources much better and distributes monitoring tasks more equally. This results in much lower average resource usage on our entire pool of monitoring agents.

Here is screenshot of CPU usage on one of our agents where this effect is clearly visible:

Testing AppBeat monitoring automation

We are currently finishing command line tool for basic AppBeat management. Soon you will be able to get your system status, pause / resume / delete services and checks, create new checks.

For example, this is minimum configuration for creating new check:


{
	"Agent": "Web",
	"Name": "Check1",
	"Description": "Check created with AppBeat command line tool / API",
	"CheckIntervalInSeconds": "60",
	"AgentSpecificSettings": {
		"URL": "https://appbeat.io",
		"TIMEOUT": "15",
		"METHOD": "GET",
		"HTTP_VER": "http1.1",
		"IP_VER": "ipv4"
	}
}

AppBeat command line tool will be available for Windows, Linux and Mac.