Skip to content

Commit

Permalink
Fixes projectkudu#1507, analytics for siteextension operation
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiaomin Wu committed Mar 28, 2015
1 parent 1f5c428 commit d6d0d4a
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 24 deletions.
46 changes: 26 additions & 20 deletions Kudu.Core/Settings/JsonSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,26 +78,6 @@ public bool DeleteValue(string key)
return false;
}

private JObject Read()
{
// need to check file exist before aquire lock
// since aquire lock will generate lock file, and if folder not exist, will create folder
if (!FileSystemHelpers.FileExists(_path))
{
return new JObject();
}

return _lock.LockOperation(() =>
{
// opens file for FileAccess.Read but does allow other read/write (FileShare.ReadWrite).
// it is the most optimal where write is infrequent and dirty read is acceptable.
using (var reader = new JsonTextReader(new StreamReader(FileSystemHelpers.OpenFile(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))))
{
return JObject.Load(reader);
}
}, _timeout);
}

public void Save(JObject json)
{
_lock.LockOperation(() =>
Expand All @@ -117,5 +97,31 @@ public void Save(JObject json)
}
}, _timeout);
}

public override string ToString()
{
// JObject.ToString() : Returns the indented JSON for this token.
return Read().ToString(Formatting.None);
}

private JObject Read()
{
// need to check file exist before aquire lock
// since aquire lock will generate lock file, and if folder not exist, will create folder
if (!FileSystemHelpers.FileExists(_path))
{
return new JObject();
}

return _lock.LockOperation(() =>
{
// opens file for FileAccess.Read but does allow other read/write (FileShare.ReadWrite).
// it is the most optimal where write is infrequent and dirty read is acceptable.
using (var reader = new JsonTextReader(new StreamReader(FileSystemHelpers.OpenFile(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))))
{
return JObject.Load(reader);
}
}, _timeout);
}
}
}
15 changes: 12 additions & 3 deletions Kudu.Core/SiteExtensions/SiteExtensionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ private static Lazy<IEnumerable<Lazy<INuGetResourceProvider, INuGetResourceProvi
private readonly IEnvironment _environment;
private readonly IDeploymentSettingsManager _settings;
private readonly ITraceFactory _traceFactory;
private readonly IAnalytics _analytics;

private const string _settingsFileName = "SiteExtensionSettings.json";
private const string _feedUrlSetting = "feed_url";
Expand Down Expand Up @@ -104,7 +105,7 @@ private static readonly Dictionary<string, SiteExtensionInfo> _preInstalledExten
private const string _installScriptName = "install.cmd";
private const string _uninstallScriptName = "uninstall.cmd";

public SiteExtensionManager(IContinuousJobsManager continuousJobManager, ITriggeredJobsManager triggeredJobManager, IEnvironment environment, IDeploymentSettingsManager settings, ITraceFactory traceFactory, HttpContextBase context)
public SiteExtensionManager(IContinuousJobsManager continuousJobManager, ITriggeredJobsManager triggeredJobManager, IEnvironment environment, IDeploymentSettingsManager settings, ITraceFactory traceFactory, HttpContextBase context, IAnalytics analytics)
{
_rootPath = Path.Combine(environment.RootPath, "SiteExtensions");
_baseUrl = context.Request.Url == null ? String.Empty : context.Request.Url.GetLeftPart(UriPartial.Authority).TrimEnd('/');
Expand All @@ -115,6 +116,7 @@ public SiteExtensionManager(IContinuousJobsManager continuousJobManager, ITrigge
_environment = environment;
_settings = settings;
_traceFactory = traceFactory;
_analytics = analytics;
}

public async Task<IEnumerable<SiteExtensionInfo>> GetRemoteExtensions(string filter, bool allowPrereleaseVersions, string feedUrl)
Expand Down Expand Up @@ -280,10 +282,9 @@ private SiteExtensionInfo GetPreInstalledExtension(string id)
// <inheritdoc />
public async Task<SiteExtensionInfo> InstallExtension(string id, string version, string feedUrl, SiteExtensionInfo.SiteExtensionType type, ITracer tracer)
{
var installationLock = SiteExtensionInstallationLock.CreateLock(_environment.SiteExtensionSettingsPath, id);

try
{
var installationLock = SiteExtensionInstallationLock.CreateLock(_environment.SiteExtensionSettingsPath, id);
// hold on to lock till action complete (success or fail)
return await installationLock.LockOperationAsync<SiteExtensionInfo>(async () =>
{
Expand All @@ -292,6 +293,8 @@ public async Task<SiteExtensionInfo> InstallExtension(string id, string version,
}
catch (Exception ex)
{
_analytics.UnexpectedException(ex, trace: false);

// handle unexpected exception
tracer.TraceError(ex);

Expand Down Expand Up @@ -375,6 +378,8 @@ private async Task<SiteExtensionInfo> TryInstallExtension(string id, string vers
}
catch (FileNotFoundException ex)
{
_analytics.UnexpectedException(ex, trace: false);

tracer.TraceError(ex);
info = new SiteExtensionInfo();
info.Id = id;
Expand All @@ -384,6 +389,8 @@ private async Task<SiteExtensionInfo> TryInstallExtension(string id, string vers
}
catch (WebException ex)
{
_analytics.UnexpectedException(ex, trace: false);

tracer.TraceError(ex);
info = new SiteExtensionInfo();
info.Id = id;
Expand All @@ -393,6 +400,8 @@ private async Task<SiteExtensionInfo> TryInstallExtension(string id, string vers
}
catch (Exception ex)
{
_analytics.UnexpectedException(ex, trace: false);

tracer.TraceError(ex);
info = new SiteExtensionInfo();
info.Id = id;
Expand Down
11 changes: 11 additions & 0 deletions Kudu.Core/Tracing/Analytics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,17 @@ public void DeprecatedApiUsed(string route, string userAgent, string method, str
DeprecatedApiPaths[path] = path;
}

public void SiteExtensionEvent(string method, string path, string result, string deploymentDurationInMilliseconds, string Message)
{
KuduEventSource.Log.KuduSiteExtensionEvent(
_serverConfiguration.ApplicationName,
NullToEmptyString(method),
NullToEmptyString(path),
NullToEmptyString(result),
NullToEmptyString(deploymentDurationInMilliseconds),
NullToEmptyString(Message));
}

private static string NullToEmptyString(string s)
{
return s ?? String.Empty;
Expand Down
2 changes: 2 additions & 0 deletions Kudu.Core/Tracing/IAnalytics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@ public interface IAnalytics
void UnexpectedException(Exception ex, bool trace = true);

void DeprecatedApiUsed(string route, string userAgent, string method, string path);

void SiteExtensionEvent(string method, string path, string result, string deploymentDurationInMilliseconds, string Message);
}
}
9 changes: 9 additions & 0 deletions Kudu.Core/Tracing/KuduEventSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,14 @@ public void DeprecatedApiUsed(string siteName, string route, string userAgent, s
WriteEvent(65510, siteName, route, userAgent, method, path);
}
}

[Event(65511, Level = EventLevel.Informational, Message = "SiteExtension action for site {0}", Channel = EventChannel.Operational)]
public void KuduSiteExtensionEvent(string siteName, string method, string path, string result, string deploymentDurationInMilliseconds, string Message)
{
if (IsEnabled())
{
WriteEvent(65511, siteName, method, path, result, deploymentDurationInMilliseconds, Message);
}
}
}
}
37 changes: 36 additions & 1 deletion Kudu.Services/SiteExtensions/SiteExtensionController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Kudu.Contracts.Tracing;
using Kudu.Core;
using Kudu.Core.Infrastructure;
using Kudu.Core.Settings;
using Kudu.Core.SiteExtensions;
using Kudu.Core.Tracing;
using Kudu.Services.Arm;
Expand Down Expand Up @@ -202,6 +203,7 @@ public async Task<HttpResponseMessage> InstallExtensionArm(string id, ArmEntry<S
[HttpPut]
public async Task<HttpResponseMessage> InstallExtension(string id, SiteExtensionInfo requestInfo)
{
var startTime = DateTime.UtcNow;
var tracer = _traceFactory.GetTracer();
var installationLock = SiteExtensionInstallationLock.CreateLock(_environment.SiteExtensionSettingsPath, id);
if (installationLock.IsHeld)
Expand Down Expand Up @@ -260,6 +262,9 @@ public async Task<HttpResponseMessage> InstallExtension(string id, SiteExtension
finally
{
installationSignal.Set();

// will be a few millionseconds off if task finshed within 15 seconds.
LogEndEvent(id, (DateTime.UtcNow - startTime), backgroundTracer);
}
}
});
Expand All @@ -276,6 +281,7 @@ public async Task<HttpResponseMessage> InstallExtension(string id, SiteExtension
}
}

// do not log end event here, since it is not done yet
return Request.CreateResponse(HttpStatusCode.Created, ArmUtils.AddEnvelopeOnArmRequest<SiteExtensionInfo>(result, Request));
}
else
Expand All @@ -288,13 +294,16 @@ public async Task<HttpResponseMessage> InstallExtension(string id, SiteExtension
throw new HttpResponseException(Request.CreateErrorResponse(armSettings.Status, result.Comment));
}

return Request.CreateResponse(HttpStatusCode.OK, result);
var response = Request.CreateResponse(HttpStatusCode.OK, result);
LogEndEvent(id, (DateTime.UtcNow - startTime), tracer);
return response;
}
}

[HttpDelete]
public async Task<HttpResponseMessage> UninstallExtension(string id)
{
var startTime = DateTime.UtcNow;
try
{
bool isUninstalled = await _manager.UninstallExtension(id);
Expand All @@ -317,8 +326,34 @@ public async Task<HttpResponseMessage> UninstallExtension(string id)
}
catch (DirectoryNotFoundException ex)
{
_analytics.UnexpectedException(ex, false);
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, ex));
}
catch (Exception ex)
{
_analytics.UnexpectedException(ex, false);
throw ex;
}
finally
{
LogEndEvent(id, (DateTime.UtcNow - startTime), _traceFactory.GetTracer());
}
}

/// <summary>
/// Log to MDS when installation/uninstallation finsihed
/// </summary>
private void LogEndEvent(string id, TimeSpan duration, ITracer tracer)
{
SiteExtensionStatus armStatus = new SiteExtensionStatus(_environment.SiteExtensionSettingsPath, id, tracer);
string filePath = Path.Combine(_environment.RootPath, "SiteExtensions", id, "SiteExtensionSettings.json");
var jsonSetting = new JsonSettings(filePath);
_analytics.SiteExtensionEvent(
Request.Method.Method,
Request.RequestUri.AbsolutePath,
armStatus.ProvisioningState,
duration.TotalMilliseconds.ToString(),
jsonSetting.ToString());
}

private async Task<SiteExtensionInfo> InitInstallSiteExtension(string id, SiteExtensionInfo.SiteExtensionType type)
Expand Down

0 comments on commit d6d0d4a

Please sign in to comment.