Exemple #1
0
 /// <summary>
 /// Collect and submit a ping for eventual upload by name.
 ///
 /// The ping will be looked up in the known instances of `PingType`. If the
 /// ping isn't known, an error is logged and the ping isn't queued for uploading.
 ///
 /// The ping content is assembled as soon as possible, but upload is not
 /// guaranteed to happen immediately, as that depends on the upload
 /// policies.
 ///
 /// If the ping currently contains no content, it will not be assembled and
 /// queued for sending, unless explicitly specified otherwise in the registry
 /// file.
 /// </summary>
 /// <param name="name">Name of the ping to submit.</param>
 /// <param name="reason">The reason the ping is being submitted.</param>
 internal void SubmitPingByName(string name, string reason = null)
 {
     Dispatchers.LaunchAPI(() =>
     {
         SubmitPingByNameSync(name, reason);
     });
 }
Exemple #2
0
        /// <summary>
        /// Indicate that an experiment is running. Glean will then add an
        /// experiment annotation to the environment which is sent with pings. This
        /// information is not persisted between runs.
        /// </summary>
        /// <param name="experimentId">The id of the active experiment (maximum 100 bytes)</param>
        /// <param name="branch">The experiment branch (maximum 100 bytes)</param>
        /// <param name="extra">Optional metadata to output with the ping</param>
        public void SetExperimentActive(string experimentId, string branch, Dictionary <string, string> extra = null)
        {
            // The Map is sent over FFI as a pair of arrays, one containing the
            // keys, and the other containing the values.
            string[] keys   = null;
            string[] values = null;

            Int32 numKeys = 0;

            if (extra != null)
            {
                // While the `ToArray` functions below could throw `ArgumentNullException`, this would
                // only happen if `extra` (and `extra.Keys|Values`) is null. Which is not the case, since
                // we're specifically checking this.
                // Note that the order of `extra.Keys` and `extra.Values` is unspecified, but guaranteed
                // to be the same. See
                // https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2.values?view=netstandard-2.0#remarks
                keys    = extra.Keys.ToArray();
                values  = extra.Values.ToArray();
                numKeys = extra.Count();
            }

            // We dispatch this asynchronously so that, if called before the Glean SDK is
            // initialized, it doesn't get ignored and will be replayed after init.
            Dispatchers.LaunchAPI(() => {
                LibGleanFFI.glean_set_experiment_active(
                    experimentId,
                    branch,
                    keys,
                    values,
                    numKeys
                    );
            });
        }
Exemple #3
0
        /// <summary>
        /// Returns the stored data for the requested active experiment, for testing purposes only.
        /// </summary>
        /// <param name="experimentId">The id of the experiment to look for.</param>
        /// <exception cref="System.NullReferenceException">Thrown when there is no data for the experiment.</exception>
        /// <returns>The `RecordedExperimentData` for the experiment</returns>
        public RecordedExperimentData TestGetExperimentData(string experimentId)
        {
            Dispatchers.AssertInTestingMode();

            string rawData = LibGleanFFI.glean_experiment_test_get_data(experimentId).AsString();

            return(RecordedExperimentData.FromJsonString(rawData));
        }
Exemple #4
0
 /// <summary>
 /// Indicate that an experiment is no longer running.
 /// </summary>
 /// <param name="experimentId">The id of the experiment to deactivate.</param>
 public void SetExperimentInactive(string experimentId)
 {
     // We dispatch this asynchronously so that, if called before the Glean SDK is
     // initialized, it doesn't get ignored and will be replayed after init.
     Dispatchers.LaunchAPI(() => {
         LibGleanFFI.glean_set_experiment_inactive(experimentId);
     });
 }
