diff options
author | Xin Li <delphij@google.com> | 2016-06-24 11:25:51 -0700 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2016-06-24 11:30:51 -0700 |
commit | caacd6905920128af1de8d7f93463eee88be1646 (patch) | |
tree | 1ad1513b968f01cb50fc929d5a972ed257d7ecb3 | |
parent | 3832b79f0895909cd4c655081e6ef70c8c334d5b (diff) | |
parent | 80b69d460f7c9f7a7c7434e29035739f325c33a2 (diff) | |
download | metricsd-caacd6905920128af1de8d7f93463eee88be1646.tar.gz |
Merge branch 'rewrite-metricsd' into merge-metricsd
Initial import of metricsd from platform/system/core.
BUG: 29548040
78 files changed, 8390 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format new file mode 120000 index 0000000..f9066d4 --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +../../../build/tools/brillo-clang-format
\ No newline at end of file diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..bb262b4 --- /dev/null +++ b/Android.mk @@ -0,0 +1,232 @@ +# Copyright (C) 2015 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +metrics_cpp_extension := .cc +libmetrics_sources := \ + c_metrics_library.cc \ + metrics_library.cc \ + timer.cc + +metrics_client_sources := \ + metrics_client.cc + +metrics_collector_common := \ + collectors/averaged_statistics_collector.cc \ + collectors/cpu_usage_collector.cc \ + collectors/disk_usage_collector.cc \ + metrics_collector.cc \ + metrics_collector_service_impl.cc \ + persistent_integer.cc + +metricsd_common := \ + persistent_integer.cc \ + uploader/bn_metricsd_impl.cc \ + uploader/crash_counters.cc \ + uploader/metrics_hashes.cc \ + uploader/metrics_log_base.cc \ + uploader/metrics_log.cc \ + uploader/metricsd_service_runner.cc \ + uploader/sender_http.cc \ + uploader/system_profile_cache.cc \ + uploader/upload_service.cc + +metrics_collector_tests_sources := \ + collectors/averaged_statistics_collector_test.cc \ + collectors/cpu_usage_collector_test.cc \ + metrics_collector_test.cc \ + metrics_library_test.cc \ + persistent_integer_test.cc \ + timer_test.cc + +metricsd_tests_sources := \ + uploader/metrics_hashes_unittest.cc \ + uploader/metrics_log_base_unittest.cc \ + uploader/mock/sender_mock.cc \ + uploader/upload_service_test.cc + +metrics_CFLAGS := -Wall \ + -Wno-char-subscripts \ + -Wno-missing-field-initializers \ + -Wno-unused-parameter \ + -Werror \ + -fvisibility=default +metrics_CPPFLAGS := -Wno-non-virtual-dtor \ + -Wno-sign-promo \ + -Wno-strict-aliasing \ + -fvisibility=default +metrics_includes := external/gtest/include \ + $(LOCAL_PATH)/include +libmetrics_shared_libraries := libchrome libbinder libbrillo libutils +metrics_collector_shared_libraries := $(libmetrics_shared_libraries) \ + libbrillo-binder \ + libbrillo-http \ + libmetrics \ + librootdev \ + libweaved + +metrics_collector_static_libraries := libmetricscollectorservice + +metricsd_shared_libraries := \ + libbinder \ + libbrillo \ + libbrillo-binder \ + libbrillo-http \ + libchrome \ + libprotobuf-cpp-lite \ + libupdate_engine_client \ + libutils + +# Static proxy library for the metricsd binder interface. +# ======================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := metricsd_binder_proxy +LOCAL_SHARED_LIBRARIES := libbinder libutils +LOCAL_SRC_FILES := aidl/android/brillo/metrics/IMetricsd.aidl +include $(BUILD_STATIC_LIBRARY) + +# Static library for the metrics_collector binder interface. +# ========================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := libmetricscollectorservice +LOCAL_CLANG := true +LOCAL_SHARED_LIBRARIES := libbinder libbrillo-binder libchrome libutils +LOCAL_CPP_EXTENSION := $(metrics_cpp_extension) +LOCAL_C_INCLUDES := $(LOCAL_PATH)/include +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +LOCAL_SRC_FILES := \ + aidl/android/brillo/metrics/IMetricsCollectorService.aidl \ + metrics_collector_service_client.cc +include $(BUILD_STATIC_LIBRARY) + +# Shared library for metrics. +# ======================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := libmetrics +LOCAL_C_INCLUDES := $(metrics_includes) +LOCAL_CFLAGS := $(metrics_CFLAGS) +LOCAL_CLANG := true +LOCAL_CPP_EXTENSION := $(metrics_cpp_extension) +LOCAL_CPPFLAGS := $(metrics_CPPFLAGS) +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +LOCAL_SHARED_LIBRARIES := $(libmetrics_shared_libraries) +LOCAL_SRC_FILES := $(libmetrics_sources) +LOCAL_STATIC_LIBRARIES := metricsd_binder_proxy +include $(BUILD_SHARED_LIBRARY) + +# CLI client for metrics. +# ======================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := metrics_client +LOCAL_C_INCLUDES := $(metrics_includes) +LOCAL_CFLAGS := $(metrics_CFLAGS) +LOCAL_CLANG := true +LOCAL_CPP_EXTENSION := $(metrics_cpp_extension) +LOCAL_CPPFLAGS := $(metrics_CPPFLAGS) +LOCAL_SHARED_LIBRARIES := $(libmetrics_shared_libraries) \ + libmetrics +LOCAL_SRC_FILES := $(metrics_client_sources) +LOCAL_STATIC_LIBRARIES := metricsd_binder_proxy +include $(BUILD_EXECUTABLE) + +# Protobuf library for metricsd. +# ======================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := metricsd_protos +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +generated_sources_dir := $(call local-generated-sources-dir) +LOCAL_EXPORT_C_INCLUDE_DIRS += \ + $(generated_sources_dir)/proto/system/core/metricsd +LOCAL_SRC_FILES := $(call all-proto-files-under,uploader/proto) +include $(BUILD_STATIC_LIBRARY) + +# metrics_collector daemon. +# ======================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := metrics_collector +LOCAL_C_INCLUDES := $(metrics_includes) +LOCAL_CFLAGS := $(metrics_CFLAGS) +LOCAL_CLANG := true +LOCAL_CPP_EXTENSION := $(metrics_cpp_extension) +LOCAL_CPPFLAGS := $(metrics_CPPFLAGS) +LOCAL_INIT_RC := metrics_collector.rc +LOCAL_REQUIRED_MODULES := metrics.json +LOCAL_SHARED_LIBRARIES := $(metrics_collector_shared_libraries) +LOCAL_SRC_FILES := $(metrics_collector_common) \ + metrics_collector_main.cc +LOCAL_STATIC_LIBRARIES := metricsd_binder_proxy \ + $(metrics_collector_static_libraries) +include $(BUILD_EXECUTABLE) + +# metricsd daemon. +# ======================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := metricsd +LOCAL_C_INCLUDES := $(metrics_includes) +LOCAL_CFLAGS := $(metrics_CFLAGS) +LOCAL_CLANG := true +LOCAL_CPP_EXTENSION := $(metrics_cpp_extension) +LOCAL_CPPFLAGS := $(metrics_CPPFLAGS) +LOCAL_INIT_RC := metricsd.rc +LOCAL_REQUIRED_MODULES := \ + metrics_collector +LOCAL_SHARED_LIBRARIES := $(metricsd_shared_libraries) +LOCAL_STATIC_LIBRARIES := metricsd_protos metricsd_binder_proxy +LOCAL_SRC_FILES := $(metricsd_common) \ + metricsd_main.cc +include $(BUILD_EXECUTABLE) + +# Unit tests for metricsd. +# ======================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := metricsd_tests +LOCAL_CFLAGS := $(metrics_CFLAGS) +LOCAL_CLANG := true +LOCAL_CPP_EXTENSION := $(metrics_cpp_extension) +LOCAL_CPPFLAGS := $(metrics_CPPFLAGS) -Wno-sign-compare +LOCAL_SHARED_LIBRARIES := $(metricsd_shared_libraries) +LOCAL_SRC_FILES := $(metricsd_tests_sources) $(metricsd_common) +LOCAL_STATIC_LIBRARIES := libBionicGtestMain libgmock metricsd_protos metricsd_binder_proxy +ifdef BRILLO +LOCAL_MODULE_TAGS := eng +endif +include $(BUILD_NATIVE_TEST) + +# Unit tests for metrics_collector. +# ======================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := metrics_collector_tests +LOCAL_CFLAGS := $(metrics_CFLAGS) +LOCAL_CLANG := true +LOCAL_CPP_EXTENSION := $(metrics_cpp_extension) +LOCAL_CPPFLAGS := $(metrics_CPPFLAGS) -Wno-sign-compare +LOCAL_SHARED_LIBRARIES := $(metrics_collector_shared_libraries) +LOCAL_SRC_FILES := $(metrics_collector_tests_sources) \ + $(metrics_collector_common) +LOCAL_STATIC_LIBRARIES := libBionicGtestMain libgmock metricsd_binder_proxy \ + $(metrics_collector_static_libraries) +ifdef BRILLO +LOCAL_MODULE_TAGS := eng +endif +include $(BUILD_NATIVE_TEST) + +# Weave schema files +# ======================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := metrics.json +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/weaved/traits +LOCAL_SRC_FILES := etc/weaved/traits/$(LOCAL_MODULE) +include $(BUILD_PREBUILT) @@ -0,0 +1,3 @@ +semenzato@chromium.org +derat@chromium.org +bsimonnet@chromium.org diff --git a/README.md b/README.md new file mode 100644 index 0000000..8d4828c --- /dev/null +++ b/README.md @@ -0,0 +1,124 @@ +Metricsd +======== + +The metricsd daemon is used to gather metrics from the platform and application, +aggregate them and upload them periodically to a server. +The metrics will then be available in their aggregated form to the developer +for analysis. + +Three components are provided to interact with `metricsd`: `libmetrics`, +`metrics_collector` and `metrics_client`. + +The Metrics Library: libmetrics +------------------------------- + +`libmetrics` is a small library that implements the basic C++ API for +metrics collection. All metrics collection is funneled through this library. The +easiest and recommended way for a client-side module to collect user metrics is +to link `libmetrics` and use its APIs to send metrics to `metricsd` for transport to +UMA. In order to use the library in a module, you need to do the following: + +- Add a dependency on the shared library in your Android.mk file: + `LOCAL_SHARED_LIBRARIES += libmetrics` + +- To access the metrics library API in the module, include the + <metrics/metrics_library.h> header file. + +- The API is documented in `metrics_library.h`. Before using the API methods, a + MetricsLibrary object needs to be constructed and initialized through its + Init method. + +- Samples are uploaded only if the `/data/misc/metrics/enabled` file exists. + + +Server Side +----------- + +You will be able to see all uploaded metrics on the metrics dashboard, +accessible via the developer console. + +*** note +It usually takes a day for metrics to be available on the dashboard. +*** + + +The Metrics Client: metrics_client +---------------------------------- + +`metrics_client` is a simple shell command-line utility for sending histogram +samples and querying `metricsd`. It's installed under `/system/bin` on the target +platform and uses `libmetrics`. + +For usage information and command-line options, run `metrics_client` on the +target platform or look for "Usage:" in `metrics_client.cc`. + + +The Metrics Daemon: metricsd +---------------------------- + +`metricsd` is the daemon that listens for metrics logging calls (via Binder), +aggregates the metrics and uploads them periodically. This daemon should start as +early as possible so that depending daemons can log at any time. + +`metricsd` is made of two threads that work as follows: + +* The binder thread listens for one-way Binder calls, aggregates the metrics in + memory (via `base::StatisticsRecorder`) and increments the crash counters when a + crash is reported. This thread is kept as simple as possible to ensure the + maximum throughput possible. +* The uploader thread takes care of backing up the metrics to disk periodically + (to avoid losing metrics on crashes), collecting metadata about the client + (version number, channel, etc..) and uploading the metrics periodically to the + server. + + +The Metrics Collector: metrics_collector +---------------------------------------- + +metrics_collector is a daemon that runs in the background on the target platform, +gathers health information about the system and maintains long running counters +(ex: number of crashes per week). + +The recommended way to generate metrics data from a module is to link and use +libmetrics directly. However, we may not want to add a dependency on libmetrics +to some modules (ex: kernel). In this case, we can add a collector to +metrics_collector that will, for example, take measurements and report them +periodically to metricsd (this is the case for the disk utilization histogram). + + +FAQ +--- + +### What should my histogram's |min| and |max| values be set at? + +You should set the values to a range that covers the vast majority of samples +that would appear in the field. Note that samples below the |min| will still +be collected in the underflow bucket and samples above the |max| will end up +in the overflow bucket. Also, the reported mean of the data will be correct +regardless of the range. + +### How many buckets should I use in my histogram? + +You should allocate as many buckets as necessary to perform proper analysis +on the collected data. Note, however, that the memory allocated in metricsd +for each histogram is proportional to the number of buckets. Therefore, it is +strongly recommended to keep this number low (e.g., 50 is normal, while 100 +is probably high). + +### When should I use an enumeration (linear) histogram vs. a regular (exponential) histogram? + +Enumeration histograms should really be used only for sampling enumerated +events and, in some cases, percentages. Normally, you should use a regular +histogram with exponential bucket layout that provides higher resolution at +the low end of the range and lower resolution at the high end. Regular +histograms are generally used for collecting performance data (e.g., timing, +memory usage, power) as well as aggregated event counts. + +### How can I test that my histogram was reported correctly? + +* Make sure no error messages appear in logcat when you log a sample. +* Run `metrics_client -d` to dump the currently aggregated metrics. Your + histogram should appear in the list. +* Make sure that the aggregated metrics were uploaded to the server successfully + (check for an OK message from `metricsd` in logcat). +* After a day, your histogram should be available on the dashboard. diff --git a/WATCHLISTS b/WATCHLISTS new file mode 100644 index 0000000..a051f35 --- /dev/null +++ b/WATCHLISTS @@ -0,0 +1,16 @@ +# See http://dev.chromium.org/developers/contributing-code/watchlists for +# a description of this file's format. +# Please keep these keys in alphabetical order. + +{ + 'WATCHLIST_DEFINITIONS': { + 'all': { + 'filepath': '.', + }, + }, + 'WATCHLISTS': { + 'all': ['petkov@chromium.org', + 'semenzato@chromium.org', + 'sosa@chromium.org'] + }, +} diff --git a/aidl/android/brillo/metrics/IMetricsCollectorService.aidl b/aidl/android/brillo/metrics/IMetricsCollectorService.aidl new file mode 100644 index 0000000..49f484f --- /dev/null +++ b/aidl/android/brillo/metrics/IMetricsCollectorService.aidl @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.brillo.metrics; + +interface IMetricsCollectorService { + oneway void notifyUserCrash(); +} diff --git a/aidl/android/brillo/metrics/IMetricsd.aidl b/aidl/android/brillo/metrics/IMetricsd.aidl new file mode 100644 index 0000000..aa3cb34 --- /dev/null +++ b/aidl/android/brillo/metrics/IMetricsd.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.brillo.metrics; + +interface IMetricsd { + oneway void recordHistogram(String name, int sample, int min, int max, + int nbuckets); + oneway void recordLinearHistogram(String name, int sample, int max); + oneway void recordSparseHistogram(String name, int sample); + oneway void recordCrash(String type); + String getHistogramsDump(); +} diff --git a/c_metrics_library.cc b/c_metrics_library.cc new file mode 100644 index 0000000..47a543e --- /dev/null +++ b/c_metrics_library.cc @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// C wrapper to libmetrics +// + +#include "metrics/c_metrics_library.h" + +#include <string> + +#include "metrics/metrics_library.h" + +extern "C" CMetricsLibrary CMetricsLibraryNew(void) { + MetricsLibrary* lib = new MetricsLibrary; + return reinterpret_cast<CMetricsLibrary>(lib); +} + +extern "C" void CMetricsLibraryDelete(CMetricsLibrary handle) { + MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle); + delete lib; +} + +extern "C" void CMetricsLibraryInit(CMetricsLibrary handle) { + MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle); + if (lib != NULL) + lib->Init(); +} + +extern "C" int CMetricsLibrarySendToUMA(CMetricsLibrary handle, + const char* name, int sample, + int min, int max, int nbuckets) { + MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle); + if (lib == NULL) + return 0; + return lib->SendToUMA(std::string(name), sample, min, max, nbuckets); +} + +extern "C" int CMetricsLibrarySendEnumToUMA(CMetricsLibrary handle, + const char* name, int sample, + int max) { + MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle); + if (lib == NULL) + return 0; + return lib->SendEnumToUMA(std::string(name), sample, max); +} + +extern "C" int CMetricsLibrarySendSparseToUMA(CMetricsLibrary handle, + const char* name, int sample) { + MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle); + if (lib == NULL) + return 0; + return lib->SendSparseToUMA(std::string(name), sample); +} + +extern "C" int CMetricsLibrarySendCrashToUMA(CMetricsLibrary handle, + const char* crash_kind) { + MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle); + if (lib == NULL) + return 0; + return lib->SendCrashToUMA(crash_kind); +} + +extern "C" int CMetricsLibraryAreMetricsEnabled(CMetricsLibrary handle) { + MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle); + if (lib == NULL) + return 0; + return lib->AreMetricsEnabled(); +} diff --git a/collectors/averaged_statistics_collector.cc b/collectors/averaged_statistics_collector.cc new file mode 100644 index 0000000..a3aaa98 --- /dev/null +++ b/collectors/averaged_statistics_collector.cc @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "averaged_statistics_collector.h" + +#include <base/bind.h> +#include <base/files/file_util.h> +#include <base/files/file_path.h> +#include <base/strings/string_number_conversions.h> +#include <base/strings/string_split.h> + +#include "metrics_collector.h" + +namespace { + +// disk stats metrics + +// The {Read,Write}Sectors numbers are in sectors/second. +// A sector is usually 512 bytes. +const char kReadSectorsHistogramName[] = "Platform.ReadSectors"; +const char kWriteSectorsHistogramName[] = "Platform.WriteSectors"; +const int kDiskMetricsStatItemCount = 11; + +// Assume a max rate of 250Mb/s for reads (worse for writes) and 512 byte +// sectors. +const int kSectorsIOMax = 500000; // sectors/second +const int kSectorsBuckets = 50; // buckets + +// Page size is 4k, sector size is 0.5k. We're not interested in page fault +// rates that the disk cannot sustain. +const int kPageFaultsMax = kSectorsIOMax / 8; // Page faults/second +const int kPageFaultsBuckets = 50; + +// Major page faults, i.e. the ones that require data to be read from disk. +const char kPageFaultsHistogramName[] = "Platform.PageFaults"; + +// Swap in and Swap out +const char kSwapInHistogramName[] = "Platform.SwapIn"; +const char kSwapOutHistogramName[] = "Platform.SwapOut"; + +const int kIntervalBetweenCollection = 60; // seconds +const int kCollectionDuration = 1; // seconds + +} // namespace + +AveragedStatisticsCollector::AveragedStatisticsCollector( + MetricsLibraryInterface* metrics_library, + const std::string& diskstats_path, + const std::string& vmstats_path) : + metrics_lib_(metrics_library), + diskstats_path_(diskstats_path), + vmstats_path_(vmstats_path) { +} + +void AveragedStatisticsCollector::ScheduleWait() { + base::MessageLoop::current()->PostDelayedTask(FROM_HERE, + base::Bind(&AveragedStatisticsCollector::WaitCallback, + base::Unretained(this)), + base::TimeDelta::FromSeconds( + kIntervalBetweenCollection - kCollectionDuration)); +} + +void AveragedStatisticsCollector::ScheduleCollect() { + base::MessageLoop::current()->PostDelayedTask(FROM_HERE, + base::Bind(&AveragedStatisticsCollector::CollectCallback, + base::Unretained(this)), + base::TimeDelta::FromSeconds(kCollectionDuration)); +} + +void AveragedStatisticsCollector::WaitCallback() { + ReadInitialValues(); + ScheduleCollect(); +} + +void AveragedStatisticsCollector::CollectCallback() { + Collect(); + ScheduleWait(); +} + +void AveragedStatisticsCollector::ReadInitialValues() { + stats_start_time_ = MetricsCollector::GetActiveTime(); + DiskStatsReadStats(&read_sectors_, &write_sectors_); + VmStatsReadStats(&vmstats_); +} + +bool AveragedStatisticsCollector::DiskStatsReadStats( + uint64_t* read_sectors, uint64_t* write_sectors) { + CHECK(read_sectors); + CHECK(write_sectors); + std::string line; + if (diskstats_path_.empty()) { + return false; + } + + if (!base::ReadFileToString(base::FilePath(diskstats_path_), &line)) { + PLOG(WARNING) << "Could not read disk stats from " + << diskstats_path_.value(); + return false; + } + + std::vector<std::string> parts = base::SplitString( + line, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + if (parts.size() != kDiskMetricsStatItemCount) { + LOG(ERROR) << "Could not parse disk stat correctly. Expected " + << kDiskMetricsStatItemCount << " elements but got " + << parts.size(); + return false; + } + if (!base::StringToUint64(parts[2], read_sectors)) { + LOG(ERROR) << "Couldn't convert read sectors " << parts[2] << " to uint64"; + return false; + } + if (!base::StringToUint64(parts[6], write_sectors)) { + LOG(ERROR) << "Couldn't convert write sectors " << parts[6] << " to uint64"; + return false; + } + + return true; +} + +bool AveragedStatisticsCollector::VmStatsParseStats( + const char* stats, struct VmstatRecord* record) { + CHECK(stats); + CHECK(record); + base::StringPairs pairs; + base::SplitStringIntoKeyValuePairs(stats, ' ', '\n', &pairs); + + for (base::StringPairs::iterator it = pairs.begin(); + it != pairs.end(); ++it) { + if (it->first == "pgmajfault" && + !base::StringToUint64(it->second, &record->page_faults)) { + return false; + } + if (it->first == "pswpin" && + !base::StringToUint64(it->second, &record->swap_in)) { + return false; + } + if (it->first == "pswpout" && + !base::StringToUint64(it->second, &record->swap_out)) { + return false; + } + } + return true; +} + +bool AveragedStatisticsCollector::VmStatsReadStats(struct VmstatRecord* stats) { + CHECK(stats); + std::string value_string; + if (!base::ReadFileToString(vmstats_path_, &value_string)) { + LOG(WARNING) << "cannot read " << vmstats_path_.value(); + return false; + } + return VmStatsParseStats(value_string.c_str(), stats); +} + +void AveragedStatisticsCollector::Collect() { + uint64_t read_sectors_now, write_sectors_now; + struct VmstatRecord vmstats_now; + double time_now = MetricsCollector::GetActiveTime(); + double delta_time = time_now - stats_start_time_; + bool diskstats_success = DiskStatsReadStats(&read_sectors_now, + &write_sectors_now); + + int delta_read = read_sectors_now - read_sectors_; + int delta_write = write_sectors_now - write_sectors_; + int read_sectors_per_second = delta_read / delta_time; + int write_sectors_per_second = delta_write / delta_time; + bool vmstats_success = VmStatsReadStats(&vmstats_now); + uint64_t delta_faults = vmstats_now.page_faults - vmstats_.page_faults; + uint64_t delta_swap_in = vmstats_now.swap_in - vmstats_.swap_in; + uint64_t delta_swap_out = vmstats_now.swap_out - vmstats_.swap_out; + uint64_t page_faults_per_second = delta_faults / delta_time; + uint64_t swap_in_per_second = delta_swap_in / delta_time; + uint64_t swap_out_per_second = delta_swap_out / delta_time; + if (diskstats_success) { + metrics_lib_->SendToUMA(kReadSectorsHistogramName, + read_sectors_per_second, + 1, + kSectorsIOMax, + kSectorsBuckets); + metrics_lib_->SendToUMA(kWriteSectorsHistogramName, + write_sectors_per_second, + 1, + kSectorsIOMax, + kSectorsBuckets); + } + if (vmstats_success) { + metrics_lib_->SendToUMA(kPageFaultsHistogramName, + page_faults_per_second, + 1, + kPageFaultsMax, + kPageFaultsBuckets); + metrics_lib_->SendToUMA(kSwapInHistogramName, + swap_in_per_second, + 1, + kPageFaultsMax, + kPageFaultsBuckets); + metrics_lib_->SendToUMA(kSwapOutHistogramName, + swap_out_per_second, + 1, + kPageFaultsMax, + kPageFaultsBuckets); + } +} diff --git a/collectors/averaged_statistics_collector.h b/collectors/averaged_statistics_collector.h new file mode 100644 index 0000000..753f70c --- /dev/null +++ b/collectors/averaged_statistics_collector.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICSD_COLLECTORS_AVERAGED_STATISTICS_COLLECTOR_H_ +#define METRICSD_COLLECTORS_AVERAGED_STATISTICS_COLLECTOR_H_ + +#include "metrics/metrics_library.h" + +class AveragedStatisticsCollector { + public: + AveragedStatisticsCollector(MetricsLibraryInterface* metrics_library, + const std::string& diskstats_path, + const std::string& vmstat_path); + + // Schedule a wait period. + void ScheduleWait(); + + // Schedule a collection period. + void ScheduleCollect(); + + // Callback used by the main loop. + void CollectCallback(); + + // Callback used by the main loop. + void WaitCallback(); + + // Read and store the initial values at the beginning of a collection cycle. + void ReadInitialValues(); + + // Collect the disk usage statistics and report them. + void Collect(); + + private: + friend class AveragedStatisticsTest; + FRIEND_TEST(AveragedStatisticsTest, ParseDiskStats); + FRIEND_TEST(AveragedStatisticsTest, ParseVmStats); + + // Record for retrieving and reporting values from /proc/vmstat + struct VmstatRecord { + uint64_t page_faults; // major faults + uint64_t swap_in; // pages swapped in + uint64_t swap_out; // pages swapped out + }; + + // Read the disk read/write statistics for the main disk. + bool DiskStatsReadStats(uint64_t* read_sectors, uint64_t* write_sectors); + + // Parse the content of the vmstats file into |record|. + bool VmStatsParseStats(const char* stats, struct VmstatRecord* record); + + // Read the vmstats into |stats|. + bool VmStatsReadStats(struct VmstatRecord* stats); + + MetricsLibraryInterface* metrics_lib_; + base::FilePath diskstats_path_; + base::FilePath vmstats_path_; + + // Values observed at the beginning of the collection period. + uint64_t read_sectors_; + uint64_t write_sectors_; + struct VmstatRecord vmstats_; + + double stats_start_time_; +}; + +#endif // METRICSD_COLLECTORS_AVERAGED_STATISTICS_COLLECTOR_H_ diff --git a/collectors/averaged_statistics_collector_test.cc b/collectors/averaged_statistics_collector_test.cc new file mode 100644 index 0000000..68f9f2f --- /dev/null +++ b/collectors/averaged_statistics_collector_test.cc @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "averaged_statistics_collector.h" + +#include <memory> + +#include <inttypes.h> + +#include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.h> +#include <base/strings/stringprintf.h> +#include <gtest/gtest.h> + + +static const char kFakeDiskStatsFormat[] = + " 1793 1788 %" PRIu64 " 105580 " + " 196 175 %" PRIu64 " 30290 " + " 0 44060 135850\n"; +static const uint64_t kFakeReadSectors[] = {80000, 100000}; +static const uint64_t kFakeWriteSectors[] = {3000, 4000}; + + +class AveragedStatisticsTest : public testing::Test { + protected: + std::string kFakeDiskStats0; + std::string kFakeDiskStats1; + + virtual void SetUp() { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + disk_stats_path_ = temp_dir_.path().Append("disk_stats"); + collector_.reset(new AveragedStatisticsCollector( + &metrics_lib_, disk_stats_path_.value(), "")); + + kFakeDiskStats0 = base::StringPrintf(kFakeDiskStatsFormat, + kFakeReadSectors[0], + kFakeWriteSectors[0]); + kFakeDiskStats1 = base::StringPrintf(kFakeDiskStatsFormat, + kFakeReadSectors[1], + kFakeWriteSectors[1]); + + CreateFakeDiskStatsFile(kFakeDiskStats0); + } + + // Creates or overwrites an input file containing fake disk stats. + void CreateFakeDiskStatsFile(const std::string& fake_stats) { + EXPECT_EQ(base::WriteFile(disk_stats_path_, + fake_stats.data(), fake_stats.size()), + fake_stats.size()); + } + + // Collector used for tests. + std::unique_ptr<AveragedStatisticsCollector> collector_; + + // Temporary directory used for tests. + base::ScopedTempDir temp_dir_; + + // Path for the fake files. + base::FilePath disk_stats_path_; + + MetricsLibrary metrics_lib_; +}; + +TEST_F(AveragedStatisticsTest, ParseDiskStats) { + uint64_t read_sectors_now, write_sectors_now; + CreateFakeDiskStatsFile(kFakeDiskStats0); + ASSERT_TRUE(collector_->DiskStatsReadStats(&read_sectors_now, + &write_sectors_now)); + EXPECT_EQ(read_sectors_now, kFakeReadSectors[0]); + EXPECT_EQ(write_sectors_now, kFakeWriteSectors[0]); + + CreateFakeDiskStatsFile(kFakeDiskStats1); + ASSERT_TRUE(collector_->DiskStatsReadStats(&read_sectors_now, + &write_sectors_now)); + EXPECT_EQ(read_sectors_now, kFakeReadSectors[1]); + EXPECT_EQ(write_sectors_now, kFakeWriteSectors[1]); +} + +TEST_F(AveragedStatisticsTest, ParseVmStats) { + static char kVmStats[] = "pswpin 1345\npswpout 8896\n" + "foo 100\nbar 200\npgmajfault 42\netcetc 300\n"; + struct AveragedStatisticsCollector::VmstatRecord stats; + EXPECT_TRUE(collector_->VmStatsParseStats(kVmStats, &stats)); + EXPECT_EQ(stats.page_faults, 42); + EXPECT_EQ(stats.swap_in, 1345); + EXPECT_EQ(stats.swap_out, 8896); +} diff --git a/collectors/cpu_usage_collector.cc b/collectors/cpu_usage_collector.cc new file mode 100644 index 0000000..9b0bb34 --- /dev/null +++ b/collectors/cpu_usage_collector.cc @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "collectors/cpu_usage_collector.h" + +#include <base/bind.h> +#include <base/files/file_path.h> +#include <base/files/file_util.h> +#include <base/message_loop/message_loop.h> +#include <base/strings/string_number_conversions.h> +#include <base/strings/string_split.h> +#include <base/strings/string_util.h> +#include <base/sys_info.h> + +#include "metrics/metrics_library.h" + +namespace { + +const char kCpuUsagePercent[] = "Platform.CpuUsage.Percent"; +const char kMetricsProcStatFileName[] = "/proc/stat"; +const int kMetricsProcStatFirstLineItemsCount = 11; + +// Collect every minute. +const int kCollectionIntervalSecs = 60; + +} // namespace + +using base::TimeDelta; + +CpuUsageCollector::CpuUsageCollector(MetricsLibraryInterface* metrics_library) { + CHECK(metrics_library); + metrics_lib_ = metrics_library; + collect_interval_ = TimeDelta::FromSeconds(kCollectionIntervalSecs); +} + +void CpuUsageCollector::Init() { + num_cpu_ = base::SysInfo::NumberOfProcessors(); + + // Get ticks per second (HZ) on this system. + // Sysconf cannot fail, so no sanity checks are needed. + ticks_per_second_ = sysconf(_SC_CLK_TCK); + CHECK_GT(ticks_per_second_, uint64_t(0)) + << "Number of ticks per seconds should be positive."; + + latest_cpu_use_ = GetCumulativeCpuUse(); +} + +void CpuUsageCollector::CollectCallback() { + Collect(); + Schedule(); +} + +void CpuUsageCollector::Schedule() { + base::MessageLoop::current()->PostDelayedTask(FROM_HERE, + base::Bind(&CpuUsageCollector::CollectCallback, base::Unretained(this)), + collect_interval_); +} + +void CpuUsageCollector::Collect() { + TimeDelta cpu_use = GetCumulativeCpuUse(); + TimeDelta diff_per_cpu = (cpu_use - latest_cpu_use_) / num_cpu_; + latest_cpu_use_ = cpu_use; + + // Report the cpu usage as a percentage of the total cpu usage possible. + int percent_use = diff_per_cpu.InMilliseconds() * 100 / + (kCollectionIntervalSecs * 1000); + + metrics_lib_->SendEnumToUMA(kCpuUsagePercent, percent_use, 101); +} + +TimeDelta CpuUsageCollector::GetCumulativeCpuUse() { + base::FilePath proc_stat_path(kMetricsProcStatFileName); + std::string proc_stat_string; + if (!base::ReadFileToString(proc_stat_path, &proc_stat_string)) { + LOG(WARNING) << "cannot open " << kMetricsProcStatFileName; + return TimeDelta(); + } + + uint64_t user_ticks, user_nice_ticks, system_ticks; + if (!ParseProcStat(proc_stat_string, &user_ticks, &user_nice_ticks, + &system_ticks)) { + return TimeDelta(); + } + + uint64_t total = user_ticks + user_nice_ticks + system_ticks; + return TimeDelta::FromMicroseconds( + total * 1000 * 1000 / ticks_per_second_); +} + +bool CpuUsageCollector::ParseProcStat(const std::string& stat_content, + uint64_t *user_ticks, + uint64_t *user_nice_ticks, + uint64_t *system_ticks) { + std::vector<std::string> proc_stat_lines = base::SplitString( + stat_content, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + if (proc_stat_lines.empty()) { + LOG(WARNING) << "No lines found in " << kMetricsProcStatFileName; + return false; + } + std::vector<std::string> proc_stat_totals = + base::SplitString(proc_stat_lines[0], base::kWhitespaceASCII, + base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + + if (proc_stat_totals.size() != kMetricsProcStatFirstLineItemsCount || + proc_stat_totals[0] != "cpu" || + !base::StringToUint64(proc_stat_totals[1], user_ticks) || + !base::StringToUint64(proc_stat_totals[2], user_nice_ticks) || + !base::StringToUint64(proc_stat_totals[3], system_ticks)) { + LOG(WARNING) << "cannot parse first line: " << proc_stat_lines[0]; + return false; + } + return true; +} diff --git a/collectors/cpu_usage_collector.h b/collectors/cpu_usage_collector.h new file mode 100644 index 0000000..f81dfcb --- /dev/null +++ b/collectors/cpu_usage_collector.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICSD_COLLECTORS_CPU_USAGE_COLLECTOR_H_ +#define METRICSD_COLLECTORS_CPU_USAGE_COLLECTOR_H_ + +#include <base/time/time.h> + +#include "metrics/metrics_library.h" + +class CpuUsageCollector { + public: + CpuUsageCollector(MetricsLibraryInterface* metrics_library); + + // Initialize this collector's state. + void Init(); + + // Schedule a collection interval. + void Schedule(); + + // Callback called at the end of the collection interval. + void CollectCallback(); + + // Measure the cpu use and report it. + void Collect(); + + // Gets the current cumulated Cpu usage. + base::TimeDelta GetCumulativeCpuUse(); + + private: + FRIEND_TEST(CpuUsageTest, ParseProcStat); + bool ParseProcStat(const std::string& stat_content, + uint64_t *user_ticks, + uint64_t *user_nice_ticks, + uint64_t *system_ticks); + + int num_cpu_; + uint32_t ticks_per_second_; + + base::TimeDelta collect_interval_; + base::TimeDelta latest_cpu_use_; + + MetricsLibraryInterface* metrics_lib_; +}; + +#endif // METRICSD_COLLECTORS_CPU_USAGE_COLLECTOR_H_ diff --git a/collectors/cpu_usage_collector_test.cc b/collectors/cpu_usage_collector_test.cc new file mode 100644 index 0000000..ee5c92b --- /dev/null +++ b/collectors/cpu_usage_collector_test.cc @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gtest/gtest.h> + +#include "collectors/cpu_usage_collector.h" +#include "metrics/metrics_library_mock.h" + + +TEST(CpuUsageTest, ParseProcStat) { + MetricsLibraryMock metrics_lib_mock; + CpuUsageCollector collector(&metrics_lib_mock); + std::vector<std::string> invalid_contents = { + "", + // First line does not start with cpu. + "spu 17191 11 36579 151118 289 0 2 0 0 0\n" + "cpu0 1564 2 866 48650 68 0 2 0 0 0\n" + "cpu1 14299 0 35116 1844 81 0 0 0 0 0\n", + // One of the field is not a number. + "cpu a17191 11 36579 151118 289 0 2 0 0 0", + // To many numbers in the first line. + "cpu 17191 11 36579 151118 289 0 2 0 0 0 102" + }; + + uint64_t user, nice, system; + for (int i = 0; i < invalid_contents.size(); i++) { + ASSERT_FALSE(collector.ParseProcStat(invalid_contents[i], &user, &nice, + &system)); + } + + ASSERT_TRUE(collector.ParseProcStat( + std::string("cpu 17191 11 36579 151118 289 0 2 0 0 0"), + &user, &nice, &system)); + ASSERT_EQ(17191, user); + ASSERT_EQ(11, nice); + ASSERT_EQ(36579, system); +} diff --git a/collectors/disk_usage_collector.cc b/collectors/disk_usage_collector.cc new file mode 100644 index 0000000..5ab51fb --- /dev/null +++ b/collectors/disk_usage_collector.cc @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "collectors/disk_usage_collector.h" + +#include <base/bind.h> +#include <base/bind_helpers.h> +#include <base/message_loop/message_loop.h> +#include <sys/statvfs.h> + +#include "metrics/metrics_library.h" + +namespace { + +const char kDiskUsageMB[] = "Platform.DataPartitionUsed.MB"; +const char kDiskUsagePercent[] = "Platform.DataPartitionUsed.Percent"; +const char kDataPartitionPath[] = "/data"; + +// Collect every 15 minutes. +const int kDiskUsageCollectorIntervalSeconds = 900; + +} // namespace + +DiskUsageCollector::DiskUsageCollector( + MetricsLibraryInterface* metrics_library) { + collect_interval_ = base::TimeDelta::FromSeconds( + kDiskUsageCollectorIntervalSeconds); + CHECK(metrics_library); + metrics_lib_ = metrics_library; +} + +void DiskUsageCollector::Collect() { + struct statvfs buf; + int result = statvfs(kDataPartitionPath, &buf); + if (result != 0) { + PLOG(ERROR) << "Failed to check the available space in " + << kDataPartitionPath; + return; + } + + unsigned long total_space = buf.f_blocks * buf.f_bsize; + unsigned long used_space = (buf.f_blocks - buf.f_bfree) * buf.f_bsize; + int percent_used = (used_space * 100) / total_space; + + metrics_lib_->SendToUMA(kDiskUsageMB, + used_space / (1024 * 1024), + 0, + 1024, // up to 1 GB. + 100); + metrics_lib_->SendEnumToUMA(kDiskUsagePercent, percent_used, 101); +} + +void DiskUsageCollector::CollectCallback() { + Collect(); + Schedule(); +} + +void DiskUsageCollector::Schedule() { + base::MessageLoop::current()->PostDelayedTask(FROM_HERE, + base::Bind(&DiskUsageCollector::CollectCallback, base::Unretained(this)), + collect_interval_); +} diff --git a/collectors/disk_usage_collector.h b/collectors/disk_usage_collector.h new file mode 100644 index 0000000..c1d4546 --- /dev/null +++ b/collectors/disk_usage_collector.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICSD_COLLECTORS_DISK_USAGE_COLLECTOR_H_ +#define METRICSD_COLLECTORS_DISK_USAGE_COLLECTOR_H_ + +#include <base/time/time.h> + +#include "metrics/metrics_library.h" + +class DiskUsageCollector { + public: + DiskUsageCollector(MetricsLibraryInterface* metrics_library); + + // Schedule the next collection. + void Schedule(); + + // Callback used by the main loop. + void CollectCallback(); + + // Collect the disk usage statistics and report them. + void Collect(); + + private: + base::TimeDelta collect_interval_; + MetricsLibraryInterface* metrics_lib_; +}; + +#endif // METRICSD_COLLECTORS_DISK_USAGE_COLLECTOR_H_ diff --git a/constants.h b/constants.h new file mode 100644 index 0000000..b702737 --- /dev/null +++ b/constants.h @@ -0,0 +1,42 @@ +// +// Copyright (C) 2015 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef METRICS_CONSTANTS_H_ +#define METRICS_CONSTANTS_H_ + +namespace metrics { +static const char kSharedMetricsDirectory[] = "/data/misc/metrics/"; +static const char kMetricsdDirectory[] = "/data/misc/metricsd/"; +static const char kMetricsCollectorDirectory[] = + "/data/misc/metrics_collector/"; +static const char kMetricsGUIDFileName[] = "Sysinfo.GUID"; +static const char kMetricsServer[] = "https://clients4.google.com/uma/v2"; +static const char kConsentFileName[] = "enabled"; +static const char kStagedLogName[] = "staged_log"; +static const char kSavedLogName[] = "saved_log"; +static const char kFailedUploadCountName[] = "failed_upload_count"; +static const char kDefaultVersion[] = "0.0.0.0"; + +// Build time properties name. +static const char kProductId[] = "product_id"; +static const char kProductVersion[] = "product_version"; + +// Weave configuration. +static const char kWeaveConfigurationFile[] = "/system/etc/weaved/weaved.conf"; +static const char kModelManifestId[] = "model_id"; +} // namespace metrics + +#endif // METRICS_CONSTANTS_H_ diff --git a/etc/weaved/traits/metrics.json b/etc/weaved/traits/metrics.json new file mode 100644 index 0000000..7583270 --- /dev/null +++ b/etc/weaved/traits/metrics.json @@ -0,0 +1,20 @@ +{ + "_metrics": { + "commands": { + "enableAnalyticsReporting": { + "minimalRole": "manager", + "parameters": {} + }, + "disableAnalyticsReporting": { + "minimalRole": "manager", + "parameters": {} + } + }, + "state": { + "analyticsReportingState": { + "type": "string", + "enum": [ "enabled", "disabled" ] + } + } + } +} diff --git a/include/metrics/c_metrics_library.h b/include/metrics/c_metrics_library.h new file mode 100644 index 0000000..1e597c2 --- /dev/null +++ b/include/metrics/c_metrics_library.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICS_C_METRICS_LIBRARY_H_ +#define METRICS_C_METRICS_LIBRARY_H_ + +#if defined(__cplusplus) +extern "C" { +#endif +typedef struct CMetricsLibraryOpaque* CMetricsLibrary; + +// C wrapper for MetricsLibrary::MetricsLibrary. +CMetricsLibrary CMetricsLibraryNew(void); + +// C wrapper for MetricsLibrary::~MetricsLibrary. +void CMetricsLibraryDelete(CMetricsLibrary handle); + +// C wrapper for MetricsLibrary::Init. +void CMetricsLibraryInit(CMetricsLibrary handle); + +// C wrapper for MetricsLibrary::SendToUMA. +int CMetricsLibrarySendToUMA(CMetricsLibrary handle, + const char* name, int sample, + int min, int max, int nbuckets); + +// C wrapper for MetricsLibrary::SendEnumToUMA. +int CMetricsLibrarySendEnumToUMA(CMetricsLibrary handle, + const char* name, int sample, int max); + +// C wrapper for MetricsLibrary::SendSparseToUMA. +int CMetricsLibrarySendSparseToUMA(CMetricsLibrary handle, + const char* name, int sample); + +// C wrapper for MetricsLibrary::SendCrashToUMA. +int CMetricsLibrarySendCrashToUMA(CMetricsLibrary handle, + const char* crash_kind); + +// C wrapper for MetricsLibrary::AreMetricsEnabled. +int CMetricsLibraryAreMetricsEnabled(CMetricsLibrary handle); + +#if defined(__cplusplus) +} +#endif +#endif // METRICS_C_METRICS_LIBRARY_H_ diff --git a/include/metrics/metrics_collector_service_client.h b/include/metrics/metrics_collector_service_client.h new file mode 100644 index 0000000..c800eae --- /dev/null +++ b/include/metrics/metrics_collector_service_client.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Client interface to IMetricsCollectorService. + +#ifndef METRICS_METRICS_COLLECTOR_SERVICE_CLIENT_H_ +#define METRICS_METRICS_COLLECTOR_SERVICE_CLIENT_H_ + +#include "android/brillo/metrics/IMetricsCollectorService.h" + +class MetricsCollectorServiceClient { + public: + MetricsCollectorServiceClient() = default; + ~MetricsCollectorServiceClient() = default; + + // Initialize. Returns true if OK, or false if IMetricsCollectorService + // is not registered. + bool Init(); + + // Called by crash_reporter to report a userspace crash event. Returns + // true if successfully called the IMetricsCollectorService method of the + // same name, or false if the service was not registered at Init() time. + bool notifyUserCrash(); + + private: + // IMetricsCollectorService binder proxy + android::sp<android::brillo::metrics::IMetricsCollectorService> + metrics_collector_service_; +}; + +#endif // METRICS_METRICS_COLLECTOR_SERVICE_CLIENT_H_ diff --git a/include/metrics/metrics_library.h b/include/metrics/metrics_library.h new file mode 100644 index 0000000..a1bb926 --- /dev/null +++ b/include/metrics/metrics_library.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICS_METRICS_LIBRARY_H_ +#define METRICS_METRICS_LIBRARY_H_ + +#include <sys/types.h> +#include <string> +#include <unistd.h> + +#include <base/compiler_specific.h> +#include <base/files/file_path.h> +#include <base/macros.h> +#include <binder/IServiceManager.h> +#include <gtest/gtest_prod.h> // for FRIEND_TEST + +namespace android { +namespace brillo { +namespace metrics { +class IMetricsd; +} // namespace metrics +} // namespace brillo +} // namespace android + +class MetricsLibraryInterface { + public: + virtual void Init() = 0; + virtual bool AreMetricsEnabled() = 0; + virtual bool SendToUMA(const std::string& name, int sample, + int min, int max, int nbuckets) = 0; + virtual bool SendEnumToUMA(const std::string& name, int sample, int max) = 0; + virtual bool SendBoolToUMA(const std::string& name, bool sample) = 0; + virtual bool SendSparseToUMA(const std::string& name, int sample) = 0; + virtual ~MetricsLibraryInterface() {} +}; + +// Library used to send metrics to Chrome/UMA. +class MetricsLibrary : public MetricsLibraryInterface { + public: + MetricsLibrary(); + virtual ~MetricsLibrary(); + + // Initializes the library. + void Init() override; + + // Initializes the library and disables the cache of whether or not the + // metrics collection is enabled. + // By disabling this, we may have to check for the metrics status more often + // but the result will never be stale. + void InitWithNoCaching(); + + // Returns whether or not the machine is running in guest mode. + bool IsGuestMode(); + + // Returns whether or not metrics collection is enabled. + bool AreMetricsEnabled() override; + + // Sends histogram data to Chrome for transport to UMA and returns + // true on success. This method results in the equivalent of an + // asynchronous non-blocking RPC to UMA_HISTOGRAM_CUSTOM_COUNTS + // inside Chrome (see base/histogram.h). + // + // |sample| is the sample value to be recorded (|min| <= |sample| < |max|). + // |min| is the minimum value of the histogram samples (|min| > 0). + // |max| is the maximum value of the histogram samples. + // |nbuckets| is the number of histogram buckets. + // [0,min) is the implicit underflow bucket. + // [|max|,infinity) is the implicit overflow bucket. + // + // Note that the memory allocated in Chrome for each histogram is + // proportional to the number of buckets. Therefore, it is strongly + // recommended to keep this number low (e.g., 50 is normal, while + // 100 is high). + bool SendToUMA(const std::string& name, int sample, + int min, int max, int nbuckets) override; + + // Sends linear histogram data to Chrome for transport to UMA and + // returns true on success. This method results in the equivalent of + // an asynchronous non-blocking RPC to UMA_HISTOGRAM_ENUMERATION + // inside Chrome (see base/histogram.h). + // + // |sample| is the sample value to be recorded (1 <= |sample| < |max|). + // |max| is the maximum value of the histogram samples. + // 0 is the implicit underflow bucket. + // [|max|,infinity) is the implicit overflow bucket. + // + // An enumeration histogram requires |max| + 1 number of + // buckets. Note that the memory allocated in Chrome for each + // histogram is proportional to the number of buckets. Therefore, it + // is strongly recommended to keep this number low (e.g., 50 is + // normal, while 100 is high). + bool SendEnumToUMA(const std::string& name, int sample, int max) override; + + // Specialization of SendEnumToUMA for boolean values. + bool SendBoolToUMA(const std::string& name, bool sample) override; + + // Sends sparse histogram sample to Chrome for transport to UMA. Returns + // true on success. + // + // |sample| is the 32-bit integer value to be recorded. + bool SendSparseToUMA(const std::string& name, int sample) override; + + // Sends a signal to UMA that a crash of the given |crash_kind| + // has occurred. Used by UMA to generate stability statistics. + bool SendCrashToUMA(const char *crash_kind); + + // Sends a "generic Chrome OS event" to UMA. This is an event name + // that is translated into an enumerated histogram entry. Event names + // are added to metrics_library.cc. Optionally, they can be added + // to histograms.xml---but part of the reason for this is to simplify + // the addition of events (at the cost of having to look them up by + // number in the histograms dashboard). + bool SendCrosEventToUMA(const std::string& event); + + // Debugging only. + // Dumps the histograms aggregated since metricsd started into |dump|. + // Returns true iff the dump succeeds. + bool GetHistogramsDump(std::string* dump); + + private: + friend class CMetricsLibraryTest; + friend class MetricsLibraryTest; + friend class UploadServiceTest; + FRIEND_TEST(MetricsLibraryTest, AreMetricsEnabled); + FRIEND_TEST(MetricsLibraryTest, AreMetricsEnabledNoCaching); + FRIEND_TEST(MetricsLibraryTest, FormatChromeMessage); + FRIEND_TEST(MetricsLibraryTest, FormatChromeMessageTooLong); + FRIEND_TEST(MetricsLibraryTest, IsDeviceMounted); + FRIEND_TEST(MetricsLibraryTest, SendMessageToChrome); + FRIEND_TEST(MetricsLibraryTest, SendMessageToChromeUMAEventsBadFileLocation); + + void InitForTest(const base::FilePath& metrics_directory); + + // Sets |*result| to whether or not the |mounts_file| indicates that + // the |device_name| is currently mounted. Uses |buffer| of + // |buffer_size| to read the file. Returns false if any error. + bool IsDeviceMounted(const char* device_name, + const char* mounts_file, + char* buffer, int buffer_size, + bool* result); + + // Connects to IMetricsd if the proxy does not exist or is not alive. + // Don't block if we fail to get the proxy for any reason. + bool CheckService(); + + // Time at which we last checked if metrics were enabled. + time_t cached_enabled_time_; + + // Cached state of whether or not metrics were enabled. + bool cached_enabled_; + + // True iff we should cache the enabled/disabled status. + bool use_caching_; + + android::sp<android::IServiceManager> manager_; + android::sp<android::brillo::metrics::IMetricsd> metricsd_proxy_; + base::FilePath consent_file_; + + DISALLOW_COPY_AND_ASSIGN(MetricsLibrary); +}; + +#endif // METRICS_METRICS_LIBRARY_H_ diff --git a/include/metrics/metrics_library_mock.h b/include/metrics/metrics_library_mock.h new file mode 100644 index 0000000..3b0b24d --- /dev/null +++ b/include/metrics/metrics_library_mock.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICS_METRICS_LIBRARY_MOCK_H_ +#define METRICS_METRICS_LIBRARY_MOCK_H_ + +#include <string> + +#include "metrics/metrics_library.h" + +#include <gmock/gmock.h> + +class MetricsLibraryMock : public MetricsLibraryInterface { + public: + bool metrics_enabled_ = true; + + MOCK_METHOD0(Init, void()); + MOCK_METHOD5(SendToUMA, bool(const std::string& name, int sample, + int min, int max, int nbuckets)); + MOCK_METHOD3(SendEnumToUMA, bool(const std::string& name, int sample, + int max)); + MOCK_METHOD2(SendBoolToUMA, bool(const std::string& name, bool sample)); + MOCK_METHOD2(SendSparseToUMA, bool(const std::string& name, int sample)); + + bool AreMetricsEnabled() override {return metrics_enabled_;}; +}; + +#endif // METRICS_METRICS_LIBRARY_MOCK_H_ diff --git a/include/metrics/timer.h b/include/metrics/timer.h new file mode 100644 index 0000000..c1b8ede --- /dev/null +++ b/include/metrics/timer.h @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Timer - class that provides timer tracking. + +#ifndef METRICS_TIMER_H_ +#define METRICS_TIMER_H_ + +#include <memory> +#include <string> + +#include <base/macros.h> +#include <base/time/time.h> +#include <gtest/gtest_prod.h> // for FRIEND_TEST + +class MetricsLibraryInterface; + +namespace chromeos_metrics { + +class TimerInterface { + public: + virtual ~TimerInterface() {} + + virtual bool Start() = 0; + virtual bool Stop() = 0; + virtual bool Reset() = 0; + virtual bool HasStarted() const = 0; +}; + +// Wrapper for calls to the system clock. +class ClockWrapper { + public: + ClockWrapper() {} + virtual ~ClockWrapper() {} + + // Returns the current time from the system. + virtual base::TimeTicks GetCurrentTime() const; + + private: + DISALLOW_COPY_AND_ASSIGN(ClockWrapper); +}; + +// Implements a Timer. +class Timer : public TimerInterface { + public: + Timer(); + virtual ~Timer() {} + + // Starts the timer. If a timer is already running, also resets current + // timer. Always returns true. + virtual bool Start(); + + // Stops the timer and calculates the total time elapsed between now and when + // Start() was called. Note that this method needs a prior call to Start(). + // Otherwise, it fails (returns false). + virtual bool Stop(); + + // Pauses a timer. If the timer is stopped, this call starts the timer in + // the paused state. Fails (returns false) if the timer is already paused. + virtual bool Pause(); + + // Restarts a paused timer (or starts a stopped timer). This method fails + // (returns false) if the timer is already running; otherwise, returns true. + virtual bool Resume(); + + // Resets the timer, erasing the current duration being tracked. Always + // returns true. + virtual bool Reset(); + + // Returns whether the timer has started or not. + virtual bool HasStarted() const; + + // Stores the current elapsed time in |elapsed_time|. If timer is stopped, + // stores the elapsed time from when Stop() was last called. Otherwise, + // calculates and stores the elapsed time since the last Start(). + // Returns false if the timer was never Start()'ed or if called with a null + // pointer argument. + virtual bool GetElapsedTime(base::TimeDelta* elapsed_time) const; + + private: + enum TimerState { kTimerStopped, kTimerRunning, kTimerPaused }; + friend class TimerTest; + friend class TimerReporterTest; + FRIEND_TEST(TimerReporterTest, StartStopReport); + FRIEND_TEST(TimerTest, InvalidElapsedTime); + FRIEND_TEST(TimerTest, InvalidStop); + FRIEND_TEST(TimerTest, PauseResumeStop); + FRIEND_TEST(TimerTest, PauseStartStopResume); + FRIEND_TEST(TimerTest, PauseStop); + FRIEND_TEST(TimerTest, Reset); + FRIEND_TEST(TimerTest, ReStart); + FRIEND_TEST(TimerTest, ResumeStartStopPause); + FRIEND_TEST(TimerTest, SeparatedTimers); + FRIEND_TEST(TimerTest, StartPauseResumePauseResumeStop); + FRIEND_TEST(TimerTest, StartPauseResumePauseStop); + FRIEND_TEST(TimerTest, StartPauseResumeStop); + FRIEND_TEST(TimerTest, StartPauseStop); + FRIEND_TEST(TimerTest, StartResumeStop); + FRIEND_TEST(TimerTest, StartStop); + + // Elapsed time of the last use of the timer. + base::TimeDelta elapsed_time_; + + // Starting time value. + base::TimeTicks start_time_; + + // Whether the timer is running, stopped, or paused. + TimerState timer_state_; + + // Wrapper for the calls to the system clock. + std::unique_ptr<ClockWrapper> clock_wrapper_; + + DISALLOW_COPY_AND_ASSIGN(Timer); +}; + +// Extends the Timer class to report the elapsed time in milliseconds through +// the UMA metrics library. +class TimerReporter : public Timer { + public: + // Initializes the timer by providing a |histogram_name| to report to with + // |min|, |max| and |num_buckets| attributes for the histogram. + TimerReporter(const std::string& histogram_name, int min, int max, + int num_buckets); + virtual ~TimerReporter() {} + + // Sets the metrics library used by all instances of this class. + static void set_metrics_lib(MetricsLibraryInterface* metrics_lib) { + metrics_lib_ = metrics_lib; + } + + // Reports the current duration to UMA, in milliseconds. Returns false if + // there is nothing to report, e.g. a metrics library is not set. + virtual bool ReportMilliseconds() const; + + // Accessor methods. + const std::string& histogram_name() const { return histogram_name_; } + int min() const { return min_; } + int max() const { return max_; } + int num_buckets() const { return num_buckets_; } + + private: + friend class TimerReporterTest; + FRIEND_TEST(TimerReporterTest, StartStopReport); + FRIEND_TEST(TimerReporterTest, InvalidReport); + + static MetricsLibraryInterface* metrics_lib_; + std::string histogram_name_; + int min_; + int max_; + int num_buckets_; + + DISALLOW_COPY_AND_ASSIGN(TimerReporter); +}; + +} // namespace chromeos_metrics + +#endif // METRICS_TIMER_H_ diff --git a/include/metrics/timer_mock.h b/include/metrics/timer_mock.h new file mode 100644 index 0000000..200ee9a --- /dev/null +++ b/include/metrics/timer_mock.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICS_TIMER_MOCK_H_ +#define METRICS_TIMER_MOCK_H_ + +#include <string> + +#include <gmock/gmock.h> + +#include "metrics/timer.h" + +namespace chromeos_metrics { + +class TimerMock : public Timer { + public: + MOCK_METHOD0(Start, bool()); + MOCK_METHOD0(Stop, bool()); + MOCK_METHOD0(Reset, bool()); + MOCK_CONST_METHOD0(HasStarted, bool()); + MOCK_CONST_METHOD1(GetElapsedTime, bool(base::TimeDelta* elapsed_time)); +}; + +class TimerReporterMock : public TimerReporter { + public: + TimerReporterMock() : TimerReporter("", 0, 0, 0) {} + MOCK_METHOD0(Start, bool()); + MOCK_METHOD0(Stop, bool()); + MOCK_METHOD0(Reset, bool()); + MOCK_CONST_METHOD0(HasStarted, bool()); + MOCK_CONST_METHOD1(GetElapsedTime, bool(base::TimeDelta* elapsed_time)); + MOCK_CONST_METHOD0(ReportMilliseconds, bool()); + MOCK_CONST_METHOD0(histogram_name, std::string&()); + MOCK_CONST_METHOD0(min, int()); + MOCK_CONST_METHOD0(max, int()); + MOCK_CONST_METHOD0(num_buckets, int()); +}; + +class ClockWrapperMock : public ClockWrapper { + public: + MOCK_CONST_METHOD0(GetCurrentTime, base::TimeTicks()); +}; + +} // namespace chromeos_metrics + +#endif // METRICS_TIMER_MOCK_H_ diff --git a/libmetrics-369476.gyp b/libmetrics-369476.gyp new file mode 100644 index 0000000..b545d35 --- /dev/null +++ b/libmetrics-369476.gyp @@ -0,0 +1,8 @@ +{ + 'variables': { + 'libbase_ver': 369476, + }, + 'includes': [ + 'libmetrics.gypi', + ], +} diff --git a/libmetrics.gypi b/libmetrics.gypi new file mode 100644 index 0000000..3c8fc7c --- /dev/null +++ b/libmetrics.gypi @@ -0,0 +1,33 @@ +{ + 'target_defaults': { + 'variables': { + 'deps': [ + 'libbrillo-<(libbase_ver)', + 'libchrome-<(libbase_ver)', + ] + }, + 'cflags_cc': [ + '-fno-exceptions', + ], + }, + 'targets': [ + { + 'target_name': 'libmetrics-<(libbase_ver)', + 'type': 'shared_library', + 'cflags': [ + '-fvisibility=default', + ], + 'libraries+': [ + '-lpolicy-<(libbase_ver)', + ], + 'sources': [ + 'c_metrics_library.cc', + 'metrics_library.cc', + 'serialization/metric_sample.cc', + 'serialization/serialization_utils.cc', + 'timer.cc', + ], + 'include_dirs': ['.'], + }, + ], +} diff --git a/libmetrics.pc.in b/libmetrics.pc.in new file mode 100644 index 0000000..233f318 --- /dev/null +++ b/libmetrics.pc.in @@ -0,0 +1,7 @@ +bslot=@BSLOT@ + +Name: libmetrics +Description: Chrome OS metrics library +Version: ${bslot} +Requires.private: libchrome-${bslot} +Libs: -lmetrics-${bslot} diff --git a/metrics.gyp b/metrics.gyp new file mode 100644 index 0000000..c9c02d7 --- /dev/null +++ b/metrics.gyp @@ -0,0 +1,184 @@ +{ + 'target_defaults': { + 'variables': { + 'deps': [ + 'dbus-1', + 'libbrillo-<(libbase_ver)', + 'libchrome-<(libbase_ver)', + ] + }, + 'cflags_cc': [ + '-fno-exceptions', + ], + }, + 'targets': [ + { + 'target_name': 'libmetrics_daemon', + 'type': 'static_library', + 'dependencies': [ + '../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)', + 'libupload_service', + 'metrics_proto', + ], + 'link_settings': { + 'libraries': [ + '-lrootdev', + ], + }, + 'sources': [ + 'persistent_integer.cc', + 'metrics_daemon.cc', + 'metrics_daemon_main.cc', + ], + 'include_dirs': ['.'], + }, + { + 'target_name': 'metrics_client', + 'type': 'executable', + 'dependencies': [ + '../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)', + ], + 'sources': [ + 'metrics_client.cc', + ] + }, + { + 'target_name': 'libupload_service', + 'type': 'static_library', + 'dependencies': [ + 'metrics_proto', + '../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)', + ], + 'link_settings': { + 'libraries': [ + '-lvboot_host', + ], + }, + 'variables': { + 'exported_deps': [ + 'protobuf-lite', + ], + 'deps': [ + '<@(exported_deps)', + ], + }, + 'all_dependent_settings': { + 'variables': { + 'deps+': [ + '<@(exported_deps)', + ], + }, + }, + 'sources': [ + 'uploader/upload_service.cc', + 'uploader/metrics_hashes.cc', + 'uploader/metrics_log.cc', + 'uploader/metrics_log_base.cc', + 'uploader/system_profile_cache.cc', + 'uploader/sender_http.cc', + ], + 'include_dirs': ['.'] + }, + { + 'target_name': 'metrics_proto', + 'type': 'static_library', + 'variables': { + 'proto_in_dir': 'uploader/proto', + 'proto_out_dir': 'include/metrics/uploader/proto', + }, + 'sources': [ + '<(proto_in_dir)/chrome_user_metrics_extension.proto', + '<(proto_in_dir)/histogram_event.proto', + '<(proto_in_dir)/system_profile.proto', + '<(proto_in_dir)/user_action_event.proto', + ], + 'includes': [ + '../common-mk/protoc.gypi' + ], + }, + ], + 'conditions': [ + ['USE_passive_metrics == 1', { + 'targets': [ + { + 'target_name': 'metrics_daemon', + 'type': 'executable', + 'dependencies': ['libmetrics_daemon'], + }, + ], + }], + ['USE_test == 1', { + 'targets': [ + { + 'target_name': 'persistent_integer_test', + 'type': 'executable', + 'includes': ['../common-mk/common_test.gypi'], + 'sources': [ + 'persistent_integer.cc', + 'persistent_integer_test.cc', + ] + }, + { + 'target_name': 'metrics_library_test', + 'type': 'executable', + 'dependencies': [ + '../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)', + ], + 'includes': ['../common-mk/common_test.gypi'], + 'sources': [ + 'metrics_library_test.cc', + 'serialization/serialization_utils_unittest.cc', + ], + 'link_settings': { + 'libraries': [ + '-lpolicy-<(libbase_ver)', + ] + } + }, + { + 'target_name': 'timer_test', + 'type': 'executable', + 'includes': ['../common-mk/common_test.gypi'], + 'sources': [ + 'timer.cc', + 'timer_test.cc', + ] + }, + { + 'target_name': 'upload_service_test', + 'type': 'executable', + 'sources': [ + 'persistent_integer.cc', + 'uploader/metrics_hashes_unittest.cc', + 'uploader/metrics_log_base_unittest.cc', + 'uploader/mock/sender_mock.cc', + 'uploader/upload_service_test.cc', + ], + 'dependencies': [ + 'libupload_service', + ], + 'includes':[ + '../common-mk/common_test.gypi', + ], + 'include_dirs': ['.'] + }, + ], + }], + ['USE_passive_metrics == 1 and USE_test == 1', { + 'targets': [ + { + 'target_name': 'metrics_daemon_test', + 'type': 'executable', + 'dependencies': [ + 'libmetrics_daemon', + ], + 'includes': ['../common-mk/common_test.gypi'], + 'sources': [ + 'metrics_daemon_test.cc', + ], + 'include_dirs': ['.'], + }, + ], + }], + ] +} diff --git a/metrics_client.cc b/metrics_client.cc new file mode 100644 index 0000000..c66b975 --- /dev/null +++ b/metrics_client.cc @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cstdio> +#include <cstdlib> + +#include "constants.h" +#include "metrics/metrics_library.h" + +enum Mode { + kModeDumpHistograms, + kModeSendSample, + kModeSendEnumSample, + kModeSendSparseSample, + kModeSendCrosEvent, + kModeHasConsent, + kModeIsGuestMode, +}; + +void ShowUsage() { + fprintf(stderr, + "Usage: metrics_client [-t] name sample min max nbuckets\n" + " metrics_client -e name sample max\n" + " metrics_client -s name sample\n" + " metrics_client -v event\n" + " metrics_client [-cdg]\n" + "\n" + " default: send metric with integer values \n" + " |min| > 0, |min| <= sample < |max|\n" + " -c: return exit status 0 if user consents to stats, 1 otherwise,\n" + " in guest mode always return 1\n" + " -d: dump the histograms recorded by metricsd to stdout\n" + " -e: send linear/enumeration histogram data\n" + " -g: return exit status 0 if machine in guest mode, 1 otherwise\n" + " -s: send a sparse histogram sample\n" + " -t: convert sample from double seconds to int milliseconds\n" + " -v: send a Platform.CrOSEvent enum histogram sample\n"); + exit(1); +} + +static int ParseInt(const char *arg) { + char *endptr; + int value = strtol(arg, &endptr, 0); + if (*endptr != '\0') { + fprintf(stderr, "metrics client: bad integer \"%s\"\n", arg); + ShowUsage(); + } + return value; +} + +static double ParseDouble(const char *arg) { + char *endptr; + double value = strtod(arg, &endptr); + if (*endptr != '\0') { + fprintf(stderr, "metrics client: bad double \"%s\"\n", arg); + ShowUsage(); + } + return value; +} + +static int DumpHistograms() { + MetricsLibrary metrics_lib; + metrics_lib.Init(); + + std::string dump; + if (!metrics_lib.GetHistogramsDump(&dump)) { + printf("Failed to dump the histograms."); + return 1; + } + + printf("%s\n", dump.c_str()); + return 0; +} + +static int SendStats(char* argv[], + int name_index, + enum Mode mode, + bool secs_to_msecs) { + const char* name = argv[name_index]; + int sample; + if (secs_to_msecs) { + sample = static_cast<int>(ParseDouble(argv[name_index + 1]) * 1000.0); + } else { + sample = ParseInt(argv[name_index + 1]); + } + + MetricsLibrary metrics_lib; + metrics_lib.Init(); + if (mode == kModeSendSparseSample) { + metrics_lib.SendSparseToUMA(name, sample); + } else if (mode == kModeSendEnumSample) { + int max = ParseInt(argv[name_index + 2]); + metrics_lib.SendEnumToUMA(name, sample, max); + } else { + int min = ParseInt(argv[name_index + 2]); + int max = ParseInt(argv[name_index + 3]); + int nbuckets = ParseInt(argv[name_index + 4]); + metrics_lib.SendToUMA(name, sample, min, max, nbuckets); + } + return 0; +} + +static int SendCrosEvent(char* argv[], int action_index) { + const char* event = argv[action_index]; + bool result; + MetricsLibrary metrics_lib; + metrics_lib.Init(); + result = metrics_lib.SendCrosEventToUMA(event); + if (!result) { + fprintf(stderr, "metrics_client: could not send event %s\n", event); + return 1; + } + return 0; +} + +static int HasConsent() { + MetricsLibrary metrics_lib; + metrics_lib.Init(); + return metrics_lib.AreMetricsEnabled() ? 0 : 1; +} + +static int IsGuestMode() { + MetricsLibrary metrics_lib; + metrics_lib.Init(); + return metrics_lib.IsGuestMode() ? 0 : 1; +} + +int main(int argc, char** argv) { + enum Mode mode = kModeSendSample; + bool secs_to_msecs = false; + + // Parse arguments + int flag; + while ((flag = getopt(argc, argv, "abcdegstv")) != -1) { + switch (flag) { + case 'c': + mode = kModeHasConsent; + break; + case 'd': + mode = kModeDumpHistograms; + break; + case 'e': + mode = kModeSendEnumSample; + break; + case 'g': + mode = kModeIsGuestMode; + break; + case 's': + mode = kModeSendSparseSample; + break; + case 't': + secs_to_msecs = true; + break; + case 'v': + mode = kModeSendCrosEvent; + break; + default: + ShowUsage(); + break; + } + } + int arg_index = optind; + + int expected_args = 0; + if (mode == kModeSendSample) + expected_args = 5; + else if (mode == kModeSendEnumSample) + expected_args = 3; + else if (mode == kModeSendSparseSample) + expected_args = 2; + else if (mode == kModeSendCrosEvent) + expected_args = 1; + + if ((arg_index + expected_args) != argc) { + ShowUsage(); + } + + switch (mode) { + case kModeDumpHistograms: + return DumpHistograms(); + case kModeSendSample: + case kModeSendEnumSample: + case kModeSendSparseSample: + if ((mode != kModeSendSample) && secs_to_msecs) { + ShowUsage(); + } + return SendStats(argv, + arg_index, + mode, + secs_to_msecs); + case kModeSendCrosEvent: + return SendCrosEvent(argv, arg_index); + case kModeHasConsent: + return HasConsent(); + case kModeIsGuestMode: + return IsGuestMode(); + default: + ShowUsage(); + return 0; + } +} diff --git a/metrics_collector.cc b/metrics_collector.cc new file mode 100644 index 0000000..45ae0a4 --- /dev/null +++ b/metrics_collector.cc @@ -0,0 +1,756 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "metrics_collector.h" + +#include <sysexits.h> +#include <time.h> + +#include <memory> + +#include <base/bind.h> +#include <base/files/file_path.h> +#include <base/files/file_util.h> +#include <base/hash.h> +#include <base/logging.h> +#include <base/strings/string_number_conversions.h> +#include <base/strings/string_split.h> +#include <base/strings/string_util.h> +#include <base/strings/stringprintf.h> +#include <brillo/binder_watcher.h> +#include <brillo/osrelease_reader.h> + +#include "constants.h" +#include "metrics_collector_service_impl.h" + +using base::FilePath; +using base::StringPrintf; +using base::Time; +using base::TimeDelta; +using base::TimeTicks; +using chromeos_metrics::PersistentInteger; +using std::map; +using std::string; +using std::vector; + +namespace { + +const int kSecondsPerMinute = 60; +const int kMinutesPerHour = 60; +const int kHoursPerDay = 24; +const int kMinutesPerDay = kHoursPerDay * kMinutesPerHour; +const int kSecondsPerDay = kSecondsPerMinute * kMinutesPerDay; +const int kDaysPerWeek = 7; +const int kSecondsPerWeek = kSecondsPerDay * kDaysPerWeek; + +// Interval between calls to UpdateStats(). +const uint32_t kUpdateStatsIntervalMs = 300000; + +const char kKernelCrashDetectedFile[] = + "/data/misc/crash_reporter/run/kernel-crash-detected"; +const char kUncleanShutdownDetectedFile[] = + "/var/run/unclean-shutdown-detected"; + +const int kMetricMeminfoInterval = 30; // seconds + +const char kMeminfoFileName[] = "/proc/meminfo"; +const char kVmStatFileName[] = "/proc/vmstat"; + +const char kWeaveComponent[] = "metrics"; +const char kWeaveTrait[] = "_metrics"; + +} // namespace + +// Zram sysfs entries. + +const char MetricsCollector::kComprDataSizeName[] = "compr_data_size"; +const char MetricsCollector::kOrigDataSizeName[] = "orig_data_size"; +const char MetricsCollector::kZeroPagesName[] = "zero_pages"; + +// Memory use stats collection intervals. We collect some memory use interval +// at these intervals after boot, and we stop collecting after the last one, +// with the assumption that in most cases the memory use won't change much +// after that. +static const int kMemuseIntervals[] = { + 1 * kSecondsPerMinute, // 1 minute mark + 4 * kSecondsPerMinute, // 5 minute mark + 25 * kSecondsPerMinute, // 0.5 hour mark + 120 * kSecondsPerMinute, // 2.5 hour mark + 600 * kSecondsPerMinute, // 12.5 hour mark +}; + +MetricsCollector::MetricsCollector() + : memuse_final_time_(0), + memuse_interval_index_(0) {} + +MetricsCollector::~MetricsCollector() { +} + +// static +double MetricsCollector::GetActiveTime() { + struct timespec ts; + int r = clock_gettime(CLOCK_MONOTONIC, &ts); + if (r < 0) { + PLOG(WARNING) << "clock_gettime(CLOCK_MONOTONIC) failed"; + return 0; + } else { + return ts.tv_sec + static_cast<double>(ts.tv_nsec) / (1000 * 1000 * 1000); + } +} + +int MetricsCollector::Run() { + if (CheckSystemCrash(kKernelCrashDetectedFile)) { + ProcessKernelCrash(); + } + + if (CheckSystemCrash(kUncleanShutdownDetectedFile)) { + ProcessUncleanShutdown(); + } + + // On OS version change, clear version stats (which are reported daily). + int32_t version = GetOsVersionHash(); + if (version_cycle_->Get() != version) { + version_cycle_->Set(version); + kernel_crashes_version_count_->Set(0); + version_cumulative_active_use_->Set(0); + version_cumulative_cpu_use_->Set(0); + } + + // Start metricscollectorservice + android::sp<BnMetricsCollectorServiceImpl> metrics_collector_service = + new BnMetricsCollectorServiceImpl(this); + android::status_t status = android::defaultServiceManager()->addService( + metrics_collector_service->getInterfaceDescriptor(), + metrics_collector_service); + CHECK(status == android::OK) + << "failed to register service metricscollectorservice"; + + // Watch Binder events in the main loop + brillo::BinderWatcher binder_watcher; + CHECK(binder_watcher.Init()) << "Binder FD watcher init failed"; + return brillo::Daemon::Run(); +} + +uint32_t MetricsCollector::GetOsVersionHash() { + brillo::OsReleaseReader reader; + reader.Load(); + string version; + if (!reader.GetString(metrics::kProductVersion, &version)) { + LOG(ERROR) << "failed to read the product version."; + version = metrics::kDefaultVersion; + } + + uint32_t version_hash = base::Hash(version); + if (testing_) { + version_hash = 42; // return any plausible value for the hash + } + return version_hash; +} + +void MetricsCollector::Init(bool testing, MetricsLibraryInterface* metrics_lib, + const string& diskstats_path, + const base::FilePath& private_metrics_directory, + const base::FilePath& shared_metrics_directory) { + CHECK(metrics_lib); + testing_ = testing; + shared_metrics_directory_ = shared_metrics_directory; + metrics_lib_ = metrics_lib; + + daily_active_use_.reset(new PersistentInteger("Platform.UseTime.PerDay", + private_metrics_directory)); + version_cumulative_active_use_.reset(new PersistentInteger( + "Platform.CumulativeUseTime", private_metrics_directory)); + version_cumulative_cpu_use_.reset(new PersistentInteger( + "Platform.CumulativeCpuTime", private_metrics_directory)); + + kernel_crash_interval_.reset(new PersistentInteger( + "Platform.KernelCrashInterval", private_metrics_directory)); + unclean_shutdown_interval_.reset(new PersistentInteger( + "Platform.UncleanShutdownInterval", private_metrics_directory)); + user_crash_interval_.reset(new PersistentInteger("Platform.UserCrashInterval", + private_metrics_directory)); + + any_crashes_daily_count_.reset(new PersistentInteger( + "Platform.AnyCrashes.PerDay", private_metrics_directory)); + any_crashes_weekly_count_.reset(new PersistentInteger( + "Platform.AnyCrashes.PerWeek", private_metrics_directory)); + user_crashes_daily_count_.reset(new PersistentInteger( + "Platform.UserCrashes.PerDay", private_metrics_directory)); + user_crashes_weekly_count_.reset(new PersistentInteger( + "Platform.UserCrashes.PerWeek", private_metrics_directory)); + kernel_crashes_daily_count_.reset(new PersistentInteger( + "Platform.KernelCrashes.PerDay", private_metrics_directory)); + kernel_crashes_weekly_count_.reset(new PersistentInteger( + "Platform.KernelCrashes.PerWeek", private_metrics_directory)); + kernel_crashes_version_count_.reset(new PersistentInteger( + "Platform.KernelCrashesSinceUpdate", private_metrics_directory)); + unclean_shutdowns_daily_count_.reset(new PersistentInteger( + "Platform.UncleanShutdown.PerDay", private_metrics_directory)); + unclean_shutdowns_weekly_count_.reset(new PersistentInteger( + "Platform.UncleanShutdowns.PerWeek", private_metrics_directory)); + + daily_cycle_.reset( + new PersistentInteger("daily.cycle", private_metrics_directory)); + weekly_cycle_.reset( + new PersistentInteger("weekly.cycle", private_metrics_directory)); + version_cycle_.reset( + new PersistentInteger("version.cycle", private_metrics_directory)); + + disk_usage_collector_.reset(new DiskUsageCollector(metrics_lib_)); + averaged_stats_collector_.reset( + new AveragedStatisticsCollector(metrics_lib_, diskstats_path, + kVmStatFileName)); + cpu_usage_collector_.reset(new CpuUsageCollector(metrics_lib_)); +} + +int MetricsCollector::OnInit() { + int return_code = brillo::Daemon::OnInit(); + if (return_code != EX_OK) + return return_code; + + StatsReporterInit(); + + // Start collecting meminfo stats. + ScheduleMeminfoCallback(kMetricMeminfoInterval); + memuse_final_time_ = GetActiveTime() + kMemuseIntervals[0]; + ScheduleMemuseCallback(kMemuseIntervals[0]); + + if (testing_) + return EX_OK; + + weave_service_subscription_ = weaved::Service::Connect( + brillo::MessageLoop::current(), + base::Bind(&MetricsCollector::OnWeaveServiceConnected, + weak_ptr_factory_.GetWeakPtr())); + + latest_cpu_use_microseconds_ = cpu_usage_collector_->GetCumulativeCpuUse(); + base::MessageLoop::current()->PostDelayedTask(FROM_HERE, + base::Bind(&MetricsCollector::HandleUpdateStatsTimeout, + weak_ptr_factory_.GetWeakPtr()), + base::TimeDelta::FromMilliseconds(kUpdateStatsIntervalMs)); + + return EX_OK; +} + +void MetricsCollector::OnWeaveServiceConnected( + const std::weak_ptr<weaved::Service>& service) { + service_ = service; + auto weave_service = service_.lock(); + if (!weave_service) + return; + + weave_service->AddComponent(kWeaveComponent, {kWeaveTrait}, nullptr); + weave_service->AddCommandHandler( + kWeaveComponent, kWeaveTrait, "enableAnalyticsReporting", + base::Bind(&MetricsCollector::OnEnableMetrics, + weak_ptr_factory_.GetWeakPtr())); + weave_service->AddCommandHandler( + kWeaveComponent, kWeaveTrait, "disableAnalyticsReporting", + base::Bind(&MetricsCollector::OnDisableMetrics, + weak_ptr_factory_.GetWeakPtr())); + + UpdateWeaveState(); +} + +void MetricsCollector::OnEnableMetrics( + std::unique_ptr<weaved::Command> command) { + if (base::WriteFile( + shared_metrics_directory_.Append(metrics::kConsentFileName), "", 0) != + 0) { + PLOG(ERROR) << "Could not create the consent file."; + command->Abort("metrics_error", "Could not create the consent file", + nullptr); + return; + } + + UpdateWeaveState(); + command->Complete({}, nullptr); +} + +void MetricsCollector::OnDisableMetrics( + std::unique_ptr<weaved::Command> command) { + if (!base::DeleteFile( + shared_metrics_directory_.Append(metrics::kConsentFileName), false)) { + PLOG(ERROR) << "Could not delete the consent file."; + command->Abort("metrics_error", "Could not delete the consent file", + nullptr); + return; + } + + UpdateWeaveState(); + command->Complete({}, nullptr); +} + +void MetricsCollector::UpdateWeaveState() { + auto weave_service = service_.lock(); + if (!weave_service) + return; + + std::string enabled = + metrics_lib_->AreMetricsEnabled() ? "enabled" : "disabled"; + + if (!weave_service->SetStateProperty(kWeaveComponent, kWeaveTrait, + "analyticsReportingState", + *brillo::ToValue(enabled), + nullptr)) { + LOG(ERROR) << "failed to update weave's state"; + } +} + +void MetricsCollector::ProcessUserCrash() { + // Counts the active time up to now. + UpdateStats(TimeTicks::Now(), Time::Now()); + + // Reports the active use time since the last crash and resets it. + SendAndResetCrashIntervalSample(user_crash_interval_); + + any_crashes_daily_count_->Add(1); + any_crashes_weekly_count_->Add(1); + user_crashes_daily_count_->Add(1); + user_crashes_weekly_count_->Add(1); +} + +void MetricsCollector::ProcessKernelCrash() { + // Counts the active time up to now. + UpdateStats(TimeTicks::Now(), Time::Now()); + + // Reports the active use time since the last crash and resets it. + SendAndResetCrashIntervalSample(kernel_crash_interval_); + + any_crashes_daily_count_->Add(1); + any_crashes_weekly_count_->Add(1); + kernel_crashes_daily_count_->Add(1); + kernel_crashes_weekly_count_->Add(1); + + kernel_crashes_version_count_->Add(1); +} + +void MetricsCollector::ProcessUncleanShutdown() { + // Counts the active time up to now. + UpdateStats(TimeTicks::Now(), Time::Now()); + + // Reports the active use time since the last crash and resets it. + SendAndResetCrashIntervalSample(unclean_shutdown_interval_); + + unclean_shutdowns_daily_count_->Add(1); + unclean_shutdowns_weekly_count_->Add(1); + any_crashes_daily_count_->Add(1); + any_crashes_weekly_count_->Add(1); +} + +bool MetricsCollector::CheckSystemCrash(const string& crash_file) { + FilePath crash_detected(crash_file); + if (!base::PathExists(crash_detected)) + return false; + + // Deletes the crash-detected file so that the daemon doesn't report + // another kernel crash in case it's restarted. + base::DeleteFile(crash_detected, false); // not recursive + return true; +} + +void MetricsCollector::StatsReporterInit() { + disk_usage_collector_->Schedule(); + + cpu_usage_collector_->Init(); + cpu_usage_collector_->Schedule(); + + // Don't start a collection cycle during the first run to avoid delaying the + // boot. + averaged_stats_collector_->ScheduleWait(); +} + +void MetricsCollector::ScheduleMeminfoCallback(int wait) { + if (testing_) { + return; + } + base::TimeDelta waitDelta = base::TimeDelta::FromSeconds(wait); + base::MessageLoop::current()->PostDelayedTask(FROM_HERE, + base::Bind(&MetricsCollector::MeminfoCallback, + weak_ptr_factory_.GetWeakPtr(), waitDelta), + waitDelta); +} + +void MetricsCollector::MeminfoCallback(base::TimeDelta wait) { + string meminfo_raw; + const FilePath meminfo_path(kMeminfoFileName); + if (!base::ReadFileToString(meminfo_path, &meminfo_raw)) { + LOG(WARNING) << "cannot read " << meminfo_path.value().c_str(); + return; + } + // Make both calls even if the first one fails. + if (ProcessMeminfo(meminfo_raw)) { + base::MessageLoop::current()->PostDelayedTask(FROM_HERE, + base::Bind(&MetricsCollector::MeminfoCallback, + weak_ptr_factory_.GetWeakPtr(), wait), + wait); + } +} + +// static +bool MetricsCollector::ReadFileToUint64(const base::FilePath& path, + uint64_t* value) { + std::string content; + if (!base::ReadFileToString(path, &content)) { + PLOG(WARNING) << "cannot read " << path.MaybeAsASCII(); + return false; + } + // Remove final newline. + base::TrimWhitespaceASCII(content, base::TRIM_TRAILING, &content); + if (!base::StringToUint64(content, value)) { + LOG(WARNING) << "invalid integer: " << content; + return false; + } + return true; +} + +bool MetricsCollector::ReportZram(const base::FilePath& zram_dir) { + // Data sizes are in bytes. |zero_pages| is in number of pages. + uint64_t compr_data_size, orig_data_size, zero_pages; + const size_t page_size = 4096; + + if (!ReadFileToUint64(zram_dir.Append(kComprDataSizeName), + &compr_data_size) || + !ReadFileToUint64(zram_dir.Append(kOrigDataSizeName), &orig_data_size) || + !ReadFileToUint64(zram_dir.Append(kZeroPagesName), &zero_pages)) { + return false; + } + + // |orig_data_size| does not include zero-filled pages. + orig_data_size += zero_pages * page_size; + + const int compr_data_size_mb = compr_data_size >> 20; + const int savings_mb = (orig_data_size - compr_data_size) >> 20; + const int zero_ratio_percent = zero_pages * page_size * 100 / orig_data_size; + + // Report compressed size in megabytes. 100 MB or less has little impact. + SendSample("Platform.ZramCompressedSize", compr_data_size_mb, 100, 4000, 50); + SendSample("Platform.ZramSavings", savings_mb, 100, 4000, 50); + // The compression ratio is multiplied by 100 for better resolution. The + // ratios of interest are between 1 and 6 (100% and 600% as reported). We + // don't want samples when very little memory is being compressed. + if (compr_data_size_mb >= 1) { + SendSample("Platform.ZramCompressionRatioPercent", + orig_data_size * 100 / compr_data_size, 100, 600, 50); + } + // The values of interest for zero_pages are between 1MB and 1GB. The units + // are number of pages. + SendSample("Platform.ZramZeroPages", zero_pages, 256, 256 * 1024, 50); + SendSample("Platform.ZramZeroRatioPercent", zero_ratio_percent, 1, 50, 50); + + return true; +} + +bool MetricsCollector::ProcessMeminfo(const string& meminfo_raw) { + static const MeminfoRecord fields_array[] = { + { "MemTotal", "MemTotal" }, // SPECIAL CASE: total system memory + { "MemFree", "MemFree" }, + { "Buffers", "Buffers" }, + { "Cached", "Cached" }, + // { "SwapCached", "SwapCached" }, + { "Active", "Active" }, + { "Inactive", "Inactive" }, + { "ActiveAnon", "Active(anon)" }, + { "InactiveAnon", "Inactive(anon)" }, + { "ActiveFile" , "Active(file)" }, + { "InactiveFile", "Inactive(file)" }, + { "Unevictable", "Unevictable", kMeminfoOp_HistLog }, + // { "Mlocked", "Mlocked" }, + { "SwapTotal", "SwapTotal", kMeminfoOp_SwapTotal }, + { "SwapFree", "SwapFree", kMeminfoOp_SwapFree }, + // { "Dirty", "Dirty" }, + // { "Writeback", "Writeback" }, + { "AnonPages", "AnonPages" }, + { "Mapped", "Mapped" }, + { "Shmem", "Shmem", kMeminfoOp_HistLog }, + { "Slab", "Slab", kMeminfoOp_HistLog }, + // { "SReclaimable", "SReclaimable" }, + // { "SUnreclaim", "SUnreclaim" }, + }; + vector<MeminfoRecord> fields(fields_array, + fields_array + arraysize(fields_array)); + if (!FillMeminfo(meminfo_raw, &fields)) { + return false; + } + int total_memory = fields[0].value; + if (total_memory == 0) { + // this "cannot happen" + LOG(WARNING) << "borked meminfo parser"; + return false; + } + int swap_total = 0; + int swap_free = 0; + // Send all fields retrieved, except total memory. + for (unsigned int i = 1; i < fields.size(); i++) { + string metrics_name = base::StringPrintf("Platform.Meminfo%s", + fields[i].name); + int percent; + switch (fields[i].op) { + case kMeminfoOp_HistPercent: + // report value as percent of total memory + percent = fields[i].value * 100 / total_memory; + SendLinearSample(metrics_name, percent, 100, 101); + break; + case kMeminfoOp_HistLog: + // report value in kbytes, log scale, 4Gb max + SendSample(metrics_name, fields[i].value, 1, 4 * 1000 * 1000, 100); + break; + case kMeminfoOp_SwapTotal: + swap_total = fields[i].value; + case kMeminfoOp_SwapFree: + swap_free = fields[i].value; + break; + } + } + if (swap_total > 0) { + int swap_used = swap_total - swap_free; + int swap_used_percent = swap_used * 100 / swap_total; + SendSample("Platform.MeminfoSwapUsed", swap_used, 1, 8 * 1000 * 1000, 100); + SendLinearSample("Platform.MeminfoSwapUsed.Percent", swap_used_percent, + 100, 101); + } + return true; +} + +bool MetricsCollector::FillMeminfo(const string& meminfo_raw, + vector<MeminfoRecord>* fields) { + vector<std::string> lines = + base::SplitString(meminfo_raw, "\n", base::KEEP_WHITESPACE, + base::SPLIT_WANT_NONEMPTY); + + // Scan meminfo output and collect field values. Each field name has to + // match a meminfo entry (case insensitive) after removing non-alpha + // characters from the entry. + size_t ifield = 0; + for (size_t iline = 0; + iline < lines.size() && ifield < fields->size(); + iline++) { + vector<string> tokens = + base::SplitString(lines[iline], ": ", base::KEEP_WHITESPACE, + base::SPLIT_WANT_NONEMPTY); + if (strcmp((*fields)[ifield].match, tokens[0].c_str()) == 0) { + // Name matches. Parse value and save. + if (!base::StringToInt(tokens[1], &(*fields)[ifield].value)) { + LOG(WARNING) << "Cound not convert " << tokens[1] << " to int"; + return false; + } + ifield++; + } + } + if (ifield < fields->size()) { + // End of input reached while scanning. + LOG(WARNING) << "cannot find field " << (*fields)[ifield].match + << " and following"; + return false; + } + return true; +} + +void MetricsCollector::ScheduleMemuseCallback(double interval) { + if (testing_) { + return; + } + base::MessageLoop::current()->PostDelayedTask(FROM_HERE, + base::Bind(&MetricsCollector::MemuseCallback, + weak_ptr_factory_.GetWeakPtr()), + base::TimeDelta::FromSeconds(interval)); +} + +void MetricsCollector::MemuseCallback() { + // Since we only care about active time (i.e. uptime minus sleep time) but + // the callbacks are driven by real time (uptime), we check if we should + // reschedule this callback due to intervening sleep periods. + double now = GetActiveTime(); + // Avoid intervals of less than one second. + double remaining_time = ceil(memuse_final_time_ - now); + if (remaining_time > 0) { + ScheduleMemuseCallback(remaining_time); + } else { + // Report stats and advance the measurement interval unless there are + // errors or we've completed the last interval. + if (MemuseCallbackWork() && + memuse_interval_index_ < arraysize(kMemuseIntervals)) { + double interval = kMemuseIntervals[memuse_interval_index_++]; + memuse_final_time_ = now + interval; + ScheduleMemuseCallback(interval); + } + } +} + +bool MetricsCollector::MemuseCallbackWork() { + string meminfo_raw; + const FilePath meminfo_path(kMeminfoFileName); + if (!base::ReadFileToString(meminfo_path, &meminfo_raw)) { + LOG(WARNING) << "cannot read " << meminfo_path.value().c_str(); + return false; + } + return ProcessMemuse(meminfo_raw); +} + +bool MetricsCollector::ProcessMemuse(const string& meminfo_raw) { + static const MeminfoRecord fields_array[] = { + { "MemTotal", "MemTotal" }, // SPECIAL CASE: total system memory + { "ActiveAnon", "Active(anon)" }, + { "InactiveAnon", "Inactive(anon)" }, + }; + vector<MeminfoRecord> fields(fields_array, + fields_array + arraysize(fields_array)); + if (!FillMeminfo(meminfo_raw, &fields)) { + return false; + } + int total = fields[0].value; + int active_anon = fields[1].value; + int inactive_anon = fields[2].value; + if (total == 0) { + // this "cannot happen" + LOG(WARNING) << "borked meminfo parser"; + return false; + } + string metrics_name = base::StringPrintf("Platform.MemuseAnon%d", + memuse_interval_index_); + SendLinearSample(metrics_name, (active_anon + inactive_anon) * 100 / total, + 100, 101); + return true; +} + +void MetricsCollector::SendSample(const string& name, int sample, + int min, int max, int nbuckets) { + metrics_lib_->SendToUMA(name, sample, min, max, nbuckets); +} + +void MetricsCollector::SendKernelCrashesCumulativeCountStats() { + // Report the number of crashes for this OS version, but don't clear the + // counter. It is cleared elsewhere on version change. + int64_t crashes_count = kernel_crashes_version_count_->Get(); + SendSample(kernel_crashes_version_count_->Name(), + crashes_count, + 1, // value of first bucket + 500, // value of last bucket + 100); // number of buckets + + + int64_t cpu_use_ms = version_cumulative_cpu_use_->Get(); + SendSample(version_cumulative_cpu_use_->Name(), + cpu_use_ms / 1000, // stat is in seconds + 1, // device may be used very little... + 8 * 1000 * 1000, // ... or a lot (a little over 90 days) + 100); + + // On the first run after an autoupdate, cpu_use_ms and active_use_seconds + // can be zero. Avoid division by zero. + if (cpu_use_ms > 0) { + // Send the crash frequency since update in number of crashes per CPU year. + SendSample("Logging.KernelCrashesPerCpuYear", + crashes_count * kSecondsPerDay * 365 * 1000 / cpu_use_ms, + 1, + 1000 * 1000, // about one crash every 30s of CPU time + 100); + } + + int64_t active_use_seconds = version_cumulative_active_use_->Get(); + if (active_use_seconds > 0) { + SendSample(version_cumulative_active_use_->Name(), + active_use_seconds, + 1, // device may be used very little... + 8 * 1000 * 1000, // ... or a lot (about 90 days) + 100); + // Same as above, but per year of active time. + SendSample("Logging.KernelCrashesPerActiveYear", + crashes_count * kSecondsPerDay * 365 / active_use_seconds, + 1, + 1000 * 1000, // about one crash every 30s of active time + 100); + } +} + +void MetricsCollector::SendAndResetDailyUseSample( + const unique_ptr<PersistentInteger>& use) { + SendSample(use->Name(), + use->GetAndClear(), + 1, // value of first bucket + kSecondsPerDay, // value of last bucket + 50); // number of buckets +} + +void MetricsCollector::SendAndResetCrashIntervalSample( + const unique_ptr<PersistentInteger>& interval) { + SendSample(interval->Name(), + interval->GetAndClear(), + 1, // value of first bucket + 4 * kSecondsPerWeek, // value of last bucket + 50); // number of buckets +} + +void MetricsCollector::SendAndResetCrashFrequencySample( + const unique_ptr<PersistentInteger>& frequency) { + SendSample(frequency->Name(), + frequency->GetAndClear(), + 1, // value of first bucket + 100, // value of last bucket + 50); // number of buckets +} + +void MetricsCollector::SendLinearSample(const string& name, int sample, + int max, int nbuckets) { + // TODO(semenzato): add a proper linear histogram to the Chrome external + // metrics API. + LOG_IF(FATAL, nbuckets != max + 1) << "unsupported histogram scale"; + metrics_lib_->SendEnumToUMA(name, sample, max); +} + +void MetricsCollector::UpdateStats(TimeTicks now_ticks, + Time now_wall_time) { + const int elapsed_seconds = (now_ticks - last_update_stats_time_).InSeconds(); + daily_active_use_->Add(elapsed_seconds); + version_cumulative_active_use_->Add(elapsed_seconds); + user_crash_interval_->Add(elapsed_seconds); + kernel_crash_interval_->Add(elapsed_seconds); + TimeDelta cpu_use = cpu_usage_collector_->GetCumulativeCpuUse(); + version_cumulative_cpu_use_->Add( + (cpu_use - latest_cpu_use_microseconds_).InMilliseconds()); + latest_cpu_use_microseconds_ = cpu_use; + last_update_stats_time_ = now_ticks; + + const TimeDelta since_epoch = now_wall_time - Time::UnixEpoch(); + const int day = since_epoch.InDays(); + const int week = day / 7; + + if (daily_cycle_->Get() != day) { + daily_cycle_->Set(day); + SendAndResetDailyUseSample(daily_active_use_); + SendAndResetCrashFrequencySample(any_crashes_daily_count_); + SendAndResetCrashFrequencySample(user_crashes_daily_count_); + SendAndResetCrashFrequencySample(kernel_crashes_daily_count_); + SendAndResetCrashFrequencySample(unclean_shutdowns_daily_count_); + SendKernelCrashesCumulativeCountStats(); + } + + if (weekly_cycle_->Get() != week) { + weekly_cycle_->Set(week); + SendAndResetCrashFrequencySample(any_crashes_weekly_count_); + SendAndResetCrashFrequencySample(user_crashes_weekly_count_); + SendAndResetCrashFrequencySample(kernel_crashes_weekly_count_); + SendAndResetCrashFrequencySample(unclean_shutdowns_weekly_count_); + } +} + +void MetricsCollector::HandleUpdateStatsTimeout() { + UpdateStats(TimeTicks::Now(), Time::Now()); + base::MessageLoop::current()->PostDelayedTask(FROM_HERE, + base::Bind(&MetricsCollector::HandleUpdateStatsTimeout, + weak_ptr_factory_.GetWeakPtr()), + base::TimeDelta::FromMilliseconds(kUpdateStatsIntervalMs)); +} diff --git a/metrics_collector.h b/metrics_collector.h new file mode 100644 index 0000000..30659bd --- /dev/null +++ b/metrics_collector.h @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICS_METRICS_COLLECTOR_H_ +#define METRICS_METRICS_COLLECTOR_H_ + +#include <stdint.h> + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include <base/files/file_path.h> +#include <base/memory/weak_ptr.h> +#include <base/time/time.h> +#include <brillo/binder_watcher.h> +#include <brillo/daemons/daemon.h> +#include <libweaved/command.h> +#include <libweaved/service.h> +#include <gtest/gtest_prod.h> // for FRIEND_TEST + +#include "collectors/averaged_statistics_collector.h" +#include "collectors/cpu_usage_collector.h" +#include "collectors/disk_usage_collector.h" +#include "metrics/metrics_library.h" +#include "persistent_integer.h" + +using chromeos_metrics::PersistentInteger; +using std::unique_ptr; + +class MetricsCollector : public brillo::Daemon { + public: + MetricsCollector(); + ~MetricsCollector(); + + // Initializes metrics class variables. + void Init(bool testing, + MetricsLibraryInterface* metrics_lib, + const std::string& diskstats_path, + const base::FilePath& private_metrics_directory, + const base::FilePath& shared_metrics_directory); + + // Initializes the daemon. + int OnInit() override; + + // Does all the work. + int Run() override; + + // Returns the active time since boot (uptime minus sleep time) in seconds. + static double GetActiveTime(); + + // Updates the active use time and logs time between user-space + // process crashes. Called via MetricsCollectorServiceTrampoline. + void ProcessUserCrash(); + + protected: + // Used also by the unit tests. + static const char kComprDataSizeName[]; + static const char kOrigDataSizeName[]; + static const char kZeroPagesName[]; + + private: + friend class MetricsCollectorTest; + FRIEND_TEST(MetricsCollectorTest, CheckSystemCrash); + FRIEND_TEST(MetricsCollectorTest, ComputeEpochNoCurrent); + FRIEND_TEST(MetricsCollectorTest, ComputeEpochNoLast); + FRIEND_TEST(MetricsCollectorTest, GetHistogramPath); + FRIEND_TEST(MetricsCollectorTest, IsNewEpoch); + FRIEND_TEST(MetricsCollectorTest, MessageFilter); + FRIEND_TEST(MetricsCollectorTest, ProcessKernelCrash); + FRIEND_TEST(MetricsCollectorTest, ProcessMeminfo); + FRIEND_TEST(MetricsCollectorTest, ProcessMeminfo2); + FRIEND_TEST(MetricsCollectorTest, ProcessUncleanShutdown); + FRIEND_TEST(MetricsCollectorTest, ProcessUserCrash); + FRIEND_TEST(MetricsCollectorTest, ReportCrashesDailyFrequency); + FRIEND_TEST(MetricsCollectorTest, ReportKernelCrashInterval); + FRIEND_TEST(MetricsCollectorTest, ReportUncleanShutdownInterval); + FRIEND_TEST(MetricsCollectorTest, ReportUserCrashInterval); + FRIEND_TEST(MetricsCollectorTest, SendSample); + FRIEND_TEST(MetricsCollectorTest, SendZramMetrics); + + // Type of scale to use for meminfo histograms. For most of them we use + // percent of total RAM, but for some we use absolute numbers, usually in + // megabytes, on a log scale from 0 to 4000, and 0 to 8000 for compressed + // swap (since it can be larger than total RAM). + enum MeminfoOp { + kMeminfoOp_HistPercent = 0, + kMeminfoOp_HistLog, + kMeminfoOp_SwapTotal, + kMeminfoOp_SwapFree, + }; + + // Record for retrieving and reporting values from /proc/meminfo. + struct MeminfoRecord { + const char* name; // print name + const char* match; // string to match in output of /proc/meminfo + MeminfoOp op; // histogram scale selector, or other operator + int value; // value from /proc/meminfo + }; + + // Enables metrics reporting. + void OnEnableMetrics(std::unique_ptr<weaved::Command> command); + + // Disables metrics reporting. + void OnDisableMetrics(std::unique_ptr<weaved::Command> command); + + // Updates the weave device state. + void UpdateWeaveState(); + + // Updates the active use time and logs time between kernel crashes. + void ProcessKernelCrash(); + + // Updates the active use time and logs time between unclean shutdowns. + void ProcessUncleanShutdown(); + + // Checks if a kernel crash has been detected and returns true if + // so. The method assumes that a kernel crash has happened if + // |crash_file| exists. It removes the file immediately if it + // exists, so it must not be called more than once. + bool CheckSystemCrash(const std::string& crash_file); + + // Sends a regular (exponential) histogram sample to Chrome for + // transport to UMA. See MetricsLibrary::SendToUMA in + // metrics_library.h for a description of the arguments. + void SendSample(const std::string& name, int sample, + int min, int max, int nbuckets); + + // Sends a linear histogram sample to Chrome for transport to UMA. See + // MetricsLibrary::SendToUMA in metrics_library.h for a description of the + // arguments. + void SendLinearSample(const std::string& name, int sample, + int max, int nbuckets); + + // Sends various cumulative kernel crash-related stats, for instance the + // total number of kernel crashes since the last version update. + void SendKernelCrashesCumulativeCountStats(); + + // Sends a sample representing the number of seconds of active use + // for a 24-hour period and reset |use|. + void SendAndResetDailyUseSample(const unique_ptr<PersistentInteger>& use); + + // Sends a sample representing a time interval between two crashes of the + // same type and reset |interval|. + void SendAndResetCrashIntervalSample( + const unique_ptr<PersistentInteger>& interval); + + // Sends a sample representing a frequency of crashes of some type and reset + // |frequency|. + void SendAndResetCrashFrequencySample( + const unique_ptr<PersistentInteger>& frequency); + + // Initializes vm and disk stats reporting. + void StatsReporterInit(); + + // Schedules meminfo collection callback. + void ScheduleMeminfoCallback(int wait); + + // Reports memory statistics. Reschedules callback on success. + void MeminfoCallback(base::TimeDelta wait); + + // Parses content of /proc/meminfo and sends fields of interest to UMA. + // Returns false on errors. |meminfo_raw| contains the content of + // /proc/meminfo. + bool ProcessMeminfo(const std::string& meminfo_raw); + + // Parses meminfo data from |meminfo_raw|. |fields| is a vector containing + // the fields of interest. The order of the fields must be the same in which + // /proc/meminfo prints them. The result of parsing fields[i] is placed in + // fields[i].value. + bool FillMeminfo(const std::string& meminfo_raw, + std::vector<MeminfoRecord>* fields); + + // Schedule a memory use callback in |interval| seconds. + void ScheduleMemuseCallback(double interval); + + // Calls MemuseCallbackWork, and possibly schedules next callback, if enough + // active time has passed. Otherwise reschedules itself to simulate active + // time callbacks (i.e. wall clock time minus sleep time). + void MemuseCallback(); + + // Reads /proc/meminfo and sends total anonymous memory usage to UMA. + bool MemuseCallbackWork(); + + // Parses meminfo data and sends it to UMA. + bool ProcessMemuse(const std::string& meminfo_raw); + + // Reads the current OS version from /etc/lsb-release and hashes it + // to a unsigned 32-bit int. + uint32_t GetOsVersionHash(); + + // Updates stats, additionally sending them to UMA if enough time has elapsed + // since the last report. + void UpdateStats(base::TimeTicks now_ticks, base::Time now_wall_time); + + // Invoked periodically by |update_stats_timeout_id_| to call UpdateStats(). + void HandleUpdateStatsTimeout(); + + // Reports zram statistics. + bool ReportZram(const base::FilePath& zram_dir); + + // Reads a string from a file and converts it to uint64_t. + static bool ReadFileToUint64(const base::FilePath& path, uint64_t* value); + + // Callback invoked when a connection to weaved's service is established + // over Binder interface. + void OnWeaveServiceConnected(const std::weak_ptr<weaved::Service>& service); + + // VARIABLES + + // Test mode. + bool testing_; + + // Publicly readable metrics directory. + base::FilePath shared_metrics_directory_; + + // The metrics library handle. + MetricsLibraryInterface* metrics_lib_; + + // The last time that UpdateStats() was called. + base::TimeTicks last_update_stats_time_; + + // End time of current memuse stat collection interval. + double memuse_final_time_; + + // Selects the wait time for the next memory use callback. + unsigned int memuse_interval_index_; + + // Used internally by GetIncrementalCpuUse() to return the CPU utilization + // between calls. + base::TimeDelta latest_cpu_use_microseconds_; + + // Persistent values and accumulators for crash statistics. + unique_ptr<PersistentInteger> daily_cycle_; + unique_ptr<PersistentInteger> weekly_cycle_; + unique_ptr<PersistentInteger> version_cycle_; + + // Active use accumulated in a day. + unique_ptr<PersistentInteger> daily_active_use_; + // Active use accumulated since the latest version update. + unique_ptr<PersistentInteger> version_cumulative_active_use_; + + // The CPU time accumulator. This contains the CPU time, in milliseconds, + // used by the system since the most recent OS version update. + unique_ptr<PersistentInteger> version_cumulative_cpu_use_; + + unique_ptr<PersistentInteger> user_crash_interval_; + unique_ptr<PersistentInteger> kernel_crash_interval_; + unique_ptr<PersistentInteger> unclean_shutdown_interval_; + + unique_ptr<PersistentInteger> any_crashes_daily_count_; + unique_ptr<PersistentInteger> any_crashes_weekly_count_; + unique_ptr<PersistentInteger> user_crashes_daily_count_; + unique_ptr<PersistentInteger> user_crashes_weekly_count_; + unique_ptr<PersistentInteger> kernel_crashes_daily_count_; + unique_ptr<PersistentInteger> kernel_crashes_weekly_count_; + unique_ptr<PersistentInteger> kernel_crashes_version_count_; + unique_ptr<PersistentInteger> unclean_shutdowns_daily_count_; + unique_ptr<PersistentInteger> unclean_shutdowns_weekly_count_; + + unique_ptr<CpuUsageCollector> cpu_usage_collector_; + unique_ptr<DiskUsageCollector> disk_usage_collector_; + unique_ptr<AveragedStatisticsCollector> averaged_stats_collector_; + + unique_ptr<weaved::Service::Subscription> weave_service_subscription_; + std::weak_ptr<weaved::Service> service_; + + base::WeakPtrFactory<MetricsCollector> weak_ptr_factory_{this}; +}; + +#endif // METRICS_METRICS_COLLECTOR_H_ diff --git a/metrics_collector.rc b/metrics_collector.rc new file mode 100644 index 0000000..2d7667d --- /dev/null +++ b/metrics_collector.rc @@ -0,0 +1,4 @@ +service metricscollector /system/bin/metrics_collector --foreground --logtosyslog + class late_start + user metrics_coll + group metrics_coll diff --git a/metrics_collector_main.cc b/metrics_collector_main.cc new file mode 100644 index 0000000..14bb935 --- /dev/null +++ b/metrics_collector_main.cc @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <base/at_exit.h> +#include <base/command_line.h> +#include <base/logging.h> +#include <base/strings/string_util.h> +#include <brillo/flag_helper.h> +#include <brillo/syslog_logging.h> +#include <rootdev.h> + +#include "constants.h" +#include "metrics_collector.h" + + +// Returns the path to the disk stats in the sysfs. Returns the null string if +// it cannot find the disk stats file. +static +const std::string MetricsMainDiskStatsPath() { + char dev_path_cstr[PATH_MAX]; + std::string dev_prefix = "/dev/block/"; + std::string dev_path; + + int ret = rootdev(dev_path_cstr, sizeof(dev_path_cstr), true, true); + if (ret != 0) { + LOG(WARNING) << "error " << ret << " determining root device"; + return ""; + } + dev_path = dev_path_cstr; + // Check that rootdev begins with "/dev/block/". + if (!base::StartsWith(dev_path, dev_prefix, + base::CompareCase::INSENSITIVE_ASCII)) { + LOG(WARNING) << "unexpected root device " << dev_path; + return ""; + } + return "/sys/class/block/" + dev_path.substr(dev_prefix.length()) + "/stat"; +} + +int main(int argc, char** argv) { + DEFINE_bool(foreground, false, "Don't daemonize"); + + DEFINE_string(private_directory, metrics::kMetricsCollectorDirectory, + "Path to the private directory used by metrics_collector " + "(testing only)"); + DEFINE_string(shared_directory, metrics::kSharedMetricsDirectory, + "Path to the shared metrics directory, used by " + "metrics_collector, metricsd and all metrics clients " + "(testing only)"); + + DEFINE_bool(logtostderr, false, "Log to standard error"); + DEFINE_bool(logtosyslog, false, "Log to syslog"); + + brillo::FlagHelper::Init(argc, argv, "Chromium OS Metrics Daemon"); + + int logging_location = (FLAGS_foreground ? brillo::kLogToStderr + : brillo::kLogToSyslog); + if (FLAGS_logtosyslog) + logging_location = brillo::kLogToSyslog; + + if (FLAGS_logtostderr) + logging_location = brillo::kLogToStderr; + + // Also log to stderr when not running as daemon. + brillo::InitLog(logging_location | brillo::kLogHeader); + + if (FLAGS_logtostderr && FLAGS_logtosyslog) { + LOG(ERROR) << "only one of --logtosyslog and --logtostderr can be set"; + return 1; + } + + if (!FLAGS_foreground && daemon(0, 0) != 0) { + return errno; + } + + MetricsLibrary metrics_lib; + metrics_lib.InitWithNoCaching(); + MetricsCollector daemon; + daemon.Init(false, + &metrics_lib, + MetricsMainDiskStatsPath(), + base::FilePath(FLAGS_private_directory), + base::FilePath(FLAGS_shared_directory)); + + daemon.Run(); +} diff --git a/metrics_collector_service_client.cc b/metrics_collector_service_client.cc new file mode 100644 index 0000000..08aaa4a --- /dev/null +++ b/metrics_collector_service_client.cc @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Client interface to IMetricsCollectorService. + +#include "metrics/metrics_collector_service_client.h" + +#include <base/logging.h> +#include <binder/IServiceManager.h> +#include <utils/String16.h> + +#include "android/brillo/metrics/IMetricsCollectorService.h" + +namespace { +const char kMetricsCollectorServiceName[] = + "android.brillo.metrics.IMetricsCollectorService"; +} + +bool MetricsCollectorServiceClient::Init() { + const android::String16 name(kMetricsCollectorServiceName); + metrics_collector_service_ = android::interface_cast< + android::brillo::metrics::IMetricsCollectorService>( + android::defaultServiceManager()->checkService(name)); + + if (metrics_collector_service_ == nullptr) + LOG(ERROR) << "Unable to lookup service " << kMetricsCollectorServiceName; + + return metrics_collector_service_ != nullptr; +} + +bool MetricsCollectorServiceClient::notifyUserCrash() { + if (metrics_collector_service_ == nullptr) + return false; + + metrics_collector_service_->notifyUserCrash(); + return true; +} diff --git a/metrics_collector_service_impl.cc b/metrics_collector_service_impl.cc new file mode 100644 index 0000000..4d9a05a --- /dev/null +++ b/metrics_collector_service_impl.cc @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "metrics_collector_service_impl.h" + +#include <binder/IServiceManager.h> +#include <binder/Status.h> +#include <utils/Errors.h> + +#include "metrics_collector.h" + +using namespace android; + +BnMetricsCollectorServiceImpl::BnMetricsCollectorServiceImpl( + MetricsCollector* metrics_collector) + : metrics_collector_(metrics_collector) { +} + +android::binder::Status BnMetricsCollectorServiceImpl::notifyUserCrash() { + metrics_collector_->ProcessUserCrash(); + return android::binder::Status::ok(); +} diff --git a/metrics_collector_service_impl.h b/metrics_collector_service_impl.h new file mode 100644 index 0000000..8db418a --- /dev/null +++ b/metrics_collector_service_impl.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICSD_METRICS_COLLECTOR_SERVICE_IMPL_H_ +#define METRICSD_METRICS_COLLECTOR_SERVICE_IMPL_H_ + +// metrics_collector binder service implementation. Constructed by +// MetricsCollector. + +#include "android/brillo/metrics/BnMetricsCollectorService.h" + +#include <binder/Status.h> + +class MetricsCollector; + +class BnMetricsCollectorServiceImpl + : public android::brillo::metrics::BnMetricsCollectorService { + public: + // Passed a this pointer from the MetricsCollector object that constructs us. + explicit BnMetricsCollectorServiceImpl( + MetricsCollector* metrics_collector_service); + + virtual ~BnMetricsCollectorServiceImpl() = default; + + // Called by crash_reporter to report a userspace crash event. We relay + // this to MetricsCollector. + android::binder::Status notifyUserCrash(); + + private: + // MetricsCollector object that constructs us, we use this to call back + // to it. + MetricsCollector* metrics_collector_; +}; + +#endif // METRICSD_METRICS_COLLECTOR_SERVICE_IMPL_H_ diff --git a/metrics_collector_test.cc b/metrics_collector_test.cc new file mode 100644 index 0000000..8dda529 --- /dev/null +++ b/metrics_collector_test.cc @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <vector> + +#include <base/at_exit.h> +#include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.h> +#include <base/strings/string_number_conversions.h> +#include <brillo/flag_helper.h> +#include <gtest/gtest.h> + +#include "constants.h" +#include "metrics_collector.h" +#include "metrics/metrics_library_mock.h" +#include "persistent_integer_mock.h" + +using base::FilePath; +using base::TimeDelta; +using std::string; +using std::vector; +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::AtLeast; +using ::testing::Return; +using ::testing::StrictMock; +using chromeos_metrics::PersistentIntegerMock; + + +class MetricsCollectorTest : public testing::Test { + protected: + virtual void SetUp() { + brillo::FlagHelper::Init(0, nullptr, ""); + EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); + + base::FilePath private_dir = temp_dir_.path().Append("private"); + base::FilePath shared_dir = temp_dir_.path().Append("shared"); + + EXPECT_TRUE(base::CreateDirectory(private_dir)); + EXPECT_TRUE(base::CreateDirectory(shared_dir)); + + daemon_.Init(true, &metrics_lib_, "", private_dir, shared_dir); + } + + // Adds a metrics library mock expectation that the specified metric + // will be generated. + void ExpectSample(const std::string& name, int sample) { + EXPECT_CALL(metrics_lib_, SendToUMA(name, sample, _, _, _)) + .Times(1) + .WillOnce(Return(true)) + .RetiresOnSaturation(); + } + + // Creates or overwrites the file in |path| so that it contains the printable + // representation of |value|. + void CreateUint64ValueFile(const base::FilePath& path, uint64_t value) { + std::string value_string = base::Uint64ToString(value); + ASSERT_EQ(value_string.length(), + base::WriteFile(path, value_string.c_str(), + value_string.length())); + } + + // The MetricsCollector under test. + MetricsCollector daemon_; + + // Temporary directory used for tests. + base::ScopedTempDir temp_dir_; + + // Mocks. They are strict mock so that all unexpected + // calls are marked as failures. + StrictMock<MetricsLibraryMock> metrics_lib_; +}; + +TEST_F(MetricsCollectorTest, SendSample) { + ExpectSample("Dummy.Metric", 3); + daemon_.SendSample("Dummy.Metric", /* sample */ 3, + /* min */ 1, /* max */ 100, /* buckets */ 50); +} + +TEST_F(MetricsCollectorTest, ProcessMeminfo) { + string meminfo = + "MemTotal: 2000000 kB\nMemFree: 500000 kB\n" + "Buffers: 1000000 kB\nCached: 213652 kB\n" + "SwapCached: 0 kB\nActive: 133400 kB\n" + "Inactive: 183396 kB\nActive(anon): 92984 kB\n" + "Inactive(anon): 58860 kB\nActive(file): 40416 kB\n" + "Inactive(file): 124536 kB\nUnevictable: 0 kB\n" + "Mlocked: 0 kB\nSwapTotal: 0 kB\n" + "SwapFree: 0 kB\nDirty: 40 kB\n" + "Writeback: 0 kB\nAnonPages: 92652 kB\n" + "Mapped: 59716 kB\nShmem: 59196 kB\n" + "Slab: 16656 kB\nSReclaimable: 6132 kB\n" + "SUnreclaim: 10524 kB\nKernelStack: 1648 kB\n" + "PageTables: 2780 kB\nNFS_Unstable: 0 kB\n" + "Bounce: 0 kB\nWritebackTmp: 0 kB\n" + "CommitLimit: 970656 kB\nCommitted_AS: 1260528 kB\n" + "VmallocTotal: 122880 kB\nVmallocUsed: 12144 kB\n" + "VmallocChunk: 103824 kB\nDirectMap4k: 9636 kB\n" + "DirectMap2M: 1955840 kB\n"; + + // All enum calls must report percents. + EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, _, 100)).Times(AtLeast(1)); + // Check that MemFree is correctly computed at 25%. + EXPECT_CALL(metrics_lib_, SendEnumToUMA("Platform.MeminfoMemFree", 25, 100)) + .Times(AtLeast(1)); + // Check that we call SendToUma at least once (log histogram). + EXPECT_CALL(metrics_lib_, SendToUMA(_, _, _, _, _)) + .Times(AtLeast(1)); + // Make sure we don't report fields not in the list. + EXPECT_CALL(metrics_lib_, SendToUMA("Platform.MeminfoMlocked", _, _, _, _)) + .Times(0); + EXPECT_CALL(metrics_lib_, SendEnumToUMA("Platform.MeminfoMlocked", _, _)) + .Times(0); + EXPECT_TRUE(daemon_.ProcessMeminfo(meminfo)); +} + +TEST_F(MetricsCollectorTest, ProcessMeminfo2) { + string meminfo = "MemTotal: 2000000 kB\nMemFree: 1000000 kB\n"; + // Not enough fields. + EXPECT_FALSE(daemon_.ProcessMeminfo(meminfo)); +} + +TEST_F(MetricsCollectorTest, SendZramMetrics) { + EXPECT_TRUE(daemon_.testing_); + + // |compr_data_size| is the size in bytes of compressed data. + const uint64_t compr_data_size = 50 * 1000 * 1000; + // The constant '3' is a realistic but random choice. + // |orig_data_size| does not include zero pages. + const uint64_t orig_data_size = compr_data_size * 3; + const uint64_t page_size = 4096; + const uint64_t zero_pages = 10 * 1000 * 1000 / page_size; + + CreateUint64ValueFile( + temp_dir_.path().Append(MetricsCollector::kComprDataSizeName), + compr_data_size); + CreateUint64ValueFile( + temp_dir_.path().Append(MetricsCollector::kOrigDataSizeName), + orig_data_size); + CreateUint64ValueFile( + temp_dir_.path().Append(MetricsCollector::kZeroPagesName), zero_pages); + + const uint64_t real_orig_size = orig_data_size + zero_pages * page_size; + const uint64_t zero_ratio_percent = + zero_pages * page_size * 100 / real_orig_size; + // Ratio samples are in percents. + const uint64_t actual_ratio_sample = real_orig_size * 100 / compr_data_size; + + EXPECT_CALL(metrics_lib_, SendToUMA(_, compr_data_size >> 20, _, _, _)); + EXPECT_CALL(metrics_lib_, + SendToUMA(_, (real_orig_size - compr_data_size) >> 20, _, _, _)); + EXPECT_CALL(metrics_lib_, SendToUMA(_, actual_ratio_sample, _, _, _)); + EXPECT_CALL(metrics_lib_, SendToUMA(_, zero_pages, _, _, _)); + EXPECT_CALL(metrics_lib_, SendToUMA(_, zero_ratio_percent, _, _, _)); + + EXPECT_TRUE(daemon_.ReportZram(temp_dir_.path())); +} diff --git a/metrics_library.cc b/metrics_library.cc new file mode 100644 index 0000000..d211ab4 --- /dev/null +++ b/metrics_library.cc @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "metrics/metrics_library.h" + +#include <base/logging.h> +#include <base/strings/stringprintf.h> +#include <binder/IServiceManager.h> +#include <errno.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <utils/String16.h> + +#include <cstdio> +#include <cstring> + +#include "android/brillo/metrics/IMetricsd.h" +#include "constants.h" + +static const char kCrosEventHistogramName[] = "Platform.CrOSEvent"; +static const int kCrosEventHistogramMax = 100; +static const char kMetricsServiceName[] = "android.brillo.metrics.IMetricsd"; + +/* Add new cros events here. + * + * The index of the event is sent in the message, so please do not + * reorder the names. + */ +static const char *kCrosEventNames[] = { + "ModemManagerCommandSendFailure", // 0 + "HwWatchdogReboot", // 1 + "Cras.NoCodecsFoundAtBoot", // 2 + "Chaps.DatabaseCorrupted", // 3 + "Chaps.DatabaseRepairFailure", // 4 + "Chaps.DatabaseCreateFailure", // 5 + "Attestation.OriginSpecificExhausted", // 6 + "SpringPowerSupply.Original.High", // 7 + "SpringPowerSupply.Other.High", // 8 + "SpringPowerSupply.Original.Low", // 9 + "SpringPowerSupply.ChargerIdle", // 10 + "TPM.NonZeroDictionaryAttackCounter", // 11 + "TPM.EarlyResetDuringCommand", // 12 +}; + +using android::binder::Status; +using android::brillo::metrics::IMetricsd; +using android::String16; + +MetricsLibrary::MetricsLibrary() {} +MetricsLibrary::~MetricsLibrary() {} + +// We take buffer and buffer_size as parameters in order to simplify testing +// of various alignments of the |device_name| with |buffer_size|. +bool MetricsLibrary::IsDeviceMounted(const char* device_name, + const char* mounts_file, + char* buffer, + int buffer_size, + bool* result) { + if (buffer == nullptr || buffer_size < 1) + return false; + int mounts_fd = open(mounts_file, O_RDONLY); + if (mounts_fd < 0) + return false; + // match_offset describes: + // -1 -- not beginning of line + // 0..strlen(device_name)-1 -- this offset in device_name is next to match + // strlen(device_name) -- matched full name, just need a space. + int match_offset = 0; + bool match = false; + while (!match) { + int read_size = read(mounts_fd, buffer, buffer_size); + if (read_size <= 0) { + if (errno == -EINTR) + continue; + break; + } + for (int i = 0; i < read_size; ++i) { + if (buffer[i] == '\n') { + match_offset = 0; + continue; + } + if (match_offset < 0) { + continue; + } + if (device_name[match_offset] == '\0') { + if (buffer[i] == ' ') { + match = true; + break; + } + match_offset = -1; + continue; + } + + if (buffer[i] == device_name[match_offset]) { + ++match_offset; + } else { + match_offset = -1; + } + } + } + close(mounts_fd); + *result = match; + return true; +} + +bool MetricsLibrary::IsGuestMode() { + char buffer[256]; + bool result = false; + if (!IsDeviceMounted("guestfs", + "/proc/mounts", + buffer, + sizeof(buffer), + &result)) { + return false; + } + return result && (access("/var/run/state/logged-in", F_OK) == 0); +} + +bool MetricsLibrary::CheckService() { + if (metricsd_proxy_.get() && + android::IInterface::asBinder(metricsd_proxy_)->isBinderAlive()) + return true; + + const String16 name(kMetricsServiceName); + metricsd_proxy_ = android::interface_cast<IMetricsd>( + android::defaultServiceManager()->checkService(name)); + return metricsd_proxy_.get(); +} + +bool MetricsLibrary::AreMetricsEnabled() { + static struct stat stat_buffer; + time_t this_check_time = time(nullptr); + if (!use_caching_ || this_check_time != cached_enabled_time_) { + cached_enabled_time_ = this_check_time; + cached_enabled_ = stat(consent_file_.value().data(), &stat_buffer) >= 0; + } + return cached_enabled_; +} + +void MetricsLibrary::Init() { + base::FilePath dir = base::FilePath(metrics::kSharedMetricsDirectory); + consent_file_ = dir.Append(metrics::kConsentFileName); + cached_enabled_ = false; + cached_enabled_time_ = 0; + use_caching_ = true; +} + +void MetricsLibrary::InitWithNoCaching() { + Init(); + use_caching_ = false; +} + +void MetricsLibrary::InitForTest(const base::FilePath& metrics_directory) { + consent_file_ = metrics_directory.Append(metrics::kConsentFileName); + cached_enabled_ = false; + cached_enabled_time_ = 0; + use_caching_ = true; +} + +bool MetricsLibrary::SendToUMA( + const std::string& name, int sample, int min, int max, int nbuckets) { + return CheckService() && + metricsd_proxy_->recordHistogram(String16(name.c_str()), sample, min, + max, nbuckets) + .isOk(); +} + +bool MetricsLibrary::SendEnumToUMA(const std::string& name, + int sample, + int max) { + return CheckService() && + metricsd_proxy_->recordLinearHistogram(String16(name.c_str()), sample, + max) + .isOk(); +} + +bool MetricsLibrary::SendBoolToUMA(const std::string& name, bool sample) { + return CheckService() && + metricsd_proxy_->recordLinearHistogram(String16(name.c_str()), + sample ? 1 : 0, 2) + .isOk(); +} + +bool MetricsLibrary::SendSparseToUMA(const std::string& name, int sample) { + return CheckService() && + metricsd_proxy_->recordSparseHistogram(String16(name.c_str()), sample) + .isOk(); +} + +bool MetricsLibrary::SendCrashToUMA(const char* crash_kind) { + return CheckService() && + metricsd_proxy_->recordCrash(String16(crash_kind)).isOk(); +} + +bool MetricsLibrary::SendCrosEventToUMA(const std::string& event) { + for (size_t i = 0; i < arraysize(kCrosEventNames); i++) { + if (strcmp(event.c_str(), kCrosEventNames[i]) == 0) { + return SendEnumToUMA(kCrosEventHistogramName, i, kCrosEventHistogramMax); + } + } + return false; +} + +bool MetricsLibrary::GetHistogramsDump(std::string* dump) { + android::String16 temp_dump; + if (!CheckService() || + !metricsd_proxy_->getHistogramsDump(&temp_dump).isOk()) { + return false; + } + + *dump = android::String8(temp_dump).string(); + return true; +} diff --git a/metrics_library_test.cc b/metrics_library_test.cc new file mode 100644 index 0000000..52fcce3 --- /dev/null +++ b/metrics_library_test.cc @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "metrics/c_metrics_library.h" +#include "metrics/metrics_library.h" + + +class MetricsLibraryTest : public testing::Test { + protected: + virtual void SetUp() { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + lib_.InitForTest(temp_dir_.path()); + // Defeat metrics enabled caching between tests. + lib_.cached_enabled_time_ = 0; + } + + void SetMetricsConsent(bool enabled) { + if (enabled) { + ASSERT_EQ(base::WriteFile(lib_.consent_file_, "", 0), 0); + } else { + ASSERT_TRUE(base::DeleteFile(lib_.consent_file_, false)); + } + } + + void VerifyEnabledCacheHit(bool to_value); + void VerifyEnabledCacheEviction(bool to_value); + + MetricsLibrary lib_; + base::ScopedTempDir temp_dir_; +}; + +TEST_F(MetricsLibraryTest, AreMetricsEnabledFalse) { + SetMetricsConsent(false); + EXPECT_FALSE(lib_.AreMetricsEnabled()); +} + +TEST_F(MetricsLibraryTest, AreMetricsEnabledTrue) { + SetMetricsConsent(true); + EXPECT_TRUE(lib_.AreMetricsEnabled()); +} + +void MetricsLibraryTest::VerifyEnabledCacheHit(bool to_value) { + // We might step from one second to the next one time, but not 100 + // times in a row. + for (int i = 0; i < 100; ++i) { + lib_.cached_enabled_time_ = 0; + SetMetricsConsent(to_value); + lib_.AreMetricsEnabled(); + // If we check the metrics status twice in a row, we use the cached value + // the second time. + SetMetricsConsent(!to_value); + if (lib_.AreMetricsEnabled() == to_value) + return; + } + ADD_FAILURE() << "Did not see evidence of caching"; +} + +void MetricsLibraryTest::VerifyEnabledCacheEviction(bool to_value) { + lib_.cached_enabled_time_ = 0; + SetMetricsConsent(!to_value); + ASSERT_EQ(!to_value, lib_.AreMetricsEnabled()); + + SetMetricsConsent(to_value); + // Sleep one second (or cheat to be faster) and check that we are not using + // the cached value. + --lib_.cached_enabled_time_; + ASSERT_EQ(to_value, lib_.AreMetricsEnabled()); +} + +TEST_F(MetricsLibraryTest, AreMetricsEnabledCaching) { + VerifyEnabledCacheHit(false); + VerifyEnabledCacheHit(true); + VerifyEnabledCacheEviction(false); + VerifyEnabledCacheEviction(true); +} + +TEST_F(MetricsLibraryTest, AreMetricsEnabledNoCaching) { + // disable caching. + lib_.use_caching_ = false; + + // Checking the consent repeatedly should return the right result. + for (int i=0; i<100; ++i) { + SetMetricsConsent(true); + ASSERT_EQ(true, lib_.AreMetricsEnabled()); + SetMetricsConsent(false); + ASSERT_EQ(false, lib_.AreMetricsEnabled()); + } +} diff --git a/metricsd.rc b/metricsd.rc new file mode 100644 index 0000000..3d3e695 --- /dev/null +++ b/metricsd.rc @@ -0,0 +1,9 @@ +on post-fs-data + mkdir /data/misc/metrics 0750 metrics_coll system + mkdir /data/misc/metricsd 0700 metricsd metricsd + mkdir /data/misc/metrics_collector 0700 metrics_coll metrics_coll + +service metricsd /system/bin/metricsd --foreground --logtosyslog + class late_start + user metricsd + group system inet diff --git a/metricsd_main.cc b/metricsd_main.cc new file mode 100644 index 0000000..0178342 --- /dev/null +++ b/metricsd_main.cc @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <base/command_line.h> +#include <base/files/file_path.h> +#include <base/logging.h> +#include <base/time/time.h> +#include <brillo/flag_helper.h> +#include <brillo/syslog_logging.h> + +#include "constants.h" +#include "uploader/metricsd_service_runner.h" +#include "uploader/upload_service.h" + +int main(int argc, char** argv) { + DEFINE_bool(foreground, false, "Don't daemonize"); + + // Upload the metrics once and exit. (used for testing) + DEFINE_bool(uploader_test, false, "run the uploader once and exit"); + + // Upload Service flags. + DEFINE_int32(upload_interval_secs, 1800, + "Interval at which metricsd uploads the metrics."); + DEFINE_int32(disk_persistence_interval_secs, 300, + "Interval at which metricsd saves the aggregated metrics to " + "disk to avoid losing them if metricsd stops in between " + "two uploads."); + DEFINE_string(server, metrics::kMetricsServer, + "Server to upload the metrics to."); + DEFINE_string(private_directory, metrics::kMetricsdDirectory, + "Path to the private directory used by metricsd " + "(testing only)"); + DEFINE_string(shared_directory, metrics::kSharedMetricsDirectory, + "Path to the shared metrics directory, used by " + "metrics_collector, metricsd and all metrics clients " + "(testing only)"); + + DEFINE_bool(logtostderr, false, "Log to standard error"); + DEFINE_bool(logtosyslog, false, "Log to syslog"); + + brillo::FlagHelper::Init(argc, argv, "Brillo metrics daemon."); + + int logging_location = + (FLAGS_foreground ? brillo::kLogToStderr : brillo::kLogToSyslog); + if (FLAGS_logtosyslog) + logging_location = brillo::kLogToSyslog; + + if (FLAGS_logtostderr) + logging_location = brillo::kLogToStderr; + + // Also log to stderr when not running as daemon. + brillo::InitLog(logging_location | brillo::kLogHeader); + + if (FLAGS_logtostderr && FLAGS_logtosyslog) { + LOG(ERROR) << "only one of --logtosyslog and --logtostderr can be set"; + return 1; + } + + if (!FLAGS_foreground && daemon(0, 0) != 0) { + return errno; + } + + UploadService upload_service( + FLAGS_server, base::TimeDelta::FromSeconds(FLAGS_upload_interval_secs), + base::TimeDelta::FromSeconds(FLAGS_disk_persistence_interval_secs), + base::FilePath(FLAGS_private_directory), + base::FilePath(FLAGS_shared_directory)); + + return upload_service.Run(); +} diff --git a/persistent_integer.cc b/persistent_integer.cc new file mode 100644 index 0000000..7fe355e --- /dev/null +++ b/persistent_integer.cc @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "persistent_integer.h" + +#include <fcntl.h> + +#include <base/logging.h> +#include <base/posix/eintr_wrapper.h> + +#include "constants.h" + +namespace chromeos_metrics { + +PersistentInteger::PersistentInteger(const std::string& name, + const base::FilePath& directory) + : value_(0), + version_(kVersion), + name_(name), + backing_file_path_(directory.Append(name_)), + synced_(false) {} + +PersistentInteger::~PersistentInteger() {} + +void PersistentInteger::Set(int64_t value) { + value_ = value; + Write(); +} + +int64_t PersistentInteger::Get() { + // If not synced, then read. If the read fails, it's a good idea to write. + if (!synced_ && !Read()) + Write(); + return value_; +} + +int64_t PersistentInteger::GetAndClear() { + int64_t v = Get(); + Set(0); + return v; +} + +void PersistentInteger::Add(int64_t x) { + Set(Get() + x); +} + +void PersistentInteger::Write() { + int fd = HANDLE_EINTR(open(backing_file_path_.value().c_str(), + O_WRONLY | O_CREAT | O_TRUNC, + S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH)); + PCHECK(fd >= 0) << "cannot open " << backing_file_path_.value() + << " for writing"; + PCHECK((HANDLE_EINTR(write(fd, &version_, sizeof(version_))) == + sizeof(version_)) && + (HANDLE_EINTR(write(fd, &value_, sizeof(value_))) == + sizeof(value_))) + << "cannot write to " << backing_file_path_.value(); + close(fd); + synced_ = true; +} + +bool PersistentInteger::Read() { + int fd = HANDLE_EINTR(open(backing_file_path_.value().c_str(), O_RDONLY)); + if (fd < 0) { + PLOG(WARNING) << "cannot open " << backing_file_path_.value() + << " for reading"; + return false; + } + int32_t version; + int64_t value; + bool read_succeeded = false; + if (HANDLE_EINTR(read(fd, &version, sizeof(version))) == sizeof(version) && + version == version_ && + HANDLE_EINTR(read(fd, &value, sizeof(value))) == sizeof(value)) { + value_ = value; + read_succeeded = true; + synced_ = true; + } + close(fd); + return read_succeeded; +} + +} // namespace chromeos_metrics diff --git a/persistent_integer.h b/persistent_integer.h new file mode 100644 index 0000000..96d9fc0 --- /dev/null +++ b/persistent_integer.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICS_PERSISTENT_INTEGER_H_ +#define METRICS_PERSISTENT_INTEGER_H_ + +#include <stdint.h> + +#include <string> + +#include <base/files/file_path.h> + +namespace chromeos_metrics { + +// PersistentIntegers is a named 64-bit integer value backed by a file. +// The in-memory value acts as a write-through cache of the file value. +// If the backing file doesn't exist or has bad content, the value is 0. + +class PersistentInteger { + public: + PersistentInteger(const std::string& name, const base::FilePath& directory); + + // Virtual only because of mock. + virtual ~PersistentInteger(); + + // Sets the value. This writes through to the backing file. + void Set(int64_t v); + + // Gets the value. May sync from backing file first. + int64_t Get(); + + // Returns the name of the object. + std::string Name() { return name_; } + + // Convenience function for Get() followed by Set(0). + int64_t GetAndClear(); + + // Convenience function for v = Get, Set(v + x). + // Virtual only because of mock. + virtual void Add(int64_t x); + + private: + static const int kVersion = 1001; + + // Writes |value_| to the backing file, creating it if necessary. + void Write(); + + // Reads the value from the backing file, stores it in |value_|, and returns + // true if the backing file is valid. Returns false otherwise, and creates + // a valid backing file as a side effect. + bool Read(); + + int64_t value_; + int32_t version_; + std::string name_; + base::FilePath backing_file_path_; + bool synced_; +}; + +} // namespace chromeos_metrics + +#endif // METRICS_PERSISTENT_INTEGER_H_ diff --git a/persistent_integer_mock.h b/persistent_integer_mock.h new file mode 100644 index 0000000..0be54d4 --- /dev/null +++ b/persistent_integer_mock.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICS_PERSISTENT_INTEGER_MOCK_H_ +#define METRICS_PERSISTENT_INTEGER_MOCK_H_ + +#include <string> + +#include <gmock/gmock.h> + +#include "persistent_integer.h" + +namespace chromeos_metrics { + +class PersistentIntegerMock : public PersistentInteger { + public: + explicit PersistentIntegerMock(const std::string& name, + const base::FilePath& directory) + : PersistentInteger(name, directory) {} + MOCK_METHOD1(Add, void(int64_t count)); +}; + +} // namespace chromeos_metrics + +#endif // METRICS_PERSISTENT_INTEGER_MOCK_H_ diff --git a/persistent_integer_test.cc b/persistent_integer_test.cc new file mode 100644 index 0000000..bf76261 --- /dev/null +++ b/persistent_integer_test.cc @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <memory> + +#include <base/compiler_specific.h> +#include <base/files/file_enumerator.h> +#include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.h> +#include <gtest/gtest.h> + +#include "persistent_integer.h" + +const char kBackingFileName[] = "1.pibakf"; + +using chromeos_metrics::PersistentInteger; + +class PersistentIntegerTest : public testing::Test { + void SetUp() override { + // Set testing mode. + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + } + + protected: + base::ScopedTempDir temp_dir_; +}; + +TEST_F(PersistentIntegerTest, BasicChecks) { + std::unique_ptr<PersistentInteger> pi( + new PersistentInteger(kBackingFileName, temp_dir_.path())); + + // Test initialization. + EXPECT_EQ(0, pi->Get()); + EXPECT_EQ(kBackingFileName, pi->Name()); // boring + + // Test set and add. + pi->Set(2); + pi->Add(3); + EXPECT_EQ(5, pi->Get()); + + // Test persistence. + pi.reset(new PersistentInteger(kBackingFileName, temp_dir_.path())); + EXPECT_EQ(5, pi->Get()); + + // Test GetAndClear. + EXPECT_EQ(5, pi->GetAndClear()); + EXPECT_EQ(pi->Get(), 0); + + // Another persistence test. + pi.reset(new PersistentInteger(kBackingFileName, temp_dir_.path())); + EXPECT_EQ(0, pi->Get()); +} diff --git a/timer.cc b/timer.cc new file mode 100644 index 0000000..06fc336 --- /dev/null +++ b/timer.cc @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "metrics/timer.h" + +#include <string> + +#include "metrics/metrics_library.h" + +namespace chromeos_metrics { + +base::TimeTicks ClockWrapper::GetCurrentTime() const { + return base::TimeTicks::Now(); +} + +Timer::Timer() + : timer_state_(kTimerStopped), + clock_wrapper_(new ClockWrapper()) {} + +bool Timer::Start() { + elapsed_time_ = base::TimeDelta(); // Sets elapsed_time_ to zero. + start_time_ = clock_wrapper_->GetCurrentTime(); + timer_state_ = kTimerRunning; + return true; +} + +bool Timer::Stop() { + if (timer_state_ == kTimerStopped) + return false; + if (timer_state_ == kTimerRunning) + elapsed_time_ += clock_wrapper_->GetCurrentTime() - start_time_; + timer_state_ = kTimerStopped; + return true; +} + +bool Timer::Pause() { + switch (timer_state_) { + case kTimerStopped: + if (!Start()) + return false; + timer_state_ = kTimerPaused; + return true; + case kTimerRunning: + timer_state_ = kTimerPaused; + elapsed_time_ += clock_wrapper_->GetCurrentTime() - start_time_; + return true; + default: + return false; + } +} + +bool Timer::Resume() { + switch (timer_state_) { + case kTimerStopped: + return Start(); + case kTimerPaused: + start_time_ = clock_wrapper_->GetCurrentTime(); + timer_state_ = kTimerRunning; + return true; + default: + return false; + } +} + +bool Timer::Reset() { + elapsed_time_ = base::TimeDelta(); // Sets elapsed_time_ to zero. + timer_state_ = kTimerStopped; + return true; +} + +bool Timer::HasStarted() const { + return timer_state_ != kTimerStopped; +} + +bool Timer::GetElapsedTime(base::TimeDelta* elapsed_time) const { + if (start_time_.is_null() || !elapsed_time) + return false; + *elapsed_time = elapsed_time_; + if (timer_state_ == kTimerRunning) { + *elapsed_time += clock_wrapper_->GetCurrentTime() - start_time_; + } + return true; +} + +// static +MetricsLibraryInterface* TimerReporter::metrics_lib_ = nullptr; + +TimerReporter::TimerReporter(const std::string& histogram_name, int min, + int max, int num_buckets) + : histogram_name_(histogram_name), + min_(min), + max_(max), + num_buckets_(num_buckets) {} + +bool TimerReporter::ReportMilliseconds() const { + base::TimeDelta elapsed_time; + if (!metrics_lib_ || !GetElapsedTime(&elapsed_time)) return false; + return metrics_lib_->SendToUMA(histogram_name_, + elapsed_time.InMilliseconds(), + min_, + max_, + num_buckets_); +} + +} // namespace chromeos_metrics diff --git a/timer_test.cc b/timer_test.cc new file mode 100644 index 0000000..cfbcd8a --- /dev/null +++ b/timer_test.cc @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdint.h> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <memory> + +#include "metrics/metrics_library_mock.h" +#include "metrics/timer.h" +#include "metrics/timer_mock.h" + +using ::testing::_; +using ::testing::Return; + +namespace chromeos_metrics { + +namespace { +const int64_t kStime1MSec = 1400; +const int64_t kEtime1MSec = 3000; +const int64_t kDelta1MSec = 1600; + +const int64_t kStime2MSec = 4200; +const int64_t kEtime2MSec = 5000; +const int64_t kDelta2MSec = 800; + +const int64_t kStime3MSec = 6600; +const int64_t kEtime3MSec = 6800; +const int64_t kDelta3MSec = 200; +} // namespace + +class TimerTest : public testing::Test { + public: + TimerTest() : clock_wrapper_mock_(new ClockWrapperMock()) {} + + protected: + virtual void SetUp() { + EXPECT_EQ(Timer::kTimerStopped, timer_.timer_state_); + stime += base::TimeDelta::FromMilliseconds(kStime1MSec); + etime += base::TimeDelta::FromMilliseconds(kEtime1MSec); + stime2 += base::TimeDelta::FromMilliseconds(kStime2MSec); + etime2 += base::TimeDelta::FromMilliseconds(kEtime2MSec); + stime3 += base::TimeDelta::FromMilliseconds(kStime3MSec); + etime3 += base::TimeDelta::FromMilliseconds(kEtime3MSec); + } + + virtual void TearDown() {} + + Timer timer_; + std::unique_ptr<ClockWrapperMock> clock_wrapper_mock_; + base::TimeTicks stime, etime, stime2, etime2, stime3, etime3; +}; + +TEST_F(TimerTest, StartStop) { + EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime()) + .WillOnce(Return(stime)) + .WillOnce(Return(etime)); + timer_.clock_wrapper_ = std::move(clock_wrapper_mock_); + ASSERT_TRUE(timer_.Start()); + ASSERT_TRUE(timer_.start_time_ == stime); + ASSERT_TRUE(timer_.HasStarted()); + ASSERT_TRUE(timer_.Stop()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec); + + base::TimeDelta elapsed_time; + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + elapsed_time.InMilliseconds()); + + ASSERT_FALSE(timer_.HasStarted()); +} + +TEST_F(TimerTest, ReStart) { + EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime()) + .WillOnce(Return(stime)) + .WillOnce(Return(etime)); + timer_.clock_wrapper_ = std::move(clock_wrapper_mock_); + timer_.Start(); + base::TimeTicks buffer = timer_.start_time_; + timer_.Start(); + ASSERT_FALSE(timer_.start_time_ == buffer); +} + +TEST_F(TimerTest, Reset) { + EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime()) + .WillOnce(Return(stime)); + timer_.clock_wrapper_ = std::move(clock_wrapper_mock_); + timer_.Start(); + ASSERT_TRUE(timer_.Reset()); + ASSERT_FALSE(timer_.HasStarted()); +} + +TEST_F(TimerTest, SeparatedTimers) { + EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime()) + .WillOnce(Return(stime)) + .WillOnce(Return(etime)) + .WillOnce(Return(stime2)) + .WillOnce(Return(etime2)); + timer_.clock_wrapper_ = std::move(clock_wrapper_mock_); + ASSERT_TRUE(timer_.Start()); + ASSERT_TRUE(timer_.Stop()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec); + ASSERT_TRUE(timer_.Start()); + ASSERT_TRUE(timer_.start_time_ == stime2); + ASSERT_TRUE(timer_.Stop()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec); + ASSERT_FALSE(timer_.HasStarted()); + + base::TimeDelta elapsed_time; + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + elapsed_time.InMilliseconds()); +} + +TEST_F(TimerTest, InvalidStop) { + EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime()) + .WillOnce(Return(stime)) + .WillOnce(Return(etime)); + timer_.clock_wrapper_ = std::move(clock_wrapper_mock_); + ASSERT_FALSE(timer_.Stop()); + // Now we try it again, but after a valid start/stop. + timer_.Start(); + timer_.Stop(); + base::TimeDelta elapsed_time = timer_.elapsed_time_; + ASSERT_FALSE(timer_.Stop()); + ASSERT_TRUE(elapsed_time == timer_.elapsed_time_); +} + +TEST_F(TimerTest, InvalidElapsedTime) { + base::TimeDelta elapsed_time; + ASSERT_FALSE(timer_.GetElapsedTime(&elapsed_time)); +} + +TEST_F(TimerTest, PauseStartStopResume) { + EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime()) + .WillOnce(Return(stime)) + .WillOnce(Return(stime2)) + .WillOnce(Return(etime2)) + .WillOnce(Return(stime3)) + .WillOnce(Return(etime3)); + timer_.clock_wrapper_ = std::move(clock_wrapper_mock_); + ASSERT_TRUE(timer_.Pause()); // Starts timer paused. + ASSERT_TRUE(timer_.start_time_ == stime); + ASSERT_TRUE(timer_.HasStarted()); + + ASSERT_TRUE(timer_.Start()); // Restarts timer. + ASSERT_TRUE(timer_.start_time_ == stime2); + ASSERT_TRUE(timer_.HasStarted()); + + ASSERT_TRUE(timer_.Stop()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec); + ASSERT_FALSE(timer_.HasStarted()); + base::TimeDelta elapsed_time; + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + elapsed_time.InMilliseconds()); + + ASSERT_TRUE(timer_.Resume()); + ASSERT_TRUE(timer_.HasStarted()); + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(kDelta3MSec, elapsed_time.InMilliseconds()); +} + +TEST_F(TimerTest, ResumeStartStopPause) { + EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime()) + .WillOnce(Return(stime)) + .WillOnce(Return(stime2)) + .WillOnce(Return(etime2)) + .WillOnce(Return(stime3)); + timer_.clock_wrapper_ = std::move(clock_wrapper_mock_); + ASSERT_TRUE(timer_.Resume()); + ASSERT_TRUE(timer_.start_time_ == stime); + ASSERT_TRUE(timer_.HasStarted()); + + ASSERT_TRUE(timer_.Start()); + ASSERT_TRUE(timer_.start_time_ == stime2); + ASSERT_TRUE(timer_.HasStarted()); + + ASSERT_TRUE(timer_.Stop()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec); + ASSERT_FALSE(timer_.HasStarted()); + base::TimeDelta elapsed_time; + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + elapsed_time.InMilliseconds()); + + ASSERT_TRUE(timer_.Pause()); + ASSERT_TRUE(timer_.HasStarted()); + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(0, elapsed_time.InMilliseconds()); +} + +TEST_F(TimerTest, StartResumeStop) { + EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime()) + .WillOnce(Return(stime)) + .WillOnce(Return(etime)); + timer_.clock_wrapper_ = std::move(clock_wrapper_mock_); + ASSERT_TRUE(timer_.Start()); + ASSERT_TRUE(timer_.start_time_ == stime); + ASSERT_TRUE(timer_.HasStarted()); + + ASSERT_FALSE(timer_.Resume()); + ASSERT_TRUE(timer_.start_time_ == stime); + ASSERT_TRUE(timer_.HasStarted()); + + ASSERT_TRUE(timer_.Stop()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec); + ASSERT_FALSE(timer_.HasStarted()); + base::TimeDelta elapsed_time; + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + elapsed_time.InMilliseconds()); +} + +TEST_F(TimerTest, StartPauseStop) { + EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime()) + .WillOnce(Return(stime)) + .WillOnce(Return(etime)); + timer_.clock_wrapper_ = std::move(clock_wrapper_mock_); + ASSERT_TRUE(timer_.Start()); + ASSERT_TRUE(timer_.start_time_ == stime); + ASSERT_TRUE(timer_.HasStarted()); + + ASSERT_TRUE(timer_.Pause()); + ASSERT_TRUE(timer_.HasStarted()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec); + base::TimeDelta elapsed_time; + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + elapsed_time.InMilliseconds()); + + ASSERT_TRUE(timer_.Stop()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec); + ASSERT_FALSE(timer_.HasStarted()); + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + elapsed_time.InMilliseconds()); +} + +TEST_F(TimerTest, StartPauseResumeStop) { + EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime()) + .WillOnce(Return(stime)) + .WillOnce(Return(etime)) + .WillOnce(Return(stime2)) + .WillOnce(Return(etime2)); + timer_.clock_wrapper_ = std::move(clock_wrapper_mock_); + ASSERT_TRUE(timer_.Start()); + ASSERT_TRUE(timer_.start_time_ == stime); + ASSERT_TRUE(timer_.HasStarted()); + + ASSERT_TRUE(timer_.Pause()); + ASSERT_TRUE(timer_.HasStarted()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec); + base::TimeDelta elapsed_time; + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + elapsed_time.InMilliseconds()); + + ASSERT_TRUE(timer_.Resume()); + ASSERT_TRUE(timer_.HasStarted()); + + ASSERT_TRUE(timer_.Stop()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec + kDelta2MSec); + ASSERT_FALSE(timer_.HasStarted()); + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + elapsed_time.InMilliseconds()); +} + +TEST_F(TimerTest, PauseStop) { + EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime()) + .WillOnce(Return(stime)); + timer_.clock_wrapper_ = std::move(clock_wrapper_mock_); + ASSERT_TRUE(timer_.Pause()); + ASSERT_TRUE(timer_.start_time_ == stime); + ASSERT_TRUE(timer_.HasStarted()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), 0); + + ASSERT_TRUE(timer_.Stop()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), 0); + ASSERT_FALSE(timer_.HasStarted()); + base::TimeDelta elapsed_time; + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + elapsed_time.InMilliseconds()); +} + +TEST_F(TimerTest, PauseResumeStop) { + EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime()) + .WillOnce(Return(stime)) + .WillOnce(Return(stime2)) + .WillOnce(Return(etime2)); + timer_.clock_wrapper_ = std::move(clock_wrapper_mock_); + ASSERT_TRUE(timer_.Pause()); + ASSERT_TRUE(timer_.start_time_ == stime); + ASSERT_TRUE(timer_.HasStarted()); + + ASSERT_TRUE(timer_.Resume()); + ASSERT_TRUE(timer_.HasStarted()); + + ASSERT_TRUE(timer_.Stop()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec); + ASSERT_FALSE(timer_.HasStarted()); + base::TimeDelta elapsed_time; + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + elapsed_time.InMilliseconds()); +} + +TEST_F(TimerTest, StartPauseResumePauseStop) { + EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime()) + .WillOnce(Return(stime)) + .WillOnce(Return(etime)) + .WillOnce(Return(stime2)) + .WillOnce(Return(stime3)) + .WillOnce(Return(etime3)); + timer_.clock_wrapper_ = std::move(clock_wrapper_mock_); + ASSERT_TRUE(timer_.Start()); + ASSERT_TRUE(timer_.start_time_ == stime); + ASSERT_TRUE(timer_.HasStarted()); + + ASSERT_TRUE(timer_.Pause()); + ASSERT_TRUE(timer_.HasStarted()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec); + base::TimeDelta elapsed_time; + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + elapsed_time.InMilliseconds()); + + ASSERT_TRUE(timer_.Resume()); + ASSERT_TRUE(timer_.HasStarted()); + // Make sure GetElapsedTime works while we're running. + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(kDelta1MSec + kStime3MSec - kStime2MSec, + elapsed_time.InMilliseconds()); + + ASSERT_TRUE(timer_.Pause()); + ASSERT_TRUE(timer_.HasStarted()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + kDelta1MSec + kEtime3MSec - kStime2MSec); + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + elapsed_time.InMilliseconds()); + + ASSERT_TRUE(timer_.Stop()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + kDelta1MSec + kEtime3MSec - kStime2MSec); + ASSERT_FALSE(timer_.HasStarted()); + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + elapsed_time.InMilliseconds()); +} + +TEST_F(TimerTest, StartPauseResumePauseResumeStop) { + EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime()) + .WillOnce(Return(stime)) + .WillOnce(Return(etime)) + .WillOnce(Return(stime2)) + .WillOnce(Return(etime2)) + .WillOnce(Return(stime3)) + .WillOnce(Return(etime3)); + timer_.clock_wrapper_ = std::move(clock_wrapper_mock_); + ASSERT_TRUE(timer_.Start()); + ASSERT_TRUE(timer_.start_time_ == stime); + ASSERT_TRUE(timer_.HasStarted()); + + ASSERT_TRUE(timer_.Pause()); + ASSERT_TRUE(timer_.HasStarted()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec); + base::TimeDelta elapsed_time; + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + elapsed_time.InMilliseconds()); + + ASSERT_TRUE(timer_.Resume()); + ASSERT_TRUE(timer_.HasStarted()); + + ASSERT_TRUE(timer_.Pause()); + ASSERT_TRUE(timer_.HasStarted()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec + kDelta2MSec); + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + elapsed_time.InMilliseconds()); + + ASSERT_TRUE(timer_.Resume()); + ASSERT_TRUE(timer_.HasStarted()); + + ASSERT_TRUE(timer_.Stop()); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + kDelta1MSec + kDelta2MSec + kDelta3MSec); + ASSERT_FALSE(timer_.HasStarted()); + ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time)); + ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), + elapsed_time.InMilliseconds()); +} + +static const char kMetricName[] = "test-timer"; +static const int kMinSample = 0; +static const int kMaxSample = 120 * 1E6; +static const int kNumBuckets = 50; + +class TimerReporterTest : public testing::Test { + public: + TimerReporterTest() : timer_reporter_(kMetricName, kMinSample, kMaxSample, + kNumBuckets), + clock_wrapper_mock_(new ClockWrapperMock()) {} + + protected: + virtual void SetUp() { + timer_reporter_.set_metrics_lib(&lib_); + EXPECT_EQ(timer_reporter_.histogram_name_, kMetricName); + EXPECT_EQ(timer_reporter_.min_, kMinSample); + EXPECT_EQ(timer_reporter_.max_, kMaxSample); + EXPECT_EQ(timer_reporter_.num_buckets_, kNumBuckets); + stime += base::TimeDelta::FromMilliseconds(kStime1MSec); + etime += base::TimeDelta::FromMilliseconds(kEtime1MSec); + } + + virtual void TearDown() { + timer_reporter_.set_metrics_lib(nullptr); + } + + TimerReporter timer_reporter_; + MetricsLibraryMock lib_; + std::unique_ptr<ClockWrapperMock> clock_wrapper_mock_; + base::TimeTicks stime, etime; +}; + +TEST_F(TimerReporterTest, StartStopReport) { + EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime()) + .WillOnce(Return(stime)) + .WillOnce(Return(etime)); + timer_reporter_.clock_wrapper_ = std::move(clock_wrapper_mock_); + EXPECT_CALL(lib_, SendToUMA(kMetricName, kDelta1MSec, kMinSample, kMaxSample, + kNumBuckets)).WillOnce(Return(true)); + ASSERT_TRUE(timer_reporter_.Start()); + ASSERT_TRUE(timer_reporter_.Stop()); + ASSERT_TRUE(timer_reporter_.ReportMilliseconds()); +} + +TEST_F(TimerReporterTest, InvalidReport) { + ASSERT_FALSE(timer_reporter_.ReportMilliseconds()); +} + +} // namespace chromeos_metrics + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/uploader/bn_metricsd_impl.cc b/uploader/bn_metricsd_impl.cc new file mode 100644 index 0000000..219ed60 --- /dev/null +++ b/uploader/bn_metricsd_impl.cc @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uploader/bn_metricsd_impl.h" + +#include <base/metrics/histogram.h> +#include <base/metrics/sparse_histogram.h> +#include <base/metrics/statistics_recorder.h> +#include <utils/Errors.h> +#include <utils/String16.h> +#include <utils/String8.h> + +using android::binder::Status; +using android::String16; + +static const char16_t kCrashTypeKernel[] = u"kernel"; +static const char16_t kCrashTypeUncleanShutdown[] = u"uncleanshutdown"; +static const char16_t kCrashTypeUser[] = u"user"; + +BnMetricsdImpl::BnMetricsdImpl(const std::shared_ptr<CrashCounters>& counters) + : counters_(counters) { + CHECK(counters_) << "Invalid counters argument to constructor"; +} + +Status BnMetricsdImpl::recordHistogram( + const String16& name, int sample, int min, int max, int nbuckets) { + base::HistogramBase* histogram = base::Histogram::FactoryGet( + android::String8(name).string(), min, max, nbuckets, + base::Histogram::kUmaTargetedHistogramFlag); + // |histogram| may be null if a client reports two contradicting histograms + // with the same name but different limits. + // FactoryGet will print a useful message if that is the case. + if (histogram) { + histogram->Add(sample); + } + return Status::ok(); +} + +Status BnMetricsdImpl::recordLinearHistogram(const String16& name, + int sample, + int max) { + base::HistogramBase* histogram = base::LinearHistogram::FactoryGet( + android::String8(name).string(), 1, max, max + 1, + base::Histogram::kUmaTargetedHistogramFlag); + // |histogram| may be null if a client reports two contradicting histograms + // with the same name but different limits. + // FactoryGet will print a useful message if that is the case. + if (histogram) { + histogram->Add(sample); + } + return Status::ok(); +} + +Status BnMetricsdImpl::recordSparseHistogram(const String16& name, int sample) { + base::HistogramBase* histogram = base::SparseHistogram::FactoryGet( + android::String8(name).string(), + base::Histogram::kUmaTargetedHistogramFlag); + // |histogram| may be null if a client reports two contradicting histograms + // with the same name but different limits. + // FactoryGet will print a useful message if that is the case. + if (histogram) { + histogram->Add(sample); + } + return Status::ok(); +} + +Status BnMetricsdImpl::recordCrash(const String16& type) { + if (type == kCrashTypeUser) { + counters_->IncrementUserCrashCount(); + } else if (type == kCrashTypeKernel) { + counters_->IncrementKernelCrashCount(); + } else if (type == kCrashTypeUncleanShutdown) { + counters_->IncrementUncleanShutdownCount(); + } else { + LOG(ERROR) << "Unknown crash type received: " << type; + } + return Status::ok(); +} + +Status BnMetricsdImpl::getHistogramsDump(String16* dump) { + std::string str_dump; + base::StatisticsRecorder::WriteGraph(std::string(), &str_dump); + *dump = String16(str_dump.c_str()); + return Status::ok(); +} diff --git a/uploader/bn_metricsd_impl.h b/uploader/bn_metricsd_impl.h new file mode 100644 index 0000000..bf47e80 --- /dev/null +++ b/uploader/bn_metricsd_impl.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICSD_UPLOADER_BN_METRICSD_IMPL_H_ +#define METRICSD_UPLOADER_BN_METRICSD_IMPL_H_ + +#include "android/brillo/metrics/BnMetricsd.h" +#include "uploader/crash_counters.h" + +class BnMetricsdImpl : public android::brillo::metrics::BnMetricsd { + public: + explicit BnMetricsdImpl(const std::shared_ptr<CrashCounters>& counters); + virtual ~BnMetricsdImpl() = default; + + // Records a histogram. + android::binder::Status recordHistogram(const android::String16& name, + int sample, + int min, + int max, + int nbuckets) override; + + // Records a linear histogram. + android::binder::Status recordLinearHistogram(const android::String16& name, + int sample, + int max) override; + + // Records a sparse histogram. + android::binder::Status recordSparseHistogram(const android::String16& name, + int sample) override; + + // Records a crash. + android::binder::Status recordCrash(const android::String16& type) override; + + // Returns a dump of the histograms aggregated in memory. + android::binder::Status getHistogramsDump(android::String16* dump) override; + + private: + std::shared_ptr<CrashCounters> counters_; +}; + +#endif // METRICSD_UPLOADER_BN_METRICSD_IMPL_H_ diff --git a/uploader/crash_counters.cc b/uploader/crash_counters.cc new file mode 100644 index 0000000..1478b9a --- /dev/null +++ b/uploader/crash_counters.cc @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uploader/crash_counters.h" + +CrashCounters::CrashCounters() + : kernel_crashes_(0), unclean_shutdowns_(0), user_crashes_(0) {} + +void CrashCounters::IncrementKernelCrashCount() { + kernel_crashes_++; +} + +unsigned int CrashCounters::GetAndResetKernelCrashCount() { + return kernel_crashes_.exchange(0); +} + +void CrashCounters::IncrementUncleanShutdownCount() { + unclean_shutdowns_++; +} + +unsigned int CrashCounters::GetAndResetUncleanShutdownCount() { + return unclean_shutdowns_.exchange(0); +} + +void CrashCounters::IncrementUserCrashCount() { + user_crashes_++; +} + +unsigned int CrashCounters::GetAndResetUserCrashCount() { + return user_crashes_.exchange(0); +} diff --git a/uploader/crash_counters.h b/uploader/crash_counters.h new file mode 100644 index 0000000..3fdbf3f --- /dev/null +++ b/uploader/crash_counters.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICSD_UPLOADER_CRASH_COUNTERS_H_ +#define METRICSD_UPLOADER_CRASH_COUNTERS_H_ + +#include <atomic> + +// This class is used to keep track of the crash counters. +// An instance of it will be used by both the binder thread (to increment the +// counters) and the uploader thread (to gather and reset the counters). +// As such, the internal counters are atomic uints to allow concurrent access. +class CrashCounters { + public: + CrashCounters(); + + void IncrementKernelCrashCount(); + unsigned int GetAndResetKernelCrashCount(); + + void IncrementUserCrashCount(); + unsigned int GetAndResetUserCrashCount(); + + void IncrementUncleanShutdownCount(); + unsigned int GetAndResetUncleanShutdownCount(); + + private: + std::atomic_uint kernel_crashes_; + std::atomic_uint unclean_shutdowns_; + std::atomic_uint user_crashes_; +}; + +#endif // METRICSD_UPLOADER_CRASH_COUNTERS_H_ diff --git a/uploader/metrics_hashes.cc b/uploader/metrics_hashes.cc new file mode 100644 index 0000000..208c560 --- /dev/null +++ b/uploader/metrics_hashes.cc @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uploader/metrics_hashes.h" + +#include "base/logging.h" +#include "base/md5.h" +#include "base/sys_byteorder.h" + +namespace metrics { + +namespace { + +// Converts the 8-byte prefix of an MD5 hash into a uint64 value. +inline uint64_t HashToUInt64(const std::string& hash) { + uint64_t value; + DCHECK_GE(hash.size(), sizeof(value)); + memcpy(&value, hash.data(), sizeof(value)); + return base::HostToNet64(value); +} + +} // namespace + +uint64_t HashMetricName(const std::string& name) { + // Create an MD5 hash of the given |name|, represented as a byte buffer + // encoded as an std::string. + base::MD5Context context; + base::MD5Init(&context); + base::MD5Update(&context, name); + + base::MD5Digest digest; + base::MD5Final(&digest, &context); + + std::string hash_str(reinterpret_cast<char*>(digest.a), arraysize(digest.a)); + return HashToUInt64(hash_str); +} + +} // namespace metrics diff --git a/uploader/metrics_hashes.h b/uploader/metrics_hashes.h new file mode 100644 index 0000000..1082b42 --- /dev/null +++ b/uploader/metrics_hashes.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICS_UPLOADER_METRICS_HASHES_H_ +#define METRICS_UPLOADER_METRICS_HASHES_H_ + +#include <string> + +namespace metrics { + +// Computes a uint64 hash of a given string based on its MD5 hash. Suitable for +// metric names. +uint64_t HashMetricName(const std::string& name); + +} // namespace metrics + +#endif // METRICS_UPLOADER_METRICS_HASHES_H_ diff --git a/uploader/metrics_hashes_unittest.cc b/uploader/metrics_hashes_unittest.cc new file mode 100644 index 0000000..b8c2575 --- /dev/null +++ b/uploader/metrics_hashes_unittest.cc @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uploader/metrics_hashes.h" + +#include <base/format_macros.h> +#include <base/macros.h> +#include <base/strings/stringprintf.h> +#include <gtest/gtest.h> + +namespace metrics { + +// Make sure our ID hashes are the same as what we see on the server side. +TEST(MetricsUtilTest, HashMetricName) { + static const struct { + std::string input; + std::string output; + } cases[] = { + {"Back", "0x0557fa923dcee4d0"}, + {"Forward", "0x67d2f6740a8eaebf"}, + {"NewTab", "0x290eb683f96572f1"}, + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { + uint64_t hash = HashMetricName(cases[i].input); + std::string hash_hex = base::StringPrintf("0x%016" PRIx64, hash); + EXPECT_EQ(cases[i].output, hash_hex); + } +} + +} // namespace metrics diff --git a/uploader/metrics_log.cc b/uploader/metrics_log.cc new file mode 100644 index 0000000..fcaa8c1 --- /dev/null +++ b/uploader/metrics_log.cc @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uploader/metrics_log.h" + +#include <string> + +#include <base/files/file_util.h> + +#include "uploader/proto/system_profile.pb.h" +#include "uploader/system_profile_setter.h" + +// We use default values for the MetricsLogBase constructor as the setter will +// override them. +MetricsLog::MetricsLog() + : MetricsLogBase("", 0, metrics::MetricsLogBase::ONGOING_LOG, "") { +} + +bool MetricsLog::LoadFromFile(const base::FilePath& saved_log) { + std::string encoded_log; + if (!base::ReadFileToString(saved_log, &encoded_log)) { + LOG(ERROR) << "Failed to read the metrics log backup from " + << saved_log.value(); + return false; + } + + if (!uma_proto()->ParseFromString(encoded_log)) { + LOG(ERROR) << "Failed to parse log from " << saved_log.value() + << ", deleting the log"; + base::DeleteFile(saved_log, false); + uma_proto()->Clear(); + return false; + } + + VLOG(1) << uma_proto()->histogram_event_size() << " histograms loaded from " + << saved_log.value(); + + return true; +} + +bool MetricsLog::SaveToFile(const base::FilePath& path) { + std::string encoded_log; + GetEncodedLog(&encoded_log); + + if (static_cast<int>(encoded_log.size()) != + base::WriteFile(path, encoded_log.data(), encoded_log.size())) { + LOG(ERROR) << "Failed to persist the current log to " << path.value(); + return false; + } + return true; +} + +void MetricsLog::IncrementUserCrashCount(unsigned int count) { + metrics::SystemProfileProto::Stability* stability( + uma_proto()->mutable_system_profile()->mutable_stability()); + int current = stability->other_user_crash_count(); + stability->set_other_user_crash_count(current + count); +} + +void MetricsLog::IncrementKernelCrashCount(unsigned int count) { + metrics::SystemProfileProto::Stability* stability( + uma_proto()->mutable_system_profile()->mutable_stability()); + int current = stability->kernel_crash_count(); + stability->set_kernel_crash_count(current + count); +} + +void MetricsLog::IncrementUncleanShutdownCount(unsigned int count) { + metrics::SystemProfileProto::Stability* stability( + uma_proto()->mutable_system_profile()->mutable_stability()); + int current = stability->unclean_system_shutdown_count(); + stability->set_unclean_system_shutdown_count(current + count); +} + +bool MetricsLog::PopulateSystemProfile(SystemProfileSetter* profile_setter) { + CHECK(profile_setter); + return profile_setter->Populate(uma_proto()); +} diff --git a/uploader/metrics_log.h b/uploader/metrics_log.h new file mode 100644 index 0000000..9e60b97 --- /dev/null +++ b/uploader/metrics_log.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICSD_UPLOADER_METRICS_LOG_H_ +#define METRICSD_UPLOADER_METRICS_LOG_H_ + +#include <string> + +#include <base/files/file_path.h> +#include <base/macros.h> + +#include "uploader/metrics_log_base.h" + +// This file defines a set of user experience metrics data recorded by +// the MetricsService. This is the unit of data that is sent to the server. +class SystemProfileSetter; + +// This class provides base functionality for logging metrics data. +class MetricsLog : public metrics::MetricsLogBase { + public: + // The constructor doesn't set any metadata. The metadata is only set by a + // SystemProfileSetter. + MetricsLog(); + + // Increment the crash counters in the protobuf. + // These methods don't have to be thread safe as metrics logs are only + // accessed by the uploader thread. + void IncrementUserCrashCount(unsigned int count); + void IncrementKernelCrashCount(unsigned int count); + void IncrementUncleanShutdownCount(unsigned int count); + + // Populate the system profile with system information using setter. + bool PopulateSystemProfile(SystemProfileSetter* setter); + + // Load the log from |path|. + bool LoadFromFile(const base::FilePath& path); + + // Save this log to |path|. + bool SaveToFile(const base::FilePath& path); + + private: + friend class UploadServiceTest; + FRIEND_TEST(UploadServiceTest, CurrentLogSavedAndResumed); + FRIEND_TEST(UploadServiceTest, LogContainsAggregatedValues); + FRIEND_TEST(UploadServiceTest, LogContainsCrashCounts); + FRIEND_TEST(UploadServiceTest, LogKernelCrash); + FRIEND_TEST(UploadServiceTest, LogUncleanShutdown); + FRIEND_TEST(UploadServiceTest, LogUserCrash); + FRIEND_TEST(UploadServiceTest, UnknownCrashIgnored); + + DISALLOW_COPY_AND_ASSIGN(MetricsLog); +}; + +#endif // METRICSD_UPLOADER_METRICS_LOG_H_ diff --git a/uploader/metrics_log_base.cc b/uploader/metrics_log_base.cc new file mode 100644 index 0000000..f23bd63 --- /dev/null +++ b/uploader/metrics_log_base.cc @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uploader/metrics_log_base.h" + +#include <memory> + +#include "base/build_time.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_samples.h" +#include "uploader/metrics_hashes.h" +#include "uploader/proto/histogram_event.pb.h" +#include "uploader/proto/system_profile.pb.h" +#include "uploader/proto/user_action_event.pb.h" + +using base::Histogram; +using base::HistogramBase; +using base::HistogramSamples; +using base::SampleCountIterator; +using base::Time; +using base::TimeDelta; +using metrics::HistogramEventProto; +using metrics::SystemProfileProto; +using metrics::UserActionEventProto; + +namespace metrics { +namespace { + +// Any id less than 16 bytes is considered to be a testing id. +bool IsTestingID(const std::string& id) { + return id.size() < 16; +} + +} // namespace + +MetricsLogBase::MetricsLogBase(const std::string& client_id, + int session_id, + LogType log_type, + const std::string& version_string) + : num_events_(0), + locked_(false), + log_type_(log_type) { + DCHECK_NE(NO_LOG, log_type); + if (IsTestingID(client_id)) + uma_proto_.set_client_id(0); + else + uma_proto_.set_client_id(Hash(client_id)); + + uma_proto_.set_session_id(session_id); + uma_proto_.mutable_system_profile()->set_build_timestamp(GetBuildTime()); + uma_proto_.mutable_system_profile()->set_app_version(version_string); +} + +MetricsLogBase::~MetricsLogBase() {} + +// static +uint64_t MetricsLogBase::Hash(const std::string& value) { + uint64_t hash = metrics::HashMetricName(value); + + // The following log is VERY helpful when folks add some named histogram into + // the code, but forgot to update the descriptive list of histograms. When + // that happens, all we get to see (server side) is a hash of the histogram + // name. We can then use this logging to find out what histogram name was + // being hashed to a given MD5 value by just running the version of Chromium + // in question with --enable-logging. + VLOG(1) << "Metrics: Hash numeric [" << value << "]=[" << hash << "]"; + + return hash; +} + +// static +int64_t MetricsLogBase::GetBuildTime() { + static int64_t integral_build_time = 0; + if (!integral_build_time) { + Time time = base::GetBuildTime(); + integral_build_time = static_cast<int64_t>(time.ToTimeT()); + } + return integral_build_time; +} + +// static +int64_t MetricsLogBase::GetCurrentTime() { + return (base::TimeTicks::Now() - base::TimeTicks()).InSeconds(); +} + +void MetricsLogBase::CloseLog() { + DCHECK(!locked_); + locked_ = true; +} + +void MetricsLogBase::GetEncodedLog(std::string* encoded_log) { + DCHECK(locked_); + uma_proto_.SerializeToString(encoded_log); +} + +void MetricsLogBase::RecordUserAction(const std::string& key) { + DCHECK(!locked_); + + UserActionEventProto* user_action = uma_proto_.add_user_action_event(); + user_action->set_name_hash(Hash(key)); + user_action->set_time(GetCurrentTime()); + + ++num_events_; +} + +void MetricsLogBase::RecordHistogramDelta(const std::string& histogram_name, + const HistogramSamples& snapshot) { + DCHECK(!locked_); + DCHECK_NE(0, snapshot.TotalCount()); + + // We will ignore the MAX_INT/infinite value in the last element of range[]. + + HistogramEventProto* histogram_proto = uma_proto_.add_histogram_event(); + histogram_proto->set_name_hash(Hash(histogram_name)); + histogram_proto->set_sum(snapshot.sum()); + + for (std::unique_ptr<SampleCountIterator> it = snapshot.Iterator(); !it->Done(); + it->Next()) { + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + it->Get(&min, &max, &count); + HistogramEventProto::Bucket* bucket = histogram_proto->add_bucket(); + bucket->set_min(min); + bucket->set_max(max); + bucket->set_count(count); + } + + // Omit fields to save space (see rules in histogram_event.proto comments). + for (int i = 0; i < histogram_proto->bucket_size(); ++i) { + HistogramEventProto::Bucket* bucket = histogram_proto->mutable_bucket(i); + if (i + 1 < histogram_proto->bucket_size() && + bucket->max() == histogram_proto->bucket(i + 1).min()) { + bucket->clear_max(); + } else if (bucket->max() == bucket->min() + 1) { + bucket->clear_min(); + } + } +} + +} // namespace metrics diff --git a/uploader/metrics_log_base.h b/uploader/metrics_log_base.h new file mode 100644 index 0000000..f4e1995 --- /dev/null +++ b/uploader/metrics_log_base.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This file defines a set of user experience metrics data recorded by +// the MetricsService. This is the unit of data that is sent to the server. + +#ifndef METRICS_UPLOADER_METRICS_LOG_BASE_H_ +#define METRICS_UPLOADER_METRICS_LOG_BASE_H_ + +#include <string> + +#include "base/macros.h" +#include "base/metrics/histogram.h" +#include "base/time/time.h" +#include "uploader/proto/chrome_user_metrics_extension.pb.h" + +namespace base { +class HistogramSamples; +} // namespace base + +namespace metrics { + +// This class provides base functionality for logging metrics data. +class MetricsLogBase { + public: + // TODO(asvitkine): Remove the NO_LOG value. + enum LogType { + INITIAL_STABILITY_LOG, // The initial log containing stability stats. + ONGOING_LOG, // Subsequent logs in a session. + NO_LOG, // Placeholder value for when there is no log. + }; + + // Creates a new metrics log of the specified type. + // client_id is the identifier for this profile on this installation + // session_id is an integer that's incremented on each application launch + MetricsLogBase(const std::string& client_id, + int session_id, + LogType log_type, + const std::string& version_string); + virtual ~MetricsLogBase(); + + // Computes the MD5 hash of the given string, and returns the first 8 bytes of + // the hash. + static uint64_t Hash(const std::string& value); + + // Get the GMT buildtime for the current binary, expressed in seconds since + // January 1, 1970 GMT. + // The value is used to identify when a new build is run, so that previous + // reliability stats, from other builds, can be abandoned. + static int64_t GetBuildTime(); + + // Convenience function to return the current time at a resolution in seconds. + // This wraps base::TimeTicks, and hence provides an abstract time that is + // always incrementing for use in measuring time durations. + static int64_t GetCurrentTime(); + + // Records a user-initiated action. + void RecordUserAction(const std::string& key); + + // Record any changes in a given histogram for transmission. + void RecordHistogramDelta(const std::string& histogram_name, + const base::HistogramSamples& snapshot); + + // Stop writing to this record and generate the encoded representation. + // None of the Record* methods can be called after this is called. + void CloseLog(); + + // Fills |encoded_log| with the serialized protobuf representation of the + // record. Must only be called after CloseLog() has been called. + void GetEncodedLog(std::string* encoded_log); + + int num_events() { return num_events_; } + + void set_hardware_class(const std::string& hardware_class) { + uma_proto_.mutable_system_profile()->mutable_hardware()->set_hardware_class( + hardware_class); + } + + LogType log_type() const { return log_type_; } + + protected: + bool locked() const { return locked_; } + + metrics::ChromeUserMetricsExtension* uma_proto() { return &uma_proto_; } + const metrics::ChromeUserMetricsExtension* uma_proto() const { + return &uma_proto_; + } + + // TODO(isherman): Remove this once the XML pipeline is outta here. + int num_events_; // the number of events recorded in this log + + private: + // locked_ is true when record has been packed up for sending, and should + // no longer be written to. It is only used for sanity checking and is + // not a real lock. + bool locked_; + + // The type of the log, i.e. initial or ongoing. + const LogType log_type_; + + // Stores the protocol buffer representation for this log. + metrics::ChromeUserMetricsExtension uma_proto_; + + DISALLOW_COPY_AND_ASSIGN(MetricsLogBase); +}; + +} // namespace metrics + +#endif // METRICS_UPLOADER_METRICS_LOG_BASE_H_ diff --git a/uploader/metrics_log_base_unittest.cc b/uploader/metrics_log_base_unittest.cc new file mode 100644 index 0000000..980afd5 --- /dev/null +++ b/uploader/metrics_log_base_unittest.cc @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uploader/metrics_log_base.h" + +#include <string> + +#include <base/metrics/bucket_ranges.h> +#include <base/metrics/sample_vector.h> +#include <gtest/gtest.h> + +#include "uploader/proto/chrome_user_metrics_extension.pb.h" + +namespace metrics { + +namespace { + +class TestMetricsLogBase : public MetricsLogBase { + public: + TestMetricsLogBase() + : MetricsLogBase("client_id", 1, MetricsLogBase::ONGOING_LOG, "1.2.3.4") { + } + virtual ~TestMetricsLogBase() {} + + using MetricsLogBase::uma_proto; + + private: + DISALLOW_COPY_AND_ASSIGN(TestMetricsLogBase); +}; + +} // namespace + +TEST(MetricsLogBaseTest, LogType) { + MetricsLogBase log1("id", 0, MetricsLogBase::ONGOING_LOG, "1.2.3"); + EXPECT_EQ(MetricsLogBase::ONGOING_LOG, log1.log_type()); + + MetricsLogBase log2("id", 0, MetricsLogBase::INITIAL_STABILITY_LOG, "1.2.3"); + EXPECT_EQ(MetricsLogBase::INITIAL_STABILITY_LOG, log2.log_type()); +} + +TEST(MetricsLogBaseTest, EmptyRecord) { + MetricsLogBase log("totally bogus client ID", 137, + MetricsLogBase::ONGOING_LOG, "bogus version"); + log.set_hardware_class("sample-class"); + log.CloseLog(); + + std::string encoded; + log.GetEncodedLog(&encoded); + + // A couple of fields are hard to mock, so these will be copied over directly + // for the expected output. + metrics::ChromeUserMetricsExtension parsed; + ASSERT_TRUE(parsed.ParseFromString(encoded)); + + metrics::ChromeUserMetricsExtension expected; + expected.set_client_id(5217101509553811875); // Hashed bogus client ID + expected.set_session_id(137); + expected.mutable_system_profile()->set_build_timestamp( + parsed.system_profile().build_timestamp()); + expected.mutable_system_profile()->set_app_version("bogus version"); + expected.mutable_system_profile()->mutable_hardware()->set_hardware_class( + "sample-class"); + + EXPECT_EQ(expected.SerializeAsString(), encoded); +} + +TEST(MetricsLogBaseTest, HistogramBucketFields) { + // Create buckets: 1-5, 5-7, 7-8, 8-9, 9-10, 10-11, 11-12. + base::BucketRanges ranges(8); + ranges.set_range(0, 1); + ranges.set_range(1, 5); + ranges.set_range(2, 7); + ranges.set_range(3, 8); + ranges.set_range(4, 9); + ranges.set_range(5, 10); + ranges.set_range(6, 11); + ranges.set_range(7, 12); + + base::SampleVector samples(&ranges); + samples.Accumulate(3, 1); // Bucket 1-5. + samples.Accumulate(6, 1); // Bucket 5-7. + samples.Accumulate(8, 1); // Bucket 8-9. (7-8 skipped) + samples.Accumulate(10, 1); // Bucket 10-11. (9-10 skipped) + samples.Accumulate(11, 1); // Bucket 11-12. + + TestMetricsLogBase log; + log.RecordHistogramDelta("Test", samples); + + const metrics::ChromeUserMetricsExtension* uma_proto = log.uma_proto(); + const metrics::HistogramEventProto& histogram_proto = + uma_proto->histogram_event(uma_proto->histogram_event_size() - 1); + + // Buckets with samples: 1-5, 5-7, 8-9, 10-11, 11-12. + // Should become: 1-/, 5-7, /-9, 10-/, /-12. + ASSERT_EQ(5, histogram_proto.bucket_size()); + + // 1-5 becomes 1-/ (max is same as next min). + EXPECT_TRUE(histogram_proto.bucket(0).has_min()); + EXPECT_FALSE(histogram_proto.bucket(0).has_max()); + EXPECT_EQ(1, histogram_proto.bucket(0).min()); + + // 5-7 stays 5-7 (no optimization possible). + EXPECT_TRUE(histogram_proto.bucket(1).has_min()); + EXPECT_TRUE(histogram_proto.bucket(1).has_max()); + EXPECT_EQ(5, histogram_proto.bucket(1).min()); + EXPECT_EQ(7, histogram_proto.bucket(1).max()); + + // 8-9 becomes /-9 (min is same as max - 1). + EXPECT_FALSE(histogram_proto.bucket(2).has_min()); + EXPECT_TRUE(histogram_proto.bucket(2).has_max()); + EXPECT_EQ(9, histogram_proto.bucket(2).max()); + + // 10-11 becomes 10-/ (both optimizations apply, omit max is prioritized). + EXPECT_TRUE(histogram_proto.bucket(3).has_min()); + EXPECT_FALSE(histogram_proto.bucket(3).has_max()); + EXPECT_EQ(10, histogram_proto.bucket(3).min()); + + // 11-12 becomes /-12 (last record must keep max, min is same as max - 1). + EXPECT_FALSE(histogram_proto.bucket(4).has_min()); + EXPECT_TRUE(histogram_proto.bucket(4).has_max()); + EXPECT_EQ(12, histogram_proto.bucket(4).max()); +} + +} // namespace metrics diff --git a/uploader/metricsd_service_runner.cc b/uploader/metricsd_service_runner.cc new file mode 100644 index 0000000..4361cac --- /dev/null +++ b/uploader/metricsd_service_runner.cc @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uploader/metricsd_service_runner.h" + +#include <thread> + +#include <binder/IServiceManager.h> +#include <brillo/binder_watcher.h> +#include <brillo/message_loops/base_message_loop.h> +#include <utils/Errors.h> + +#include "uploader/bn_metricsd_impl.h" + +MetricsdServiceRunner::MetricsdServiceRunner( + std::shared_ptr<CrashCounters> counters) + : counters_(counters) {} + +void MetricsdServiceRunner::Start() { + thread_.reset(new std::thread(&MetricsdServiceRunner::Run, this)); +} + +void MetricsdServiceRunner::Run() { + android::sp<BnMetricsdImpl> metrics_service(new BnMetricsdImpl(counters_)); + + android::status_t status = android::defaultServiceManager()->addService( + metrics_service->getInterfaceDescriptor(), metrics_service); + CHECK(status == android::OK) << "Metricsd service registration failed"; + + message_loop_for_io_.reset(new base::MessageLoopForIO); + message_loop_.reset(new brillo::BaseMessageLoop(message_loop_for_io_.get())); + + brillo::BinderWatcher watcher(message_loop_.get()); + CHECK(watcher.Init()) << "failed to initialize the binder file descriptor " + << "watcher"; + + message_loop_->Run(); + + // Delete the message loop here as it needs to be deconstructed in the thread + // it is attached to. + message_loop_.reset(); + message_loop_for_io_.reset(); +} + +void MetricsdServiceRunner::Stop() { + message_loop_for_io_->PostTask(FROM_HERE, + message_loop_for_io_->QuitWhenIdleClosure()); + + thread_->join(); +} diff --git a/uploader/metricsd_service_runner.h b/uploader/metricsd_service_runner.h new file mode 100644 index 0000000..f5dad21 --- /dev/null +++ b/uploader/metricsd_service_runner.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICS_UPLOADER_METRISCD_SERVICE_RUNNER_H_ +#define METRICS_UPLOADER_METRISCD_SERVICE_RUNNER_H_ + +#include <memory> +#include <thread> + +#include <base/message_loop/message_loop.h> +#include <brillo/message_loops/message_loop.h> + +#include "uploader/crash_counters.h" + +class MetricsdServiceRunner { + public: + MetricsdServiceRunner(std::shared_ptr<CrashCounters> counters); + + // Start the Metricsd Binder service in a new thread. + void Start(); + + // Stop the Metricsd service and wait for its thread to exit. + void Stop(); + + private: + // Creates and run the main loop for metricsd's Binder service. + void Run(); + + std::unique_ptr<base::MessageLoopForIO> message_loop_for_io_; + std::unique_ptr<brillo::MessageLoop> message_loop_; + + std::unique_ptr<std::thread> thread_; + std::shared_ptr<CrashCounters> counters_; +}; + +#endif // METRICS_UPLOADER_METRISCD_SERVICE_RUNNER_H_ diff --git a/uploader/mock/mock_system_profile_setter.h b/uploader/mock/mock_system_profile_setter.h new file mode 100644 index 0000000..9b20291 --- /dev/null +++ b/uploader/mock/mock_system_profile_setter.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICS_UPLOADER_MOCK_MOCK_SYSTEM_PROFILE_SETTER_H_ +#define METRICS_UPLOADER_MOCK_MOCK_SYSTEM_PROFILE_SETTER_H_ + +#include "uploader/system_profile_setter.h" + +namespace metrics { +class ChromeUserMetricsExtension; +} + +// Mock profile setter used for testing. +class MockSystemProfileSetter : public SystemProfileSetter { + public: + bool Populate(metrics::ChromeUserMetricsExtension* profile_proto) override { + return true; + } +}; + +#endif // METRICS_UPLOADER_MOCK_MOCK_SYSTEM_PROFILE_SETTER_H_ diff --git a/uploader/mock/sender_mock.cc b/uploader/mock/sender_mock.cc new file mode 100644 index 0000000..bb4dc7d --- /dev/null +++ b/uploader/mock/sender_mock.cc @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uploader/mock/sender_mock.h" + +SenderMock::SenderMock() { + Reset(); +} + +bool SenderMock::Send(const std::string& content, const std::string& hash) { + send_call_count_ += 1; + last_message_ = content; + is_good_proto_ = last_message_proto_.ParseFromString(content); + return should_succeed_; +} + +void SenderMock::Reset() { + send_call_count_ = 0; + last_message_ = ""; + should_succeed_ = true; + last_message_proto_.Clear(); + is_good_proto_ = false; +} diff --git a/uploader/mock/sender_mock.h b/uploader/mock/sender_mock.h new file mode 100644 index 0000000..e79233f --- /dev/null +++ b/uploader/mock/sender_mock.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICS_UPLOADER_MOCK_SENDER_MOCK_H_ +#define METRICS_UPLOADER_MOCK_SENDER_MOCK_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "uploader/proto/chrome_user_metrics_extension.pb.h" +#include "uploader/sender.h" + +class SenderMock : public Sender { + public: + SenderMock(); + + bool Send(const std::string& content, const std::string& hash) override; + void Reset(); + + bool is_good_proto() { return is_good_proto_; } + int send_call_count() { return send_call_count_; } + const std::string last_message() { return last_message_; } + metrics::ChromeUserMetricsExtension last_message_proto() { + return last_message_proto_; + } + void set_should_succeed(bool succeed) { should_succeed_ = succeed; } + + private: + // Is set to true if the proto was parsed successfully. + bool is_good_proto_; + + // If set to true, the Send method will return true to simulate a successful + // send. + bool should_succeed_; + + // Count of how many times Send was called since the last reset. + int send_call_count_; + + // Last message received by Send. + std::string last_message_; + + // If is_good_proto is true, last_message_proto is the deserialized + // representation of last_message. + metrics::ChromeUserMetricsExtension last_message_proto_; +}; + +#endif // METRICS_UPLOADER_MOCK_SENDER_MOCK_H_ diff --git a/uploader/proto/README b/uploader/proto/README new file mode 100644 index 0000000..4292a40 --- /dev/null +++ b/uploader/proto/README @@ -0,0 +1,37 @@ +Copyright (C) 2015 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + + + +This directory contains the protocol buffers used by the standalone metrics +uploader. Those protobuffers are copied from the chromium protobuffers from +https://chromium.googlesource.com/chromium/src/+/master/components/metrics/proto/ +at 3bfe5f2b4c03d2cac718d137ed14cd2c6354bfed. + +Any change to this protobuf must first be made to the backend's protobuf and be +compatible with the chromium protobuffers. + + +Q: Why fork the chromium protobuffers ? +A: The standalone metrics uploader needs chromium os fields that are not defined +by the chromium protobufs. Instead of pushing chromium os specific changes to +chromium, we can add them only to chromium os (and to the backend of course). + + +Q: What's the difference between those protobuffers and chromium's protobuffers? +A: When the protobuffers were copied, some chromium specific protobuffers were +not imported: +* omnibox related protobuffers. +* performance profiling protobuffers (not used in chromium os). diff --git a/uploader/proto/chrome_user_metrics_extension.proto b/uploader/proto/chrome_user_metrics_extension.proto new file mode 100644 index 0000000..a07830f --- /dev/null +++ b/uploader/proto/chrome_user_metrics_extension.proto @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Protocol buffer for Chrome UMA (User Metrics Analysis). +// +// Note: this protobuf must be compatible with the one in chromium. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "ChromeUserMetricsExtensionProtos"; +option java_package = "org.chromium.components.metrics"; + +package metrics; + +import "system/core/metricsd/uploader/proto/histogram_event.proto"; +import "system/core/metricsd/uploader/proto/system_profile.proto"; +import "system/core/metricsd/uploader/proto/user_action_event.proto"; + +// Next tag: 13 +message ChromeUserMetricsExtension { + // The product (i.e. end user application) for a given UMA log. + enum Product { + // Google Chrome product family. + CHROME = 0; + } + // The product corresponding to this log. The field type is int32 instead of + // Product so that downstream users of the Chromium metrics component can + // introduce products without needing to make changes to the Chromium code + // (though they still need to add the new product to the server-side enum). + // Note: The default value is Chrome, so Chrome products will not transmit + // this field. + optional int32 product = 10 [default = 0]; + + // The id of the client install that generated these events. + // + // For Chrome clients, this id is unique to a top-level (one level above the + // "Default" directory) Chrome user data directory [1], and so is shared among + // all Chrome user profiles contained in this user data directory. + // An id of 0 is reserved for test data (monitoring and internal testing) and + // should normally be ignored in analysis of the data. + // [1] http://www.chromium.org/user-experience/user-data-directory + optional fixed64 client_id = 1; + + // The session id for this user. + // Values such as tab ids are only meaningful within a particular session. + // The client keeps track of the session id and sends it with each event. + // The session id is simply an integer that is incremented each time the user + // relaunches Chrome. + optional int32 session_id = 2; + + // Information about the user's browser and system configuration. + optional SystemProfileProto system_profile = 3; + + // This message will log one or more of the following event types: + repeated UserActionEventProto user_action_event = 4; + repeated HistogramEventProto histogram_event = 6; + +} diff --git a/uploader/proto/histogram_event.proto b/uploader/proto/histogram_event.proto new file mode 100644 index 0000000..3825063 --- /dev/null +++ b/uploader/proto/histogram_event.proto @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Histogram-collected metrics. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "HistogramEventProtos"; +option java_package = "org.chromium.components.metrics"; + +package metrics; + +// Next tag: 4 +message HistogramEventProto { + // The name of the histogram, hashed. + optional fixed64 name_hash = 1; + + // The sum of all the sample values. + // Together with the total count of the sample values, this allows us to + // compute the average value. The count of all sample values is just the sum + // of the counts of all the buckets. + optional int64 sum = 2; + + // The per-bucket data. + message Bucket { + // Each bucket's range is bounded by min <= x < max. + // It is valid to omit one of these two fields in a bucket, but not both. + // If the min field is omitted, its value is assumed to be equal to max - 1. + // If the max field is omitted, its value is assumed to be equal to the next + // bucket's min value (possibly computed per above). The last bucket in a + // histogram should always include the max field. + optional int64 min = 1; + optional int64 max = 2; + + // The bucket's index in the list of buckets, sorted in ascending order. + // This field was intended to provide extra redundancy to detect corrupted + // records, but was never used. As of M31, it is no longer sent by Chrome + // clients to reduce the UMA upload size. + optional int32 bucket_index = 3 [deprecated = true]; + + // The number of entries in this bucket. + optional int64 count = 4; + } + repeated Bucket bucket = 3; +} diff --git a/uploader/proto/system_profile.proto b/uploader/proto/system_profile.proto new file mode 100644 index 0000000..bac828b --- /dev/null +++ b/uploader/proto/system_profile.proto @@ -0,0 +1,759 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Stores information about the user's brower and system configuration. +// The system configuration fields are recorded once per client session. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "SystemProfileProtos"; +option java_package = "org.chromium.components.metrics"; + +package metrics; + +// Next tag: 21 +message SystemProfileProto { + // The time when the client was compiled/linked, in seconds since the epoch. + optional int64 build_timestamp = 1; + + // A version number string for the application. + // Most commonly this is the browser version number found in a user agent + // string, and is typically a 4-tuple of numbers separated by periods. In + // cases where the user agent version might be ambiguous (example: Linux 64- + // bit build, rather than 32-bit build, or a Windows version used in some + // special context, such as ChromeFrame running in IE), then this may include + // some additional postfix to provide clarification not available in the UA + // string. + // + // An example of a browser version 4-tuple is "5.0.322.0". Currently used + // postfixes are: + // + // "-64": a 64-bit build + // "-F": Chrome is running under control of ChromeFrame + // "-devel": this is not an official build of Chrome + // + // A full version number string could look similar to: + // "5.0.322.0-F-devel". + // + // This value, when available, is more trustworthy than the UA string + // associated with the request; and including the postfix, may be more + // specific. + optional string app_version = 2; + + // The brand code or distribution tag assigned to a partner, if available. + // Brand codes are only available on Windows. Not every Windows install + // though will have a brand code. + optional string brand_code = 12; + + // The possible channels for an installation, from least to most stable. + enum Channel { + CHANNEL_UNKNOWN = 0; // Unknown channel -- perhaps an unofficial build? + CHANNEL_CANARY = 1; + CHANNEL_DEV = 2; + CHANNEL_BETA = 3; + CHANNEL_STABLE = 4; + } + optional Channel channel = 10; + + // True if Chrome build is ASan-instrumented. + optional bool is_asan_build = 20 [default = false]; + + // The date the user enabled UMA, in seconds since the epoch. + // If the user has toggled the UMA enabled state multiple times, this will + // be the most recent date on which UMA was enabled. + // For privacy, this is rounded to the nearest hour. + optional int64 uma_enabled_date = 3; + + // The time when the client was installed, in seconds since the epoch. + // For privacy, this is rounded to the nearest hour. + optional int64 install_date = 16; + + // The user's selected application locale, i.e. the user interface language. + // The locale includes a language code and, possibly, also a country code, + // e.g. "en-US". + optional string application_locale = 4; + + message BrilloDeviceData { + optional string product_id = 1; + } + optional BrilloDeviceData brillo = 21; + + // Information on the user's operating system. + message OS { + // The user's operating system. This should be one of: + // - Android + // - Windows NT + // - Linux (includes ChromeOS) + // - iPhone OS + // - Mac OS X + optional string name = 1; + + // The version of the OS. The meaning of this field is OS-dependent. + optional string version = 2; + + // The fingerprint of the build. This field is used only on Android. + optional string fingerprint = 3; + + // Whether the version of iOS appears to be "jailbroken". This field is + // used only on iOS. Chrome for iOS detects whether device contains a + // DynamicLibraries/ directory. It's a necessary but insufficient indicator + // of whether the operating system has been jailbroken. + optional bool is_jailbroken = 4; + } + optional OS os = 5; + + // Next tag for Hardware: 18 + // Information on the user's hardware. + message Hardware { + // The CPU architecture (x86, PowerPC, x86_64, ...) + optional string cpu_architecture = 1; + + // The amount of RAM present on the system, in megabytes. + optional int64 system_ram_mb = 2; + + // The base memory address that chrome.dll was loaded at. + // (Logged only on Windows.) + optional int64 dll_base = 3; + + // The Chrome OS device hardware class ID is a unique string associated with + // each Chrome OS device product revision generally assigned at hardware + // qualification time. The hardware class effectively identifies the + // configured system components such as CPU, WiFi adapter, etc. + // + // An example of such a hardware class is "IEC MARIO PONY 6101". An + // internal database associates this hardware class with the qualified + // device specifications including OEM information, schematics, hardware + // qualification reports, test device tags, etc. + optional string hardware_class = 4; + + // The number of physical screens. + optional int32 screen_count = 5; + + // The screen dimensions of the primary screen, in pixels. + optional int32 primary_screen_width = 6; + optional int32 primary_screen_height = 7; + + // The device scale factor of the primary screen. + optional float primary_screen_scale_factor = 12; + + // Max DPI for any attached screen. (Windows only) + optional float max_dpi_x = 9; + optional float max_dpi_y = 10; + + // Information on the CPU obtained by CPUID. + message CPU { + // A 12 character string naming the vendor, e.g. "GeniuneIntel". + optional string vendor_name = 1; + + // The signature reported by CPUID (from EAX). + optional uint32 signature = 2; + + // Number of logical processors/cores on the current machine. + optional uint32 num_cores = 3; + } + optional CPU cpu = 13; + + // Information on the GPU + message Graphics { + // The GPU manufacturer's vendor id. + optional uint32 vendor_id = 1; + + // The GPU manufacturer's device id for the chip set. + optional uint32 device_id = 2; + + // The driver version on the GPU. + optional string driver_version = 3; + + // The driver date on the GPU. + optional string driver_date = 4; + + // The GL_VENDOR string. An example of a gl_vendor string is + // "Imagination Technologies". "" if we are not using OpenGL. + optional string gl_vendor = 6; + + // The GL_RENDERER string. An example of a gl_renderer string is + // "PowerVR SGX 540". "" if we are not using OpenGL. + optional string gl_renderer = 7; + } + optional Graphics gpu = 8; + + // Information about Bluetooth devices paired with the system. + message Bluetooth { + // Whether Bluetooth is present on this system. + optional bool is_present = 1; + + // Whether Bluetooth is enabled on this system. + optional bool is_enabled = 2; + + // Describes a paired device. + message PairedDevice { + // Assigned class of the device. This is a bitfield according to the + // Bluetooth specification available at the following URL: + // https://www.bluetooth.org/en-us/specification/assigned-numbers-overview/baseband + optional uint32 bluetooth_class = 1; + + // Decoded device type. + enum Type { + DEVICE_UNKNOWN = 0; + DEVICE_COMPUTER = 1; + DEVICE_PHONE = 2; + DEVICE_MODEM = 3; + DEVICE_AUDIO = 4; + DEVICE_CAR_AUDIO = 5; + DEVICE_VIDEO = 6; + DEVICE_PERIPHERAL = 7; + DEVICE_JOYSTICK = 8; + DEVICE_GAMEPAD = 9; + DEVICE_KEYBOARD = 10; + DEVICE_MOUSE = 11; + DEVICE_TABLET = 12; + DEVICE_KEYBOARD_MOUSE_COMBO = 13; + } + optional Type type = 2; + + // Vendor prefix of the Bluetooth address, these are OUI registered by + // the IEEE and are encoded with the first byte in bits 16-23, the + // second byte in bits 8-15 and the third byte in bits 0-7. + // + // ie. Google's OUI (00:1A:11) is encoded as 0x00001A11 + optional uint32 vendor_prefix = 4; + + // The Vendor ID of a device, returned in vendor_id below, can be + // either allocated by the Bluetooth SIG or USB IF, providing two + // completely overlapping namespaces for identifiers. + // + // This field should be read along with vendor_id to correctly + // identify the vendor. For example Google is identified by either + // vendor_id_source = VENDOR_ID_BLUETOOTH, vendor_id = 0x00E0 or + // vendor_id_source = VENDOR_ID_USB, vendor_id = 0x18D1. + // + // If the device does not support the Device ID specification the + // unknown value will be set. + enum VendorIDSource { + VENDOR_ID_UNKNOWN = 0; + VENDOR_ID_BLUETOOTH = 1; + VENDOR_ID_USB = 2; + } + optional VendorIDSource vendor_id_source = 8; + + // Vendor ID of the device, where available. + optional uint32 vendor_id = 5; + + // Product ID of the device, where available. + optional uint32 product_id = 6; + + // Device ID of the device, generally the release or version number in + // BCD format, where available. + optional uint32 device_id = 7; + } + repeated PairedDevice paired_device = 3; + } + optional Bluetooth bluetooth = 11; + + // Whether the internal display produces touch events. Omitted if unknown. + // Logged on ChromeOS only. + optional bool internal_display_supports_touch = 14; + + // Vendor ids and product ids of external touchscreens. + message TouchScreen { + // Touch screen vendor id. + optional uint32 vendor_id = 1; + // Touch screen product id. + optional uint32 product_id = 2; + } + // Lists vendor and product ids of external touchscreens. + // Logged on ChromeOS only. + repeated TouchScreen external_touchscreen = 15; + + // Drive messages are currently logged on Windows 7+, iOS, and Android. + message Drive { + // Whether this drive incurs a time penalty when randomly accessed. This + // should be true for spinning disks but false for SSDs or other + // flash-based drives. + optional bool has_seek_penalty = 1; + } + // The drive that the application executable was loaded from. + optional Drive app_drive = 16; + // The drive that the current user data directory was loaded from. + optional Drive user_data_drive = 17; + } + optional Hardware hardware = 6; + + // Information about the network connection. + message Network { + // Set to true if connection_type changed during the lifetime of the log. + optional bool connection_type_is_ambiguous = 1; + + // See net::NetworkChangeNotifier::ConnectionType. + enum ConnectionType { + CONNECTION_UNKNOWN = 0; + CONNECTION_ETHERNET = 1; + CONNECTION_WIFI = 2; + CONNECTION_2G = 3; + CONNECTION_3G = 4; + CONNECTION_4G = 5; + CONNECTION_BLUETOOTH = 6; + } + // The connection type according to NetworkChangeNotifier. + optional ConnectionType connection_type = 2; + + // Set to true if wifi_phy_layer_protocol changed during the lifetime of the log. + optional bool wifi_phy_layer_protocol_is_ambiguous = 3; + + // See net::WifiPHYLayerProtocol. + enum WifiPHYLayerProtocol { + WIFI_PHY_LAYER_PROTOCOL_NONE = 0; + WIFI_PHY_LAYER_PROTOCOL_ANCIENT = 1; + WIFI_PHY_LAYER_PROTOCOL_A = 2; + WIFI_PHY_LAYER_PROTOCOL_B = 3; + WIFI_PHY_LAYER_PROTOCOL_G = 4; + WIFI_PHY_LAYER_PROTOCOL_N = 5; + WIFI_PHY_LAYER_PROTOCOL_UNKNOWN = 6; + } + // The physical layer mode of the associated wifi access point, if any. + optional WifiPHYLayerProtocol wifi_phy_layer_protocol = 4; + + // Describe wifi access point information. + message WifiAccessPoint { + // Vendor prefix of the access point's BSSID, these are OUIs + // (Organizationally Unique Identifiers) registered by + // the IEEE and are encoded with the first byte in bits 16-23, the + // second byte in bits 8-15 and the third byte in bits 0-7. + optional uint32 vendor_prefix = 1; + + // Access point seurity mode definitions. + enum SecurityMode { + SECURITY_UNKNOWN = 0; + SECURITY_WPA = 1; + SECURITY_WEP = 2; + SECURITY_RSN = 3; + SECURITY_802_1X = 4; + SECURITY_PSK = 5; + SECURITY_NONE = 6; + } + // The security mode of the access point. + optional SecurityMode security_mode = 2; + + // Vendor specific information. + message VendorInformation { + // The model number, for example "0". + optional string model_number = 1; + + // The model name (sometimes the same as the model_number), + // for example "WZR-HP-AG300H". + optional string model_name = 2; + + // The device name (sometimes the same as the model_number), + // for example "Dummynet" + optional string device_name = 3; + + // The list of vendor-specific OUIs (Organziationally Unqiue + // Identifiers). These are provided by the vendor through WPS + // (Wireless Provisioning Service) information elements, which + // identifies the content of the element. + repeated uint32 element_identifier = 4; + } + // The wireless access point vendor information. + optional VendorInformation vendor_info = 3; + } + // Information of the wireless AP that device is connected to. + optional WifiAccessPoint access_point_info = 5; + } + optional Network network = 13; + + // Information on the Google Update install that is managing this client. + message GoogleUpdate { + // Whether the Google Update install is system-level or user-level. + optional bool is_system_install = 1; + + // The date at which Google Update last started performing an automatic + // update check, in seconds since the Unix epoch. + optional int64 last_automatic_start_timestamp = 2; + + // The date at which Google Update last successfully sent an update check + // and recieved an intact response from the server, in seconds since the + // Unix epoch. (The updates don't need to be successfully installed.) + optional int64 last_update_check_timestamp = 3; + + // Describes a product being managed by Google Update. (This can also + // describe Google Update itself.) + message ProductInfo { + // The current version of the product that is installed. + optional string version = 1; + + // The date at which Google Update successfully updated this product, + // stored in seconds since the Unix epoch. This is updated when an update + // is successfully applied, or if the server reports that no update + // is available. + optional int64 last_update_success_timestamp = 2; + + // The result reported by the product updater on its last run. + enum InstallResult { + INSTALL_RESULT_SUCCESS = 0; + INSTALL_RESULT_FAILED_CUSTOM_ERROR = 1; + INSTALL_RESULT_FAILED_MSI_ERROR = 2; + INSTALL_RESULT_FAILED_SYSTEM_ERROR = 3; + INSTALL_RESULT_EXIT_CODE = 4; + } + optional InstallResult last_result = 3; + + // The error code reported by the product updater on its last run. This + // will typically be a error code specific to the product installer. + optional int32 last_error = 4; + + // The extra error code reported by the product updater on its last run. + // This will typically be a Win32 error code. + optional int32 last_extra_error = 5; + } + optional ProductInfo google_update_status = 4; + optional ProductInfo client_status = 5; + } + optional GoogleUpdate google_update = 11; + + // Information on all installed plugins. + message Plugin { + // The plugin's self-reported name and filename (without path). + optional string name = 1; + optional string filename = 2; + + // The plugin's version. + optional string version = 3; + + // True if the plugin is disabled. + // If a client has multiple local Chrome user accounts, this is logged based + // on the first user account launched during the current session. + optional bool is_disabled = 4; + + // True if the plugin is PPAPI. + optional bool is_pepper = 5; + } + repeated Plugin plugin = 7; + + // Figures that can be used to generate application stability metrics. + // All values are counts of events since the last time that these + // values were reported. + // Next tag: 24 + message Stability { + // Total amount of time that the program was running, in seconds, + // since the last time a log was recorded, as measured using a client-side + // clock implemented via TimeTicks, which guarantees that it is monotonic + // and does not jump if the user changes his/her clock. The TimeTicks + // implementation also makes the clock not count time the computer is + // suspended. + optional int64 incremental_uptime_sec = 1; + + // Total amount of time that the program was running, in seconds, + // since startup, as measured using a client-side clock implemented + // via TimeTicks, which guarantees that it is monotonic and does not + // jump if the user changes his/her clock. The TimeTicks implementation + // also makes the clock not count time the computer is suspended. + // This field was added for M-35. + optional int64 uptime_sec = 23; + + // Page loads along with renderer crashes and hangs, since page load count + // roughly corresponds to usage. + optional int32 page_load_count = 2; + optional int32 renderer_crash_count = 3; + optional int32 renderer_hang_count = 4; + + // Number of renderer crashes that were for extensions. These crashes are + // not counted in renderer_crash_count. + optional int32 extension_renderer_crash_count = 5; + + // Number of non-renderer child process crashes. + optional int32 child_process_crash_count = 6; + + // Number of times the browser has crashed while logged in as the "other + // user" (guest) account. + // Logged on ChromeOS only. + optional int32 other_user_crash_count = 7; + + // Number of times the kernel has crashed. + // Logged on ChromeOS only. + optional int32 kernel_crash_count = 8; + + // Number of times the system has shut down uncleanly. + // Logged on ChromeOS only. + optional int32 unclean_system_shutdown_count = 9; + + // + // All the remaining fields in the Stability are recorded at most once per + // client session. + // + + // The number of times the program was launched. + // This will typically be equal to 1. However, it is possible that Chrome + // was unable to upload stability metrics for previous launches (e.g. due to + // crashing early during startup), and hence this value might be greater + // than 1. + optional int32 launch_count = 15; + // The number of times that it didn't exit cleanly (which we assume to be + // mostly crashes). + optional int32 crash_count = 16; + + // The number of times the program began, but did not complete, the shutdown + // process. (For example, this may occur when Windows is shutting down, and + // it only gives the process a few seconds to clean up.) + optional int32 incomplete_shutdown_count = 17; + + // The number of times the program was able register with breakpad crash + // services. + optional int32 breakpad_registration_success_count = 18; + + // The number of times the program failed to register with breakpad crash + // services. If crash registration fails then when the program crashes no + // crash report will be generated. + optional int32 breakpad_registration_failure_count = 19; + + // The number of times the program has run under a debugger. This should + // be an exceptional condition. Running under a debugger prevents crash + // dumps from being generated. + optional int32 debugger_present_count = 20; + + // The number of times the program has run without a debugger attached. + // This should be most common scenario and should be very close to + // |launch_count|. + optional int32 debugger_not_present_count = 21; + + // Stability information for all installed plugins. + message PluginStability { + // The relevant plugin's information (name, etc.) + optional Plugin plugin = 1; + + // The number of times this plugin's process was launched. + optional int32 launch_count = 2; + + // The number of times this plugin was instantiated on a web page. + // This will be >= |launch_count|. + // (A page load with multiple sections drawn by this plugin will + // increase this count multiple times.) + optional int32 instance_count = 3; + + // The number of times this plugin process crashed. + // This value will be <= |launch_count|. + optional int32 crash_count = 4; + + // The number of times this plugin could not be loaded. + optional int32 loading_error_count = 5; + } + repeated PluginStability plugin_stability = 22; + } + optional Stability stability = 8; + + // Description of a field trial or experiment that the user is currently + // enrolled in. + // All metrics reported in this upload can potentially be influenced by the + // field trial. + message FieldTrial { + // The name of the field trial, as a 32-bit identifier. + // Currently, the identifier is a hash of the field trial's name. + optional fixed32 name_id = 1; + + // The user's group within the field trial, as a 32-bit identifier. + // Currently, the identifier is a hash of the group's name. + optional fixed32 group_id = 2; + } + repeated FieldTrial field_trial = 9; + + // Information about the A/V output device(s) (typically just a TV). + // However, a configuration may have one or more intermediate A/V devices + // between the source device and the TV (e.g. an A/V receiver, video + // processor, etc.). + message ExternalAudioVideoDevice { + // The manufacturer name (possibly encoded as a 3-letter code, e.g. "YMH" + // for Yamaha). + optional string manufacturer_name = 1; + + // The model name (e.g. "RX-V1900"). Some devices may report generic names + // like "receiver" or use the full manufacturer name (e.g "PHILIPS"). + optional string model_name = 2; + + // The product code (e.g. "0218"). + optional string product_code = 3; + + // The device types. A single device can have multiple types (e.g. a set-top + // box could be both a tuner and a player). The same type may even be + // repeated (e.g a device that reports two tuners). + enum AVDeviceType { + AV_DEVICE_TYPE_UNKNOWN = 0; + AV_DEVICE_TYPE_TV = 1; + AV_DEVICE_TYPE_RECORDER = 2; + AV_DEVICE_TYPE_TUNER = 3; + AV_DEVICE_TYPE_PLAYER = 4; + AV_DEVICE_TYPE_AUDIO_SYSTEM = 5; + } + repeated AVDeviceType av_device_type = 4; + + // The year of manufacture. + optional int32 manufacture_year = 5; + + // The week of manufacture. + // Note: per the Wikipedia EDID article, numbering for this field may not + // be consistent between manufacturers. + optional int32 manufacture_week = 6; + + // Max horizontal resolution in pixels. + optional int32 horizontal_resolution = 7; + + // Max vertical resolution in pixels. + optional int32 vertical_resolution = 8; + + // Audio capabilities of the device. + // Ref: http://en.wikipedia.org/wiki/Extended_display_identification_data + message AudioDescription { + // Audio format + enum AudioFormat { + AUDIO_FORMAT_UNKNOWN = 0; + AUDIO_FORMAT_LPCM = 1; + AUDIO_FORMAT_AC_3 = 2; + AUDIO_FORMAT_MPEG1 = 3; + AUDIO_FORMAT_MP3 = 4; + AUDIO_FORMAT_MPEG2 = 5; + AUDIO_FORMAT_AAC = 6; + AUDIO_FORMAT_DTS = 7; + AUDIO_FORMAT_ATRAC = 8; + AUDIO_FORMAT_ONE_BIT = 9; + AUDIO_FORMAT_DD_PLUS = 10; + AUDIO_FORMAT_DTS_HD = 11; + AUDIO_FORMAT_MLP_DOLBY_TRUEHD = 12; + AUDIO_FORMAT_DST_AUDIO = 13; + AUDIO_FORMAT_MICROSOFT_WMA_PRO = 14; + } + optional AudioFormat audio_format = 1; + + // Number of channels (e.g. 1, 2, 8, etc.). + optional int32 num_channels = 2; + + // Supported sample frequencies in Hz (e.g. 32000, 44100, etc.). + // Multiple frequencies may be specified. + repeated int32 sample_frequency_hz = 3; + + // Maximum bit rate in bits/s. + optional int32 max_bit_rate_per_second = 4; + + // Bit depth (e.g. 16, 20, 24, etc.). + optional int32 bit_depth = 5; + } + repeated AudioDescription audio_description = 9; + + // The position in AV setup. + // A value of 0 means this device is the TV. + // A value of 1 means this device is directly connected to one of + // the TV's inputs. + // Values > 1 indicate there are 1 or more devices between this device + // and the TV. + optional int32 position_in_setup = 10; + + // Whether this device is in the path to the TV. + optional bool is_in_path_to_tv = 11; + + // The CEC version the device supports. + // CEC stands for Consumer Electronics Control, a part of the HDMI + // specification. Not all HDMI devices support CEC. + // Only devices that support CEC will report a value here. + optional int32 cec_version = 12; + + // This message reports CEC commands seen by a device. + // After each log is sent, this information is cleared and gathered again. + // By collecting CEC status information by opcode we can determine + // which CEC features can be supported. + message CECCommand { + // The CEC command opcode. CEC supports up to 256 opcodes. + // We add only one CECCommand message per unique opcode. Only opcodes + // seen by the device will be reported. The remainder of the message + // accumulates status for this opcode (and device). + optional int32 opcode = 1; + + // The total number of commands received from the external device. + optional int32 num_received_direct = 2; + + // The number of commands received from the external device as part of a + // broadcast message. + optional int32 num_received_broadcast = 3; + + // The total number of commands sent to the external device. + optional int32 num_sent_direct = 4; + + // The number of commands sent to the external device as part of a + // broadcast message. + optional int32 num_sent_broadcast = 5; + + // The number of aborted commands for unknown reasons. + optional int32 num_aborted_unknown_reason = 6; + + // The number of aborted commands because of an unrecognized opcode. + optional int32 num_aborted_unrecognized = 7; + } + repeated CECCommand cec_command = 13; + } + repeated ExternalAudioVideoDevice external_audio_video_device = 14; + + // Information about the current wireless access point. Collected directly + // from the wireless access point via standard apis if the device is + // connected to the Internet wirelessly. Introduced for Chrome on TV devices + // but also can be collected by ChromeOS, Android or other clients. + message ExternalAccessPoint { + // The manufacturer name, for example "ASUSTeK Computer Inc.". + optional string manufacturer = 1; + + // The model name, for example "Wi-Fi Protected Setup Router". + optional string model_name = 2; + + // The model number, for example "RT-N16". + optional string model_number = 3; + + // The device name (sometime same as model_number), for example "RT-N16". + optional string device_name = 4; + } + optional ExternalAccessPoint external_access_point = 15; + + // Number of users currently signed into a multiprofile session. + // A zero value indicates that the user count changed while the log is open. + // Logged only on ChromeOS. + optional uint32 multi_profile_user_count = 17; + + // Information about extensions that are installed, masked to provide better + // privacy. Only extensions from a single profile are reported; this will + // generally be the profile used when the browser is started. The profile + // reported on will remain consistent at least until the browser is + // relaunched (or the profile is deleted by the user). + // + // Each client first picks a value for client_key derived from its UMA + // client_id: + // client_key = client_id % 4096 + // Then, each installed extension is mapped into a hash bucket according to + // bucket = CityHash64(StringPrintf("%d:%s", + // client_key, extension_id)) % 1024 + // The client reports the set of hash buckets occupied by all installed + // extensions. If multiple extensions map to the same bucket, that bucket is + // still only reported once. + repeated int32 occupied_extension_bucket = 18; + + // The state of loaded extensions for this system. The system can have either + // no applicable extensions, extensions only from the webstore and verified by + // the webstore, extensions only from the webstore but not verified, or + // extensions not from the store. If there is a single off-store extension, + // then HAS_OFFSTORE is reported. This should be kept in sync with the + // corresponding enum in chrome/browser/metrics/extensions_metrics_provider.cc + enum ExtensionsState { + NO_EXTENSIONS = 0; + NO_OFFSTORE_VERIFIED = 1; + NO_OFFSTORE_UNVERIFIED = 2; + HAS_OFFSTORE = 3; + } + optional ExtensionsState offstore_extensions_state = 19; +} diff --git a/uploader/proto/user_action_event.proto b/uploader/proto/user_action_event.proto new file mode 100644 index 0000000..464f3c8 --- /dev/null +++ b/uploader/proto/user_action_event.proto @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// +// Stores information about an event that occurs in response to a user action, +// e.g. an interaction with a browser UI element. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "UserActionEventProtos"; +option java_package = "org.chromium.components.metrics"; + +package metrics; + +// Next tag: 3 +message UserActionEventProto { + // The name of the action, hashed. + optional fixed64 name_hash = 1; + + // The timestamp for the event, in seconds since the epoch. + optional int64 time = 2; +} diff --git a/uploader/sender.h b/uploader/sender.h new file mode 100644 index 0000000..369c9c2 --- /dev/null +++ b/uploader/sender.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICS_UPLOADER_SENDER_H_ +#define METRICS_UPLOADER_SENDER_H_ + +#include <string> + +// Abstract class for a Sender that uploads a metrics message. +class Sender { + public: + virtual ~Sender() {} + // Sends a message |content| with its sha1 hash |hash| + virtual bool Send(const std::string& content, const std::string& hash) = 0; +}; + +#endif // METRICS_UPLOADER_SENDER_H_ diff --git a/uploader/sender_http.cc b/uploader/sender_http.cc new file mode 100644 index 0000000..4b572a6 --- /dev/null +++ b/uploader/sender_http.cc @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uploader/sender_http.h" + +#include <string> + +#include <base/logging.h> +#include <base/strings/string_number_conversions.h> +#include <brillo/http/http_utils.h> +#include <brillo/mime_utils.h> + +HttpSender::HttpSender(const std::string server_url) + : server_url_(server_url) {} + +bool HttpSender::Send(const std::string& content, + const std::string& content_hash) { + const std::string hash = + base::HexEncode(content_hash.data(), content_hash.size()); + + brillo::http::HeaderList headers = {{"X-Chrome-UMA-Log-SHA1", hash}}; + brillo::ErrorPtr error; + auto response = brillo::http::PostTextAndBlock( + server_url_, + content, + brillo::mime::application::kWwwFormUrlEncoded, + headers, + brillo::http::Transport::CreateDefault(), + &error); + if (!response || response->ExtractDataAsString() != "OK") { + if (error) { + DLOG(ERROR) << "Failed to send data: " << error->GetMessage(); + } + return false; + } + return true; +} diff --git a/uploader/sender_http.h b/uploader/sender_http.h new file mode 100644 index 0000000..4f1c08f --- /dev/null +++ b/uploader/sender_http.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICS_UPLOADER_SENDER_HTTP_H_ +#define METRICS_UPLOADER_SENDER_HTTP_H_ + +#include <string> + +#include <base/macros.h> + +#include "uploader/sender.h" + +// Sender implemented using http_utils from libbrillo +class HttpSender : public Sender { + public: + explicit HttpSender(std::string server_url); + ~HttpSender() override = default; + // Sends |content| whose SHA1 hash is |hash| to server_url with a synchronous + // POST request to server_url. + bool Send(const std::string& content, const std::string& hash) override; + + private: + const std::string server_url_; + + DISALLOW_COPY_AND_ASSIGN(HttpSender); +}; + +#endif // METRICS_UPLOADER_SENDER_HTTP_H_ diff --git a/uploader/system_profile_cache.cc b/uploader/system_profile_cache.cc new file mode 100644 index 0000000..e6f6617 --- /dev/null +++ b/uploader/system_profile_cache.cc @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uploader/system_profile_cache.h" + +#include <base/files/file_util.h> +#include <base/guid.h> +#include <base/logging.h> +#include <base/strings/string_number_conversions.h> +#include <base/strings/string_util.h> +#include <brillo/osrelease_reader.h> +#include <string> +#include <update_engine/client.h> +#include <vector> + +#include "constants.h" +#include "persistent_integer.h" +#include "uploader/metrics_log_base.h" +#include "uploader/proto/chrome_user_metrics_extension.pb.h" + +namespace { + +const char kPersistentSessionIdFilename[] = "Sysinfo.SessionId"; + +} // namespace + +std::string ChannelToString( + const metrics::SystemProfileProto_Channel& channel) { + switch (channel) { + case metrics::SystemProfileProto::CHANNEL_STABLE: + return "STABLE"; + case metrics::SystemProfileProto::CHANNEL_DEV: + return "DEV"; + case metrics::SystemProfileProto::CHANNEL_BETA: + return "BETA"; + case metrics::SystemProfileProto::CHANNEL_CANARY: + return "CANARY"; + default: + return "UNKNOWN"; + } +} + +SystemProfileCache::SystemProfileCache() + : initialized_(false), + testing_(false), + metrics_directory_(metrics::kMetricsdDirectory), + session_id_(new chromeos_metrics::PersistentInteger( + kPersistentSessionIdFilename, metrics_directory_)) {} + +SystemProfileCache::SystemProfileCache(bool testing, + const base::FilePath& metrics_directory) + : initialized_(false), + testing_(testing), + metrics_directory_(metrics_directory), + session_id_(new chromeos_metrics::PersistentInteger( + kPersistentSessionIdFilename, metrics_directory)) {} + +bool SystemProfileCache::Initialize() { + CHECK(!initialized_) + << "this should be called only once in the metrics_daemon lifetime."; + + brillo::OsReleaseReader reader; + std::string channel; + if (testing_) { + reader.LoadTestingOnly(metrics_directory_); + channel = "unknown"; + } else { + reader.Load(); + auto client = update_engine::UpdateEngineClient::CreateInstance(); + if (!client) { + LOG(ERROR) << "failed to create the update engine client"; + return false; + } + if (!client->GetChannel(&channel)) { + LOG(ERROR) << "failed to read the current channel from update engine."; + return false; + } + } + + if (!reader.GetString(metrics::kProductId, &profile_.product_id) + || profile_.product_id.empty()) { + LOG(ERROR) << "product_id is not set."; + return false; + } + + if (!reader.GetString(metrics::kProductVersion, &profile_.version)) { + LOG(ERROR) << "failed to read the product version"; + } + + if (channel.empty() || profile_.version.empty()) { + // If the channel or version is missing, the image is not official. + // In this case, set the channel to unknown and the version to 0.0.0.0 to + // avoid polluting the production data. + channel = ""; + profile_.version = metrics::kDefaultVersion; + } + std::string guid_path = metrics_directory_.Append( + metrics::kMetricsGUIDFileName).value(); + profile_.client_id = testing_ ? + "client_id_test" : + GetPersistentGUID(guid_path); + profile_.model_manifest_id = "unknown"; + if (!testing_) { + brillo::KeyValueStore weave_config; + if (!weave_config.Load(base::FilePath(metrics::kWeaveConfigurationFile))) { + LOG(ERROR) << "Failed to load the weave configuration file."; + } else if (!weave_config.GetString(metrics::kModelManifestId, + &profile_.model_manifest_id)) { + LOG(ERROR) << "The model manifest id (model_id) is undefined in " + << metrics::kWeaveConfigurationFile; + } + } + + profile_.channel = ProtoChannelFromString(channel); + + // Increment the session_id everytime we initialize this. If metrics_daemon + // does not crash, this should correspond to the number of reboots of the + // system. + session_id_->Add(1); + profile_.session_id = static_cast<int32_t>(session_id_->Get()); + + initialized_ = true; + return initialized_; +} + +bool SystemProfileCache::InitializeOrCheck() { + return initialized_ || Initialize(); +} + +bool SystemProfileCache::Populate( + metrics::ChromeUserMetricsExtension* metrics_proto) { + CHECK(metrics_proto); + if (not InitializeOrCheck()) { + return false; + } + + // The client id is hashed before being sent. + metrics_proto->set_client_id( + metrics::MetricsLogBase::Hash(profile_.client_id)); + metrics_proto->set_session_id(profile_.session_id); + + // Sets the product id. + metrics_proto->set_product(9); + + metrics::SystemProfileProto* profile_proto = + metrics_proto->mutable_system_profile(); + profile_proto->mutable_hardware()->set_hardware_class( + profile_.model_manifest_id); + profile_proto->set_app_version(profile_.version); + profile_proto->set_channel(profile_.channel); + metrics::SystemProfileProto_BrilloDeviceData* device_data = + profile_proto->mutable_brillo(); + device_data->set_product_id(profile_.product_id); + + return true; +} + +std::string SystemProfileCache::GetPersistentGUID( + const std::string& filename) { + std::string guid; + base::FilePath filepath(filename); + if (!base::ReadFileToString(filepath, &guid)) { + guid = base::GenerateGUID(); + // If we can't read or write the file, the guid will not be preserved during + // the next reboot. Crash. + CHECK(base::WriteFile(filepath, guid.c_str(), guid.size())); + } + return guid; +} + +metrics::SystemProfileProto_Channel SystemProfileCache::ProtoChannelFromString( + const std::string& channel) { + if (channel == "stable-channel") { + return metrics::SystemProfileProto::CHANNEL_STABLE; + } else if (channel == "dev-channel") { + return metrics::SystemProfileProto::CHANNEL_DEV; + } else if (channel == "beta-channel") { + return metrics::SystemProfileProto::CHANNEL_BETA; + } else if (channel == "canary-channel") { + return metrics::SystemProfileProto::CHANNEL_CANARY; + } + + DLOG(INFO) << "unknown channel: " << channel; + return metrics::SystemProfileProto::CHANNEL_UNKNOWN; +} diff --git a/uploader/system_profile_cache.h b/uploader/system_profile_cache.h new file mode 100644 index 0000000..f9c484c --- /dev/null +++ b/uploader/system_profile_cache.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_ +#define METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_ + +#include <stdint.h> + +#include <memory> +#include <string> + +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/gtest_prod_util.h" +#include "persistent_integer.h" +#include "uploader/proto/system_profile.pb.h" +#include "uploader/system_profile_setter.h" + +namespace metrics { +class ChromeUserMetricsExtension; +} + +struct SystemProfile { + std::string version; + std::string model_manifest_id; + std::string client_id; + int session_id; + metrics::SystemProfileProto::Channel channel; + std::string product_id; +}; + +// Retrieves general system informations needed by the protobuf for context and +// remembers them to avoid expensive calls. +// +// The cache is populated lazily. The only method needed is Populate. +class SystemProfileCache : public SystemProfileSetter { + public: + SystemProfileCache(); + + SystemProfileCache(bool testing, const base::FilePath& metrics_directory); + + // Populates the ProfileSystem protobuf with system information. + bool Populate(metrics::ChromeUserMetricsExtension* metrics_proto) override; + + // Converts a string representation of the channel to a + // SystemProfileProto_Channel + static metrics::SystemProfileProto_Channel ProtoChannelFromString( + const std::string& channel); + + // Gets the persistent GUID and create it if it has not been created yet. + static std::string GetPersistentGUID(const std::string& filename); + + private: + friend class UploadServiceTest; + FRIEND_TEST(UploadServiceTest, ExtractChannelFromDescription); + FRIEND_TEST(UploadServiceTest, ReadKeyValueFromFile); + FRIEND_TEST(UploadServiceTest, SessionIdIncrementedAtInitialization); + FRIEND_TEST(UploadServiceTest, ValuesInConfigFileAreSent); + FRIEND_TEST(UploadServiceTest, ProductIdMandatory); + + // Fetches all informations and populates |profile_| + bool Initialize(); + + // Initializes |profile_| only if it has not been yet initialized. + bool InitializeOrCheck(); + + bool initialized_; + bool testing_; + base::FilePath metrics_directory_; + std::unique_ptr<chromeos_metrics::PersistentInteger> session_id_; + SystemProfile profile_; +}; + +#endif // METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_ diff --git a/uploader/system_profile_setter.h b/uploader/system_profile_setter.h new file mode 100644 index 0000000..bd3ff42 --- /dev/null +++ b/uploader/system_profile_setter.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICS_UPLOADER_SYSTEM_PROFILE_SETTER_H_ +#define METRICS_UPLOADER_SYSTEM_PROFILE_SETTER_H_ + +namespace metrics { +class ChromeUserMetricsExtension; +} + +// Abstract class used to delegate populating SystemProfileProto with system +// information to simplify testing. +class SystemProfileSetter { + public: + virtual ~SystemProfileSetter() {} + // Populates the protobuf with system informations. + virtual bool Populate(metrics::ChromeUserMetricsExtension* profile_proto) = 0; +}; + +#endif // METRICS_UPLOADER_SYSTEM_PROFILE_SETTER_H_ diff --git a/uploader/upload_service.cc b/uploader/upload_service.cc new file mode 100644 index 0000000..333a7e6 --- /dev/null +++ b/uploader/upload_service.cc @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uploader/upload_service.h" + +#include <sysexits.h> + +#include <memory> +#include <string> + +#include <base/bind.h> +#include <base/files/file_util.h> +#include <base/logging.h> +#include <base/memory/scoped_vector.h> +#include <base/message_loop/message_loop.h> +#include <base/metrics/histogram.h> +#include <base/metrics/histogram_base.h> +#include <base/metrics/histogram_snapshot_manager.h> +#include <base/metrics/sparse_histogram.h> +#include <base/metrics/statistics_recorder.h> +#include <base/sha1.h> + +#include "constants.h" +#include "uploader/metrics_log.h" +#include "uploader/sender_http.h" +#include "uploader/system_profile_setter.h" + +const int UploadService::kMaxFailedUpload = 10; + +UploadService::UploadService(const std::string& server, + const base::TimeDelta& upload_interval, + const base::TimeDelta& disk_persistence_interval, + const base::FilePath& private_metrics_directory, + const base::FilePath& shared_metrics_directory) + : brillo::Daemon(), + histogram_snapshot_manager_(this), + sender_(new HttpSender(server)), + failed_upload_count_(metrics::kFailedUploadCountName, + private_metrics_directory), + counters_(new CrashCounters), + upload_interval_(upload_interval), + disk_persistence_interval_(disk_persistence_interval), + metricsd_service_runner_(counters_) { + staged_log_path_ = private_metrics_directory.Append(metrics::kStagedLogName); + saved_log_path_ = private_metrics_directory.Append(metrics::kSavedLogName); + consent_file_ = shared_metrics_directory.Append(metrics::kConsentFileName); +} + +void UploadService::LoadSavedLog() { + if (base::PathExists(saved_log_path_)) { + GetOrCreateCurrentLog()->LoadFromFile(saved_log_path_); + } +} + +int UploadService::OnInit() { + brillo::Daemon::OnInit(); + + base::StatisticsRecorder::Initialize(); + metricsd_service_runner_.Start(); + + system_profile_setter_.reset(new SystemProfileCache()); + + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&UploadService::UploadEventCallback, base::Unretained(this)), + upload_interval_); + + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&UploadService::PersistEventCallback, base::Unretained(this)), + disk_persistence_interval_); + + LoadSavedLog(); + + return EX_OK; +} + +void UploadService::OnShutdown(int* exit_code) { + metricsd_service_runner_.Stop(); + PersistToDisk(); +} + +void UploadService::InitForTest(SystemProfileSetter* setter) { + LoadSavedLog(); + system_profile_setter_.reset(setter); +} + +void UploadService::StartNewLog() { + current_log_.reset(new MetricsLog()); +} + +void UploadService::UploadEventCallback() { + UploadEvent(); + + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&UploadService::UploadEventCallback, base::Unretained(this)), + upload_interval_); +} + +void UploadService::PersistEventCallback() { + PersistToDisk(); + + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&UploadService::PersistEventCallback, base::Unretained(this)), + disk_persistence_interval_); +} + +void UploadService::PersistToDisk() { + GatherHistograms(); + if (current_log_) { + current_log_->SaveToFile(saved_log_path_); + } +} + +void UploadService::UploadEvent() { + // If the system shutdown or crashed while uploading a report, we may not have + // deleted an old log. + RemoveFailedLog(); + + if (HasStagedLog()) { + // Previous upload failed, retry sending the logs. + SendStagedLog(); + return; + } + + // Previous upload successful, stage another log. + GatherHistograms(); + StageCurrentLog(); + + // If a log is available for upload, upload it. + if (HasStagedLog()) { + SendStagedLog(); + } +} + +void UploadService::SendStagedLog() { + // If metrics are not enabled, discard the log and exit. + if (!AreMetricsEnabled()) { + LOG(INFO) << "Metrics disabled. Don't upload metrics samples."; + base::DeleteFile(staged_log_path_, false); + return; + } + + std::string staged_log; + CHECK(base::ReadFileToString(staged_log_path_, &staged_log)); + + // Increase the failed count in case the daemon crashes while sending the log. + failed_upload_count_.Add(1); + + if (!sender_->Send(staged_log, base::SHA1HashString(staged_log))) { + LOG(WARNING) << "log failed to upload"; + } else { + VLOG(1) << "uploaded " << staged_log.length() << " bytes"; + base::DeleteFile(staged_log_path_, false); + } + + RemoveFailedLog(); +} + +void UploadService::Reset() { + base::DeleteFile(staged_log_path_, false); + current_log_.reset(); + failed_upload_count_.Set(0); +} + +void UploadService::GatherHistograms() { + base::StatisticsRecorder::Histograms histograms; + base::StatisticsRecorder::GetHistograms(&histograms); + + histogram_snapshot_manager_.PrepareDeltas( + histograms.begin(), histograms.end(), + base::Histogram::kNoFlags, base::Histogram::kUmaTargetedHistogramFlag); + + // Gather and reset the crash counters, shared with the binder threads. + unsigned int kernel_crashes = counters_->GetAndResetKernelCrashCount(); + unsigned int unclean_shutdowns = counters_->GetAndResetUncleanShutdownCount(); + unsigned int user_crashes = counters_->GetAndResetUserCrashCount(); + + // Only create a log if the counters have changed. + if (kernel_crashes > 0 || unclean_shutdowns > 0 || user_crashes > 0) { + GetOrCreateCurrentLog()->IncrementKernelCrashCount(kernel_crashes); + GetOrCreateCurrentLog()->IncrementUncleanShutdownCount(unclean_shutdowns); + GetOrCreateCurrentLog()->IncrementUserCrashCount(user_crashes); + } +} + +void UploadService::RecordDelta(const base::HistogramBase& histogram, + const base::HistogramSamples& snapshot) { + GetOrCreateCurrentLog()->RecordHistogramDelta(histogram.histogram_name(), + snapshot); +} + +void UploadService::StageCurrentLog() { + // If we haven't logged anything since the last upload, don't upload an empty + // report. + if (!current_log_) + return; + + std::unique_ptr<MetricsLog> staged_log; + staged_log.swap(current_log_); + staged_log->CloseLog(); + if (!staged_log->PopulateSystemProfile(system_profile_setter_.get())) { + LOG(WARNING) << "Error while adding metadata to the log. Discarding the " + << "log."; + return; + } + + if (!base::DeleteFile(saved_log_path_, false)) { + // There is a chance that we will upload the same metrics twice but, if we + // are lucky, the backup should be overridden before that. In doubt, try not + // to lose any metrics. + LOG(ERROR) << "failed to delete the last backup of the current log."; + } + + failed_upload_count_.Set(0); + staged_log->SaveToFile(staged_log_path_); +} + +MetricsLog* UploadService::GetOrCreateCurrentLog() { + if (!current_log_) { + StartNewLog(); + } + return current_log_.get(); +} + +bool UploadService::HasStagedLog() { + return base::PathExists(staged_log_path_); +} + +void UploadService::RemoveFailedLog() { + if (failed_upload_count_.Get() > kMaxFailedUpload) { + LOG(INFO) << "log failed more than " << kMaxFailedUpload << " times."; + CHECK(base::DeleteFile(staged_log_path_, false)) + << "failed to delete staged log at " << staged_log_path_.value(); + failed_upload_count_.Set(0); + } +} + +bool UploadService::AreMetricsEnabled() { + return base::PathExists(consent_file_); +} diff --git a/uploader/upload_service.h b/uploader/upload_service.h new file mode 100644 index 0000000..a1d9d3b --- /dev/null +++ b/uploader/upload_service.h @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef METRICS_UPLOADER_UPLOAD_SERVICE_H_ +#define METRICS_UPLOADER_UPLOAD_SERVICE_H_ + +#include <memory> +#include <string> + +#include <base/metrics/histogram_base.h> +#include <base/metrics/histogram_flattener.h> +#include <base/metrics/histogram_snapshot_manager.h> +#include <brillo/daemons/daemon.h> + +#include "persistent_integer.h" +#include "uploader/crash_counters.h" +#include "uploader/metrics_log.h" +#include "uploader/metricsd_service_runner.h" +#include "uploader/proto/chrome_user_metrics_extension.pb.h" +#include "uploader/sender.h" +#include "uploader/system_profile_cache.h" + +class SystemProfileSetter; + +// Service responsible for backing up the currently aggregated metrics to disk +// and uploading them periodically to the server. +// +// A given metrics sample can be in one of three locations. +// * in-memory metrics: in memory aggregated metrics, waiting to be staged for +// upload. +// * saved log: protobuf message, written to disk periodically and on shutdown +// to make a backup of metrics data for uploading later. +// * staged log: protobuf message waiting to be uploaded. +// +// The service works as follows: +// On startup, we create the in-memory metrics from the saved log if it exists. +// +// Periodically (every |disk_persistence_interval_| seconds), we take a snapshot +// of the in-memory metrics and save them to disk. +// +// Periodically (every |upload_interval| seconds), we: +// * take a snapshot of the in-memory metrics and create the staged log +// * save the staged log to disk to avoid losing it if metricsd or the system +// crashes between two uploads. +// * delete the last saved log: all the metrics contained in it are also in the +// newly created staged log. +// +// On shutdown (SIGINT or SIGTERM), we save the in-memory metrics to disk. +// +// Note: the in-memory metrics can be stored in |current_log_| or +// base::StatisticsRecorder. +class UploadService : public base::HistogramFlattener, public brillo::Daemon { + public: + UploadService(const std::string& server, + const base::TimeDelta& upload_interval, + const base::TimeDelta& disk_persistence_interval, + const base::FilePath& private_metrics_directory, + const base::FilePath& shared_metrics_directory); + + // Initializes the upload service. + int OnInit() override; + + // Cleans up the internal state before exiting. + void OnShutdown(int* exit_code) override; + + // Starts a new log. The log needs to be regenerated after each successful + // launch as it is destroyed when staging the log. + void StartNewLog(); + + // Saves the current metrics to a file. + void PersistToDisk(); + + // Triggers an upload event. + void UploadEvent(); + + // Sends the staged log. + void SendStagedLog(); + + // Implements inconsistency detection to match HistogramFlattener's + // interface. + void InconsistencyDetected( + base::HistogramBase::Inconsistency problem) override {} + void UniqueInconsistencyDetected( + base::HistogramBase::Inconsistency problem) override {} + void InconsistencyDetectedInLoggedCount(int amount) override {} + + private: + friend class UploadServiceTest; + + FRIEND_TEST(UploadServiceTest, CanSendMultipleTimes); + FRIEND_TEST(UploadServiceTest, CorruptedSavedLog); + FRIEND_TEST(UploadServiceTest, CurrentLogSavedAndResumed); + FRIEND_TEST(UploadServiceTest, DiscardLogsAfterTooManyFailedUpload); + FRIEND_TEST(UploadServiceTest, EmptyLogsAreNotSent); + FRIEND_TEST(UploadServiceTest, FailedSendAreRetried); + FRIEND_TEST(UploadServiceTest, LogContainsAggregatedValues); + FRIEND_TEST(UploadServiceTest, LogContainsCrashCounts); + FRIEND_TEST(UploadServiceTest, LogEmptyAfterUpload); + FRIEND_TEST(UploadServiceTest, LogEmptyByDefault); + FRIEND_TEST(UploadServiceTest, LogFromTheMetricsLibrary); + FRIEND_TEST(UploadServiceTest, LogKernelCrash); + FRIEND_TEST(UploadServiceTest, LogUncleanShutdown); + FRIEND_TEST(UploadServiceTest, LogUserCrash); + FRIEND_TEST(UploadServiceTest, PersistEmptyLog); + FRIEND_TEST(UploadServiceTest, UnknownCrashIgnored); + FRIEND_TEST(UploadServiceTest, ValuesInConfigFileAreSent); + + // Initializes the upload service for testing. + void InitForTest(SystemProfileSetter* setter); + + // If a staged log fails to upload more than kMaxFailedUpload times, it + // will be discarded. + static const int kMaxFailedUpload; + + // Loads the log saved to disk if it exists. + void LoadSavedLog(); + + // Resets the internal state. + void Reset(); + + // Returns true iff metrics reporting is enabled. + bool AreMetricsEnabled(); + + // Event callback for handling Upload events. + void UploadEventCallback(); + + // Event callback for handling Persist events. + void PersistEventCallback(); + + // Aggregates all histogram available in memory and store them in the current + // log. + void GatherHistograms(); + + // Callback for HistogramSnapshotManager to store the histograms. + void RecordDelta(const base::HistogramBase& histogram, + const base::HistogramSamples& snapshot) override; + + // Compiles all the samples received into a single protobuf and adds all + // system information. + void StageCurrentLog(); + + // Returns true iff a log is staged. + bool HasStagedLog(); + + // Remove the staged log iff the upload failed more than |kMaxFailedUpload|. + void RemoveFailedLog(); + + // Returns the current log. If there is no current log, creates it first. + MetricsLog* GetOrCreateCurrentLog(); + + std::unique_ptr<SystemProfileSetter> system_profile_setter_; + base::HistogramSnapshotManager histogram_snapshot_manager_; + std::unique_ptr<Sender> sender_; + chromeos_metrics::PersistentInteger failed_upload_count_; + std::unique_ptr<MetricsLog> current_log_; + std::shared_ptr<CrashCounters> counters_; + + base::TimeDelta upload_interval_; + base::TimeDelta disk_persistence_interval_; + + MetricsdServiceRunner metricsd_service_runner_; + + base::FilePath consent_file_; + base::FilePath staged_log_path_; + base::FilePath saved_log_path_; + + bool testing_; +}; + +#endif // METRICS_UPLOADER_UPLOAD_SERVICE_H_ diff --git a/uploader/upload_service_test.cc b/uploader/upload_service_test.cc new file mode 100644 index 0000000..0f77fe4 --- /dev/null +++ b/uploader/upload_service_test.cc @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <memory> + +#include <base/at_exit.h> +#include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.h> +#include <base/logging.h> +#include <base/metrics/sparse_histogram.h> +#include <base/metrics/statistics_recorder.h> +#include <base/sys_info.h> +#include <gtest/gtest.h> + +#include "constants.h" +#include "persistent_integer.h" +#include "uploader/metrics_log.h" +#include "uploader/mock/mock_system_profile_setter.h" +#include "uploader/mock/sender_mock.h" +#include "uploader/proto/chrome_user_metrics_extension.pb.h" +#include "uploader/proto/histogram_event.pb.h" +#include "uploader/proto/system_profile.pb.h" +#include "uploader/system_profile_cache.h" +#include "uploader/upload_service.h" + +class UploadServiceTest : public testing::Test { + protected: + virtual void SetUp() { + CHECK(dir_.CreateUniqueTempDir()); + // Make sure the statistics recorder is inactive (contains no metrics) then + // initialize it. + ASSERT_FALSE(base::StatisticsRecorder::IsActive()); + base::StatisticsRecorder::Initialize(); + + private_dir_ = dir_.path().Append("private"); + shared_dir_ = dir_.path().Append("shared"); + + EXPECT_TRUE(base::CreateDirectory(private_dir_)); + EXPECT_TRUE(base::CreateDirectory(shared_dir_)); + + ASSERT_EQ(0, base::WriteFile(shared_dir_.Append(metrics::kConsentFileName), + "", 0)); + + upload_service_.reset(new UploadService( + "", base::TimeDelta(), base::TimeDelta(), private_dir_, shared_dir_)); + counters_ = upload_service_->counters_; + + upload_service_->sender_.reset(new SenderMock); + upload_service_->InitForTest(new MockSystemProfileSetter); + upload_service_->GatherHistograms(); + upload_service_->Reset(); + } + + void SendSparseHistogram(const std::string& name, int sample) { + base::HistogramBase* histogram = base::SparseHistogram::FactoryGet( + name, base::Histogram::kUmaTargetedHistogramFlag); + histogram->Add(sample); + } + + void SendHistogram( + const std::string& name, int sample, int min, int max, int nbuckets) { + base::HistogramBase* histogram = base::Histogram::FactoryGet( + name, min, max, nbuckets, base::Histogram::kUmaTargetedHistogramFlag); + histogram->Add(sample); + } + + void SetTestingProperty(const std::string& name, const std::string& value) { + base::FilePath filepath = + dir_.path().Append("etc/os-release.d").Append(name); + ASSERT_TRUE(base::CreateDirectory(filepath.DirName())); + ASSERT_EQ(value.size(), + base::WriteFile(filepath, value.data(), value.size())); + } + + const metrics::SystemProfileProto_Stability GetCurrentStability() { + EXPECT_TRUE(upload_service_->current_log_.get()); + + return upload_service_->current_log_->uma_proto() + ->system_profile() + .stability(); + } + + base::ScopedTempDir dir_; + std::unique_ptr<UploadService> upload_service_; + + std::unique_ptr<base::AtExitManager> exit_manager_; + std::shared_ptr<CrashCounters> counters_; + base::FilePath private_dir_; + base::FilePath shared_dir_; +}; + +TEST_F(UploadServiceTest, FailedSendAreRetried) { + SenderMock* sender = new SenderMock(); + upload_service_->sender_.reset(sender); + + sender->set_should_succeed(false); + + SendSparseHistogram("hello", 1); + upload_service_->UploadEvent(); + EXPECT_EQ(1, sender->send_call_count()); + std::string sent_string = sender->last_message(); + + upload_service_->UploadEvent(); + EXPECT_EQ(2, sender->send_call_count()); + EXPECT_EQ(sent_string, sender->last_message()); +} + +TEST_F(UploadServiceTest, DiscardLogsAfterTooManyFailedUpload) { + SenderMock* sender = new SenderMock(); + upload_service_->sender_.reset(sender); + + sender->set_should_succeed(false); + + SendSparseHistogram("hello", 1); + + for (int i = 0; i < UploadService::kMaxFailedUpload; i++) { + upload_service_->UploadEvent(); + } + + EXPECT_TRUE(upload_service_->HasStagedLog()); + upload_service_->UploadEvent(); + EXPECT_FALSE(upload_service_->HasStagedLog()); + + // Log a new sample. The failed upload counter should be reset. + SendSparseHistogram("hello", 1); + for (int i = 0; i < UploadService::kMaxFailedUpload; i++) { + upload_service_->UploadEvent(); + } + // The log is not discarded after multiple failed uploads. + EXPECT_TRUE(upload_service_->HasStagedLog()); +} + +TEST_F(UploadServiceTest, EmptyLogsAreNotSent) { + SenderMock* sender = new SenderMock(); + upload_service_->sender_.reset(sender); + upload_service_->UploadEvent(); + EXPECT_FALSE(upload_service_->current_log_); + EXPECT_EQ(0, sender->send_call_count()); +} + +TEST_F(UploadServiceTest, LogEmptyByDefault) { + // current_log_ should be initialized later as it needs AtExitManager to exist + // in order to gather system information from SysInfo. + EXPECT_FALSE(upload_service_->current_log_); +} + +TEST_F(UploadServiceTest, CanSendMultipleTimes) { + SenderMock* sender = new SenderMock(); + upload_service_->sender_.reset(sender); + + SendSparseHistogram("hello", 1); + + upload_service_->UploadEvent(); + + std::string first_message = sender->last_message(); + SendSparseHistogram("hello", 2); + + upload_service_->UploadEvent(); + + EXPECT_NE(first_message, sender->last_message()); +} + +TEST_F(UploadServiceTest, LogEmptyAfterUpload) { + SendSparseHistogram("hello", 2); + + upload_service_->UploadEvent(); + EXPECT_FALSE(upload_service_->current_log_); +} + +TEST_F(UploadServiceTest, LogContainsAggregatedValues) { + SendHistogram("foo", 11, 0, 42, 10); + SendHistogram("foo", 12, 0, 42, 10); + + upload_service_->GatherHistograms(); + metrics::ChromeUserMetricsExtension* proto = + upload_service_->current_log_->uma_proto(); + EXPECT_EQ(1, proto->histogram_event().size()); +} + +TEST_F(UploadServiceTest, LogContainsCrashCounts) { + // By default, there is no current log. + upload_service_->GatherHistograms(); + EXPECT_FALSE(upload_service_->current_log_); + + // If the user crash counter is incremented, we add the count to the current + // log. + counters_->IncrementUserCrashCount(); + upload_service_->GatherHistograms(); + EXPECT_EQ(1, GetCurrentStability().other_user_crash_count()); + + // If the kernel crash counter is incremented, we add the count to the current + // log. + counters_->IncrementKernelCrashCount(); + upload_service_->GatherHistograms(); + EXPECT_EQ(1, GetCurrentStability().kernel_crash_count()); + + // If the kernel crash counter is incremented, we add the count to the current + // log. + counters_->IncrementUncleanShutdownCount(); + counters_->IncrementUncleanShutdownCount(); + upload_service_->GatherHistograms(); + EXPECT_EQ(2, GetCurrentStability().unclean_system_shutdown_count()); + + // If no counter is incremented, the reported numbers don't change. + upload_service_->GatherHistograms(); + EXPECT_EQ(1, GetCurrentStability().other_user_crash_count()); + EXPECT_EQ(1, GetCurrentStability().kernel_crash_count()); + EXPECT_EQ(2, GetCurrentStability().unclean_system_shutdown_count()); +} + +TEST_F(UploadServiceTest, ExtractChannelFromString) { + EXPECT_EQ(SystemProfileCache::ProtoChannelFromString("developer-build"), + metrics::SystemProfileProto::CHANNEL_UNKNOWN); + + EXPECT_EQ(metrics::SystemProfileProto::CHANNEL_DEV, + SystemProfileCache::ProtoChannelFromString("dev-channel")); + + EXPECT_EQ(metrics::SystemProfileProto::CHANNEL_STABLE, + SystemProfileCache::ProtoChannelFromString("stable-channel")); + + EXPECT_EQ(metrics::SystemProfileProto::CHANNEL_UNKNOWN, + SystemProfileCache::ProtoChannelFromString("this is a test")); +} + +TEST_F(UploadServiceTest, ValuesInConfigFileAreSent) { + SenderMock* sender = new SenderMock(); + upload_service_->sender_.reset(sender); + + SetTestingProperty(metrics::kProductId, "hello"); + SetTestingProperty(metrics::kProductVersion, "1.2.3.4"); + + SendSparseHistogram("hello", 1); + + // Reset to create the new log with the profile setter. + upload_service_->system_profile_setter_.reset( + new SystemProfileCache(true, dir_.path())); + upload_service_->Reset(); + upload_service_->UploadEvent(); + + EXPECT_EQ(1, sender->send_call_count()); + EXPECT_TRUE(sender->is_good_proto()); + EXPECT_EQ(1, sender->last_message_proto().histogram_event().size()); + + EXPECT_NE(0, sender->last_message_proto().client_id()); + EXPECT_NE(0, sender->last_message_proto().system_profile().build_timestamp()); + EXPECT_NE(0, sender->last_message_proto().session_id()); +} + +TEST_F(UploadServiceTest, PersistentGUID) { + std::string tmp_file = dir_.path().Append("tmpfile").value(); + + std::string first_guid = SystemProfileCache::GetPersistentGUID(tmp_file); + std::string second_guid = SystemProfileCache::GetPersistentGUID(tmp_file); + + // The GUID are cached. + EXPECT_EQ(first_guid, second_guid); + + base::DeleteFile(base::FilePath(tmp_file), false); + + first_guid = SystemProfileCache::GetPersistentGUID(tmp_file); + base::DeleteFile(base::FilePath(tmp_file), false); + second_guid = SystemProfileCache::GetPersistentGUID(tmp_file); + + // Random GUIDs are generated (not all the same). + EXPECT_NE(first_guid, second_guid); +} + +TEST_F(UploadServiceTest, SessionIdIncrementedAtInitialization) { + SetTestingProperty(metrics::kProductId, "hello"); + SystemProfileCache cache(true, dir_.path()); + cache.Initialize(); + int session_id = cache.profile_.session_id; + cache.initialized_ = false; + cache.Initialize(); + EXPECT_EQ(cache.profile_.session_id, session_id + 1); +} + +// The product id must be set for metrics to be uploaded. +// If it is not set, the system profile cache should fail to initialize. +TEST_F(UploadServiceTest, ProductIdMandatory) { + SystemProfileCache cache(true, dir_.path()); + ASSERT_FALSE(cache.Initialize()); + SetTestingProperty(metrics::kProductId, ""); + ASSERT_FALSE(cache.Initialize()); + SetTestingProperty(metrics::kProductId, "hello"); + ASSERT_TRUE(cache.Initialize()); +} + +TEST_F(UploadServiceTest, CurrentLogSavedAndResumed) { + SendHistogram("hello", 10, 0, 100, 10); + upload_service_->PersistToDisk(); + EXPECT_EQ( + 1, upload_service_->current_log_->uma_proto()->histogram_event().size()); + // Destroy the old service before creating a new one. + upload_service_.reset(); + upload_service_.reset(new UploadService( + "", base::TimeDelta(), base::TimeDelta(), private_dir_, shared_dir_)); + upload_service_->InitForTest(nullptr); + + SendHistogram("hello", 10, 0, 100, 10); + upload_service_->GatherHistograms(); + EXPECT_EQ(2, upload_service_->GetOrCreateCurrentLog() + ->uma_proto() + ->histogram_event() + .size()); +} + +TEST_F(UploadServiceTest, PersistEmptyLog) { + upload_service_->PersistToDisk(); + EXPECT_FALSE(base::PathExists(upload_service_->saved_log_path_)); +} + +TEST_F(UploadServiceTest, CorruptedSavedLog) { + // Write a bogus saved log. + EXPECT_EQ(5, base::WriteFile(upload_service_->saved_log_path_, "hello", 5)); + + // Destroy the old service before creating a new one. + upload_service_.reset(); + upload_service_.reset(new UploadService( + "", base::TimeDelta(), base::TimeDelta(), private_dir_, shared_dir_)); + + upload_service_->InitForTest(nullptr); + // If the log is unreadable, we drop it and continue execution. + ASSERT_NE(nullptr, upload_service_->GetOrCreateCurrentLog()); + ASSERT_FALSE(base::PathExists(upload_service_->saved_log_path_)); +} |