blob: f2046a35e50fb170140085507d1204f56d4fe783 [file] [log] [blame]
Darin Petkov65b01462010-04-14 13:32:20 -07001// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Darin Petkov65b01462010-04-14 13:32:20 -07005#include "metrics_library.h"
6
7#include <errno.h>
8#include <sys/file.h>
Ken Mixter4c5daa42010-08-26 18:35:06 -07009#include <sys/stat.h>
Darin Petkov4fcb2ac2010-04-15 16:40:23 -070010
11#include <cstdarg>
12#include <cstdio>
13#include <cstring>
Darin Petkov65b01462010-04-14 13:32:20 -070014
15#define READ_WRITE_ALL_FILE_FLAGS \
16 (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
17
Ken Mixter4c5daa42010-08-26 18:35:06 -070018static const char kAutotestPath[] = "/var/log/metrics/autotest-events";
19static const char kUMAEventsPath[] = "/var/log/metrics/uma-events";
20static const char kConsentFile[] = "/home/chronos/Consent To Send Stats";
Darin Petkov4fcb2ac2010-04-15 16:40:23 -070021static const int32_t kBufferSize = 1024;
Darin Petkov65b01462010-04-14 13:32:20 -070022
Ken Mixter4c5daa42010-08-26 18:35:06 -070023time_t MetricsLibrary::cached_enabled_time_ = 0;
24bool MetricsLibrary::cached_enabled_ = false;
25
David James3b3add52010-06-04 15:01:19 -070026using std::string;
Darin Petkov65b01462010-04-14 13:32:20 -070027
28// TODO(sosa@chromium.org) - use Chromium logger instead of stderr
Darin Petkov11b8eb32010-05-18 11:00:59 -070029static void PrintError(const char* message, const char* file,
Darin Petkov4fcb2ac2010-04-15 16:40:23 -070030 int code) {
David James3b3add52010-06-04 15:01:19 -070031 static const char kProgramName[] = "libmetrics";
Darin Petkov65b01462010-04-14 13:32:20 -070032 if (code == 0) {
33 fprintf(stderr, "%s: %s\n", kProgramName, message);
34 } else if (file == NULL) {
35 fprintf(stderr, "%s: ", kProgramName);
36 perror(message);
37 } else {
38 fprintf(stderr, "%s: %s: ", kProgramName, file);
39 perror(message);
40 }
41}
42
Darin Petkov11b8eb32010-05-18 11:00:59 -070043MetricsLibrary::MetricsLibrary()
Ken Mixter4c5daa42010-08-26 18:35:06 -070044 : uma_events_file_(NULL),
45 consent_file_(kConsentFile) {}
46
Ken Mixtereafbbdf2010-10-01 15:38:42 -070047// We take buffer and buffer_size as parameters in order to simplify testing
48// of various alignments of the |device_name| with |buffer_size|.
49bool MetricsLibrary::IsDeviceMounted(const char* device_name,
50 const char* mounts_file,
51 char* buffer,
52 int buffer_size,
53 bool* result) {
54 if (buffer == NULL || buffer_size < 1)
55 return false;
56 int mounts_fd = open(mounts_file, O_RDONLY);
57 if (mounts_fd < 0)
58 return false;
59 // match_offset describes:
60 // -1 -- not beginning of line
61 // 0..strlen(device_name)-1 -- this offset in device_name is next to match
62 // strlen(device_name) -- matched full name, just need a space.
63 int match_offset = 0;
64 bool match = false;
65 while (!match) {
66 int read_size = read(mounts_fd, buffer, buffer_size);
67 if (read_size <= 0) {
68 if (errno == -EINTR)
69 continue;
70 break;
71 }
72 for (int i = 0; i < read_size; ++i) {
73 if (buffer[i] == '\n') {
74 match_offset = 0;
75 continue;
76 }
77 if (match_offset < 0) {
78 continue;
79 }
80 if (device_name[match_offset] == '\0') {
81 if (buffer[i] == ' ') {
82 match = true;
83 break;
84 }
85 match_offset = -1;
86 continue;
87 }
88
89 if (buffer[i] == device_name[match_offset]) {
90 ++match_offset;
91 } else {
92 match_offset = -1;
93 }
94 }
95 }
96 close(mounts_fd);
97 *result = match;
98 return true;
99}
100
101bool MetricsLibrary::IsGuestMode() {
102 char buffer[256];
103 bool result = false;
104 if (!IsDeviceMounted("guestfs",
105 "/proc/mounts",
106 buffer,
107 sizeof(buffer),
108 &result)) {
109 return false;
110 }
111 return result;
112}
113
Ken Mixter4c5daa42010-08-26 18:35:06 -0700114bool MetricsLibrary::AreMetricsEnabled() {
115 static struct stat stat_buffer;
116 time_t this_check_time = time(NULL);
117
118 if (this_check_time != cached_enabled_time_) {
119 cached_enabled_time_ = this_check_time;
Ken Mixtereafbbdf2010-10-01 15:38:42 -0700120 if (stat(consent_file_, &stat_buffer) >= 0 &&
121 !IsGuestMode())
122 cached_enabled_ = true;
123 else
124 cached_enabled_ = false;
Ken Mixter4c5daa42010-08-26 18:35:06 -0700125 }
126 return cached_enabled_;
127}
Darin Petkov11b8eb32010-05-18 11:00:59 -0700128
129bool MetricsLibrary::SendMessageToChrome(int32_t length, const char* message) {
Ken Mixter4c5daa42010-08-26 18:35:06 -0700130 if (!AreMetricsEnabled())
131 return true;
132
Darin Petkov11b8eb32010-05-18 11:00:59 -0700133 int chrome_fd = open(uma_events_file_,
Darin Petkov65b01462010-04-14 13:32:20 -0700134 O_WRONLY | O_APPEND | O_CREAT,
135 READ_WRITE_ALL_FILE_FLAGS);
Darin Petkov4fcb2ac2010-04-15 16:40:23 -0700136 // If we failed to open it, return.
Darin Petkov65b01462010-04-14 13:32:20 -0700137 if (chrome_fd < 0) {
Darin Petkov11b8eb32010-05-18 11:00:59 -0700138 PrintError("open", uma_events_file_, errno);
Darin Petkov4fcb2ac2010-04-15 16:40:23 -0700139 return false;
Darin Petkov65b01462010-04-14 13:32:20 -0700140 }
141
Darin Petkov4fcb2ac2010-04-15 16:40:23 -0700142 // Need to chmod because open flags are anded with umask. Ignore the
143 // exit code -- a chronos process may fail chmoding because the file
144 // has been created by a root process but that should be OK.
145 fchmod(chrome_fd, READ_WRITE_ALL_FILE_FLAGS);
Darin Petkov65b01462010-04-14 13:32:20 -0700146
Darin Petkov4fcb2ac2010-04-15 16:40:23 -0700147 // Grab an exclusive lock to protect Chrome from truncating
148 // underneath us. Keep the file locked as briefly as possible.
Darin Petkov65b01462010-04-14 13:32:20 -0700149 if (flock(chrome_fd, LOCK_EX) < 0) {
Darin Petkov11b8eb32010-05-18 11:00:59 -0700150 PrintError("flock", uma_events_file_, errno);
Darin Petkov65b01462010-04-14 13:32:20 -0700151 close(chrome_fd);
Darin Petkov4fcb2ac2010-04-15 16:40:23 -0700152 return false;
Darin Petkov65b01462010-04-14 13:32:20 -0700153 }
154
Darin Petkov4fcb2ac2010-04-15 16:40:23 -0700155 bool success = true;
156 if (write(chrome_fd, message, length) != length) {
Darin Petkov11b8eb32010-05-18 11:00:59 -0700157 PrintError("write", uma_events_file_, errno);
Darin Petkov4fcb2ac2010-04-15 16:40:23 -0700158 success = false;
159 }
Darin Petkov65b01462010-04-14 13:32:20 -0700160
Darin Petkov4fcb2ac2010-04-15 16:40:23 -0700161 // Release the file lock and close file.
162 if (flock(chrome_fd, LOCK_UN) < 0) {
Darin Petkov11b8eb32010-05-18 11:00:59 -0700163 PrintError("unlock", uma_events_file_, errno);
Darin Petkov4fcb2ac2010-04-15 16:40:23 -0700164 success = false;
165 }
Darin Petkov65b01462010-04-14 13:32:20 -0700166 close(chrome_fd);
Darin Petkov4fcb2ac2010-04-15 16:40:23 -0700167 return success;
168}
169
Darin Petkov11b8eb32010-05-18 11:00:59 -0700170int32_t MetricsLibrary::FormatChromeMessage(int32_t buffer_size, char* buffer,
171 const char* format, ...) {
Darin Petkov4fcb2ac2010-04-15 16:40:23 -0700172 int32_t message_length;
173 size_t len_size = sizeof(message_length);
174
175 // Format the non-LENGTH contents in the buffer by leaving space for
176 // LENGTH at the start of the buffer.
177 va_list args;
178 va_start(args, format);
179 message_length = vsnprintf(&buffer[len_size], buffer_size - len_size,
180 format, args);
181 va_end(args);
182
183 if (message_length < 0) {
184 PrintError("chrome message format error", NULL, 0);
185 return -1;
186 }
187
188 // +1 to account for the trailing \0.
189 message_length += len_size + 1;
190 if (message_length > buffer_size) {
191 PrintError("chrome message too long", NULL, 0);
192 return -1;
193 }
194
195 // Prepend LENGTH to the message.
196 memcpy(buffer, &message_length, len_size);
197 return message_length;
198}
199
Darin Petkovfc91b422010-05-12 13:05:45 -0700200void MetricsLibrary::Init() {
Darin Petkov11b8eb32010-05-18 11:00:59 -0700201 uma_events_file_ = kUMAEventsPath;
Darin Petkovfc91b422010-05-12 13:05:45 -0700202}
203
Darin Petkovc2526a12010-04-21 14:24:04 -0700204bool MetricsLibrary::SendToAutotest(const string& name, int value) {
David James3b3add52010-06-04 15:01:19 -0700205 FILE* autotest_file = fopen(kAutotestPath, "a+");
Darin Petkov4fcb2ac2010-04-15 16:40:23 -0700206 if (autotest_file == NULL) {
207 PrintError("fopen", kAutotestPath, errno);
208 return false;
209 }
210
211 fprintf(autotest_file, "%s=%d\n", name.c_str(), value);
212 fclose(autotest_file);
213 return true;
214}
215
Darin Petkov21cd2c52010-05-12 15:26:16 -0700216bool MetricsLibrary::SendToUMA(const string& name, int sample,
217 int min, int max, int nbuckets) {
Darin Petkov4fcb2ac2010-04-15 16:40:23 -0700218 // Format the message.
219 char message[kBufferSize];
220 int32_t message_length =
221 FormatChromeMessage(kBufferSize, message,
Darin Petkovc2526a12010-04-21 14:24:04 -0700222 "histogram%c%s %d %d %d %d", '\0',
223 name.c_str(), sample, min, max, nbuckets);
Darin Petkov4fcb2ac2010-04-15 16:40:23 -0700224 if (message_length < 0)
225 return false;
226
227 // Send the message.
228 return SendMessageToChrome(message_length, message);
Darin Petkov65b01462010-04-14 13:32:20 -0700229}
Darin Petkov5b7dce12010-04-21 15:45:10 -0700230
Darin Petkov21cd2c52010-05-12 15:26:16 -0700231bool MetricsLibrary::SendEnumToUMA(const std::string& name, int sample,
232 int max) {
Darin Petkov5b7dce12010-04-21 15:45:10 -0700233 // Format the message.
234 char message[kBufferSize];
235 int32_t message_length =
236 FormatChromeMessage(kBufferSize, message,
237 "linearhistogram%c%s %d %d", '\0',
238 name.c_str(), sample, max);
Darin Petkoved824852011-01-06 10:51:47 -0800239 if (message_length < 0)
240 return false;
Darin Petkov5b7dce12010-04-21 15:45:10 -0700241
Darin Petkoved824852011-01-06 10:51:47 -0800242 // Send the message.
243 return SendMessageToChrome(message_length, message);
244}
245
246bool MetricsLibrary::SendUserActionToUMA(const std::string& action) {
247 // Format the message.
248 char message[kBufferSize];
249 int32_t message_length =
250 FormatChromeMessage(kBufferSize, message,
251 "useraction%c%s", '\0', action.c_str());
Darin Petkov5b7dce12010-04-21 15:45:10 -0700252 if (message_length < 0)
253 return false;
254
255 // Send the message.
256 return SendMessageToChrome(message_length, message);
257}
Ken Mixterbe2e13b2011-01-22 06:15:56 -0800258
259bool MetricsLibrary::SendCrashToUMA(const char *crash_kind) {
260 // Format the message.
261 char message[kBufferSize];
262 int32_t message_length =
263 FormatChromeMessage(kBufferSize, message,
264 "crash%c%s", '\0', crash_kind);
265
266 if (message_length < 0)
267 return false;
268
269 // Send the message.
270 return SendMessageToChrome(message_length, message);
271}