Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.
Merged
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
3 changes: 2 additions & 1 deletion src/GitHub.App/Models/RepositoryHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using System.Linq;
using System.Reactive.Threading.Tasks;
using System.Collections.Generic;
using GitHub.Extensions;

namespace GitHub.Models
{
Expand Down Expand Up @@ -241,7 +242,7 @@ IObservable<AuthenticationResult> LoginWithApiUser(UserAndScopes userAndScopes)
if (result.IsSuccess())
{
var accountCacheItem = new AccountCacheItem(userAndScopes.User);
usage.IncrementLoginCount();
usage.IncrementLoginCount().Forget();
return ModelService.InsertUser(accountCacheItem).Select(_ => result);
}

Expand Down
2 changes: 1 addition & 1 deletion src/GitHub.App/Services/PullRequestService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ async Task<IPullRequestModel> PushAndCreatePR(IRepositoryHost host,
await Task.Delay(TimeSpan.FromSeconds(5));

var ret = await host.ModelService.CreatePullRequest(sourceRepository, targetRepository, sourceBranch, targetBranch, title, body);
usageTracker.IncrementUpstreamPullRequestCount();
await usageTracker.IncrementUpstreamPullRequestCount();
return ret;
}

Expand Down
2 changes: 1 addition & 1 deletion src/GitHub.App/ViewModels/GistCreationViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ IObservable<Gist> OnCreateGist(object unused)
newGist.Files.Add(FileName, SelectedText);

return gistPublishService.PublishGist(apiClient, newGist)
.Do(_ => usageTracker.IncrementCreateGistCount())
.Do(_ => usageTracker.IncrementCreateGistCount().Forget())
.Catch<Gist, Exception>(ex =>
{
if (!ex.IsCriticalException())
Expand Down
2 changes: 1 addition & 1 deletion src/GitHub.App/ViewModels/RepositoryCloneViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ IObservable<Unit> OnCloneRepository(object state)
return cloneService.CloneRepository(repository.CloneUrl, repository.Name, BaseRepositoryPath)
.ContinueAfter(() =>
{
usageTracker.IncrementCloneCount();
usageTracker.IncrementCloneCount().Forget();
return Observable.Return(Unit.Default);
});
})
Expand Down
2 changes: 1 addition & 1 deletion src/GitHub.App/ViewModels/RepositoryCreationViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ IObservable<Unit> OnCreateRepository(object state)
SelectedAccount,
BaseRepositoryPath,
repositoryHost.ApiClient)
.Do(_ => usageTracker.IncrementCreateCount());
.Do(_ => usageTracker.IncrementCreateCount().Forget());
}

ReactiveCommand<Unit> InitializeCreateRepositoryCommand()
Expand Down
2 changes: 1 addition & 1 deletion src/GitHub.App/ViewModels/RepositoryPublishViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ IObservable<ProgressState> OnPublishRepository(object arg)
var account = SelectedAccount;

return repositoryPublishService.PublishRepository(newRepository, account, SelectedHost.ApiClient)
.Do(_ => usageTracker.IncrementPublishCount())
.Do(_ => usageTracker.IncrementPublishCount().Forget())
.Select(_ => ProgressState.Success)
.Catch<ProgressState, Exception>(ex =>
{
Expand Down
2 changes: 1 addition & 1 deletion src/GitHub.App/ViewModels/StartPageCloneViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ IObservable<Unit> OnCloneRepository(object state)
return cloneService.CloneRepository(repository.CloneUrl, repository.Name, BaseRepositoryPath)
.ContinueAfter(() =>
{
usageTracker.IncrementCloneCount();
usageTracker.IncrementCloneCount().Forget();
return Observable.Return(Unit.Default);
});
})
Expand Down
19 changes: 10 additions & 9 deletions src/GitHub.Exports/Services/IUsageTracker.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
using GitHub.VisualStudio;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace GitHub.Services
{
[Guid(Guids.UsageTrackerId)]
public interface IUsageTracker
{
void IncrementLaunchCount();
void IncrementCloneCount();
void IncrementCreateCount();
void IncrementPublishCount();
void IncrementOpenInGitHubCount();
void IncrementLinkToGitHubCount();
void IncrementCreateGistCount();
void IncrementUpstreamPullRequestCount();
void IncrementLoginCount();
Task IncrementLaunchCount();
Task IncrementCloneCount();
Task IncrementCreateCount();
Task IncrementPublishCount();
Task IncrementOpenInGitHubCount();
Task IncrementLinkToGitHubCount();
Task IncrementCreateGistCount();
Task IncrementUpstreamPullRequestCount();
Task IncrementLoginCount();
}
}
1 change: 1 addition & 0 deletions src/GitHub.VisualStudio/GitHub.VisualStudio.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Helpers\Browser.cs" />
<Compile Include="Services\UsageTracker.cs" />
<Compile Include="Services\UsageTrackerDispatcher.cs" />
<Compile Include="Settings\Constants.cs" />
<Compile Include="Services\ConnectionManager.cs" />
<Compile Include="Services\Program.cs" />
Expand Down
10 changes: 8 additions & 2 deletions src/GitHub.VisualStudio/GitHubPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
await base.InitializeAsync(cancellationToken, progress);
await EnsurePackageLoaded(new Guid(ServiceProviderPackage.ServiceProviderPackageId));

