AppBeat.io Blog

Uptime and performance monitoring made easy

60% discount for Starter plan

First, we would like to wish you happy & prosperous new year! For this opportunity we would also like to give you nice gift: remarkable 60% OFF for new customers on first purchase!

If you purchase our yearly Starter plan you will pay only €19.96 for entire year for 25 one-minute monitors! This is just €0.80 for each one-minute monitor per year (or approximately €0.067 per month) !

This offer makes AppBeat most affordable monitoring solution on planet. Our monitoring reliability so far has been outstanding and we outperformed most (if not all) of our competition. If you decide for AppBeat you won't regret it!

Today we also published first upgrade for our web application in 2017, where we added PDF export capability for downtime reports.

Happy monitoring!

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.