private Glean() { // Private constructor to disallow instantiation since // this is meant to be a singleton. It only wires up the // glean-core logging on the Rust side. LibGleanFFI.glean_enable_logging(); }
/// <summary> /// Get the specific metric for a given label. /// /// If a set of acceptable labels were specified in the metrics.yaml file, /// and the given label is not in the set, it will be recorded under the /// special `__other__`. /// /// If a set of acceptable labels was not specified in the metrics.yaml file, /// only the first 16 unique labels will be used. After that, any additional /// labels will be recorded under the special `__other__` label. /// /// Labels must be snake_case and less than 30 characters. If an invalid label /// is used, the metric will be recorded in the special `__other__` label. /// </summary> /// <param name="label">The label</param> /// <exception cref="InvalidOperationException"> /// If the type of `T` is currently not supported. /// </exception> public T this[string label] { get { // Note the double `(T)(ILabeledSubmetricInterface)` cast before returning. This is // required in order to make the compiler not complain. Since the `where` clause for // this class cannot list more than one class, we need all our supported subtypes to // implement a common interface and use that interface as the T type constraint. This // allows us to then explicitly cast back to T, which is otherwise impossible. switch (submetric) { case BooleanMetricType _: { UInt64 handle = LibGleanFFI.glean_labeled_boolean_metric_get(this.handle, label); return((T)(ILabeledSubmetricInterface) new BooleanMetricType(handle, disabled, sendInPings)); } case CounterMetricType _: { UInt64 handle = LibGleanFFI.glean_labeled_counter_metric_get(this.handle, label); return((T)(ILabeledSubmetricInterface) new CounterMetricType(handle, disabled, sendInPings)); } case StringMetricType _: { UInt64 handle = LibGleanFFI.glean_labeled_string_metric_get(this.handle, label); return((T)(ILabeledSubmetricInterface) new StringMetricType(handle, disabled, sendInPings)); } default: throw new InvalidOperationException("Can not get a submetric of this metric type"); } } }
/// <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> /// Set a datetime value, truncating it to the metric's resolution. /// </summary> /// <param name="value"> The [Date] value to set. If not provided, will record the current time. public void Set(DateTimeOffset value = new DateTimeOffset()) { if (disabled) { return; } // The current time of datetime offset. var currentTime = value.DateTime; // InvariantCulture calendar still preserves timezones and locality information, // it just formats them in a way to ease persistence. var calendar = CultureInfo.InvariantCulture.Calendar; Dispatchers.LaunchAPI(() => { LibGleanFFI.glean_datetime_set( handle, year: calendar.GetYear(currentTime), month: calendar.GetMonth(currentTime), day: calendar.GetDayOfMonth(currentTime), hour: calendar.GetHour(currentTime), minute: calendar.GetMinute(currentTime), second: calendar.GetSecond(currentTime), nano: Convert.ToInt64(1000000 * calendar.GetMilliseconds(currentTime)), offset_seconds: Convert.ToInt32((currentTime - value.UtcDateTime).TotalSeconds) );; }); }
/// <summary> /// Returns the number of errors recorded for the given metric. /// </summary> /// <param name="errorType">The type of the error recorded.</param> /// <param name="pingName">Represents the name of the ping to retrieve the metric for. /// Defaults to the first value in `sendInPings`.</param> /// <returns>the number of errors recorded for the metric.</returns> public Int32 TestGetNumRecordedErrors(Testing.ErrorType errorType, string pingName = null) { Dispatchers.AssertInTestingMode(); string ping = pingName ?? sendInPings[0]; switch (submetric) { case BooleanMetricType _: { return(LibGleanFFI.glean_labeled_boolean_test_get_num_recorded_errors(handle, (int)errorType, ping)); } case CounterMetricType _: { return(LibGleanFFI.glean_labeled_counter_test_get_num_recorded_errors(handle, (int)errorType, ping)); } case StringMetricType _: { return(LibGleanFFI.glean_labeled_string_test_get_num_recorded_errors(handle, (int)errorType, ping)); } default: throw new InvalidOperationException("Can not return errors for this metric type"); } }
/// <summary> /// Tests whether a value is stored for the metric for testing purposes only. This function will /// attempt to await the last task (if any) writing to the the metric's storage engine before /// returning a value. /// </summary> /// <param name="pingName">represents the name of the ping to retrieve the metric for Defaults /// to the first value in `sendInPings`</param> /// <returns>true if metric value exists, otherwise false</returns> public bool TestHasValue(string pingName = null) { Dispatchers.AssertInTestingMode(); string ping = pingName ?? sendInPings[0]; return(LibGleanFFI.glean_string_test_has_value(this.handle, ping) != 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)); }
/// <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> /// Internal only, synchronous API for setting a boolean value. /// </summary> /// <param name="value">This is a user defined boolean value.</param> internal void SetSync(bool value) { if (disabled) { return; } LibGleanFFI.glean_boolean_set(this.handle, Convert.ToByte(value)); }
/// <summary> /// Internal only, synchronous API for setting a string value. /// </summary> /// <param name="value">This is a user defined string value. If the length of the string exceeds /// the maximum length, it will be truncated</param> internal void SetSync(string value) { if (disabled) { return; } LibGleanFFI.glean_string_set(this.handle, value); }
/// <summary> /// Internal only, synchronous API for setting a UUID value. /// </summary> /// <param name="value">This is a user defined boolean value.</param> internal void SetSync(Guid value) { if (disabled) { return; } LibGleanFFI.glean_uuid_set(this.handle, value.ToString()); }
/// <summary> /// Add to counter value synchronously. /// /// This is only to be used within the Glean SDK. /// </summary> /// <param name="amount">this is the amount to increment the counter by, defaulting to 1 /// if called without parameters.</param> internal void AddSync(Int32 amount) { if (disabled) { return; } LibGleanFFI.glean_counter_add(handle, amount); }
/** * Returns the number of errors recorded for the given metric. * * @param errorType The type of the error recorded. * @param pingName represents the name of the ping to retrieve the metric for. * Defaults to the first value in `sendInPings`. * @return the number of errors recorded for the metric. */ public Int32 TestGetNumRecordedErrors(Testing.ErrorType errorType, string pingName = null) { Dispatchers.AssertInTestingMode(); string ping = pingName ?? sendInPings[0]; return(LibGleanFFI.glean_string_test_get_num_recorded_errors( this.handle, (int)errorType, ping )); }
/// <summary> /// Explicitly set the timespan value, in nanoseconds. /// /// This API should only be used if your library or application requires recording /// times in a way that can not make use of `Start`/`Stop`/`Cancel`. /// /// `SetRawNanos` does not overwrite a running timer or an already existing value. /// </summary> /// <param name="elapsedNanos">The elapsed time to record, in nanoseconds.</param> public void SetRawNanos(ulong elapsedNanos) { if (disabled) { return; } Dispatchers.LaunchAPI(() => { LibGleanFFI.glean_timespan_set_raw_nanos(handle, elapsedNanos); }); }
/// <summary> /// Set a quantity value. /// </summary> /// <param name="value">The value to set. Must be non-negative.</param> public void Set(Int32 value) { if (disabled) { return; } Dispatchers.LaunchAPI(() => { LibGleanFFI.glean_quantity_set(this.handle, value); }); }
/// <summary> /// Record a single value, in the unit specified by `memoryUnit`, to the distribution. /// </summary> /// <param name="sample">the value</param> public void Accumulate(UInt64 sample) { if (disabled) { return; } Dispatchers.LaunchAPI(() => { LibGleanFFI.glean_memory_distribution_accumulate(handle, sample); }); }
/// <summary> /// TEST ONLY FUNCTION. /// Destroy the owned glean-core handle. /// </summary> internal void TestDestroyGleanHandle() { if (!IsInitialized()) { // We don't need to destroy Glean: it wasn't initialized. return; } LibGleanFFI.glean_destroy_glean(); initialized = false; }
/// <summary> /// Set a JWE value. /// </summary> /// <param name="value"> The [`compact representation`](https://tools.ietf.org/html/rfc7516#appendix-A.2.7) of a JWE value.</param> public void setWithCompactRepresentation(string value) { if (disabled) { return; } Dispatchers.LaunchAPI(() => { LibGleanFFI.glean_jwe_set_with_compact_representation(this.handle, value); }); }
/// <summary> /// Build a JWE value from it's elements and set to it. /// </summary> /// <param name="header">A variable-size JWE protected header.</param> /// <param name="key">A variable-size encrypted key. /// This can be an empty octet sequence.</param> /// <param name="initVector">A fixed-size, 96-bit, base64 encoded Jwe initialization vector. /// If not required by the encryption algorithm, can be an empty octet sequence.</param> /// <param name="cipherText">The variable-size base64 encoded cipher text.</param> /// <param name="authTag">A fixed-size, 132-bit, base64 encoded authentication tag. /// Can be an empty octet sequence.</param> public void Set(string header, string key, string initVector, string cipherText, string authTag) { if (disabled) { return; } Dispatchers.LaunchAPI(() => { LibGleanFFI.glean_jwe_set(this.handle, header, key, initVector, cipherText, authTag); }); }
/// <summary> /// Abort a previous `Start` call. No error is recorded if no `Start` was called. /// </summary> public void Cancel() { if (disabled) { return; } Dispatchers.LaunchAPI(() => { LibGleanFFI.glean_timespan_cancel(handle); }); }
/// <summary> /// Get whether or not Glean is allowed to record and upload data. /// /// Caution: the result is only correct if Glean is already initialized. /// </summary> /// <returns>`true` if Glean is allowed to record and upload data.</returns> bool GetUploadEnabled() { if (IsInitialized()) { return(LibGleanFFI.glean_is_upload_enabled() != 0); } else { return(false); } }
/// <summary> /// Sets a string list to one or more metric stores in a synchronous way. /// This is only to be used for the glean-ac to glean-core data migration. /// </summary> /// <param name="value">This is a user defined string list.</param> internal void SetSync(string[] value) { if (disabled) { return; } LibGleanFFI.glean_string_list_set( this.handle, value, value.Length); }
/// <summary> /// Abort a previous `Start` call. No error is recorded if no `Start` was called. /// </summary> /// <param name="timerId"> /// The `GleanTimerId` associated with this timing. This allows for concurrent timing /// of events associated with different ids to the same timing distribution metric. /// </param> public void Cancel(GleanTimerId timerId) { if (disabled || timerId == null) { return; } Dispatchers.LaunchAPI(() => { LibGleanFFI.glean_timing_distribution_cancel(handle, timerId); }); }
/// <summary> /// Appends a string value to one or more string list metric stores. /// If the length of the string exceeds the maximum length, it will be truncated. /// </summary> /// <param name="value">This is a user defined string value.</param> public void Add(string value) { if (disabled) { return; } Dispatchers.LaunchAPI(() => { LibGleanFFI.glean_string_list_add( this.handle, value); }); }
/// <summary> /// Stop tracking time for the provided metric. /// Sets the metric to the elapsed time, but does not overwrite an already /// existing value. /// This will record an error if no `Start` was called or there is an already /// existing value. /// </summary> public void Stop() { if (disabled) { return; } ulong stopTime = HighPrecisionTimestamp.GetTimestamp(TimeUnit.Nanosecond); Dispatchers.LaunchAPI(() => { LibGleanFFI.glean_timespan_set_stop(handle, stopTime); }); }
/// <summary> /// Returns the stored value for testing purposes only. This function will attempt to await the /// last task (if any) writing to the the metric's storage engine before returning a value. /// @throws [NullPointerException] if no value is stored /// </summary> /// <param name="pingName">represents the name of the ping to retrieve the metric for. /// Defaults to the first value in `sendInPings`</param> /// <returns>value of the stored metric</returns> /// <exception cref="System.NullReferenceException">Thrown when the metric contains no value</exception> public string TestGetValue(string pingName = null) { Dispatchers.AssertInTestingMode(); if (!TestHasValue(pingName)) { throw new NullReferenceException(); } string ping = pingName ?? sendInPings[0]; return(LibGleanFFI.glean_string_test_get_value(this.handle, ping).AsString()); }
/// <summary> /// TEST ONLY FUNCTION. /// Destroy the owned glean-core handle. /// </summary> internal void TestDestroyGleanHandle() { if (!IsInitialized()) { // We don't need to destroy Glean: it wasn't initialized. return; } LibGleanFFI.glean_destroy_glean(); // Reset all state. Dispatchers.QueueInitialTasks = true; initFinished = false; initialized = false; }
/// <summary> /// Returns the stored value for testing purposes only. This function will attempt to await the /// last task (if any) writing to the the metric's storage engine before returning a value. /// </summary> /// <param name="pingName">represents the name of the ping to retrieve the metric for. /// Defaults to the first value in `sendInPings`</param> /// <returns>value of the stored metric</returns> /// <exception cref="System.NullReferenceException">Thrown when the metric contains no value</exception> public DistributionData TestGetValue(string pingName = null) { Dispatchers.AssertInTestingMode(); if (!TestHasValue(pingName)) { throw new NullReferenceException(); } string ping = pingName ?? sendInPings[0]; return(DistributionData.FromJsonString( LibGleanFFI.glean_timing_distribution_test_get_value_as_json_string(handle, ping).AsString())); }
public StringMetricType( bool disabled, string category, Lifetime lifetime, string name, string[] sendInPings ) : this(0, disabled, sendInPings) { handle = LibGleanFFI.glean_new_string_metric( category: category, name: name, send_in_pings: sendInPings, send_in_pings_len: sendInPings.Length, lifetime: (int)lifetime, disabled: disabled); }
/// <summary> /// Start tracking time for the provided metric. This records an error if /// it’s already tracking time (i.e. start was already called with no /// corresponding `StopAndAccumulate`): in that case the original start time will /// be preserved. /// </summary> /// <returns> /// The `GleanTimerId` object to associate with this timing or `null` /// if the collection was disabled. /// </returns> public GleanTimerId Start() { if (disabled) { return(null); } // Even though the Rust code for `Start` runs synchronously, the Rust // code for `StopAndAccumulate` runs asynchronously, and we need to use the same // clock for start and stop. Therefore we take the time on the C# side, both // here and in `StopAndAccumulate`. ulong startTime = HighPrecisionTimestamp.GetTimestamp(TimeUnit.Nanosecond); // No dispatcher, we need the return value return((GleanTimerId)LibGleanFFI.glean_timing_distribution_set_start(handle, startTime)); }