Bug 670059 - Add some JS engine telemetry counters to measure occurrences of: E4X, __iterator__, mutable __proto__ (r=taras,waldo)
authorLuke Wagner <luke@mozilla.com>
Thu, 07 Jul 2011 15:40:33 -0700
changeset 73898 ba19e1cd3f918d684ad8d71551254631c8d23870
parent 73897 1d186a5f3a96e938a253726ac48ecaf00e80c712
child 73899 6181622382cfe47637cac70bff1a09e80b6cc2cb
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
reviewerstaras, waldo
bugs670059
milestone8.0a1
Bug 670059 - Add some JS engine telemetry counters to measure occurrences of: E4X, __iterator__, mutable __proto__ (r=taras,waldo)
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
js/src/jsgc.cpp
js/src/jsgcinlines.h
js/src/jsiter.cpp
js/src/jsobj.cpp
js/src/jsxml.cpp
js/src/xpconnect/idl/Makefile.in
js/src/xpconnect/idl/nsIJSEngineTelemetryStats.idl
js/src/xpconnect/src/nsXPConnect.cpp
js/src/xpconnect/src/xpcprivate.h
toolkit/components/telemetry/TelemetryPing.js
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -1429,16 +1429,22 @@ JSContext::generatorFor(StackFrame *fp) 
     for (size_t i = 0; i < genStack.length(); ++i) {
         if (genStack[i]->liveFrame() == fp)
             return genStack[i];
     }
     JS_NOT_REACHED("no matching generator");
     return NULL;
 }
 
+bool
+JSContext::runningWithTrustedPrincipals() const
+{
+    return !compartment || compartment->principals == runtime->trustedPrincipals();
+}
+
 JS_FRIEND_API(void)
 JSRuntime::onTooMuchMalloc()
 {
 #ifdef JS_THREADSAFE
     AutoLockGC lock(this);
 
     /*
      * We can be called outside a request and can race against a GC that
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -1291,16 +1291,22 @@ struct JSContext
 #ifdef DEBUG
     /*
      * Controls whether a quadratic-complexity assertion is performed during
      * stack iteration, defaults to true.
      */
     bool stackIterAssertionEnabled;
 #endif
 
+    /*
+     * See JS_SetTrustedPrincipals in jsapi.h.
+     * Note: !cx->compartment is treated as trusted.
+     */
+    bool runningWithTrustedPrincipals() const;
+
   private:
     /*
      * The allocation code calls the function to indicate either OOM failure
      * when p is null or that a memory pressure counter has reached some
      * threshold when p is not null. The function takes the pointer and not
      * a boolean flag to minimize the amount of code in its inlined callers.
      */
     JS_FRIEND_API(void) checkMallocGCPressure(void *p);
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -75,8 +75,37 @@ JS_UnwrapObject(JSObject *obj)
     return obj->unwrap();
 }
 
 JS_FRIEND_API(JSObject *)
 JS_GetFrameScopeChainRaw(JSStackFrame *fp)
 {
     return &Valueify(fp)->scopeChain();
 }
+
+/*
+ * The below code is for temporary telemetry use. It can be removed when
+ * sufficient data has been harvested.
+ */
+
+extern size_t sE4XObjectsCreated;
+
+JS_FRIEND_API(size_t)
+JS_GetE4XObjectsCreated(JSContext *)
+{
+  return sE4XObjectsCreated;
+}
+
+extern size_t sSetProtoCalled;
+
+JS_FRIEND_API(size_t)
+JS_SetProtoCalled(JSContext *)
+{
+  return sSetProtoCalled;
+}
+
+extern size_t sCustomIteratorCount;
+
+JS_FRIEND_API(size_t)
+JS_GetCustomIteratorCount(JSContext *cx)
+{
+  return sCustomIteratorCount;
+}
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -52,11 +52,20 @@ extern JS_FRIEND_API(JSObject *)
 JS_FindCompilationScope(JSContext *cx, JSObject *obj);
 
 extern JS_FRIEND_API(JSObject *)
 JS_UnwrapObject(JSObject *obj);
 
 extern JS_FRIEND_API(JSObject *)
 JS_GetFrameScopeChainRaw(JSStackFrame *fp);
 
+extern JS_FRIEND_API(size_t)
+JS_GetE4XObjectsCreated(JSContext *cx);
+
+extern JS_FRIEND_API(size_t)
+JS_SetProtoCalled(JSContext *cx);
+
+extern JS_FRIEND_API(size_t)
+JS_GetCustomIteratorCount(JSContext *cx);
+
 JS_END_EXTERN_C
 
 #endif /* jsfriendapi_h___ */
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2903,8 +2903,21 @@ RunDebugGC(JSContext *cx)
         RunLastDitchGC(cx);
     }
 #endif
 }
 
 } /* namespace gc */
 
 } /* namespace js */
