Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions dev/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ services:
- redis
- cloud

telemetry-dashboard:
image: mcr.microsoft.com/dotnet/aspire-dashboard:latest
ports:
- "18888:18888"
- "4317:18889"
profiles:
- telemetry

volumes:
mssql_dev_data:
postgres_dev_data:
Expand Down
23 changes: 23 additions & 0 deletions src/Api/Platform/Sync/SyncMetrics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Diagnostics.Metrics;

namespace Bit.Api.Platform.Sync;

public sealed class SyncMetrics
{
private readonly Histogram<int> _syncVaultCount;

public SyncMetrics(IMeterFactory meterFactory)
{
var meter = meterFactory.Create("Bitwarden.Sync");
_syncVaultCount = meter.CreateHistogram<int>(
"bitwarden.sync.vault_count",
unit: "{item}",
description: "The number of ciphers returned in the sync operation."
);
}

public void RecordSyncInfo(int cipherCount)
{
_syncVaultCount.Record(cipherCount);
}
}
4 changes: 4 additions & 0 deletions src/Api/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
using Bit.Core.Auth.IdentityServer;
using Bit.Core.Auth.Identity;
using Bit.Core.Enums;
using Bit.Api.Platform.Sync;



#if !OSS
Expand Down Expand Up @@ -179,6 +181,8 @@ public void ConfigureServices(IServiceCollection services)
.AddScoped<IRotationValidator<IEnumerable<OtherDeviceKeysUpdateRequestModel>, IEnumerable<Device>>,
DeviceRotationValidator>();

services.TryAddSingleton<SyncMetrics>();

// Services
services.AddBaseServices(globalSettings);
services.AddDefaultServices(globalSettings);
Expand Down
17 changes: 10 additions & 7 deletions src/Api/Vault/Controllers/SyncController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable

using Bit.Api.Platform.Sync;
using Bit.Api.Vault.Models.Response;
using Bit.Core;
using Bit.Core.AdminConsole.Entities;
Expand Down Expand Up @@ -47,6 +45,7 @@ public class SyncController : Controller
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
private readonly IWebAuthnCredentialRepository _webAuthnCredentialRepository;
private readonly IUserAccountKeysQuery _userAccountKeysQuery;
private readonly SyncMetrics _syncMetrics;

public SyncController(
IUserService userService,
Expand All @@ -64,7 +63,8 @@ public SyncController(
IApplicationCacheService applicationCacheService,
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
IWebAuthnCredentialRepository webAuthnCredentialRepository,
IUserAccountKeysQuery userAccountKeysQuery)
IUserAccountKeysQuery userAccountKeysQuery,
SyncMetrics syncMetrics)
{
_userService = userService;
_folderRepository = folderRepository;
Expand All @@ -82,6 +82,7 @@ public SyncController(
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
_webAuthnCredentialRepository = webAuthnCredentialRepository;
_userAccountKeysQuery = userAccountKeysQuery;
_syncMetrics = syncMetrics;
}

[HttpGet("")]
Expand All @@ -107,8 +108,8 @@ await _providerUserRepository.GetManyOrganizationDetailsByUserAsync(user.Id,
var ciphers = FilterSSHKeys(allCiphers);
var sends = await _sendRepository.GetManyByUserIdAsync(user.Id);

IEnumerable<CollectionDetails> collections = null;
IDictionary<Guid, IGrouping<Guid, CollectionCipher>> collectionCiphersGroupDict = null;
IEnumerable<CollectionDetails>? collections = null;
IDictionary<Guid, IGrouping<Guid, CollectionCipher>>? collectionCiphersGroupDict = null;
IEnumerable<Policy> policies = await _policyRepository.GetManyByUserIdAsync(user.Id);

if (hasEnabledOrgs)
Expand All @@ -128,13 +129,15 @@ await _providerUserRepository.GetManyOrganizationDetailsByUserAsync(user.Id,
? await _webAuthnCredentialRepository.GetManyByUserIdAsync(user.Id)
: [];

UserAccountKeysData userAccountKeys = null;
UserAccountKeysData? userAccountKeys = null;
// JIT TDE users and some broken/old users may not have a private key.
if (!string.IsNullOrWhiteSpace(user.PrivateKey))
{
userAccountKeys = await _userAccountKeysQuery.Run(user);
}

_syncMetrics.RecordSyncInfo(ciphers.Count);

var response = new SyncResponseModel(_globalSettings, user, userAccountKeys, userTwoFactorEnabled, userHasPremiumFromOrganization, organizationAbilities,
organizationIdsClaimingActiveUser, organizationUserDetails, providerUserDetails, providerUserOrganizationDetails,
folders, collections, ciphers, collectionCiphersGroupDict, excludeDomains, policies, sends, webAuthnCredentials);
Expand Down
1 change: 1 addition & 0 deletions test/Api.Test/Vault/Controllers/SyncControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
namespace Bit.Api.Test.Controllers;

[ControllerCustomize(typeof(SyncController))]
[MeterCustomize]
[SutProviderCustomize]
public class SyncControllerTests
{
Expand Down
35 changes: 35 additions & 0 deletions test/Common/AutoFixture/Attributes/MeterCustomizeAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Diagnostics.Metrics;
using AutoFixture;
using NSubstitute;

namespace Bit.Test.Common.AutoFixture.Attributes;

/// <summary>
/// Customizes a <see cref="IMeterFactory"/> to be able to actually create <see cref="Meter"/>'s.
/// </summary>
public class MeterCustomizeAttribute : BitCustomizeAttribute
{
private static readonly MeterCustomization _meterCustomization = new();
public override ICustomization GetCustomization() => _meterCustomization;

private class MeterCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<IMeterFactory>(factory =>
{
return factory.FromFactory(() =>
{
var fakeFactory = Substitute.For<IMeterFactory>();
fakeFactory.Create(Arg.Any<MeterOptions>())
.Returns((call) =>
{
return new Meter(call.Arg<MeterOptions>());
});

return fakeFactory;
});
});
}
}
}
Loading