//var usageTracker = await GetServiceAsync(typeof(IUsageTracker)) as IUsageTracker;
//usageTracker.IncrementLaunchCount();
// Activate the usage tracker by forcing an instance to be created.
GetServiceAsync(typeof(IUsageTracker)).Forget();

InitializeMenus().Forget();
}

Expand Down Expand Up @@ -178,6 +179,11 @@ async Task<object> CreateService(IAsyncServiceContainer container, CancellationT
var sp = await GetServiceAsync(typeof(IUIProvider)) as IUIProvider;
return new MenuProvider(sp);
}
else if (serviceType == typeof(IUsageTracker))
{
var uiProvider = await GetServiceAsync(typeof(IUIProvider)) as IUIProvider;
return new UsageTracker(uiProvider);
}
// go the mef route
else
{
Expand Down
2 changes: 1 addition & 1 deletion src/GitHub.VisualStudio/Menus/CopyLink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public async void Activate([AllowNull]object data = null)
Clipboard.SetText(link);
var ns = ServiceProvider.TryGetService<IStatusBarNotificationService>();
ns?.ShowMessage(Resources.LinkCopiedToClipboardMessage);
UsageTracker.IncrementLinkToGitHubCount();
await UsageTracker.IncrementLinkToGitHubCount();
}
catch
{
Expand Down
2 changes: 1 addition & 1 deletion src/GitHub.VisualStudio/Menus/OpenLink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public async void Activate([AllowNull]object data = null)
var browser = ServiceProvider.TryGetService<IVisualStudioBrowser>();
browser?.OpenUrl(link.ToUri());

UsageTracker.IncrementOpenInGitHubCount();
await UsageTracker.IncrementOpenInGitHubCount();
}

public bool CanShow()
Expand Down
118 changes: 63 additions & 55 deletions src/GitHub.VisualStudio/Services/UsageTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,34 @@
using Microsoft.VisualStudio.Shell;
using Task = System.Threading.Tasks.Task;
using GitHub.Extensions;
using System.Threading.Tasks;

