AppBeat Blog

Perfect Monitoring for Your Cloud

Upgraded website monitor: now supporting multi-user login

Today we published new web application with one of the most requested feature for our advanced users - multi-user login!

This functionality is very suitable for larger companies and DevOps teams, where it is prefered for each member to have its own AppBeat sub account (and not sharing primary account with all your users anymore).

Currently it is possible to add up to 25 power users for your subscription. These users have very similar permissions to primary account administrator with following restrictions:

  • they can not deactivate primary account
  • they can not change account personalization
  • they don't see account log

Based on your subscription plan you can have:

  • Standard plan - 3 Power Users
  • Advanced plan - 10 Power Users
  • Enterprise plan - 25 Power Users

If you need more than that, please contact us and we will arrange you more users. In future versions we plan to add more roles with less permissions (for example only to view data without any changes).

Hopefully you will like this new feature! And as always - happy monitoring!

Special offer for Pingdom, StatusCake and UptimeRobot users

Have more than 50 periodic web monitors and want to save some money? You are not satisfied with your current monitoring service quality?

Don't worry, we have something special for you! In December 2016 and January 2017 we offer you three months free service for any of our paid packages! In this free period you will experience our superior service quality, without monitoring outages or false positive detections!

Our solution was architected from the ground up with reliability and robustness in mind. Now you have great chance to experience this first-hand. Simply contact us and we will arrange all the details. And of course: you have no obligations! After three months of our free service it is up to you to decide if you will continue to use our paid service or not.

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 :)

Better website monitoring experience for your team

We deployed our upgraded rule engine. You can now have more descriptive notifications for Warning and Error rules, by combining them together with custom note:

[trigger_rule: my notification message: why so slow???]
%RESPONSE_TIME% > 10000

[trigger_rule: server X needs restart???]
%STATUS% = 503 AND MATCH(%RESPONSE%, "YOUR_REGULAR_EXPRESSION")

If conditions for trigger are met, you will automatically receive notification with your note. This may be useful in critical situtations for your team, so they can respond more quickly!

Happy monitoring!

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:

 

Should we support more rich rule expressions?

We are seriously considering to upgrade our rule engine with option to specify multiple Warning or Error rules by using "named expressions".

In current version you can write quite complex expressions, but they trigger generic Warning / Error message. Our idea is to allow users to specify multiple named Warning / Error expressions which would give you more information / context.

For example, in Warning rule you could have something like this (expression with two rules):

--This expression checks HTML response for expected title.
--If test fails you would get "Website title not correct!"
--in your notification.
Expression("Website title not correct!") {
   NOT(MATCH(%RESPONSE%, "<title>My monitored website!!!</title>"))
}

--Our second expression checks how fast
--server response was.
--If test fails you would get "Page is too slow"
--in your notification.
Expression("Page is too slow") {
   %RESPONSE_TIME% > 5000
}

What do you think about this feature? Please let us know.

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.