+
+#if JS_HAS_XML_SUPPORT
+extern size_t sE4XObjectsCreated;
+
+JSXML *
+js_NewGCXML(JSContext *cx)
+{
+    if (!cx->runningWithTrustedPrincipals())
+        ++sE4XObjectsCreated;
+
+    return NewGCThing<JSXML>(cx, js::gc::FINALIZE_XML, sizeof(JSXML));
+}
+#endif
--- a/js/src/jsgcinlines.h
+++ b/js/src/jsgcinlines.h
@@ -251,17 +251,13 @@ js_NewGCFunction(JSContext *cx)
 
 inline js::Shape *
 js_NewGCShape(JSContext *cx)
 {
     return NewGCThing<js::Shape>(cx, js::gc::FINALIZE_SHAPE, sizeof(js::Shape));
 }
 
 #if JS_HAS_XML_SUPPORT
-inline JSXML *
-js_NewGCXML(JSContext *cx)
-{
-    return NewGCThing<JSXML>(cx, js::gc::FINALIZE_XML, sizeof(JSXML));
-}
+extern JSXML *
+js_NewGCXML(JSContext *cx);
 #endif
 
-
 #endif /* jsgcinlines_h___ */
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -333,30 +333,35 @@ VectorToIdArray(JSContext *cx, AutoIdVec
 JS_FRIEND_API(bool)
 GetPropertyNames(JSContext *cx, JSObject *obj, uintN flags, AutoIdVector *props)
 {
     return Snapshot(cx, obj, flags & (JSITER_OWNONLY | JSITER_HIDDEN), props);
 }
 
 }
 
