/// <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); }); }
/// <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 ); }); }
/// <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)); }
/// <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); }); }
/// <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); } }); }
/// <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; } }
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; }
/// <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); }
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) * }*/ }); }