Use Cloud Services To Track Your Application’s Performance
When you begin scaling your connected product from a handful of prototypes to significant numbers of production units in use in the field, you will start to stress your application in ways you may not have foreseen during development, or were unable to test at scale. For this reason, monitoring the moment-by-moment state of your application — its ‘health’ — is essential.
Continually observing your application’s performance and operational behavior will not only provide you with timely warnings of imminent problems which may impact performance and therefore end-user satisfaction, but may also highlight unexpected weaknesses in the application’s implementation.
Only continuous monitoring can expose limitations arising from factors such as high-volume communications and large-scale interaction with hosts outside of the Electric Imp impCloud™, such as third-party cloud services and your own server infrastructure.
Application monitoring will alert you when and where situations like these arise, whether they are one-off occurrences or indicative of a systematic issue with your application logic. This document will help you understand what facilities the Electric Imp Platform provides to help you and your team establish a framework to monitor your application’s health and efficiency.
Central to the monitoring process is the capture of event data logged by your application. While the Electric Imp Platform provides a production device logging facility to help you temporarily oversee the operation of specific end-user devices for the purposes of technical support, it does not incorporate a broad monitoring system to gather data about how your application — more precisely, all of its many thousands of separate instances — is performing at any given moment. However, it is possible to generate this information yourself and collate it through any one of a number of open source data-gathering systems.
For example, a number of Public impCloud customers use the Loggly service, which offers a variety of free and commercial customer accounts depending on the number of data sources you have, the frequency they will post information, and the volume of the data that they log.
We’ll use Loggly in the discussion below, but the same approach applies to many comparable online services — Sumo Logic, for example — and to systems that customers may choose to install and themselves using software such as Syslog and Logstash.
Incorporating Loggly is just a matter of first creating an account with the service — details of this process is beyond the scope of this document; please refer to the Loggly website — and incorporating Electric Imp’s Loggly library into your application’s agent code.
To load the Loggly library, use a standard #require
statement at the top of your code and then instantiate an object from the class as a global variable:
#require "Loggly.class.nut:1.1.0"
loggly <- Loggly("<YOUR_CUSTOMER_TOKEN>", { "tags" : "app_monitor_test",
"timeout" : 60,
"limit" : 50 });
The first parameter is your customer ID code, provided by Loggly. The second parameter is a table of key-value pairs, each of which provides Loggly with optional configuration data:
Key | Default | Notes |
---|---|---|
id | agent ID | A unique ID for the device/agent |
tags | electricimp | A comma separated list of tags for all log events |
timeout | 15 | Time between sends (in seconds) |
limit | 100 | Maximum number of logs that will be queued before sending |
debug | true | Enables/disables also posting log messages with server.log() |
In the example above, we replace the default tag, re-set the timeout period to 60 seconds and reduce the size of the log queue.
The class has three logging methods: log(), warn() and err(), respectively intended for general log messages, warnings and encountered errors. Each takes either a standard printf string or a table of key-value pairs:
// Log information sent from the device
device.on("data", function(data) {
loggly.log({
"timestamp" : Loggly.ISODateTime(data.ts),
"power" : data.voltage,
"rssi" : data.rssi,
"msg" : data.msg
});
});
This approach is not dissimilar to the Electric Imp device logging system. Using a service like Loggly, however, allows you to capture richer, more structured data than server.log() and server.error() allow, and to do so from production agents and devices.
Electric Imp’s Loggly library is freely available to be included in your application. Electric Imp regularly releases new libraries that support popular cloud services. However, even if your preferred log management tool is not yet supported with a code library, it is not difficult to create one of your own using the imp API’s tools for making HTTP requests to remote services.
Posting data to Loggly is just a matter for collating appropriate local state information and posting it via one of the methods discussed above. That information can range from rudimentary state information — ‘this is what I am doing at the moment’ — to more important status messages, such as ‘I am unable to access an API I need to access’.
In the latter case, typically triggered by a response containing an HTTP status code other than 200 (‘OK’), your agent code might post a warning message to Loggly to issue a warning message to your application monitors, perhaps via a function:
// Log a warning
function logWarning(response) {
// 'response' is the table passed into the callback function
// specified when making an HTTP request using httprequst.sendasync
// (see https://electricimp.com/api/httprequest/sendasync)
loggly.warn({
"timestamp" : Loggly.ISODateTime(data.ts),
"code" : response.statuscode,
"headers" : response.headers,
"message" : response.body
});
}
Temporal data included in each post allows events to be compared across agents. For example, if a large number of agents all report that their attempts to connect to a third-party API are failing, analysis may reveal that these access attempts are being made simultaneously — and the service provider is rate-limiting access to the account being used. Perhaps the agent code needs to spread the posts over time and thus avoid the rate-limit — or maybe the customer needs to upgrade their API account to one with more generous rate-limit terms.
Squirrel’s time() function provides a Unix-style timestamp. The Loggly class method ISODateTime() converts such a timestamp into the ISO 8601 format Loggly requires.
Current application state information might not warrant a severity level, but issues which have the potential to impact application performance and thus customer satisfaction do. Only you can determine the severity of a given incident for you application, but the Loggly library’s methods provide an easy mechanism to categorize log messages posted to the service.
Electric Imp’s imp API provides a number of useful tools to help you with application monitoring. Each device is uniquely identifiable and so is its agent — their IDs can be accessed through imp API calls in code: http.agenturl() and hardware.getdeviceid(), respectively. That data can be included with log posts, as can useful information such as the amount of free memory available on the device (imp.getmemoryfree()). Network information is available via imp.net.info(). This is in addition to your own application’s data and parameters.
New Relic Insights provides a platform to which your application can post event-related data — essentially any and every action that the application takes for each end-user. This allows you to collect information on usage patterns, on the performance of specific sections of your code and of various APIs it might access. New Relic Insights allows you to examine that information visually, the better to spot trends and potential issues. Since the data is coming from every application instance, at both agent and device level, you can quickly build up a vital picture of your application is performing in the real world.
Again, Electric Imp provides a code library to simplify the process of posting event information to your New Relic Insights account. To load the New Relics Insights library, use a standard #require statement at the top of your code and then instantiate an object from the class as a global variable:
#require "NewRelicInsights.class.nut:1.0.0"
insights <- NewRelicInsights("<YOUR_NRI_ACCOUNT_NUMBER>",
"<YOUR_NRI_API_KEY>",
"<YOUR_APPLICATION_NAME>");
The first two parameters are both strings: your account and API key credentials, which you receive when you sign up with the service. The third parameter is a unique string that identifies your application.
The class a single method: sendEvent(), which takes three parameters. The first is a string identifying the type of event you are posting. This is for you to define and set. You might, say, use "api_access"
to indicate an attempt by the agent to communicate with a third-party cloud service.
The second parameter is also for you to set: you pass in whatever data generated by the event that you wish to post to New Relics Insight. Typically, this is a table of data: a timestamp, perhaps (using the example above) the name of the API, and the parameters of the call your agent attempted to make to it.
These parameters are mandatory; the third is optional. It is reference to a function that will be called when the event information has been posted. This callback has a single parameter: a table with two keys, err and data. If you don’t pass such a function into sendEvent(), it will block while the data is posted and then return the above table.
// Post a device-level event to NRI
device.on("temp", function(data) {
local insightsData = {
"ts" : data.timestamp,
"temp" : data.temperature
};
// Send to NRI
insights.sendEvent("temperature", insightsData, function(err, result) {
// If there was an issue, report it
if (err != null) {
server.error(err);
return;
}
// Otherwise, log success
server.log("Success");
});
});
How Electric Imp Monitors the Public impCloud — What we measure to check the health of our platform