зеркало из https://github.com/mozilla/glean.git
Implement the C# experiments API
This closely follows the Kotlin implementation.
This commit is contained in:
Родитель
74964cc36b
Коммит
1263a0b3aa
|
@ -5,6 +5,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Mozilla.Glean.FFI;
|
||||
|
@ -324,6 +325,87 @@ namespace Mozilla.Glean
|
|||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// 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>
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// TEST ONLY FUNCTION.
|
||||
/// Resets the Glean state and triggers init again.
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/* 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/. */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
/// <summary>
|
||||
/// Deserialized experiment data.
|
||||
/// </summary>
|
||||
public sealed class RecordedExperimentData
|
||||
{
|
||||
/// <summary>
|
||||
/// The experiment's branch as set through `SetExperimentActive`.
|
||||
/// </summary>
|
||||
public readonly string Branch;
|
||||
|
||||
/// <summary>
|
||||
/// Any extra data associated with this experiment through `SetExperimentActive`.
|
||||
/// </summary>
|
||||
public readonly Dictionary<string, string> Extra;
|
||||
|
||||
// This constructor is only useful for tests.
|
||||
internal RecordedExperimentData() { }
|
||||
|
||||
RecordedExperimentData(string branch, Dictionary<string, string> extra)
|
||||
{
|
||||
Branch = branch;
|
||||
Extra = extra;
|
||||
}
|
||||
|
||||
public static RecordedExperimentData FromJsonString(string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
JsonDocument data = JsonDocument.Parse(json);
|
||||
JsonElement root = data.RootElement;
|
||||
|
||||
string branch = root.GetProperty("branch").GetString();
|
||||
Dictionary<string, string> processedExtra = new Dictionary<string, string>();
|
||||
JsonElement rawExtraMap = root.GetProperty("extra");
|
||||
foreach (var entry in rawExtraMap.EnumerateObject())
|
||||
{
|
||||
processedExtra.Add(entry.Name, entry.Value.GetString());
|
||||
}
|
||||
return new RecordedExperimentData(branch, processedExtra);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
// 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/.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
using static Mozilla.Glean.Glean;
|
||||
|
@ -34,6 +35,61 @@ namespace Mozilla.Glean.Tests
|
|||
GleanInstance.HandleBackgroundEvent();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestExperimentsRecording()
|
||||
{
|
||||
GleanInstance.SetExperimentActive(
|
||||
"experiment_test", "branch_a"
|
||||
);
|
||||
GleanInstance.SetExperimentActive(
|
||||
"experiment_api", "branch_b",
|
||||
new Dictionary<string, string>() { { "test_key", "value" } }
|
||||
);
|
||||
Assert.True(GleanInstance.TestIsExperimentActive("experiment_api"));
|
||||
Assert.True(GleanInstance.TestIsExperimentActive("experiment_test"));
|
||||
|
||||
GleanInstance.SetExperimentInactive("experiment_test");
|
||||
|
||||
Assert.True(GleanInstance.TestIsExperimentActive("experiment_api"));
|
||||
Assert.False(GleanInstance.TestIsExperimentActive("experiment_test"));
|
||||
|
||||
var storedData = GleanInstance.TestGetExperimentData("experiment_api");
|
||||
Assert.Equal("branch_b", storedData.Branch);
|
||||
Assert.Single(storedData.Extra);
|
||||
Assert.Equal("value", storedData.Extra["test_key"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestExperimentsRecordingBeforeGleanInits()
|
||||
{
|
||||
// This test relies on Glean not being initialized and task queuing to be on.
|
||||
GleanInstance.TestDestroyGleanHandle();
|
||||
Dispatchers.QueueInitialTasks = true;
|
||||
|
||||
GleanInstance.SetExperimentActive(
|
||||
"experiment_set_preinit", "branch_a"
|
||||
);
|
||||
|
||||
GleanInstance.SetExperimentActive(
|
||||
"experiment_preinit_disabled", "branch_a"
|
||||
);
|
||||
|
||||
GleanInstance.SetExperimentInactive("experiment_preinit_disabled");
|
||||
|
||||
// This will init glean and flush the dispatcher's queue.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
|
||||
Assert.True(GleanInstance.TestIsExperimentActive("experiment_set_preinit"));
|
||||
Assert.False(GleanInstance.TestIsExperimentActive("experiment_preinit_disabled"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SettingMaxEventsDoesNotCrash()
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче