summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbsimonnet@chromium.org <bsimonnet@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-19 05:05:01 +0000
committerbsimonnet@chromium.org <bsimonnet@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-19 05:05:01 +0000
commit6e9b5e203cf4f5419ccf21d801f354c668f16837 (patch)
tree9c3cee77528e3cd78097f0767c92e2a92c473fd3
parent8f6bc9ec04fdbf168225b5e39da5a389b3b071b5 (diff)
downloadchromium_src-6e9b5e203cf4f5419ccf21d801f354c668f16837.zip
chromium_src-6e9b5e203cf4f5419ccf21d801f354c668f16837.tar.gz
chromium_src-6e9b5e203cf4f5419ccf21d801f354c668f16837.tar.bz2
Create a histogram serialization mechanism in base
We are removing the dependency of ChromeOS on Chrome to send metrics. If Chrome is not install on a system, we will provide a simple service that will upload the metrics. This module will need to read the same logs and use the same parsing mechanism as Chrome does now. The common serialization code will be kept in base to make it accessible to both Chrome and ChromeOS. The code in chrome/browser/chromeos/ will be migrated to use this in an other CL. BUG=chromium:358371 TEST=unittest included Review URL: https://codereview.chromium.org/227873002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@271341 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--components/components_tests.gyp4
-rw-r--r--components/metrics.gypi26
-rw-r--r--components/metrics/README3
-rw-r--r--components/metrics/chromeos/metric_sample.cc197
-rw-r--r--components/metrics/chromeos/metric_sample.h119
-rw-r--r--components/metrics/chromeos/serialization_utils.cc216
-rw-r--r--components/metrics/chromeos/serialization_utils.h48
-rw-r--r--components/metrics/chromeos/serialization_utils_unittest.cc170
8 files changed, 782 insertions, 1 deletions
diff --git a/components/components_tests.gyp b/components/components_tests.gyp
index d1a6812..a66f32d 100644
--- a/components/components_tests.gyp
+++ b/components/components_tests.gyp
@@ -443,11 +443,15 @@
]
}],
['chromeos==1', {
+ 'sources': [
+ 'metrics/chromeos/serialization_utils_unittest.cc',
+ ],
'sources!': [
'storage_monitor/storage_monitor_linux_unittest.cc',
],
'dependencies': [
'../chromeos/chromeos.gyp:chromeos_test_support',
+ 'components.gyp:metrics_chromeos',
],
}],
['OS=="linux"', {
diff --git a/components/metrics.gypi b/components/metrics.gypi
index 5c9b1f9..354d5f5 100644
--- a/components/metrics.gypi
+++ b/components/metrics.gypi
@@ -26,6 +26,13 @@
'metrics/persisted_logs.cc',
'metrics/persisted_logs.h',
],
+ 'conditions': [
+ ['chromeos==1', {
+ 'dependencies': [
+ 'metrics_chromeos',
+ ],
+ }],
+ ],
},
{
# Protobuf compiler / generator for UMA (User Metrics Analysis).
@@ -48,4 +55,23 @@
'includes': [ '../build/protoc.gypi' ],
},
],
+ 'conditions': [
+ ['chromeos==1', {
+ 'targets': [
+ {
+ 'target_name': 'metrics_chromeos',
+ 'type': 'static_library',
+ 'sources': [
+ 'metrics/chromeos/serialization_utils.cc',
+ 'metrics/chromeos/serialization_utils.h',
+ 'metrics/chromeos/metric_sample.cc',
+ 'metrics/chromeos/metric_sample.h',
+ ],
+ 'dependencies': [
+ '../base/base.gyp:base',
+ ],
+ },
+ ],
+ }],
+ ],
}
diff --git a/components/metrics/README b/components/metrics/README
index 3461dea..5a2abbb2 100644
--- a/components/metrics/README
+++ b/components/metrics/README
@@ -18,5 +18,6 @@ separation between the metrics upload logic (protbuf generation, retry, etc...),
the chrome part (gathering histogram from all the threads, populating the
log with hardware characteristics, plugin state, etc.).
-It is a plus if the component stays in a single directory as it would be easier
+It is a plus if the code currently in the component (i.e., the code that can
+depend only on //base) stays in a single directory as it would be easier
for ChromeOS to pull it :).
diff --git a/components/metrics/chromeos/metric_sample.cc b/components/metrics/chromeos/metric_sample.cc
new file mode 100644
index 0000000..9e8ff22
--- /dev/null
+++ b/components/metrics/chromeos/metric_sample.cc
@@ -0,0 +1,197 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/chromeos/metric_sample.h"
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+
+namespace metrics {
+
+MetricSample::MetricSample(MetricSample::SampleType sample_type,
+ const std::string& metric_name,
+ int sample,
+ int min,
+ int max,
+ int bucket_count)
+ : type_(sample_type),
+ name_(metric_name),
+ sample_(sample),
+ min_(min),
+ max_(max),
+ bucket_count_(bucket_count) {
+}
+
+MetricSample::~MetricSample() {
+}
+
+bool MetricSample::IsValid() const {
+ return name().find(' ') == std::string::npos &&
+ name().find('\0') == std::string::npos && !name().empty();
+}
+
+std::string MetricSample::ToString() const {
+ if (type_ == CRASH) {
+ return base::StringPrintf("crash%c%s%c",
+ '\0',
+ name().c_str(),
+ '\0');
+ } else if (type_ == SPARSE_HISTOGRAM) {
+ return base::StringPrintf("sparsehistogram%c%s %d%c",
+ '\0',
+ name().c_str(),
+ sample_,
+ '\0');
+ } else if (type_ == LINEAR_HISTOGRAM) {
+ return base::StringPrintf("linearhistogram%c%s %d %d%c",
+ '\0',
+ name().c_str(),
+ sample_,
+ max_,
+ '\0');
+ } else if (type_ == HISTOGRAM) {
+ return base::StringPrintf("histogram%c%s %d %d %d %d%c",
+ '\0',
+ name().c_str(),
+ sample_,
+ min_,
+ max_,
+ bucket_count_,
+ '\0');
+ } else {
+ // The type can only be USER_ACTION.
+ CHECK_EQ(type_, USER_ACTION);
+ return base::StringPrintf("useraction%c%s%c",
+ '\0',
+ name().c_str(),
+ '\0');
+ }
+}
+
+const int MetricSample::sample() const {
+ CHECK_NE(type_, USER_ACTION);
+ CHECK_NE(type_, CRASH);
+ return sample_;
+}
+
+const int MetricSample::min() const {
+ CHECK_EQ(type_, HISTOGRAM);
+ return min_;
+}
+
+const int MetricSample::max() const {
+ CHECK_NE(type_, CRASH);
+ CHECK_NE(type_, USER_ACTION);
+ CHECK_NE(type_, SPARSE_HISTOGRAM);
+ return max_;
+}
+
+const int MetricSample::bucket_count() const {
+ CHECK_EQ(type_, HISTOGRAM);
+ return bucket_count_;
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::CrashSample(
+ const std::string& crash_name) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(CRASH, crash_name, 0, 0, 0, 0));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::HistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int min,
+ int max,
+ int bucket_count) {
+ return scoped_ptr<MetricSample>(new MetricSample(
+ HISTOGRAM, histogram_name, sample, min, max, bucket_count));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::ParseHistogram(
+ const std::string& serialized_histogram) {
+ std::vector<std::string> parts;
+ base::SplitString(serialized_histogram, ' ', &parts);
+
+ if (parts.size() != 5)
+ return scoped_ptr<MetricSample>();
+ int sample, min, max, bucket_count;
+ if (parts[0].empty() || !base::StringToInt(parts[1], &sample) ||
+ !base::StringToInt(parts[2], &min) ||
+ !base::StringToInt(parts[3], &max) ||
+ !base::StringToInt(parts[4], &bucket_count)) {
+ return scoped_ptr<MetricSample>();
+ }
+
+ return HistogramSample(parts[0], sample, min, max, bucket_count);
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::SparseHistogramSample(
+ const std::string& histogram_name,
+ int sample) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(SPARSE_HISTOGRAM, histogram_name, sample, 0, 0, 0));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::ParseSparseHistogram(
+ const std::string& serialized_histogram) {
+ std::vector<std::string> parts;
+ base::SplitString(serialized_histogram, ' ', &parts);
+ if (parts.size() != 2)
+ return scoped_ptr<MetricSample>();
+ int sample;
+ if (parts[0].empty() || !base::StringToInt(parts[1], &sample))
+ return scoped_ptr<MetricSample>();
+
+ return SparseHistogramSample(parts[0], sample);
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::LinearHistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int max) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(LINEAR_HISTOGRAM, histogram_name, sample, 0, max, 0));
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::ParseLinearHistogram(
+ const std::string& serialized_histogram) {
+ std::vector<std::string> parts;
+ int sample, max;
+ base::SplitString(serialized_histogram, ' ', &parts);
+ if (parts.size() != 3)
+ return scoped_ptr<MetricSample>();
+ if (parts[0].empty() || !base::StringToInt(parts[1], &sample) ||
+ !base::StringToInt(parts[2], &max)) {
+ return scoped_ptr<MetricSample>();
+ }
+
+ return LinearHistogramSample(parts[0], sample, max);
+}
+
+// static
+scoped_ptr<MetricSample> MetricSample::UserActionSample(
+ const std::string& action_name) {
+ return scoped_ptr<MetricSample>(
+ new MetricSample(USER_ACTION, action_name, 0, 0, 0, 0));
+}
+
+bool MetricSample::IsEqual(const MetricSample& metric) {
+ return type_ == metric.type_ && name_ == metric.name_ &&
+ sample_ == metric.sample_ && min_ == metric.min_ &&
+ max_ == metric.max_ && bucket_count_ == metric.bucket_count_;
+}
+
+} // namespace metrics
diff --git a/components/metrics/chromeos/metric_sample.h b/components/metrics/chromeos/metric_sample.h
new file mode 100644
index 0000000..06358e4
--- /dev/null
+++ b/components/metrics/chromeos/metric_sample.h
@@ -0,0 +1,119 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_CHROMEOS_METRIC_SAMPLE_H_
+#define COMPONENTS_METRICS_CHROMEOS_METRIC_SAMPLE_H_
+
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+
+namespace metrics {
+
+// This class is used by libmetrics (ChromeOS) to serialize
+// and deserialize measurements to send them to a metrics sending service.
+// It is meant to be a simple container with serialization functions.
+class MetricSample {
+ public:
+ // Types of metric sample used.
+ enum SampleType {
+ CRASH,
+ HISTOGRAM,
+ LINEAR_HISTOGRAM,
+ SPARSE_HISTOGRAM,
+ USER_ACTION
+ };
+
+ ~MetricSample();
+
+ // Returns true if the sample is valid (can be serialized without ambiguity).
+ //
+ // This function should be used to filter bad samples before serializing them.
+ bool IsValid() const;
+
+ // Getters for type and name. All types of metrics have these so we do not
+ // need to check the type.
+ SampleType type() const { return type_; }
+ const std::string& name() const { return name_; }
+
+ // Getters for sample, min, max, bucket_count.
+ // Check the metric type to make sure the request make sense. (ex: a crash
+ // sample does not have a bucket_count so we crash if we call bucket_count()
+ // on it.)
+ const int sample() const;
+ const int min() const;
+ const int max() const;
+ const int bucket_count() const;
+
+ // Returns a serialized version of the sample.
+ //
+ // The serialized message for each type is:
+ // crash: crash\0|name_|\0
+ // user action: useraction\0|name_|\0
+ // histogram: histogram\0|name_| |sample_| |min_| |max_| |bucket_count_|\0
+ // sparsehistogram: sparsehistogram\0|name_| |sample_|\0
+ // linearhistogram: linearhistogram\0|name_| |sample_| |max_|\0
+ std::string ToString() const;
+
+ // Builds a crash sample.
+ static scoped_ptr<MetricSample> CrashSample(const std::string& crash_name);
+
+ // Builds a histogram sample.
+ static scoped_ptr<MetricSample> HistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int min,
+ int max,
+ int bucket_count);
+ // Deserializes a histogram sample.
+ static scoped_ptr<MetricSample> ParseHistogram(const std::string& serialized);
+
+ // Builds a sparse histogram sample.
+ static scoped_ptr<MetricSample> SparseHistogramSample(
+ const std::string& histogram_name,
+ int sample);
+ // Deserializes a sparse histogram sample.
+ static scoped_ptr<MetricSample> ParseSparseHistogram(
+ const std::string& serialized);
+
+ // Builds a linear histogram sample.
+ static scoped_ptr<MetricSample> LinearHistogramSample(
+ const std::string& histogram_name,
+ int sample,
+ int max);
+ // Deserializes a linear histogram sample.
+ static scoped_ptr<MetricSample> ParseLinearHistogram(
+ const std::string& serialized);
+
+ // Builds a user action sample.
+ static scoped_ptr<MetricSample> UserActionSample(
+ const std::string& action_name);
+
+ // Returns true if sample and this object represent the same sample (type,
+ // name, sample, min, max, bucket_count match).
+ bool IsEqual(const MetricSample& sample);
+
+ private:
+ MetricSample(SampleType sample_type,
+ const std::string& metric_name,
+ const int sample,
+ const int min,
+ const int max,
+ const int bucket_count);
+
+ const SampleType type_;
+ const std::string name_;
+ const int sample_;
+ const int min_;
+ const int max_;
+ const int bucket_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(MetricSample);
+};
+
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_CHROMEOS_METRIC_SAMPLE_H_
diff --git a/components/metrics/chromeos/serialization_utils.cc b/components/metrics/chromeos/serialization_utils.cc
new file mode 100644
index 0000000..adfadc5
--- /dev/null
+++ b/components/metrics/chromeos/serialization_utils.cc
@@ -0,0 +1,216 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/chromeos/serialization_utils.h"
+
+#include <sys/file.h>
+
+#include <string>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "components/metrics/chromeos/metric_sample.h"
+
+#define READ_WRITE_ALL_FILE_FLAGS \
+ (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
+
+namespace metrics {
+namespace {
+
+// Reads the next message from |file_descriptor| into |message|.
+//
+// |message| will be set to the empty string if no message could be read (EOF)
+// or the message was badly constructed.
+//
+// Returns false if no message can be read from this file anymore (EOF or
+// unrecoverable error).
+bool ReadMessage(int fd, std::string* message) {
+ CHECK(message);
+
+ int result;
+ int32 message_size;
+ // The file containing the metrics do not leave the device so the writer and
+ // the reader will always have the same endianness.
+ result = HANDLE_EINTR(read(fd, &message_size, sizeof(message_size)));
+ if (result < 0) {
+ DPLOG(ERROR) << "reading metrics message header";
+ return false;
+ }
+ if (result == 0) {
+ // This indicates a normal EOF.
+ return false;
+ }
+ if (result < static_cast<int>(sizeof(message_size))) {
+ DLOG(ERROR) << "bad read size " << result << ", expecting "
+ << sizeof(message_size);
+ return false;
+ }
+
+ // kMessageMaxLength applies to the entire message: the 4-byte
+ // length field and the content.
+ if (message_size > metrics::SerializationUtils::kMessageMaxLength) {
+ DLOG(ERROR) << "message too long : " << message_size;
+ if (HANDLE_EINTR(lseek(fd, message_size - 4, SEEK_CUR)) == -1) {
+ DLOG(ERROR) << "error while skipping message. abort";
+ return false;
+ }
+ // Badly formatted message was skipped. Treat the badly formatted sample as
+ // an empty sample.
+ message->clear();
+ return true;
+ }
+
+ message_size -= sizeof(message_size); // The message size includes itself.
+ char buffer[metrics::SerializationUtils::kMessageMaxLength];
+ if (!base::ReadFromFD(fd, buffer, message_size)) {
+ DPLOG(ERROR) << "reading metrics message body";
+ return false;
+ }
+ *message = std::string(buffer, message_size);
+ return true;
+}
+
+} // namespace
+
+scoped_ptr<MetricSample> SerializationUtils::ParseSample(
+ const std::string& sample) {
+ if (sample.empty())
+ return scoped_ptr<MetricSample>();
+
+ std::vector<std::string> parts;
+ base::SplitString(sample, '\0', &parts);
+ // We should have two null terminated strings so split should produce
+ // three chunks.
+ if (parts.size() != 3) {
+ DLOG(ERROR) << "splitting message on \\0 produced " << parts.size()
+ << " parts (expected 3)";
+ return scoped_ptr<MetricSample>();
+ }
+ const std::string& name = parts[0];
+ const std::string& value = parts[1];
+
+ if (LowerCaseEqualsASCII(name, "crash")) {
+ return MetricSample::CrashSample(value);
+ } else if (LowerCaseEqualsASCII(name, "histogram")) {
+ return MetricSample::ParseHistogram(value);
+ } else if (LowerCaseEqualsASCII(name, "linearhistogram")) {
+ return MetricSample::ParseLinearHistogram(value);
+ } else if (LowerCaseEqualsASCII(name, "sparsehistogram")) {
+ return MetricSample::ParseSparseHistogram(value);
+ } else if (LowerCaseEqualsASCII(name, "useraction")) {
+ return MetricSample::UserActionSample(value);
+ } else {
+ DLOG(ERROR) << "invalid event type: " << name << ", value: " << value;
+ }
+ return scoped_ptr<MetricSample>();
+}
+
+void SerializationUtils::ReadAndTruncateMetricsFromFile(
+ const std::string& filename,
+ ScopedVector<MetricSample>* metrics) {
+ struct stat stat_buf;
+ int result;
+
+ result = stat(filename.c_str(), &stat_buf);
+ if (result < 0) {
+ if (errno != ENOENT)
+ DPLOG(ERROR) << filename << ": bad metrics file stat";
+
+ // Nothing to collect---try later.
+ return;
+ }
+ if (stat_buf.st_size == 0) {
+ // Also nothing to collect.
+ return;
+ }
+ base::ScopedFD fd(open(filename.c_str(), O_RDWR));
+ if (fd.get() < 0) {
+ DPLOG(ERROR) << filename << ": cannot open";
+ return;
+ }
+ result = flock(fd.get(), LOCK_EX);
+ if (result < 0) {
+ DPLOG(ERROR) << filename << ": cannot lock";
+ return;
+ }
+
+ // This processes all messages in the log. When all messages are
+ // read and processed, or an error occurs, truncate the file to zero size.
+ for (;;) {
+ std::string message;
+
+ if (!ReadMessage(fd.get(), &message))
+ break;
+
+ scoped_ptr<MetricSample> sample = ParseSample(message);
+ if (sample)
+ metrics->push_back(sample.release());
+ }
+
+ result = ftruncate(fd.get(), 0);
+ if (result < 0)
+ DPLOG(ERROR) << "truncate metrics log";
+
+ result = flock(fd.get(), LOCK_UN);
+ if (result < 0)
+ DPLOG(ERROR) << "unlock metrics log";
+}
+
+bool SerializationUtils::WriteMetricToFile(const MetricSample& sample,
+ const std::string& filename) {
+ if (!sample.IsValid())
+ return false;
+
+ base::ScopedFD file_descriptor(open(filename.c_str(),
+ O_WRONLY | O_APPEND | O_CREAT,
+ READ_WRITE_ALL_FILE_FLAGS));
+
+ if (file_descriptor.get() < 0) {
+ DLOG(ERROR) << "error openning the file";
+ return false;
+ }
+
+ fchmod(file_descriptor.get(), READ_WRITE_ALL_FILE_FLAGS);
+ // Grab a lock to avoid chrome truncating the file
+ // underneath us. Keep the file locked as briefly as possible.
+ // Freeing file_descriptor will close the file and and remove the lock.
+ if (HANDLE_EINTR(flock(file_descriptor.get(), LOCK_EX)) < 0) {
+ DLOG(ERROR) << "error locking" << filename << " : " << errno;
+ return false;
+ }
+
+ std::string msg = sample.ToString();
+ int32 size = msg.length() + sizeof(int32);
+ if (size > kMessageMaxLength) {
+ DLOG(ERROR) << "cannot write message: too long";
+ return false;
+ }
+
+ // The file containing the metrics samples will only be read by programs on
+ // the same device so we do not check endianness.
+ if (base::WriteFileDescriptor(file_descriptor.get(),
+ reinterpret_cast<char*>(&size),
+ sizeof(size)) != sizeof(size)) {
+ DLOG(ERROR) << "error writing message length " << errno;
+ return false;
+ }
+
+ if (base::WriteFileDescriptor(
+ file_descriptor.get(), msg.c_str(), msg.length()) !=
+ static_cast<int>(msg.length())) {
+ DLOG(ERROR) << "error writing message" << errno;
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace metrics
diff --git a/components/metrics/chromeos/serialization_utils.h b/components/metrics/chromeos/serialization_utils.h
new file mode 100644
index 0000000..1fb27bc
--- /dev/null
+++ b/components/metrics/chromeos/serialization_utils.h
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_METRICS_CHROMEOS_SERIALIZATION_UTILS_H_
+#define COMPONENTS_METRICS_CHROMEOS_SERIALIZATION_UTILS_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+
+namespace metrics {
+
+class MetricSample;
+
+// Metrics helpers to serialize and deserialize metrics collected by
+// ChromeOS.
+namespace SerializationUtils {
+
+// Deserializes a sample passed as a string and return a sample.
+// The return value will either be a scoped_ptr to a Metric sample (if the
+// deserialization was successful) or a NULL scoped_ptr.
+scoped_ptr<MetricSample> ParseSample(const std::string& sample);
+
+// Reads all samples from a file and truncate the file when done.
+void ReadAndTruncateMetricsFromFile(const std::string& filename,
+ ScopedVector<MetricSample>* metrics);
+
+// Serializes a sample and write it to filename.
+// The format for the message is:
+// message_size, serialized_message
+// where
+// * message_size is the total length of the message (message_size +
+// serialized_message) on 4 bytes
+// * serialized_message is the serialized version of sample (using ToString)
+//
+// NB: the file will never leave the device so message_size will be written
+// with the architecture's endianness.
+bool WriteMetricToFile(const MetricSample& sample, const std::string& filename);
+
+// Maximum length of a serialized message
+static const int kMessageMaxLength = 1024;
+
+} // namespace SerializationUtils
+} // namespace metrics
+
+#endif // COMPONENTS_METRICS_CHROMEOS_SERIALIZATION_UTILS_H_
diff --git a/components/metrics/chromeos/serialization_utils_unittest.cc b/components/metrics/chromeos/serialization_utils_unittest.cc
new file mode 100644
index 0000000..5ed7379
--- /dev/null
+++ b/components/metrics/chromeos/serialization_utils_unittest.cc
@@ -0,0 +1,170 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/metrics/chromeos/serialization_utils.h"
+
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "components/metrics/chromeos/metric_sample.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace metrics {
+namespace {
+
+class SerializationUtilsChromeOSTest : public testing::Test {
+ protected:
+ SerializationUtilsChromeOSTest() {
+ bool success = temporary_dir.CreateUniqueTempDir();
+ if (success) {
+ base::FilePath dir_path = temporary_dir.path();
+ filename = dir_path.value() + "chromeossampletest";
+ filepath = base::FilePath(filename);
+ }
+ }
+
+ virtual void SetUp() OVERRIDE {
+ base::DeleteFile(filepath, false);
+ }
+
+ void TestSerialization(MetricSample* sample) {
+ std::string serialized(sample->ToString());
+ ASSERT_EQ('\0', serialized[serialized.length() - 1]);
+ scoped_ptr<MetricSample> deserialized =
+ SerializationUtils::ParseSample(serialized);
+ ASSERT_TRUE(deserialized);
+ EXPECT_TRUE(sample->IsEqual(*deserialized.get()));
+ }
+
+ std::string filename;
+ base::ScopedTempDir temporary_dir;
+ base::FilePath filepath;
+};
+
+TEST_F(SerializationUtilsChromeOSTest, CrashSerializeTest) {
+ TestSerialization(MetricSample::CrashSample("test").get());
+}
+
+TEST_F(SerializationUtilsChromeOSTest, HistogramSerializeTest) {
+ TestSerialization(
+ MetricSample::HistogramSample("myhist", 13, 1, 100, 10).get());
+}
+
+TEST_F(SerializationUtilsChromeOSTest, LinearSerializeTest) {
+ TestSerialization(
+ MetricSample::LinearHistogramSample("linearhist", 12, 30).get());
+}
+
+TEST_F(SerializationUtilsChromeOSTest, SparseSerializeTest) {
+ TestSerialization(MetricSample::SparseHistogramSample("mysparse", 30).get());
+}
+
+TEST_F(SerializationUtilsChromeOSTest, UserActionSerializeTest) {
+ TestSerialization(MetricSample::UserActionSample("myaction").get());
+}
+
+TEST_F(SerializationUtilsChromeOSTest, IllegalNameAreFilteredTest) {
+ scoped_ptr<MetricSample> sample1 =
+ MetricSample::SparseHistogramSample("no space", 10);
+ scoped_ptr<MetricSample> sample2 = MetricSample::LinearHistogramSample(
+ base::StringPrintf("here%cbhe", '\0'), 1, 3);
+
+ EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*sample1.get(), filename));
+ EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*sample2.get(), filename));
+ int64 size = 0;
+
+ ASSERT_TRUE(!PathExists(filepath) || base::GetFileSize(filepath, &size));
+
+ EXPECT_EQ(0, size);
+}
+
+TEST_F(SerializationUtilsChromeOSTest, BadInputIsCaughtTest) {
+ std::string input(
+ base::StringPrintf("sparsehistogram%cname foo%c", '\0', '\0'));
+ EXPECT_EQ(NULL, MetricSample::ParseSparseHistogram(input).get());
+}
+
+TEST_F(SerializationUtilsChromeOSTest, MessageSeparatedByZero) {
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample("mycrash");
+
+ SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+ int64 size = 0;
+ ASSERT_TRUE(base::GetFileSize(filepath, &size));
+ // 4 bytes for the size
+ // 5 bytes for crash
+ // 7 bytes for mycrash
+ // 2 bytes for the \0
+ // -> total of 18
+ EXPECT_EQ(size, 18);
+}
+
+TEST_F(SerializationUtilsChromeOSTest, MessagesTooLongAreDiscardedTest) {
+ // Creates a message that is bigger than the maximum allowed size.
+ // As we are adding extra character (crash, \0s, etc), if the name is
+ // kMessageMaxLength long, it will be too long.
+ std::string name(SerializationUtils::kMessageMaxLength, 'c');
+
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample(name);
+ EXPECT_FALSE(SerializationUtils::WriteMetricToFile(*crash.get(), filename));
+ int64 size = 0;
+ ASSERT_TRUE(base::GetFileSize(filepath, &size));
+ EXPECT_EQ(0, size);
+}
+
+TEST_F(SerializationUtilsChromeOSTest, ReadLongMessageTest) {
+ base::File test_file(filepath,
+ base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND);
+ std::string message(SerializationUtils::kMessageMaxLength + 1, 'c');
+
+ int32 message_size = message.length() + sizeof(int32);
+ test_file.WriteAtCurrentPos(reinterpret_cast<const char*>(&message_size),
+ sizeof(message_size));
+ test_file.WriteAtCurrentPos(message.c_str(), message.length());
+ test_file.Close();
+
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample("test");
+ SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+
+ ScopedVector<MetricSample> samples;
+ SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &samples);
+ ASSERT_EQ(size_t(1), samples.size());
+ ASSERT_TRUE(samples[0] != NULL);
+ EXPECT_TRUE(crash->IsEqual(*samples[0]));
+}
+
+TEST_F(SerializationUtilsChromeOSTest, WriteReadTest) {
+ scoped_ptr<MetricSample> hist =
+ MetricSample::HistogramSample("myhist", 1, 2, 3, 4);
+ scoped_ptr<MetricSample> crash = MetricSample::CrashSample("mycrash");
+ scoped_ptr<MetricSample> lhist =
+ MetricSample::LinearHistogramSample("linear", 1, 10);
+ scoped_ptr<MetricSample> shist =
+ MetricSample::SparseHistogramSample("mysparse", 30);
+ scoped_ptr<MetricSample> action = MetricSample::UserActionSample("myaction");
+
+ SerializationUtils::WriteMetricToFile(*hist.get(), filename);
+ SerializationUtils::WriteMetricToFile(*crash.get(), filename);
+ SerializationUtils::WriteMetricToFile(*lhist.get(), filename);
+ SerializationUtils::WriteMetricToFile(*shist.get(), filename);
+ SerializationUtils::WriteMetricToFile(*action.get(), filename);
+ ScopedVector<MetricSample> vect;
+ SerializationUtils::ReadAndTruncateMetricsFromFile(filename, &vect);
+ ASSERT_EQ(vect.size(), size_t(5));
+ for (int i = 0; i < 5; i++) {
+ ASSERT_TRUE(vect[0] != NULL);
+ }
+ EXPECT_TRUE(hist->IsEqual(*vect[0]));
+ EXPECT_TRUE(crash->IsEqual(*vect[1]));
+ EXPECT_TRUE(lhist->IsEqual(*vect[2]));
+ EXPECT_TRUE(shist->IsEqual(*vect[3]));
+ EXPECT_TRUE(action->IsEqual(*vect[4]));
+
+ int64 size = 0;
+ ASSERT_TRUE(base::GetFileSize(filepath, &size));
+ ASSERT_EQ(0, size);
+}
+
+} // namespace
+} // namespace metrics