Exemple #5
0
        /// <summary>
        /// Enable or disable Glean collection and upload.
        ///
        /// Metric collection is enabled by default.
        ///
        /// When uploading is disabled, metrics aren't recorded at all and no data
        /// is uploaded.
        ///
        /// When disabling, all pending metrics, events and queued pings are cleared
        /// and a `deletion-request` is generated.
        ///
        /// When enabling, the core Glean metrics are recreated.
        /// </summary>
        /// <param name="enabled">When `true`, enable metric collection.</param>
        public void SetUploadEnabled(bool enabled)
        {
            if (!this.initFinished)
            {
                string msg = "Changing upload enabled before Glean is initialized is not supported.\n" +
                             "Pass the correct state into `Glean.initialize()`.\n" +
                             "See documentation at https://mozilla.github.io/glean/book/user/general-api.html#initializing-the-glean-sdk";
                Log.Error(msg);

                return;
            }
            // Changing upload enabled always happens asynchronous.
            // That way it follows what a user expect when calling it inbetween other calls:
            // It executes in the right order.
            //
            // Because the dispatch queue is halted until Glean is fully initialized
            // we can safely enqueue here and it will execute after initialization.
            Dispatchers.LaunchAPI(() => {
                bool originalEnabled = this.GetUploadEnabled();
                LibGleanFFI.glean_set_upload_enabled(enabled);

                if (!enabled)
                {
                    // Cancel any pending workers here so that we don't accidentally upload or
                    // collect data after the upload has been disabled.
                    // TODO: metricsPingScheduler.cancel()

                    // Cancel any pending workers here so that we don't accidentally upload
                    // data after the upload has been disabled.
                    httpClient.CancelUploads();
                }

                if (!originalEnabled && enabled)
                {
                    // If uploading is being re-enabled, we have to restore the
                    // application-lifetime metrics.
                    InitializeCoreMetrics();
                }

                if (originalEnabled && !enabled)
                {
                    // If uploading is disabled, we need to send the deletion-request ping
                    httpClient.TriggerUpload(configuration);
                }
            });
        }
Exemple #6
0
        /// <summary>
        /// Enable or disable Glean collection and upload.
        ///
        /// Metric collection is enabled by default.
        ///
        /// When uploading is disabled, metrics aren't recorded at all and no data
        /// is uploaded.
        ///
        /// When disabling, all pending metrics, events and queued pings are cleared
        /// and a `deletion-request` is generated.
        ///
        /// When enabling, the core Glean metrics are recreated.
        /// </summary>
        /// <param name="enabled">When `true`, enable metric collection.</param>
        public void SetUploadEnabled(bool enabled)
        {
            if (IsInitialized())
            {
                bool originalEnabled = GetUploadEnabled();

                Dispatchers.LaunchAPI(() => {
                    LibGleanFFI.glean_set_upload_enabled(enabled);

                    if (!enabled)
                    {
                        // Cancel any pending workers here so that we don't accidentally upload or
                        // collect data after the upload has been disabled.
                        // TODO: metricsPingScheduler.cancel()

                        // Cancel any pending workers here so that we don't accidentally upload
                        // data after the upload has been disabled.
                        httpClient.CancelUploads();
                    }

                    if (!originalEnabled && enabled)
                    {
                        // If uploading is being re-enabled, we have to restore the
                        // application-lifetime metrics.
                        InitializeCoreMetrics();
                    }

                    if (originalEnabled && !enabled)
                    {
                        // If uploading is disabled, we need to send the deletion-request ping
                        httpClient.TriggerUpload(configuration);
                    }
                });
            }
            else
            {
                uploadEnabled = enabled;
            }
        }
