Browse Source

UDC: include date and environment block on first upload

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5620 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
pull/1/head
Daniel Grunwald 16 years ago
parent
commit
e3f935c15e
  1. 52
      src/AddIns/Misc/UsageDataCollector/UsageDataCollector.AddIn/AnalyticsMonitor.cs
  2. 63
      src/AddIns/Misc/UsageDataCollector/UsageDataCollector/UsageDataSessionWriter.cs
  3. 43
      src/AddIns/Misc/UsageDataCollector/UsageDataCollector/UsageDataUploader.cs

52
src/AddIns/Misc/UsageDataCollector/UsageDataCollector.AddIn/AnalyticsMonitor.cs

@ -6,14 +6,16 @@
// </file> // </file>
using System; using System;
using System.Linq; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Threading; using System.Threading;
using ICSharpCode.Core; using ICSharpCode.Core;
using ICSharpCode.Core.Services; using ICSharpCode.Core.Services;
using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop;
using ICSharpCode.UsageDataCollector.Contracts;
namespace ICSharpCode.UsageDataCollector namespace ICSharpCode.UsageDataCollector
{ {
@ -78,29 +80,12 @@ namespace ICSharpCode.UsageDataCollector
/// </summary> /// </summary>
public void OpenSession() public void OpenSession()
{ {
IEnumerable<UsageDataEnvironmentProperty> appEnvironmentProperties = GetAppProperties();
bool sessionOpened = false; bool sessionOpened = false;
lock (lockObj) { lock (lockObj) {
if (session == null) { if (session == null) {
try { try {
session = new UsageDataSessionWriter(dbFileName); session = new UsageDataSessionWriter(dbFileName);
session.OnException = MessageService.ShowException;
session.AddEnvironmentData("appVersion", RevisionClass.FullVersion);
session.AddEnvironmentData("language", ResourceService.Language);
session.AddEnvironmentData("culture", CultureInfo.CurrentCulture.Name);
string PROCESSOR_ARCHITECTURE = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432");
if (string.IsNullOrEmpty(PROCESSOR_ARCHITECTURE)) {
PROCESSOR_ARCHITECTURE = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
}
if (!string.IsNullOrEmpty(PROCESSOR_ARCHITECTURE)) {
session.AddEnvironmentData("architecture", PROCESSOR_ARCHITECTURE);
}
session.AddEnvironmentData("userAddInCount", AddInTree.AddIns.Where(a => !a.IsPreinstalled).Count().ToString());
#if DEBUG
session.AddEnvironmentData("debug", "true");
#endif
sessionOpened = true;
} catch (IncompatibleDatabaseException ex) { } catch (IncompatibleDatabaseException ex) {
if (ex.ActualVersion < ex.ExpectedVersion) { if (ex.ActualVersion < ex.ExpectedVersion) {
LoggingService.Info("AnalyticsMonitor: " + ex.Message + ", removing old database"); LoggingService.Info("AnalyticsMonitor: " + ex.Message + ", removing old database");
@ -115,14 +100,43 @@ namespace ICSharpCode.UsageDataCollector
LoggingService.Warn("AnalyticsMonitor: " + ex.Message); LoggingService.Warn("AnalyticsMonitor: " + ex.Message);
} }
} }
if (session != null) {
session.OnException = MessageService.ShowException;
session.AddEnvironmentData(appEnvironmentProperties);
sessionOpened = true;
}
} }
} }
if (sessionOpened) { if (sessionOpened) {
UsageDataUploader uploader = new UsageDataUploader(dbFileName); UsageDataUploader uploader = new UsageDataUploader(dbFileName);
uploader.EnvironmentDataForDummySession = appEnvironmentProperties;
ThreadPool.QueueUserWorkItem(delegate { uploader.StartUpload(UploadUrl); }); ThreadPool.QueueUserWorkItem(delegate { uploader.StartUpload(UploadUrl); });
} }
} }
static IEnumerable<UsageDataEnvironmentProperty> GetAppProperties()
{
List<UsageDataEnvironmentProperty> properties = new List<UsageDataEnvironmentProperty> {
new UsageDataEnvironmentProperty { Name = "appVersion", Value = RevisionClass.FullVersion },
new UsageDataEnvironmentProperty { Name = "language", Value = ResourceService.Language },
new UsageDataEnvironmentProperty { Name = "culture", Value = CultureInfo.CurrentCulture.Name },
new UsageDataEnvironmentProperty { Name = "userAddInCount", Value = AddInTree.AddIns.Where(a => !a.IsPreinstalled).Count().ToString() }
};
string PROCESSOR_ARCHITECTURE = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432");
if (string.IsNullOrEmpty(PROCESSOR_ARCHITECTURE)) {
PROCESSOR_ARCHITECTURE = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
}
if (!string.IsNullOrEmpty(PROCESSOR_ARCHITECTURE)) {
properties.Add(new UsageDataEnvironmentProperty { Name = "architecture", Value = PROCESSOR_ARCHITECTURE });
}
#if DEBUG
properties.Add(new UsageDataEnvironmentProperty { Name = "debug", Value = "true" });
#endif
return properties;
}
/// <summary> /// <summary>
/// Retrieves all stored data as XML text. /// Retrieves all stored data as XML text.
/// </summary> /// </summary>

63
src/AddIns/Misc/UsageDataCollector/UsageDataCollector/UsageDataSessionWriter.cs

@ -11,13 +11,17 @@ using System.Data.SQLite;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Security; using System.Security;
using System.Security.Permissions;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using ICSharpCode.UsageDataCollector.Contracts;
namespace ICSharpCode.UsageDataCollector namespace ICSharpCode.UsageDataCollector
{ {
/// <summary> /// <summary>
@ -25,7 +29,7 @@ namespace ICSharpCode.UsageDataCollector
/// ///
/// This class is thread-safe. /// This class is thread-safe.
/// </summary> /// </summary>
public class UsageDataSessionWriter : IDisposable public sealed class UsageDataSessionWriter : IDisposable
{ {
readonly object lockObj = new object(); readonly object lockObj = new object();
SQLiteConnection connection; SQLiteConnection connection;
@ -153,6 +157,19 @@ namespace ICSharpCode.UsageDataCollector
} }
} }
/// <summary>
/// Gets the default environment data.
/// </summary>
public static UsageDataEnvironmentProperty[] GetDefaultEnvironmentData()
{
return new [] {
new UsageDataEnvironmentProperty { Name = "platform", Value = Environment.OSVersion.Platform.ToString() },
new UsageDataEnvironmentProperty { Name = "osVersion", Value = Environment.OSVersion.Version.ToString() },
new UsageDataEnvironmentProperty { Name = "processorCount", Value = Environment.ProcessorCount.ToString() },
new UsageDataEnvironmentProperty { Name = "dotnetRuntime", Value = Environment.Version.ToString() }
};
}
void StartSession() void StartSession()
{ {
using (SQLiteTransaction transaction = this.connection.BeginTransaction()) { using (SQLiteTransaction transaction = this.connection.BeginTransaction()) {
@ -161,10 +178,7 @@ namespace ICSharpCode.UsageDataCollector
"SELECT last_insert_rowid();"; "SELECT last_insert_rowid();";
sessionID = (long)cmd.ExecuteScalar(); sessionID = (long)cmd.ExecuteScalar();
} }
AddEnvironmentData("platform", Environment.OSVersion.Platform.ToString()); AddEnvironmentData(GetDefaultEnvironmentData());
AddEnvironmentData("osVersion", Environment.OSVersion.Version.ToString());
AddEnvironmentData("processorCount", Environment.ProcessorCount.ToString());
AddEnvironmentData("dotnetRuntime", Environment.Version.ToString());
transaction.Commit(); transaction.Commit();
} }
} }
@ -185,22 +199,26 @@ namespace ICSharpCode.UsageDataCollector
/// <summary> /// <summary>
/// Adds environment data to the current session. /// Adds environment data to the current session.
/// </summary> /// </summary>
/// <param name="name">Name of the data entry.</param> public void AddEnvironmentData(IEnumerable<UsageDataEnvironmentProperty> properties)
/// <param name="value">Value of the data entry.</param>
public void AddEnvironmentData(string name, string value)
{ {
if (name == null) if (properties == null)
throw new ArgumentNullException("name"); throw new ArgumentNullException("properties");
UsageDataEnvironmentProperty[] pArray = properties.ToArray();
lock (lockObj) { lock (lockObj) {
if (isDisposed) if (isDisposed)
throw new ObjectDisposedException(GetType().Name); throw new ObjectDisposedException(GetType().Name);
using (SQLiteCommand cmd = this.connection.CreateCommand()) { using (SQLiteTransaction transaction = this.connection.BeginTransaction()) {
cmd.CommandText = "INSERT INTO Environment (session, name, value)" + foreach (UsageDataEnvironmentProperty p in pArray) {
" VALUES (?, ?, ?);"; using (SQLiteCommand cmd = this.connection.CreateCommand()) {
cmd.Parameters.Add(new SQLiteParameter { Value = sessionID }); cmd.CommandText = "INSERT INTO Environment (session, name, value)" +
cmd.Parameters.Add(new SQLiteParameter { Value = name }); " VALUES (?, ?, ?);";
cmd.Parameters.Add(new SQLiteParameter { Value = value }); cmd.Parameters.Add(new SQLiteParameter { Value = sessionID });
cmd.ExecuteNonQuery(); cmd.Parameters.Add(new SQLiteParameter { Value = p.Name });
cmd.Parameters.Add(new SQLiteParameter { Value = p.Value });
cmd.ExecuteNonQuery();
}
}
transaction.Commit();
} }
} }
} }
@ -482,6 +500,16 @@ namespace ICSharpCode.UsageDataCollector
/// </summary> /// </summary>
public IncompatibleDatabaseException() {} public IncompatibleDatabaseException() {}
/// <summary>
/// Creates a new IncompatibleDatabaseException instance.
/// </summary>
public IncompatibleDatabaseException(string message) : base(message) {}
/// <summary>
/// Creates a new IncompatibleDatabaseException instance.
/// </summary>
public IncompatibleDatabaseException(string message, Exception innerException) : base(message, innerException) {}
/// <summary> /// <summary>
/// Creates a new IncompatibleDatabaseException instance. /// Creates a new IncompatibleDatabaseException instance.
/// </summary> /// </summary>
@ -504,6 +532,7 @@ namespace ICSharpCode.UsageDataCollector
} }
/// <inheritdoc/> /// <inheritdoc/>
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
public override void GetObjectData(SerializationInfo info, StreamingContext context) public override void GetObjectData(SerializationInfo info, StreamingContext context)
{ {
base.GetObjectData(info, context); base.GetObjectData(info, context);

43
src/AddIns/Misc/UsageDataCollector/UsageDataCollector/UsageDataUploader.cs

@ -37,6 +37,11 @@ namespace ICSharpCode.UsageDataCollector
this.databaseFileName = databaseFileName; this.databaseFileName = databaseFileName;
} }
/// <summary>
/// Gets/Sets environment data that is sent with the dummy session on the first upload.
/// </summary>
public IEnumerable<UsageDataEnvironmentProperty> EnvironmentDataForDummySession { get; set; }
SQLiteConnection OpenConnection() SQLiteConnection OpenConnection()
{ {
SQLiteConnectionStringBuilder conn = new SQLiteConnectionStringBuilder(); SQLiteConnectionStringBuilder conn = new SQLiteConnectionStringBuilder();
@ -60,7 +65,7 @@ namespace ICSharpCode.UsageDataCollector
message = FetchDataForUpload(connection, true); message = FetchDataForUpload(connection, true);
} }
} }
using (StringWriter w = new StringWriter()) { using (StringWriter w = new StringWriter(CultureInfo.InvariantCulture)) {
using (XmlTextWriter xmlWriter = new XmlTextWriter(w)) { using (XmlTextWriter xmlWriter = new XmlTextWriter(w)) {
xmlWriter.Formatting = Formatting.Indented; xmlWriter.Formatting = Formatting.Indented;
DataContractSerializer serializer = new DataContractSerializer(typeof(UsageDataMessage)); DataContractSerializer serializer = new DataContractSerializer(typeof(UsageDataMessage));
@ -93,13 +98,17 @@ namespace ICSharpCode.UsageDataCollector
public void StartUpload(Binding binding, EndpointAddress endpoint) public void StartUpload(Binding binding, EndpointAddress endpoint)
{ {
UsageDataMessage message; UsageDataMessage message;
bool addDummySession = false;
using (SQLiteConnection connection = OpenConnection()) { using (SQLiteConnection connection = OpenConnection()) {
using (SQLiteTransaction transaction = connection.BeginTransaction()) { using (SQLiteTransaction transaction = connection.BeginTransaction()) {
CheckDatabaseVersion(connection); CheckDatabaseVersion(connection);
if (HasAlreadyUploadedToday(connection)) { HasUploadedTodayStatus status = HasAlreadyUploadedToday(connection);
if (status == HasUploadedTodayStatus.Yes) {
message = null; message = null;
} else { } else {
message = FetchDataForUpload(connection, false); message = FetchDataForUpload(connection, false);
if (status == HasUploadedTodayStatus.NeverUploaded)
addDummySession = true;
} }
transaction.Commit(); transaction.Commit();
} }
@ -107,6 +116,19 @@ namespace ICSharpCode.UsageDataCollector
if (message != null) { if (message != null) {
string commaSeparatedSessionIDList = GetCommaSeparatedIDList(message.Sessions); string commaSeparatedSessionIDList = GetCommaSeparatedIDList(message.Sessions);
if (addDummySession) {
// A dummy session is used for the first upload to notify the server of the user's environment.
// Without this, we couldn't tell the version of a user who tries SharpDevelop once but doesn't use it long
// enough for an actual session to be uploaded.
UsageDataSession dummySession = new UsageDataSession();
dummySession.SessionID = 0;
dummySession.StartTime = DateTime.UtcNow;
dummySession.EnvironmentProperties.AddRange(UsageDataSessionWriter.GetDefaultEnvironmentData());
if (this.EnvironmentDataForDummySession != null)
dummySession.EnvironmentProperties.AddRange(this.EnvironmentDataForDummySession);
message.Sessions.Add(dummySession);
}
DataContractSerializer serializer = new DataContractSerializer(typeof(UsageDataMessage)); DataContractSerializer serializer = new DataContractSerializer(typeof(UsageDataMessage));
byte[] data; byte[] data;
using (MemoryStream ms = new MemoryStream()) { using (MemoryStream ms = new MemoryStream()) {
@ -158,19 +180,26 @@ namespace ICSharpCode.UsageDataCollector
} }
} }
bool HasAlreadyUploadedToday(SQLiteConnection connection) enum HasUploadedTodayStatus
{
No,
Yes,
NeverUploaded
}
static HasUploadedTodayStatus HasAlreadyUploadedToday(SQLiteConnection connection)
{ {
using (SQLiteCommand cmd = connection.CreateCommand()) { using (SQLiteCommand cmd = connection.CreateCommand()) {
cmd.CommandText = "SELECT value > datetime('now','-1 day') FROM Properties WHERE name='lastUpload';"; cmd.CommandText = "SELECT value > datetime('now','-1 day') FROM Properties WHERE name='lastUpload';";
object result = cmd.ExecuteScalar(); object result = cmd.ExecuteScalar();
if (result == null) if (result == null)
return false; // no lastUpload entry -> DB was never uploaded return HasUploadedTodayStatus.NeverUploaded; // no lastUpload entry -> DB was never uploaded
return (long)result > 0; return (long)result > 0 ? HasUploadedTodayStatus.Yes : HasUploadedTodayStatus.No;
} }
} }
#region FetchDataForUpload #region FetchDataForUpload
UsageDataMessage FetchDataForUpload(SQLiteConnection connection, bool fetchIncompleteSessions) static UsageDataMessage FetchDataForUpload(SQLiteConnection connection, bool fetchIncompleteSessions)
{ {
UsageDataMessage message = new UsageDataMessage(); UsageDataMessage message = new UsageDataMessage();
// Retrieve the User ID // Retrieve the User ID
@ -261,7 +290,7 @@ namespace ICSharpCode.UsageDataCollector
} }
#endregion #endregion
string GetCommaSeparatedIDList(IEnumerable<UsageDataSession> sessions) static string GetCommaSeparatedIDList(IEnumerable<UsageDataSession> sessions)
{ {
return string.Join( return string.Join(
",", ",",

Loading…
Cancel
Save