namespace GitHub.Services
{
[Export(typeof(IUsageTracker))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class UsageTracker : IUsageTracker
{
const string StoreFileName = "ghfvs.usage";
static readonly Calendar cal = CultureInfo.InvariantCulture.Calendar;

readonly IUIProvider uiProvider;
readonly DispatcherTimer timer;

IMetricsService client;
readonly IConnectionManager connectionManager;
IConnectionManager connectionManager;
IPackageSettings userSettings;
readonly IVSServices vsservices;
readonly DispatcherTimer timer;
readonly string storePath;
readonly IServiceProvider serviceProvider;
IVSServices vsservices;
string storePath;
bool firstRun = true;

Func<string, bool> fileExists;
Func<string, Encoding, string> readAllText;
Action<string, string, Encoding> writeAllText;
Action<string> dirCreate;

[ImportingConstructor]
public UsageTracker(
IProgram program,
IConnectionManager connectionManager,
IVSServices vsservices,
[Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider)
public UsageTracker(IUIProvider uiProvider)
{
this.serviceProvider = serviceProvider;
this.uiProvider = uiProvider;

fileExists = (path) => System.IO.File.Exists(path);
readAllText = (path, encoding) =>
Expand All @@ -65,88 +62,105 @@ public UsageTracker(
};
dirCreate = (path) => System.IO.Directory.CreateDirectory(path);

this.connectionManager = connectionManager;
this.vsservices = vsservices;
this.timer = new DispatcherTimer(
TimeSpan.FromMinutes(1),
TimeSpan.FromMinutes(3),
DispatcherPriority.Background,
TimerTick,
Dispatcher.CurrentDispatcher);
this.storePath = System.IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
program.ApplicationName,
StoreFileName);

RunTimer();
}

public void IncrementLaunchCount()
public async Task IncrementLaunchCount()
{
var usage = LoadUsage();
var usage = await LoadUsage();
++usage.Model.NumberOfStartups;
++usage.Model.NumberOfStartupsWeek;
++usage.Model.NumberOfStartupsMonth;
SaveUsage(usage);
}

public void IncrementCloneCount()
public async Task IncrementCloneCount()
{
var usage = LoadUsage();
var usage = await LoadUsage();
++usage.Model.NumberOfClones;
SaveUsage(usage);
}

public void IncrementCreateCount()
public async Task IncrementCreateCount()
{
var usage = LoadUsage();
var usage = await LoadUsage();
++usage.Model.NumberOfReposCreated;
SaveUsage(usage);
}

public void IncrementPublishCount()
public async Task IncrementPublishCount()
{
var usage = LoadUsage();
var usage = await LoadUsage();
++usage.Model.NumberOfReposPublished;
SaveUsage(usage);
}

public void IncrementOpenInGitHubCount()
public async Task IncrementOpenInGitHubCount()
{
var usage = LoadUsage();
var usage = await LoadUsage();
++usage.Model.NumberOfOpenInGitHub;
SaveUsage(usage);
}

public void IncrementLinkToGitHubCount()
public async Task IncrementLinkToGitHubCount()
{
var usage = LoadUsage();
var usage = await LoadUsage();
++usage.Model.NumberOfLinkToGitHub;
SaveUsage(usage);
}

public void IncrementCreateGistCount()
public async Task IncrementCreateGistCount()
{
var usage = LoadUsage();
var usage = await LoadUsage();
++usage.Model.NumberOfGists;
SaveUsage(usage);
}

public void IncrementUpstreamPullRequestCount()
public async Task IncrementUpstreamPullRequestCount()
{
var usage = LoadUsage();
var usage = await LoadUsage();
++usage.Model.NumberOfUpstreamPullRequests;
SaveUsage(usage);
}

public void IncrementLoginCount()
public async Task IncrementLoginCount()
{
var usage = LoadUsage();
var usage = await LoadUsage();
++usage.Model.NumberOfLogins;
SaveUsage(usage);
}

UsageStore LoadUsage()
async Task Initialize()
{
// The services needed by the usage tracker are loaded when they are first needed to
// improve the startup time of the extension.
if (userSettings == null)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

client = uiProvider.GetService<IMetricsService>();
connectionManager = uiProvider.GetService<IConnectionManager>();
userSettings = uiProvider.GetService<IPackageSettings>();
vsservices = uiProvider.GetService<IVSServices>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any or all of these things can trigger a MEF call via GetExportedValue, so we need to make sure this is done from the main thread, not a background thread. We'll need await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); before these calls, GetService is not thread-safe.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, the timer is a DispatcherTimer so the tick method will always call this from the main thread so that's fine. I don't think anything else will be calling this from background threads, but in case that happens, probably best to make sure we're on the right thread here.


var program = uiProvider.GetService<IProgram>();
storePath = System.IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
program.ApplicationName,
StoreFileName);
}
}

async Task<UsageStore> LoadUsage()
{
await Initialize();

var json = fileExists(storePath) ? readAllText(storePath, Encoding.UTF8) : null;
UsageStore result = null;
try
Expand Down Expand Up @@ -178,7 +192,6 @@ void RunTimer()
{
// The timer first ticks after 3 minutes to allow things to settle down after startup.
// This will be changed to 8 hours after the first tick by the TimerTick method.
timer.Interval = TimeSpan.FromMinutes(3);
timer.Start();
}

Expand All @@ -194,31 +207,26 @@ void TimerTick(object sender, EventArgs e)

async Task TimerTick()
{
Debug.Assert(client != null, "TimerTick should not be triggered when there is no IMetricsService");
await Initialize();

// Subsequent timer ticks should occur every 8 hours.
timer.Interval = TimeSpan.FromHours(8);

if (userSettings == null)
if (firstRun)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
client = serviceProvider.GetExportedValue<IMetricsService>();
if (client == null)
{
timer.Stop();
return;
}
userSettings = serviceProvider.GetExportedValue<IPackageSettings>();
await IncrementLaunchCount();
timer.Interval = TimeSpan.FromHours(8);
firstRun = false;
}

if (!userSettings.CollectMetrics)
if (client == null || !userSettings.CollectMetrics)
{
timer.Stop();
return;
}

// Every time we increment the launch count we increment both daily and weekly
// launch count but we only submit (and clear) the weekly launch count when we've
// transitioned into a new week. We've defined a week by the ISO8601 definition,
// i.e. week starting on Monday and ending on Sunday.
var usage = LoadUsage();
var usage = await LoadUsage();
var lastDate = usage.LastUpdated;
var currentDate = DateTimeOffset.Now;
var includeWeekly = GetIso8601WeekOfYear(lastDate) != GetIso8601WeekOfYear(currentDate);
Expand Down
Loading