Exemple #7
0
        public void Initialize(
            string applicationId,
            string applicationVersion,
            bool uploadEnabled,
            Configuration configuration,
            string dataDir
            )
        {
            /*
             * // Glean initialization must be called on the main thread, or lifecycle
             * // registration may fail. This is also enforced at build time by the
             * // @MainThread decorator, but this run time check is also performed to
             * // be extra certain.
             * ThreadUtils.assertOnUiThread()
             *
             * // In certain situations Glean.initialize may be called from a process other than the main
             * // process.  In this case we want initialize to be a no-op and just return.
             * if (!isMainProcess(applicationContext)) {
             *  Log.e(LOG_TAG, "Attempted to initialize Glean on a process other than the main process")
             *  return
             * }
             *
             * this.applicationContext = applicationContext*/

            if (IsInitialized())
            {
                Log.Error("Glean should not be initialized multiple times");
                return;
            }

            this.configuration      = configuration;
            this.applicationVersion = applicationVersion;
            httpClient = new BaseUploader(configuration.httpClient);
            // this.gleanDataDir = File(applicationContext.applicationInfo.dataDir, GLEAN_DATA_DIR)

            Dispatchers.ExecuteTask(() =>
            {
                RegisterPings(Pings);

                IntPtr maxEventsPtr = IntPtr.Zero;
                if (configuration.maxEvents != null)
                {
                    maxEventsPtr = Marshal.AllocHGlobal(sizeof(int));
                    // It's safe to call `configuration.maxEvents.Value` because we know
                    // `configuration.maxEvents` is not null.
                    Marshal.WriteInt32(maxEventsPtr, configuration.maxEvents.Value);
                }

                LibGleanFFI.FfiConfiguration cfg = new LibGleanFFI.FfiConfiguration
                {
                    data_dir               = dataDir,
                    package_name           = applicationId,
                    language_binding_name  = LanguageBindingName,
                    upload_enabled         = uploadEnabled,
                    max_events             = maxEventsPtr,
                    delay_ping_lifetime_io = false
                };

                // To work around a bug in the version of Mono shipped with Unity 2019.4.1f1,
                // copy the FFI configuration structure to unmanaged memory and pass that over
                // to glean-core, otherwise calling `glean_initialize` will crash and have
                // `__icall_wrapper_mono_struct_delete_old` in the stack. See bug 1648784 for
                // more details.
                IntPtr ptrCfg = Marshal.AllocHGlobal(Marshal.SizeOf(cfg));
                Marshal.StructureToPtr(cfg, ptrCfg, false);

                initialized = LibGleanFFI.glean_initialize(ptrCfg) != 0;

                // This is safe to call even if `maxEventsPtr = IntPtr.Zero`.
                Marshal.FreeHGlobal(maxEventsPtr);
                // We were able to call `glean_initialize`, free the memory allocated for the
                // FFI configuration object.
                Marshal.FreeHGlobal(ptrCfg);

                // If initialization of Glean fails we bail out and don't initialize further.
                if (!initialized)
                {
                    return;
                }

                // If any pings were registered before initializing, do so now.
                // We're not clearing this queue in case Glean is reset by tests.
                lock (this)
                {
                    foreach (var ping in pingTypeQueue)
                    {
                        RegisterPingType(ping);
                    }
                }

                // If this is the first time ever the Glean SDK runs, make sure to set
                // some initial core metrics in case we need to generate early pings.
                // The next times we start, we would have them around already.
                bool isFirstRun = LibGleanFFI.glean_is_first_run() != 0;
                if (isFirstRun)
                {
                    InitializeCoreMetrics();
                }


                // Deal with any pending events so we can start recording new ones
                bool pingSubmitted = LibGleanFFI.glean_on_ready_to_submit_pings() != 0;

                // We need to enqueue the BaseUploader in these cases:
                // 1. Pings were submitted through Glean and it is ready to upload those pings;
                // 2. Upload is disabled, to upload a possible deletion-request ping.
                if (pingSubmitted || !uploadEnabled)
                {
                    httpClient.TriggerUpload(configuration);
                }

                /*
                 * // Set up information and scheduling for Glean owned pings. Ideally, the "metrics"
                 * // ping startup check should be performed before any other ping, since it relies
                 * // on being dispatched to the API context before any other metric.
                 * metricsPingScheduler = MetricsPingScheduler(applicationContext)
                 * metricsPingScheduler.schedule()
                 *
                 * // Check if the "dirty flag" is set. That means the product was probably
                 * // force-closed. If that's the case, submit a 'baseline' ping with the
                 * // reason "dirty_startup". We only do that from the second run.
                 * if (!isFirstRun && LibGleanFFI.INSTANCE.glean_is_dirty_flag_set().toBoolean()) {
                 *  submitPingByNameSync("baseline", "dirty_startup")
                 *  // Note: while in theory we should set the "dirty flag" to true
                 *  // here, in practice it's not needed: if it hits this branch, it
                 *  // means the value was `true` and nothing needs to be done.
                 * }*/

                // From the second time we run, after all startup pings are generated,
                // make sure to clear `lifetime: application` metrics and set them again.
                // Any new value will be sent in newly generated pings after startup.
                if (!isFirstRun)
                {
                    LibGleanFFI.glean_clear_application_lifetime_metrics();
                    InitializeCoreMetrics();
                }

                // Signal Dispatcher that init is complete
                Dispatchers.FlushQueuedInitialTasks();

                /*
                 * // At this point, all metrics and events can be recorded.
                 * // This should only be called from the main thread. This is enforced by
                 * // the @MainThread decorator and the `assertOnUiThread` call.
                 * MainScope().launch {
                 *  ProcessLifecycleOwner.get().lifecycle.addObserver(gleanLifecycleObserver)
                 * }*/
            });

            this.initFinished = true;
        }
Exemple #8
0
        /// <summary>
        /// Tests whether an experiment is active, for testing purposes only.
        /// </summary>
        /// <param name="experimentId">The id of the experiment to look for.</param>
        /// <returns>true if the experiment is active and reported in pings, otherwise false</returns>
        public bool TestIsExperimentActive(string experimentId)
        {
            Dispatchers.AssertInTestingMode();

            return(LibGleanFFI.glean_experiment_test_is_active(experimentId) != 0);
        }
