Home | History | Annotate | Download | only in common
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/common/metrics_helpers.h"
      6 
      7 #if defined(USE_SYSTEM_LIBBZ2)
      8 #include <bzlib.h>
      9 #else
     10 #include "third_party/bzip2/bzlib.h"
     11 #endif
     12 
     13 #include "base/base64.h"
     14 #include "base/time.h"
     15 #include "base/basictypes.h"
     16 #include "base/file_util.h"
     17 #include "base/md5.h"
     18 #include "base/perftimer.h"
     19 #include "base/string_number_conversions.h"
     20 #include "base/sys_info.h"
     21 #include "base/utf_string_conversions.h"
     22 #include "base/third_party/nspr/prtime.h"
     23 #include "chrome/common/logging_chrome.h"
     24 #include "googleurl/src/gurl.h"
     25 #include "libxml/xmlwriter.h"
     26 
     27 #define OPEN_ELEMENT_FOR_SCOPE(name) ScopedElement scoped_element(this, name)
     28 
     29 using base::Histogram;
     30 using base::StatisticsRecorder;
     31 using base::Time;
     32 using base::TimeDelta;
     33 
     34 // http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx
     35 #if defined(OS_WIN)
     36 extern "C" IMAGE_DOS_HEADER __ImageBase;
     37 #endif
     38 
     39 namespace {
     40 
     41 // libxml take xmlChar*, which is unsigned char*
     42 inline const unsigned char* UnsignedChar(const char* input) {
     43   return reinterpret_cast<const unsigned char*>(input);
     44 }
     45 
     46 }  // namespace
     47 
     48 class MetricsLogBase::XmlWrapper {
     49  public:
     50   XmlWrapper()
     51       : doc_(NULL),
     52         buffer_(NULL),
     53         writer_(NULL) {
     54     buffer_ = xmlBufferCreate();
     55     CHECK(buffer_);
     56 
     57     #if defined(OS_CHROMEOS)
     58       writer_ = xmlNewTextWriterDoc(&doc_, /* compression */ 0);
     59     #else
     60       writer_ = xmlNewTextWriterMemory(buffer_, /* compression */ 0);
     61     #endif  // OS_CHROMEOS
     62     DCHECK(writer_);
     63 
     64     int result = xmlTextWriterSetIndent(writer_, 2);
     65     DCHECK_EQ(0, result);
     66   }
     67 
     68   ~XmlWrapper() {
     69     FreeDocWriter();
     70     if (buffer_) {
     71       xmlBufferFree(buffer_);
     72       buffer_ = NULL;
     73     }
     74   }
     75 
     76   void FreeDocWriter() {
     77     if (writer_) {
     78       xmlFreeTextWriter(writer_);
     79       writer_ = NULL;
     80     }
     81     if (doc_) {
     82       xmlFreeDoc(doc_);
     83       doc_ = NULL;
     84     }
     85   }
     86 
     87   xmlDocPtr doc() const { return doc_; }
     88   xmlTextWriterPtr writer() const { return writer_; }
     89   xmlBufferPtr buffer() const { return buffer_; }
     90 
     91  private:
     92   xmlDocPtr doc_;
     93   xmlBufferPtr buffer_;
     94   xmlTextWriterPtr writer_;
     95 };
     96 
     97 // static
     98 std::string MetricsLogBase::version_extension_;
     99 
    100 MetricsLogBase::MetricsLogBase(const std::string& client_id, int session_id,
    101                                const std::string& version_string)
    102     : start_time_(Time::Now()),
    103       client_id_(client_id),
    104       session_id_(base::IntToString(session_id)),
    105       locked_(false),
    106       xml_wrapper_(new XmlWrapper),
    107       num_events_(0) {
    108 
    109   StartElement("log");
    110   WriteAttribute("clientid", client_id_);
    111   WriteInt64Attribute("buildtime", GetBuildTime());
    112   WriteAttribute("appversion", version_string);
    113 }
    114 
    115 MetricsLogBase::~MetricsLogBase() {
    116   delete xml_wrapper_;
    117 }
    118 
    119 void MetricsLogBase::CloseLog() {
    120   DCHECK(!locked_);
    121   locked_ = true;
    122 
    123   int result = xmlTextWriterEndDocument(xml_wrapper_->writer());
    124   DCHECK_GE(result, 0);
    125 
    126   result = xmlTextWriterFlush(xml_wrapper_->writer());
    127   DCHECK_GE(result, 0);
    128 
    129 #if defined(OS_CHROMEOS)
    130   xmlNodePtr root = xmlDocGetRootElement(xml_wrapper_->doc());
    131   if (!hardware_class_.empty()) {
    132     // The hardware class is determined after the first ongoing log is
    133     // constructed, so this adds the root element's "hardwareclass"
    134     // attribute when the log is closed instead.
    135     xmlNewProp(root, UnsignedChar("hardwareclass"),
    136                UnsignedChar(hardware_class_.c_str()));
    137   }
    138 
    139   // Flattens the XML tree into a character buffer.
    140   PerfTimer dump_timer;
    141   result = xmlNodeDump(xml_wrapper_->buffer(), xml_wrapper_->doc(),
    142                        root, /* level */ 0, /* format */ 1);
    143   DCHECK_GE(result, 0);
    144   UMA_HISTOGRAM_TIMES("UMA.XMLNodeDumpTime", dump_timer.Elapsed());
    145 
    146   PerfTimer free_timer;
    147   xml_wrapper_->FreeDocWriter();
    148   UMA_HISTOGRAM_TIMES("UMA.XMLWriterDestructionTime", free_timer.Elapsed());
    149 #endif  // OS_CHROMEOS
    150 }
    151 
    152 int MetricsLogBase::GetEncodedLogSize() {
    153   DCHECK(locked_);
    154   CHECK(xml_wrapper_);
    155   CHECK(xml_wrapper_->buffer());
    156   return xml_wrapper_->buffer()->use;
    157 }
    158 
    159 bool MetricsLogBase::GetEncodedLog(char* buffer, int buffer_size) {
    160   DCHECK(locked_);
    161   if (buffer_size < GetEncodedLogSize())
    162     return false;
    163 
    164   memcpy(buffer, xml_wrapper_->buffer()->content, GetEncodedLogSize());
    165   return true;
    166 }
    167 
    168 std::string MetricsLogBase::GetEncodedLogString() {
    169   DCHECK(locked_);
    170   return std::string(reinterpret_cast<char*>(xml_wrapper_->buffer()->content));
    171 }
    172 
    173 int MetricsLogBase::GetElapsedSeconds() {
    174   return static_cast<int>((Time::Now() - start_time_).InSeconds());
    175 }
    176 
    177 std::string MetricsLogBase::CreateHash(const std::string& value) {
    178   MD5Context ctx;
    179   MD5Init(&ctx);
    180   MD5Update(&ctx, value.data(), value.length());
    181 
    182   MD5Digest digest;
    183   MD5Final(&digest, &ctx);
    184 
    185   uint64 reverse_uint64;
    186   // UMA only uses first 8 chars of hash. We use the above uint64 instead
    187   // of a unsigned char[8] so that we don't run into strict aliasing issues
    188   // in the LOG statement below when trying to interpret reverse as a uint64.
    189   unsigned char* reverse = reinterpret_cast<unsigned char *>(&reverse_uint64);
    190   DCHECK(arraysize(digest.a) >= sizeof(reverse_uint64));
    191   for (size_t i = 0; i < sizeof(reverse_uint64); ++i)
    192     reverse[i] = digest.a[sizeof(reverse_uint64) - i - 1];
    193   // The following log is VERY helpful when folks add some named histogram into
    194   // the code, but forgot to update the descriptive list of histograms.  When
    195   // that happens, all we get to see (server side) is a hash of the histogram
    196   // name.  We can then use this logging to find out what histogram name was
    197   // being hashed to a given MD5 value by just running the version of Chromium
    198   // in question with --enable-logging.
    199   VLOG(1) << "Metrics: Hash numeric [" << value
    200           << "]=[" << reverse_uint64 << "]";
    201   return std::string(reinterpret_cast<char*>(digest.a), arraysize(digest.a));
    202 }
    203 
    204 std::string MetricsLogBase::CreateBase64Hash(const std::string& string) {
    205   std::string encoded_digest;
    206   if (base::Base64Encode(CreateHash(string), &encoded_digest)) {
    207     DVLOG(1) << "Metrics: Hash [" << encoded_digest << "]=[" << string << "]";
    208     return encoded_digest;
    209   }
    210   return std::string();
    211 }
    212 
    213 void MetricsLogBase::RecordUserAction(const char* key) {
    214   DCHECK(!locked_);
    215 
    216   std::string command_hash = CreateBase64Hash(key);
    217   if (command_hash.empty()) {
    218     NOTREACHED() << "Unable generate encoded hash of command: " << key;
    219     return;
    220   }
    221 
    222   OPEN_ELEMENT_FOR_SCOPE("uielement");
    223   WriteAttribute("action", "command");
    224   WriteAttribute("targetidhash", command_hash);
    225 
    226   // TODO(jhughes): Properly track windows.
    227   WriteIntAttribute("window", 0);
    228   WriteCommonEventAttributes();
    229 
    230   ++num_events_;
    231 }
    232 
    233 void MetricsLogBase::RecordLoadEvent(int window_id,
    234                                  const GURL& url,
    235                                  PageTransition::Type origin,
    236                                  int session_index,
    237                                  TimeDelta load_time) {
    238   DCHECK(!locked_);
    239 
    240   OPEN_ELEMENT_FOR_SCOPE("document");
    241   WriteAttribute("action", "load");
    242   WriteIntAttribute("docid", session_index);
    243   WriteIntAttribute("window", window_id);
    244   WriteAttribute("loadtime", base::Int64ToString(load_time.InMilliseconds()));
    245 
    246   std::string origin_string;
    247 
    248   switch (PageTransition::StripQualifier(origin)) {
    249     // TODO(jhughes): Some of these mappings aren't right... we need to add
    250     // some values to the server's enum.
    251     case PageTransition::LINK:
    252     case PageTransition::MANUAL_SUBFRAME:
    253       origin_string = "link";
    254       break;
    255 
    256     case PageTransition::TYPED:
    257       origin_string = "typed";
    258       break;
    259 
    260     case PageTransition::AUTO_BOOKMARK:
    261       origin_string = "bookmark";
    262       break;
    263 
    264     case PageTransition::AUTO_SUBFRAME:
    265     case PageTransition::RELOAD:
    266       origin_string = "refresh";
    267       break;
    268 
    269     case PageTransition::GENERATED:
    270     case PageTransition::KEYWORD:
    271       origin_string = "global-history";
    272       break;
    273 
    274     case PageTransition::START_PAGE:
    275       origin_string = "start-page";
    276       break;
    277 
    278     case PageTransition::FORM_SUBMIT:
    279       origin_string = "form-submit";
    280       break;
    281 
    282     default:
    283       NOTREACHED() << "Received an unknown page transition type: " <<
    284                        PageTransition::StripQualifier(origin);
    285   }
    286   if (!origin_string.empty())
    287     WriteAttribute("origin", origin_string);
    288 
    289   WriteCommonEventAttributes();
    290 
    291   ++num_events_;
    292 }
    293 
    294 void MetricsLogBase::RecordWindowEvent(WindowEventType type,
    295                                    int window_id,
    296                                    int parent_id) {
    297   DCHECK(!locked_);
    298 
    299   OPEN_ELEMENT_FOR_SCOPE("window");
    300   WriteAttribute("action", WindowEventTypeToString(type));
    301   WriteAttribute("windowid", base::IntToString(window_id));
    302   if (parent_id >= 0)
    303     WriteAttribute("parent", base::IntToString(parent_id));
    304   WriteCommonEventAttributes();
    305 
    306   ++num_events_;
    307 }
    308 
    309 std::string MetricsLogBase::GetCurrentTimeString() {
    310   return base::Uint64ToString(Time::Now().ToTimeT());
    311 }
    312 
    313 // These are the attributes that are common to every event.
    314 void MetricsLogBase::WriteCommonEventAttributes() {
    315   WriteAttribute("session", session_id_);
    316   WriteAttribute("time", GetCurrentTimeString());
    317 }
    318 
    319 void MetricsLogBase::WriteAttribute(const std::string& name,
    320                                 const std::string& value) {
    321   DCHECK(!locked_);
    322   DCHECK(!name.empty());
    323 
    324   int result = xmlTextWriterWriteAttribute(xml_wrapper_->writer(),
    325                                            UnsignedChar(name.c_str()),
    326                                            UnsignedChar(value.c_str()));
    327   DCHECK_GE(result, 0);
    328 }
    329 
    330 void MetricsLogBase::WriteIntAttribute(const std::string& name, int value) {
    331   WriteAttribute(name, base::IntToString(value));
    332 }
    333 
    334 void MetricsLogBase::WriteInt64Attribute(const std::string& name, int64 value) {
    335   WriteAttribute(name, base::Int64ToString(value));
    336 }
    337 
    338 // static
    339 const char* MetricsLogBase::WindowEventTypeToString(WindowEventType type) {
    340   switch (type) {
    341     case WINDOW_CREATE:  return "create";
    342     case WINDOW_OPEN:    return "open";
    343     case WINDOW_CLOSE:   return "close";
    344     case WINDOW_DESTROY: return "destroy";
    345 
    346     default:
    347       NOTREACHED();
    348       return "unknown";  // Can't return NULL as this is used in a required
    349                          // attribute.
    350   }
    351 }
    352 
    353 void MetricsLogBase::StartElement(const char* name) {
    354   DCHECK(!locked_);
    355   DCHECK(name);
    356 
    357   int result = xmlTextWriterStartElement(xml_wrapper_->writer(),
    358                                          UnsignedChar(name));
    359   DCHECK_GE(result, 0);
    360 }
    361 
    362 void MetricsLogBase::EndElement() {
    363   DCHECK(!locked_);
    364 
    365   int result = xmlTextWriterEndElement(xml_wrapper_->writer());
    366   DCHECK_GE(result, 0);
    367 }
    368 
    369 // static
    370 int64 MetricsLogBase::GetBuildTime() {
    371   static int64 integral_build_time = 0;
    372   if (!integral_build_time) {
    373     Time time;
    374     const char* kDateTime = __DATE__ " " __TIME__ " GMT";
    375     bool result = Time::FromString(ASCIIToWide(kDateTime).c_str(), &time);
    376     DCHECK(result);
    377     integral_build_time = static_cast<int64>(time.ToTimeT());
    378   }
    379   return integral_build_time;
    380 }
    381 
    382 MetricsLog* MetricsLogBase::AsMetricsLog() {
    383   return NULL;
    384 }
    385 
    386 // TODO(JAR): A The following should really be part of the histogram class.
    387 // Internal state is being needlessly exposed, and it would be hard to reuse
    388 // this code. If we moved this into the Histogram class, then we could use
    389 // the same infrastructure for logging StatsCounters, RatesCounters, etc.
    390 void MetricsLogBase::RecordHistogramDelta(
    391     const Histogram& histogram,
    392     const Histogram::SampleSet& snapshot) {
    393   DCHECK(!locked_);
    394   DCHECK_NE(0, snapshot.TotalCount());
    395   snapshot.CheckSize(histogram);
    396 
    397   // We will ignore the MAX_INT/infinite value in the last element of range[].
    398 
    399   OPEN_ELEMENT_FOR_SCOPE("histogram");
    400 
    401   WriteAttribute("name", CreateBase64Hash(histogram.histogram_name()));
    402 
    403   WriteInt64Attribute("sum", snapshot.sum());
    404   // TODO(jar): Remove sumsquares when protobuffer accepts this as optional.
    405   WriteInt64Attribute("sumsquares", 0);
    406 
    407   for (size_t i = 0; i < histogram.bucket_count(); i++) {
    408     if (snapshot.counts(i)) {
    409       OPEN_ELEMENT_FOR_SCOPE("histogrambucket");
    410       WriteIntAttribute("min", histogram.ranges(i));
    411       WriteIntAttribute("max", histogram.ranges(i + 1));
    412       WriteIntAttribute("count", snapshot.counts(i));
    413     }
    414   }
    415 }
    416 
    417 
    418 // MetricsServiceBase
    419 MetricsServiceBase::MetricsServiceBase()
    420     : pending_log_(NULL),
    421       compressed_log_(),
    422       current_log_(NULL) {
    423 }
    424 
    425 MetricsServiceBase::~MetricsServiceBase() {
    426   if (pending_log_) {
    427     delete pending_log_;
    428     pending_log_ = NULL;
    429   }
    430   if (current_log_) {
    431     delete current_log_;
    432     current_log_ = NULL;
    433   }
    434 }
    435 
    436 // This implementation is based on the Firefox MetricsService implementation.
    437 bool MetricsServiceBase::Bzip2Compress(const std::string& input,
    438                                        std::string* output) {
    439   bz_stream stream = {0};
    440   // As long as our input is smaller than the bzip2 block size, we should get
    441   // the best compression.  For example, if your input was 250k, using a block
    442   // size of 300k or 500k should result in the same compression ratio.  Since
    443   // our data should be under 100k, using the minimum block size of 100k should
    444   // allocate less temporary memory, but result in the same compression ratio.
    445   int result = BZ2_bzCompressInit(&stream,
    446                                   1,   // 100k (min) block size
    447                                   0,   // quiet
    448                                   0);  // default "work factor"
    449   if (result != BZ_OK) {  // out of memory?
    450     return false;
    451   }
    452 
    453   output->clear();
    454 
    455   stream.next_in = const_cast<char*>(input.data());
    456   stream.avail_in = static_cast<int>(input.size());
    457   // NOTE: we don't need a BZ_RUN phase since our input buffer contains
    458   //       the entire input
    459   do {
    460     output->resize(output->size() + 1024);
    461     stream.next_out = &((*output)[stream.total_out_lo32]);
    462     stream.avail_out = static_cast<int>(output->size()) - stream.total_out_lo32;
    463     result = BZ2_bzCompress(&stream, BZ_FINISH);
    464   } while (result == BZ_FINISH_OK);
    465   if (result != BZ_STREAM_END)  // unknown failure?
    466     return false;
    467   result = BZ2_bzCompressEnd(&stream);
    468   DCHECK(result == BZ_OK);
    469 
    470   output->resize(stream.total_out_lo32);
    471 
    472   return true;
    473 }
    474 
    475 void MetricsServiceBase::DiscardPendingLog() {
    476   if (pending_log_) {  // Shutdown might have deleted it!
    477     delete pending_log_;
    478     pending_log_ = NULL;
    479   }
    480   compressed_log_.clear();
    481 }
    482 
    483 void MetricsServiceBase::RecordCurrentHistograms() {
    484   DCHECK(current_log_);
    485   TransmitAllHistograms(base::Histogram::kNoFlags, true);
    486 }
    487 
    488 void MetricsServiceBase::TransmitHistogramDelta(
    489       const base::Histogram& histogram,
    490       const base::Histogram::SampleSet& snapshot) {
    491   current_log_->RecordHistogramDelta(histogram, snapshot);
    492 }
    493 
    494 void MetricsServiceBase::InconsistencyDetected(int problem) {
    495   UMA_HISTOGRAM_ENUMERATION("Histogram.InconsistenciesBrowser",
    496                             problem, Histogram::NEVER_EXCEEDED_VALUE);
    497 }
    498 
    499 void MetricsServiceBase::UniqueInconsistencyDetected(int problem) {
    500   UMA_HISTOGRAM_ENUMERATION("Histogram.InconsistenciesBrowserUnique",
    501                             problem, Histogram::NEVER_EXCEEDED_VALUE);
    502 }
    503 
    504 void MetricsServiceBase::SnapshotProblemResolved(int amount) {
    505   UMA_HISTOGRAM_COUNTS("Histogram.InconsistentSnapshotBrowser",
    506                        std::abs(amount));
    507 }
    508 
    509 HistogramSender::HistogramSender() {}
    510 
    511 HistogramSender::~HistogramSender() {}
    512 
    513 void HistogramSender::TransmitAllHistograms(Histogram::Flags flag_to_set,
    514                                             bool send_only_uma) {
    515   StatisticsRecorder::Histograms histograms;
    516   StatisticsRecorder::GetHistograms(&histograms);
    517   for (StatisticsRecorder::Histograms::const_iterator it = histograms.begin();
    518        histograms.end() != it;
    519        ++it) {
    520     (*it)->SetFlags(flag_to_set);
    521     if (send_only_uma &&
    522         0 == ((*it)->flags() & Histogram::kUmaTargetedHistogramFlag))
    523       continue;
    524     TransmitHistogram(**it);
    525   }
    526 }
    527 
    528 void HistogramSender::TransmitHistogram(const Histogram& histogram) {
    529   // Get up-to-date snapshot of sample stats.
    530   Histogram::SampleSet snapshot;
    531   histogram.SnapshotSample(&snapshot);
    532   const std::string& histogram_name = histogram.histogram_name();
    533 
    534   int corruption = histogram.FindCorruption(snapshot);
    535 
    536   // Crash if we detect that our histograms have been overwritten.  This may be
    537   // a fair distance from the memory smasher, but we hope to correlate these
    538   // crashes with other events, such as plugins, or usage patterns, etc.
    539   if (Histogram::BUCKET_ORDER_ERROR & corruption) {
    540     // The checksum should have caught this, so crash separately if it didn't.
    541     CHECK_NE(0, Histogram::RANGE_CHECKSUM_ERROR & corruption);
    542     CHECK(false);  // Crash for the bucket order corruption.
    543   }
    544   // Checksum corruption might not have caused order corruption.
    545   CHECK_EQ(0, Histogram::RANGE_CHECKSUM_ERROR & corruption);
    546 
    547   if (corruption) {
    548     NOTREACHED();
    549     InconsistencyDetected(corruption);
    550     // Don't send corrupt data to metrics survices.
    551     if (NULL == inconsistencies_.get())
    552       inconsistencies_.reset(new ProblemMap);
    553     int old_corruption = (*inconsistencies_)[histogram_name];
    554     if (old_corruption == (corruption | old_corruption))
    555       return;  // We've already seen this corruption for this histogram.
    556     (*inconsistencies_)[histogram_name] |= corruption;
    557     UniqueInconsistencyDetected(corruption);
    558     return;
    559   }
    560 
    561   // Find the already sent stats, or create an empty set.  Remove from our
    562   // snapshot anything that we've already sent.
    563   LoggedSampleMap::iterator it = logged_samples_.find(histogram_name);
    564   Histogram::SampleSet* already_logged;
    565   if (logged_samples_.end() == it) {
    566     // Add new entry
    567     already_logged = &logged_samples_[histogram.histogram_name()];
    568     already_logged->Resize(histogram);  // Complete initialization.
    569   } else {
    570     already_logged = &(it->second);
    571     int64 discrepancy(already_logged->TotalCount() -
    572                     already_logged->redundant_count());
    573     if (discrepancy) {
    574       NOTREACHED();  // Already_logged has become corrupt.
    575       int problem = static_cast<int>(discrepancy);
    576       if (problem != discrepancy)
    577         problem = INT_MAX;
    578       SnapshotProblemResolved(problem);
    579       // With no valid baseline, we'll act like we've sent everything in our
    580       // snapshot.
    581       already_logged->Subtract(*already_logged);
    582       already_logged->Add(snapshot);
    583     }
    584     // Deduct any stats we've already logged from our snapshot.
    585     snapshot.Subtract(*already_logged);
    586   }
    587 
    588   // Snapshot now contains only a delta to what we've already_logged.
    589   if (snapshot.redundant_count() > 0) {
    590     TransmitHistogramDelta(histogram, snapshot);
    591     // Add new data into our running total.
    592     already_logged->Add(snapshot);
    593   }
    594 }
    595