+size_t sCustomIteratorCount = 0;
+
 static inline bool
 GetCustomIterator(JSContext *cx, JSObject *obj, uintN flags, Value *vp)
 {
     /* Check whether we have a valid __iterator__ method. */
     JSAtom *atom = cx->runtime->atomState.iteratorAtom;
     if (!js_GetMethod(cx, obj, ATOM_TO_JSID(atom), JSGET_NO_METHOD_BARRIER, vp))
         return false;
 
     /* If there is no custom __iterator__ method, we are done here. */
     if (!vp->isObject()) {
         vp->setUndefined();
         return true;
     }
 
+    if (!cx->runningWithTrustedPrincipals())
+        ++sCustomIteratorCount;
+
     /* Otherwise call it and return that object. */
     LeaveTrace(cx);
     Value arg = BooleanValue((flags & JSITER_FOREACH) == 0);
     if (!ExternalInvoke(cx, ObjectValue(*obj), *vp, 1, &arg, vp))
         return false;
     if (vp->isPrimitive()) {
         /*
          * We are always coming from js_ValueToIterator, and we are no longer on
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -160,19 +160,24 @@ static JSBool
 obj_getProto(JSContext *cx, JSObject *obj, jsid id, Value *vp)
 {
     /* Let CheckAccess get the slot's value, based on the access mode. */
     uintN attrs;
     id = ATOM_TO_JSID(cx->runtime->atomState.protoAtom);
     return CheckAccess(cx, obj, id, JSACC_PROTO, vp, &attrs);
 }
 
+size_t sSetProtoCalled = 0;
+
 static JSBool
 obj_setProto(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *vp)
 {
+    if (!cx->runningWithTrustedPrincipals())
+        ++sSetProtoCalled;
+
     /* ECMAScript 5 8.6.2 forbids changing [[Prototype]] if not [[Extensible]]. */
     if (!obj->isExtensible()) {
         obj->reportNotExtensible(cx);
         return false;
     }
 
     if (!vp->isObjectOrNull())
         return JS_TRUE;
--- a/js/src/jsxml.cpp
+++ b/js/src/jsxml.cpp
@@ -159,24 +159,29 @@ IsDeclared(const JSObject *obj)
 
 static JSBool
 xml_isXMLName(JSContext *cx, uintN argc, jsval *vp)
 {
     *vp = BOOLEAN_TO_JSVAL(js_IsXMLName(cx, argc ? vp[2] : JSVAL_VOID));
     return JS_TRUE;
 }
 
+size_t sE4XObjectsCreated = 0;
+
 /*
  * This wrapper is needed because NewBuiltinClassInstance doesn't
  * call the constructor, and we need a place to set the
  * HAS_EQUALITY bit.
  */
 static inline JSObject *
 NewBuiltinClassInstanceXML(JSContext *cx, Class *clasp)
 {
+    if (!cx->runningWithTrustedPrincipals())
+        ++sE4XObjectsCreated;
+
     JSObject *obj = NewBuiltinClassInstance(cx, clasp);
     if (obj)
         obj->syncSpecialEquality();
     return obj;
 }
 
 #define DEFINE_GETTER(name,code)                                               \
     static JSBool                                                              \
@@ -7181,16 +7186,22 @@ js_InitXMLClass(JSContext *cx, JSObject 
     if (!xmlProto)
         return NULL;
     JSXML *xml = js_NewXML(cx, JSXML_CLASS_TEXT);
     if (!xml)
         return NULL;
     xmlProto->setPrivate(xml);
     xml->object = xmlProto;
 
+    /* Don't count this as a real content-created XML object. */
+    if (!cx->runningWithTrustedPrincipals()) {
+        JS_ASSERT(sE4XObjectsCreated > 0);
+        --sE4XObjectsCreated;
+    }
+
     const uintN XML_CTOR_LENGTH = 1;
     JSFunction *ctor = global->createConstructor(cx, XML, &js_XMLClass, CLASS_ATOM(cx, XML),
                                                  XML_CTOR_LENGTH);
     if (!ctor)
         return NULL;
 
     if (!LinkConstructorAndPrototype(cx, ctor, xmlProto))
         return NULL;
--- a/js/src/xpconnect/idl/Makefile.in
+++ b/js/src/xpconnect/idl/Makefile.in
@@ -60,12 +60,13 @@ XPIDLSRCS	= \
 		nsIXPConnect.idl \
 		nsIXPCSecurityManager.idl \
 		nsIXPCScriptable.idl \
 		nsIScriptError.idl \
 		nsIXPCScriptNotify.idl \
 		nsIScriptableInterfaces.idl \
 		xpcIJSWeakReference.idl \
 		xpcIJSGetFactory.idl \
+		nsIJSEngineTelemetryStats.idl \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
new file mode 100644
--- /dev/null
+++ b/js/src/xpconnect/idl/nsIJSEngineTelemetryStats.idl
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Firefox.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation <http://www.mozilla.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * 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"
+
+/**
+ * Telemetry uses this interface on the @mozilla.org/js/xpc/XPConnect;1 service
+ * to extract JS engine stats.
+ */
+[scriptable, uuid(5a6ea52b-4e23-402f-93e3-59f29b2f1a88)]
+interface nsIJSEngineTelemetryStats : nsISupports
+{
+  /**
+   * The value returned by this attribute is included as the 'js' property of
+   * the telemetry ping JSON blob.
+   */
+  [implicit_jscontext]
+  readonly attribute jsval telemetryValue;
+};
--- a/js/src/xpconnect/src/nsXPConnect.cpp
+++ b/js/src/xpconnect/src/nsXPConnect.cpp
@@ -43,16 +43,17 @@
 /* High level class and public functions implementation. */
 
 #include "xpcprivate.h"
 #include "XPCWrapper.h"
 #include "nsBaseHashtable.h"
 #include "nsHashKeys.h"
 #include "jsatom.h"
 #include "jsobj.h"
+#include "jsfriendapi.h"
 #include "jsfun.h"
 #include "jsgc.h"
 #include "jsscript.h"
 #include "nsThreadUtilsInternal.h"
 #include "dom_quickstubs.h"
 #include "nsNullPrincipal.h"
 #include "nsIURI.h"
 #include "nsJSEnvironment.h"
@@ -61,23 +62,24 @@
 #include "XrayWrapper.h"
 #include "WrapperFactory.h"
 #include "AccessCheck.h"
 
 #include "jsdIDebuggerService.h"
 
 #include "xpcquickstubs.h"
 
-NS_IMPL_THREADSAFE_ISUPPORTS6(nsXPConnect,
+NS_IMPL_THREADSAFE_ISUPPORTS7(nsXPConnect,
                               nsIXPConnect,
                               nsISupportsWeakReference,
                               nsIThreadObserver,
                               nsIJSRuntimeService,
                               nsIJSContextStack,
-                              nsIThreadJSContextStack)
+                              nsIThreadJSContextStack,
+                              nsIJSEngineTelemetryStats)
 
 nsXPConnect* nsXPConnect::gSelf = nsnull;
 JSBool       nsXPConnect::gOnceAliveNowDead = JS_FALSE;
 PRUint32     nsXPConnect::gReportAllJSExceptions = 0;
 JSBool       nsXPConnect::gDebugMode = JS_FALSE;
 JSBool       nsXPConnect::gDesiredDebugMode = JS_FALSE;
 
 // Global cache of the default script security manager (QI'd to
@@ -2898,16 +2900,44 @@ NS_IMETHODIMP
 nsXPConnect::SetDebugModeWhenPossible(PRBool mode)
 {
     gDesiredDebugMode = mode;
     if (!mode)
         CheckForDebugMode(mRuntime->GetJSRuntime());
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsXPConnect::GetTelemetryValue(JSContext *cx, jsval *rval)
+{
+    JSObject *obj = JS_NewObject(cx, NULL, NULL, NULL);
+    if (!obj)
+        return NS_ERROR_OUT_OF_MEMORY;
+
+    uintN attrs = JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT;
+
+    size_t i = JS_GetE4XObjectsCreated(cx);
+    jsval v = DOUBLE_TO_JSVAL(i);
+    if (!JS_DefineProperty(cx, obj, "e4x", v, NULL, NULL, attrs))
+        return NS_ERROR_OUT_OF_MEMORY;
+
+    i = JS_SetProtoCalled(cx);
+    v = DOUBLE_TO_JSVAL(i);
+    if (!JS_DefineProperty(cx, obj, "setProto", v, NULL, NULL, attrs))
+        return NS_ERROR_OUT_OF_MEMORY;
+
+    i = JS_GetCustomIteratorCount(cx);
+    v = DOUBLE_TO_JSVAL(i);
+    if (!JS_DefineProperty(cx, obj, "customIter", v, NULL, NULL, attrs))
+        return NS_ERROR_OUT_OF_MEMORY;
+
+    *rval = OBJECT_TO_JSVAL(obj);
+    return NS_OK;
+}
+
 /* These are here to be callable from a debugger */
 JS_BEGIN_EXTERN_C
 JS_EXPORT_API(void) DumpJSStack()
 {
     nsresult rv;
     nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv));
     if(NS_SUCCEEDED(rv) && xpc)
         xpc->DebugDumpJSStack(PR_TRUE, PR_TRUE, PR_FALSE);
--- a/js/src/xpconnect/src/xpcprivate.h
+++ b/js/src/xpconnect/src/xpcprivate.h
@@ -98,16 +98,17 @@
 #include "nsAutoJSValHolder.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/Mutex.h"
 #include "nsDataHashtable.h"
 
 #include "nsThreadUtils.h"
 #include "nsIJSContextStack.h"
+#include "nsIJSEngineTelemetryStats.h"
 #include "nsDeque.h"
 
 #include "nsIConsoleService.h"
 #include "nsIScriptError.h"
 #include "nsIExceptionService.h"
 
 #include "nsVariant.h"
 #include "nsIPropertyBag.h"
@@ -456,26 +457,28 @@ const PRBool OBJ_IS_GLOBAL = PR_TRUE;
 const PRBool OBJ_IS_NOT_GLOBAL = PR_FALSE;
 
 class nsXPConnect : public nsIXPConnect,
                     public nsIThreadObserver,
                     public nsSupportsWeakReference,
                     public nsCycleCollectionJSRuntime,
                     public nsCycleCollectionParticipant,
                     public nsIJSRuntimeService,
-                    public nsIThreadJSContextStack
+                    public nsIThreadJSContextStack,
+                    public nsIJSEngineTelemetryStats
 {
 public:
     // all the interface method declarations...
     NS_DECL_ISUPPORTS
     NS_DECL_NSIXPCONNECT
     NS_DECL_NSITHREADOBSERVER
     NS_DECL_NSIJSRUNTIMESERVICE
     NS_DECL_NSIJSCONTEXTSTACK
     NS_DECL_NSITHREADJSCONTEXTSTACK
+    NS_DECL_NSIJSENGINETELEMETRYSTATS
 
     // non-interface implementation
 public:
     // These get non-addref'd pointers
     static nsXPConnect*  GetXPConnect();
     static nsXPConnect*  FastGetXPConnect() { return gSelf ? gSelf : GetXPConnect(); }
     static XPCJSRuntime* GetRuntimeInstance();
     XPCJSRuntime* GetRuntime() {return mRuntime;}
--- a/toolkit/components/telemetry/TelemetryPing.js
+++ b/toolkit/components/telemetry/TelemetryPing.js
@@ -181,23 +181,29 @@ function getMetadata(reason) {
 function getSimpleMeasurements() {
   let si = Cc["@mozilla.org/toolkit/app-startup;1"].
            getService(Ci.nsIAppStartup).getStartupInfo();
 
   var ret = {
     // uptime in minutes
     uptime: Math.round((new Date() - si.process) / 60000)
   }
+
   if (si.process) {
     for each (let field in ["main", "firstPaint", "sessionRestored"]) {
       if (!(field in si))
         continue;
       ret[field] = si[field] - si.process
     }
   }
+
+  ret.js = Cc["@mozilla.org/js/xpc/XPConnect;1"]
+           .getService(Ci.nsIJSEngineTelemetryStats)
+           .telemetryValue;
+
   return ret;
 }
 
 function TelemetryPing() {}
 
 TelemetryPing.prototype = {
   _histograms: {},
   _initialized: false,