Bug 707320 - provide interface for loading and saving histograms; r=taras
authorNathan Froyd <froydnj@mozilla.com>
Fri, 09 Dec 2011 15:15:53 -0500
changeset 86352 6c2b90a11ea8348c63590fc9e5cc3713c750885a
parent 86351 5a315a55ea7e3380354d9fa7b84f7f28b4d7ff71
child 86353 5c40a413d9a979e55e0da0fa5a7d6b968308d5d0
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstaras
bugs707320
milestone12.0a1
Bug 707320 - provide interface for loading and saving histograms; r=taras
toolkit/components/telemetry/Telemetry.cpp
toolkit/components/telemetry/nsITelemetry.idl
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -33,32 +33,36 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "base/histogram.h"
+#include "base/pickle.h"
 #include "nsIComponentManager.h"
 #include "nsIServiceManager.h"
 #include "nsCOMPtr.h"
 #include "mozilla/ModuleUtils.h"
 #include "nsIXPConnect.h"
 #include "mozilla/Services.h"
 #include "jsapi.h" 
 #include "nsStringGlue.h"
 #include "nsITelemetry.h"
+#include "nsIFile.h"
+#include "nsILocalFile.h"
 #include "Telemetry.h" 
 #include "nsTHashtable.h"
 #include "nsHashKeys.h"
 #include "nsBaseHashtable.h"
 #include "nsXULAppAPI.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Mutex.h"
