Bug 1204554 part 3.0 - Collect lcov output on the JSCompartment, and on the JSRuntime. r=terrence,bhackett
authorNicolas B. Pierron <nicolas.b.pierron@mozilla.com>
Thu, 01 Oct 2015 12:41:40 +0200
changeset 265601 673f622280ed4fb926e12fb012ba0054af4ece14
parent 265600 6d0e0a5cfb4ba045de4205bfdd19e1b5887909f0
child 265602 35247eec9d6151731b08fb9f5919bbe6758a012b
push id15472
push usercbook@mozilla.com
push dateFri, 02 Oct 2015 11:51:34 +0000
treeherderfx-team@2c33ef6b27e0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence, bhackett
bugs1204554
milestone44.0a1
Bug 1204554 part 3.0 - Collect lcov output on the JSCompartment, and on the JSRuntime. r=terrence,bhackett
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsscript.cpp
js/src/vm/CodeCoverage.cpp
js/src/vm/CodeCoverage.h
js/src/vm/Printer.h
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -75,27 +75,33 @@ JSCompartment::JSCompartment(Zone* zone,
     debugScriptMap(nullptr),
     debugScopes(nullptr),
     enumerators(nullptr),
     compartmentStats(nullptr),
     scheduledForDestruction(false),
     maybeAlive(true),
     jitCompartment_(nullptr),
     mappedArgumentsTemplate_(nullptr),
-    unmappedArgumentsTemplate_(nullptr)
+    unmappedArgumentsTemplate_(nullptr),
+    lcovOutput()
 {
     PodArrayZero(sawDeprecatedLanguageExtension);
     runtime_->numCompartments++;
     MOZ_ASSERT_IF(options.mergeable(), options.invisibleToDebugger());
 }
 
 JSCompartment::~JSCompartment()
 {
     reportTelemetry();
 
+    // Write the code coverage information in a file.
+    JSRuntime* rt = runtimeFromMainThread();
+    if (rt->lcovOutput.isEnabled())
+        rt->lcovOutput.writeLCovResult(lcovOutput);
+
     js_delete(jitCompartment_);
     js_delete(watchpointMap);
     js_delete(scriptCountsMap);
     js_delete(debugScriptMap);
     js_delete(debugScopes);
     js_delete(objectMetadataTable);
     js_delete(lazyArrayBuffers);
     js_free(enumerators);
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -657,17 +657,18 @@ struct JSCompartment
         return (debugModeBits & Mask) == Mask;
     }
     void updateDebuggerObservesCoverage();
 
     // The code coverage can be enabled either for each compartment, with the
     // Debugger API, or for the entire runtime.
     bool collectCoverage() const {
         return debuggerObservesCoverage() ||
-               runtimeFromAnyThread()->profilingScripts;
+               runtimeFromAnyThread()->profilingScripts ||
+               runtimeFromAnyThread()->lcovOutput.isEnabled();
     }
     void clearScriptCounts();
 
     bool needsDelazificationForDebugger() const {
         return debugModeBits & DebuggerNeedsDelazification;
     }
 
     /*
@@ -741,16 +742,21 @@ struct JSCompartment
   private:
     // Used for collecting telemetry on SpiderMonkey's deprecated language extensions.
     bool sawDeprecatedLanguageExtension[DeprecatedLanguageExtensionCount];
 
     void reportTelemetry();
 
   public:
     void addTelemetry(const char* filename, DeprecatedLanguageExtension e);
+
+  public:
+    // Aggregated output used to collect JSScript hit counts when code coverage
+    // is enabled.
+    js::coverage::LCovCompartment lcovOutput;
 };
 
 inline bool
 JSRuntime::isAtomsZone(const JS::Zone* zone) const
 {
     return zone == atomsCompartment_->zone();
 }
 
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -2931,16 +2931,21 @@ JSScript::uninlinedGlobal() const
 void
 JSScript::finalize(FreeOp* fop)
 {
     // NOTE: this JSScript may be partially initialized at this point.  E.g. we
     // may have created it and partially initialized it with
     // JSScript::Create(), but not yet finished initializing it with
     // fullyInitFromEmitter() or fullyInitTrivial().
 
+    // Collect code coverage information for this script and all its inner
+    // scripts, and store the aggregated information on the compartment.
+    if (isTopLevel() && fop->runtime()->lcovOutput.isEnabled())
+        compartment()->lcovOutput.collectCodeCoverageInfo(compartment(), this);
+
     fop->runtime()->spsProfiler.onScriptFinalized(this);
 
     if (types_)
         types_->destroy();
 
     jit::DestroyJitScripts(fop, this);
 
     destroyScriptCounts(fop);
--- a/js/src/vm/CodeCoverage.cpp
+++ b/js/src/vm/CodeCoverage.cpp
@@ -1,23 +1,33 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "vm/CodeCoverage.h"
 
+#include "mozilla/Atomics.h"
 #include "mozilla/IntegerPrintfMacros.h"
 
+#include <stdio.h>
+#if defined(XP_WIN)
+# include <windows.h>
+#else
+# include <unistd.h>
+#endif
+
 #include "jscompartment.h"
 #include "jsopcode.h"
+#include "jsprf.h"
 #include "jsscript.h"
 
 #include "vm/Runtime.h"
+#include "vm/Time.h"
 
 // This file contains a few functions which are used to produce files understood
 // by lcov tools. A detailed description of the format is available in the man
 // page for "geninfo" [1].  To make it short, the following paraphrases what is
 // commented in the man page by using curly braces prefixed by for-each to
 // express repeated patterns.
 //
 //   TN:<compartment name>
@@ -355,11 +365,73 @@ LCovCompartment::writeCompartmentName(JS
         outTN_.put("\n", 1);
     } else {
         outTN_.printf("Compartment_%p%p\n", (void*) size_t('_'), comp);
     }
 
     return !outTN_.hadOutOfMemory();
 }
 
+LCovRuntime::LCovRuntime()
+  : out_(),
+#if defined(XP_WIN)
+    pid_(GetCurrentProcessId())
+#else
+    pid_(getpid())
+#endif
+{
+}
+
+LCovRuntime::~LCovRuntime()
+{
+    if (out_.isInitialized())
+        out_.finish();
+}
+
+void
+LCovRuntime::init()
+{
+    const char* outDir = getenv("JS_CODE_COVERAGE_OUTPUT_DIR");
+    if (!outDir || *outDir == 0)
+        return;
+
+    int64_t timestamp = static_cast<double>(PRMJ_Now()) / PRMJ_USEC_PER_SEC;
+    static mozilla::Atomic<size_t> globalRuntimeId(0);
+    size_t rid = globalRuntimeId++;
+
+    char name[1024];
+    size_t len = JS_snprintf(name, sizeof(name), "%s/%" PRId64 "-%d-%d.info",
+                             outDir, timestamp, size_t(pid_), rid);
+    if (sizeof(name) < len) {
+        fprintf(stderr, "Warning: LCovRuntime::init: Cannot serialize file name.");
+        return;
+    }
+
+    // If we cannot open the file, report a warning.
+    if (!out_.init(name))
+        fprintf(stderr, "Warning: LCovRuntime::init: Cannot open file named '%s'.", name);
+}
+
+void
+LCovRuntime::writeLCovResult(LCovCompartment& comp)
+{
+    if (!out_.isInitialized())
+        return;
+
+#if defined(XP_WIN)
+    size_t p = GetCurrentProcessId();
+#else
+    size_t p = getpid();
+#endif
+    if (pid_ != p) {
+        pid_ = p;
+        out_.finish();
+        init();
+        if (!out_.isInitialized())
+            return;
+    }
+
+    comp.exportInto(out_);
+    out_.flush();
+}
 
 } // namespace coverage
 } // namespace js
--- a/js/src/vm/CodeCoverage.h
+++ b/js/src/vm/CodeCoverage.h
@@ -96,13 +96,50 @@ class LCovCompartment
 
     // LifoAlloc string which hold the name of the compartment.
     LSprinter outTN_;
 
     // Vector of all sources which are used in this compartment.
     LCovSourceVector* sources_;
 };
 
+class LCovRuntime
+{
+  public:
+    LCovRuntime();
+    ~LCovRuntime();
+
+    // If the environment variable JS_CODE_COVERAGE_OUTPUT_DIR is set to a
+    // directory, create a file inside this directory which uses the process
+    // ID, the thread ID and a timestamp to ensure the uniqueness of the
+    // file.
+    //
+    // At the end of the execution, this file should contains the LCOV output of
+    // all the scripts executed in the current JSRuntime.
+    void init();
+
+    // Check if we should collect code coverage information.
+    bool isEnabled() const { return out_.isInitialized(); }
+
+    // Write the aggregated result of the code coverage of a compartment
+    // into a file.
+    void writeLCovResult(LCovCompartment& comp);
+
+  private:
+    // When a process forks, the file will remain open, but 2 processes will
+    // have the same file. To avoid conflicting writes, we open a new file for
+    // the child process.
+    void maybeReopenAfterFork();
+
+  private:
+    // Output file which is created if code coverage is enabled.
+    Fprinter out_;
+
+    // The process' PID is used to watch for fork. When the process fork,
+    // we want to close the current file and open a new one.
+    size_t pid_;
+};
+
 } // namespace coverage
 } // namespace js
 
 #endif // vm_Printer_h
 
--- a/js/src/vm/Printer.h
+++ b/js/src/vm/Printer.h
@@ -128,17 +128,17 @@ class Fprinter final : public GenericPri
   public:
     explicit Fprinter(FILE* fp);
     Fprinter();
     ~Fprinter();
 
     // Initialize this printer, returns false on error.
     bool init(const char* path);
     void init(FILE* fp);
-    bool isInitialized() {
+    bool isInitialized() const {
         return file_ != nullptr;
     }
     void flush();
     void finish();
 
     // Puts |len| characters from |s| at the current position and return an
     // offset to the beginning of this new data.
     virtual int put(const char* s, size_t len) override;
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -171,16 +171,17 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
     activeContext(nullptr),
 #endif
     gc(thisFromCtor()),
     gcInitialized(false),
 #ifdef JS_SIMULATOR
     simulator_(nullptr),
 #endif
     scriptAndCountsVector(nullptr),
+    lcovOutput(),
     NaNValue(DoubleNaNValue()),
     negativeInfinityValue(DoubleValue(NegativeInfinity<double>())),
     positiveInfinityValue(DoubleValue(PositiveInfinity<double>())),
     emptyString(nullptr),
     spsProfiler(thisFromCtor()),
     profilingScripts(false),
     suppressProfilerSampling(false),
     hadOutOfMemory(false),
@@ -237,16 +238,17 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
 
     liveRuntimesCount++;
 
     /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
     JS_INIT_CLIST(&onNewGlobalObjectWatchers);
 
     PodArrayZero(nativeStackQuota);
     PodZero(&asmJSCacheOps);
+    lcovOutput.init();
 }
 
 static bool
 SignalBasedTriggersDisabled()
 {
   // Don't bother trying to cache the getenv lookup; this should be called
   // infrequently.
   return !!getenv("JS_DISABLE_SLOW_SCRIPT_SIGNALS") || !!getenv("JS_NO_SIGNALS");
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -34,16 +34,17 @@
 #include "irregexp/RegExpStack.h"
 #include "js/Debug.h"
 #include "js/HashTable.h"
 #ifdef DEBUG
 # include "js/Proxy.h" // For AutoEnterPolicy
 #endif
 #include "js/TraceableVector.h"
 #include "js/Vector.h"
+#include "vm/CodeCoverage.h"
 #include "vm/CommonPropertyNames.h"
 #include "vm/DateTime.h"
 #include "vm/MallocProvider.h"
 #include "vm/SPSProfiler.h"
 #include "vm/Stack.h"
 #include "vm/Symbol.h"
 
 #ifdef _MSC_VER
@@ -1051,16 +1052,19 @@ struct JSRuntime : public JS::shadow::Ru
 #ifdef JS_SIMULATOR
     js::jit::Simulator* simulator() const;
     uintptr_t* addressOfSimulatorStackLimit();
 #endif
 
     /* Strong references on scripts held for PCCount profiling API. */
     JS::PersistentRooted<js::ScriptAndCountsVector>* scriptAndCountsVector;
 
+    /* Code coverage output. */
+    js::coverage::LCovRuntime lcovOutput;
+
     /* Well-known numbers held for use by this runtime's contexts. */
     const js::Value     NaNValue;
     const js::Value     negativeInfinityValue;
     const js::Value     positiveInfinityValue;
 
     js::PropertyName*   emptyString;
 
     /* List of active contexts sharing this runtime. */