blob: 6d709b79683ccbc53a07562673481b37f961d851 [file] [log] [blame]
/*
* Chrome OS Metrics - collect and record metrics data through UMA
*
* This file initially created by Google, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#if 0
#define CONFIG_PSB_SUPPORT /* enable Public Safety Band support */
#endif
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <metrics/c_metrics_library.h>
#define CONNMAN_API_SUBJECT_TO_CHANGE
#include <connman/assert.h>
#include <connman/plugin.h>
#include <connman/notifier.h>
#include <connman/network.h>
#include <connman/service.h>
#include <connman/log.h>
#define _DBG_METRICS(fmt, arg...) DBG(DBG_METRICS, fmt, ## arg)
#define METRIC_NAME_LEN 80 /* max size of a constructed name */
static CMetricsLibrary lib = NULL;
const char *kMetricNetworkTimeToDropName = "Network.TimeToDrop";
const int kMetricNetworkTimeToDropMin = 1;
const int kMetricNetworkTimeToDropMax = 8 * 60 * 60; /* 8 hours */
const int kMetricNetworkTimeToDropBuckets = 50;
const char *kMetricNetworkTimeOnlineName = "Network.%c%s.TimeOnline";
const int kMetricNetworkTimeOnlineMin = 1;
const int kMetricNetworkTimeOnlineMax = 8 * 60 * 60; /* 8 hours */
const int kMetricNetworkTimeOnlineBuckets = 50;
const char *kMetricNetworkTimeToJoinName = "Network.%c%s.TimeToJoin";
const char *kMetricNetworkTimeToConfigName = "Network.%c%s.TimeToConfig";
const char *kMetricNetworkTimeToOnline = "Network.%c%s.TimeToOnline";
const char *kMetricNetworkTimeToPortal = "Network.%c%s.TimeToPortal";
const char *kMetricNetworkTimeResumeToReady = "Network.%c%s.TimeResumeToReady";
const char *kMetricNetworkServiceErrors = "Network.ServiceErrors";
const int kMetricNetworkServiceErrorsMax = CONNMAN_SERVICE_ERROR_MAX;
const char *kMetricNetworkSecurity = "Network.%c%s.Security";
const int kMetricNetworkSecurityMax = CONNMAN_SERVICE_SECURITY_MAX;
const char *kMetricNetworkAuthMode = "Network.%c%s.AuthMode";
enum connman_authmode {
CONNMAN_AUTHMODE_UNKNOWN = 0,
CONNMAN_AUTHMODE_EAP_AKA = 1,
CONNMAN_AUTHMODE_EAP_FAST = 2,
CONNMAN_AUTHMODE_EAP_GPSK = 3,
CONNMAN_AUTHMODE_EAP_GTC = 4,
CONNMAN_AUTHMODE_EAP_IKEV2 = 5,
CONNMAN_AUTHMODE_EAP_LEAP = 6,
CONNMAN_AUTHMODE_EAP_MD5 = 7,
CONNMAN_AUTHMODE_EAP_MSCHAPV2 = 8,
CONNMAN_AUTHMODE_EAP_OTP = 9,
CONNMAN_AUTHMODE_EAP_PAX = 10,
CONNMAN_AUTHMODE_EAP_PEAP = 11,
CONNMAN_AUTHMODE_EAP_PSK = 12,
CONNMAN_AUTHMODE_EAP_SAKE = 13,
CONNMAN_AUTHMODE_EAP_SIM = 14,
CONNMAN_AUTHMODE_EAP_TLS = 15,
CONNMAN_AUTHMODE_EAP_TNC = 16,
CONNMAN_AUTHMODE_EAP_TTLS = 17,
CONNMAN_AUTHMODE_MAX,
};
const int kMetricNetworkAuthModeMax = CONNMAN_AUTHMODE_MAX;
enum connman_channel {
CONNMAN_CHANNEL_UNDEF = 0,
CONNMAN_CHANNEL_2412 = 1,
CONNMAN_CHANNEL_2417 = 2,
CONNMAN_CHANNEL_2422 = 3,
CONNMAN_CHANNEL_2427 = 4,
CONNMAN_CHANNEL_2432 = 5,
CONNMAN_CHANNEL_2437 = 6,
CONNMAN_CHANNEL_2442 = 7,
CONNMAN_CHANNEL_2447 = 8,
CONNMAN_CHANNEL_2452 = 9,
CONNMAN_CHANNEL_2457 = 10,
CONNMAN_CHANNEL_2462 = 11,
CONNMAN_CHANNEL_2467 = 12,
CONNMAN_CHANNEL_2472 = 13,
CONNMAN_CHANNEL_2484 = 14,
CONNMAN_CHANNEL_5180 = 15,
CONNMAN_CHANNEL_5200 = 16,
CONNMAN_CHANNEL_5220 = 17,
CONNMAN_CHANNEL_5240 = 18,
CONNMAN_CHANNEL_5260 = 19,
CONNMAN_CHANNEL_5280 = 20,
CONNMAN_CHANNEL_5300 = 21,
CONNMAN_CHANNEL_5320 = 22,
CONNMAN_CHANNEL_5500 = 23,
CONNMAN_CHANNEL_5520 = 24,
CONNMAN_CHANNEL_5540 = 25,
CONNMAN_CHANNEL_5560 = 26,
CONNMAN_CHANNEL_5580 = 27,
CONNMAN_CHANNEL_5600 = 28,
CONNMAN_CHANNEL_5620 = 29,
CONNMAN_CHANNEL_5640 = 30,
CONNMAN_CHANNEL_5660 = 31,
CONNMAN_CHANNEL_5680 = 32,
CONNMAN_CHANNEL_5700 = 33,
CONNMAN_CHANNEL_5745 = 34,
CONNMAN_CHANNEL_5765 = 35,
CONNMAN_CHANNEL_5785 = 36,
CONNMAN_CHANNEL_5805 = 37,
CONNMAN_CHANNEL_5825 = 38,
CONNMAN_CHANNEL_5170 = 39,
CONNMAN_CHANNEL_5190 = 40,
CONNMAN_CHANNEL_5210 = 41,
CONNMAN_CHANNEL_5230 = 42,
/* NB: ignore old 11b bands 2312..2372 and 2512..2532 */
/* NB: ignore regulated bands 4920..4980 and 5020..5160 */
#ifdef CONFIG_PSB_SUPPORT
CONNMAN_CHANNEL_PSB_4940 = 43, /* NB: PSB center freq's are +.5MHz */
CONNMAN_CHANNEL_PSB_4941 = 44,
CONNMAN_CHANNEL_PSB_4942 = 45,
CONNMAN_CHANNEL_PSB_4943 = 46,
CONNMAN_CHANNEL_PSB_4944 = 47,
CONNMAN_CHANNEL_PSB_4947 = 48,
CONNMAN_CHANNEL_PSB_4952 = 49,
CONNMAN_CHANNEL_PSB_4957 = 50,
CONNMAN_CHANNEL_PSB_4962 = 51,
CONNMAN_CHANNEL_PSB_4967 = 52,
CONNMAN_CHANNEL_PSB_4972 = 53,
CONNMAN_CHANNEL_PSB_4977 = 54,
CONNMAN_CHANNEL_PSB_4982 = 55,
CONNMAN_CHANNEL_PSB_4985 = 56,
CONNMAN_CHANNEL_PSB_4986 = 57,
CONNMAN_CHANNEL_PSB_4987 = 58,
CONNMAN_CHANNEL_PSB_4988 = 59,
CONNMAN_CHANNEL_PSB_4989 = 60,
#endif
CONNMAN_CHANNEL_MAX
};
const char *kMetricNetworkChannel = "Network.%c%s.Channel";
const int kMetricNetworkChannelMax = CONNMAN_CHANNEL_MAX;
const char *kMetricNetworkPhyMode = "Network.%c%s.PhyMode";
const int kMetricNetworkPhyModeMax = CONNMAN_NETWORK_PHYMODE_MAX;
static int is_suspended = FALSE;
/*
* Set to the current time when resume event occurs. Cleared when the ready
* state is reached.
*/
static GTimeVal time_of_resume;
static void metrics_system_suspend(void)
{
_DBG_METRICS("");
is_suspended = TRUE;
}
static void metrics_system_resume(void)
{
_DBG_METRICS("");
is_suspended = FALSE;
g_get_current_time(&time_of_resume);
}
static void clear_time_of_resume()
{
memset(&time_of_resume, 0, sizeof(time_of_resume));
}
static connman_bool_t is_timeset(const GTimeVal *tv)
{
return !(tv->tv_sec == 0 && tv->tv_usec == 0);
}
static void __network_metric_name(char *name, size_t name_len,
const char *type, const char *key)
{
/* NB: we append the network type but capitalized */
/* TODO(sleffler) maybe map type entirely; e.g. "wifi" -> "WiFi" */
g_snprintf(name, name_len, key, toupper(type[0]), type+1);
}
static void network_metric_name(char *name, size_t name_len,
const struct connman_service *service, const char *key)
{
const char *type = connman_service_get_type(service);
CONNMAN_ASSERT(type != NULL);
__network_metric_name(name, name_len, type, key);
}
static void send_to_uma(const char *name, int value, int min, int max,
int nbuckets)
{
if (!CMetricsLibrarySendToUMA(lib, name, value, min, max, nbuckets))
connman_error("error sending metric %s", name);
}
static void send_enum_to_uma(const char *name, int value, int max)
{
if (!CMetricsLibrarySendEnumToUMA(lib, name, value, max))
connman_error("error sending metric %s", name);
}
static void metrics_default_changed(struct connman_service *service)
{
static int was_online = FALSE; /* NB: start offline */
static time_t last_change = 0;
static time_t last_default_change = 0;
static const char *last_default_type = "";
time_t now = time(NULL);
const char *type;
_DBG_METRICS("service %s was_online %d is_suspended %d delta %d secs",
service != NULL ? connman_service_get_identifier(service) : NULL,
was_online, is_suspended, (int)(now - last_change));
/*
* NB: we cannot record the service ptr w/o holding a
* reference as the device may go away (e.g. a usb device).
* But we just need the type and connman_service_get_type
* is known to return ptr's to stable storage so just use
* that to detect technology switching.
*/
type = connman_service_get_type(service);
if (g_strcmp0(type, last_default_type) != 0) {
/*
* Type of default service changed; calculate the
* TimeOnline for the previous service type so we can track
* how much time is spent on each technology with:
*
* PerCent(sum(TimeOnline.<tech>), sum(TimeOnline.*))
*
* Note we do not include time suspended so this time is
* real time on the network (unlike TimeToDrop).
*/
if (g_strcmp0(last_default_type, "") != 0) {
char name[METRIC_NAME_LEN];
__network_metric_name(name, sizeof(name),
last_default_type, kMetricNetworkTimeOnlineName);
send_to_uma(name, now - last_default_change,
kMetricNetworkTimeOnlineMin,
kMetricNetworkTimeOnlineMax,
kMetricNetworkTimeOnlineBuckets);
_DBG_METRICS("send_to_uma %s %d",
name, (int)(now - last_default_change));
}
last_default_type = type;
last_default_change = now;
}
/*
* Ignore changes when suspending. This assumes the suspend
* notification arrives before the change to the default service
* state which looks to be true.
*/
if (is_suspended)
return;
/*
* Ignore changes that are not online/offline transitions; e.g.
* switching between wired and wireless. TimeToDrop measures
* time online regardless of how we are connected.
*/
if ((service == NULL && !was_online) || (service != NULL && was_online))
return;
if (service == NULL) {
/*
* Calculate overall TimeToDrop; this is the time spent
* online ignoring transitions between devices.
*/
send_to_uma(kMetricNetworkTimeToDropName, now - last_change,
kMetricNetworkTimeToDropMin,
kMetricNetworkTimeToDropMax,
kMetricNetworkTimeToDropBuckets);
}
was_online = (service != NULL);
last_change = now;
}
static void send_msec_to_uma(const struct connman_service *service,
const char *key, long secs, long usecs)
{
long msecs;
char name[METRIC_NAME_LEN];
msecs = secs * 1000 + (usecs / 1000);
network_metric_name(name, sizeof(name), service, key);
send_to_uma(name, (int) msecs, 1, 45 * 1000, 50);
}
static void send_state_delta(const struct connman_service *service,
const char *key,
enum connman_service_state from, enum connman_service_state to)
{
long secs, usecs;
connman_service_get_time_delta(service, from, to, &secs, &usecs);
send_msec_to_uma(service, key, secs, usecs);
}
static void send_service_error(const struct connman_service *service)
{
const enum connman_service_error error =
connman_service_get_error(service);
_DBG_METRICS("error %d", error);
send_enum_to_uma(kMetricNetworkServiceErrors, error,
kMetricNetworkServiceErrorsMax);
}
static void maybe_send_resume_to_ready(const struct connman_service *service)
{
long secs, usecs;
if (!is_timeset(&time_of_resume))
return;
connman_service_get_time(service, CONNMAN_SERVICE_STATE_READY, &secs,
&usecs);
secs -= time_of_resume.tv_sec;
usecs -= time_of_resume.tv_usec;
if (usecs < 0) {
secs--;
usecs += 1000 * 1000;
}
_DBG_METRICS("secs %ld usecs %ld", secs, usecs);
send_msec_to_uma(service, kMetricNetworkTimeResumeToReady, secs, usecs);
}
static enum connman_authmode map_authmode(const char *mode)
{
/* NB: order some more common ones first */
if (g_strcmp0(mode, "EAP-TLS") == 0)
return CONNMAN_AUTHMODE_EAP_TLS;
if (g_strcmp0(mode, "EAP-TTLS") == 0)
return CONNMAN_AUTHMODE_EAP_TTLS;
if (g_strcmp0(mode, "EAP-MSCHAPV2") == 0)
return CONNMAN_AUTHMODE_EAP_MSCHAPV2;
if (g_strcmp0(mode, "EAP-MD5") == 0)
return CONNMAN_AUTHMODE_EAP_MD5;
if (g_strcmp0(mode, "EAP-AKA") == 0)
return CONNMAN_AUTHMODE_EAP_AKA;
if (g_strcmp0(mode, "EAP-FAST") == 0)
return CONNMAN_AUTHMODE_EAP_FAST;
if (g_strcmp0(mode, "EAP-GPSK") == 0)
return CONNMAN_AUTHMODE_EAP_GPSK;
if (g_strcmp0(mode, "EAP-GTC") == 0)
return CONNMAN_AUTHMODE_EAP_GTC;
if (g_strcmp0(mode, "EAP-IKEV2") == 0)
return CONNMAN_AUTHMODE_EAP_IKEV2;
if (g_strcmp0(mode, "EAP-LEAP") == 0)
return CONNMAN_AUTHMODE_EAP_LEAP;
if (g_strcmp0(mode, "EAP-OTP") == 0)
return CONNMAN_AUTHMODE_EAP_OTP;
if (g_strcmp0(mode, "EAP-PAX") == 0)
return CONNMAN_AUTHMODE_EAP_PAX;
if (g_strcmp0(mode, "EAP-PEAP") == 0)
return CONNMAN_AUTHMODE_EAP_PEAP;
if (g_strcmp0(mode, "EAP-PSK") == 0)
return CONNMAN_AUTHMODE_EAP_PSK;
if (g_strcmp0(mode, "EAP-SAKE") == 0)
return CONNMAN_AUTHMODE_EAP_SAKE;
if (g_strcmp0(mode, "EAP-SIM") == 0)
return CONNMAN_AUTHMODE_EAP_SIM;
if (g_strcmp0(mode, "EAP-TNC") == 0)
return CONNMAN_AUTHMODE_EAP_TNC;
if (mode != NULL)
connman_warn("%s: unknown authmode %s", __func__, mode);
return CONNMAN_AUTHMODE_UNKNOWN;
}
static void send_service_security(struct connman_service *service)
{
const enum connman_service_security security =
connman_service_get_security(service);
char name[METRIC_NAME_LEN];
_DBG_METRICS("security %d", security);
network_metric_name(name, sizeof(name), service,
kMetricNetworkSecurity);
send_enum_to_uma(name, security, kMetricNetworkSecurityMax);
/*
* For 802.1x security send the negotiated EAP method.
*/
if (security == CONNMAN_SERVICE_SECURITY_802_1X) {
const enum connman_authmode authmode = map_authmode(
connman_service_get_authmode(service));
_DBG_METRICS("authmode %d", authmode);
network_metric_name(name, sizeof(name), service,
kMetricNetworkAuthMode);
send_enum_to_uma(name, authmode, kMetricNetworkAuthModeMax);
}
}
/*
* Map WiFi frequency to UMA enum value.
*/
static enum connman_channel __map_frequency(int frequency)
{
if (2412 <= frequency && frequency <= 2472) {
if (((frequency - 2412) % 5) == 0)
return CONNMAN_CHANNEL_2412 + (frequency - 2412) / 5;
} else if (frequency == 2484) {
return CONNMAN_CHANNEL_2484;
} else if (5170 <= frequency && frequency <= 5230) {
if ((frequency % 20) == 0)
return CONNMAN_CHANNEL_5180 + (frequency - 5180) / 20;
if ((frequency % 20) == 10)
return CONNMAN_CHANNEL_5170 + (frequency - 5170) / 20;
/* NB: fall through to return undefined */
} else if (5240 <= frequency && frequency <= 5320) {
if (((frequency - 5180) % 20) == 0)
return CONNMAN_CHANNEL_5180 + (frequency - 5180) / 20;
} else if (5500 <= frequency && frequency <= 5700) {
if (((frequency - 5500) % 20) == 0)
return CONNMAN_CHANNEL_5500 + (frequency - 5500) / 20;
} else if (5745 <= frequency && frequency <= 5825) {
if (((frequency - 5745) % 20) == 0)
return CONNMAN_CHANNEL_5745 + (frequency - 5745) / 20;
#ifdef CONFIG_PSB_SUPPORT
} else if (4940 <= frequency && frequency <= 4990) {
/* Public Safety Band */
return CONNMAN_CHANNEL_PSB_4940 + (frequency * 10) +
((frequency % 5) == 2 ? 5 : 0) - 49400) / 5;
#endif
}
connman_info("%s: no mapping for WiFi frequency %d", __func__,
frequency);
return CONNMAN_CHANNEL_UNDEF;
}
static void send_service_channel(struct connman_service *service)
{
int frequency = connman_service_get_frequency(service);
enum connman_channel channel;
char name[METRIC_NAME_LEN];
network_metric_name(name, sizeof(name), service, kMetricNetworkChannel);
/*
* Map frequency to UMA enum value.
*/
channel = __map_frequency(frequency);
_DBG_METRICS("map %d to %d", frequency, channel);
send_enum_to_uma(name, channel, kMetricNetworkChannelMax);
}
static void send_service_phymode(struct connman_service *service)
{
enum connman_network_phymode phymode =
connman_service_get_phymode(service);
char name[80];
network_metric_name(name, sizeof(name), service, kMetricNetworkPhyMode);
send_enum_to_uma(name, phymode, kMetricNetworkPhyModeMax);
}
static void metrics_service_state_changed(struct connman_service *service)
{
const char *state = connman_service_get_state(service);
const char *type = connman_service_get_type(service);
_DBG_METRICS("service %s state %s", service != NULL ?
connman_service_get_identifier(service) : NULL, state);
if (strcmp(state, "failure") == 0)
send_service_error(service);
if (strcmp(state, "online") == 0) {
/*
* Time to online is the time from when the network is
* marked "ready" until we can retrieve a webpage
* indicating connectivity.
*/
send_state_delta(service, kMetricNetworkTimeToOnline,
CONNMAN_SERVICE_STATE_READY,
CONNMAN_SERVICE_STATE_ONLINE);
return;
}
if (strcmp(state, "portal") == 0) {
/*
* Time to portal is usually the same as the timeout
* used for portal detection, but it will be less when
* the restricted network hijacks DNS and redirects
* HTTP requests to a captive portal webserver.
*/
send_state_delta(service, kMetricNetworkTimeToPortal,
CONNMAN_SERVICE_STATE_READY,
CONNMAN_SERVICE_STATE_PORTAL);
return;
}
if (strcmp(state, "ready") != 0)
return;
if (strcmp(type, "wifi") == 0) {
/*
* Time to join a network covers scan+auth+assoc. This
* is presently provided only for wifi networks; might
* want to collect this for other networks.
*/
send_state_delta(service, kMetricNetworkTimeToJoinName,
CONNMAN_SERVICE_STATE_ASSOCIATION,
CONNMAN_SERVICE_STATE_CONFIGURATION);
/*
* If this is the first time the WiFi network is ready after a
* resume, send the time from the resume event to the ready
* event.
*/
maybe_send_resume_to_ready(service);
/*
* BSS channel (frequency); mapped to an enum.
*/
send_service_channel(service);
/*
* Description of the channel capabilities.
*/
send_service_phymode(service);
/*
* All services have a security type, but this is
* really only meaningful for WiFi.
*/
send_service_security(service);
}
/*
* Subsequent connections without a resume should not report a
* TimeResumeToReady.
*/
clear_time_of_resume();
/*
* Time to config a network covers Layer 3 configuration
* work (typically acquiring a DHCP lease).
*/
send_state_delta(service, kMetricNetworkTimeToConfigName,
CONNMAN_SERVICE_STATE_CONFIGURATION,
CONNMAN_SERVICE_STATE_READY);
}
static struct connman_notifier metrics_notifier = {
.name = "metrics",
.priority = CONNMAN_NOTIFIER_PRIORITY_DEFAULT,
.default_changed = metrics_default_changed,
.service_state_changed = metrics_service_state_changed,
.system_suspend = metrics_system_suspend,
.system_resume = metrics_system_resume,
};
static int metrics_init(void)
{
if (connman_notifier_register(&metrics_notifier) < 0) {
connman_error("Failed to register metrics notifier");
return -1;
}
lib = CMetricsLibraryNew(); /* NB: does not return NULL */
CMetricsLibraryInit(lib);
clear_time_of_resume();
return 0;
}
static void metrics_finis(void)
{
CMetricsLibraryDelete(lib);
connman_notifier_unregister(&metrics_notifier);
}
CONNMAN_PLUGIN_DEFINE(crosmetrics, "Chrome OS metrics plugin", VERSION,
CONNMAN_PLUGIN_PRIORITY_DEFAULT, metrics_init, metrics_finis)