+#include "mozilla/FileUtils.h"
 
 namespace {
 
 using namespace base;
 using namespace mozilla;
 
 class TelemetryImpl : public nsITelemetry
 {
@@ -678,16 +682,343 @@ TelemetryImpl::GetHistogramById(const ns
   Histogram *h;
   nsresult rv = GetHistogramByName(name, &h);
   if (NS_FAILED(rv))
     return rv;
 
   return WrapAndReturnHistogram(h, cx, ret);
 }
 
+class TelemetrySessionData : public nsITelemetrySessionData
+{
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSITELEMETRYSESSIONDATA
+
+public:
+  static nsresult LoadFromDisk(nsIFile *, TelemetrySessionData **ptr);
+  static nsresult SaveToDisk(nsIFile *, const nsACString &uuid);
+
+  TelemetrySessionData(const char *uuid);
+  ~TelemetrySessionData();
+
+private:
+  struct EnumeratorArgs {
+    JSContext *cx;
+    JSObject *snapshots;
+  };
+  typedef nsBaseHashtableET<nsUint32HashKey, Histogram::SampleSet> EntryType;
+  typedef nsTHashtable<EntryType> SessionMapType;
+  static PLDHashOperator ReflectSamples(EntryType *entry, void *arg);
+  SessionMapType mSampleSetMap;
+  nsCString mUUID;
+
+  bool DeserializeHistogramData(Pickle &pickle, void **iter);
+  static bool SerializeHistogramData(Pickle &pickle);
+
+  // The file format version.  Should be incremented whenever we change
+  // how individual SampleSets are stored in the file.
+  static const unsigned int sVersion = 1;
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(TelemetrySessionData, nsITelemetrySessionData)
+
+TelemetrySessionData::TelemetrySessionData(const char *uuid)
+  : mUUID(uuid)
+{
+  mSampleSetMap.Init();
+}
+
+TelemetrySessionData::~TelemetrySessionData()
+{
+  mSampleSetMap.Clear();
+}
+
+NS_IMETHODIMP
+TelemetrySessionData::GetUuid(nsACString &uuid)
+{
+  uuid = mUUID;
+  return NS_OK;
+}
+
+PLDHashOperator
+TelemetrySessionData::ReflectSamples(EntryType *entry, void *arg)
+{
+  struct EnumeratorArgs *args = static_cast<struct EnumeratorArgs *>(arg);
+  // This has the undesirable effect of creating a histogram for the
+  // current session with the given ID.  But there's no good way to
+  // compute the ranges and buckets from scratch.
+  Histogram *h = nsnull;
+  nsresult rv = GetHistogramByEnumId(Telemetry::ID(entry->GetKey()), &h);
+  if (NS_FAILED(rv)) {
+    return PL_DHASH_STOP;
+  }
+
+  // Don't reflect histograms with no data associated with them.
+  if (entry->mData.sum() == 0) {
+    return PL_DHASH_NEXT;
+  }
+
+  JSObject *snapshot = JS_NewObject(args->cx, NULL, NULL, NULL);
+  if (!(snapshot
+        && ReflectHistogramAndSamples(args->cx, snapshot, h, entry->mData)
+        && JS_DefineProperty(args->cx, args->snapshots,
+                             h->histogram_name().c_str(),
+                             OBJECT_TO_JSVAL(snapshot), NULL, NULL,
+                             JSPROP_ENUMERATE))) {
+    return PL_DHASH_STOP;
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+NS_IMETHODIMP
+TelemetrySessionData::GetSnapshots(JSContext *cx, jsval *ret)
+{
+  JSObject *snapshots = JS_NewObject(cx, NULL, NULL, NULL);
+  if (!snapshots) {
+    return NS_ERROR_FAILURE;
+  }
+
+  struct EnumeratorArgs args = { cx, snapshots };
+  PRUint32 count = mSampleSetMap.EnumerateEntries(ReflectSamples,
+                                                  static_cast<void*>(&args));
+  if (count != mSampleSetMap.Count()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  *ret = OBJECT_TO_JSVAL(snapshots);
+  return NS_OK;
+}
+
+bool
+TelemetrySessionData::DeserializeHistogramData(Pickle &pickle, void **iter)
+{
+  PRUint32 count = 0;
+  if (!pickle.ReadUInt32(iter, &count)) {
+    return false;
+  }
+
+  for (size_t i = 0; i < count; ++i) {
+    int stored_length;
+    const char *name;
+    if (!pickle.ReadData(iter, &name, &stored_length)) {
+      return false;
+    }
+
+    Telemetry::ID id;
+    nsresult rv = TelemetryImpl::GetHistogramEnumId(name, &id);
+    if (NS_FAILED(rv)) {
+      // We serialized a non-static histogram.  Just drop its data on
+      // the floor.  If we can't deserialize the data, though, we're in
+      // trouble.
+      Histogram::SampleSet ss;
+      if (!ss.Deserialize(iter, pickle)) {
+        return false;
+      }
+    }
+
+    EntryType *entry = mSampleSetMap.GetEntry(id);
+    if (!entry) {
+      entry = mSampleSetMap.PutEntry(id);
+      if (NS_UNLIKELY(!entry)) {
+        return false;
+      }
+      if (!entry->mData.Deserialize(iter, pickle)) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+nsresult
+TelemetrySessionData::LoadFromDisk(nsIFile *file, TelemetrySessionData **ptr)
+{
+  *ptr = nsnull;
+  nsresult rv;
+  nsCOMPtr<nsILocalFile> f(do_QueryInterface(file, &rv));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  AutoFDClose fd;
+  rv = f->OpenNSPRFileDesc(PR_RDONLY, 0, &fd);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  PRInt32 size = PR_Available(fd);
+  if (size == -1) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsAutoArrayPtr<char> data(new char[size]);
+  PRInt32 amount = PR_Read(fd, data, size);
+  if (amount != size) {
+    return NS_ERROR_FAILURE;
+  }
+
+  Pickle pickle(data, size);
+  void *iter = NULL;
+
+  unsigned int storedVersion;
+  if (!(pickle.ReadUInt32(&iter, &storedVersion)
+        && storedVersion == sVersion)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  const char *uuid;
+  int uuidLength;
+  if (!pickle.ReadData(&iter, &uuid, &uuidLength)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsAutoPtr<TelemetrySessionData> sessionData(new TelemetrySessionData(uuid));
+  if (!sessionData->DeserializeHistogramData(pickle, &iter)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  *ptr = sessionData.forget();
+  return NS_OK;
+}
+
+bool
+TelemetrySessionData::SerializeHistogramData(Pickle &pickle)
+{
+  StatisticsRecorder::Histograms hs;
+  StatisticsRecorder::GetHistograms(&hs);
+
+  if (!pickle.WriteUInt32(hs.size())) {
+    return false;
+  }
+
+  for (StatisticsRecorder::Histograms::const_iterator it = hs.begin();
+       it != hs.end();
+       ++it) {
+    const Histogram *h = *it;
+    const char *name = h->histogram_name().c_str();
+
+    Histogram::SampleSet ss;
+    h->SnapshotSample(&ss);
+
+    if (!(pickle.WriteData(name, strlen(name)+1)
+          && ss.Serialize(&pickle))) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+nsresult
+TelemetrySessionData::SaveToDisk(nsIFile *file, const nsACString &uuid)
+{
+  nsresult rv;
+  nsCOMPtr<nsILocalFile> f(do_QueryInterface(file, &rv));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  AutoFDClose fd;
+  rv = f->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0600, &fd);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  Pickle pickle;
+  if (!pickle.WriteUInt32(sVersion)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Include the trailing NULL for the UUID to make reading easier.
+  const char *data;
+  size_t length = uuid.GetData(&data);
+  if (!(pickle.WriteData(data, length+1)
+        && SerializeHistogramData(pickle))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  PRInt32 amount = PR_Write(fd, static_cast<const char*>(pickle.data()),
+                            pickle.size());
+  if (amount != pickle.size()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+class SaveHistogramEvent : public nsRunnable
+{
+public:
+  SaveHistogramEvent(nsIFile *file, const nsACString &uuid,
+                     nsITelemetrySaveSessionDataCallback *callback)
+    : mFile(file), mUUID(uuid), mCallback(callback)
+  {}
+
+  NS_IMETHOD Run()
+  {
+    nsresult rv = TelemetrySessionData::SaveToDisk(mFile, mUUID);
+    mCallback->Handle(!!NS_SUCCEEDED(rv));
+    return rv;
+  }
+
+private:
+  nsCOMPtr<nsIFile> mFile;
+  nsCString mUUID;
+  nsCOMPtr<nsITelemetrySaveSessionDataCallback> mCallback;
+};
+
+NS_IMETHODIMP
+TelemetryImpl::SaveHistograms(nsIFile *file, const nsACString &uuid,
+                              nsITelemetrySaveSessionDataCallback *callback,
+                              bool isSynchronous)
+{
+  nsCOMPtr<nsIRunnable> event = new SaveHistogramEvent(file, uuid, callback);
+  if (isSynchronous) {
+    return event ? event->Run() : NS_ERROR_FAILURE;
+  } else {
+    return NS_DispatchToCurrentThread(event);
+  }
+}
+
+class LoadHistogramEvent : public nsRunnable
+{
+public:
+  LoadHistogramEvent(nsIFile *file,
+                     nsITelemetryLoadSessionDataCallback *callback)
+    : mFile(file), mCallback(callback)
+  {}
+
+  NS_IMETHOD Run()
+  {
+    TelemetrySessionData *sessionData = nsnull;
+    nsresult rv = TelemetrySessionData::LoadFromDisk(mFile, &sessionData);
+    if (NS_FAILED(rv)) {
+      mCallback->Handle(nsnull);
+    } else {
+      nsCOMPtr<nsITelemetrySessionData> data(sessionData);
+      mCallback->Handle(data);
+    }
+    return rv;
+  }
+
+private:
+  nsCOMPtr<nsIFile> mFile;
+  nsCOMPtr<nsITelemetryLoadSessionDataCallback> mCallback;
+};
+
+NS_IMETHODIMP
+TelemetryImpl::LoadHistograms(nsIFile *file,
+                              nsITelemetryLoadSessionDataCallback *callback)
+{
+  nsCOMPtr<nsIRunnable> event = new LoadHistogramEvent(file, callback);
+  return NS_DispatchToCurrentThread(event);
+}
+
 NS_IMETHODIMP
 TelemetryImpl::GetCanRecord(bool *ret) {
   *ret = mCanRecord;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TelemetryImpl::SetCanRecord(bool canRecord) {
--- a/toolkit/components/telemetry/nsITelemetry.idl
+++ b/toolkit/components/telemetry/nsITelemetry.idl
@@ -33,18 +33,49 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
+#include "nsIFile.idl"
 
-[scriptable, uuid(db854295-478d-4de9-8211-d73ed7d81cd0)]
+[scriptable, uuid(c177b6b0-5ef1-44f5-bc67-6bcf7d2518e5)]
+interface nsITelemetrySessionData : nsISupports
+{
+  /**
+   * The UUID of our previous session.
+   */
+  readonly attribute ACString uuid;
+
+  /**
+   * An object containing a snapshot from all registered histograms that had
+   * data recorded in the previous session.
+   * { name1: data1, name2: data2, .... }
+   * where the individual dataN are as nsITelemetry.histogramSnapshots.
+   */
+  [implicit_jscontext]
+  readonly attribute jsval snapshots;
+};
+
+[scriptable, function, uuid(aff36c9d-7e4c-41ab-a9b6-53773bbca0cd)]
+interface nsITelemetryLoadSessionDataCallback : nsISupports
+{
+  void handle(in nsITelemetrySessionData data);
+};
+
+[scriptable, function, uuid(40065f26-afd2-4417-93de-c1de9adb1548)]
+interface nsITelemetrySaveSessionDataCallback : nsISupports
+{
+  void handle(in bool success);
+};
+
+[scriptable, uuid(22fc825e-288f-457e-80d5-5bb35f06d37e)]
 interface nsITelemetry : nsISupports
 {
   /**
    * Histogram types:
    * HISTOGRAM_EXPONENTIAL - buckets increase exponentially
    * HISTOGRAM_LINEAR - buckets increase linearly
    * HISTOGRAM_BOOLEAN - For storing 0/1 values
    */
@@ -123,12 +154,40 @@ interface nsITelemetry : nsISupports
    * Same as newHistogram above, but for histograms registered in TelemetryHistograms.h.
    *
    * @param id - unique identifier from TelemetryHistograms.h
    */
   [implicit_jscontext]
   jsval getHistogramById(in ACString id);
 
   /**
+   * Save persistent histograms to the given file.
+   *
+   * @param file - filename for saving
+   * @param uuid - UUID of this session
+   * @param callback - function to be caled when file writing is complete
+   * @param isSynchronous - whether the save is done synchronously.  Defaults
+   *        to asynchronous saving.
+   */
+  void saveHistograms(in nsIFile file, in ACString uuid,
+                      in nsITelemetrySaveSessionDataCallback callback,
+                      [optional] in boolean isSynchronous);
+
+  /* Reconstruct an nsITelemetryDataSession object containing histogram
+   * information from the given file; the file must have been produced
+   * via saveHistograms.  The file is read asynchronously.
+   *
+   * This method does not modify the histogram information being
+   * collected in the current session.
+   *
+   * The reconstructed object is then passed to the given callback.
+   *
+   * @param file - the file to load histogram information from
+   * @param callback - function to process histogram information
+   */
+  void loadHistograms(in nsIFile file,
+                      in nsITelemetryLoadSessionDataCallback callback);
+
+  /**
    * Set this to false to disable gathering of telemetry statistics.
    */
   attribute boolean canRecord;
 };