Exemple #9
0
        public void Initialize(
            string applicationId,
            string applicationVersion,
            bool uploadEnabled,
            Configuration configuration,
            string dataDir
            )
        {
            /*
             * // Glean initialization must be called on the main thread, or lifecycle
             * // registration may fail. This is also enforced at build time by the
             * // @MainThread decorator, but this run time check is also performed to
             * // be extra certain.
             * ThreadUtils.assertOnUiThread()
             *
             * // In certain situations Glean.initialize may be called from a process other than the main
             * // process.  In this case we want initialize to be a no-op and just return.
             * if (!isMainProcess(applicationContext)) {
             *  Log.e(LOG_TAG, "Attempted to initialize Glean on a process other than the main process")
             *  return
             * }
             *
             * if (isInitialized()) {
             *  Log.e(LOG_TAG, "Glean should not be initialized multiple times")
             *  return
             * }
             *
             * this.applicationContext = applicationContext*/

            this.configuration = configuration;
            httpClient         = new BaseUploader(configuration.httpClient);
            // this.gleanDataDir = File(applicationContext.applicationInfo.dataDir, GLEAN_DATA_DIR)

            SetUploadEnabled(uploadEnabled);

            Dispatchers.ExecuteTask(() =>
            {
                RegisterPings(GleanInternalPings);

                LibGleanFFI.FfiConfiguration cfg = new LibGleanFFI.FfiConfiguration
                {
                    data_dir               = dataDir,
                    package_name           = applicationId,
                    upload_enabled         = uploadEnabled,
                    max_events             = configuration.maxEvents ?? null,
                    delay_ping_lifetime_io = false
                };

                initialized = LibGleanFFI.glean_initialize(cfg) != 0;

                // If initialization of Glean fails we bail out and don't initialize further.
                if (!initialized)
                {
                    return;
                }

                // If any pings were registered before initializing, do so now.
                // We're not clearing this queue in case Glean is reset by tests.
                lock (this)
                {
                    foreach (var ping in pingTypeQueue)
                    {
                        RegisterPingType(ping);
                    }
                }

                // If this is the first time ever the Glean SDK runs, make sure to set
                // some initial core metrics in case we need to generate early pings.
                // The next times we start, we would have them around already.
                bool isFirstRun = LibGleanFFI.glean_is_first_run() != 0;
                if (isFirstRun)
                {
                    InitializeCoreMetrics();
                }


                // Deal with any pending events so we can start recording new ones
                bool pingSubmitted = LibGleanFFI.glean_on_ready_to_submit_pings() != 0;

                // We need to enqueue the BaseUploader in these cases:
                // 1. Pings were submitted through Glean and it is ready to upload those pings;
                // 2. Upload is disabled, to upload a possible deletion-request ping.
                if (pingSubmitted || !uploadEnabled)
                {
                    httpClient.TriggerUpload(configuration);
                }

                /*
                 * // Set up information and scheduling for Glean owned pings. Ideally, the "metrics"
                 * // ping startup check should be performed before any other ping, since it relies
                 * // on being dispatched to the API context before any other metric.
                 * metricsPingScheduler = MetricsPingScheduler(applicationContext)
                 * metricsPingScheduler.schedule()
                 *
                 * // Check if the "dirty flag" is set. That means the product was probably
                 * // force-closed. If that's the case, submit a 'baseline' ping with the
                 * // reason "dirty_startup". We only do that from the second run.
                 * if (!isFirstRun && LibGleanFFI.INSTANCE.glean_is_dirty_flag_set().toBoolean()) {
                 *  submitPingByNameSync("baseline", "dirty_startup")
                 *  // Note: while in theory we should set the "dirty flag" to true
                 *  // here, in practice it's not needed: if it hits this branch, it
                 *  // means the value was `true` and nothing needs to be done.
                 * }*/

                // From the second time we run, after all startup pings are generated,
                // make sure to clear `lifetime: application` metrics and set them again.
                // Any new value will be sent in newly generated pings after startup.
                if (!isFirstRun)
                {
                    LibGleanFFI.glean_clear_application_lifetime_metrics();
                    InitializeCoreMetrics();
                }

                // Signal Dispatcher that init is complete
                Dispatchers.FlushQueuedInitialTasks();

                /*
                 * // At this point, all metrics and events can be recorded.
                 * // This should only be called from the main thread. This is enforced by
                 * // the @MainThread decorator and the `assertOnUiThread` call.
                 * MainScope().launch {
                 *  ProcessLifecycleOwner.get().lifecycle.addObserver(gleanLifecycleObserver)
                 * }*/
            });
        }