Metrics Chrome Extension API Proposal

Overview

Chrome includes a system for collecting metrics about how various features of the application are used.  This system provides useful information to the development team when deciding what changes or additions should be included in the product, as well as helping to prioritize this work.


Exposing this system to developers of chrome-internal extensions can provide the same benefit to this group, allowing them to make better and more informed decisions about how to evolve their extensions.


Use Cases

We are developing a complex Chrome Extension and want to to track which features are used, which are most popular, in what combination they are used, and other similar non-personally-identifiable statistics.


For example, the extension may include a button that opens a tab to google.com.  We would like to track how often users press this button.  As another example, the extension might want to track how long it takes to load google.com whenever this button is pressed.


Could this API be part of the web platform?

Probably not.  Most web applications track usage statistics through server logs rather than sending separate tracking information.  Additionally, because access to the metrics server is restricted to Google, metrics collection is only useful to chrome-internal extensions.


Do you expect this API to be fairly stable?

This API is expected to be very stable.  Should a new metric type be needed in the future, the MetricsType structure (see below) is easily extended in a backward compatible way.


What UI does this API expose?

This API proposal does not expose any UI elements.


How could this API be abused?

This API exposes chrome's internal metrics system to extensions, and therefore shares its security and privacy issues and solutions.  To prevent extensions from affecting chrome's internal metrics or the metrics of other extensions, the implementation will append the extension's id to all metric names.


An extension could try to declare too many metrics, causing too much memory to be used.  This would affect both the extension process and the browser process.  The implementation could impose an upper limit on the number of metrics that an extension can define.  This API is hidden behind the command line argument used for experimental APIs.


How would you implement your desired features if this API didn't exist?

If this API did not exist, we would need to re-implement existing chrome code as a combination of javascript and NPAPI plugin.


Are you willing and able to develop and maintain this API?

Yes.


Draft API spec

Use the chrome.experimental.metrics module to collect metrics for your extension.

Methods

getEnabled
chrome.experimental.metrics.getEnabled(function callback)

Returns a boolean indicating if the user agreed to send metrics.

Parameters
callback (function (enabled))
Function called with enabled being true or false depending if the user agreed to send metrics or not.


setEnabled
chrome.experimental.metrics.setEnabled(boolean value, optional function callback)

Enable or disable recording and sending metrics.

Parameters
value (boolean)
Whether Chrome should record metrics and send them.

callback (function (enabled))
Function called after setting the value, with enabled being the value set in the settings. This value should be the same as value above, unless if there were errors.


recordUserAction
chrome.experimental.metrics.recordUserAction(string name)

Records an action performed by the user, such as "reload page", "close tab", "show bookmarks".

Parameters
name (string)
The name of the action performed.  Each action should have a unique name within the extension.


recordValue
chrome.experimental.metrics.recordValue(object metricType, optional integer value)

Records a value for the metric specified.

The first time that a value is recorded, the metrics module will remember the name, type, min/max, and number of buckets assigned to the specific metric, and the name becomes its unique identifier.  It is an error to record another value using the same name but with different values for type, min/max, or number of buckets.

Parameters
metricType (object)
metricName (string)
The name of the metric.  Each metric should have a unique name within the extension.

type (string)
The type of metric.  Valid possibilities:
  • histogram-log: use this type to record metrics that may have a different numerical value each time they are recorded, like the elapsed time for an operation.  A logarithmic bucket layout provides more granularity for lower values than upper values
  • histogram-linear: similar to the histogram-log type, except that a linear bucket layout provides equal granularity for lower and upper values

min (integer)
The minimum sample value to be recoded.  Lower recorded values go into an underflow bucket.

max (integer)
The maximum sample value to be recoded.  Higher recorded values go into an overflow bucket.

buckets (integer)
The number of buckets to use when separating the recordings.
value (integer)
A value to record for this metric.


recordPercentage
chrome.experimental.metrics.recordPercentage(string metricName, integer value)

Records a percentage metric whose values range from 1 to 100.  This is a convenience wrapper around recordValue() with type/min/max/buckets equals to linear/1/101/102.

Parameters
metricName (string)
The name of the metric.  Each metric should have a unique name within the extension.

value (integer)
The percentage value to record for this metric.


recordTime
chrome.experimental.metrics.recordTime(string metricName, integer value)

Records a metric that measures elapsed times that are usually less than 10 seconds.  This is a convenience wrapper around recordValue() with type/min/max/buckets equals to log/1/10000/50.

Parameters
metricName (string)
The name of the metric.  Each metric should have a unique name within the extension.

value (integer)
The elapsed time to record expressed in milliseconds.


recordMediumTime
chrome.experimental.metrics.recordMediumTime(string metricName, integer value)

Similar to recordTime(), but meant for elapsed times up to 3 minutes.  This is a convenience wrapper around recordValue() with type/min/max/buckets equals to log/1/180000/50.

Parameters
metricName (string)
The name of the metric.  Each metric should have a unique name within the extension.

