diff --git a/Backup/BuildMonitor.sln b/Backup/BuildMonitor.sln new file mode 100644 index 0000000..18202dc --- /dev/null +++ b/Backup/BuildMonitor.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{4FDB4354-8389-4675-ACED-6CA6DF570561}" + ProjectSection(SolutionItems) = preProject + .nuget\NuGet.exe = .nuget\NuGet.exe + .nuget\NuGet.targets = .nuget\NuGet.targets + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildMonitor", "BuildMonitor\BuildMonitor.csproj", "{700A9D28-E9DE-428B-A8F2-B40BAF4A3E87}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildMonitor.UnitTests", "BuildMonitor.UnitTests\BuildMonitor.UnitTests.csproj", "{4107248F-F2FC-4152-816B-E8633C4913F5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{68E63FC4-249A-4EFE-A8D8-5154DA8E3415}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildMonitorPackage", "BuildMonitorPackage\BuildMonitorPackage.csproj", "{F1623C5E-614B-435B-B625-AC534EF3920F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {700A9D28-E9DE-428B-A8F2-B40BAF4A3E87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {700A9D28-E9DE-428B-A8F2-B40BAF4A3E87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {700A9D28-E9DE-428B-A8F2-B40BAF4A3E87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {700A9D28-E9DE-428B-A8F2-B40BAF4A3E87}.Release|Any CPU.Build.0 = Release|Any CPU + {4107248F-F2FC-4152-816B-E8633C4913F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4107248F-F2FC-4152-816B-E8633C4913F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4107248F-F2FC-4152-816B-E8633C4913F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4107248F-F2FC-4152-816B-E8633C4913F5}.Release|Any CPU.Build.0 = Release|Any CPU + {F1623C5E-614B-435B-B625-AC534EF3920F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1623C5E-614B-435B-B625-AC534EF3920F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1623C5E-614B-435B-B625-AC534EF3920F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1623C5E-614B-435B-B625-AC534EF3920F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {4107248F-F2FC-4152-816B-E8633C4913F5} = {68E63FC4-249A-4EFE-A8D8-5154DA8E3415} + EndGlobalSection +EndGlobal diff --git a/Backup/BuildMonitorPackage/BuildMonitorPackage.cs b/Backup/BuildMonitorPackage/BuildMonitorPackage.cs new file mode 100644 index 0000000..e3bb449 --- /dev/null +++ b/Backup/BuildMonitorPackage/BuildMonitorPackage.cs @@ -0,0 +1,231 @@ +using System; +using System.ComponentModel.Design; +using System.Runtime.InteropServices; +using BuildMonitor; +using BuildMonitor.Domain; +using BuildMonitor.UI; +using EnvDTE; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Shell; +using Constants = EnvDTE.Constants; + +namespace BuildMonitorPackage +{ + [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] + [Guid(GuidList.guidBuildMonitorPackagePkgString)] + [PackageRegistration(UseManagedResourcesOnly = true)] + [ProvideAutoLoad("{f1536ef8-92ec-443c-9ed7-fdadf150da82}")] + sealed class BuildMonitorPackage : Package, IVsUpdateSolutionEvents2 + { + private DTE dte; + private readonly Monitor monitor; + private readonly DataAdjusterWithLogging dataAdjuster; + private BuildMonitor.Domain.Solution solution; + + private IVsSolutionBuildManager2 sbm; + private uint updateSolutionEventsCookie; + private OutputWindowPane outputWindowPane; + private SolutionEvents events; + private IVsSolution2 vsSolution; + + public BuildMonitorPackage() + { + Settings.CreateApplicationFolderIfNotExist(); + + var factory = new BuildFactory(); + var repository = new BuildRepository(Settings.RepositoryPath); + + monitor = new Monitor(factory, repository); + dataAdjuster = new DataAdjusterWithLogging(repository, PrintLine); + } + + protected override void Initialize() + { + base.Initialize(); + + //if invalid data, adjust it + dataAdjuster.Adjust(); + + // Get solution build manager + sbm = ServiceProvider.GlobalProvider.GetService(typeof(SVsSolutionBuildManager)) as IVsSolutionBuildManager2; + if (sbm != null) + { + sbm.AdviseUpdateSolutionEvents(this, out updateSolutionEventsCookie); + } + + // Must hold a reference to the solution events object or the events wont fire, garbage collection related + events = GetDTE().Events.SolutionEvents; + events.Opened += Solution_Opened; + + PrintLine("Build monitor initialized"); + PrintLine("Path to persist data: {0}", Settings.RepositoryPath); + + monitor.SolutionBuildFinished = b => + { + Print("[{0}] Time Elapsed: {1} \t\t", b.SessionBuildCount, b.SolutionBuildTime.ToTime()); + PrintLine("Session build time: {0}\n", b.SessionMillisecondsElapsed.ToTime()); + }; + + monitor.ProjectBuildFinished = b => PrintLine(" - {0}\t-- {1} --", b.MillisecondsElapsed.ToTime(), b.ProjectName); + } + + private void Solution_Opened() + { + solution = new BuildMonitor.Domain.Solution { Name = GetSolutionName() }; + PrintLine("\nSolution loaded: \t{0}", solution.Name); + PrintLine("{0}", 60.Times("-")); + } + + #region Print to output window pane + + private OutputWindowPane GetOutputWindowPane() + { + if (outputWindowPane == null) + { + var outputWindow = (OutputWindow)GetDTE().Windows.Item(Constants.vsWindowKindOutput).Object; + outputWindowPane = outputWindow.OutputWindowPanes.Add("Build monitor"); + } + return outputWindowPane; + } + + private void Print(string format, params object[] args) + { + GetOutputWindowPane().OutputString(string.Format(format, args)); + } + + private void PrintLine(string format, params object[] args) + { + Print(format + Environment.NewLine, args); + } + + #endregion + + #region Get objects from vs + + private DTE GetDTE() + { + if (dte == null) + { + var serviceContainer = this as IServiceContainer; + dte = serviceContainer.GetService(typeof(SDTE)) as DTE; + } + return dte; + } + + private void SetVsSolution() + { + if (vsSolution == null) + vsSolution = ServiceProvider.GlobalProvider.GetService(typeof(SVsSolution)) as IVsSolution2; + } + + private string GetSolutionName() + { + SetVsSolution(); + object solutionName; + vsSolution.GetProperty((int)__VSPROPID.VSPROPID_SolutionBaseName, out solutionName); + return (string)solutionName; + } + + private IProject GetProject(IVsHierarchy pHierProj) + { + object n; + pHierProj.GetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_Name, out n); + var name = n as string; + + Guid id; + vsSolution.GetGuidOfProject(pHierProj, out id); + + return new BuildMonitor.Domain.Project { Name = name, Id = id }; + } + + #endregion + + int IVsUpdateSolutionEvents.UpdateSolution_Begin(ref int pfCancelUpdate) + { + // This method is called when the entire solution starts to build. + monitor.SolutionBuildStart(solution); + + return VSConstants.S_OK; + } + + int IVsUpdateSolutionEvents2.UpdateProjectCfg_Begin(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, ref int pfCancel) + { + // This method is called when a specific project begins building. + var project = GetProject(pHierProj); + monitor.ProjectBuildStart(project); + + return VSConstants.S_OK; + } + + int IVsUpdateSolutionEvents2.UpdateProjectCfg_Done(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, int fSuccess, int fCancel) + { + // This method is called when a specific project finishes building. + var project = GetProject(pHierProj); + monitor.ProjectBuildStop(project); + + return VSConstants.S_OK; + } + + int IVsUpdateSolutionEvents.UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand) + { + // This method is called when the entire solution is done building. + monitor.SolutionBuildStop(); + + return VSConstants.S_OK; + } + + #region empty impl. of solution events interface + + int IVsUpdateSolutionEvents2.UpdateSolution_StartUpdate(ref int pfCancelUpdate) + { + return VSConstants.S_OK; + } + + int IVsUpdateSolutionEvents2.UpdateSolution_Cancel() + { + return VSConstants.S_OK; + } + + int IVsUpdateSolutionEvents2.OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) + { + return VSConstants.S_OK; + } + + int IVsUpdateSolutionEvents2.UpdateSolution_Begin(ref int pfCancelUpdate) + { + return VSConstants.S_OK; + } + + int IVsUpdateSolutionEvents2.UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand) + { + return VSConstants.S_OK; + } + + int IVsUpdateSolutionEvents.UpdateSolution_StartUpdate(ref int pfCancelUpdate) + { + return VSConstants.S_OK; + } + + int IVsUpdateSolutionEvents.UpdateSolution_Cancel() + { + return VSConstants.S_OK; + } + + int IVsUpdateSolutionEvents.OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) + { + return VSConstants.S_OK; + } + + #endregion + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + // Unadvise all events + if (sbm != null && updateSolutionEventsCookie != 0) + sbm.UnadviseUpdateSolutionEvents(updateSolutionEventsCookie); + } + } +} \ No newline at end of file diff --git a/Backup/BuildMonitorPackage/BuildMonitorPackage.csproj b/Backup/BuildMonitorPackage/BuildMonitorPackage.csproj new file mode 100644 index 0000000..a358092 --- /dev/null +++ b/Backup/BuildMonitorPackage/BuildMonitorPackage.csproj @@ -0,0 +1,181 @@ + + + + 12.0 + 11.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 4.0 + + + + Debug + AnyCPU + 2.0 + {F1623C5E-614B-435B-B625-AC534EF3920F} + {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + BuildMonitorPackage + BuildMonitorPackage + True + Key.snk + v4.5 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + + + + + + + + true + + + + + + + + + + + + + + + + {80CC9F66-E7D8-4DDD-85B6-D9E6CD0E93E2} + 8 + 0 + 0 + primary + False + False + + + {26AD1324-4B7C-44BC-84F8-B86AED45729F} + 10 + 0 + 0 + primary + False + False + + + {1A31287A-4D7D-413E-8E32-3B374931BD89} + 8 + 0 + 0 + primary + False + False + + + {2CE2370E-D744-4936-A090-3FFFE667B0E1} + 9 + 0 + 0 + primary + False + False + + + {1CBA492E-7263-47BB-87FE-639000619B15} + 8 + 0 + 0 + primary + False + False + + + {00020430-0000-0000-C000-000000000046} + 2 + 0 + 0 + primary + False + False + + + + + + + True + True + Resources.resx + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + true + VSPackage + + + + + Designer + + + + + + + + Always + true + + + + + + {700a9d28-e9de-428b-a8f2-b40baf4a3e87} + BuildMonitor + + + + true + + + + + \ No newline at end of file diff --git a/Backup/BuildMonitorPackage/DataAdjusterWithLogging.cs b/Backup/BuildMonitorPackage/DataAdjusterWithLogging.cs new file mode 100644 index 0000000..92a3789 --- /dev/null +++ b/Backup/BuildMonitorPackage/DataAdjusterWithLogging.cs @@ -0,0 +1,17 @@ +using BuildMonitor.Domain; +using BuildMonitor.LocalData; + +namespace BuildMonitorPackage +{ + public class DataAdjusterWithLogging : DataAdjuster + { + public delegate void LogAction(string s, params object[] args); + + public DataAdjusterWithLogging(IBuildRepository repository, LogAction log) : base(repository) + { + OnFoundInvalidData = data => log("-- Found invalid json-data in file: {0}", data.Source); + OnFixedInvalidData = () => log("-- Successfully converted invalid json data to valid"); + OnCouldNotConvertData = e => log("-- Could not convert json-data : {0}", e.Message); + } + } +} \ No newline at end of file diff --git a/Backup/BuildMonitorPackage/GlobalSuppressions.cs b/Backup/BuildMonitorPackage/GlobalSuppressions.cs new file mode 100644 index 0000000..a893f9d --- /dev/null +++ b/Backup/BuildMonitorPackage/GlobalSuppressions.cs @@ -0,0 +1,11 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. Project-level +// suppressions either have no target or are given a specific target +// and scoped to a namespace, type, member, etc. +// +// To add a suppression to this file, right-click the message in the +// Error List, point to "Suppress Message(s)", and click "In Project +// Suppression File". You do not need to add suppressions to this +// file manually. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1017:MarkAssembliesWithComVisible")] diff --git a/Backup/BuildMonitorPackage/Guids.cs b/Backup/BuildMonitorPackage/Guids.cs new file mode 100644 index 0000000..80b5b9c --- /dev/null +++ b/Backup/BuildMonitorPackage/Guids.cs @@ -0,0 +1,15 @@ +// Guids.cs +// MUST match guids.h + +using System; + +namespace BuildMonitorPackage +{ + static class GuidList + { + public const string guidBuildMonitorPackagePkgString = "d350a95e-5d09-4b5d-9075-0a5f7bb6b1dc"; + public const string guidBuildMonitorPackageCmdSetString = "ec1339d5-c4f2-44de-9908-af4b79ce6f74"; + + public static readonly Guid guidBuildMonitorPackageCmdSet = new Guid(guidBuildMonitorPackageCmdSetString); + }; +} \ No newline at end of file diff --git a/Backup/BuildMonitorPackage/Key.snk b/Backup/BuildMonitorPackage/Key.snk new file mode 100644 index 0000000..0a721d8 Binary files /dev/null and b/Backup/BuildMonitorPackage/Key.snk differ diff --git a/Backup/BuildMonitorPackage/Properties/AssemblyInfo.cs b/Backup/BuildMonitorPackage/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0f12b42 --- /dev/null +++ b/Backup/BuildMonitorPackage/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("BuildMonitorPackage")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Daniel Vinntreus")] +[assembly: AssemblyProduct("BuildMonitorPackage")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] +[assembly: CLSCompliant(false)] +[assembly: NeutralResourcesLanguage("en-US")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + + + diff --git a/Backup/BuildMonitorPackage/Resources.Designer.cs b/Backup/BuildMonitorPackage/Resources.Designer.cs new file mode 100644 index 0000000..abbe023 --- /dev/null +++ b/Backup/BuildMonitorPackage/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34003 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace BuildMonitorPackage { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("BuildMonitorPackage.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Backup/BuildMonitorPackage/Resources.resx b/Backup/BuildMonitorPackage/Resources.resx new file mode 100644 index 0000000..891c592 --- /dev/null +++ b/Backup/BuildMonitorPackage/Resources.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Backup/BuildMonitorPackage/Resources/Package.ico b/Backup/BuildMonitorPackage/Resources/Package.ico new file mode 100644 index 0000000..449296f Binary files /dev/null and b/Backup/BuildMonitorPackage/Resources/Package.ico differ diff --git a/Backup/BuildMonitorPackage/Settings.cs b/Backup/BuildMonitorPackage/Settings.cs new file mode 100644 index 0000000..372846d --- /dev/null +++ b/Backup/BuildMonitorPackage/Settings.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; + +// ReSharper disable InconsistentNaming +namespace BuildMonitorPackage +{ + public static class OptionKey + { + public const string SolutionId = "bm_solution_id"; + } + + public static class Settings + { + public static string RepositoryPath = string.Format("{0}\\{1}\\{2}", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ApplicationFolderName, JsonFileName); + + private static string ApplicationFolder = string.Format("{0}\\{1}", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ApplicationFolderName); + + private const string ApplicationFolderName = "Build Monitor"; + private const string JsonFileName = "buildtimes.json"; + + + public static void CreateApplicationFolderIfNotExist() + { + if(!Directory.Exists(ApplicationFolder)) + { + Directory.CreateDirectory(ApplicationFolder); + } + if(!File.Exists(RepositoryPath)) + { + using(var f = File.Create(RepositoryPath)){} + } + } + } +} +// ReSharper restore InconsistentNaming \ No newline at end of file diff --git a/Backup/BuildMonitorPackage/VSPackage.resx b/Backup/BuildMonitorPackage/VSPackage.resx new file mode 100644 index 0000000..bba6465 --- /dev/null +++ b/Backup/BuildMonitorPackage/VSPackage.resx @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + BuildMonitorPackage + + + Monitor builds in Visual Studio. + + + Resources\Package.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/Backup/BuildMonitorPackage/license.txt b/Backup/BuildMonitorPackage/license.txt new file mode 100644 index 0000000..426c72c --- /dev/null +++ b/Backup/BuildMonitorPackage/license.txt @@ -0,0 +1,13 @@ +Copyright 2012 Daniel Vinntreus + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/Backup/BuildMonitorPackage/source.extension.vsixmanifest b/Backup/BuildMonitorPackage/source.extension.vsixmanifest new file mode 100644 index 0000000..b232ae7 --- /dev/null +++ b/Backup/BuildMonitorPackage/source.extension.vsixmanifest @@ -0,0 +1,27 @@ + + + + + Build Monitor + Measure how long it takes to build your solution and individual projects in Visual Studio. + http://visualstudiogallery.msdn.microsoft.com/b0c87e47-f4ee-4935-9a59-f2c81ce692ab + license.txt + Build, Monitor, Build Monitor, Build times + + + + + + + + + + + + + + + + + + diff --git a/BuildMonitor.UnitTests/BuildMonitor.UnitTests.csproj b/BuildMonitor.UnitTests/BuildMonitor.UnitTests.csproj index 9c7cd44..fb9120e 100644 --- a/BuildMonitor.UnitTests/BuildMonitor.UnitTests.csproj +++ b/BuildMonitor.UnitTests/BuildMonitor.UnitTests.csproj @@ -41,6 +41,8 @@ + + diff --git a/BuildMonitor.UnitTests/LocalData/AnalyseBuildTimesTests.cs b/BuildMonitor.UnitTests/LocalData/AnalyseBuildTimesTests.cs new file mode 100644 index 0000000..d4f1429 --- /dev/null +++ b/BuildMonitor.UnitTests/LocalData/AnalyseBuildTimesTests.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using BuildMonitor.LocalData; +using System.Collections; + +namespace BuildMonitor.UnitTests.LocalData +{ + internal struct SolutionBuildTime + { + public string Solution { get; set; } + public DateTime BuildDateTime { get; set; } + public int BuildTimeInMilliseconds { get; set; } + } + + [TestFixture] + public class AnalyseBuildTimesTests + { + [Test] + public void IgnoreNullSolutionNames() + { + const string json = +@"[{ + 'Start': '2016-01-22T18:53:33.6172723+00:00', + 'Time': 2, + 'Solution': { + } +}]"; + + var buildTimes = new AnalyseBuildTimes().Calculate(json); + + Assert.AreEqual(0, buildTimes.AvailableSolutions.Count()); + } + + [Test] + public void AvailableMonths() + { + var solutionbuilds = CreateBuilds( + CreateBuild(buildDateTime: JANUARY1()) + , CreateBuild(buildDateTime: JANUARY31()) + , CreateBuild(buildDateTime: MARCH()) + ); + + var json = CreateBuildsJSON(solutionbuilds); + + var buildTimes = new AnalyseBuildTimes().Calculate(json); + + var expected = CreateSolutionMonths(CreateSolutionMonth(JANUARY1()), CreateSolutionMonth(MARCH())); + + CollectionAssert.AreEqual(expected, buildTimes.AvailableMonths); + } + + [Test] + public void AvailableSolutions() + { + const string CEDD = "Cedd"; + const string BUILDMONITOR = "BuildMonitor"; + + var solutionbuilds = CreateBuilds( + CreateBuild(solution: CEDD) + , CreateBuild(solution: BUILDMONITOR) + , CreateBuild(solution: CEDD) + ); + + var json = CreateBuildsJSON(solutionbuilds); + + var buildTimes = new AnalyseBuildTimes().Calculate(json); + + var expected = new List() { CEDD, BUILDMONITOR }; + + CollectionAssert.AreEqual(expected, buildTimes.AvailableSolutions); + } + + [TestCase(19366)] + [TestCase(1)] + [TestCase(395)] + public void OneBuildTimeTotal(int singleBuildTimeInMilliseconds) + { + var json = CreateBuildsJSON(CreateBuildJSON(CreateBuild(buildTimeInMilliseconds: singleBuildTimeInMilliseconds))); + + var buildTimes = new AnalyseBuildTimes().Calculate(json); + + Assert.That(buildTimes.Total == TimeSpan.FromMilliseconds(singleBuildTimeInMilliseconds)); + } + + [TestCase(1, 2)] + [TestCase(21341, 123642, 21389734)] + [TestCase(543, 0, 34534567)] + public void MultipleBuildsSameSolution(params int[] buildTimeInMilliseconds) + { + var json = CreateBuildsJSON(buildTimeInMilliseconds.Select(b => CreateBuild(buildTimeInMilliseconds: b))); + + var buildTimes = new AnalyseBuildTimes().Calculate(json); + + Assert.That(buildTimes.Total == TimeSpan.FromMilliseconds(buildTimeInMilliseconds.Sum())); + } + + // Add a testcasesource here if more test cases are wanted + [Test] + public void MultipleBuildsMultipleSolutions() + { + var solutionbuilds = CreateBuilds( + CreateBuild("BuildMonitor", 1) + , CreateBuild("Cedd", 2) + , CreateBuild("BuildMonitor", 982734789) + , CreateBuild("Cedd", 83468) + ); + + var json = CreateBuildsJSON(solutionbuilds); + + var buildTimes = new AnalyseBuildTimes().Calculate(json); + + foreach (var solution in solutionbuilds.Select(s => s.Solution).Distinct()) + Assert.That(buildTimes.Solution(solution) == TimeSpan.FromMilliseconds(solutionbuilds.Where(s => s.Solution == solution).Sum(s => s.BuildTimeInMilliseconds))); + } + + // Add a testcasesource here if more test cases are wanted + [Test] + public void MultipleBuildsMultipleMonthsMultipleSolutions() + { + var solutionbuilds = CreateBuilds( + CreateBuild("BuildMonitor", 1, JANUARY1()) + , CreateBuild("Cedd", 2, JANUARY31()) + , CreateBuild("BuildMonitor", 982734789, FEBRUARY()) + , CreateBuild("Cedd", 83468, MARCH()) + ); + + var json = CreateBuildsJSON(solutionbuilds); + + //act + var buildTimes = new AnalyseBuildTimes().Calculate(json); + + // assert + var solutionMonths = solutionbuilds.Select(s => new SolutionMonth(solution: s.Solution, month: s.BuildDateTime.Month, year: s.BuildDateTime.Year)).Distinct(); + foreach (var solutionMonth in solutionMonths) + Assert.That(buildTimes.SolutionMonth(solutionMonth.Solution, solutionMonth.Month, solutionMonth.Year) == TimeSpan.FromMilliseconds(solutionbuilds.Where(s => s.Solution == solutionMonth.Solution && s.BuildDateTime.Month == solutionMonth.Month && s.BuildDateTime.Year == solutionMonth.Year).Sum(s => s.BuildTimeInMilliseconds))); + } + + private static string CreateBuildsJSON(string build) + { + return CreateBuildsJSON(new List() { build }); + } + + private static string CreateBuildsJSON(IEnumerable builds) + { + return "[" + string.Join("\n,\n", builds) + "]"; + } + + private static string CreateBuildsJSON(IEnumerable solutionbuilds) + { + return CreateBuildsJSON(solutionbuilds.Select(b => CreateBuildJSON(b))); + } + + private static string CreateBuildJSON(SolutionBuildTime build) + { + return @"{ + 'Start': '" + build.BuildDateTime.ToString() + @"', + 'Time': " + build.BuildTimeInMilliseconds.ToString() + @", + 'Solution': { + 'Name': '" + build.Solution + @"' + }, + 'Projects': [{ + 'Start': '2016-01-21T18:53:33.6172723+00:00', + 'Time': 2, + 'Project': { + 'Name': 'blah', + 'Id': '700a9d28-e9de-428b-a8f2-b40baf4a3e87' + } + }, + { + 'Start': '2016-01-22T18:53:33.6172723+00:00', + 'Time': 4, + 'Project': { + 'Name': 'blah2', + 'Id': '700a9d28-e9de-428b-a8f2-b40baf4a3e88' + } + }] +}"; + } + + private static IEnumerable CreateBuilds(params SolutionBuildTime[] solutionBuildTimes) + { + return solutionBuildTimes; + } + + private static SolutionBuildTime CreateBuild(string solution = "", int buildTimeInMilliseconds = 0, DateTime buildDateTime = new DateTime()) + { + return new SolutionBuildTime() { Solution = solution, BuildTimeInMilliseconds = buildTimeInMilliseconds, BuildDateTime = buildDateTime }; + } + + private IEnumerable CreateSolutionMonths(params SolutionMonth[] solutionMonths) + { + return solutionMonths; + } + + private SolutionMonth CreateSolutionMonth(DateTime dateTime) + { + return new SolutionMonth("", dateTime.Year, dateTime.Month); + } + + private DateTime MARCH() + { + return new DateTime(2000, 3, 1); + } + + private DateTime FEBRUARY() + { + return new DateTime(2000, 2, 1); + } + + private DateTime JANUARY1() + { + return new DateTime(2000, 1, 1); + } + + private DateTime JANUARY31() + { + return new DateTime(2000, 1, 31); + } + + } +} \ No newline at end of file diff --git a/BuildMonitor.UnitTests/LocalData/BuildTimesTests.cs b/BuildMonitor.UnitTests/LocalData/BuildTimesTests.cs new file mode 100644 index 0000000..0afe816 --- /dev/null +++ b/BuildMonitor.UnitTests/LocalData/BuildTimesTests.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using BuildMonitor.LocalData; + +namespace BuildMonitor.UnitTests.LocalData +{ + [TestFixture] + public class BuildTimesTests + { + [Test] + public void ReturnZeroForMissingMonth() + { + var buildTimes = new BuildTimes(TimeSpan.FromSeconds(0), new Dictionary(), new Dictionary()); + + Assert.AreEqual(TimeSpan.FromSeconds(0), buildTimes.SolutionMonth("", 0, 0)); + } + } +} diff --git a/BuildMonitor.UnitTests/packages.config b/BuildMonitor.UnitTests/packages.config index 62adf5a..09bcfd1 100644 --- a/BuildMonitor.UnitTests/packages.config +++ b/BuildMonitor.UnitTests/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/BuildMonitor.sln b/BuildMonitor.sln index 18202dc..c1c3d94 100644 --- a/BuildMonitor.sln +++ b/BuildMonitor.sln @@ -1,18 +1,20 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 +# Visual Studio 14 +VisualStudioVersion = 14.0.23107.0 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{4FDB4354-8389-4675-ACED-6CA6DF570561}" ProjectSection(SolutionItems) = preProject .nuget\NuGet.exe = .nuget\NuGet.exe .nuget\NuGet.targets = .nuget\NuGet.targets EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{68E63FC4-249A-4EFE-A8D8-5154DA8E3415}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildMonitor", "BuildMonitor\BuildMonitor.csproj", "{700A9D28-E9DE-428B-A8F2-B40BAF4A3E87}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildMonitor.UnitTests", "BuildMonitor.UnitTests\BuildMonitor.UnitTests.csproj", "{4107248F-F2FC-4152-816B-E8633C4913F5}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{68E63FC4-249A-4EFE-A8D8-5154DA8E3415}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildMonitorPackage", "BuildMonitorPackage\BuildMonitorPackage.csproj", "{F1623C5E-614B-435B-B625-AC534EF3920F}" EndProject Global diff --git a/BuildMonitor/BuildMonitor.csproj b/BuildMonitor/BuildMonitor.csproj index f8916ed..7e3ef91 100644 --- a/BuildMonitor/BuildMonitor.csproj +++ b/BuildMonitor/BuildMonitor.csproj @@ -35,9 +35,10 @@ true - key.pfx + cedd.snk + ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll @@ -62,15 +63,22 @@ + + + + + + + diff --git a/BuildMonitor/LocalData/AnalyseBuildTimes.cs b/BuildMonitor/LocalData/AnalyseBuildTimes.cs new file mode 100644 index 0000000..30c3e5f --- /dev/null +++ b/BuildMonitor/LocalData/AnalyseBuildTimes.cs @@ -0,0 +1,53 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BuildMonitor.LocalData +{ + public class AnalyseBuildTimes + { + public IBuildTimes Calculate(string json) + { + var solutionMonths = new Dictionary(); + var solutions = new Dictionary(); + int total = 0; + + foreach (var jsonSolutionBuildTime in ReadJSON(json)) + { + UpdateSolutionMonths(jsonSolutionBuildTime, solutionMonths); + UpdateSolutions(jsonSolutionBuildTime, solutions); + total += jsonSolutionBuildTime.Time; + } + + return new BuildTimes(TimeSpan.FromMilliseconds(total), solutions, solutionMonths); + } + + private static IEnumerable ReadJSON(string json) + { + var jsonSerializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; + jsonSerializerSettings.Converters.Add(new IsoDateTimeConverter()); + var jsonSolutionBuildTimes = JsonConvert.DeserializeObject>(json, jsonSerializerSettings); + return jsonSolutionBuildTimes.Where(b => b.Name != null); + } + + private void UpdateSolutions(JSONSolutionTimes jsonSolutionBuildTime, Dictionary solutions) + { + if (solutions.ContainsKey(jsonSolutionBuildTime.Name)) + solutions[jsonSolutionBuildTime.Name] = solutions[jsonSolutionBuildTime.Name] + TimeSpan.FromMilliseconds(jsonSolutionBuildTime.Time); + else + solutions.Add(jsonSolutionBuildTime.Name, TimeSpan.FromMilliseconds(jsonSolutionBuildTime.Time)); + } + + private void UpdateSolutionMonths(JSONSolutionTimes jsonSolutionBuildTime, Dictionary solutionMonths) + { + var solutionMonth = new SolutionMonth( solution: jsonSolutionBuildTime.Name, month: jsonSolutionBuildTime.Start.Month, year: jsonSolutionBuildTime.Start.Year ); + + if (solutionMonths.ContainsKey(solutionMonth)) + solutionMonths[solutionMonth] = solutionMonths[solutionMonth] + TimeSpan.FromMilliseconds(jsonSolutionBuildTime.Time); + else + solutionMonths.Add(solutionMonth, TimeSpan.FromMilliseconds(jsonSolutionBuildTime.Time)); + } + } +} \ No newline at end of file diff --git a/BuildMonitor/LocalData/BuildTimes.cs b/BuildMonitor/LocalData/BuildTimes.cs new file mode 100644 index 0000000..26cc01c --- /dev/null +++ b/BuildMonitor/LocalData/BuildTimes.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BuildMonitor.LocalData +{ + public class BuildTimes : IBuildTimes + { + public BuildTimes(TimeSpan total, Dictionary solutions, Dictionary solutionMonths) + { + Total = total; + this.solutions = solutions; + this.solutionMonths = solutionMonths; + } + + public TimeSpan Total { get; protected set; } + + public IEnumerable AvailableMonths + { + get + { + return this.solutionMonths.Keys; + } + } + + public IEnumerable AvailableSolutions + { + get + { + return this.solutions.Keys; + } + } + + public TimeSpan SolutionMonth(string solution, int month, int year) + { + var solutionMonth = new SolutionMonth(solution: solution, month: month, year: year); + + return this.solutionMonths.ContainsKey(solutionMonth) ? this.solutionMonths[solutionMonth] : TimeSpan.FromSeconds(0); + } + + public TimeSpan Solution(string solution) + { + return this.solutions[solution]; + } + + // this is for the UI, in MVC land it would be a ViewModel, but that seems a bit excessive here. + public IEnumerable SolutionMonthTable() + { + var table = new List(); + + foreach (var solution in AvailableSolutions) + { + dynamic row = new ExpandoObject(); + var p = row as IDictionary; + p["Solution"] = solution; + foreach (var month in AvailableMonths) + { + var buildTime = SolutionMonth(solution, month.Month, month.Year); + + if (buildTime == TimeSpan.FromSeconds(0)) + p[FormatMonth(month)] = ""; + else + p[FormatMonth(month)] = $@"{buildTime:hh\:mm\:ss}"; + } + + table.Add(row); + } + + return table; + } + + private static string FormatMonth(SolutionMonth month) + { + return $"{new DateTime(2000, month.Month, 1):MMM} {month.Year}"; + } + + private Dictionary solutionMonths; + private Dictionary solutions; + } +} diff --git a/BuildMonitor/LocalData/IBuildTimes.cs b/BuildMonitor/LocalData/IBuildTimes.cs new file mode 100644 index 0000000..5d9cdad --- /dev/null +++ b/BuildMonitor/LocalData/IBuildTimes.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BuildMonitor.LocalData +{ + public interface IBuildTimes + { + IEnumerable SolutionMonthTable(); + + // these are only used in testing, but I can't think of a good way to take them out of the interface and keep the rest of the code ok. + TimeSpan Total { get; } + IEnumerable AvailableMonths { get; } + IEnumerable AvailableSolutions { get; } + TimeSpan SolutionMonth(string solution, int month, int year); + TimeSpan Solution(string solution); + } +} diff --git a/BuildMonitor/LocalData/JSONSolutionName.cs b/BuildMonitor/LocalData/JSONSolutionName.cs new file mode 100644 index 0000000..b9689ae --- /dev/null +++ b/BuildMonitor/LocalData/JSONSolutionName.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BuildMonitor.LocalData +{ + internal struct JSONSolutionName + { + public string Name; + } +} diff --git a/BuildMonitor/LocalData/JSONSolutionTimes.cs b/BuildMonitor/LocalData/JSONSolutionTimes.cs new file mode 100644 index 0000000..8a93bad --- /dev/null +++ b/BuildMonitor/LocalData/JSONSolutionTimes.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BuildMonitor.LocalData +{ + internal struct JSONSolutionTimes + { + public int Time; + public DateTime Start; + public JSONSolutionName Solution; + public string Name { get { return Solution.Name; } } + } +} diff --git a/BuildMonitor/LocalData/SolutionMonth.cs b/BuildMonitor/LocalData/SolutionMonth.cs new file mode 100644 index 0000000..e5d2c64 --- /dev/null +++ b/BuildMonitor/LocalData/SolutionMonth.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BuildMonitor.LocalData +{ + public class SolutionMonth + { + private readonly string solution; + private readonly int year; + private readonly int month; + + public SolutionMonth(string solution = "", int year = 0, int month = 0) + { + this.solution = solution ?? ""; + this.year = year; + this.month = month; + } + + public string Solution { get { return this.solution; } } + public int Year { get { return this.year; } } + public int Month { get { return this.month; } } + + public override bool Equals(object obj) + { + if (!(obj is SolutionMonth)) + return base.Equals(obj); + + var other = (obj as SolutionMonth); + + return (Solution == other.Solution && Year == other.Year && Month == other.Month); + } + + public override int GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + int hash = 17; + // Suitable nullity checks etc, of course :) + hash = hash * 23 + Solution.GetHashCode(); + hash = hash * 23 + Year.GetHashCode(); + hash = hash * 23 + Month.GetHashCode(); + return hash; + } + } + } +} diff --git a/BuildMonitor/Properties/AssemblyInfo.cs b/BuildMonitor/Properties/AssemblyInfo.cs index c6b0fac..ab4bb43 100644 --- a/BuildMonitor/Properties/AssemblyInfo.cs +++ b/BuildMonitor/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("BuildMonitor")] -[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyCopyright("Copyright © 2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyVersion("1.5.0.0")] +[assembly: AssemblyFileVersion("1.5.0.0")] diff --git a/BuildMonitor/cedd.snk b/BuildMonitor/cedd.snk new file mode 100644 index 0000000..2a530fd Binary files /dev/null and b/BuildMonitor/cedd.snk differ diff --git a/BuildMonitorPackage/AnalyseBuildTimes.xaml b/BuildMonitorPackage/AnalyseBuildTimes.xaml new file mode 100644 index 0000000..712c8e3 --- /dev/null +++ b/BuildMonitorPackage/AnalyseBuildTimes.xaml @@ -0,0 +1,14 @@ + + +