зеркало из https://github.com/mozilla/glean.git
Remove the Glean C# SDK codebase
The Glean C# SDK had been abandoned for quite some time
and officially been marked deprecated in September (9836b2063d
).
This just sunsets it officially by removing the codebase from the repository.
This commit is contained in:
Родитель
3960ab54cc
Коммит
16c929adf5
10
Makefile
10
Makefile
|
@ -51,10 +51,7 @@ build-apk: build-kotlin ## Build an apk of the Glean sample app
|
|||
build-python: python-setup ## Build the Python bindings
|
||||
$(GLEAN_PYENV)/bin/python3 glean-core/python/setup.py build install
|
||||
|
||||
build-csharp: ## Build the C# bindings
|
||||
dotnet build glean-core/csharp/csharp.sln
|
||||
|
||||
.PHONY: build build-rust build-kotlin build-swift build-apk build-csharp
|
||||
.PHONY: build build-rust build-kotlin build-swift build-apk
|
||||
|
||||
# All tests
|
||||
|
||||
|
@ -81,10 +78,7 @@ test-ios-sample: ## Run the iOS UI tests on the sample app
|
|||
test-python: build-python ## Run all Python tests
|
||||
$(GLEAN_PYENV)/bin/py.test glean-core/python/tests $(PYTEST_ARGS)
|
||||
|
||||
test-csharp: ## Run all C# tests
|
||||
dotnet test glean-core/csharp/csharp.sln
|
||||
|
||||
.PHONY: test test-rust test-rust-with-logs test-kotlin test-swift test-ios-sample test-csharp
|
||||
.PHONY: test test-rust test-rust-with-logs test-kotlin test-swift test-ios-sample
|
||||
|
||||
# Benchmarks
|
||||
|
||||
|
|
|
@ -140,14 +140,6 @@ run $SED -i.bak -E \
|
|||
"${WORKSPACE_ROOT}/${FILE}"
|
||||
run rm "${WORKSPACE_ROOT}/${FILE}.bak"
|
||||
|
||||
### GLEAN C# BINDINGS ###
|
||||
|
||||
FILE=glean-core/csharp/Glean/Glean.csproj
|
||||
run $SED -i.bak -E \
|
||||
-e "s/<Version>[0-9A-Z.-]+<\/Version>/<Version>${NEW_VERSION}<\/Version>/" \
|
||||
"${WORKSPACE_ROOT}/${FILE}"
|
||||
run rm "${WORKSPACE_ROOT}/${FILE}.bak"
|
||||
|
||||
### CHANGELOG ###
|
||||
|
||||
FILE=CHANGELOG.md
|
||||
|
|
|
@ -56,13 +56,6 @@ run $SED -i.bak -E \
|
|||
"${WORKSPACE_ROOT}/${FILE}"
|
||||
run rm "${WORKSPACE_ROOT}/${FILE}.bak"
|
||||
|
||||
# update the version in glean-core/csharp/Glean/GleanParser.cs
|
||||
FILE=glean-core/csharp/Glean/GleanParser.cs
|
||||
run $SED -i.bak -E \
|
||||
-e "s/GleanParserVersion = \"[0-9.]+\"/GleanParserVersion = \"${NEW_VERSION}\"/" \
|
||||
"${WORKSPACE_ROOT}/${FILE}"
|
||||
run rm "${WORKSPACE_ROOT}/${FILE}.bak"
|
||||
|
||||
# update the version in glean-core/Cargo.toml
|
||||
FILE=glean-core/Cargo.toml
|
||||
run $SED -i.bak -E \
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
// 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 Mozilla.Glean.Net;
|
||||
|
||||
namespace Mozilla.Glean
|
||||
{
|
||||
public sealed class Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// The default server pings are sent to.
|
||||
/// </summary>
|
||||
public const string DefaultTelemetryEndpoint = "https://incoming.telemetry.mozilla.org";
|
||||
|
||||
public string serverEndpoint;
|
||||
public string channel;
|
||||
public string buildId;
|
||||
public int? maxEvents;
|
||||
|
||||
public IPingUploader httpClient;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for Glean.
|
||||
/// </summary>
|
||||
/// <param name="serverEndpoint"> the server pings are sent to. Please note that this
|
||||
/// is only meant to be changed for tests.</param>
|
||||
/// <param name="channel">the release channel the application is on, if known. This will be
|
||||
/// sent along with all the pings, in the `client_info` section.</param>
|
||||
/// <param name="buildId">a build identifier generated by the CI system</param>
|
||||
/// <param name="maxEvents">the number of events to store before the events ping is sent.</param>
|
||||
/// <param name="httpClient">The HTTP client implementation to use for uploading pings.</param>
|
||||
public Configuration(
|
||||
string serverEndpoint = DefaultTelemetryEndpoint,
|
||||
string channel = null,
|
||||
string buildId = null,
|
||||
int? maxEvents = null,
|
||||
IPingUploader httpClient = null)
|
||||
{
|
||||
this.serverEndpoint = serverEndpoint;
|
||||
this.channel = channel;
|
||||
this.buildId = buildId;
|
||||
this.maxEvents = maxEvents;
|
||||
this.httpClient = httpClient ?? new HttpClientUploader();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,223 +0,0 @@
|
|||
// 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.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Schedulers;
|
||||
using Serilog;
|
||||
using static Mozilla.Glean.Utils.GleanLogger;
|
||||
|
||||
namespace Mozilla.Glean
|
||||
{
|
||||
internal static class Dispatchers
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the tag used for logging from this class.
|
||||
/// </summary>
|
||||
private const string LogTag = "glean/Dispatchers";
|
||||
|
||||
/// <summary>
|
||||
/// This is the number of milliseconds that are allowed for the initial
|
||||
/// tasks queue to process all of the queued tasks.
|
||||
/// </summary>
|
||||
private const int QueueProcessingTimeoutMs = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// This is the maximum number of tasks that will be queued before
|
||||
/// Glean is initialized.
|
||||
/// </summary>
|
||||
internal const int MaxQueueSize = 100;
|
||||
|
||||
/// <summary>
|
||||
/// When true, tasks will be executed synchronously.
|
||||
/// </summary>
|
||||
internal static bool TestingMode { get; set; } = false;
|
||||
|
||||
// Private backing field for QueueInitialTasks.
|
||||
private static int _queueInitialTasks = 1;
|
||||
/// <summary>
|
||||
/// When true, tasks will be queued and not ran until triggered by
|
||||
/// calling FlushQueuedInitialTasks. This uses an int backing field
|
||||
/// in order to take advantage of Interlocked.Exchange for thread
|
||||
/// safety.
|
||||
/// </summary>
|
||||
internal static bool QueueInitialTasks
|
||||
{
|
||||
get => _queueInitialTasks == 1;
|
||||
set => Interlocked.Exchange(ref _queueInitialTasks, value ? 1 : 0);
|
||||
}
|
||||
|
||||
// Create a scheduler that uses a single thread.
|
||||
private static readonly LimitedConcurrencyLevelTaskScheduler
|
||||
apiScheduler = new LimitedConcurrencyLevelTaskScheduler(1);
|
||||
|
||||
// Create a new TaskFactory and pass it the scheduler.
|
||||
private static readonly TaskFactory factory = new TaskFactory(apiScheduler);
|
||||
|
||||
/// <summary>
|
||||
/// This Queue holds the initial Actions that are launched before Glean
|
||||
/// is initialized.
|
||||
/// </summary>
|
||||
internal static ConcurrentQueue<Action> preInitActionQueue =
|
||||
new ConcurrentQueue<Action>();
|
||||
|
||||
/// <summary>
|
||||
/// The number of Actions added to the queue beyond the MaxQueueSize.
|
||||
/// </summary>
|
||||
private static int overflowCount = 0;
|
||||
|
||||
/// <summary>
|
||||
/// A logger configured for this class.
|
||||
/// </summary>
|
||||
private static readonly ILogger Log = GetLogger(LogTag);
|
||||
|
||||
/// <summary>
|
||||
/// Launch a block of work asynchronously.
|
||||
///
|
||||
/// Takes an Action and launches it using the TaskFactory ensuring the
|
||||
/// tasks are executed serially in the order the were launched.
|
||||
///
|
||||
/// If `QueueInitialTasks` is enabled, then the operation will be
|
||||
/// created and added to the `preInitActionQueue` but not executed until
|
||||
/// flushed.
|
||||
///
|
||||
/// If `TestingMode` is enabled, then `LaunchAPI` will execute the task
|
||||
/// immediately and synchronously to avoid asynchronous issues in tests.
|
||||
/// </summary>
|
||||
/// <param name="action">The Action to invoke</param>
|
||||
/// <returns>A Task or null if queued or run synchronously</returns>
|
||||
internal static Task LaunchAPI(Action action)
|
||||
{
|
||||
Task task = null;
|
||||
|
||||
// Wrap the provided action in a try/catch block: we don't want to
|
||||
// break execution if something throws.
|
||||
Action safeAction = () => {
|
||||
try
|
||||
{
|
||||
action.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception thrown by task and swallowed.");
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
if (QueueInitialTasks)
|
||||
{
|
||||
// If we are queuing, typically before Glean has been
|
||||
// initialized, then we should just add the action to
|
||||
// the queue.
|
||||
AddActionToQueue(safeAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!TestingMode)
|
||||
{
|
||||
// If we are not queuing we can go ahead and execute the
|
||||
// task asynchronously
|
||||
task = factory.StartNew(safeAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we are in testing mode, then go ahead and await
|
||||
// the task to ensure synchronous execution.
|
||||
safeAction.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops queueing Actions and processes any Actions in the queue.
|
||||
/// </summary>
|
||||
internal static void FlushQueuedInitialTasks()
|
||||
{
|
||||
factory.StartNew(() =>
|
||||
{
|
||||
QueueInitialTasks = false;
|
||||
|
||||
while (preInitActionQueue.TryDequeue(out Action action))
|
||||
{
|
||||
action.Invoke();
|
||||
}
|
||||
}).Wait();
|
||||
|
||||
// This must happen after `QueueInitialTasks = false` is run, or
|
||||
// it would be added to a full task queue and be silently dropped.
|
||||
if (overflowCount > 0)
|
||||
{
|
||||
//TODO GleanError.preinitTasksOverflow
|
||||
// .addSync(MaxQueueSize + OverflowCount)
|
||||
}
|
||||
}
|
||||
|
||||
internal static Task ExecuteTask(Action action)
|
||||
{
|
||||
if (TestingMode)
|
||||
{
|
||||
// If we are in testing mode, then invoke the action and return
|
||||
// null
|
||||
action.Invoke();
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// **NOTE** This does not ensure that this task is executed at
|
||||
// the front of the apiScheduler. This will be addressed with
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1646750
|
||||
return factory.StartNew(action);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper function to add an Action to the queue and monitor the
|
||||
/// MaxQueueSize.
|
||||
/// </summary>
|
||||
/// <param name="task">The Task to add to the queue</param>
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
private static void AddActionToQueue(Action action)
|
||||
{
|
||||
if (preInitActionQueue.Count >= MaxQueueSize)
|
||||
{
|
||||
Log.Error("Exceeded maximum queue size, discarding task");
|
||||
|
||||
// This value ends up in the `preinit_tasks_overflow` metric,
|
||||
// but we can't record directly there, because that would only
|
||||
// add the recording to an already-overflowing task queue and
|
||||
// would be silently dropped.
|
||||
overflowCount += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (TestingMode)
|
||||
{
|
||||
Log.Information("Task queued for execution in test mode");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Information("Task queued for execution and delayed until flushed");
|
||||
}
|
||||
|
||||
preInitActionQueue.Enqueue(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper function to ensure the Glean SDK is being used in testing
|
||||
/// mode and async jobs are being run synchronously. This should be
|
||||
/// called from every method of the testing API to make sure that the
|
||||
/// results of the main API can be tested as expected.
|
||||
/// </summary>
|
||||
public static void AssertInTestingMode()
|
||||
{
|
||||
Debug.Assert(TestingMode);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,636 +0,0 @@
|
|||
// 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.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Mozilla.Glean.FFI;
|
||||
using Mozilla.Glean.Net;
|
||||
using Mozilla.Glean.Private;
|
||||
using Serilog;
|
||||
using static Mozilla.Glean.GleanMetrics.GleanInternalMetricsDefinition;
|
||||
using static Mozilla.Glean.GleanMetrics.PingsDefinition;
|
||||
using static Mozilla.Glean.Utils.GleanLogger;
|
||||
|
||||
namespace Mozilla.Glean
|
||||
{
|
||||
/// <summary>
|
||||
/// The Glean Gneral API.
|
||||
/// </summary>
|
||||
public sealed class Glean
|
||||
{
|
||||
// Initialize the singleton using the `Lazy` facilities.
|
||||
private static readonly Lazy<Glean>
|
||||
lazy = new Lazy<Glean>(() => new Glean());
|
||||
public static Glean GleanInstance => lazy.Value;
|
||||
|
||||
private bool initialized = false;
|
||||
// Set when `initialize()` returns.
|
||||
// This allows to detect calls that happen before `Glean.initialize()` was called.
|
||||
// Note: The initialization might still be in progress, as it runs in a separate thread.
|
||||
private bool initFinished = false;
|
||||
|
||||
// Keep track of ping types that have been registered before Glean is initialized.
|
||||
private HashSet<PingTypeBase> pingTypeQueue = new HashSet<PingTypeBase>();
|
||||
|
||||
private Configuration configuration;
|
||||
|
||||
// This is the wrapped http uploading mechanism: provides base functionalities
|
||||
// for logging and delegates the actual upload to the implementation in
|
||||
// the `Configuration`.
|
||||
private BaseUploader httpClient;
|
||||
|
||||
// The version of the application sending Glean data.
|
||||
private string applicationVersion;
|
||||
|
||||
/// <summary>
|
||||
/// This is the tag used for logging from this class.
|
||||
/// </summary>
|
||||
private const string LogTag = "glean/Glean";
|
||||
|
||||
/// <summary>
|
||||
/// This is the name of the language used by this Glean binding.
|
||||
/// </summary>
|
||||
private readonly static string LanguageBindingName = "C#";
|
||||
|
||||
/// <summary>
|
||||
/// A logger configured for this class
|
||||
/// </summary>
|
||||
private static readonly ILogger Log = GetLogger(LogTag);
|
||||
|
||||
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>
|
||||
/// Initialize the Glean SDK.
|
||||
///
|
||||
/// This should only be initialized once by the application, and not by
|
||||
/// libraries using the Glean SDK. A message is logged to error and no
|
||||
/// changes are made to the state if initialize is called a more than
|
||||
/// once.
|
||||
///
|
||||
/// This method must be called from the main thread.
|
||||
/// </summary>
|
||||
/// <param name="applicationId">The application id to use when sending pings.</param>
|
||||
/// <param name="applicationVersion">The version of the application sending
|
||||
/// Glean data.</param>
|
||||
/// <param name="uploadEnabled">A `bool` that determines whether telemetry is enabled.
|
||||
/// If disabled, all persisted metrics, events and queued pings (except first_run_date)
|
||||
/// are cleared.</param>
|
||||
/// <param name="configuration">A Glean `Configuration` object with global settings</param>
|
||||
/// <param name="dataDir">The path to the Glean data directory.</param>
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
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>
|
||||
/// Whether or not the Glean SDK has been initialized.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the Glean SDK has been initialized.</returns>
|
||||
internal bool IsInitialized()
|
||||
{
|
||||
return initialized;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// 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>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="applicationId">The application id to use when sending pings.</param>
|
||||
/// <param name="applicationVersion">The version of the application sending
|
||||
/// Glean data.</param>
|
||||
/// <param name="uploadEnabled">A `bool` that determines whether telemetry is enabled.
|
||||
/// If disabled, all persisted metrics, events and queued pings (except first_run_date)
|
||||
/// are cleared.</param>
|
||||
/// <param name="configuration">A Glean `Configuration` object with global settings</param>
|
||||
/// <param name="dataDir">The path to the Glean data directory.</param>
|
||||
/// <param name="clearStores">If `true` clear the contents of all stores.</param>
|
||||
internal void Reset(
|
||||
string applicationId,
|
||||
string applicationVersion,
|
||||
bool uploadEnabled,
|
||||
Configuration configuration,
|
||||
string dataDir,
|
||||
bool clearStores = true)
|
||||
{
|
||||
Dispatchers.TestingMode = true;
|
||||
|
||||
if (IsInitialized() && clearStores)
|
||||
{
|
||||
// Clear all the stored data.
|
||||
LibGleanFFI.glean_test_clear_all_stores();
|
||||
}
|
||||
|
||||
// TODO: isMainProcess = null
|
||||
|
||||
// Init Glean.
|
||||
TestDestroyGleanHandle();
|
||||
Initialize(applicationId, applicationVersion, uploadEnabled, configuration, dataDir);
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
private void InitializeCoreMetrics()
|
||||
{
|
||||
// The `Environment.OSVersion` object will return a version that represents an approximate
|
||||
// version of the OS. As the MSDN docs state, this is unreliable:
|
||||
// https://docs.microsoft.com/en-us/dotnet/api/system.environment.osversion?redirectedfrom=MSDN&view=netstandard-2.0
|
||||
// However, there's really nothing we could do about it. Unless the product using the
|
||||
// Glean SDK correctly marks their manifest as Windows 10 compatible, this API will report
|
||||
// Windows 8 version (6.2). See the remarks section here:
|
||||
// https://docs.microsoft.com/en-gb/windows/win32/api/winnt/ns-winnt-osversioninfoexa?redirectedfrom=MSDN#remarks
|
||||
try
|
||||
{
|
||||
GleanInternalMetrics.osVersion.SetSync(Environment.OSVersion.Version.ToString());
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
GleanInternalMetrics.osVersion.SetSync("Unknown");
|
||||
}
|
||||
|
||||
// Possible values for `RuntimeInformation.OSArchitecture` are documented here:
|
||||
// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.architecture?view=netstandard-2.0
|
||||
GleanInternalMetrics.architecture.SetSync(RuntimeInformation.OSArchitecture.ToString());
|
||||
|
||||
try
|
||||
{
|
||||
// CurrentUiCulture is used for representing the locale of the UI, coming from the OS,
|
||||
// while CurrentCulture is the general locale used for other things (e.g. currency).
|
||||
GleanInternalMetrics.locale.SetSync(CultureInfo.CurrentUICulture.Name);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
GleanInternalMetrics.locale.SetSync("und");
|
||||
}
|
||||
|
||||
if (configuration.channel != null)
|
||||
{
|
||||
GleanInternalMetrics.appChannel.SetSync(configuration.channel);
|
||||
}
|
||||
|
||||
GleanInternalMetrics.appDisplayVersion.SetSync(applicationVersion);
|
||||
GleanInternalMetrics.appBuild.SetSync(configuration.buildId ?? "Unknown");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register the pings generated from `ManualPings` with the Glean SDK.
|
||||
/// </summary>
|
||||
/// <param name="pings"> The `Pings` object generated for your library or application
|
||||
/// by the Glean SDK.</param>
|
||||
private void RegisterPings(object pings)
|
||||
{
|
||||
// Instantiating the Pings object to send this function is enough to
|
||||
// call the constructor and have it registered through [Glean.registerPingType].
|
||||
Log.Information("Registering pings");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the background event and send the appropriate pings.
|
||||
/// </summary>
|
||||
internal void HandleBackgroundEvent()
|
||||
{
|
||||
Pings.baseline.Submit(baselineReasonCodes.background);
|
||||
Pings.events.Submit(eventsReasonCodes.background);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collect and submit a ping for eventual upload.
|
||||
///
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="ping">Ping to submit.</param>
|
||||
/// <param name="reason">The reason the ping is being submitted.</param>
|
||||
internal void SubmitPing(PingTypeBase ping, string reason = null)
|
||||
{
|
||||
SubmitPingByName(ping.name, reason);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Collect and submit a ping (by its name) for eventual upload, synchronously.
|
||||
///
|
||||
/// 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 SubmitPingByNameSync(string name, string reason = null)
|
||||
{
|
||||
if (!IsInitialized())
|
||||
{
|
||||
Log.Error("Glean must be initialized before submitting pings.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GetUploadEnabled())
|
||||
{
|
||||
Log.Information("Glean disabled: not submitting any pings.");
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasSubmittedPing = Convert.ToBoolean(LibGleanFFI.glean_submit_ping_by_name(name, reason));
|
||||
if (hasSubmittedPing)
|
||||
{
|
||||
httpClient.TriggerUpload(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a [PingType] in the registry associated with this [Glean] object.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
internal void RegisterPingType(PingTypeBase pingType)
|
||||
{
|
||||
if (IsInitialized())
|
||||
{
|
||||
LibGleanFFI.glean_register_ping_type(
|
||||
pingType.handle
|
||||
);
|
||||
}
|
||||
|
||||
// We need to keep track of pings, so they get re-registered after a reset.
|
||||
// This state is kept across Glean resets, which should only ever happen in test mode.
|
||||
// Or by the instrumentation tests (`connectedAndroidTest`), which relaunches the application activity,
|
||||
// but not the whole process, meaning globals, such as the ping types, still exist from the old run.
|
||||
// It's a set and keeping them around forever should not have much of an impact.
|
||||
|
||||
pingTypeQueue.Add(pingType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TEST ONLY FUNCTION.
|
||||
/// Returns true if a ping by this name is in the ping registry.
|
||||
/// </summary>
|
||||
internal bool TestHasPingType(string pingName)
|
||||
{
|
||||
return LibGleanFFI.glean_test_has_ping_type(pingName) != 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,208 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk" DefaultTargets="Build" InitialTargets="ValidateContentFiles">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Authors>Mozilla</Authors>
|
||||
<RepositoryUrl>https://github.com/mozilla/glean</RepositoryUrl>
|
||||
<Description>The Glean SDK is a modern approach for a telemetry library by Mozilla.</Description>
|
||||
<!--
|
||||
While we're still testing, mark this as a pre-release package.
|
||||
See https://docs.microsoft.com/en-us/nuget/concepts/package-versioning#pre-release-versions
|
||||
-->
|
||||
<Version>42.3.1</Version>
|
||||
<RootNamespace>Mozilla.Glean</RootNamespace>
|
||||
<PackageId>Mozilla.Telemetry.Glean</PackageId>
|
||||
<PackageProjectUrl>https://github.com/mozilla/glean/</PackageProjectUrl>
|
||||
<PackageLicenseExpression>MPL-2.0</PackageLicenseExpression>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>telemetry analytics glean</PackageTags>
|
||||
<Product>Glean SDK</Product>
|
||||
<!--
|
||||
The following properties were determined by following the solution outlined here:
|
||||
https://github.com/Microsoft/msbuild/issues/539#issuecomment-289930591
|
||||
-->
|
||||
<IsWindows Condition="'$(OS)' == 'Windows_NT'">true</IsWindows>
|
||||
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
|
||||
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
|
||||
<Platforms>AnyCPU;x86</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
Print a message to warn user this is attempting to build a multi-platform nuGet
|
||||
package meant for uploading.
|
||||
-->
|
||||
<Target Condition="$(IsPublicPackage) == true" Name="TestMessage" AfterTargets="Build">
|
||||
<Message Text="Building a multi-platform nuGet package." Importance="high" />
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
Provide a way to pack all the native libraries when creating a nuGet package
|
||||
intended for the public.
|
||||
|
||||
This can be built using, from the glean root:
|
||||
|
||||
`dotnet pack glean-core/csharp/csharp.sln -c Release -p:IsPublicPackage=true`
|
||||
|
||||
TODO: package will be built even if any of the file listed below is missing.
|
||||
This is a bug and we should fix it by making the package step fail if any
|
||||
of the files are missing.
|
||||
-->
|
||||
<ItemGroup Condition="$(IsPublicPackage) == true">
|
||||
<!--
|
||||
TODO: once we enable building from CI, the paths below will require tweaking
|
||||
to point at the appropriate directory under target/.
|
||||
-->
|
||||
<!-- Windows libraries -->
|
||||
<Content Include="../../../target/$(Configuration.ToLowerInvariant())/glean_ffi.dll" Link="runtimes/win-64/native/glean_ffi.dll">
|
||||
<PackagePath>runtimes/win-x64/native</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="../../../target/$(Configuration.ToLowerInvariant())/glean_ffi.dll" Link="runtimes/win-86/native/glean_ffi.dll">
|
||||
<PackagePath>runtimes/win-x86/native</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<!-- Linux libraries -->
|
||||
<Content Include="../../../target/$(Configuration.ToLowerInvariant())/libglean_ffi.so" Link="runtimes/linux-64/native/libglean_ffi.so">
|
||||
<PackagePath>runtimes/linux-x64/native</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="../../../target/$(Configuration.ToLowerInvariant())/libglean_ffi.so" Link="runtimes/linux-86/native/libglean_ffi.so">
|
||||
<PackagePath>runtimes/linux-x86/native</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<!-- MacOS libraries -->
|
||||
<Content Include="../../../target/$(Configuration.ToLowerInvariant())/libglean_ffi.dylib" Link="runtimes/osx-64/native/libglean_ffi.dylib">
|
||||
<PackagePath>runtimes/osx-x64/native</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
For local developer builds, pick the default glean-core target
|
||||
locations.
|
||||
-->
|
||||
<ItemGroup Condition="$(IsWindows) == true AND $(IsPublicPackage) != true">
|
||||
<!--
|
||||
Note: cargo build will produce the file in target/<buildtype>/glean_ffi.dll based
|
||||
on the current architecture. For example, if we run `cargo build`-->
|
||||
<Content Include="../../../target/$(Configuration.ToLowerInvariant())/glean_ffi.dll" Link="runtimes/win-64/native/glean_ffi.dll" Condition="'$(Platform)'=='x64'">
|
||||
<PackagePath>runtimes/win-x64/native</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="../../../target/$(Configuration.ToLowerInvariant())/glean_ffi.dll" Link="runtimes/win-86/native/glean_ffi.dll" Condition="'$(Platform)'=='x86'">
|
||||
<PackagePath>runtimes/win-x86/native</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<!-- Fall back to AnyCPU and hope for the best? -->
|
||||
<Content Include="../../../target/$(Configuration.ToLowerInvariant())/glean_ffi.dll" Link="runtimes/win-64/native/glean_ffi.dll" Condition="'$(Platform)'=='AnyCPU'">
|
||||
<PackagePath>runtimes/win-x64/native</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Local developer builds on Linux. -->
|
||||
<ItemGroup Condition="$(IsLinux) == true AND $(IsPublicPackage) != true">
|
||||
<Content Include="../../../target/$(Configuration.ToLowerInvariant())/libglean_ffi.so" Link="runtimes/linux-64/native/libglean_ffi.so" Condition="'$(Platform)'=='x64'">
|
||||
<PackagePath>runtimes/linux-x64/native</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="../../../target/$(Configuration.ToLowerInvariant())/libglean_ffi.so" Link="runtimes/linux-86/native/libglean_ffi.so" Condition="'$(Platform)'=='x86'">
|
||||
<PackagePath>runtimes/linux-x86/native</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<!-- Fall back to AnyCPU and hope for the best? -->
|
||||
<Content Include="../../../target/$(Configuration.ToLowerInvariant())/libglean_ffi.so" Link="runtimes/linux-64/native/libglean_ffi.so" Condition="'$(Platform)'=='x64'">
|
||||
<PackagePath>runtimes/linux-x64/native</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Local developer builds on OSX. -->
|
||||
<ItemGroup Condition="$(IsOSX) == true AND $(IsPublicPackage) != true">
|
||||
<Content Include="../../../target/$(Configuration.ToLowerInvariant())/libglean_ffi.dylib" Link="runtimes/osx-64/native/libglean_ffi.dylib">
|
||||
<PackagePath>runtimes/osx-x64/native</PackagePath>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Expose internal access objects to unit tests -->
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>GleanTests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Json" Version="4.7.2" />
|
||||
<PackageReference Include="Serilog" Version="2.9.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
We create a new target to make sure that all the required files are present
|
||||
when building a cumulative nuGet package. Thanks https://stackoverflow.com/a/1028860/261698 !
|
||||
-->
|
||||
<Target Name="ValidateContentFiles" Condition="$(IsPublicPackage) == true">
|
||||
<Error Condition="!Exists(%(Content.FullPath))" Text="Missing Glean FFI file [%(Content.FullPath)]" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="build\*" PackagePath="build\" />
|
||||
<Content Include="buildMultiTargeting\*" PackagePath="buildMultiTargeting\" />
|
||||
|
||||
<!--
|
||||
Mark the following packages as 'local only': they will be used when building this project
|
||||
but they will not become transitive dependencies. These dependencies are required for
|
||||
`GleanParser.cs`.
|
||||
-->
|
||||
<PackageReference Include="Microsoft.Build.Framework" Version="16.6.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="16.6.0" PrivateAssets="All">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Define the code generating task. We can't load it directly from the AssemblyFile, otherwise
|
||||
msbuild will lock the file and fail next builds. To work around this, we generate a task
|
||||
definition ourselves and load the real task code inside it.
|
||||
External consumers will not need to do that, they will simply reference the AssemblyName or
|
||||
AssemblyFile in the UsingTask directive.
|
||||
|
||||
Note that we are using the `RoslynCodeTaskFactory` for compiling inline code fragments instead
|
||||
of `CodeTaskFactory`. That's because the former is deprecated and not available outside of the
|
||||
Windows .NET framework. See https://github.com/dotnet/msbuild/issues/2890 .
|
||||
-->
|
||||
<UsingTask TaskName="GleanParser" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
|
||||
<ParameterGroup>
|
||||
<RegistryFiles ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
|
||||
<OutputPath ParameterType="System.String" Required="true" />
|
||||
<Namespace ParameterType="System.String" Required="true" />
|
||||
<AllowReserved ParameterType="System.Boolean" Required="false" />
|
||||
</ParameterGroup>
|
||||
<Task>
|
||||
<Code Type="Class" Source="GleanParser.cs" Language="cs" />
|
||||
</Task>
|
||||
</UsingTask>
|
||||
|
||||
<!-- Run the task to generate the Glean SDK APIs -->
|
||||
<Target Name="GleanIntegration" BeforeTargets="CoreCompile">
|
||||
<!-- Declare the locations of the Glean registry files. -->
|
||||
<ItemGroup>
|
||||
<GleanRegistryFiles Include="..\..\metrics.yaml"/>
|
||||
<GleanRegistryFiles Include="..\..\pings.yaml"/>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- This is what actually runs the parser. -->
|
||||
<GleanParser RegistryFiles="@(GleanRegistryFiles)" OutputPath="$(IntermediateOutputPath)Glean" AllowReserved="true" Namespace="Mozilla.Glean.GleanMetrics" />
|
||||
<!--
|
||||
And this adds the generated files to the project, so that they can be found by
|
||||
the compiler and Intellisense.
|
||||
-->
|
||||
<ItemGroup>
|
||||
<Compile Include="$(IntermediateOutputPath)Glean\**\*.cs" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
|
@ -1,270 +0,0 @@
|
|||
// 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.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
|
||||
namespace GleanTasks
|
||||
{
|
||||
public class GleanParser : ToolTask
|
||||
{
|
||||
// The name of the directory in which the Python virtual
|
||||
// environment must be created.
|
||||
private const string DefaultVirtualEnvDir = ".venv";
|
||||
|
||||
// The glean_parser pypi package version
|
||||
private const string GleanParserVersion = "4.3.1";
|
||||
|
||||
// This script runs a given Python module as a "main" module, like
|
||||
// `python -m module`. However, it first checks that the installed
|
||||
// package is at the desired version, and if not, upgrades it using `pip`.
|
||||
//
|
||||
// ** IMPORTANT**
|
||||
// Keep this script in sync with the one in `glean-gradle-plugin/GleanGradlePlugin.groovy`.
|
||||
private const string RunPythonScript = @"
|
||||
import importlib
|
||||
import subprocess
|
||||
import sys
|
||||
module_name = sys.argv[1]
|
||||
expected_version = sys.argv[2]
|
||||
try:
|
||||
module = importlib.import_module(module_name)
|
||||
except ImportError:
|
||||
found_version = None
|
||||
else:
|
||||
found_version = getattr(module, '__version__')
|
||||
if found_version != expected_version:
|
||||
subprocess.check_call([
|
||||
sys.executable,
|
||||
'-m',
|
||||
'pip',
|
||||
'install',
|
||||
'--upgrade',
|
||||
f'{module_name}=={expected_version}'
|
||||
])
|
||||
try:
|
||||
subprocess.check_call([
|
||||
sys.executable,
|
||||
'-m',
|
||||
module_name
|
||||
] + sys.argv[3:])
|
||||
except:
|
||||
# We don't need to show a traceback in this helper script.
|
||||
# Only the output of the subprocess is interesting.
|
||||
sys.exit(1)
|
||||
";
|
||||
|
||||
/// <summary>
|
||||
/// The Glean registry files to process.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public ITaskItem[] RegistryFiles { get; set; }
|
||||
|
||||
[Required]
|
||||
public string OutputPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The namespace in which to generate the Glean metrics.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Namespace { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This *MUST* only be used by the Glean SDK.
|
||||
///
|
||||
/// Whether or not to allow using Glean reserved names. Defaults to
|
||||
/// `false`.
|
||||
/// </summary>
|
||||
public bool AllowReserved { get; set; } = false;
|
||||
|
||||
private bool IsWindows() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
|
||||
// Make sure that we can see the output from the glean_parser when
|
||||
// building.
|
||||
protected override MessageImportance StandardOutputLoggingImportance
|
||||
{
|
||||
get { return MessageImportance.Normal; }
|
||||
}
|
||||
|
||||
protected override string ToolName
|
||||
{
|
||||
get {
|
||||
try
|
||||
{
|
||||
// If the "GLEAN_PYTHON" environment variable is set, use it and don't
|
||||
// look in the system PATH for a python interpreter.
|
||||
string gleanFromEnv = Environment.GetEnvironmentVariable("GLEAN_PYTHON");
|
||||
if (gleanFromEnv != null)
|
||||
{
|
||||
return gleanFromEnv;
|
||||
}
|
||||
}
|
||||
catch (SecurityException)
|
||||
{
|
||||
Log.LogMessage(
|
||||
MessageImportance.Low, "Failed to look for the GLEAN_PYTHON environment variable");
|
||||
}
|
||||
|
||||
// No "GLEAN_PYTHON" is available, look for the Python interpreter in the PATH.
|
||||
if (IsWindows())
|
||||
{
|
||||
return "python.exe";
|
||||
}
|
||||
|
||||
// We don't need the '.exe' suffix outside of Windows. The
|
||||
// canonical name of the file for Python3 is... "python3".
|
||||
return "python3";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to get the full path of the tool.
|
||||
///
|
||||
/// When returning `ToolExe`, this will look for the tool in the PATH.
|
||||
/// If `ToolPath` is set, then the value of this function is ignored.
|
||||
/// </summary>
|
||||
/// <returns>A string representing the path to the tool</returns>
|
||||
protected override string GenerateFullPathToTool()
|
||||
{
|
||||
return ToolExe;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the path to the Virtual Environment directory containing the
|
||||
/// Python interpreter.
|
||||
/// </summary>
|
||||
/// <param name="venvDir">The path of the virtual environment directory</param>
|
||||
/// <returns>The path to the "Scripts" or "bin" dir in the virtual environment</returns>
|
||||
private string GetVirtualEnvironmentPath(string venvDir)
|
||||
{
|
||||
// "Scripts" is only there on windows. On Linux/Mac is "bin"
|
||||
if (IsWindows())
|
||||
{
|
||||
return Path.Combine(venvDir, "Scripts");
|
||||
}
|
||||
|
||||
return Path.Combine(venvDir, "bin");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a Python virtual environment is available.
|
||||
/// </summary>
|
||||
/// <param name="venvDir">The python virtual environment directory</param>
|
||||
/// <returns>True if the virtual environment exists, false otherwise</returns>
|
||||
private bool CheckVirtualEnvironmentExists(string venvDir)
|
||||
{
|
||||
string venvPython = Path.Combine(GetVirtualEnvironmentPath(venvDir), ToolName);
|
||||
|
||||
if (Directory.Exists(DefaultVirtualEnvDir)
|
||||
&& File.Exists(venvPython))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses a Python interpreter to create a Python3 virtual environment.
|
||||
/// </summary>
|
||||
/// <param name="venvDir">The directory in which to create the virtual environemnt</param>
|
||||
/// <returns>True if the environment was correctly created, false otherwise</returns>
|
||||
private bool CreateVirtualEnvironment(string venvDir)
|
||||
{
|
||||
Log.LogMessage(MessageImportance.High, "Creating a Glean SDK virtual environment at: " + venvDir);
|
||||
|
||||
CommandLineBuilder venvCommand = new CommandLineBuilder();
|
||||
venvCommand.AppendSwitch("-m venv " + venvDir);
|
||||
|
||||
if (ExecuteTool(GenerateFullPathToTool(), string.Empty, venvCommand.ToString()) != 0)
|
||||
{
|
||||
// We failed to create the virtual environment. Bail out.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Install bdist_wheel.
|
||||
string pipTool = Path.Combine(GetVirtualEnvironmentPath(venvDir), IsWindows() ? "pip3.exe" : "pip3");
|
||||
|
||||
CommandLineBuilder pipCommand = new CommandLineBuilder();
|
||||
pipCommand.AppendSwitch("install");
|
||||
pipCommand.AppendSwitch("wheel");
|
||||
|
||||
return ExecuteTool(pipTool, string.Empty, pipCommand.ToString()) == 0;
|
||||
}
|
||||
|
||||
private bool Setup()
|
||||
{
|
||||
if (CheckVirtualEnvironmentExists(DefaultVirtualEnvDir))
|
||||
{
|
||||
// The virtual environment was already created. Try to use it!
|
||||
Log.LogMessage(MessageImportance.High, "Using Glean SDK virtual environment at: " + DefaultVirtualEnvDir);
|
||||
} else if (!CreateVirtualEnvironment(DefaultVirtualEnvDir))
|
||||
{
|
||||
// Attempt to create a new virtual environemnt. If we fail there's
|
||||
// nothing we could possibly do to move forward.
|
||||
Log.LogError("Failed to create a Glean SDK virtual environment at: " + DefaultVirtualEnvDir);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the python instance to use from now on! Once we set this to a path,
|
||||
// this will be the one used by `Execute`.
|
||||
ToolPath = GetVirtualEnvironmentPath(DefaultVirtualEnvDir);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked by ToolTask to generate the command to execute.
|
||||
/// </summary>
|
||||
/// <returns>A string representing the command to execute</returns>
|
||||
protected override string GenerateCommandLineCommands()
|
||||
{
|
||||
CommandLineBuilder builder = new CommandLineBuilder();
|
||||
builder.AppendSwitch("-c");
|
||||
builder.AppendTextUnquoted(" \"" + RunPythonScript + "\" ");
|
||||
builder.AppendSwitch("glean_parser");
|
||||
builder.AppendSwitch(GleanParserVersion);
|
||||
builder.AppendSwitch("translate");
|
||||
builder.AppendSwitch("-f \"csharp\"");
|
||||
builder.AppendSwitch("-o");
|
||||
builder.AppendFileNameIfNotNull(OutputPath);
|
||||
builder.AppendSwitch("-s \"glean_namespace=Mozilla.Glean\"");
|
||||
builder.AppendSwitch($"-s \"namespace={Namespace}\"");
|
||||
if (AllowReserved)
|
||||
{
|
||||
builder.AppendSwitch("--allow-reserved");
|
||||
}
|
||||
|
||||
foreach (ITaskItem file in RegistryFiles)
|
||||
{
|
||||
builder.AppendFileNameIfNotNull(Path.GetFullPath(file.ItemSpec));
|
||||
}
|
||||
|
||||
Log.LogMessage(MessageImportance.Low, "GleanParser.GenerateCommandLineCommands command: " + builder.ToString());
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public override bool Execute()
|
||||
{
|
||||
if (!Setup())
|
||||
{
|
||||
Log.LogError("Failed to setup the Glean SDK build environment");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = base.Execute();
|
||||
if (!result)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,703 +0,0 @@
|
|||
// 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.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Mozilla.Glean.FFI
|
||||
{
|
||||
/// <summary>
|
||||
/// Result values of attempted ping uploads encoded for FFI use.
|
||||
/// They are defined in `glean-core/src/upload/result.rs` and re-defined for use in Kotlin here.
|
||||
///
|
||||
/// NOTE:
|
||||
/// THEY MUST BE THE SAME ACROSS BOTH FILES!
|
||||
/// </summary>
|
||||
internal enum Constants : int
|
||||
{
|
||||
// A recoverable error.
|
||||
UPLOAD_RESULT_RECOVERABLE = 0x1,
|
||||
|
||||
// An unrecoverable error.
|
||||
UPLOAD_RESULT_UNRECOVERABLE = 0x2,
|
||||
|
||||
// A HTTP response code.
|
||||
UPLOAD_RESULT_HTTP_STATUS = 0x8000
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rust represents the upload task as an Enum
|
||||
/// and to go through the FFI that gets transformed into a tagged union.
|
||||
/// Each variant is represented as an 8-bit unsigned integer.
|
||||
///
|
||||
/// This *MUST* have the same order as the variants in `glean-core/ffi/src/upload.rs`.
|
||||
/// </summary>
|
||||
enum UploadTaskTag : int
|
||||
{
|
||||
Upload,
|
||||
Wait,
|
||||
Done
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct FfiUploadTaskBody
|
||||
{
|
||||
public byte tag;
|
||||
public IntPtr documentId;
|
||||
public IntPtr path;
|
||||
public int bodyLen;
|
||||
public IntPtr body;
|
||||
public IntPtr headers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represent an upload task by simulating the union passed through
|
||||
/// the FFI layer.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
internal struct FfiUploadTask
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public byte tag;
|
||||
[FieldOffset(0), MarshalAs(UnmanagedType.Struct)]
|
||||
public FfiUploadTaskBody body;
|
||||
}
|
||||
|
||||
internal static class LibGleanFFI
|
||||
{
|
||||
private const string SharedGleanLibrary = "glean_ffi";
|
||||
|
||||
// Define the order of fields as laid out in memory.
|
||||
// **CAUTION**: This must match _exactly_ the definition on the Rust side.
|
||||
// If this side is changed, the Rust side need to be changed, too.
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal class FfiConfiguration
|
||||
{
|
||||
public string data_dir;
|
||||
public string package_name;
|
||||
public string language_binding_name;
|
||||
public bool upload_enabled;
|
||||
public IntPtr max_events;
|
||||
public bool delay_ping_lifetime_io;
|
||||
}
|
||||
|
||||
public static string GetFromRustString(IntPtr pointer)
|
||||
{
|
||||
int len = 0;
|
||||
while (Marshal.ReadByte(pointer, len) != 0) { ++len; }
|
||||
byte[] buffer = new byte[len];
|
||||
Marshal.Copy(pointer, buffer, 0, buffer.Length);
|
||||
return Encoding.UTF8.GetString(buffer);
|
||||
}
|
||||
|
||||
internal class StringAsReturnValue : SafeHandle
|
||||
{
|
||||
public StringAsReturnValue() : base(IntPtr.Zero, true) { }
|
||||
|
||||
public override bool IsInvalid
|
||||
{
|
||||
get { return this.handle == IntPtr.Zero; }
|
||||
}
|
||||
|
||||
public string AsString()
|
||||
{
|
||||
return GetFromRustString(handle);
|
||||
}
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (!this.IsInvalid)
|
||||
{
|
||||
glean_str_free(handle);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Glean top-level API.
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_initialize(IntPtr cfg);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_clear_application_lifetime_metrics();
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_set_dirty_flag(byte flag);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_is_dirty_flag_set();
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_test_clear_all_stores();
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_is_first_run();
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_destroy_glean();
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_on_ready_to_submit_pings();
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_enable_logging();
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_set_upload_enabled(bool flag);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_is_upload_enabled();
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern StringAsReturnValue glean_ping_collect(UInt64 ping_type_handle, string reason);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_submit_ping_by_name(string ping_name, string reason);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_set_experiment_active(
|
||||
string experiment_id,
|
||||
string branch,
|
||||
string[] extra_keys,
|
||||
string[] extra_values,
|
||||
Int32 extra_len
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_set_experiment_inactive(string experiment_id);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_experiment_test_is_active(string experiment_id);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern StringAsReturnValue glean_experiment_test_get_data(string experiment_id);
|
||||
|
||||
// String
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_new_string_metric(
|
||||
string category,
|
||||
string name,
|
||||
string[] send_in_pings,
|
||||
Int32 send_in_pings_len,
|
||||
Int32 lifetime,
|
||||
bool disabled
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_destroy_string_metric(UInt64 handle);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_string_set(UInt64 metric_id, string value);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern StringAsReturnValue glean_string_test_get_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_string_test_has_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Int32 glean_string_test_get_num_recorded_errors(
|
||||
UInt64 metric_id,
|
||||
Int32 error_type,
|
||||
string storage_name
|
||||
);
|
||||
|
||||
// Boolean
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_new_boolean_metric(
|
||||
string category,
|
||||
string name,
|
||||
string[] send_in_pings,
|
||||
Int32 send_in_pings_len,
|
||||
Int32 lifetime,
|
||||
bool disabled
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_destroy_boolean_metric(IntPtr handle);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_boolean_set(UInt64 metric_id, byte value);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_boolean_test_get_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_boolean_test_has_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
|
||||
// Counter
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_new_counter_metric(
|
||||
string category,
|
||||
string name,
|
||||
string[] send_in_pings,
|
||||
Int32 send_in_pings_len,
|
||||
Int32 lifetime,
|
||||
bool disabled
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_destroy_counter_metric(IntPtr handle);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_counter_add(UInt64 metric_id, Int32 amount);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Int32 glean_counter_test_get_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern bool glean_counter_test_has_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Int32 glean_counter_test_get_num_recorded_errors(
|
||||
UInt64 metric_id,
|
||||
Int32 error_type,
|
||||
String storage_name
|
||||
);
|
||||
|
||||
// Uuid
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_new_uuid_metric(
|
||||
string category,
|
||||
string name,
|
||||
string[] send_in_pings,
|
||||
Int32 send_in_pings_len,
|
||||
Int32 lifetime,
|
||||
bool disabled
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_destroy_uuid_metric(IntPtr handle);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_uuid_set(UInt64 metric_id, string value);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern StringAsReturnValue glean_uuid_test_get_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_uuid_test_has_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
// Timespan
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_new_timespan_metric(
|
||||
string category,
|
||||
string name,
|
||||
string[] send_in_pings,
|
||||
Int32 send_in_pings_len,
|
||||
Int32 lifetime,
|
||||
bool disabled,
|
||||
Int32 time_unit
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_destroy_timespan_metric(IntPtr handle);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_timespan_set_start(UInt64 handle, UInt64 start_time);
|
||||
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_timespan_set_stop(UInt64 metric_id, UInt64 stop_time);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_timespan_cancel(UInt64 metric_id);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_timespan_set_raw_nanos(UInt64 metric_id, UInt64 elapsed_nanos);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_timespan_test_has_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_timespan_test_get_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Int32 glean_timespan_test_get_num_recorded_errors(
|
||||
UInt64 metric_id,
|
||||
Int32 error_type,
|
||||
String storage_name
|
||||
);
|
||||
|
||||
// TimingDistribution
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_new_timing_distribution_metric(
|
||||
string category,
|
||||
string name,
|
||||
string[] send_in_pings,
|
||||
Int32 send_in_pings_len,
|
||||
Int32 lifetime,
|
||||
bool disabled,
|
||||
Int32 time_unit
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_destroy_timing_distribution_metric(UInt64 handle);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_timing_distribution_set_start(UInt64 metric_id, UInt64 start_time);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_timing_distribution_set_stop_and_accumulate(
|
||||
UInt64 metric_id,
|
||||
UInt64 timer_id,
|
||||
UInt64 stop_time
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_timing_distribution_cancel(UInt64 metric_id, UInt64 timer_id);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_timing_distribution_accumulate_samples(UInt64 metric_id, Int64[] samples, Int32 len);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_timing_distribution_test_has_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern StringAsReturnValue glean_timing_distribution_test_get_value_as_json_string(
|
||||
UInt64 metric_id,
|
||||
string storage_name
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Int32 glean_timing_distribution_test_get_num_recorded_errors(
|
||||
UInt64 metric_id,
|
||||
Int32 error_type,
|
||||
string storage_name
|
||||
);
|
||||
|
||||
// MemoryDistribution
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_new_memory_distribution_metric(
|
||||
string category,
|
||||
string name,
|
||||
string[] send_in_pings,
|
||||
Int32 send_in_pings_len,
|
||||
Int32 lifetime,
|
||||
bool disabled,
|
||||
Int32 memory_unit
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_destroy_memory_distribution_metric(UInt64 handle);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_memory_distribution_accumulate(UInt64 metric_id, UInt64 sample);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_memory_distribution_accumulate_samples(UInt64 metric_id, Int64[] samples, Int32 len);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_memory_distribution_test_has_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern StringAsReturnValue glean_memory_distribution_test_get_value_as_json_string(
|
||||
UInt64 metric_id,
|
||||
string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Int32 glean_memory_distribution_test_get_num_recorded_errors(
|
||||
UInt64 metric_id,
|
||||
Int32 error_type,
|
||||
string storage_name
|
||||
);
|
||||
|
||||
// Datetime
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_new_datetime_metric(
|
||||
string category,
|
||||
string name,
|
||||
string[] send_in_pings,
|
||||
Int32 send_in_pings_len,
|
||||
Int32 lifetime,
|
||||
bool disabled,
|
||||
Int32 time_unit
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_destroy_datetime_metric(IntPtr handle);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_datetime_set(UInt64 metric_id, Int32 year,
|
||||
Int32 month, Int32 day, Int32 hour, Int32 minute, Int32 second, long nano, Int32 offset_seconds);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_datetime_test_has_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern StringAsReturnValue glean_datetime_test_get_value_as_string(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Int32 glean_datetime_test_get_num_recorded_errors(
|
||||
UInt64 metric_id,
|
||||
Int32 error_type,
|
||||
string storage_name
|
||||
);
|
||||
|
||||
// Event
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_new_event_metric(
|
||||
string category,
|
||||
string name,
|
||||
string[] send_in_pings,
|
||||
Int32 send_in_pings_len,
|
||||
Int32 lifetime,
|
||||
bool disabled,
|
||||
string[] allowed_extra_keys,
|
||||
Int32 allowed_extra_keys_len
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_event_record(
|
||||
UInt64 handle,
|
||||
UInt64 timestamp,
|
||||
Int32[] extra_keys,
|
||||
string[] extra_values,
|
||||
Int32 extra_len
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_event_test_has_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern StringAsReturnValue glean_event_test_get_value_as_json_string(
|
||||
UInt64 handle,
|
||||
string storage_Name
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Int32 glean_event_test_get_num_recorded_errors(
|
||||
UInt64 metric_id,
|
||||
Int32 error_type,
|
||||
string storage_name
|
||||
);
|
||||
|
||||
// Labeled Counter
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_new_labeled_counter_metric(
|
||||
string category,
|
||||
string name,
|
||||
string[] send_in_pings,
|
||||
Int32 send_in_pings_len,
|
||||
Int32 lifetime,
|
||||
bool disabled,
|
||||
string[] labels,
|
||||
Int32 label_count
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_labeled_counter_metric_get(UInt64 handle, string label);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Int32 glean_labeled_counter_test_get_num_recorded_errors(
|
||||
UInt64 metric_id,
|
||||
Int32 error_type,
|
||||
string storage_name
|
||||
);
|
||||
|
||||
// Labeled Boolean
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_new_labeled_boolean_metric(
|
||||
string category,
|
||||
string name,
|
||||
string [] send_in_pings,
|
||||
Int32 send_in_pings_len,
|
||||
Int32 lifetime,
|
||||
bool disabled,
|
||||
string [] labels,
|
||||
Int32 label_count
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_labeled_boolean_metric_get(UInt64 handle, string label);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Int32 glean_labeled_boolean_test_get_num_recorded_errors(
|
||||
UInt64 metric_id,
|
||||
Int32 error_type,
|
||||
string storage_name
|
||||
);
|
||||
|
||||
// Labeled string
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_new_labeled_string_metric(
|
||||
string category,
|
||||
string name,
|
||||
string[] send_in_pings,
|
||||
Int32 send_in_pings_len,
|
||||
Int32 lifetime,
|
||||
bool disabled,
|
||||
string[] labels,
|
||||
Int32 label_count
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_labeled_string_metric_get(UInt64 handle, string label);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Int32 glean_labeled_string_test_get_num_recorded_errors(
|
||||
UInt64 metric_id,
|
||||
Int32 error_type,
|
||||
string storage_name
|
||||
);
|
||||
|
||||
// JWE
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_new_jwe_metric(
|
||||
string category,
|
||||
string name,
|
||||
string[] send_in_pings,
|
||||
Int32 send_in_pings_len,
|
||||
Int32 lifetime,
|
||||
bool disabled
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_destroy_jwe_metric(UInt64 handle);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_jwe_set(
|
||||
UInt64 metric_id,
|
||||
string header,
|
||||
string key,
|
||||
string init_vector,
|
||||
string cipher_text,
|
||||
string auth_tag
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_jwe_set_with_compact_representation(UInt64 metric_id, string value);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern StringAsReturnValue glean_jwe_test_get_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern StringAsReturnValue glean_jwe_test_get_value_as_json_string(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_jwe_test_has_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Int32 glean_jwe_test_get_num_recorded_errors(
|
||||
UInt64 metric_id,
|
||||
Int32 error_type,
|
||||
string storage_name
|
||||
);
|
||||
|
||||
// StringList
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_new_string_list_metric(
|
||||
string category,
|
||||
string name,
|
||||
string[] send_in_pings,
|
||||
Int32 send_in_pings_len,
|
||||
Int32 lifetime,
|
||||
bool disabled
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_destroy_string_list_metric(UInt64 handle);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_string_list_add(UInt64 metric_id, string value);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_string_list_set(UInt64 metric_id, string[] value, Int32 values_len);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_string_list_test_has_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern StringAsReturnValue glean_string_list_test_get_value_as_json_string(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Int32 glean_string_list_test_get_num_recorded_errors(
|
||||
UInt64 metric_id,
|
||||
Int32 error_type,
|
||||
string storage_name
|
||||
);
|
||||
|
||||
// Quantity
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_new_quantity_metric(
|
||||
string category,
|
||||
string name,
|
||||
string[] send_in_pings,
|
||||
Int32 send_in_pings_len,
|
||||
Int32 lifetime,
|
||||
bool disabled
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_destroy_quantity_metric(IntPtr handle);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_quantity_set(UInt64 metric_id, Int32 value);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Int32 glean_quantity_test_get_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern bool glean_quantity_test_has_value(UInt64 metric_id, string storage_name);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Int32 glean_quantity_test_get_num_recorded_errors(
|
||||
UInt64 metric_id,
|
||||
Int32 error_type,
|
||||
String storage_name
|
||||
);
|
||||
|
||||
// Custom pings
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern UInt64 glean_new_ping_type(
|
||||
string name,
|
||||
byte include_client_id,
|
||||
byte send_if_empty,
|
||||
string[] reason,
|
||||
Int32 reason_codes_len
|
||||
);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_destroy_ping_type(IntPtr handle);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_register_ping_type(UInt64 ping_type_handle);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern byte glean_test_has_ping_type(string ping_name);
|
||||
|
||||
// Upload API
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_get_upload_task(ref FfiUploadTask result);
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_process_ping_upload_response(IntPtr task, int status);
|
||||
|
||||
// Misc
|
||||
|
||||
[DllImport(SharedGleanLibrary, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void glean_str_free(IntPtr ptr);
|
||||
}
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
//
|
||||
// Link to LICENSE.md: https://github.com/dotnet/samples/blob/master/LICENSE-CODE
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace System.Threading.Tasks.Schedulers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a task scheduler that ensures a maximum concurrency level while
|
||||
/// running on top of the ThreadPool.
|
||||
/// </summary>
|
||||
public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
|
||||
{
|
||||
/// <summary>Whether the current thread is processing work items.</summary>
|
||||
[ThreadStatic]
|
||||
private static bool s_currentThreadIsProcessingItems;
|
||||
/// <summary>The list of tasks to be executed.</summary>
|
||||
private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); // protected by lock(_tasks)
|
||||
/// <summary>The maximum concurrency level allowed by this scheduler.</summary>
|
||||
private readonly int _maxDegreeOfParallelism;
|
||||
/// <summary>Whether the scheduler is currently processing work items.</summary>
|
||||
private int _delegatesQueuedOrRunning = 0; // protected by lock(_tasks)
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the
|
||||
/// specified degree of parallelism.
|
||||
/// </summary>
|
||||
/// <param name="maxDegreeOfParallelism">The maximum degree of parallelism provided by this scheduler.</param>
|
||||
public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
|
||||
{
|
||||
if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism));
|
||||
_maxDegreeOfParallelism = maxDegreeOfParallelism;
|
||||
}
|
||||
|
||||
/// <summary>Queues a task to the scheduler.</summary>
|
||||
/// <param name="task">The task to be queued.</param>
|
||||
protected sealed override void QueueTask(Task task)
|
||||
{
|
||||
// Add the task to the list of tasks to be processed. If there aren't enough
|
||||
// delegates currently queued or running to process tasks, schedule another.
|
||||
lock (_tasks)
|
||||
{
|
||||
_tasks.AddLast(task);
|
||||
if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
|
||||
{
|
||||
++_delegatesQueuedOrRunning;
|
||||
NotifyThreadPoolOfPendingWork();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Informs the ThreadPool that there's work to be executed for this scheduler.
|
||||
/// </summary>
|
||||
private void NotifyThreadPoolOfPendingWork() => ThreadPool.UnsafeQueueUserWorkItem(_ =>
|
||||
{
|
||||
// Note that the current thread is now processing work items.
|
||||
// This is necessary to enable inlining of tasks into this thread.
|
||||
s_currentThreadIsProcessingItems = true;
|
||||
try
|
||||
{
|
||||
// Process all available items in the queue.
|
||||
while (true)
|
||||
{
|
||||
Task item;
|
||||
lock (_tasks)
|
||||
{
|
||||
// When there are no more items to be processed,
|
||||
// note that we're done processing, and get out.
|
||||
if (_tasks.Count == 0)
|
||||
{
|
||||
--_delegatesQueuedOrRunning;
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the next item from the queue
|
||||
item = _tasks.First.Value;
|
||||
_tasks.RemoveFirst();
|
||||
}
|
||||
|
||||
// Execute the task we pulled out of the queue
|
||||
TryExecuteTask(item);
|
||||
}
|
||||
}
|
||||
// We're done processing items on the current thread
|
||||
finally { s_currentThreadIsProcessingItems = false; }
|
||||
}, null);
|
||||
|
||||
/// <summary>Attempts to execute the specified task on the current thread.</summary>
|
||||
/// <param name="task">The task to be executed.</param>
|
||||
/// <param name="taskWasPreviouslyQueued"></param>
|
||||
/// <returns>Whether the task could be executed on the current thread.</returns>
|
||||
protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
|
||||
{
|
||||
// If this thread isn't already processing a task, we don't support inlining
|
||||
if (!s_currentThreadIsProcessingItems) return false;
|
||||
|
||||
// If the task was previously queued, remove it from the queue
|
||||
if (taskWasPreviouslyQueued) TryDequeue(task);
|
||||
|
||||
// Try to run the task.
|
||||
return TryExecuteTask(task);
|
||||
}
|
||||
|
||||
/// <summary>Attempts to remove a previously scheduled task from the scheduler.</summary>
|
||||
/// <param name="task">The task to be removed.</param>
|
||||
/// <returns>Whether the task could be found and removed.</returns>
|
||||
protected sealed override bool TryDequeue(Task task)
|
||||
{
|
||||
lock (_tasks) return _tasks.Remove(task);
|
||||
}
|
||||
|
||||
/// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
|
||||
public sealed override int MaximumConcurrencyLevel => _maxDegreeOfParallelism;
|
||||
|
||||
/// <summary>Gets an enumerable of the tasks currently scheduled on this scheduler.</summary>
|
||||
/// <returns>An enumerable of the tasks currently scheduled.</returns>
|
||||
protected sealed override IEnumerable<Task> GetScheduledTasks()
|
||||
{
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
Monitor.TryEnter(_tasks, ref lockTaken);
|
||||
if (lockTaken) return _tasks.ToArray();
|
||||
else throw new NotSupportedException();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) Monitor.Exit(_tasks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
// 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 Mozilla.Glean.FFI;
|
||||
using System;
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
/// <summary>
|
||||
/// This implements the developer facing API for recording boolean metrics.
|
||||
///
|
||||
/// Instances of this class type are automatically generated by the parsers at build time,
|
||||
/// allowing developers to record values that were previously registered in the metrics.yaml file.
|
||||
///
|
||||
/// The boolean API only exposes the [set] method.
|
||||
///
|
||||
/// The internal constructor is only used by [LabeledMetricType] directly.
|
||||
/// </summary>
|
||||
public sealed class BooleanMetricType : ILabeledSubmetricInterface
|
||||
{
|
||||
private bool disabled;
|
||||
private string[] sendInPings;
|
||||
private UInt64 handle;
|
||||
|
||||
/// <summary>
|
||||
/// The public constructor used by automatically generated metrics.
|
||||
/// </summary>
|
||||
public BooleanMetricType(
|
||||
bool disabled,
|
||||
string category,
|
||||
Lifetime lifetime,
|
||||
string name,
|
||||
string[] sendInPings
|
||||
) : this(0, disabled, sendInPings)
|
||||
{
|
||||
handle = LibGleanFFI.glean_new_boolean_metric(
|
||||
category: category,
|
||||
name: name,
|
||||
send_in_pings: sendInPings,
|
||||
send_in_pings_len: sendInPings.Length,
|
||||
lifetime: (int)lifetime,
|
||||
disabled: disabled);
|
||||
}
|
||||
|
||||
internal BooleanMetricType(
|
||||
UInt64 handle,
|
||||
bool disabled,
|
||||
string[] sendInPings
|
||||
)
|
||||
{
|
||||
this.disabled = disabled;
|
||||
this.sendInPings = sendInPings;
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a boolean value.
|
||||
/// </summary>
|
||||
/// <param name="value"> This is a user defined boolean value.</param>
|
||||
public void Set(bool value)
|
||||
{
|
||||
if (disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatchers.LaunchAPI(() => {
|
||||
SetSync(value);
|
||||
});
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// 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_boolean_test_has_value(this.handle, ping) != 0;
|
||||
}
|
||||
|
||||
/// <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 bool TestGetValue(string pingName = null)
|
||||
{
|
||||
Dispatchers.AssertInTestingMode();
|
||||
|
||||
if (!TestHasValue(pingName))
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
string ping = pingName ?? sendInPings[0];
|
||||
return LibGleanFFI.glean_boolean_test_get_value(this.handle, ping) != 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
// 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 Mozilla.Glean.FFI;
|
||||
using System;
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
/// <summary>
|
||||
/// This implements the developer facing API for recording counter metrics.
|
||||
///
|
||||
/// Instances of this class type are automatically generated by the parsers at build time,
|
||||
/// allowing developers to record values that were previously registered in the metrics.yaml file.
|
||||
///
|
||||
/// The internal constructor is only used by [LabeledMetricType] directly.
|
||||
/// </summary>
|
||||
public sealed class CounterMetricType : ILabeledSubmetricInterface
|
||||
{
|
||||
private bool disabled;
|
||||
private string[] sendInPings;
|
||||
private UInt64 handle;
|
||||
|
||||
/// <summary>
|
||||
/// The public constructor used by automatically generated metrics.
|
||||
/// </summary>
|
||||
public CounterMetricType(
|
||||
bool disabled,
|
||||
string category,
|
||||
Lifetime lifetime,
|
||||
string name,
|
||||
string[] sendInPings
|
||||
) : this(0, disabled, sendInPings)
|
||||
{
|
||||
handle = LibGleanFFI.glean_new_counter_metric(
|
||||
category: category,
|
||||
name: name,
|
||||
send_in_pings: sendInPings,
|
||||
send_in_pings_len: sendInPings.Length,
|
||||
lifetime: (int)lifetime,
|
||||
disabled: disabled);
|
||||
}
|
||||
|
||||
internal CounterMetricType(
|
||||
UInt64 handle,
|
||||
bool disabled,
|
||||
string[] sendInPings
|
||||
)
|
||||
{
|
||||
this.disabled = disabled;
|
||||
this.sendInPings = sendInPings;
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add to counter value.
|
||||
/// </summary>
|
||||
/// <param name="amount">this is the amount to increment the counter by, defaulting to
|
||||
/// 1 if called without parameters.</param>
|
||||
public void Add(Int32 amount = 1)
|
||||
{
|
||||
if (disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatchers.LaunchAPI(() => {
|
||||
AddSync(amount);
|
||||
});
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <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_counter_test_has_value(handle, ping);
|
||||
}
|
||||
|
||||
/// <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 Int32 TestGetValue(string pingName = null)
|
||||
{
|
||||
Dispatchers.AssertInTestingMode();
|
||||
|
||||
if (!TestHasValue(pingName))
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
string ping = pingName ?? sendInPings[0];
|
||||
return LibGleanFFI.glean_counter_test_get_value(handle, ping);
|
||||
}
|
||||
|
||||
/// <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];
|
||||
return LibGleanFFI.glean_counter_test_get_num_recorded_errors(
|
||||
handle, (int)errorType, ping
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,157 +0,0 @@
|
|||
// 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 Mozilla.Glean.FFI;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
/// <summary>
|
||||
/// This implements the developer facing API for recording datetime metrics.
|
||||
///
|
||||
/// Instances of this class type are automatically generated by the parsers at build time,
|
||||
/// allowing developers to record values that were previously registered in the metrics.yaml file.
|
||||
/// </summary>
|
||||
public sealed class DatetimeMetricType
|
||||
{
|
||||
private bool disabled;
|
||||
private string[] sendInPings;
|
||||
private UInt64 handle;
|
||||
|
||||
/// <summary>
|
||||
/// The public constructor used by automatically generated metrics.
|
||||
/// </summary>
|
||||
public DatetimeMetricType(
|
||||
bool disabled,
|
||||
string category,
|
||||
Lifetime lifetime,
|
||||
string name,
|
||||
string[] sendInPings,
|
||||
TimeUnit timeUnit = TimeUnit.Minute
|
||||
) : this(0, disabled, sendInPings)
|
||||
{
|
||||
handle = LibGleanFFI.glean_new_datetime_metric(
|
||||
category: category,
|
||||
name: name,
|
||||
send_in_pings: sendInPings,
|
||||
send_in_pings_len: sendInPings.Length,
|
||||
lifetime: (int)lifetime,
|
||||
disabled: disabled,
|
||||
time_unit: (int)timeUnit);
|
||||
}
|
||||
|
||||
internal DatetimeMetricType(
|
||||
UInt64 handle,
|
||||
bool disabled,
|
||||
string[] sendInPings
|
||||
)
|
||||
{
|
||||
this.disabled = disabled;
|
||||
this.sendInPings = sendInPings;
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// 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_datetime_test_has_value(this.handle, ping) != 0;
|
||||
}
|
||||
|
||||
/// <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 TestGetValueAsString(string pingName = null)
|
||||
{
|
||||
Dispatchers.AssertInTestingMode();
|
||||
|
||||
string ping = pingName ?? sendInPings[0];
|
||||
if (!TestHasValue(ping))
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
return LibGleanFFI.glean_datetime_test_get_value_as_string(this.handle, ping).AsString();
|
||||
}
|
||||
|
||||
/// <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 DateTimeOffset TestGetValue(string pingName = null)
|
||||
{
|
||||
return DateTimeOffset.Parse(TestGetValueAsString(pingName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_datetime_test_get_num_recorded_errors(
|
||||
this.handle, (int)errorType, ping
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
/* 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents the structure of a distribution according to the pipeline schema. It
|
||||
/// is meant to help serialize and deserialize data to the correct format for transport and storage,
|
||||
/// as well as including a helper function to calculate the bucket sizes.
|
||||
/// </summary>
|
||||
public sealed class DistributionData
|
||||
{
|
||||
/// <summary>
|
||||
/// A map containing the bucket index mapped to the accumulated count.
|
||||
/// </summary>
|
||||
public readonly Dictionary<Int64, Int64> Values;
|
||||
/// <summary>
|
||||
/// The accumulated sum of all the samples in the distribution.
|
||||
/// </summary>
|
||||
public readonly Int64 Sum;
|
||||
|
||||
// This constructor is only useful for tests.
|
||||
internal DistributionData() { }
|
||||
|
||||
DistributionData(Dictionary<Int64, Int64> values, Int64 sum)
|
||||
{
|
||||
this.Values = values;
|
||||
this.Sum = sum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory function that takes stringified JSON and converts it back into a
|
||||
/// `DistributionData`. This tries to read all values and attempts to
|
||||
/// use a default where no value exists.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="json">Stringified JSON value representing a `DistributionData` object</param>
|
||||
/// <returns>A `DistributionData` or null if unable to rebuild from the string.</returns>
|
||||
public static DistributionData FromJsonString(string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
JsonDocument data = JsonDocument.Parse(json);
|
||||
JsonElement root = data.RootElement;
|
||||
|
||||
Int64 sum = root.GetProperty("sum").GetInt64();
|
||||
Dictionary<Int64, Int64> processedValues = new Dictionary<Int64, Int64>();
|
||||
JsonElement rawValuesMap = root.GetProperty("values");
|
||||
foreach (var entry in rawValuesMap.EnumerateObject())
|
||||
{
|
||||
processedValues.Add(Convert.ToInt64(entry.Name), entry.Value.GetInt64());
|
||||
}
|
||||
return new DistributionData(processedValues, sum);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// We're really interesting in catching anything that could have go wrong
|
||||
// in the try block, and return null. There's nothing we could do anyway.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The total count of accumulated values.
|
||||
///
|
||||
/// This is calculated from all recorded values.
|
||||
/// </summary>
|
||||
/// <returns>The count of accumulated values</returns>
|
||||
Int64 Count()
|
||||
{
|
||||
return Values.Values.Sum();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,242 +0,0 @@
|
|||
// 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 Mozilla.Glean.FFI;
|
||||
using Mozilla.Glean.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
///<summary>
|
||||
///Deserialized event data.
|
||||
///</summary>
|
||||
public readonly struct RecordedEventData
|
||||
{
|
||||
/// <summary>
|
||||
/// The event's category, part of the full identifier
|
||||
/// </summary>
|
||||
public string Category { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The event's name, part of the full identifier
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The event's timestamp
|
||||
/// </summary>
|
||||
public UInt64 Timestamp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Any extra data recorded for the event
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Extra { get; }
|
||||
|
||||
internal string Identifier { get => String.IsNullOrEmpty(Category) ? Name : $"{Category}.{Name}"; }
|
||||
|
||||
public RecordedEventData(string category, string name, UInt64 timestamp, Dictionary<string, string> extra = null)
|
||||
{
|
||||
Category = category;
|
||||
Name = name;
|
||||
Timestamp = timestamp;
|
||||
Extra = extra;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An enum with no values for convenient use as the default set of extra keys
|
||||
/// that an `EventMetricType` can accept.
|
||||
/// </summary>
|
||||
public enum NoExtraKeys : int
|
||||
{
|
||||
value
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This implements the developer facing API for recording events.
|
||||
///
|
||||
/// Instances of this class type are automatically generated by the parsers at built time,
|
||||
/// allowing developers to record events that were previously registered in the metrics.yaml file.
|
||||
/// </summary>
|
||||
public sealed class EventMetricType<ExtraKeysEnum> where ExtraKeysEnum : struct, Enum
|
||||
{
|
||||
private bool disabled;
|
||||
private string[] sendInPings;
|
||||
private UInt64 handle;
|
||||
|
||||
/// <summary>
|
||||
/// The public constructor used by automatically generated metrics.
|
||||
/// </summary>
|
||||
public EventMetricType(
|
||||
bool disabled,
|
||||
string category,
|
||||
Lifetime lifetime,
|
||||
string name,
|
||||
string[] sendInPings,
|
||||
string[] allowedExtraKeys = null
|
||||
) : this(0, disabled, sendInPings)
|
||||
{
|
||||
handle = LibGleanFFI.glean_new_event_metric(
|
||||
category: category,
|
||||
name: name,
|
||||
send_in_pings: sendInPings,
|
||||
send_in_pings_len: sendInPings.Length,
|
||||
lifetime: (int)lifetime,
|
||||
disabled: disabled,
|
||||
allowed_extra_keys: allowedExtraKeys,
|
||||
allowed_extra_keys_len: allowedExtraKeys == null ? 0 : allowedExtraKeys.Length);
|
||||
}
|
||||
|
||||
internal EventMetricType(
|
||||
UInt64 handle,
|
||||
bool disabled,
|
||||
string[] sendInPings
|
||||
)
|
||||
{
|
||||
this.disabled = disabled;
|
||||
this.sendInPings = sendInPings;
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Record an event by using the information provided by the instance of this class.
|
||||
/// </summary>
|
||||
/// <param name="extra">optional. This is a map, both keys and values need to be strings, keys are
|
||||
/// identifiers.This is used for events where additional richer context is needed.
|
||||
/// The maximum length for values is 100 bytes.</param>
|
||||
public void Record(Dictionary<ExtraKeysEnum, string> extra = null)
|
||||
{
|
||||
if (disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ulong timestamp = HighPrecisionTimestamp.GetTimestamp(TimeUnit.Millisecond);
|
||||
|
||||
Dispatchers.LaunchAPI(() => {
|
||||
// The Map is sent over FFI as a pair of arrays, one containing the
|
||||
// keys, and the other containing the values.
|
||||
Int32[] keys = null;
|
||||
string[] values = null;
|
||||
|
||||
Int32 len = 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.Cast<int>().ToArray();
|
||||
values = extra.Values.ToArray();
|
||||
len = extra.Count();
|
||||
}
|
||||
|
||||
LibGleanFFI.glean_event_record(
|
||||
handle,
|
||||
timestamp,
|
||||
keys,
|
||||
values,
|
||||
len
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// <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 Convert.ToBoolean(LibGleanFFI.glean_event_test_has_value(handle, ping));
|
||||
}
|
||||
|
||||
/// <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 RecordedEventData[] TestGetValue(string pingName = null)
|
||||
{
|
||||
Dispatchers.AssertInTestingMode();
|
||||
|
||||
if (!TestHasValue(pingName))
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
string ping = pingName ?? sendInPings[0];
|
||||
|
||||
string jsonValue = LibGleanFFI.glean_event_test_get_value_as_json_string(handle, ping).AsString();
|
||||
|
||||
List<RecordedEventData> parsedEvents = new List<RecordedEventData>();
|
||||
try
|
||||
{
|
||||
JsonDocument data = JsonDocument.Parse(jsonValue);
|
||||
JsonElement root = data.RootElement;
|
||||
|
||||
foreach (var entry in root.EnumerateArray())
|
||||
{
|
||||
// Parse the 'extra' properties, if available.
|
||||
Dictionary<string, string> parsedExtra = null;
|
||||
JsonElement jsonExtra;
|
||||
if (entry.TryGetProperty("extra", out jsonExtra))
|
||||
{
|
||||
parsedExtra = new Dictionary<string, string>();
|
||||
foreach (var extraEntry in jsonExtra.EnumerateObject())
|
||||
{
|
||||
parsedExtra.Add(extraEntry.Name, extraEntry.Value.GetString());
|
||||
}
|
||||
}
|
||||
|
||||
parsedEvents.Add(new RecordedEventData(
|
||||
category: entry.GetProperty("category").GetString(),
|
||||
name: entry.GetProperty("name").GetString(),
|
||||
timestamp: entry.GetProperty("timestamp").GetUInt64(),
|
||||
extra: parsedExtra
|
||||
));
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// We're interested in catching anything that could have gone wrong
|
||||
// in the try block, and throw.
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
return parsedEvents.ToArray();
|
||||
}
|
||||
|
||||
/// <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];
|
||||
return LibGleanFFI.glean_event_test_get_num_recorded_errors(
|
||||
handle, (int)errorType, ping
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,201 +0,0 @@
|
|||
// 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 Mozilla.Glean.FFI;
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
/// <summary>
|
||||
/// A representation of a JWE value.
|
||||
/// </summary>
|
||||
public readonly struct JweData
|
||||
{
|
||||
public JweData(string header, string key, string initVector, string cipherText, string authTag)
|
||||
{
|
||||
Header = header;
|
||||
Key = key;
|
||||
InitVector = initVector;
|
||||
CipherText = cipherText;
|
||||
AuthTag = authTag;
|
||||
}
|
||||
|
||||
public string Header { get; }
|
||||
public string Key { get; }
|
||||
public string InitVector { get; }
|
||||
public string CipherText { get; }
|
||||
public string AuthTag { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This implements the developer facing API for recording string metrics.
|
||||
///
|
||||
/// Instances of this class type are automatically generated by the parsers at build time,
|
||||
/// allowing developers to record values that were previously registered in the metrics.yaml file.
|
||||
///
|
||||
/// The internal constructor is only used by `LabeledMetricType` directly.
|
||||
/// </summary>
|
||||
public sealed class JweMetricType
|
||||
{
|
||||
private readonly bool disabled;
|
||||
private readonly string[] sendInPings;
|
||||
private readonly UInt64 handle;
|
||||
|
||||
public JweMetricType(
|
||||
bool disabled,
|
||||
string category,
|
||||
Lifetime lifetime,
|
||||
string name,
|
||||
string[] sendInPings
|
||||
) : this(0, disabled, sendInPings)
|
||||
{
|
||||
handle = LibGleanFFI.glean_new_jwe_metric(
|
||||
category: category,
|
||||
name: name,
|
||||
send_in_pings: sendInPings,
|
||||
send_in_pings_len: sendInPings.Length,
|
||||
lifetime: (int)lifetime,
|
||||
disabled: disabled);
|
||||
}
|
||||
|
||||
internal JweMetricType(
|
||||
UInt64 handle,
|
||||
bool disabled,
|
||||
string[] sendInPings
|
||||
)
|
||||
{
|
||||
this.disabled = disabled;
|
||||
this.sendInPings = sendInPings;
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// 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_jwe_test_has_value(this.handle, ping) != 0;
|
||||
}
|
||||
|
||||
/// <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 JweData TestGetValue(string pingName = null)
|
||||
{
|
||||
Dispatchers.AssertInTestingMode();
|
||||
|
||||
if (!TestHasValue(pingName)) {
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
string ping = pingName ?? sendInPings[0];
|
||||
|
||||
JsonDocument jsonPayload = JsonDocument.Parse(
|
||||
LibGleanFFI.glean_jwe_test_get_value_as_json_string(this.handle, ping).AsString()
|
||||
);
|
||||
JsonElement root = jsonPayload.RootElement;
|
||||
return new JweData(
|
||||
root.GetProperty("header").GetString(),
|
||||
root.GetProperty("key").GetString(),
|
||||
root.GetProperty("init_vector").GetString(),
|
||||
root.GetProperty("cipher_text").GetString(),
|
||||
root.GetProperty("auth_tag").GetString()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the stored value in the compact representation for testing purposes only.
|
||||
/// This function will attempt to await the last task (if any)
|
||||
/// writing to 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 testGetCompactRepresentation(string pingName = null)
|
||||
{
|
||||
Dispatchers.AssertInTestingMode();
|
||||
|
||||
if (!TestHasValue(pingName)) {
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
string ping = pingName ?? sendInPings[0];
|
||||
return LibGleanFFI.glean_jwe_test_get_value(this.handle, ping).AsString();
|
||||
}
|
||||
|
||||
/// <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];
|
||||
return LibGleanFFI.glean_jwe_test_get_num_recorded_errors(
|
||||
this.handle, (int)errorType, ping
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
// 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 Mozilla.Glean.FFI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// This empty interface must be implemented by all the submetric types
|
||||
/// supported by `LabeledMetricType` in order to allow the type casting
|
||||
/// powering its indexer implementation (subscript operator).
|
||||
/// </summary>
|
||||
public interface ILabeledSubmetricInterface {}
|
||||
|
||||
/// <summary>
|
||||
/// This implements the developer facing API for labeled metrics.
|
||||
///
|
||||
/// Instances of this class type are automatically generated by the parsers at build time,
|
||||
/// allowing developers to record values that were previously registered in the metrics.yaml file.
|
||||
///
|
||||
/// Unlike most metric types, `LabeledMetricType` does not have its own corresponding storage,
|
||||
/// but records metrics for the underlying metric type `T` in the storage for that type. The
|
||||
/// only difference is that labeled metrics are stored with the special key
|
||||
/// `$category.$name/$label`. The collect method knows how to pull these special values back
|
||||
/// out of the individual metric storage and rearrange them correctly in the ping.
|
||||
/// </summary>
|
||||
public sealed class LabeledMetricType<T> where T : ILabeledSubmetricInterface
|
||||
{
|
||||
private bool disabled;
|
||||
private string[] sendInPings;
|
||||
private T submetric;
|
||||
private UInt64 handle;
|
||||
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// If the type of the `submetric` parameter is currently not supported.
|
||||
/// </exception>
|
||||
public LabeledMetricType(
|
||||
bool disabled,
|
||||
string category,
|
||||
Lifetime lifetime,
|
||||
string name,
|
||||
string[] sendInPings,
|
||||
T submetric,
|
||||
HashSet<string> labels = null
|
||||
)
|
||||
{
|
||||
this.disabled = disabled;
|
||||
this.sendInPings = sendInPings;
|
||||
this.submetric = submetric;
|
||||
|
||||
Func<string, string, string[], int, int, bool, string[], int, UInt64> metricTypeInstantiator;
|
||||
switch (submetric)
|
||||
{
|
||||
case BooleanMetricType _:
|
||||
metricTypeInstantiator = LibGleanFFI.glean_new_labeled_boolean_metric;
|
||||
break;
|
||||
case CounterMetricType _:
|
||||
metricTypeInstantiator = LibGleanFFI.glean_new_labeled_counter_metric;
|
||||
break;
|
||||
case StringMetricType _:
|
||||
metricTypeInstantiator = LibGleanFFI.glean_new_labeled_string_metric;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Can not create a labeled version of this metric type");
|
||||
}
|
||||
|
||||
handle = metricTypeInstantiator(
|
||||
category,
|
||||
name,
|
||||
sendInPings,
|
||||
sendInPings.Length,
|
||||
(int)lifetime,
|
||||
disabled,
|
||||
labels?.ToArray(),
|
||||
(labels != null) ? labels.Count : 0);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
// 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/.
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
public enum Lifetime
|
||||
{
|
||||
/// <summary>
|
||||
/// The metric is reset with each sent ping
|
||||
/// </summary>
|
||||
Ping,
|
||||
/// <summary>
|
||||
/// The metric is reset on application restart
|
||||
/// </summary>
|
||||
Application,
|
||||
/// <summary>
|
||||
/// The metric is reset with each user profile
|
||||
/// </summary>
|
||||
User
|
||||
}
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
// 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 Mozilla.Glean.FFI;
|
||||
using System;
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
/// <summary>
|
||||
/// This implements the developer facing API for recording memory distribution metrics.
|
||||
///
|
||||
/// Instances of this class type are automatically generated by the parsers at build time,
|
||||
/// allowing developers to record values that were previously registered in the metrics.yaml file.
|
||||
/// </summary>
|
||||
public sealed class MemoryDistributionMetricType
|
||||
{
|
||||
private bool disabled;
|
||||
private string[] sendInPings;
|
||||
private UInt64 handle;
|
||||
|
||||
/// <summary>
|
||||
/// The public constructor used by automatically generated metrics.
|
||||
/// </summary>
|
||||
public MemoryDistributionMetricType(
|
||||
bool disabled,
|
||||
string category,
|
||||
Lifetime lifetime,
|
||||
string name,
|
||||
string[] sendInPings,
|
||||
MemoryUnit memoryUnit
|
||||
) : this(0, disabled, sendInPings)
|
||||
{
|
||||
handle = LibGleanFFI.glean_new_memory_distribution_metric(
|
||||
category: category,
|
||||
name: name,
|
||||
send_in_pings: sendInPings,
|
||||
send_in_pings_len: sendInPings.Length,
|
||||
lifetime: (int)lifetime,
|
||||
disabled: disabled,
|
||||
memory_unit: (int)memoryUnit
|
||||
);
|
||||
}
|
||||
|
||||
internal MemoryDistributionMetricType(
|
||||
UInt64 handle,
|
||||
bool disabled,
|
||||
string[] sendInPings
|
||||
)
|
||||
{
|
||||
this.disabled = disabled;
|
||||
this.sendInPings = sendInPings;
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// 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_memory_distribution_test_has_value(handle, ping) != 0;
|
||||
}
|
||||
|
||||
/// <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_memory_distribution_test_get_value_as_json_string(handle, ping).AsString());
|
||||
}
|
||||
|
||||
/// <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];
|
||||
return LibGleanFFI.glean_memory_distribution_test_get_num_recorded_errors(
|
||||
handle, (int)errorType, ping
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration of different resolutions supported by the MemoryDistribution metric type.
|
||||
///
|
||||
/// These use the power-of-2 values of these units, that is, Kilobyte is pedantically a Kibibyte.
|
||||
/// </summary>
|
||||
public enum MemoryUnit: int
|
||||
{
|
||||
///<summary>Byte: 1 byte.</summary>
|
||||
Byte,
|
||||
|
||||
///<summary>Kilobyte: 2^10 bytes.</summary>
|
||||
Kilobyte,
|
||||
|
||||
///<summary>Megabyte: 2^20 bytes.</summary>
|
||||
Megabyte,
|
||||
|
||||
///<summary>Gigabyte: 2^30 bytes</summary>
|
||||
Gigabyte,
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
// 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 Mozilla.Glean.FFI;
|
||||
using System;
|
||||
using static Mozilla.Glean.Glean;
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
/// <summary>
|
||||
/// An enum with no values for convenient use as the default set of reason codes.
|
||||
/// </summary>
|
||||
public enum NoReasonCodes : int
|
||||
{
|
||||
value
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The base class of all PingTypes with just enough to track their registration, so
|
||||
/// we can create a heterogeneous collection of ping types.
|
||||
/// </summary>
|
||||
public class PingTypeBase
|
||||
{
|
||||
internal string name;
|
||||
internal UInt64 handle;
|
||||
|
||||
protected internal PingTypeBase() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This implements the developer facing API for custom pings.
|
||||
/// Instances of this class type are automatically generated by the parsers at build time.
|
||||
/// The Ping API only exposes the [send] method, which schedules a ping for sending.
|
||||
/// </summary>
|
||||
public class PingType<ReasonCodesEnum> : PingTypeBase where ReasonCodesEnum : struct, Enum
|
||||
{
|
||||
private readonly string[] reasonCodes;
|
||||
|
||||
public PingType (
|
||||
string name,
|
||||
bool includeClientId,
|
||||
bool sendIfEmpty,
|
||||
string[] reasonCodes
|
||||
)
|
||||
{
|
||||
handle = LibGleanFFI.glean_new_ping_type(
|
||||
name: name,
|
||||
include_client_id: Convert.ToByte(includeClientId),
|
||||
send_if_empty: Convert.ToByte(sendIfEmpty),
|
||||
reason: reasonCodes,
|
||||
reason_codes_len: reasonCodes == null ? 0 : reasonCodes.Length
|
||||
);
|
||||
|
||||
this.name = name;
|
||||
this.reasonCodes = reasonCodes;
|
||||
|
||||
GleanInstance.RegisterPingType(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collect and submit the ping for eventual upload.
|
||||
///
|
||||
/// While the collection of metrics into pings happens synchronously, the
|
||||
/// ping queuing and ping uploading happens asyncronously.
|
||||
/// There are no guarantees that this will happen immediately.
|
||||
///
|
||||
/// If the ping currently contains no content, it will not be queued.
|
||||
/// </summary>
|
||||
/// <param name="reason">The reason code enum for ping submit.</param>
|
||||
public void Submit(ReasonCodesEnum? reason = null)
|
||||
{
|
||||
int enumValue = Convert.ToInt32(reason.GetValueOrDefault());
|
||||
string reasonString =
|
||||
(reason != null && reasonCodes != null && reasonCodes.Length > 0) ? reasonCodes[enumValue] : null;
|
||||
GleanInstance.SubmitPing(this, reasonString);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
// 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 Mozilla.Glean.FFI;
|
||||
using System;
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
/// <summary>
|
||||
/// This implements the developer facing API for recording quantity metrics.
|
||||
///
|
||||
/// Instances of this class type are automatically generated by the parsers at build time,
|
||||
/// allowing developers to record values that were previously registered in the metrics.yaml file.
|
||||
///
|
||||
/// The quantity API only exposes the [set] method.
|
||||
//
|
||||
/// </summary>
|
||||
public sealed class QuantityMetricType {
|
||||
private bool disabled;
|
||||
private string[] sendInPings;
|
||||
private UInt64 handle;
|
||||
|
||||
/// <summary>
|
||||
/// The public constructor used by automatically generated metrics.
|
||||
/// </summary>
|
||||
public QuantityMetricType(
|
||||
bool disabled,
|
||||
string category,
|
||||
Lifetime lifetime,
|
||||
string name,
|
||||
string[] sendInPings
|
||||
) : this(0, disabled, sendInPings)
|
||||
{
|
||||
handle = LibGleanFFI.glean_new_quantity_metric(
|
||||
category: category,
|
||||
name: name,
|
||||
send_in_pings: sendInPings,
|
||||
send_in_pings_len: sendInPings.Length,
|
||||
lifetime: (int)lifetime,
|
||||
disabled: disabled);
|
||||
}
|
||||
|
||||
internal QuantityMetricType(
|
||||
UInt64 handle,
|
||||
bool disabled,
|
||||
string[] sendInPings
|
||||
)
|
||||
{
|
||||
this.disabled = disabled;
|
||||
this.sendInPings = sendInPings;
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
|
||||
/// <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>
|
||||
/// 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_quantity_test_has_value(this.handle, ping);
|
||||
}
|
||||
|
||||
/// <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 Int32 TestGetValue(string pingName = null)
|
||||
{
|
||||
Dispatchers.AssertInTestingMode();
|
||||
|
||||
if (!TestHasValue(pingName))
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
string ping = pingName ?? sendInPings[0];
|
||||
return LibGleanFFI.glean_quantity_test_get_value(this.handle, ping);
|
||||
}
|
||||
|
||||
/// <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];
|
||||
return LibGleanFFI.glean_quantity_test_get_num_recorded_errors(
|
||||
handle, (int)errorType, ping
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
/* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
// 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 Mozilla.Glean.FFI;
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
/// <summary>
|
||||
/// This implements the developer facing API for recording string list metrics.
|
||||
///
|
||||
/// Instances of this class type are automatically generated by the parsers at build time,
|
||||
/// allowing developers to record values that were previously registered in the metrics.yaml file.
|
||||
///
|
||||
/// The internal constructor is only used by [LabeledMetricType] directly.
|
||||
/// </summary>
|
||||
public sealed class StringListMetricType
|
||||
{
|
||||
private readonly bool disabled;
|
||||
private readonly string[] sendInPings;
|
||||
private readonly UInt64 handle;
|
||||
|
||||
public StringListMetricType(
|
||||
bool disabled,
|
||||
string category,
|
||||
Lifetime lifetime,
|
||||
string name,
|
||||
string[] sendInPings
|
||||
) : this(0, disabled, sendInPings)
|
||||
{
|
||||
handle = LibGleanFFI.glean_new_string_list_metric(
|
||||
category: category,
|
||||
name: name,
|
||||
send_in_pings: sendInPings,
|
||||
send_in_pings_len: sendInPings.Length,
|
||||
lifetime: (int)lifetime,
|
||||
disabled: disabled);
|
||||
}
|
||||
|
||||
internal StringListMetricType(
|
||||
UInt64 handle,
|
||||
bool disabled,
|
||||
string[] sendInPings
|
||||
)
|
||||
{
|
||||
this.disabled = disabled;
|
||||
this.sendInPings = sendInPings;
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Sets a string list to one or more 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 list.</param>
|
||||
public void Set(string[] value)
|
||||
{
|
||||
if (disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatchers.LaunchAPI(() => {
|
||||
SetSync(value);
|
||||
});
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// 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_list_test_has_value(this.handle, ping) != 0;
|
||||
}
|
||||
|
||||
/// <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 string[] TestGetValue(string pingName = null)
|
||||
{
|
||||
Dispatchers.AssertInTestingMode();
|
||||
|
||||
if (!TestHasValue(pingName))
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
string ping = pingName ?? sendInPings[0];
|
||||
|
||||
JsonDocument jsonPayload = JsonDocument.Parse(
|
||||
LibGleanFFI.glean_string_list_test_get_value_as_json_string(this.handle, ping).AsString()
|
||||
);
|
||||
JsonElement root = jsonPayload.RootElement;
|
||||
return JsonSerializer.Deserialize<string[]>(root.ToString());
|
||||
}
|
||||
|
||||
/// <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 int TestGetNumRecordedErrors(Testing.ErrorType errorType, string pingName = null)
|
||||
{
|
||||
Dispatchers.AssertInTestingMode();
|
||||
|
||||
string ping = pingName ?? sendInPings[0];
|
||||
return LibGleanFFI.glean_string_list_test_get_num_recorded_errors(
|
||||
this.handle, (int)errorType, ping
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
// 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 Mozilla.Glean.FFI;
|
||||
using System;
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
/// <summary>
|
||||
/// This implements the developer facing API for recording string metrics.
|
||||
///
|
||||
/// Instances of this class type are automatically generated by the parsers at build time,
|
||||
/// allowing developers to record values that were previously registered in the metrics.yaml file.
|
||||
///
|
||||
/// The internal constructor is only used by `LabeledMetricType` directly.
|
||||
/// </summary>
|
||||
public sealed class StringMetricType : ILabeledSubmetricInterface
|
||||
{
|
||||
private readonly bool disabled;
|
||||
private readonly string[] sendInPings;
|
||||
private readonly UInt64 handle;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
internal StringMetricType(
|
||||
UInt64 handle,
|
||||
bool disabled,
|
||||
string[] sendInPings
|
||||
)
|
||||
{
|
||||
this.disabled = disabled;
|
||||
this.sendInPings = sendInPings;
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set 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>
|
||||
public void Set(string value)
|
||||
{
|
||||
if (disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatchers.LaunchAPI(() => {
|
||||
SetSync(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>
|
||||
/// 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 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration of different resolutions supported by
|
||||
/// the Timespan and DateTime metric types.
|
||||
/// </summary>
|
||||
public enum TimeUnit: int
|
||||
{
|
||||
// Represents nanosecond precision.
|
||||
Nanosecond,
|
||||
|
||||
// Represents microsecond precision.
|
||||
Microsecond,
|
||||
|
||||
// Represents millisecond precision.
|
||||
Millisecond,
|
||||
|
||||
// Represents second precision.
|
||||
Second,
|
||||
|
||||
// Represents minute precision.
|
||||
Minute,
|
||||
|
||||
// Represents hour precision.
|
||||
Hour,
|
||||
|
||||
// Represents day precision.
|
||||
Day,
|
||||
}
|
||||
}
|
|
@ -1,214 +0,0 @@
|
|||
// 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 Mozilla.Glean.FFI;
|
||||
using Mozilla.Glean.Utils;
|
||||
using System;
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
/// <summary>
|
||||
/// This implements the developer facing API for recording timespan metrics.
|
||||
///
|
||||
/// Instances of this class type are automatically generated by the parsers at build time,
|
||||
/// allowing developers to record values that were previously registered in the metrics.yaml file.
|
||||
/// </summary>
|
||||
public sealed class TimespanMetricType
|
||||
{
|
||||
private bool disabled;
|
||||
private string[] sendInPings;
|
||||
private UInt64 handle;
|
||||
|
||||
/// <summary>
|
||||
/// The public constructor used by automatically generated metrics.
|
||||
/// </summary>
|
||||
public TimespanMetricType(
|
||||
bool disabled,
|
||||
string category,
|
||||
Lifetime lifetime,
|
||||
string name,
|
||||
string[] sendInPings,
|
||||
TimeUnit timeUnit = TimeUnit.Minute
|
||||
) : this(0, disabled, sendInPings)
|
||||
{
|
||||
handle = LibGleanFFI.glean_new_timespan_metric(
|
||||
category: category,
|
||||
name: name,
|
||||
send_in_pings: sendInPings,
|
||||
send_in_pings_len: sendInPings.Length,
|
||||
lifetime: (int)lifetime,
|
||||
disabled: disabled,
|
||||
time_unit: (int)timeUnit
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The internal constructor is only used by `LabeledMetricType` directly.
|
||||
/// </summary>
|
||||
internal TimespanMetricType(
|
||||
UInt64 handle,
|
||||
bool disabled,
|
||||
string[] sendInPings
|
||||
)
|
||||
{
|
||||
this.disabled = disabled;
|
||||
this.sendInPings = sendInPings;
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/// <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 `Stop`): in that case the original
|
||||
/// start time will be preserved.
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
if (disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ulong startTime = HighPrecisionTimestamp.GetTimestamp(TimeUnit.Nanosecond);
|
||||
|
||||
Dispatchers.LaunchAPI(() => {
|
||||
LibGleanFFI.glean_timespan_set_start(handle, startTime);
|
||||
});
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Convenience method to simplify measuring a function or block of code
|
||||
///
|
||||
/// If the measured function throws, the measurement is canceled and the exception rethrown.
|
||||
/// </summary>
|
||||
/// <exception>If the measured function throws, the measurement is
|
||||
/// canceled and the exception rethrown.</exception>
|
||||
public T Measure<T>(Func<T> funcToMeasure)
|
||||
{
|
||||
Start();
|
||||
|
||||
T returnValue;
|
||||
|
||||
try {
|
||||
returnValue = funcToMeasure();
|
||||
} catch (Exception e) {
|
||||
Cancel();
|
||||
throw e;
|
||||
}
|
||||
|
||||
Stop();
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// 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>
|
||||
/// Tests whether a value is stored for the metric for testing purposes only.
|
||||
/// </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_timespan_test_has_value(this.handle, ping) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the stored value for testing purposes only.
|
||||
/// </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 ulong TestGetValue(string pingName = null)
|
||||
{
|
||||
Dispatchers.AssertInTestingMode();
|
||||
|
||||
if (!TestHasValue(pingName))
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
string ping = pingName ?? sendInPings[0];
|
||||
return LibGleanFFI.glean_timespan_test_get_value(this.handle, ping);
|
||||
}
|
||||
|
||||
/// <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];
|
||||
return LibGleanFFI.glean_timespan_test_get_num_recorded_errors(
|
||||
this.handle, (int)errorType, ping
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,241 +0,0 @@
|
|||
// 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 Mozilla.Glean.FFI;
|
||||
using Mozilla.Glean.Utils;
|
||||
using System;
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
/// <summary>
|
||||
/// An opaque type representing a Glean timer.
|
||||
/// </summary>
|
||||
public class GleanTimerId
|
||||
{
|
||||
private readonly ulong timerId;
|
||||
|
||||
// Don't allow direct instantiations.
|
||||
private GleanTimerId() { }
|
||||
|
||||
private GleanTimerId(ulong id)
|
||||
{
|
||||
timerId = id;
|
||||
}
|
||||
|
||||
public static implicit operator ulong(GleanTimerId id) => id.timerId;
|
||||
public static explicit operator GleanTimerId(ulong b) => new GleanTimerId(b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This implements the developer facing API for recording timing distribution metrics.
|
||||
///
|
||||
/// Instances of this class type are automatically generated by the parsers at build time,
|
||||
/// allowing developers to record values that were previously registered in the metrics.yaml file.
|
||||
/// </summary>
|
||||
public sealed class TimingDistributionMetricType
|
||||
{
|
||||
private bool disabled;
|
||||
private string[] sendInPings;
|
||||
private UInt64 handle;
|
||||
|
||||
/// <summary>
|
||||
/// The public constructor used by automatically generated metrics.
|
||||
/// </summary>
|
||||
public TimingDistributionMetricType(
|
||||
bool disabled,
|
||||
string category,
|
||||
Lifetime lifetime,
|
||||
string name,
|
||||
string[] sendInPings,
|
||||
TimeUnit timeUnit = TimeUnit.Minute
|
||||
) : this(0, disabled, sendInPings)
|
||||
{
|
||||
handle = LibGleanFFI.glean_new_timing_distribution_metric(
|
||||
category: category,
|
||||
name: name,
|
||||
send_in_pings: sendInPings,
|
||||
send_in_pings_len: sendInPings.Length,
|
||||
lifetime: (int)lifetime,
|
||||
disabled: disabled,
|
||||
time_unit: (int)timeUnit
|
||||
);
|
||||
}
|
||||
|
||||
internal TimingDistributionMetricType(
|
||||
UInt64 handle,
|
||||
bool disabled,
|
||||
string[] sendInPings
|
||||
)
|
||||
{
|
||||
this.disabled = disabled;
|
||||
this.sendInPings = sendInPings;
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop tracking time for the provided metric and associated timer id. Add a
|
||||
/// count to the corresponding bucket in the timing distribution.
|
||||
/// This will record an error 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.
|
||||
/// </param>
|
||||
public void StopAndAccumulate(GleanTimerId timerId)
|
||||
{
|
||||
// `Start` might return null.
|
||||
// Accepting that means users of this API don't need to do a null check.
|
||||
if (disabled || timerId == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The Rust code runs async and might be delayed. We need the time as precise as possible.
|
||||
// We also need the same clock for start and stop (`Start` takes the time on the C# side).
|
||||
ulong stopTime = HighPrecisionTimestamp.GetTimestamp(TimeUnit.Nanosecond);
|
||||
|
||||
Dispatchers.LaunchAPI(() =>
|
||||
{
|
||||
LibGleanFFI.glean_timing_distribution_set_stop_and_accumulate(
|
||||
handle,
|
||||
timerId,
|
||||
stopTime
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
///<summary>
|
||||
/// Convenience method to simplify measuring a function or block of code
|
||||
///
|
||||
/// If the measured function throws, the measurement is canceled and the exception rethrown.
|
||||
/// </summary>
|
||||
/// <exception>
|
||||
/// If the measured function throws, the measurement is
|
||||
/// canceled and the exception rethrown.
|
||||
/// </exception>
|
||||
public T Measure<T>(Func<T> funcToMeasure)
|
||||
{
|
||||
GleanTimerId timerId = Start();
|
||||
|
||||
T returnValue;
|
||||
|
||||
try
|
||||
{
|
||||
returnValue = funcToMeasure();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Cancel(timerId);
|
||||
throw e;
|
||||
}
|
||||
|
||||
StopAndAccumulate(timerId);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// 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_timing_distribution_test_has_value(handle, ping) != 0;
|
||||
}
|
||||
|
||||
/// <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());
|
||||
}
|
||||
|
||||
/// <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];
|
||||
return LibGleanFFI.glean_timing_distribution_test_get_num_recorded_errors(
|
||||
handle, (int)errorType, ping
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,143 +0,0 @@
|
|||
// 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 Mozilla.Glean.FFI;
|
||||
using System;
|
||||
|
||||
namespace Mozilla.Glean.Private
|
||||
{
|
||||
/// <summary>
|
||||
/// This implements the developer facing API for recording UUID metrics.
|
||||
///
|
||||
/// Instances of this class type are automatically generated by the parsers at build time,
|
||||
/// allowing developers to record values that were previously registered in the metrics.yaml file.
|
||||
///
|
||||
/// The UUID API exposes the [generateAndSet] and [set] methods.
|
||||
///
|
||||
/// The internal constructor is only used by [LabeledMetricType] directly.
|
||||
/// </summary>
|
||||
public sealed class UuidMetricType
|
||||
{
|
||||
private bool disabled;
|
||||
private string[] sendInPings;
|
||||
private UInt64 handle;
|
||||
|
||||
/// <summary>
|
||||
/// The public constructor used by automatically generated metrics.
|
||||
/// </summary>
|
||||
public UuidMetricType(
|
||||
bool disabled,
|
||||
string category,
|
||||
Lifetime lifetime,
|
||||
string name,
|
||||
string[] sendInPings
|
||||
) : this(0, disabled, sendInPings)
|
||||
{
|
||||
handle = LibGleanFFI.glean_new_uuid_metric(
|
||||
category: category,
|
||||
name: name,
|
||||
send_in_pings: sendInPings,
|
||||
send_in_pings_len: sendInPings.Length,
|
||||
lifetime: (int)lifetime,
|
||||
disabled: disabled);
|
||||
}
|
||||
|
||||
internal UuidMetricType(
|
||||
UInt64 handle,
|
||||
bool disabled,
|
||||
string[] sendInPings
|
||||
)
|
||||
{
|
||||
this.disabled = disabled;
|
||||
this.sendInPings = sendInPings;
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new UUID value and set it in the metric store.
|
||||
/// </summary>
|
||||
public Guid? GenerateAndSet()
|
||||
{
|
||||
// Even if `set` is already checking if we're allowed to record,
|
||||
// we need to check here as well otherwise we'd return a `UUID`
|
||||
// that won't be stored anywhere.
|
||||
if (disabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var uuid = System.Guid.NewGuid();
|
||||
Set(uuid);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly set an existing UUID value.
|
||||
/// </summary>
|
||||
/// <param name="value"> A valid [UUID] to set the metric to.
|
||||
public void Set(Guid value)
|
||||
{
|
||||
if (disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatchers.LaunchAPI(() =>
|
||||
{
|
||||
SetSync(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>
|
||||
/// 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();
|
||||
|
||||
var ping = pingName ?? sendInPings[0];
|
||||
return LibGleanFFI.glean_uuid_test_has_value(this.handle, ping) != 0;
|
||||
}
|
||||
|
||||
/// <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 Guid TestGetValue(string pingName = null)
|
||||
{
|
||||
Dispatchers.AssertInTestingMode();
|
||||
|
||||
if (!TestHasValue(pingName))
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
var ping = pingName ?? sendInPings[0];
|
||||
return Guid.Parse(LibGleanFFI.glean_uuid_test_get_value(this.handle, ping).AsString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
// 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 Mozilla.Glean.FFI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using Serilog;
|
||||
using static Mozilla.Glean.Utils.GleanLogger;
|
||||
|
||||
namespace Mozilla.Glean.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// The logic for uploading pings: this leaves the actual upload implementation
|
||||
/// to the user-provided delegate.
|
||||
/// </summary>
|
||||
internal class BaseUploader
|
||||
{
|
||||
internal const int THROTTLED_BACKOFF_MS = 60_000;
|
||||
private readonly IPingUploader uploader;
|
||||
|
||||
/// <summary>
|
||||
/// This is the tag used for logging from this class.
|
||||
/// </summary>
|
||||
private const string LogTag = "glean/BaseUploader";
|
||||
|
||||
/// <summary>
|
||||
/// A logger configured for this class
|
||||
/// </summary>
|
||||
private static readonly ILogger Log = GetLogger(LogTag);
|
||||
|
||||
internal BaseUploader(IPingUploader uploader)
|
||||
{
|
||||
this.uploader = uploader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function triggers the actual upload.
|
||||
///
|
||||
/// It calls the implementation-specific upload function.
|
||||
/// </summary>
|
||||
/// <param name="url">the URL path to upload the data to</param>
|
||||
/// <param name="data">the serialized text data to send</param>
|
||||
/// <param name="headers">a vector of tuples containing the headers to add</param>
|
||||
/// <param name="config">the Glean configuration</param>
|
||||
/// <returns>An `UploadResult` representing the response that came from
|
||||
/// the upload attempt.</returns>
|
||||
internal UploadResult Upload(string url, byte[] data, (string, string)[] headers, Configuration config)
|
||||
{
|
||||
// Bail out if we don't have a valid uploader. This should never happen outside of tests.
|
||||
if (uploader == null)
|
||||
{
|
||||
Log.Error("No HTTP uploader defined. Please set it in the Glean SDK configuration.");
|
||||
return new RecoverableFailure();
|
||||
}
|
||||
|
||||
return uploader.Upload(config.serverEndpoint + url, data, headers);
|
||||
}
|
||||
|
||||
internal static (string, string)[] GetHeadersFromJSONString(string documentId, string headers, Configuration config)
|
||||
{
|
||||
List<(string, string)> headerList = new List<(string, string)>();
|
||||
try
|
||||
{
|
||||
// Parse the headers from JSON.
|
||||
Dictionary<string, string> parsedHeaders = JsonSerializer.Deserialize<Dictionary<string, string>>(headers);
|
||||
|
||||
foreach (KeyValuePair<string, string> h in parsedHeaders)
|
||||
{
|
||||
headerList.Add((h.Key, h.Value));
|
||||
}
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
Log.Error(e, $"Error while parsing headers for ping {documentId}");
|
||||
}
|
||||
|
||||
return headerList.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signals Glean to upload pings at the next best opportunity.
|
||||
/// </summary>
|
||||
internal void TriggerUpload(Configuration config)
|
||||
{
|
||||
// TODO: must not work like this, it should work off the main thread.
|
||||
// FOR TESTING Implement the upload worker here and call this from Glean.cs
|
||||
|
||||
// Limits are enforced by glean-core to avoid an inifinite loop here.
|
||||
// Whenever a limit is reached, this binding will receive `UploadTaskTag.Done` and step out.
|
||||
while (true)
|
||||
{
|
||||
FfiUploadTask incomingTask = new FfiUploadTask();
|
||||
LibGleanFFI.glean_get_upload_task(ref incomingTask);
|
||||
|
||||
UploadTaskTag tag = (UploadTaskTag)incomingTask.tag;
|
||||
switch (tag)
|
||||
{
|
||||
case UploadTaskTag.Upload:
|
||||
{
|
||||
// Extract C#-friendly data from the FFI object
|
||||
string documentId = LibGleanFFI.GetFromRustString(incomingTask.body.documentId);
|
||||
string path = LibGleanFFI.GetFromRustString(incomingTask.body.path);
|
||||
string headersString = LibGleanFFI.GetFromRustString(incomingTask.body.headers);
|
||||
(string, string)[] headers = GetHeadersFromJSONString(documentId, headersString, config);
|
||||
byte[] body = new byte[incomingTask.body.bodyLen];
|
||||
Marshal.Copy(incomingTask.body.body, body, 0, body.Length);
|
||||
|
||||
// Delegate the actual upload and get its return value.
|
||||
UploadResult result = Upload(path, body, headers, config);
|
||||
|
||||
// Copy the `FfiUploadTask` to unmanaged memory, because
|
||||
// `glean_process_ping_upload` assumes it has to free the memory.
|
||||
IntPtr ptrCopy = Marshal.AllocHGlobal(Marshal.SizeOf(incomingTask));
|
||||
Marshal.StructureToPtr(incomingTask, ptrCopy, false);
|
||||
|
||||
// Process the upload response in the core.
|
||||
LibGleanFFI.glean_process_ping_upload_response(ptrCopy, result.ToFfi());
|
||||
|
||||
// Free the allocated.
|
||||
Marshal.FreeHGlobal(ptrCopy);
|
||||
}
|
||||
break;
|
||||
case UploadTaskTag.Wait:
|
||||
Thread.Sleep(THROTTLED_BACKOFF_MS);
|
||||
break;
|
||||
case UploadTaskTag.Done:
|
||||
// Nothing to do here, break out of the loop.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel any outstanding upload.
|
||||
/// </summary>
|
||||
internal void CancelUploads()
|
||||
{
|
||||
// TODO: to be implemented once a real HTTP uploader is added.
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
// 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.Net.Http;
|
||||
|
||||
namespace Mozilla.Glean.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple ping uploader that uses the `HttpClient` C# object.
|
||||
/// </summary>
|
||||
public class HttpClientUploader : IPingUploader
|
||||
{
|
||||
// The timeout, in milliseconds, to use when connecting to the server.
|
||||
internal const int DEFAULT_CONNECTION_TIMEOUT_MS = 10000;
|
||||
|
||||
internal readonly HttpClient httpClient;
|
||||
|
||||
public HttpClientUploader() : this(null)
|
||||
{
|
||||
}
|
||||
|
||||
// This constructor is only meant to be used for testing.
|
||||
internal HttpClientUploader(HttpMessageHandler customHandler)
|
||||
{
|
||||
if (customHandler != null)
|
||||
{
|
||||
httpClient = new HttpClient(customHandler);
|
||||
}
|
||||
else
|
||||
{
|
||||
httpClient = new HttpClient();
|
||||
}
|
||||
|
||||
httpClient.Timeout = TimeSpan.FromMilliseconds(DEFAULT_CONNECTION_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
public UploadResult Upload(string url, byte[] data, (string, string)[] headers)
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpRequestMessage request = new HttpRequestMessage(new HttpMethod("POST"), url);
|
||||
|
||||
// TODO: Verify that cookies are not being sent in bug 1646778
|
||||
|
||||
// Create `request.Content` first, as we might need to slap headers on it.
|
||||
request.Content = new ByteArrayContent(data);
|
||||
|
||||
foreach ((string name, string value) header in headers)
|
||||
{
|
||||
// The `HttpRequestMessage` separates the headers in categories: 'Content-*' headers
|
||||
// have to be set in `request.Content.Headers.*` while other headers can be set
|
||||
// through `request.Headers`. Let's try to deal with this by checking the header name.
|
||||
// The following code will throw an exception if glean-core sends an header that's
|
||||
// mistakenly routed to the wrong category, but that's fine: we'll hopefully catch that
|
||||
// in integration tests.
|
||||
if (header.name.StartsWith("Content-", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
request.Content.Headers.Add(header.name, header.value);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ok, that's not a content header. Try with a request header.
|
||||
request.Headers.Add(header.name, header.value);
|
||||
}
|
||||
|
||||
// While `SendAsync` is triggering an off-the-main thread request, we synchronously wait
|
||||
// for it to finish by adding `.Result`. We're fine with doing that as we want the non-blocking
|
||||
var httpResponseMessage = httpClient.SendAsync(request).Result;
|
||||
|
||||
return new HttpResponse((int)httpResponseMessage.StatusCode);
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
// There's nothing we can do to recover from this here. So let's just log an error and
|
||||
// notify the service that this job has been completed - even though we didn't upload
|
||||
// anything to the server.
|
||||
Console.WriteLine("Glean - Could not upload telemetry due to malformed URL");
|
||||
return new UnrecoverableFailure();
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException || ex is System.Threading.Tasks.TaskCanceledException)
|
||||
{
|
||||
// The request failed due to an underlying issue such as network connectivity. We need to catch
|
||||
// both `HttpRequestException` and `TaskCanceledException`: the former for HTTP specific exception
|
||||
// and the latter for the task being cancelled due to hitting the provided connection timeout.
|
||||
Console.WriteLine("Glean - there was a problem while performing the network request.");
|
||||
return new RecoverableFailure();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
// 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/.
|
||||
|
||||
namespace Mozilla.Glean.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// The result of the ping upload.
|
||||
///
|
||||
/// See below for the different possible cases.
|
||||
/// </summary>
|
||||
public class UploadResult
|
||||
{
|
||||
public virtual int ToFfi()
|
||||
{
|
||||
return (int)FFI.Constants.UPLOAD_RESULT_UNRECOVERABLE;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A HTTP response code.
|
||||
///
|
||||
/// This can still indicate an error, depending on the status code.
|
||||
/// </summary>
|
||||
public class HttpResponse : UploadResult
|
||||
{
|
||||
internal readonly int statusCode;
|
||||
|
||||
public HttpResponse(int statusCode)
|
||||
{
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public override int ToFfi()
|
||||
{
|
||||
return (int)FFI.Constants.UPLOAD_RESULT_HTTP_STATUS | statusCode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An unrecoverable upload failure.
|
||||
///
|
||||
/// A possible cause might be a malformed URL.
|
||||
/// The ping data is removed afterwards.
|
||||
/// </summary>
|
||||
public class UnrecoverableFailure : UploadResult
|
||||
{
|
||||
public override int ToFfi()
|
||||
{
|
||||
return (int)FFI.Constants.UPLOAD_RESULT_UNRECOVERABLE;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An unrecoverable upload failure.
|
||||
///
|
||||
/// During upload something went wrong,
|
||||
/// e.g. the network connection failed.
|
||||
/// The upload should be retried at a later time.
|
||||
/// </summary>
|
||||
public class RecoverableFailure : UploadResult
|
||||
{
|
||||
public override int ToFfi()
|
||||
{
|
||||
return (int)FFI.Constants.UPLOAD_RESULT_RECOVERABLE;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The interface defining how to send pings.
|
||||
/// </summary>
|
||||
public interface IPingUploader
|
||||
{
|
||||
/// <summary>
|
||||
/// Synchronously upload a ping to a server.
|
||||
/// </summary>
|
||||
/// <param name="url">the URL path to upload the data to</param>
|
||||
/// <param name="data">the serialized text data to send</param>
|
||||
/// <param name="headers">a vector of tuples containing the headers to add</param>
|
||||
/// <returns>An `UploadResult` representing the response that came from
|
||||
/// the upload attempt.</returns>
|
||||
UploadResult Upload(string url, byte[] data, (string, string)[] headers);
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// 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/.
|
||||
|
||||
namespace Mozilla.Glean.Testing
|
||||
{
|
||||
/// <summary>
|
||||
/// Different types of errors that can be reported through Glean's error reporting metrics.
|
||||
/// </summary>
|
||||
public enum ErrorType
|
||||
{
|
||||
/// <summary>
|
||||
/// For when the value to be recorded does not match the metric-specific restrictions
|
||||
/// </summary>
|
||||
InvalidValue,
|
||||
|
||||
/// <summary>
|
||||
/// For when the label of a labeled metric does not match the restrictions
|
||||
/// </summary>
|
||||
InvalidLabel,
|
||||
|
||||
/// <summary>
|
||||
/// For when timings are recorded incorrectly
|
||||
/// </summary>
|
||||
InvalidState,
|
||||
|
||||
/// <summary>
|
||||
/// For when the value to be recorded overflows the metric-specific upper range
|
||||
/// </summary>
|
||||
InvalidOverflow
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
// 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 Serilog;
|
||||
|
||||
namespace Mozilla.Glean.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a utility class to wrap the Serilog logger and simplify setup
|
||||
/// by returning a correctly configured logger while only passing in the
|
||||
/// log tag.
|
||||
/// </summary>
|
||||
internal static class GleanLogger
|
||||
{
|
||||
public static ILogger GetLogger(string logTag)
|
||||
{
|
||||
return new LoggerConfiguration()
|
||||
.WriteTo.Console(outputTemplate:
|
||||
"[{Timestamp:HH:mm:ss} {Level:u3}] {LogTag}: {Message:lj}{NewLine}{Exception}")
|
||||
.CreateLogger().ForContext("LogTag", logTag);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
// 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 Mozilla.Glean.Private;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Mozilla.Glean.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps an high precision timer.
|
||||
/// </summary>
|
||||
internal static class HighPrecisionTimestamp
|
||||
{
|
||||
// The number of nanoseconds within a second, used to convert ticks
|
||||
// to nanoseconds.
|
||||
private const long NanosecondsInSeconds = 1000000000;
|
||||
|
||||
// The number of milliseconds within a second, used to convert ticks
|
||||
// to milliseconds.
|
||||
private const long MillisecondsInSeconds = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// *** ONLY FOR TESTS ***
|
||||
///
|
||||
/// When not `null` this sets the value that will be returned
|
||||
/// by `GetTimestamp`.
|
||||
/// </summary>
|
||||
internal static ulong? MockedValue { get; set; }
|
||||
|
||||
private static ulong GetTimeFromTicks(long ticks, long unitsInSeconds)
|
||||
{
|
||||
// The computation below is a bit tricky: make sure all the divisions happen in double, then
|
||||
// cast it back to ulong to avoid overflows.
|
||||
return (ulong)(ticks / (1.0 * Stopwatch.Frequency) * unitsInSeconds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current timestamp in the requested time unit.
|
||||
///
|
||||
/// Note that only `TimeUnit.Nanosecond` and `TimeUnit.Millisecond` are
|
||||
/// supported. This will throw an exception on other units.
|
||||
/// </summary>
|
||||
/// <param name="unit">
|
||||
/// Either `TimeUnit.Nanosecond` or `TimeUnit.Millisecond` to indicate which
|
||||
/// unit the timestamp should be converted to.
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// If some value other than `TimeUnit.Nanosecond` or `TimeUnit.Millisecond` was
|
||||
/// provided as input.
|
||||
/// </exception>
|
||||
/// <returns>The timestamp in the desired time unit</returns>
|
||||
public static ulong GetTimestamp(TimeUnit unit)
|
||||
{
|
||||
// This should only be set in tests.
|
||||
if (MockedValue != null)
|
||||
{
|
||||
return MockedValue.Value;
|
||||
}
|
||||
|
||||
// The `Stopwatch` class tries to do almost the same thing we're doing! Unfortunately
|
||||
// we only want to use its ability to provide us a monotonic timer. At least on recent
|
||||
// Windows this is guaranteed to be monotonic due to the usage of the Windows Performance
|
||||
// timers under the hood. See
|
||||
// https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps
|
||||
// Note that this function will return "ticks", not milliseconds, so we need to convert that.
|
||||
long ticks = Stopwatch.GetTimestamp();
|
||||
|
||||
if (unit == TimeUnit.Nanosecond)
|
||||
{
|
||||
return GetTimeFromTicks(ticks, NanosecondsInSeconds);
|
||||
} else if (unit == TimeUnit.Millisecond)
|
||||
{
|
||||
return GetTimeFromTicks(ticks, MillisecondsInSeconds);
|
||||
}
|
||||
|
||||
// We were passed an unsupported unit.
|
||||
throw new ArgumentOutOfRangeException("Unexpected time unit for GetTimestamp");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<!--
|
||||
This file is automatically imported by NuGet into a user's project
|
||||
when it targets a single framework, or in classic (pre 2017) csproj projects.
|
||||
-->
|
||||
<Project TreatAsLocalProperty="TaskFolder;TaskAssembly">
|
||||
<PropertyGroup>
|
||||
<TaskAssembly>$(MSBuildThisFileDirectory)..\lib\netstandard2.0\Glean.dll</TaskAssembly>
|
||||
</PropertyGroup>
|
||||
|
||||
<UsingTask TaskName="GleanTasks.GleanParser" AssemblyFile="$(TaskAssembly)" />
|
||||
</Project>
|
|
@ -1,7 +0,0 @@
|
|||
<!--
|
||||
This file is automatically imported by NuGet into a user's project
|
||||
when it targets multiple frameworks.
|
||||
-->
|
||||
<Project>
|
||||
<Import Project="..\build\Mozilla.Telemetry.Glean.props" />
|
||||
</Project>
|
|
@ -1,230 +0,0 @@
|
|||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Mozilla.Glean.Tests
|
||||
{
|
||||
public class DispatchersTests
|
||||
{
|
||||
[Fact]
|
||||
public void LaunchAPIDoesNotRunOnMainThread()
|
||||
{
|
||||
var mainThread = Thread.CurrentThread;
|
||||
var threadCanary = false;
|
||||
|
||||
Dispatchers.TestingMode = false;
|
||||
Dispatchers.QueueInitialTasks = false;
|
||||
|
||||
var task = Dispatchers.LaunchAPI(() =>
|
||||
{
|
||||
Assert.NotSame(mainThread, Thread.CurrentThread);
|
||||
threadCanary = true;
|
||||
});
|
||||
|
||||
// Wait for the task to complete
|
||||
task.Wait();
|
||||
|
||||
Dispatchers.TestingMode = true;
|
||||
Assert.True(threadCanary);
|
||||
Assert.Same(mainThread, Thread.CurrentThread);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestTaskQueueing()
|
||||
{
|
||||
var threadCanary = 0;
|
||||
|
||||
Dispatchers.TestingMode = true;
|
||||
Dispatchers.QueueInitialTasks = true;
|
||||
|
||||
List<Task> tasks = new List<Task>();
|
||||
|
||||
// Add 3 tasks to the queue, each one incrementing threadCanary to
|
||||
// indicate the task has executed.
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
tasks.Add(Dispatchers.LaunchAPI(() =>
|
||||
{
|
||||
threadCanary += 1;
|
||||
}));
|
||||
}
|
||||
|
||||
Assert.Equal(3, Dispatchers.preInitActionQueue.Count);
|
||||
Assert.Equal(0, threadCanary);
|
||||
|
||||
Dispatchers.FlushQueuedInitialTasks();
|
||||
|
||||
Assert.Equal(3, threadCanary);
|
||||
Assert.Empty(Dispatchers.preInitActionQueue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueuedTasksAreFlushedOffTheMainThread()
|
||||
{
|
||||
var mainThread = Thread.CurrentThread;
|
||||
long threadCanary = 0;
|
||||
|
||||
Dispatchers.TestingMode = false;
|
||||
Dispatchers.QueueInitialTasks = true;
|
||||
|
||||
// Add 3 tasks to queue each one setting threadCanary to true
|
||||
// to indicate if any task has ran.
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
Dispatchers.LaunchAPI(() =>
|
||||
{
|
||||
Assert.NotSame(mainThread, Thread.CurrentThread);
|
||||
Interlocked.Add(ref threadCanary, 1);
|
||||
});
|
||||
}
|
||||
|
||||
Assert.Equal(3, Dispatchers.preInitActionQueue.Count);
|
||||
Assert.Equal(0, Interlocked.Read(ref threadCanary));
|
||||
|
||||
// Trigger execution to ensure tasks have fired
|
||||
Dispatchers.FlushQueuedInitialTasks();
|
||||
|
||||
Assert.Equal(3, Interlocked.Read(ref threadCanary));
|
||||
Assert.Empty(Dispatchers.preInitActionQueue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestQueuedTasksAreExecutedInOrderAsync()
|
||||
{
|
||||
Dispatchers.TestingMode = false;
|
||||
Dispatchers.QueueInitialTasks = true;
|
||||
|
||||
var flushTasks = false;
|
||||
List<int> orderedList = new List<int>();
|
||||
|
||||
var task1 = Task.Factory.StartNew(() =>
|
||||
{
|
||||
while (!flushTasks) { Thread.Yield(); }
|
||||
Dispatchers.FlushQueuedInitialTasks();
|
||||
});
|
||||
|
||||
List<Task> tasks = new List<Task>();
|
||||
var counter = 0;
|
||||
var task2 = Task.Factory.StartNew(() =>
|
||||
{
|
||||
for (int num = 0; num < 99; num++)
|
||||
{
|
||||
if (num == 50)
|
||||
{
|
||||
flushTasks = true;
|
||||
}
|
||||
|
||||
// Need to "capture" the value here with a local variable.
|
||||
// Just using `num` was insufficient as it appeared to be
|
||||
// not getting captured by the lambda below.
|
||||
var value = num;
|
||||
var t = Dispatchers.LaunchAPI(() =>
|
||||
{
|
||||
orderedList.Add(value);
|
||||
counter += 1;
|
||||
});
|
||||
|
||||
if (t != null) { tasks.Add(t); }
|
||||
}
|
||||
});
|
||||
|
||||
task1.Wait();
|
||||
task2.Wait(5000);
|
||||
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
|
||||
var counterTimeout = 0;
|
||||
while (counter < 99)
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
|
||||
Assert.True(++counterTimeout < 5000);
|
||||
}
|
||||
|
||||
for (int num = 0; num < 99; num++)
|
||||
{
|
||||
Assert.Equal(num, orderedList[num]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DispatchedTasksThrowingExceptionsAreCorrectlyHandled()
|
||||
{
|
||||
List<Task> tasks = new List<Task>();
|
||||
var mainThread = Thread.CurrentThread;
|
||||
long threadCanary = 0;
|
||||
|
||||
// Ensure tasks are executed asynchronously
|
||||
Dispatchers.TestingMode = false;
|
||||
Dispatchers.QueueInitialTasks = false;
|
||||
|
||||
// Dispatch an initial task that throws an exception
|
||||
tasks.Add(Dispatchers.LaunchAPI(() =>
|
||||
{
|
||||
Assert.NotSame(mainThread, Thread.CurrentThread);
|
||||
throw new Exception("Test exception for DispatchersTest");
|
||||
}));
|
||||
|
||||
// Add 3 tasks to queue each one setting threadCanary to true
|
||||
// to indicate if any task has ran.
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
tasks.Add(Dispatchers.LaunchAPI(() =>
|
||||
{
|
||||
Assert.NotSame(mainThread, Thread.CurrentThread);
|
||||
threadCanary += 1;
|
||||
}));
|
||||
}
|
||||
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
|
||||
Assert.Equal(3, threadCanary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QueuedTasksExceptionsAreCaughtWhenFlushing()
|
||||
{
|
||||
var mainThread = Thread.CurrentThread;
|
||||
long threadCanary = 0;
|
||||
|
||||
Dispatchers.TestingMode = false;
|
||||
Dispatchers.QueueInitialTasks = true;
|
||||
|
||||
Assert.Empty(Dispatchers.preInitActionQueue);
|
||||
|
||||
// Dispatch an initial task that throws an exception. Even if this
|
||||
// throws, the dispatcher should catch and keep on executing other
|
||||
// tasks while flushing.
|
||||
Dispatchers.LaunchAPI(() =>
|
||||
{
|
||||
throw new Exception("Test exception for DispatchersTest");
|
||||
});
|
||||
|
||||
// Add 3 tasks to queue each one setting threadCanary to true
|
||||
// to indicate if any task has ran.
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
Dispatchers.LaunchAPI(() =>
|
||||
{
|
||||
Assert.NotSame(mainThread, Thread.CurrentThread);
|
||||
threadCanary += 1;
|
||||
});
|
||||
}
|
||||
|
||||
Assert.Equal(4, Dispatchers.preInitActionQueue.Count);
|
||||
Assert.Equal(0, threadCanary);
|
||||
|
||||
// Trigger execution to ensure tasks have fired
|
||||
Dispatchers.FlushQueuedInitialTasks();
|
||||
|
||||
Assert.Equal(3, threadCanary);
|
||||
Assert.Empty(Dispatchers.preInitActionQueue);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
using static Mozilla.Glean.Glean;
|
||||
|
||||
namespace Mozilla.Glean.Tests
|
||||
{
|
||||
public class GleanTests
|
||||
{
|
||||
public GleanTests()
|
||||
{
|
||||
// Get a random test directory just for this single test.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// In xUnit, the constructor will be called before each test. This
|
||||
// feels like a natural place to initialize / reset Glean.
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendAPing()
|
||||
{
|
||||
// TODO: This test needs a server to verify data are submitted successfully.
|
||||
// We should take `GleanTest.kt` as a reference to implement.
|
||||
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()
|
||||
{
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(maxEvents: 500),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>Mozilla.Glean.Tests</RootNamespace>
|
||||
<!--
|
||||
The following properties were determined by following the solution outlined here:
|
||||
https://github.com/Microsoft/msbuild/issues/539#issuecomment-289930591
|
||||
-->
|
||||
<IsWindows Condition="'$(OS)' == 'Windows_NT'">true</IsWindows>
|
||||
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
|
||||
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
|
||||
<Platforms>AnyCPU;x86</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Glean\Glean.csproj" />
|
||||
|
||||
<!-- Use the xUnit configuration file. -->
|
||||
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
|
||||
<!--
|
||||
Unfortunately, tests won't be able to find the native dlls, even though they
|
||||
live within the runtimes/<rid>/native directory. To make it possible to run
|
||||
tests, manually copy the Glean FFI dll to $(OutDir).
|
||||
-->
|
||||
<Content Condition="$(IsWindows) == true" Include="../../../target/$(Configuration.ToLowerInvariant())/glean_ffi.dll" CopyToOutputDirectory="Always" />
|
||||
<Content Condition="$(IsLinux) == true" Include="../../../target/$(Configuration.ToLowerInvariant())/libglean_ffi.so" CopyToOutputDirectory="Always" />
|
||||
<Content Condition="$(IsOSX) == true" Include="../../../target/$(Configuration.ToLowerInvariant())/libglean_ffi.dylib" CopyToOutputDirectory="Always" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,109 +0,0 @@
|
|||
// 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.IO;
|
||||
using Xunit;
|
||||
using static Mozilla.Glean.Glean;
|
||||
|
||||
namespace Mozilla.Glean.Tests.Metrics
|
||||
{
|
||||
public class BooleanMetricTypeTest
|
||||
{
|
||||
public BooleanMetricTypeTest()
|
||||
{
|
||||
// Get a random test directory just for this single test.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// In xUnit, the constructor will be called before each test. This
|
||||
// feels like a natural place to initialize / reset Glean.
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToStorage()
|
||||
{
|
||||
Private.BooleanMetricType booleanMetric = new Private.BooleanMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "boolean_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
// Record two strings of the same type, with a little delay.
|
||||
booleanMetric.Set(true);
|
||||
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(booleanMetric.TestHasValue());
|
||||
Assert.True(booleanMetric.TestGetValue());
|
||||
|
||||
booleanMetric.Set(true);
|
||||
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(booleanMetric.TestHasValue());
|
||||
Assert.True(booleanMetric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisabledstringsMustNotRecordData()
|
||||
{
|
||||
Private.BooleanMetricType booleanMetric = new Private.BooleanMetricType(
|
||||
category: "telemetry",
|
||||
disabled: true,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "boolean_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
// Attempt to store the string.
|
||||
booleanMetric.Set(true);
|
||||
// Check that nothing was recorded.
|
||||
Assert.False(booleanMetric.TestHasValue(), "Booleans must not be recorded if they are disabled");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetValueThrows()
|
||||
{
|
||||
Private.BooleanMetricType booleanMetric = new Private.BooleanMetricType(
|
||||
category: "telemetry",
|
||||
disabled: true,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "boolean_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
Assert.Throws<NullReferenceException>(() => booleanMetric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToSecondaryPings()
|
||||
{
|
||||
Private.BooleanMetricType booleanMetric = new Private.BooleanMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "boolean_metric",
|
||||
sendInPings: new string[] { "store1", "store2" }
|
||||
);
|
||||
|
||||
// Record two strings of the same type, with a little delay.
|
||||
booleanMetric.Set(true);
|
||||
|
||||
// Check that data was properly recorded in the second ping.
|
||||
Assert.True(booleanMetric.TestHasValue("store2"));
|
||||
Assert.True(booleanMetric.TestGetValue("store2"));
|
||||
|
||||
booleanMetric.Set(false);
|
||||
// Check that data was properly recorded in the second ping.
|
||||
Assert.True(booleanMetric.TestHasValue("store2"));
|
||||
Assert.False(booleanMetric.TestGetValue("store2"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
// 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 Mozilla.Glean.Testing;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
using static Mozilla.Glean.Glean;
|
||||
|
||||
namespace Mozilla.Glean.Tests.Metrics
|
||||
{
|
||||
public class CounterMetricTypeTest
|
||||
{
|
||||
public CounterMetricTypeTest()
|
||||
{
|
||||
// Get a random test directory just for this single test.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// In xUnit, the constructor will be called before each test. This
|
||||
// feels like a natural place to initialize / reset Glean.
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToStorage()
|
||||
{
|
||||
Private.CounterMetricType counterMetric = new Private.CounterMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "counter_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
Assert.False(counterMetric.TestHasValue());
|
||||
|
||||
// Add to the counter a couple of times with a little delay. The first call will check
|
||||
// calling add() without parameters to test increment by 1.
|
||||
counterMetric.Add();
|
||||
|
||||
// Check that the count was incremented and properly recorded.
|
||||
Assert.True(counterMetric.TestHasValue());
|
||||
Assert.Equal(1, counterMetric.TestGetValue());
|
||||
|
||||
counterMetric.Add(10);
|
||||
// Check that count was incremented and properly recorded. This second call will check
|
||||
// calling add() with 10 to test increment by other amount
|
||||
Assert.True(counterMetric.TestHasValue());
|
||||
Assert.Equal(11, counterMetric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisabledCountersMustNotRecordData()
|
||||
{
|
||||
Private.CounterMetricType counterMetric = new Private.CounterMetricType(
|
||||
category: "telemetry",
|
||||
disabled: true,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "counter_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
// Attempt to store the counter.
|
||||
counterMetric.Add();
|
||||
// Check that nothing was recorded.
|
||||
Assert.False(counterMetric.TestHasValue(), "Counters must not be recorded if they are disabled");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetValueThrows()
|
||||
{
|
||||
Private.CounterMetricType counterMetric = new Private.CounterMetricType(
|
||||
category: "telemetry",
|
||||
disabled: true,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "counter_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
Assert.Throws<NullReferenceException>(() => counterMetric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToSecondaryPings()
|
||||
{
|
||||
Private.CounterMetricType counterMetric = new Private.CounterMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "counter_metric",
|
||||
sendInPings: new string[] { "store1", "store2" }
|
||||
);
|
||||
|
||||
// Add to the counter a couple of times with a little delay. The first call will check
|
||||
// calling add() without parameters to test increment by 1.
|
||||
counterMetric.Add();
|
||||
|
||||
// Check that the count was incremented and properly recorded for the second ping.
|
||||
Assert.True(counterMetric.TestHasValue("store2"));
|
||||
Assert.Equal(1, counterMetric.TestGetValue("store2"));
|
||||
|
||||
counterMetric.Add(10);
|
||||
// Check that count was incremented and properly recorded for the second ping.
|
||||
// This second call will check calling add() with 10 to test increment by other amount
|
||||
Assert.True(counterMetric.TestHasValue("store2"));
|
||||
Assert.Equal(11, counterMetric.TestGetValue("store2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NegativeValuesAreNotCounted()
|
||||
{
|
||||
Private.CounterMetricType counterMetric = new Private.CounterMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "counter_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
// Increment to 1 (initial value)
|
||||
counterMetric.Add();
|
||||
|
||||
// Check that the count was incremented
|
||||
Assert.True(counterMetric.TestHasValue("store1"));
|
||||
Assert.Equal(1, counterMetric.TestGetValue("store1"));
|
||||
|
||||
counterMetric.Add(-10);
|
||||
// Check that count was NOT incremented.
|
||||
Assert.True(counterMetric.TestHasValue("store1"));
|
||||
Assert.Equal(1, counterMetric.TestGetValue("store1"));
|
||||
Assert.Equal(1, counterMetric.TestGetNumRecordedErrors(ErrorType.InvalidValue));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
// 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 Mozilla.Glean.Private;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
using static Mozilla.Glean.Glean;
|
||||
|
||||
namespace Mozilla.Glean.Tests.Metrics
|
||||
{
|
||||
public class DatetimeMetricTypeTest
|
||||
{
|
||||
public DatetimeMetricTypeTest()
|
||||
{
|
||||
// Get a random test directory just for this single test.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// In xUnit, the constructor will be called before each test. This
|
||||
// feels like a natural place to initialize / reset Glean.
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToStorage()
|
||||
{
|
||||
var datetimeMetric = new Private.DatetimeMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "datetime_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
string displayName = "(UTC-08:00) Pacific Time (US & Canada)";
|
||||
string standardName = "America/Los_Angeles";
|
||||
var offset = new TimeSpan(-08, 00, 00);
|
||||
var timeZone = TimeZoneInfo.CreateCustomTimeZone(standardName, offset, displayName, standardName);
|
||||
var value = new DateTimeOffset(2004, 11, 9, 8, 3, 29, timeZone.BaseUtcOffset);
|
||||
datetimeMetric.Set(value);
|
||||
Assert.True(datetimeMetric.TestHasValue());
|
||||
Assert.Equal("2004-11-09T08:03-08:00", datetimeMetric.TestGetValueAsString());
|
||||
|
||||
displayName = "(UTC+00:00) Dublin, Edinburgh, Lisbon, London";
|
||||
standardName = "GMT+0";
|
||||
offset = new TimeSpan(00, 00, 00);
|
||||
timeZone = TimeZoneInfo.CreateCustomTimeZone(standardName, offset, displayName, standardName);
|
||||
var value2 = new DateTimeOffset(1993, 1, 23, 9, 5, 43, timeZone.BaseUtcOffset);
|
||||
datetimeMetric.Set(value2);
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(datetimeMetric.TestHasValue());
|
||||
Assert.Equal("1993-01-23T09:05+00:00", datetimeMetric.TestGetValueAsString());
|
||||
|
||||
// A date prior to the UNIX epoch
|
||||
displayName = "(UTC-12:00) International Date Line West";
|
||||
standardName = "GMT-12";
|
||||
offset = new TimeSpan(-12, 00, 00);
|
||||
timeZone = TimeZoneInfo.CreateCustomTimeZone(standardName, offset, displayName, standardName);
|
||||
var value3 = new DateTimeOffset(1969, 7, 20, 20, 17, 3, timeZone.BaseUtcOffset);
|
||||
datetimeMetric.Set(value3);
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(datetimeMetric.TestHasValue());
|
||||
Assert.Equal("1969-07-20T20:17-12:00", datetimeMetric.TestGetValueAsString());
|
||||
|
||||
// A date following 2038 (the extent of signed 32-bits after UNIX epoch)
|
||||
// This fails on some workers on Taskcluster. 32-bit platforms, perhaps?
|
||||
|
||||
// val value4 = Calendar.getInstance()
|
||||
// value4.set(2039, 7, 20, 20, 17, 3)
|
||||
// datetimeMetric.set(value4)
|
||||
// // Check that data was properly recorded.
|
||||
// assertTrue(datetimeMetric.testHasValue())
|
||||
// assertEquals("2039-08-20T20:17:03-04:00", datetimeMetric.testGetValueAsString())
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisabledDatetimesMustNotRecordData()
|
||||
{
|
||||
var datetimeMetric = new Private.DatetimeMetricType(
|
||||
category: "telemetry",
|
||||
disabled: true,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "datetime_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
// Attempt to store the datetime.
|
||||
datetimeMetric.Set();
|
||||
Assert.False(datetimeMetric.TestHasValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SettingDateAndReadingResultsInTheSame()
|
||||
{
|
||||
var datetimeMetric = new Private.DatetimeMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "datetime_metric",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Millisecond
|
||||
);
|
||||
|
||||
var unixStart = new DateTime(1970, 1, 1, 0, 0, 0, 0);
|
||||
var now = new DateTime(DateTime.Now.Ticks);
|
||||
datetimeMetric.Set(now);
|
||||
Assert.Equal(Math.Floor((now.ToUniversalTime() - unixStart).TotalSeconds),
|
||||
datetimeMetric.TestGetValue().ToUnixTimeSeconds());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToSecondaryPings()
|
||||
{
|
||||
// Define a 'datetimeMetric' datetime metric, which will be stored in "store1" and "store2"
|
||||
var datetimeMetric = new Private.DatetimeMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "datetime_metric",
|
||||
sendInPings: new string[] { "store1", "store2" },
|
||||
timeUnit: TimeUnit.Second
|
||||
);
|
||||
|
||||
const string displayName = "(UTC-06:00) Central Time (US & Canada)";
|
||||
const string standardName = "Central Standard Time";
|
||||
var offset = new TimeSpan(-06, 00, 00);
|
||||
var timeZone = TimeZoneInfo.CreateCustomTimeZone(standardName, offset, displayName, standardName);
|
||||
var value = new DateTimeOffset(2010, 11, 29, 18, 3, 35, timeZone.BaseUtcOffset);
|
||||
datetimeMetric.Set(value);
|
||||
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(datetimeMetric.TestHasValue("store1"));
|
||||
Assert.Equal(value, datetimeMetric.TestGetValue("store1"));
|
||||
Assert.True(datetimeMetric.TestHasValue("store2"));
|
||||
Assert.Equal(value, datetimeMetric.TestGetValue("store2"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,381 +0,0 @@
|
|||
// 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 Mozilla.Glean.Testing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using Xunit;
|
||||
using static Mozilla.Glean.Glean;
|
||||
|
||||
namespace Mozilla.Glean.Tests.Metrics
|
||||
{
|
||||
enum clickKeys : int
|
||||
{
|
||||
objectId,
|
||||
other
|
||||
}
|
||||
|
||||
enum testNameKeys : int
|
||||
{
|
||||
testName
|
||||
}
|
||||
|
||||
enum SomeExtraKeys : int
|
||||
{
|
||||
SomeExtra
|
||||
}
|
||||
|
||||
public class EventMetricTypeTest
|
||||
{
|
||||
private readonly MockUploader mockUploader = new MockUploader();
|
||||
|
||||
// Define a convenience function to manually reset Glean.
|
||||
private void ResetGlean(string dataDir)
|
||||
{
|
||||
GleanInstance.TestDestroyGleanHandle();
|
||||
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(httpClient: mockUploader),
|
||||
dataDir: dataDir
|
||||
);
|
||||
}
|
||||
|
||||
public EventMetricTypeTest()
|
||||
{
|
||||
// Get a random test directory just for this single test.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// Remove all the pending pings from the queue.
|
||||
mockUploader.Clear();
|
||||
|
||||
ResetGlean(tempDataDir);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToStorage()
|
||||
{
|
||||
Private.EventMetricType<clickKeys> click = new Private.EventMetricType<clickKeys>(
|
||||
category: "ui",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Ping,
|
||||
name: "click",
|
||||
sendInPings: new string[] { "store1" },
|
||||
allowedExtraKeys: new string[] { "object_id", "other" }
|
||||
);
|
||||
|
||||
// Record two events of the same type, with a little delay.
|
||||
click.Record(extra: new Dictionary<clickKeys, string> {
|
||||
{ clickKeys.objectId, "buttonA" },
|
||||
{ clickKeys.other, "foo" }
|
||||
});
|
||||
|
||||
Thread.Sleep(37);
|
||||
|
||||
click.Record(extra: new Dictionary<clickKeys, string> {
|
||||
{ clickKeys.objectId, "buttonB" },
|
||||
{ clickKeys.other, "bar" }
|
||||
});
|
||||
|
||||
// Check that data was properly recorded.
|
||||
var snapshot = click.TestGetValue();
|
||||
Assert.True(click.TestHasValue());
|
||||
Assert.Equal(2, snapshot.Length);
|
||||
|
||||
var firstEvent = snapshot.First(e => (e.Extra != null) && e.Extra["object_id"] == "buttonA");
|
||||
Assert.Equal("ui", firstEvent.Category);
|
||||
Assert.Equal("click", firstEvent.Name);
|
||||
Assert.Equal("foo", firstEvent.Extra["other"]);
|
||||
|
||||
var secondEvent = snapshot.First(e => (e.Extra != null) && e.Extra["object_id"] == "buttonB");
|
||||
Assert.Equal("ui", secondEvent.Category);
|
||||
Assert.Equal("click", secondEvent.Name);
|
||||
Assert.Equal("bar", secondEvent.Extra["other"]);
|
||||
|
||||
Assert.True(firstEvent.Timestamp < secondEvent.Timestamp, "The sequence of the events must be preserved");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TheAPIRecordsToItsStorageEngineWhenCategoryIsEmpty()
|
||||
{
|
||||
Private.EventMetricType<clickKeys> click = new Private.EventMetricType<clickKeys>(
|
||||
category: "",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Ping,
|
||||
name: "click",
|
||||
sendInPings: new string[] { "store1" },
|
||||
allowedExtraKeys: new string[] { "object_id" }
|
||||
);
|
||||
|
||||
// Record two events of the same type, with a little delay.
|
||||
click.Record(extra: new Dictionary<clickKeys, string> {
|
||||
{ clickKeys.objectId, "buttonA" }
|
||||
});
|
||||
|
||||
Thread.Sleep(37);
|
||||
|
||||
click.Record(extra: new Dictionary<clickKeys, string> {
|
||||
{ clickKeys.objectId, "buttonB" }
|
||||
});
|
||||
|
||||
// Check that data was properly recorded.
|
||||
var snapshot = click.TestGetValue();
|
||||
Assert.True(click.TestHasValue());
|
||||
Assert.Equal(2, snapshot.Length);
|
||||
|
||||
var firstEvent = snapshot.First(e => (e.Extra != null) && e.Extra["object_id"] == "buttonA");
|
||||
Assert.Equal("click", firstEvent.Name);
|
||||
|
||||
var secondEvent = snapshot.First(e => (e.Extra != null) && e.Extra["object_id"] == "buttonB");
|
||||
Assert.Equal("click", secondEvent.Name);
|
||||
|
||||
Assert.True(firstEvent.Timestamp < secondEvent.Timestamp, "The sequence of the events must be preserved");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisabledEventsMustNotRecordData()
|
||||
{
|
||||
Private.EventMetricType<clickKeys> click = new Private.EventMetricType<clickKeys>(
|
||||
category: "ui",
|
||||
disabled: true,
|
||||
lifetime: Private.Lifetime.Ping,
|
||||
name: "click",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
// Attempt to store the event.
|
||||
click.Record();
|
||||
|
||||
// Check that nothing was recorded.
|
||||
Assert.False(click.TestHasValue(), "Events must not be recorded if they are disabled");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetValueThrows()
|
||||
{
|
||||
Private.EventMetricType<clickKeys> testEvent = new Private.EventMetricType<clickKeys>(
|
||||
category: "ui",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Ping,
|
||||
name: "testEvent",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
Assert.Throws<NullReferenceException>(() => testEvent.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TheAPIRecordsToSecondaryPings()
|
||||
{
|
||||
Private.EventMetricType<clickKeys> click = new Private.EventMetricType<clickKeys>(
|
||||
category: "ui",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Ping,
|
||||
name: "click",
|
||||
sendInPings: new string[] { "store1", "store2" },
|
||||
allowedExtraKeys: new string[] { "object_id" }
|
||||
);
|
||||
|
||||
// Record two events of the same type, with a little delay.
|
||||
click.Record(extra: new Dictionary<clickKeys, string> {
|
||||
{ clickKeys.objectId, "buttonA" }
|
||||
});
|
||||
|
||||
Thread.Sleep(37);
|
||||
|
||||
|
||||
click.Record(extra: new Dictionary<clickKeys, string> {
|
||||
{ clickKeys.objectId, "buttonB" }
|
||||
});
|
||||
|
||||
// Check that data was properly recorded in the second ping.
|
||||
var snapshot = click.TestGetValue("store2");
|
||||
Assert.True(click.TestHasValue("store2"));
|
||||
Assert.Equal(2, snapshot.Length);
|
||||
|
||||
var firstEvent = snapshot.First(e => (e.Extra != null) && e.Extra["object_id"] == "buttonA");
|
||||
Assert.Equal("ui", firstEvent.Category);
|
||||
Assert.Equal("click", firstEvent.Name);
|
||||
|
||||
var secondEvent = snapshot.First(e => (e.Extra != null) && e.Extra["object_id"] == "buttonB");
|
||||
Assert.Equal("ui", secondEvent.Category);
|
||||
Assert.Equal("click", secondEvent.Name);
|
||||
|
||||
Assert.True(firstEvent.Timestamp < secondEvent.Timestamp, "The sequence of the events must be preserved");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EventsShouldNotRecordWhenUploadIsDisabled()
|
||||
{
|
||||
Private.EventMetricType<testNameKeys> eventMetric = new Private.EventMetricType<testNameKeys>(
|
||||
category: "ui",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Ping,
|
||||
name: "event_metric",
|
||||
sendInPings: new string[] { "store1" },
|
||||
allowedExtraKeys: new string[] { "test_name" }
|
||||
);
|
||||
|
||||
GleanInstance.SetUploadEnabled(true);
|
||||
eventMetric.Record(extra: new Dictionary<testNameKeys, string> {
|
||||
{ testNameKeys.testName, "event1" }
|
||||
});
|
||||
var snapshot1 = eventMetric.TestGetValue();
|
||||
Assert.Single(snapshot1);
|
||||
GleanInstance.SetUploadEnabled(false);
|
||||
eventMetric.Record(extra: new Dictionary<testNameKeys, string> {
|
||||
{ testNameKeys.testName, "event2" }
|
||||
});
|
||||
|
||||
try {
|
||||
eventMetric.TestGetValue();
|
||||
Assert.True(false, "Expected events to be empty");
|
||||
} catch (NullReferenceException) {
|
||||
}
|
||||
GleanInstance.SetUploadEnabled(true);
|
||||
eventMetric.Record(extra: new Dictionary<testNameKeys, string> {
|
||||
{ testNameKeys.testName, "event3" }
|
||||
});
|
||||
var snapshot3 = eventMetric.TestGetValue();
|
||||
Assert.Single(snapshot3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FlushQueuedEventsOnStartup()
|
||||
{
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// Re-initialize, we need to point this to the data directory we know of.
|
||||
ResetGlean(tempDataDir);
|
||||
|
||||
Private.EventMetricType<SomeExtraKeys> eventMetric = new Private.EventMetricType<SomeExtraKeys>(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Ping,
|
||||
name: "test_event",
|
||||
sendInPings: new string[] { "events" },
|
||||
allowedExtraKeys: new string[] { "someExtra" }
|
||||
);
|
||||
|
||||
eventMetric.Record(extra: new Dictionary<SomeExtraKeys, string> {
|
||||
{ SomeExtraKeys.SomeExtra, "bar" }
|
||||
});
|
||||
Assert.Single(eventMetric.TestGetValue());
|
||||
|
||||
// Start a new Glean instance to trigger the sending of "stale" events
|
||||
ResetGlean(tempDataDir);
|
||||
|
||||
MockUploader.UploadRequest request = mockUploader.GetPendingUpload();
|
||||
Assert.Equal("events", request.docType);
|
||||
Assert.Contains("/submit/org-mozilla-csharp-tests/events/", request.url);
|
||||
|
||||
// Check the content of the events ping.
|
||||
JsonDocument data = JsonDocument.Parse(request.payload);
|
||||
JsonElement root = data.RootElement;
|
||||
|
||||
// TODO: Check the ping schema.
|
||||
// checkPingSchema(data);
|
||||
|
||||
JsonElement eventsProperty;
|
||||
Assert.True(root.TryGetProperty("events", out eventsProperty));
|
||||
Assert.Equal(1, eventsProperty.GetArrayLength());
|
||||
Assert.Equal("startup", root.GetProperty("ping_info").GetProperty("reason").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FlushQueuedEventsOnStartupAndCorrectlyHandlePreInitEvents()
|
||||
{
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// Re-initialize, we need to point this to the data directory we know of.
|
||||
ResetGlean(tempDataDir);
|
||||
|
||||
Private.EventMetricType<SomeExtraKeys> eventMetric = new Private.EventMetricType<SomeExtraKeys>(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Ping,
|
||||
name: "test_event",
|
||||
sendInPings: new string[] { "events" },
|
||||
allowedExtraKeys: new string[] { "someExtra" }
|
||||
);
|
||||
|
||||
eventMetric.Record(extra: new Dictionary<SomeExtraKeys, string> {
|
||||
{ SomeExtraKeys.SomeExtra, "run1" }
|
||||
});
|
||||
Assert.Single(eventMetric.TestGetValue());
|
||||
|
||||
Dispatchers.QueueInitialTasks = true;
|
||||
eventMetric.Record(extra: new Dictionary<SomeExtraKeys, string> {
|
||||
{ SomeExtraKeys.SomeExtra, "pre-init" }
|
||||
});
|
||||
|
||||
ResetGlean(tempDataDir);
|
||||
|
||||
eventMetric.Record(extra: new Dictionary<SomeExtraKeys, string> {
|
||||
{ SomeExtraKeys.SomeExtra, "post-init" }
|
||||
});
|
||||
|
||||
MockUploader.UploadRequest request = mockUploader.GetPendingUpload();
|
||||
Assert.Equal("events", request.docType);
|
||||
|
||||
// Check the content of the events ping.
|
||||
JsonDocument data = JsonDocument.Parse(request.payload);
|
||||
JsonElement root = data.RootElement;
|
||||
|
||||
// This event comes from disk from the prior "run"
|
||||
Assert.Equal("startup", root.GetProperty("ping_info").GetProperty("reason").GetString());
|
||||
|
||||
JsonElement eventsProperty;
|
||||
Assert.True(root.TryGetProperty("events", out eventsProperty));
|
||||
Assert.Equal(1, eventsProperty.GetArrayLength());
|
||||
Assert.Equal("run1",
|
||||
eventsProperty.EnumerateArray().ElementAt(0).GetProperty("extra").GetProperty("someExtra").GetString()
|
||||
);
|
||||
|
||||
GleanInstance.SubmitPingByName("events", "background");
|
||||
|
||||
request = mockUploader.GetPendingUpload();
|
||||
Assert.Equal("events", request.docType);
|
||||
data = JsonDocument.Parse(request.payload);
|
||||
root = data.RootElement;
|
||||
|
||||
// This event comes from the pre-initialization event
|
||||
Assert.Equal("background", root.GetProperty("ping_info").GetProperty("reason").GetString());
|
||||
Assert.True(root.TryGetProperty("events", out eventsProperty));
|
||||
Assert.Equal(2, eventsProperty.GetArrayLength());
|
||||
Assert.Equal("pre-init",
|
||||
eventsProperty.EnumerateArray().ElementAt(0).GetProperty("extra").GetProperty("someExtra").GetString()
|
||||
);
|
||||
Assert.Equal("post-init",
|
||||
eventsProperty.EnumerateArray().ElementAt(1).GetProperty("extra").GetProperty("someExtra").GetString()
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LongExtraValuesRecordAnError()
|
||||
{
|
||||
Private.EventMetricType<clickKeys> click = new Private.EventMetricType<clickKeys>(
|
||||
category: "ui",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Ping,
|
||||
name: "click",
|
||||
sendInPings: new string[] { "store1" },
|
||||
allowedExtraKeys: new string[] { "object_id", "other" }
|
||||
);
|
||||
|
||||
string longString = new string('a', 110);
|
||||
|
||||
click.Record(extra: new Dictionary<clickKeys, string> {
|
||||
{ clickKeys.objectId, longString }
|
||||
});
|
||||
|
||||
Assert.Equal(1, click.TestGetNumRecordedErrors(ErrorType.InvalidOverflow));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
// 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 Mozilla.Glean.Testing;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
using static Mozilla.Glean.Glean;
|
||||
|
||||
namespace Mozilla.Glean.Tests.Metrics
|
||||
{
|
||||
public sealed class JweMetricTypeTest
|
||||
{
|
||||
private string header = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ";
|
||||
private string key = "OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg";
|
||||
private string initVector = "48V1_ALb6US04U3b";
|
||||
private string cipherText = "5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A";
|
||||
private string authTag = "XFBoMYUZodetZdvTiFvSkQ";
|
||||
private string jwe = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ";
|
||||
private string minimumJwe = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ...5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.";
|
||||
|
||||
public JweMetricTypeTest()
|
||||
{
|
||||
// Get a random test directory just for this single test.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// In xUnit, the constructor will be called before each test. This
|
||||
// feels like a natural place to initialize / reset Glean.
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToStorage()
|
||||
{
|
||||
Private.JweMetricType jweMetric = new Private.JweMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "jwe_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
// Record two JWEs of the same type, with a little delay.
|
||||
jweMetric.Set(this.header, this.key, this.initVector, this.cipherText, this.authTag);
|
||||
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(jweMetric.TestHasValue());
|
||||
Assert.Equal(this.jwe, jweMetric.testGetCompactRepresentation());
|
||||
|
||||
jweMetric.Set(this.header, "", "", this.cipherText, "");
|
||||
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(jweMetric.TestHasValue());
|
||||
Assert.Equal(this.minimumJwe, jweMetric.testGetCompactRepresentation());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisabledJwesMustNotRecordData()
|
||||
{
|
||||
Private.JweMetricType jweMetric = new Private.JweMetricType(
|
||||
category: "telemetry",
|
||||
disabled: true,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "jwe_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
// Attempt to store the string.
|
||||
jweMetric.Set(this.header, this.key, this.initVector, this.cipherText, this.authTag);
|
||||
|
||||
// Check that nothing was recorded.
|
||||
Assert.False(jweMetric.TestHasValue(), "JWEs must not be recorded if they are disabled");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetValueThrows()
|
||||
{
|
||||
Private.JweMetricType jweMetric = new Private.JweMetricType(
|
||||
category: "telemetry",
|
||||
disabled: true,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "jwe_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
Assert.Throws<NullReferenceException>(() => jweMetric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestJweGetValueReturnsCorrectJweDataRepresentation()
|
||||
{
|
||||
Private.JweMetricType jweMetric = new Private.JweMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "jwe_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
jweMetric.Set(this.header, this.key, this.initVector, this.cipherText, this.authTag);
|
||||
|
||||
Private.JweData data = jweMetric.TestGetValue();
|
||||
Assert.Equal(data.Header, this.header);
|
||||
Assert.Equal(data.Key, this.key);
|
||||
Assert.Equal(data.InitVector, this.initVector);
|
||||
Assert.Equal(data.CipherText, this.cipherText);
|
||||
Assert.Equal(data.AuthTag, this.authTag);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToSecondaryPings()
|
||||
{
|
||||
Private.JweMetricType jweMetric = new Private.JweMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "jwe_metric",
|
||||
sendInPings: new string[] { "store1", "store2" }
|
||||
);
|
||||
|
||||
// Record two JWEs of the same type, with a little delay.
|
||||
jweMetric.Set(this.header, this.key, this.initVector, this.cipherText, this.authTag);
|
||||
|
||||
// Check that data was properly recorded in the second ping.
|
||||
Assert.True(jweMetric.TestHasValue("store2"));
|
||||
Assert.Equal(this.jwe, jweMetric.testGetCompactRepresentation("store2"));
|
||||
|
||||
jweMetric.Set(this.header, "", "", this.cipherText, "");
|
||||
// Check that data was properly recorded in the second ping.
|
||||
Assert.True(jweMetric.TestHasValue("store2"));
|
||||
Assert.Equal(this.minimumJwe, jweMetric.testGetCompactRepresentation("store2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SettingALongStringRecordsAnError()
|
||||
{
|
||||
Private.JweMetricType jweMetric = new Private.JweMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "jwe_metric",
|
||||
sendInPings: new string[] { "store1", "store2" }
|
||||
);
|
||||
|
||||
// Too long elements should yield a InvalidOverflow error
|
||||
jweMetric.Set(new string('X', 1025), this.key, this.initVector, this.cipherText, this.authTag);
|
||||
Assert.Equal(1, jweMetric.TestGetNumRecordedErrors(ErrorType.InvalidOverflow));
|
||||
|
||||
// Invalid compact string representation yield a InvalidValue error
|
||||
jweMetric.setWithCompactRepresentation("");
|
||||
Assert.Equal(1, jweMetric.TestGetNumRecordedErrors(ErrorType.InvalidValue));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,362 +0,0 @@
|
|||
// 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 Mozilla.Glean.Private;
|
||||
using Mozilla.Glean.Testing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
using static Mozilla.Glean.Glean;
|
||||
|
||||
namespace Mozilla.Glean.Tests.Metrics
|
||||
{
|
||||
public class LabeledMetricTypeTest
|
||||
{
|
||||
// Get a random test directory just for this single test.
|
||||
string TempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
public LabeledMetricTypeTest()
|
||||
{
|
||||
// In xUnit, the constructor will be called before each test. This
|
||||
// feels like a natural place to initialize / reset Glean.
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: TempDataDir
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestLabeledCounterType()
|
||||
{
|
||||
CounterMetricType counterMetric = new CounterMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_counter_metric",
|
||||
sendInPings: new string[] { "metrics" }
|
||||
);
|
||||
|
||||
var labeledCounterMetric = new LabeledMetricType<CounterMetricType>(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_counter_metric",
|
||||
sendInPings: new string[] { "metrics" },
|
||||
submetric: counterMetric
|
||||
);
|
||||
|
||||
labeledCounterMetric["label1"].Add(1);
|
||||
labeledCounterMetric["label2"].Add(2);
|
||||
|
||||
// Record a regular non-labeled counter. This isn't normally
|
||||
// possible with the generated code because the subMetric is private,
|
||||
// but it's useful to test here that it works.
|
||||
counterMetric.Add(3);
|
||||
|
||||
Assert.True(labeledCounterMetric["label1"].TestHasValue());
|
||||
Assert.Equal(1, labeledCounterMetric["label1"].TestGetValue());
|
||||
|
||||
Assert.True(labeledCounterMetric["label2"].TestHasValue());
|
||||
Assert.Equal(2, labeledCounterMetric["label2"].TestGetValue());
|
||||
|
||||
Assert.True(counterMetric.TestHasValue());
|
||||
Assert.Equal(3, counterMetric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestOtherLabelWithPredefinedLabels()
|
||||
{
|
||||
CounterMetricType counterMetric = new CounterMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_counter_metric",
|
||||
sendInPings: new string[] { "metrics" }
|
||||
);
|
||||
|
||||
var labeledCounterMetric = new LabeledMetricType<CounterMetricType>(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_counter_metric",
|
||||
sendInPings: new string[] { "metrics" },
|
||||
submetric: counterMetric,
|
||||
labels: new HashSet<string>() { "foo", "bar", "baz" }
|
||||
);
|
||||
|
||||
labeledCounterMetric["foo"].Add(1);
|
||||
labeledCounterMetric["foo"].Add(2);
|
||||
labeledCounterMetric["bar"].Add(1);
|
||||
labeledCounterMetric["not_there"].Add(1);
|
||||
labeledCounterMetric["also_not_there"].Add(1);
|
||||
labeledCounterMetric["not_me"].Add(1);
|
||||
|
||||
Assert.Equal(3, labeledCounterMetric["foo"].TestGetValue());
|
||||
Assert.Equal(1, labeledCounterMetric["bar"].TestGetValue());
|
||||
Assert.False(labeledCounterMetric["baz"].TestHasValue());
|
||||
// The rest all lands in the __other__ bucket
|
||||
Assert.Equal(3, labeledCounterMetric["not_there"].TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestOtherLabelWithoutPredefinedLabels()
|
||||
{
|
||||
CounterMetricType counterMetric = new CounterMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_counter_metric",
|
||||
sendInPings: new string[] { "metrics" }
|
||||
);
|
||||
|
||||
var labeledCounterMetric = new LabeledMetricType<CounterMetricType>(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_counter_metric",
|
||||
sendInPings: new string[] { "metrics" },
|
||||
submetric: counterMetric
|
||||
);
|
||||
|
||||
for (int i = 0; i <= 20; i++)
|
||||
{
|
||||
labeledCounterMetric[$"label_{i}"].Add(1);
|
||||
}
|
||||
// Go back and record in one of the real labels again
|
||||
labeledCounterMetric["label_0"].Add(1);
|
||||
|
||||
Assert.Equal(2, labeledCounterMetric["label_0"].TestGetValue());
|
||||
for (int i = 1; i <= 15; i++)
|
||||
{
|
||||
Assert.Equal(1, labeledCounterMetric[$"label_{i}"].TestGetValue());
|
||||
}
|
||||
Assert.Equal(5, labeledCounterMetric["__other__"].TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestOtherLabelWithoutPredefinedLabelsBeforeGleanInits()
|
||||
{
|
||||
CounterMetricType counterMetric = new CounterMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_counter_metric",
|
||||
sendInPings: new string[] { "metrics" }
|
||||
);
|
||||
|
||||
var labeledCounterMetric = new LabeledMetricType<CounterMetricType>(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_counter_metric",
|
||||
sendInPings: new string[] { "metrics" },
|
||||
submetric: counterMetric
|
||||
);
|
||||
|
||||
// Make sure Glean isn't initialized, and turn task queueing on
|
||||
GleanInstance.TestDestroyGleanHandle();
|
||||
Dispatchers.QueueInitialTasks = true;
|
||||
|
||||
for (int i = 0; i <= 20; i++)
|
||||
{
|
||||
labeledCounterMetric[$"label_{i}"].Add(1);
|
||||
}
|
||||
// Go back and record in one of the real labels again
|
||||
labeledCounterMetric["label_0"].Add(1);
|
||||
|
||||
// Initialize glean
|
||||
GleanInstance.Initialize(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: TempDataDir
|
||||
);
|
||||
|
||||
Assert.Equal(2, labeledCounterMetric["label_0"].TestGetValue());
|
||||
for (int i = 1; i <= 15; i++)
|
||||
{
|
||||
Assert.Equal(1, labeledCounterMetric[$"label_{i}"].TestGetValue());
|
||||
}
|
||||
Assert.Equal(5, labeledCounterMetric["__other__"].TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureInvalidLabelsOnLabeledCounterGoToOther()
|
||||
{
|
||||
CounterMetricType counterMetric = new CounterMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_counter_metric",
|
||||
sendInPings: new string[] { "metrics" }
|
||||
);
|
||||
|
||||
var labeledCounterMetric = new LabeledMetricType<CounterMetricType>(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_counter_metric",
|
||||
sendInPings: new string[] { "metrics" },
|
||||
submetric: counterMetric
|
||||
);
|
||||
|
||||
labeledCounterMetric["notSnakeCase"].Add(1);
|
||||
labeledCounterMetric[""].Add(1);
|
||||
labeledCounterMetric["with/slash"].Add(1);
|
||||
labeledCounterMetric["this_string_has_more_than_thirty_characters"].Add(1);
|
||||
|
||||
Assert.Equal(
|
||||
4,
|
||||
labeledCounterMetric.TestGetNumRecordedErrors(
|
||||
ErrorType.InvalidLabel
|
||||
)
|
||||
);
|
||||
Assert.Equal(
|
||||
4,
|
||||
labeledCounterMetric["__other__"].TestGetValue()
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureInvalidLabelsOnLabeledBooleanGoToOther()
|
||||
{
|
||||
BooleanMetricType booleanMetric = new BooleanMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_boolean_metric",
|
||||
sendInPings: new string[] { "metrics" }
|
||||
);
|
||||
|
||||
var labeledBooleanMetric = new LabeledMetricType<BooleanMetricType>(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_boolean_metric",
|
||||
sendInPings: new string[] { "metrics" },
|
||||
submetric: booleanMetric
|
||||
);
|
||||
|
||||
labeledBooleanMetric["notSnakeCase"].Set(true);
|
||||
labeledBooleanMetric[""].Set(true);
|
||||
labeledBooleanMetric["with/slash"].Set(true);
|
||||
labeledBooleanMetric["this_string_has_more_than_thirty_characters"].Set(true);
|
||||
|
||||
Assert.Equal(
|
||||
4,
|
||||
labeledBooleanMetric.TestGetNumRecordedErrors(
|
||||
ErrorType.InvalidLabel
|
||||
)
|
||||
);
|
||||
Assert.True(
|
||||
labeledBooleanMetric["__other__"].TestGetValue()
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureInvalidLabelsOnLabeledStringGoToOther()
|
||||
{
|
||||
StringMetricType stringMetric = new StringMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_string_metric",
|
||||
sendInPings: new string[] { "metrics" }
|
||||
);
|
||||
|
||||
var labeledStringMetric = new LabeledMetricType<StringMetricType>(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_string_metric",
|
||||
sendInPings: new string[] { "metrics" },
|
||||
submetric: stringMetric
|
||||
);
|
||||
|
||||
labeledStringMetric["notSnakeCase"].Set("foo");
|
||||
labeledStringMetric[""].Set("foo");
|
||||
labeledStringMetric["with/slash"].Set("foo");
|
||||
labeledStringMetric["this_string_has_more_than_thirty_characters"].Set("foo");
|
||||
|
||||
Assert.Equal(
|
||||
4,
|
||||
labeledStringMetric.TestGetNumRecordedErrors(
|
||||
ErrorType.InvalidLabel
|
||||
)
|
||||
);
|
||||
Assert.Equal(
|
||||
"foo",
|
||||
labeledStringMetric["__other__"].TestGetValue()
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestLabeledStringMetricType()
|
||||
{
|
||||
StringMetricType stringMetric = new StringMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_string_metric",
|
||||
sendInPings: new string[] { "metrics" }
|
||||
);
|
||||
|
||||
var labeledStringMetric = new LabeledMetricType<StringMetricType>(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_string_metric",
|
||||
sendInPings: new string[] { "metrics" },
|
||||
submetric: stringMetric
|
||||
);
|
||||
|
||||
labeledStringMetric["label1"].Set("foo");
|
||||
labeledStringMetric["label2"].Set("bar");
|
||||
|
||||
Assert.Equal("foo", labeledStringMetric["label1"].TestGetValue());
|
||||
Assert.Equal("bar", labeledStringMetric["label2"].TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestLabeledBooleanMetricType()
|
||||
{
|
||||
BooleanMetricType booleanMetric = new BooleanMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_boolean_metric",
|
||||
sendInPings: new string[] { "metrics" }
|
||||
);
|
||||
|
||||
var labeledBooleanMetric = new LabeledMetricType<BooleanMetricType>(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Application,
|
||||
name: "labeled_boolean_metric",
|
||||
sendInPings: new string[] { "metrics" },
|
||||
submetric: booleanMetric
|
||||
);
|
||||
|
||||
labeledBooleanMetric["label1"].Set(false);
|
||||
labeledBooleanMetric["label2"].Set(true);
|
||||
|
||||
Assert.False(labeledBooleanMetric["label1"].TestGetValue());
|
||||
Assert.True(labeledBooleanMetric["label2"].TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestLabeledEventsAreAnException()
|
||||
{
|
||||
// TODO: Placeholder. Implement in bug 1648422 by converting the related Kotlin test.
|
||||
}
|
||||
|
||||
// SKIPPED `test recording to static labels by label index` (Kotlin) because labels by label
|
||||
// index are not supported in C# (only useful on Android for project EXTRACT).
|
||||
}
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
// 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 Mozilla.Glean.Private;
|
||||
using Mozilla.Glean.Testing;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
using static Mozilla.Glean.Glean;
|
||||
|
||||
namespace Mozilla.Glean.Tests.Metrics
|
||||
{
|
||||
public class MemoryDistributionMetricTypeTest
|
||||
{
|
||||
public MemoryDistributionMetricTypeTest()
|
||||
{
|
||||
// Get a random test directory just for this single test.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// In xUnit, the constructor will be called before each test. This
|
||||
// feels like a natural place to initialize / reset Glean.
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToStorage()
|
||||
{
|
||||
MemoryDistributionMetricType metric = new MemoryDistributionMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "memory_distribution",
|
||||
sendInPings: new string[] { "store1" },
|
||||
memoryUnit: MemoryUnit.Kilobyte
|
||||
);
|
||||
|
||||
// Accumulate a few values
|
||||
for (ulong i = 1; i <= 3; i++)
|
||||
{
|
||||
metric.Accumulate(i);
|
||||
}
|
||||
|
||||
long kb = 1024;
|
||||
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(metric.TestHasValue());
|
||||
DistributionData snapshot = metric.TestGetValue();
|
||||
// Check the sum
|
||||
Assert.Equal<long>(1L * kb + 2L * kb + 3L * kb, snapshot.Sum);
|
||||
// Check that the 1L fell into the first value bucket
|
||||
Assert.Equal(1L, snapshot.Values[1023]);
|
||||
// Check that the 2L fell into the second value bucket
|
||||
Assert.Equal(1L, snapshot.Values[2047]);
|
||||
// Check that the 3L fell into the third value bucket
|
||||
Assert.Equal(1L, snapshot.Values[3024]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValuesAreTruncatedTo1TB()
|
||||
{
|
||||
MemoryDistributionMetricType metric = new MemoryDistributionMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "memory_distribution",
|
||||
sendInPings: new string[] { "store1" },
|
||||
memoryUnit: MemoryUnit.Gigabyte
|
||||
);
|
||||
|
||||
metric.Accumulate(2048L);
|
||||
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(metric.TestHasValue());
|
||||
var snapshot = metric.TestGetValue();
|
||||
// Check the sum
|
||||
Assert.Equal(1L << 40, snapshot.Sum);
|
||||
// Check that the 1L fell into 1TB bucket
|
||||
Assert.Equal(1L, snapshot.Values[(1L << 40) - 1]);
|
||||
// Check that an error was recorded
|
||||
Assert.Equal(1, metric.TestGetNumRecordedErrors(ErrorType.InvalidValue));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisabledMemoryDistributionsMustNotRecordData()
|
||||
{
|
||||
MemoryDistributionMetricType metric = new MemoryDistributionMetricType(
|
||||
disabled: true,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "memory_distribution",
|
||||
sendInPings: new string[] { "store1" },
|
||||
memoryUnit: MemoryUnit.Gigabyte
|
||||
);
|
||||
|
||||
metric.Accumulate(1L);
|
||||
|
||||
// Check that nothing was recorded.
|
||||
Assert.False(metric.TestHasValue(), "MemoryDistributions without a lifetime should not record data.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetValueThrows()
|
||||
{
|
||||
MemoryDistributionMetricType metric = new MemoryDistributionMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "memory_distribution",
|
||||
sendInPings: new string[] { "store1" },
|
||||
memoryUnit: MemoryUnit.Gigabyte
|
||||
);
|
||||
Assert.Throws<NullReferenceException>(() => metric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToSecondaryPings()
|
||||
{
|
||||
MemoryDistributionMetricType metric = new MemoryDistributionMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "memory_distribution",
|
||||
sendInPings: new string[] { "store1", "store2", "store3" },
|
||||
memoryUnit: MemoryUnit.Kilobyte
|
||||
);
|
||||
|
||||
// Accumulate a few values
|
||||
for (ulong i = 1; i <= 3; i++)
|
||||
{
|
||||
metric.Accumulate(i);
|
||||
}
|
||||
|
||||
// Check that data was properly recorded in the second ping.
|
||||
Assert.True(metric.TestHasValue("store2"));
|
||||
var snapshot = metric.TestGetValue("store2");
|
||||
// Check the sum
|
||||
Assert.Equal(6144L, snapshot.Sum);
|
||||
// Check that the 1L fell into the first bucket
|
||||
Assert.Equal(1L, snapshot.Values[1023]);
|
||||
// Check that the 2L fell into the second bucket
|
||||
Assert.Equal(1L, snapshot.Values[2047]);
|
||||
// Check that the 3L fell into the third bucket
|
||||
Assert.Equal(1L, snapshot.Values[3024]);
|
||||
|
||||
// Check that data was properly recorded in the third ping.
|
||||
Assert.True(metric.TestHasValue("store3"));
|
||||
var snapshot2 = metric.TestGetValue("store3");
|
||||
// Check the sum
|
||||
Assert.Equal(6144L, snapshot2.Sum);
|
||||
// Check that the 1L fell into the first bucket
|
||||
Assert.Equal(1L, snapshot2.Values[1023]);
|
||||
// Check that the 2L fell into the second bucket
|
||||
Assert.Equal(1L, snapshot2.Values[2047]);
|
||||
// Check that the 3L fell into the third bucket
|
||||
Assert.Equal(1L, snapshot2.Values[3024]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
// 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.IO;
|
||||
using Mozilla.Glean.Net;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace Mozilla.Glean.Tests.Metrics
|
||||
{
|
||||
/// <summary>
|
||||
/// A mock uploader class to support testing uploaded pings.
|
||||
/// </summary>
|
||||
internal class MockUploader : IPingUploader
|
||||
{
|
||||
public struct UploadRequest
|
||||
{
|
||||
public string url;
|
||||
public string docType;
|
||||
public string payload;
|
||||
public (string, string)[] headers;
|
||||
};
|
||||
|
||||
private readonly BlockingCollection<UploadRequest> requestQueue = new BlockingCollection<UploadRequest>();
|
||||
|
||||
UploadResult IPingUploader.Upload(string url, byte[] data, (string, string)[] headers)
|
||||
{
|
||||
requestQueue.Add(new UploadRequest {
|
||||
url = url,
|
||||
docType = url.Split("/")[5],
|
||||
payload = GetPlainBody(data, headers),
|
||||
headers = headers,
|
||||
});
|
||||
|
||||
return new HttpResponse(200);
|
||||
}
|
||||
|
||||
public UploadRequest GetPendingUpload()
|
||||
{
|
||||
return requestQueue.Take();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
while (requestQueue.TryTake(out _)) { };
|
||||
}
|
||||
|
||||
public int GetUploadRequestCount()
|
||||
{
|
||||
return requestQueue.Count;
|
||||
}
|
||||
|
||||
private string DecompressGZIP(byte[] data)
|
||||
{
|
||||
// Note: we need two memory streams because we don't know the size
|
||||
// of the uncompressed buffer
|
||||
using MemoryStream outputMemoryStream = new MemoryStream();
|
||||
using MemoryStream inputMemoryStream = new MemoryStream(data);
|
||||
using (GZipStream gzipStream = new GZipStream(inputMemoryStream, CompressionMode.Decompress))
|
||||
{
|
||||
gzipStream.CopyTo(outputMemoryStream);
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(outputMemoryStream.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to get the body of a request as a String.
|
||||
/// The UTF8 representation of the request body will be returned.
|
||||
/// If the request body is gzipped, it will be decompressed first.
|
||||
/// </summary>
|
||||
/// <param name="data">The input byte payload.</param>
|
||||
/// <param name="headers">The headers that come with the payload.</param>
|
||||
/// <returns>a `string` containing the body of the request.</returns>
|
||||
private string GetPlainBody(byte[] data, (string, string)[] headers)
|
||||
{
|
||||
bool isGzip = false;
|
||||
|
||||
foreach ((string, string) h in headers)
|
||||
{
|
||||
if (h.Item1 == "Content-Encoding" && h.Item2 == "gzip")
|
||||
{
|
||||
isGzip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We don't have gzip, so just decode the UTF8.
|
||||
if (!isGzip)
|
||||
{
|
||||
return Encoding.UTF8.GetString(data);
|
||||
}
|
||||
|
||||
// We have GZIP, decompress and then decode.
|
||||
return DecompressGZIP(data);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,208 +0,0 @@
|
|||
// 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.IO;
|
||||
using static Mozilla.Glean.Glean;
|
||||
using Xunit;
|
||||
using Mozilla.Glean.Private;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Mozilla.Glean.Tests.Metrics
|
||||
{
|
||||
public class PingTypeTest
|
||||
{
|
||||
private readonly MockUploader mockUploader = new MockUploader();
|
||||
|
||||
public PingTypeTest()
|
||||
{
|
||||
// Get a random test directory just for this single test.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// Remove all the pending pings from the queue.
|
||||
mockUploader.Clear();
|
||||
|
||||
// In xUnit, the constructor will be called before each test. This
|
||||
// feels like a natural place to initialize / reset Glean.
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(httpClient: mockUploader),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendCustomPings()
|
||||
{
|
||||
PingType<NoReasonCodes> customPing = new PingType<NoReasonCodes>(
|
||||
name: "custom",
|
||||
includeClientId: true,
|
||||
sendIfEmpty: false,
|
||||
reasonCodes: null
|
||||
);
|
||||
|
||||
BooleanMetricType sampleMetric = new BooleanMetricType(
|
||||
disabled: false,
|
||||
category: "test",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "boolean_metric",
|
||||
sendInPings: new string[] { "custom" }
|
||||
);
|
||||
|
||||
sampleMetric.Set(true);
|
||||
Assert.True(sampleMetric.TestHasValue());
|
||||
|
||||
customPing.Submit();
|
||||
|
||||
MockUploader.UploadRequest request = mockUploader.GetPendingUpload();
|
||||
Assert.Equal("custom", request.docType);
|
||||
|
||||
// Check that we have a non-null client id.
|
||||
JsonDocument jsonPayload = JsonDocument.Parse(request.payload);
|
||||
JsonElement root = jsonPayload.RootElement;
|
||||
Assert.NotNull(root.GetProperty("client_info").GetProperty("client_id").GetString());
|
||||
|
||||
// TODO: Check the ping schema.
|
||||
// checkPingSchema(pingJson)
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendCustomPingsWithSnakeCase()
|
||||
{
|
||||
PingType<NoReasonCodes> customPing = new PingType<NoReasonCodes>(
|
||||
name: "custom_ping",
|
||||
includeClientId: true,
|
||||
sendIfEmpty: false,
|
||||
reasonCodes: null
|
||||
);
|
||||
|
||||
BooleanMetricType sampleMetric = new BooleanMetricType(
|
||||
disabled: false,
|
||||
category: "test",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "boolean_metric",
|
||||
sendInPings: new string[] { "custom_ping" }
|
||||
);
|
||||
|
||||
sampleMetric.Set(true);
|
||||
Assert.True(sampleMetric.TestHasValue());
|
||||
|
||||
customPing.Submit();
|
||||
|
||||
MockUploader.UploadRequest request = mockUploader.GetPendingUpload();
|
||||
Assert.Equal("custom_ping", request.docType);
|
||||
|
||||
// Check that we have a non-null client id.
|
||||
JsonDocument jsonPayload = JsonDocument.Parse(request.payload);
|
||||
JsonElement root = jsonPayload.RootElement;
|
||||
Assert.NotNull(root.GetProperty("client_info").GetProperty("client_id").GetString());
|
||||
|
||||
// TODO: Check the ping schema.
|
||||
// checkPingSchema(pingJson)
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendCustomPingsWithKebabCase()
|
||||
{
|
||||
PingType<NoReasonCodes> customPing = new PingType<NoReasonCodes>(
|
||||
name: "custom-ping",
|
||||
includeClientId: true,
|
||||
sendIfEmpty: false,
|
||||
reasonCodes: null
|
||||
);
|
||||
|
||||
BooleanMetricType sampleMetric = new BooleanMetricType(
|
||||
disabled: false,
|
||||
category: "test",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "boolean_metric",
|
||||
sendInPings: new string[] { "custom-ping" }
|
||||
);
|
||||
|
||||
sampleMetric.Set(true);
|
||||
Assert.True(sampleMetric.TestHasValue());
|
||||
|
||||
customPing.Submit();
|
||||
|
||||
MockUploader.UploadRequest request = mockUploader.GetPendingUpload();
|
||||
Assert.Equal("custom-ping", request.docType);
|
||||
|
||||
// Check that we have a non-null client id.
|
||||
JsonDocument jsonPayload = JsonDocument.Parse(request.payload);
|
||||
JsonElement root = jsonPayload.RootElement;
|
||||
Assert.NotNull(root.GetProperty("client_info").GetProperty("client_id").GetString());
|
||||
|
||||
// TODO: Check the ping schema.
|
||||
// checkPingSchema(pingJson)
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendCustomPingsWithoutClientId()
|
||||
{
|
||||
PingType<NoReasonCodes> customPing = new PingType<NoReasonCodes>(
|
||||
name: "custom",
|
||||
includeClientId: false,
|
||||
sendIfEmpty: false,
|
||||
reasonCodes: null
|
||||
);
|
||||
|
||||
BooleanMetricType sampleMetric = new BooleanMetricType(
|
||||
disabled: false,
|
||||
category: "test",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "boolean_metric",
|
||||
sendInPings: new string[] { "custom" }
|
||||
);
|
||||
|
||||
sampleMetric.Set(true);
|
||||
Assert.True(sampleMetric.TestHasValue());
|
||||
|
||||
customPing.Submit();
|
||||
|
||||
MockUploader.UploadRequest request = mockUploader.GetPendingUpload();
|
||||
Assert.Equal("custom", request.docType);
|
||||
|
||||
// Check that we have a non-null client id.
|
||||
JsonDocument jsonPayload = JsonDocument.Parse(request.payload);
|
||||
JsonElement root = jsonPayload.RootElement;
|
||||
Assert.False(root.GetProperty("client_info").TryGetProperty("client_id", out _));
|
||||
|
||||
// TODO: Check the ping schema.
|
||||
// checkPingSchema(pingJson)
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendCustomPingsWithAnUnknownNameNoOp()
|
||||
{
|
||||
const string unknownPingName = "unknown";
|
||||
|
||||
Assert.False(GleanInstance.TestHasPingType(unknownPingName));
|
||||
|
||||
BooleanMetricType sampleMetric = new BooleanMetricType(
|
||||
disabled: false,
|
||||
category: "test",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "boolean_metric",
|
||||
sendInPings: new string[] { unknownPingName }
|
||||
);
|
||||
|
||||
sampleMetric.Set(true);
|
||||
Assert.True(sampleMetric.TestHasValue());
|
||||
|
||||
GleanInstance.SubmitPingByName(unknownPingName);
|
||||
|
||||
// We don't expect any ping to be sent.
|
||||
Assert.Equal(0, mockUploader.GetUploadRequestCount());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegistryShouldContainBuildInPings()
|
||||
{
|
||||
Assert.True(GleanInstance.TestHasPingType("metrics"));
|
||||
Assert.True(GleanInstance.TestHasPingType("events"));
|
||||
Assert.True(GleanInstance.TestHasPingType("baseline"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
// 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 Mozilla.Glean.Testing;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
using static Mozilla.Glean.Glean;
|
||||
|
||||
namespace Mozilla.Glean.Tests.Metrics
|
||||
{
|
||||
public class QuantityMetricTypeTest
|
||||
{
|
||||
public QuantityMetricTypeTest()
|
||||
{
|
||||
// Get a random test directory just for this single test.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// In xUnit, the constructor will be called before each test. This
|
||||
// feels like a natural place to initialize / reset Glean.
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToStorage()
|
||||
{
|
||||
Private.QuantityMetricType quantityMetric = new Private.QuantityMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "quantity_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
Assert.False(quantityMetric.TestHasValue());
|
||||
|
||||
quantityMetric.Set(1);
|
||||
|
||||
// Check that the metric was properly recorded.
|
||||
Assert.True(quantityMetric.TestHasValue());
|
||||
Assert.Equal(1, quantityMetric.TestGetValue());
|
||||
|
||||
quantityMetric.Set(10);
|
||||
// Check that the metric was properly overwritten.
|
||||
Assert.True(quantityMetric.TestHasValue());
|
||||
Assert.Equal(10, quantityMetric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisabledCountersMustNotRecordData()
|
||||
{
|
||||
Private.QuantityMetricType quantityMetric = new Private.QuantityMetricType(
|
||||
category: "telemetry",
|
||||
disabled: true,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "quantity_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
// Attempt to store the quantity.
|
||||
quantityMetric.Set(1);
|
||||
// Check that nothing was recorded.
|
||||
Assert.False(quantityMetric.TestHasValue(), "Quantities must not be recorded if they are disabled");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetValueThrows()
|
||||
{
|
||||
Private.QuantityMetricType quantityMetric = new Private.QuantityMetricType(
|
||||
category: "telemetry",
|
||||
disabled: true,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "quantity_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
Assert.Throws<NullReferenceException>(() => quantityMetric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToSecondaryPings()
|
||||
{
|
||||
Private.QuantityMetricType quantityMetric = new Private.QuantityMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "quantity_metric",
|
||||
sendInPings: new string[] { "store1", "store2" }
|
||||
);
|
||||
|
||||
quantityMetric.Set(1);
|
||||
|
||||
// Check that the metric was properly recorded for the secondary ping.
|
||||
Assert.True(quantityMetric.TestHasValue("store2"));
|
||||
Assert.Equal(1, quantityMetric.TestGetValue("store2"));
|
||||
|
||||
quantityMetric.Set(10);
|
||||
// Check that the metric was properly overwritten for the secondary ping.
|
||||
Assert.True(quantityMetric.TestHasValue("store2"));
|
||||
Assert.Equal(10, quantityMetric.TestGetValue("store2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NegativeValuesAreNotRecorded()
|
||||
{
|
||||
Private.QuantityMetricType quantityMetric = new Private.QuantityMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "quantity_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
quantityMetric.Set(-10);
|
||||
// Check that quantity was NOT recorded.
|
||||
Assert.False(quantityMetric.TestHasValue("store1"));
|
||||
Assert.Equal(1, quantityMetric.TestGetNumRecordedErrors(ErrorType.InvalidValue));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,200 +0,0 @@
|
|||
// 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 Mozilla.Glean.Testing;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
using static Mozilla.Glean.Glean;
|
||||
|
||||
namespace Mozilla.Glean.Tests.Metrics
|
||||
{
|
||||
public class StringListMetricTypeTest
|
||||
{
|
||||
public StringListMetricTypeTest()
|
||||
{
|
||||
// Get a random test directory just for this single test.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// In xUnit, the constructor will be called before each test. This
|
||||
// feels like a natural place to initialize / reset Glean.
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToStorageByAddingThenSetting()
|
||||
{
|
||||
// Define a 'stringListMetric' string list metric, which will be stored in "store1".
|
||||
var stringListMetric = new Private.StringListMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "string_list_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
// Record two lists using add and set.
|
||||
stringListMetric.Add("value1");
|
||||
stringListMetric.Add("value2");
|
||||
stringListMetric.Add("value3");
|
||||
|
||||
// Check that data was properly recorded.
|
||||
var snapshot = stringListMetric.TestGetValue();
|
||||
Assert.Equal(3, snapshot.Length);
|
||||
Assert.True(stringListMetric.TestHasValue());
|
||||
Assert.Equal("value1", snapshot[0]);
|
||||
Assert.Equal("value2", snapshot[1]);
|
||||
Assert.Equal("value3", snapshot[2]);
|
||||
|
||||
// Use Set() to see that the first list is replaced by the new list.
|
||||
stringListMetric.Set(new string[]{ "other1", "other2", "other3"});
|
||||
// Check that data was properly recorded.
|
||||
var snapshot2 = stringListMetric.TestGetValue();
|
||||
Assert.Equal(3, snapshot2.Length);
|
||||
Assert.True(stringListMetric.TestHasValue());
|
||||
Assert.Equal("other1", snapshot2[0]);
|
||||
Assert.Equal("other2", snapshot2[1]);
|
||||
Assert.Equal("other3", snapshot2[2]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToStorageBySettingThenAdding()
|
||||
{
|
||||
// Define a 'stringListMetric' string list metric, which will be stored in "store1".
|
||||
var stringListMetric = new Private.StringListMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "string_list_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
// Record two lists using set and add.
|
||||
stringListMetric.Set(new string[] { "value1", "value2", "value3" });
|
||||
|
||||
// Check that data was properly recorded.
|
||||
var snapshot = stringListMetric.TestGetValue();
|
||||
Assert.Equal(3, snapshot.Length);
|
||||
Assert.True(stringListMetric.TestHasValue());
|
||||
Assert.Equal("value1", snapshot[0]);
|
||||
Assert.Equal("value2", snapshot[1]);
|
||||
Assert.Equal("value3", snapshot[2]);
|
||||
|
||||
// Use Add() to see that the list is appended to.
|
||||
stringListMetric.Add("added1");
|
||||
// Check that data was properly recorded.
|
||||
var snapshot2 = stringListMetric.TestGetValue();
|
||||
Assert.Equal(4, snapshot2.Length);
|
||||
Assert.True(stringListMetric.TestHasValue());
|
||||
Assert.Equal("value1", snapshot2[0]);
|
||||
Assert.Equal("value2", snapshot2[1]);
|
||||
Assert.Equal("value3", snapshot2[2]);
|
||||
Assert.Equal("added1", snapshot2[3]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisabledListMustNotRecordData()
|
||||
{
|
||||
// Define a 'stringListMetric' string list metric, which will be stored in "store1".
|
||||
// It's disabled so it should not record anything.
|
||||
var stringListMetric = new Private.StringListMetricType(
|
||||
category: "telemetry",
|
||||
disabled: true,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "string_list_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
// Attempt to store the string list using set.
|
||||
stringListMetric.Set(new string[] { "value1", "value2", "value3" });
|
||||
// Check that nothing was recorded.
|
||||
// StringLists must not be recorded if they are disabled.
|
||||
Assert.False(stringListMetric.TestHasValue());
|
||||
|
||||
// Attempt to store the string list using add.
|
||||
stringListMetric.Add("value4");
|
||||
// Check that nothing was recorded.
|
||||
// StringLists must not be recorded if they are disabled.
|
||||
Assert.False(stringListMetric.TestHasValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetValueThrows()
|
||||
{
|
||||
var stringListMetric = new Private.StringListMetricType(
|
||||
disabled: true,
|
||||
category: "telemetry",
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "string_list_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
Assert.Throws<NullReferenceException>(() => stringListMetric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToSecondaryPings()
|
||||
{
|
||||
// Define a 'stringListMetric' string list metric, which will be stored in "store1".
|
||||
var stringListMetric = new Private.StringListMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "string_list_metric",
|
||||
sendInPings: new string[] { "store1", "store2" }
|
||||
);
|
||||
|
||||
// Record two lists using Add() and Set().
|
||||
stringListMetric.Add("value1");
|
||||
stringListMetric.Add("value2");
|
||||
stringListMetric.Add("value3");
|
||||
|
||||
// Check that data was properly recorded in the second ping.
|
||||
Assert.True(stringListMetric.TestHasValue("store2"));
|
||||
var snapshot = stringListMetric.TestGetValue("store2");
|
||||
Assert.Equal(3, snapshot.Length);
|
||||
Assert.Equal("value1", snapshot[0]);
|
||||
Assert.Equal("value2", snapshot[1]);
|
||||
Assert.Equal("value3", snapshot[2]);
|
||||
|
||||
// Use Set() to see that the first list is replaced by the new list.
|
||||
stringListMetric.Set(new string[] { "other1", "other2", "other3" });
|
||||
// Check that data was properly recorded in the second ping.
|
||||
Assert.True(stringListMetric.TestHasValue("store2"));
|
||||
var snapshot2 = stringListMetric.TestGetValue("store2");
|
||||
Assert.Equal(3, snapshot2.Length);
|
||||
Assert.Equal("other1", snapshot2[0]);
|
||||
Assert.Equal("other2", snapshot2[1]);
|
||||
Assert.Equal("other3", snapshot2[2]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LongStringListsAreTruncated()
|
||||
{
|
||||
// Define a 'stringListMetric' string list metric, which will be stored in "store1".
|
||||
var stringListMetric = new Private.StringListMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "string_list_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
for (int x = 0; x <= 20; x++)
|
||||
{
|
||||
stringListMetric.Add("value" + x);
|
||||
}
|
||||
|
||||
var snapshot = stringListMetric.TestGetValue("store1");
|
||||
Assert.Equal(20, snapshot.Length);
|
||||
|
||||
Assert.Equal(1, stringListMetric.TestGetNumRecordedErrors(ErrorType.InvalidValue));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
// 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 Mozilla.Glean.Testing;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
using static Mozilla.Glean.Glean;
|
||||
|
||||
namespace Mozilla.Glean.Tests.Metrics
|
||||
{
|
||||
public class StringMetricTypeTest
|
||||
{
|
||||
public StringMetricTypeTest()
|
||||
{
|
||||
// Get a random test directory just for this single test.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// In xUnit, the constructor will be called before each test. This
|
||||
// feels like a natural place to initialize / reset Glean.
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToStorage()
|
||||
{
|
||||
Private.StringMetricType stringMetric = new Private.StringMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "string_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
// Record two strings of the same type, with a little delay.
|
||||
stringMetric.Set("value");
|
||||
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(stringMetric.TestHasValue());
|
||||
Assert.Equal("value", stringMetric.TestGetValue());
|
||||
|
||||
stringMetric.Set("overriddenValue");
|
||||
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(stringMetric.TestHasValue());
|
||||
Assert.Equal("overriddenValue", stringMetric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisabledstringsMustNotRecordData()
|
||||
{
|
||||
Private.StringMetricType stringMetric = new Private.StringMetricType(
|
||||
category: "telemetry",
|
||||
disabled: true,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "string_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
// Attempt to store the string.
|
||||
stringMetric.Set("value");
|
||||
// Check that nothing was recorded.
|
||||
Assert.False(stringMetric.TestHasValue(), "Strings must not be recorded if they are disabled");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetValueThrows()
|
||||
{
|
||||
Private.StringMetricType stringMetric = new Private.StringMetricType(
|
||||
category: "telemetry",
|
||||
disabled: true,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "string_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
Assert.Throws<NullReferenceException>(() => stringMetric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToSecondaryPings()
|
||||
{
|
||||
Private.StringMetricType stringMetric = new Private.StringMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "string_metric",
|
||||
sendInPings: new string[] { "store1", "store2" }
|
||||
);
|
||||
|
||||
// Record two strings of the same type, with a little delay.
|
||||
stringMetric.Set("value");
|
||||
|
||||
// Check that data was properly recorded in the second ping.
|
||||
Assert.True(stringMetric.TestHasValue("store2"));
|
||||
Assert.Equal("value", stringMetric.TestGetValue("store2"));
|
||||
|
||||
stringMetric.Set("overriddenValue");
|
||||
// Check that data was properly recorded in the second ping.
|
||||
Assert.True(stringMetric.TestHasValue("store2"));
|
||||
Assert.Equal("overriddenValue", stringMetric.TestGetValue("store2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SettingALongStringRecordsAnError()
|
||||
{
|
||||
Private.StringMetricType stringMetric = new Private.StringMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "string_metric",
|
||||
sendInPings: new string[] { "store1", "store2" }
|
||||
);
|
||||
|
||||
stringMetric.Set(new string('3', 110));
|
||||
|
||||
Assert.Equal(1, stringMetric.TestGetNumRecordedErrors(ErrorType.InvalidOverflow));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,323 +0,0 @@
|
|||
// 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 Mozilla.Glean.Private;
|
||||
using Mozilla.Glean.Testing;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
using static Mozilla.Glean.Glean;
|
||||
|
||||
namespace Mozilla.Glean.Tests.Metrics
|
||||
{
|
||||
public class TimespanMetricTypeTest
|
||||
{
|
||||
public TimespanMetricTypeTest()
|
||||
{
|
||||
// Get a random test directory just for this single test.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// In xUnit, the constructor will be called before each test. This
|
||||
// feels like a natural place to initialize / reset Glean.
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToStorage()
|
||||
{
|
||||
TimespanMetricType metric = new TimespanMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Lifetime.Application,
|
||||
name: "timespan_metric",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Millisecond
|
||||
);
|
||||
|
||||
// Record a timespan.
|
||||
metric.Start();
|
||||
metric.Stop();
|
||||
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(metric.TestHasValue());
|
||||
Assert.True(metric.TestGetValue() >= 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisabledTimespansMustNotRecordData()
|
||||
{
|
||||
TimespanMetricType metric = new TimespanMetricType(
|
||||
category: "telemetry",
|
||||
disabled: true,
|
||||
lifetime: Lifetime.Application,
|
||||
name: "timespan_metric",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Millisecond
|
||||
);
|
||||
|
||||
// Record a timespan.
|
||||
metric.Start();
|
||||
metric.Stop();
|
||||
|
||||
// Let's also call cancel() to make sure it's a no-op.
|
||||
metric.Cancel();
|
||||
|
||||
// Check that data was not recorded.
|
||||
Assert.False(metric.TestHasValue(), "The API should not record a counter if metric is disabled");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APIMustCorrectlyCancel()
|
||||
{
|
||||
TimespanMetricType metric = new TimespanMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Lifetime.Application,
|
||||
name: "timespan_metric",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Millisecond
|
||||
);
|
||||
|
||||
// Record a timespan.
|
||||
metric.Start();
|
||||
metric.Cancel();
|
||||
metric.Stop();
|
||||
|
||||
// Check that data was not recorded.
|
||||
Assert.False(metric.TestHasValue(), "The API should not record an error if the metric is cancelled");
|
||||
Assert.Equal(1, metric.TestGetNumRecordedErrors(ErrorType.InvalidState));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetValueThrows()
|
||||
{
|
||||
TimespanMetricType metric = new TimespanMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Lifetime.Application,
|
||||
name: "timespan_metric",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Millisecond
|
||||
);
|
||||
|
||||
Assert.Throws<NullReferenceException>(() => metric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToSecondaryPings()
|
||||
{
|
||||
TimespanMetricType metric = new TimespanMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Lifetime.Application,
|
||||
name: "timespan_metric",
|
||||
sendInPings: new string[] { "store1", "store2" },
|
||||
timeUnit: TimeUnit.Millisecond
|
||||
);
|
||||
|
||||
metric.Start();
|
||||
metric.Stop();
|
||||
|
||||
// Check that data was properly recorded in the second ping.
|
||||
Assert.True(metric.TestHasValue("store2"));
|
||||
Assert.True(metric.TestGetValue("store2") >= 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecordsAnErrorIfStartedTwice()
|
||||
{
|
||||
TimespanMetricType metric = new TimespanMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Lifetime.Application,
|
||||
name: "timespan_metric",
|
||||
sendInPings: new string[] { "store1", "store2" },
|
||||
timeUnit: TimeUnit.Millisecond
|
||||
);
|
||||
|
||||
// Record a timespan.
|
||||
metric.Start();
|
||||
metric.Start();
|
||||
metric.Stop();
|
||||
|
||||
// Check that data was properly recorded in the second ping.
|
||||
Assert.True(metric.TestHasValue("store2"));
|
||||
Assert.True(metric.TestGetValue("store2") >= 0);
|
||||
Assert.Equal(1, metric.TestGetNumRecordedErrors(ErrorType.InvalidState));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValueUnchangedIfStoppedTwice()
|
||||
{
|
||||
TimespanMetricType metric = new TimespanMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Lifetime.Application,
|
||||
name: "timespan_metric",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Millisecond
|
||||
);
|
||||
|
||||
// Record a timespan.
|
||||
metric.Start();
|
||||
metric.Stop();
|
||||
Assert.True(metric.TestHasValue());
|
||||
ulong value = metric.TestGetValue();
|
||||
|
||||
metric.Stop();
|
||||
|
||||
Assert.Equal(value, metric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestSetRawNanos()
|
||||
{
|
||||
ulong timespanNanos = 6 * 1000000000L;
|
||||
|
||||
TimespanMetricType metric = new TimespanMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "explicit_timespan",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Second
|
||||
);
|
||||
|
||||
metric.SetRawNanos(timespanNanos);
|
||||
Assert.Equal<ulong>(6, metric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestSetRawNanosFollowedByOtherAPI()
|
||||
{
|
||||
ulong timespanNanos = 6 * 1000000000L;
|
||||
|
||||
TimespanMetricType metric = new TimespanMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "explicit_timespan",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Second
|
||||
);
|
||||
|
||||
metric.SetRawNanos(timespanNanos);
|
||||
Assert.Equal<ulong>(6, metric.TestGetValue());
|
||||
|
||||
metric.Start();
|
||||
metric.Stop();
|
||||
ulong value = metric.TestGetValue();
|
||||
Assert.Equal<ulong>(6, value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetRawNanosDoesNotOverwriteValue()
|
||||
{
|
||||
ulong timespanNanos = 6 * 1000000000L;
|
||||
TimespanMetricType metric = new TimespanMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "explicit_timespan_1",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Second
|
||||
);
|
||||
|
||||
metric.Start();
|
||||
metric.Stop();
|
||||
ulong value = metric.TestGetValue();
|
||||
|
||||
metric.SetRawNanos(timespanNanos);
|
||||
|
||||
Assert.Equal<ulong>(value, metric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetRawNanosDoesNothingWhenTimerIsRunning()
|
||||
{
|
||||
ulong timespanNanos = 1000000000L;
|
||||
|
||||
TimespanMetricType metric = new TimespanMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "explicit_timespan",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Second
|
||||
);
|
||||
|
||||
metric.Start();
|
||||
metric.SetRawNanos(timespanNanos);
|
||||
metric.Stop();
|
||||
|
||||
// If setRawNanos worked, (which it's not supposed to in this case), it would
|
||||
// have recorded 1000000000 ns == 1s. Make sure it's not that.
|
||||
Assert.NotEqual<ulong>(1, metric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MeasureFunctionCorrectlyMeasuresValues()
|
||||
{
|
||||
TimespanMetricType metric = new TimespanMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Lifetime.Application,
|
||||
name: "timespan_metric",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Millisecond
|
||||
);
|
||||
|
||||
// Create a function to measure, which also returns a value to test that we properly pass
|
||||
// along the returned value from the measure function
|
||||
static bool TestFunc(bool value) => value;
|
||||
|
||||
// Capture returned value to determine if the function return value matches what is expected
|
||||
// and measure the test function, which should record to the metric
|
||||
bool testValue = metric.Measure(() => TestFunc(true));
|
||||
|
||||
// Make sure the returned valued matches the expected value of "true"
|
||||
Assert.True(testValue, "Test value must match");
|
||||
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(metric.TestHasValue(), "Metric must have a value");
|
||||
Assert.True(metric.TestGetValue() >= 0, "Metric value must be greater than zero");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MeasureFunctionBubblesUpExceptionsAndTimingIsCanceled()
|
||||
{
|
||||
TimespanMetricType metric = new TimespanMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Lifetime.Application,
|
||||
name: "timespan_metric",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Millisecond
|
||||
);
|
||||
|
||||
// Create a function that will throw a NPE
|
||||
static bool TestFunc()
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
// Attempt to measure the function that will throw an exception. The `Measure` function
|
||||
// should allow the exception to bubble up, the timespan measurement is canceled.
|
||||
try {
|
||||
metric.Measure(() => TestFunc());
|
||||
} catch (Exception e) {
|
||||
// Make sure we caught the right kind of exception: NPE
|
||||
Assert.True(e is NullReferenceException, "Exception type must match");
|
||||
} finally {
|
||||
Assert.True(!metric.TestHasValue(), "Metric must not have a value");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,336 +0,0 @@
|
|||
// 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 Mozilla.Glean.Private;
|
||||
using Mozilla.Glean.Testing;
|
||||
using Mozilla.Glean.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
using static Mozilla.Glean.Glean;
|
||||
|
||||
namespace Mozilla.Glean.Tests.Metrics
|
||||
{
|
||||
public class TimingDistributionMetricTypeTest
|
||||
{
|
||||
public TimingDistributionMetricTypeTest()
|
||||
{
|
||||
// Get a random test directory just for this single test.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// Make sure to clear the HighPrecisionTimestamp mocked value between tests.
|
||||
HighPrecisionTimestamp.MockedValue = null;
|
||||
|
||||
// In xUnit, the constructor will be called before each test. This
|
||||
// feels like a natural place to initialize / reset Glean.
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToStorage()
|
||||
{
|
||||
TimingDistributionMetricType metric = new TimingDistributionMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "timing_distribution",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Nanosecond
|
||||
);
|
||||
|
||||
// Accumulate a few values
|
||||
for (ulong i = 1; i <= 3; i++)
|
||||
{
|
||||
HighPrecisionTimestamp.MockedValue = 0;
|
||||
GleanTimerId id = metric.Start();
|
||||
HighPrecisionTimestamp.MockedValue = i;
|
||||
metric.StopAndAccumulate(id);
|
||||
}
|
||||
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(metric.TestHasValue());
|
||||
var snapshot = metric.TestGetValue();
|
||||
// Check the sum
|
||||
Assert.Equal(6L, snapshot.Sum);
|
||||
// Check that the 1L fell into the first bucket (max 1)
|
||||
Assert.Equal(1L, snapshot.Values[1]);
|
||||
// Check that the 2L fell into the second bucket (max 2)
|
||||
Assert.Equal(1L, snapshot.Values[2]);
|
||||
// Check that the 3L fell into the third bucket (max 3)
|
||||
Assert.Equal(1L, snapshot.Values[3]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisableTimingDistributionsMustNotRecordData()
|
||||
{
|
||||
TimingDistributionMetricType metric = new TimingDistributionMetricType(
|
||||
disabled: true,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "timing_distribution",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Nanosecond
|
||||
);
|
||||
|
||||
// Attempt to store the timing distribution using set
|
||||
GleanTimerId id = metric.Start();
|
||||
metric.StopAndAccumulate(id);
|
||||
|
||||
// Check that nothing was recorded.
|
||||
Assert.False(metric.TestHasValue(), "Disabled TimingDistributions should not record data.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetValueThrows()
|
||||
{
|
||||
TimingDistributionMetricType metric = new TimingDistributionMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "timing_distribution",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Nanosecond
|
||||
);
|
||||
Assert.Throws<NullReferenceException>(() => metric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToSecondaryPings()
|
||||
{
|
||||
TimingDistributionMetricType metric = new TimingDistributionMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "timing_distribution",
|
||||
sendInPings: new string[] { "store1", "store2", "store3" },
|
||||
timeUnit: TimeUnit.Nanosecond
|
||||
);
|
||||
|
||||
// Accumulate a few values
|
||||
for (ulong i = 1; i <= 3; i++)
|
||||
{
|
||||
HighPrecisionTimestamp.MockedValue = 0;
|
||||
GleanTimerId id = metric.Start();
|
||||
HighPrecisionTimestamp.MockedValue = i;
|
||||
metric.StopAndAccumulate(id);
|
||||
}
|
||||
|
||||
// Check that data was properly recorded in the second ping.
|
||||
Assert.True(metric.TestHasValue("store2"));
|
||||
var snapshot = metric.TestGetValue("store2");
|
||||
// Check the sum
|
||||
Assert.Equal(6L, snapshot.Sum);
|
||||
// Check that the 1L fell into the first bucket
|
||||
Assert.Equal(1L, snapshot.Values[1]);
|
||||
// Check that the 2L fell into the second bucket
|
||||
Assert.Equal(1L, snapshot.Values[2]);
|
||||
// Check that the 3L fell into the third bucket
|
||||
Assert.Equal(1L, snapshot.Values[3]);
|
||||
|
||||
// Check that data was properly recorded in the third ping.
|
||||
Assert.True(metric.TestHasValue("store3"));
|
||||
var snapshot2 = metric.TestGetValue("store3");
|
||||
// Check the sum
|
||||
Assert.Equal(6L, snapshot2.Sum);
|
||||
// Check that the 1L fell into the first bucket
|
||||
Assert.Equal(1L, snapshot2.Values[1]);
|
||||
// Check that the 2L fell into the second bucket
|
||||
Assert.Equal(1L, snapshot2.Values[2]);
|
||||
// Check that the 3L fell into the third bucket
|
||||
Assert.Equal(1L, snapshot2.Values[3]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartingATimerBeforeInitializationDoesNotCrash()
|
||||
{
|
||||
GleanInstance.TestDestroyGleanHandle();
|
||||
Dispatchers.QueueInitialTasks = true;
|
||||
|
||||
TimingDistributionMetricType metric = new TimingDistributionMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "timing_distribution",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Second
|
||||
);
|
||||
|
||||
GleanTimerId timerId = metric.Start();
|
||||
|
||||
// Start Glean again.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
GleanInstance.Initialize(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
|
||||
metric.StopAndAccumulate(timerId);
|
||||
Assert.True(metric.TestGetValue().Sum >= 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartingAndStoppingATimerBeforeInitializationDoesNotCrash()
|
||||
{
|
||||
GleanInstance.TestDestroyGleanHandle();
|
||||
Dispatchers.QueueInitialTasks = true;
|
||||
|
||||
TimingDistributionMetricType metric = new TimingDistributionMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "timing_distribution",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Second
|
||||
);
|
||||
|
||||
GleanTimerId timerId = metric.Start();
|
||||
metric.StopAndAccumulate(timerId);
|
||||
|
||||
// Start Glean again.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
GleanInstance.Initialize(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
Assert.True(metric.TestGetValue().Sum >= 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StoppingANonExistentTimerRecordsAnError()
|
||||
{
|
||||
TimingDistributionMetricType metric = new TimingDistributionMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "timing_distribution",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Second
|
||||
);
|
||||
|
||||
// Hopefully ulong.MaxValue wasn't used already :)
|
||||
metric.StopAndAccumulate((GleanTimerId)(ulong.MaxValue));
|
||||
Assert.Equal(1, metric.TestGetNumRecordedErrors(ErrorType.InvalidState));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MeasureFunctionCorrectlyMeasuresValues()
|
||||
{
|
||||
TimingDistributionMetricType metric = new TimingDistributionMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "timing_distribution",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Nanosecond
|
||||
);
|
||||
|
||||
// Create a test function to "measure". This works by mocking the timer return
|
||||
// value setting it to return a known value to make it easier to validate.
|
||||
static ulong TestFunc(ulong value) {
|
||||
HighPrecisionTimestamp.MockedValue = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
// Accumulate a few values
|
||||
for (ulong i = 1; i <= 3; i++)
|
||||
{
|
||||
// Measure the test function, capturing the value to verify we correctly return the
|
||||
// value of the underlying function.
|
||||
HighPrecisionTimestamp.MockedValue = 0;
|
||||
var testValue = metric.Measure(() => TestFunc(i));
|
||||
|
||||
// Returned value must match
|
||||
Assert.Equal(i, testValue);
|
||||
}
|
||||
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(metric.TestHasValue());
|
||||
var snapshot = metric.TestGetValue();
|
||||
// Check the sum
|
||||
Assert.Equal(6L, snapshot.Sum);
|
||||
// Check that the 1L fell into the first bucket (max 1)
|
||||
Assert.Equal(1L, snapshot.Values[1]);
|
||||
// Check that the 2L fell into the second bucket (max 2)
|
||||
Assert.Equal(1L, snapshot.Values[2]);
|
||||
// Check that the 3L fell into the third bucket (max 3)
|
||||
Assert.Equal(1L, snapshot.Values[3]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MeasureFunctionBubblesUpExceptionsAndTimingIsCanceled()
|
||||
{
|
||||
TimingDistributionMetricType metric = new TimingDistributionMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: "timing_distribution",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: TimeUnit.Second
|
||||
);
|
||||
|
||||
// Create a test function that throws a NPE
|
||||
static bool TestFunc()
|
||||
{
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
// Attempt to measure the function that will throw an exception. The `measure` function
|
||||
// should allow the exception to bubble up, the timing distribution measurement is canceled.
|
||||
Assert.Throws<NullReferenceException>(() => metric.Measure(() => TestFunc()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureThatTimeUnitControlsTruncation()
|
||||
{
|
||||
ulong maxSampleTime = 1000L * 1000 * 1000 * 60 * 10;
|
||||
|
||||
foreach (TimeUnit unit in new List<TimeUnit>() {
|
||||
TimeUnit.Nanosecond,
|
||||
TimeUnit.Microsecond,
|
||||
TimeUnit.Millisecond
|
||||
})
|
||||
{
|
||||
TimingDistributionMetricType metric = new TimingDistributionMetricType(
|
||||
disabled: false,
|
||||
category: "telemetry",
|
||||
lifetime: Lifetime.Ping,
|
||||
name: $"test_{unit}",
|
||||
sendInPings: new string[] { "store1" },
|
||||
timeUnit: unit
|
||||
);
|
||||
|
||||
foreach (ulong value in new List<ulong>() {
|
||||
1L,
|
||||
1000L,
|
||||
100000L,
|
||||
maxSampleTime,
|
||||
maxSampleTime* 1000L,
|
||||
maxSampleTime* 1000000L
|
||||
})
|
||||
{
|
||||
HighPrecisionTimestamp.MockedValue = 0;
|
||||
var timerId = metric.Start();
|
||||
HighPrecisionTimestamp.MockedValue = value;
|
||||
metric.StopAndAccumulate(timerId);
|
||||
}
|
||||
|
||||
var snapshot = metric.TestGetValue();
|
||||
Assert.True(snapshot.Values.Count < 318);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
// 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 Mozilla.Glean.Testing;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
using static Mozilla.Glean.Glean;
|
||||
|
||||
namespace Mozilla.Glean.Tests.Metrics
|
||||
{
|
||||
public sealed class UuidMetricTypeTest
|
||||
{
|
||||
public UuidMetricTypeTest()
|
||||
{
|
||||
// Get a random test directory just for this single test.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// In xUnit, the constructor will be called before each test. This
|
||||
// feels like a natural place to initialize / reset Glean.
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToStorage()
|
||||
{
|
||||
// Define a 'uuidMetric' uuid metric, which will be stored in "store1"
|
||||
var uuidMetric = new Private.UuidMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "uuid_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
// Check that there is no UUID recorded
|
||||
Assert.False(uuidMetric.TestHasValue());
|
||||
|
||||
// Record two uuids of the same type, with a little delay.
|
||||
var uuid = uuidMetric.GenerateAndSet();
|
||||
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(uuidMetric.TestHasValue());
|
||||
Assert.Equal(uuid, uuidMetric.TestGetValue());
|
||||
|
||||
var uuid2 = System.Guid.NewGuid();
|
||||
uuidMetric.Set(uuid2);
|
||||
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(uuidMetric.TestHasValue());
|
||||
Assert.Equal(uuid2, uuidMetric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisabledstringsMustNotRecordData()
|
||||
{
|
||||
// Define a 'uuidMetric' uuid metric, which will be stored in "store1". It's disabled
|
||||
// so it should not record anything.
|
||||
var uuidMetric = new Private.UuidMetricType(
|
||||
category: "telemetry",
|
||||
disabled: true,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "uuid_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
// Attempt to store the uuid.
|
||||
uuidMetric.GenerateAndSet();
|
||||
|
||||
// Check that nothing was recorded.
|
||||
Assert.False(uuidMetric.TestHasValue(),
|
||||
"Uuids must not be recorded if they are disabled");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetValueThrows()
|
||||
{
|
||||
var uuidMetric = new Private.UuidMetricType(
|
||||
category: "telemetry",
|
||||
disabled: true,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "uuid_metric",
|
||||
sendInPings: new string[] { "store1" }
|
||||
);
|
||||
|
||||
Assert.Throws<NullReferenceException>(() => uuidMetric.TestGetValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void APISavesToSecondaryPings()
|
||||
{
|
||||
// Define a 'uuidMetric' uuid metric, which will be stored in "store1" and "store2"
|
||||
var uuidMetric = new Private.UuidMetricType(
|
||||
category: "telemetry",
|
||||
disabled: false,
|
||||
lifetime: Private.Lifetime.Application,
|
||||
name: "uuid_metric",
|
||||
sendInPings: new string[] { "store1", "store2" }
|
||||
);
|
||||
|
||||
// Record two uuids of the same type, with a little delay.
|
||||
var uuid = uuidMetric.GenerateAndSet();
|
||||
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(uuidMetric.TestHasValue("store2"));
|
||||
Assert.Equal(uuid, uuidMetric.TestGetValue("store2"));
|
||||
|
||||
var uuid2 = System.Guid.NewGuid();
|
||||
uuidMetric.Set(uuid2);
|
||||
|
||||
// Check that data was properly recorded.
|
||||
Assert.True(uuidMetric.TestHasValue("store2"));
|
||||
Assert.Equal(uuid2, uuidMetric.TestGetValue("store2"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
// 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 Mozilla.Glean.Net;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Xunit;
|
||||
using static Mozilla.Glean.Glean;
|
||||
|
||||
namespace Mozilla.Glean.Tests.Net
|
||||
{
|
||||
public class BaseUploaderTest
|
||||
{
|
||||
public BaseUploaderTest()
|
||||
{
|
||||
// Get a random test directory just for this single test.
|
||||
string tempDataDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// In xUnit, the constructor will be called before each test. This
|
||||
// feels like a natural place to initialize / reset Glean.
|
||||
GleanInstance.Reset(
|
||||
applicationId: "org.mozilla.csharp.tests",
|
||||
applicationVersion: "1.0-test",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: tempDataDir
|
||||
);
|
||||
}
|
||||
|
||||
// Define a fake uploader to use in our tests.
|
||||
class FakeUploader : IPingUploader
|
||||
{
|
||||
private Action<string, byte[], (string, string)[]> uploadAction;
|
||||
|
||||
public FakeUploader(Action<string, byte[], (string, string)[]> uploadAction)
|
||||
{
|
||||
this.uploadAction = uploadAction;
|
||||
}
|
||||
|
||||
UploadResult IPingUploader.Upload(string url, byte[] data, (string, string)[] headers)
|
||||
{
|
||||
uploadAction.Invoke(url, data, headers);
|
||||
return new UnrecoverableFailure();
|
||||
}
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void TestUploadMustgetCalledWithTheFullSubmissionPath()
|
||||
{
|
||||
const string testPath = "/some/random/path/not/important";
|
||||
const string testPing = "{ 'ping': 'test' }";
|
||||
(string, string)[] testHeaders = { ("X-Test-Glean", "nothing-to-see-here") };
|
||||
Configuration testConfig = new Configuration(serverEndpoint: "https://example.com");
|
||||
|
||||
BaseUploader testBaseUploader =
|
||||
new BaseUploader(new FakeUploader(new Action<string, byte[], (string, string)[]>((url, data, headers) => {
|
||||
Assert.Equal(testConfig.serverEndpoint + testPath, url);
|
||||
})));
|
||||
|
||||
// Manually trigger upload.
|
||||
testBaseUploader.Upload(testPath, Encoding.UTF8.GetBytes(testPing), testHeaders, testConfig);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGetHeadersFromJSONString()
|
||||
{
|
||||
const string testDocumentId = "c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0";
|
||||
|
||||
// Empty headers string.
|
||||
(string, string)[] headers = BaseUploader.GetHeadersFromJSONString(testDocumentId, "", new Configuration());
|
||||
Assert.Empty(headers);
|
||||
|
||||
// Corrupted headers.
|
||||
headers = BaseUploader.GetHeadersFromJSONString(testDocumentId, "[not-json", new Configuration());
|
||||
Assert.Empty(headers);
|
||||
|
||||
// Good headers.
|
||||
string testHeaders = "{\"X-Test-Glean\": \"nothing-to-see-here\"}";
|
||||
headers = BaseUploader.GetHeadersFromJSONString(testDocumentId, testHeaders, new Configuration());
|
||||
Assert.Equal(new[] { ("X-Test-Glean", "nothing-to-see-here") }, headers);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
// 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 Mozilla.Glean.Net;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Mozilla.Glean.Tests.Net
|
||||
{
|
||||
public class HttpClientUploaderTest
|
||||
{
|
||||
private const string TEST_URL = "http://example.com/some/random/path/not/important";
|
||||
private const string TEST_PING = "{ 'ping': 'test' }";
|
||||
|
||||
private class FakeHttpMessageHandler : HttpMessageHandler
|
||||
{
|
||||
private readonly Action<HttpRequestMessage> requestAction;
|
||||
private readonly HttpStatusCode returnCode;
|
||||
|
||||
public FakeHttpMessageHandler(
|
||||
Action<HttpRequestMessage> requestAction,
|
||||
HttpStatusCode returnCode
|
||||
)
|
||||
{
|
||||
this.requestAction = requestAction;
|
||||
this.returnCode = returnCode;
|
||||
}
|
||||
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
var taskCompletionSource = new TaskCompletionSource<HttpResponseMessage>();
|
||||
|
||||
// Invoke the handler.
|
||||
requestAction.Invoke(request);
|
||||
|
||||
// Generate a result to return.
|
||||
taskCompletionSource.SetResult(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = returnCode,
|
||||
Content = new StringContent("Response-Test-Data")
|
||||
});
|
||||
|
||||
return taskCompletionSource.Task;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestTimeoutsProperlySet()
|
||||
{
|
||||
HttpClientUploader uploader = new HttpClientUploader();
|
||||
Assert.Equal(HttpClientUploader.DEFAULT_CONNECTION_TIMEOUT_MS, uploader.httpClient.Timeout.TotalMilliseconds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestGleanHeadersAreCorrectlyDispatched()
|
||||
{
|
||||
(string, string)[] expectedHeaders = {
|
||||
("Content-Type", "application/json; charset=utf-8"),
|
||||
("Test-header", "SomeValue"),
|
||||
("OtherHeader", "Glean/Test 25.0.2")
|
||||
};
|
||||
|
||||
List<(string, string)> receivedHeaders = new List<(string, string)>();
|
||||
|
||||
HttpClientUploader uploader = new HttpClientUploader(new FakeHttpMessageHandler((request) =>
|
||||
{
|
||||
// Get both content and request headers.
|
||||
foreach (var h in request.Headers)
|
||||
{
|
||||
receivedHeaders.Add((h.Key, string.Join(",", h.Value.ToArray())));
|
||||
}
|
||||
|
||||
foreach (var h in request.Content.Headers)
|
||||
{
|
||||
receivedHeaders.Add((h.Key, string.Join(",", h.Value.ToArray())));
|
||||
}
|
||||
}, HttpStatusCode.OK));
|
||||
uploader.Upload(TEST_URL, Encoding.UTF8.GetBytes(TEST_PING), expectedHeaders);
|
||||
|
||||
Assert.Equal(expectedHeaders.Length, receivedHeaders.Count);
|
||||
// For some reason checking `expectedHeaders.SequenceEqual(receivedHeaders.ToArray())`
|
||||
// does not work: it always fails. Computing the intersection of the two lists work, though.
|
||||
// As long as the size of the intersection equals that of the expected headers, we should be
|
||||
// fine.
|
||||
Assert.Equal(expectedHeaders.Length, receivedHeaders.Intersect(expectedHeaders).ToArray().Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestUploadReturnsTheStatusCodeForSuccessfulRequests()
|
||||
{
|
||||
// Create a fake uploader that always reports 200.
|
||||
HttpClientUploader uploader =
|
||||
new HttpClientUploader(new FakeHttpMessageHandler((_) => { }, HttpStatusCode.OK));
|
||||
var result = uploader.Upload(TEST_URL, Encoding.UTF8.GetBytes(TEST_PING), new (string, string)[]{ });
|
||||
// Check if we received 200.
|
||||
Assert.IsType<HttpResponse>(result);
|
||||
Assert.Equal((int)HttpStatusCode.OK, ((HttpResponse)result).statusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestUploadCorrectlyUploadsThePingData()
|
||||
{
|
||||
// Create a fake uploader that always reports 200.
|
||||
HttpClientUploader uploader = new HttpClientUploader(new FakeHttpMessageHandler((request) =>
|
||||
{
|
||||
Assert.Equal(TEST_PING, Encoding.UTF8.GetString(request.Content.ReadAsByteArrayAsync().Result));
|
||||
}, HttpStatusCode.OK));
|
||||
var result = uploader.Upload(TEST_URL, Encoding.UTF8.GetBytes(TEST_PING), new (string, string)[] { });
|
||||
|
||||
// We received a result, the content will be checked in the fake handler above.
|
||||
Assert.IsType<HttpResponse>(result);
|
||||
}
|
||||
|
||||
// TODO: Add cookie related tests with bug 1646778
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
|
||||
"parallelizeAssembly": false,
|
||||
"parallelizeTestCollections": false
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
# Glean C# SDK
|
||||
|
||||
> 🚨 The Glean C# SDK is deprecated.
|
||||
> It's not up-to-date with latest Glean features and might not work.
|
||||
> If you want to use this please contact us at [glean-team@mozilla.com](mailto:glean-team@mozilla.com)
|
||||
> 🚨
|
|
@ -1,51 +0,0 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30128.74
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glean", "Glean\Glean.csproj", "{AC607140-A511-4AAD-A521-A344A94E79CA}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GleanTests", "GleanTests\GleanTests.csproj", "{F0CBD450-0A95-4D2C-8AAF-149329F9B559}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "..\..\samples\csharp\Sample.csproj", "{B15FB0D6-7C0C-4DE9-A922-3972F9BE5179}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{AC607140-A511-4AAD-A521-A344A94E79CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AC607140-A511-4AAD-A521-A344A94E79CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AC607140-A511-4AAD-A521-A344A94E79CA}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{AC607140-A511-4AAD-A521-A344A94E79CA}.Debug|x86.Build.0 = Debug|x86
|
||||
{AC607140-A511-4AAD-A521-A344A94E79CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AC607140-A511-4AAD-A521-A344A94E79CA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AC607140-A511-4AAD-A521-A344A94E79CA}.Release|x86.ActiveCfg = Release|x86
|
||||
{AC607140-A511-4AAD-A521-A344A94E79CA}.Release|x86.Build.0 = Release|x86
|
||||
{F0CBD450-0A95-4D2C-8AAF-149329F9B559}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F0CBD450-0A95-4D2C-8AAF-149329F9B559}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F0CBD450-0A95-4D2C-8AAF-149329F9B559}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{F0CBD450-0A95-4D2C-8AAF-149329F9B559}.Debug|x86.Build.0 = Debug|x86
|
||||
{F0CBD450-0A95-4D2C-8AAF-149329F9B559}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F0CBD450-0A95-4D2C-8AAF-149329F9B559}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F0CBD450-0A95-4D2C-8AAF-149329F9B559}.Release|x86.ActiveCfg = Release|x86
|
||||
{F0CBD450-0A95-4D2C-8AAF-149329F9B559}.Release|x86.Build.0 = Release|x86
|
||||
{B15FB0D6-7C0C-4DE9-A922-3972F9BE5179}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B15FB0D6-7C0C-4DE9-A922-3972F9BE5179}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B15FB0D6-7C0C-4DE9-A922-3972F9BE5179}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{B15FB0D6-7C0C-4DE9-A922-3972F9BE5179}.Debug|x86.Build.0 = Debug|x86
|
||||
{B15FB0D6-7C0C-4DE9-A922-3972F9BE5179}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B15FB0D6-7C0C-4DE9-A922-3972F9BE5179}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B15FB0D6-7C0C-4DE9-A922-3972F9BE5179}.Release|x86.ActiveCfg = Release|x86
|
||||
{B15FB0D6-7C0C-4DE9-A922-3972F9BE5179}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3B955DBB-AAF7-4C8B-9F9F-D12743AD314A}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -54,9 +54,6 @@ class GleanPlugin implements Plugin<Project> {
|
|||
* `python -m module`. However, it first checks that the installed
|
||||
* package is at the desired version, and if not, upgrades it using `pip`.
|
||||
*
|
||||
* ** IMPORTANT**
|
||||
* Keep this script in sync with the one in `glean-core/csharp/GleanTasks/GleanParser.cs`.
|
||||
*
|
||||
* Note: Groovy doesn't support embedded " in multi-line strings, so care
|
||||
* should be taken to use ' everywhere in this code snippet.
|
||||
*/
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
// 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 Mozilla.Glean;
|
||||
using System;
|
||||
using System.IO;
|
||||
using static Mozilla.Glean.Glean;
|
||||
using static csharp.GleanMetrics.CsharpTestDefinition;
|
||||
using static csharp.GleanMetrics.PingsDefinition;
|
||||
|
||||
namespace csharp
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
string gleanDataDir = Path.Combine(Directory.GetCurrentDirectory(), "glean_data");
|
||||
Console.WriteLine("Adding Glean data to {0}", gleanDataDir);
|
||||
|
||||
GleanInstance.Initialize(
|
||||
applicationId: "org.mozilla.glean.csharp.sample",
|
||||
applicationVersion: "1.0",
|
||||
uploadEnabled: true,
|
||||
configuration: new Configuration(),
|
||||
dataDir: gleanDataDir
|
||||
);
|
||||
|
||||
CsharpTest.mystring.Set("test-string");
|
||||
|
||||
Pings.sample.Submit();
|
||||
|
||||
Console.WriteLine("Press any key to exit the sample...");
|
||||
Console.ReadKey();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<Version>0.3.7</Version>
|
||||
<!--
|
||||
The following properties were determined by following the solution outlined here:
|
||||
https://github.com/Microsoft/msbuild/issues/539#issuecomment-289930591
|
||||
-->
|
||||
<IsWindows Condition="'$(OS)' == 'Windows_NT'">true</IsWindows>
|
||||
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
|
||||
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
|
||||
<Platforms>AnyCPU;x86</Platforms>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../glean-core/csharp/Glean/Glean.csproj" />
|
||||
|
||||
<!--
|
||||
Unfortunately, the sample won't be able to find the native dlls, even though they
|
||||
live within the runtimes/<rid>/native directory. To make it possible to run
|
||||
the sample, manually copy the Glean FFI dll to $(OutDir).
|
||||
-->
|
||||
<Content Condition="$(IsWindows) == true" Include="../../target/$(Configuration.ToLowerInvariant())/glean_ffi.dll" CopyToOutputDirectory="Always" />
|
||||
<Content Condition="$(IsLinux) == true" Include="../../target/$(Configuration.ToLowerInvariant())/libglean_ffi.so" CopyToOutputDirectory="Always" />
|
||||
<Content Condition="$(IsOSX) == true" Include="../../target/$(Configuration.ToLowerInvariant())/libglean_ffi.dylib" CopyToOutputDirectory="Always" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Define the code generating task. We can't load it directly from the AssemblyFile, otherwise
|
||||
msbuild will lock the file and fail next builds. To work around this, we generate a task
|
||||
definition ourselves and load the real task code inside it.
|
||||
External consumers will not need to do that, they will simply reference the AssemblyName or
|
||||
AssemblyFile in the UsingTask directive.
|
||||
|
||||
Note that we are using the `RoslynCodeTaskFactory` for compiling inline code fragments instead
|
||||
of `CodeTaskFactory`. That's because the former is deprecated and not available outside of the
|
||||
Windows .NET framework. See https://github.com/dotnet/msbuild/issues/2890 .
|
||||
-->
|
||||
<UsingTask TaskName="GleanParser" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
|
||||
<ParameterGroup>
|
||||
<RegistryFiles ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
|
||||
<OutputPath ParameterType="System.String" Required="true" />
|
||||
<Namespace ParameterType="System.String" Required="true" />
|
||||
<AllowReserved ParameterType="System.Boolean" Required="false" />
|
||||
</ParameterGroup>
|
||||
<Task>
|
||||
<Code Type="Class" Source="../../glean-core/csharp/Glean/GleanParser.cs" Language="cs" />
|
||||
</Task>
|
||||
</UsingTask>
|
||||
|
||||
<Target Name="GleanIntegration" BeforeTargets="CoreCompile">
|
||||
<ItemGroup>
|
||||
<GleanRegistryFiles Include="metrics.yaml" />
|
||||
<GleanRegistryFiles Include="pings.yaml" />
|
||||
</ItemGroup>
|
||||
<!-- This is what actually runs the parser. -->
|
||||
<GleanParser RegistryFiles="@(GleanRegistryFiles)" OutputPath="$(IntermediateOutputPath)Glean" Namespace="csharp.GleanMetrics" />
|
||||
|
||||
<!--
|
||||
And this adds the generated files to the project, so that they can be found by
|
||||
the compiler and Intellisense.
|
||||
-->
|
||||
<ItemGroup>
|
||||
<Compile Include="$(IntermediateOutputPath)Glean/**/*.cs" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
|
@ -1,76 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<!-- UAC Manifest Options
|
||||
If you want to change the Windows User Account Control level replace the
|
||||
requestedExecutionLevel node with one of the following.
|
||||
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
|
||||
|
||||
Specifying requestedExecutionLevel element will disable file and registry virtualization.
|
||||
Remove this element if your application requires this virtualization for backwards
|
||||
compatibility.
|
||||
-->
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- A list of the Windows versions that this application has been tested on
|
||||
and is designed to work with. Uncomment the appropriate elements
|
||||
and Windows will automatically select the most compatible environment. -->
|
||||
|
||||
<!-- Windows Vista -->
|
||||
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
|
||||
|
||||
<!-- Windows 7 -->
|
||||
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
|
||||
|
||||
<!-- Windows 8 -->
|
||||
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
|
||||
|
||||
<!-- Windows 8.1 -->
|
||||
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
|
||||
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
|
||||
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
|
||||
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
|
||||
<!--
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
-->
|
||||
|
||||
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
|
||||
<!--
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
-->
|
||||
|
||||
</assembly>
|
|
@ -1,27 +0,0 @@
|
|||
# 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/.
|
||||
|
||||
# This file defines the metrics that are recorded by the Glean SDK. They are
|
||||
# automatically converted to Kotlin code at build time using the `glean_parser`
|
||||
# PyPI package.
|
||||
|
||||
---
|
||||
|
||||
$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
|
||||
|
||||
csharp.test:
|
||||
mystring:
|
||||
type: string
|
||||
description: |
|
||||
Testing a sample string
|
||||
send_in_pings:
|
||||
- sample
|
||||
lifetime: application
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/123456789
|
||||
data_reviews:
|
||||
- N/A
|
||||
notification_emails:
|
||||
- CHANGE-ME@example.com
|
||||
expires: 2100-01-01
|
|
@ -1,22 +0,0 @@
|
|||
# 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/.
|
||||
|
||||
# This file defines the built-in pings that are recorded by the Glean SDK. They
|
||||
# are automatically converted to Kotlin code at build time using the
|
||||
# `glean_parser` PyPI package.
|
||||
|
||||
---
|
||||
|
||||
$schema: moz://mozilla.org/schemas/glean/pings/2-0-0
|
||||
|
||||
sample:
|
||||
description: |
|
||||
A sample custom ping.
|
||||
include_client_id: true
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/123456789
|
||||
data_reviews:
|
||||
- N/A
|
||||
notification_emails:
|
||||
- CHANGE-ME@example.com
|
Загрузка…
Ссылка в новой задаче