value (integer)
The elapsed time to record expressed in milliseconds.


recordLongTime
chrome.experimental.metrics.recordLongTime(string metricName, integer value)

Similar to recordTime(), but meant for elapsed times up to 1 hour.  This is a convenience wrapper around recordValue() with type/min/max/buckets equals to log/1/3600000/50.

Parameters
metricName (string)
The name of the metric.  Each metric should have a unique name within the extension.

value (integer)
The elapsed time to record expressed in milliseconds.


recordCount
chrome.experimental.metrics.recordCount(string metricName, integer value)

Records a metric whose values are usually less than 1 million.  This is a convenience wrapper around recordValue() with type/min/max/buckets equals to log/1/1000000/50.

Parameters
metricName (string)
The name of the metric.  Each metric should have a unique name within the extension.

value (integer)
The value to record.


recordSmallCount
chrome.experimental.metrics.recordSmallCount(string metricName, integer value)

Similar to recordCount(), but meant for values up to 100.  This is a convenience wrapper around recordValue() with type/min/max/buckets equals to log/1/100/50.

Parameters
metricName (string)
The name of the metric.  Each metric should have a unique name within the extension.

value (integer)
The value to record.


recordMediumCount
chrome.experimental.metrics.recordMediumCount(string metricName, integer value)

Similar to recordCount(), but meant for values up to 10000.  This is a convenience wrapper around recordValue() with type/min/max/buckets equals to log/1/10000/50.

Parameters
metricName (string)
The name of the metric.  Each metric should have a unique name within the extension.

value (integer)
The value to record.


A more formal description of the API and some sample code usage.

{
 "namespace": "experimental.metrics",

 "types": [
   {
     "id": "MetricType",
     "type": "object",
     "description": "Describes the type of metric that is to be collected.",
     "properties": {
       "metricName": {"type": "string"},
       "type": {"type": "string", "enum": ["histogram-log", "histogram-linear"]},
       "min": {"type", "integer"},
       "max": {"type", "integer"},
       "buckets": {"type", "integer"}
     }
   }
 ],

 "functions": [
   {
     "name": "recordUserAction",
     "type": "function",
     "description": "Records an action performed by the user.",
     "parameters": [
       {"name": "name", "type": "string"}
     ]
   },
   {
     "name": "recordValue",
     "type": "function",
     "description": "Adds a value to the given metric.",
     "parameters": [
       {"name": "metric", "$ref": "MetricType"},
       {"name": "value", "type": "integer"}
     ]
   },
   {
     "name": "recordPercentage",
     "type": "function",
     "description": "Adds a percentage value to the given metric.",
     "parameters": [
       {"name": "metricName", "type": "string"},
       {"name": "value", "type": "integer"}
     ]
   },
   {
     "name": "recordCount",
     "type": "function",
     "description": "Adds a value (less than 1 million) to the given metric.",
     "parameters": [
       {"name": "metricName", "type": "string"},
       {"name": "value", "type": "integer"}
     ]
   },
   {
     "name": "recordSmallCount",
     "type": "function",
     "description": "Adds a small value (less than 100) to the given metric.",
     "parameters": [
       {"name": "metricName", "type": "string"},
       {"name": "value", "type": "integer"}
     ]
   },
   {
     "name": "recordMediumCount",
     "type": "function",
     "description": "Adds a medium value (less than 10000) to the given metric.",
     "parameters": [
       {"name": "metricName", "type": "string"},
       {"name": "value", "type": "integer"}
     ]
   },
   {
     "name": "recordTime",
     "type": "function",
     "description": "Adds an elapsed time (less than 10 sec) to the given metric.",
     "parameters": [
       {"name": "metricName", "type": "string"},
       {"name": "value", "type": "integer"}
     ]
   },
   {
     "name": "recordMediumTime",
     "type": "function",
     "description": "Adds an elapsed time (less than 3 minutes) to the given metric.",
     "parameters": [
       {"name": "metricName", "type": "string"},
       {"name": "value", "type": "integer"}
     ]
   },
   {
     "name": "recordLongTime",
     "type": "function",
     "description": "Adds an elapsed time (less than 1 hour) to the given metric.",
     "parameters": [
       {"name": "metricName", "type": "string"},
       {"name": "value", "type": "integer"}
     ]
   }
 ]
}

Sample code:


var myCaloricIntake = {
 metricName: 'rogerta.calories',
 type: 'histogram-log'
 min: 1,
 max: 3000
};

function eatCarrot() {
  chrome.experimental.metrics.recordValue(myCaloricIntake, 1);
}

function eatCake() {
  chrome.experimental.metrics.recordValue(myCaloricIntake, 1000);
}

function wentJogging(km, minutes) {
  chrome.experimental.metrics.recordUserAction('rogerta.jogs');
  chrome.experimental.metrics.recordSmallCount('rogerta.jobs.km', km);
  chrome.experimental.metrics.recordLongTime('rogerta.jogs.duration', minutes * 60 * 1000);
}




Comments