From 62ecbddaa278345924ae5c0e56e403b04c2ad1b4 Mon Sep 17 00:00:00 2001 From: Thomas Nind Date: Wed, 8 Jan 2020 10:41:47 +0000 Subject: [PATCH 1/9] Added performance tracking of identifier mapper - Added abstract base - Added IDisposable TimeTracker class to simplify time tracking flow - All ISwapIdentifiers must now log (enforced with interface) --- src/common/Smi.Common/TimeTracker.cs | 33 ++++++++++ .../Execution/IdentifierMapperHost.cs | 5 +- .../Swappers/ForGuidIdentifierSwapper.cs | 61 +++++++++++-------- .../Execution/Swappers/ISwapIdentifiers.cs | 7 +++ .../Execution/Swappers/PreloadTableSwapper.cs | 53 +++++++++------- .../Execution/Swappers/SwapIdentifiers.cs | 29 +++++++++ .../Execution/Swappers/TableLookupSwapper.cs | 60 +++++++++--------- .../TableLookupWithGuidFallbackSwapper.cs | 15 +++-- .../MicroservicesIntegrationTest.cs | 10 +-- .../IdentifierMapperTests.cs | 12 ++-- .../IdentifierMapperUnitTests.cs | 30 +++++++++ .../SwapForFixedValueTester.cs | 16 +++-- 12 files changed, 233 insertions(+), 98 deletions(-) create mode 100644 src/common/Smi.Common/TimeTracker.cs create mode 100644 src/microservices/Microservices.IdentifierMapper/Execution/Swappers/SwapIdentifiers.cs create mode 100644 tests/microservices/Microservices.IdentifierMapper.Tests/IdentifierMapperUnitTests.cs diff --git a/src/common/Smi.Common/TimeTracker.cs b/src/common/Smi.Common/TimeTracker.cs new file mode 100644 index 000000000..dc6a96e26 --- /dev/null +++ b/src/common/Smi.Common/TimeTracker.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace Smi.Common +{ + /// + /// Runs a for the duration of the using statement (this class is ) + /// + public class TimeTracker:IDisposable + { + private readonly Stopwatch _sw; + + /// + /// Starts the and runs it until disposal (use this in a using statement) + /// + /// + public TimeTracker(Stopwatch sw) + { + _sw = sw; + _sw.Start(); + } + + /// + /// Stops the + /// + public void Dispose() + { + _sw.Stop(); + } + } +} diff --git a/src/microservices/Microservices.IdentifierMapper/Execution/IdentifierMapperHost.cs b/src/microservices/Microservices.IdentifierMapper/Execution/IdentifierMapperHost.cs index cd5e714bb..3735a3c08 100644 --- a/src/microservices/Microservices.IdentifierMapper/Execution/IdentifierMapperHost.cs +++ b/src/microservices/Microservices.IdentifierMapper/Execution/IdentifierMapperHost.cs @@ -93,11 +93,8 @@ public override void Stop(string reason) { } - - var asLookup = _swapper as TableLookupSwapper; - if (asLookup != null) - Logger.Debug("TableLookupSwapper: TotalSwapCount={0} TotalCachedSwapCount={1}", asLookup.TotalSwapCount, asLookup.TotalCachedSwapCount); + _swapper?.LogProgress(Logger); base.Stop(reason); } diff --git a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/ForGuidIdentifierSwapper.cs b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/ForGuidIdentifierSwapper.cs index 5c544f1c8..cc982aa51 100644 --- a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/ForGuidIdentifierSwapper.cs +++ b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/ForGuidIdentifierSwapper.cs @@ -8,6 +8,7 @@ using System.Data.Common; using System.Data.SqlClient; using System.Text; +using Smi.Common; using TypeGuesser; namespace Microservices.IdentifierMapper.Execution.Swappers @@ -16,7 +17,7 @@ namespace Microservices.IdentifierMapper.Execution.Swappers /// Connects to a (possibly empty) database containing values to swap identifiers with. If no valid replacement found for a value, /// we create a new , insert it into the database, and return it as the swapped value. Keeps a cache of swap values /// - public class ForGuidIdentifierSwapper : ISwapIdentifiers + public class ForGuidIdentifierSwapper : SwapIdentifiers { private readonly ILogger _logger; @@ -37,21 +38,23 @@ public ForGuidIdentifierSwapper() /// Connects to the specified swapping table if it exists, or creates it /// /// - public void Setup(IMappingTableOptions mappingTableOptions) + public override void Setup(IMappingTableOptions mappingTableOptions) { _options = mappingTableOptions; _table = _options.Discover(); - CreateTableIfNotExists(); + using(new TimeTracker(DatabaseStopwatch)) + CreateTableIfNotExists(); } - public string GetSubstitutionFor(string toSwap, out string reason) + public override string GetSubstitutionFor(string toSwap, out string reason) { reason = null; if (toSwap.Length > 10) { reason = "Supplied value was too long (" + toSwap.Length + ")"; + Invalid++; return null; } @@ -59,7 +62,12 @@ public string GetSubstitutionFor(string toSwap, out string reason) lock (_oCacheLock) { if (_cachedAnswers.ContainsKey(toSwap)) + { + CacheHit++; + Success++; return _cachedAnswers[toSwap]; + } + var guid = Guid.NewGuid().ToString(); @@ -100,37 +108,41 @@ where not exists(select * } - using (var con = _table.Database.Server.BeginNewTransactedConnection()) - { - DbCommand cmd = _table.Database.Server.GetCommand(insertSql, con); - - try - { - cmd.ExecuteNonQuery(); - } - catch (Exception e) + using(new TimeTracker(DatabaseStopwatch)) + using (var con = _table.Database.Server.BeginNewTransactedConnection()) { - throw new Exception("Failed to perform lookup of toSwap with SQL:" + insertSql, e); - } + DbCommand cmd = _table.Database.Server.GetCommand(insertSql, con); - //guid may not have been inserted. Just because we don't have it in our cache doesn't mean that other poeple might - //not have allocated that one at the same time. + try + { + cmd.ExecuteNonQuery(); + } + catch (Exception e) + { + Invalid++; + throw new Exception("Failed to perform lookup of toSwap with SQL:" + insertSql, e); + } - DbCommand cmd2 = _table.Database.Server.GetCommand($"SELECT {_options.ReplacementColumnName} FROM {_table.GetFullyQualifiedName()} WHERE {_options.SwapColumnName} = '{toSwap}' ",con); - var syncAnswer = (string)cmd2.ExecuteScalar(); + //guid may not have been inserted. Just because we don't have it in our cache doesn't mean that other poeple might + //not have allocated that one at the same time. - _cachedAnswers.Add(toSwap, syncAnswer); + DbCommand cmd2 = _table.Database.Server.GetCommand($"SELECT {_options.ReplacementColumnName} FROM {_table.GetFullyQualifiedName()} WHERE {_options.SwapColumnName} = '{toSwap}' ",con); + var syncAnswer = (string)cmd2.ExecuteScalar(); - con.ManagedTransaction.CommitAndCloseConnection(); - return syncAnswer; - } + _cachedAnswers.Add(toSwap, syncAnswer); + + con.ManagedTransaction.CommitAndCloseConnection(); + Success++; + CacheMiss++; + return syncAnswer; + } } } /// /// Clears the in-memory cache of swap pairs /// - public void ClearCache() + public override void ClearCache() { lock (_oCacheLock) { @@ -139,6 +151,7 @@ public void ClearCache() } } + private void CreateTableIfNotExists() { try diff --git a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/ISwapIdentifiers.cs b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/ISwapIdentifiers.cs index e06b90ffb..d4c92193b 100644 --- a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/ISwapIdentifiers.cs +++ b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/ISwapIdentifiers.cs @@ -1,4 +1,5 @@  +using NLog; using Smi.Common.Options; namespace Microservices.IdentifierMapper.Execution.Swappers @@ -23,5 +24,11 @@ public interface ISwapIdentifiers /// Clear the mapping cache (if exists) and reload /// void ClearCache(); + + /// + /// Report on the current number of swapped identifiers + /// + /// + void LogProgress(ILogger logger); } } diff --git a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/PreloadTableSwapper.cs b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/PreloadTableSwapper.cs index e9836d15f..87607515d 100644 --- a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/PreloadTableSwapper.cs +++ b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/PreloadTableSwapper.cs @@ -6,13 +6,14 @@ using System.Collections.Generic; using System.Data.Common; using System.Diagnostics; +using Smi.Common; namespace Microservices.IdentifierMapper.Execution.Swappers { /// /// Connects to a database containing values to swap identifiers with, and loads it entirely into memory /// - public class PreloadTableSwapper : ISwapIdentifiers + public class PreloadTableSwapper : SwapIdentifiers { private readonly ILogger _logger; @@ -31,61 +32,67 @@ public PreloadTableSwapper() /// Preloads the swap table into memory /// /// - public void Setup(IMappingTableOptions options) + public override void Setup(IMappingTableOptions options) { _logger.Info("Setting up mapping dictionary"); - lock (_oDictionaryLock) - { - _options = options; + using(new TimeTracker(DatabaseStopwatch)) + lock (_oDictionaryLock) + { + _options = options; - DiscoveredTable tbl = options.Discover(); + DiscoveredTable tbl = options.Discover(); - using (DbConnection con = tbl.Database.Server.GetConnection()) - { - con.Open(); + using (DbConnection con = tbl.Database.Server.GetConnection()) + { + con.Open(); - string sql = string.Format("SELECT {0}, {1} FROM {2}", options.SwapColumnName, options.ReplacementColumnName, tbl.GetFullyQualifiedName()); - _logger.Debug("SQL: " + sql); + string sql = string.Format("SELECT {0}, {1} FROM {2}", options.SwapColumnName, options.ReplacementColumnName, tbl.GetFullyQualifiedName()); + _logger.Debug("SQL: " + sql); - DbCommand cmd = tbl.Database.Server.GetCommand(sql, con); - cmd.CommandTimeout = _options.TimeoutInSeconds; + DbCommand cmd = tbl.Database.Server.GetCommand(sql, con); + cmd.CommandTimeout = _options.TimeoutInSeconds; - DbDataReader dataReader = cmd.ExecuteReader(); + DbDataReader dataReader = cmd.ExecuteReader(); - _mapping = new Dictionary(); + _mapping = new Dictionary(); - _logger.Debug("Populating dictionary from mapping table..."); - Stopwatch sw = Stopwatch.StartNew(); + _logger.Debug("Populating dictionary from mapping table..."); + Stopwatch sw = Stopwatch.StartNew(); - while (dataReader.Read()) - _mapping.Add(dataReader[_options.SwapColumnName].ToString(), dataReader[_options.ReplacementColumnName].ToString()); + while (dataReader.Read()) + _mapping.Add(dataReader[_options.SwapColumnName].ToString(), dataReader[_options.ReplacementColumnName].ToString()); - _logger.Debug("Mapping dictionary populated with " + _mapping.Count + " entries in " + sw.Elapsed.ToString("g")); + _logger.Debug("Mapping dictionary populated with " + _mapping.Count + " entries in " + sw.Elapsed.ToString("g")); + } } - } + } - public string GetSubstitutionFor(string toSwap, out string reason) + public override string GetSubstitutionFor(string toSwap, out string reason) { lock (_oDictionaryLock) { if (!_mapping.ContainsKey(toSwap)) { reason = "PatientID was not in mapping table"; + Fail++; + CacheMiss++; return null; } reason = null; } + Success++; + CacheHit++; return _mapping[toSwap]; } /// /// Clears the cached table and reloads it from the database /// - public void ClearCache() + public override void ClearCache() { _logger.Debug("Clearing cache and reloading"); diff --git a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/SwapIdentifiers.cs b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/SwapIdentifiers.cs new file mode 100644 index 000000000..8c78170d1 --- /dev/null +++ b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/SwapIdentifiers.cs @@ -0,0 +1,29 @@ +using System.Diagnostics; +using NLog; +using Smi.Common.Options; + +namespace Microservices.IdentifierMapper.Execution.Swappers +{ + public abstract class SwapIdentifiers: ISwapIdentifiers + { + public int CacheHit { get; protected set; } + public int CacheMiss { get; protected set;} + + public int Success { get; protected set;} + public int Fail { get; protected set;} + public int Invalid { get; protected set;} + + public Stopwatch DatabaseStopwatch { get; } = new Stopwatch(); + + public abstract void Setup(IMappingTableOptions mappingTableOptions); + + public abstract string GetSubstitutionFor(string toSwap, out string reason); + + public abstract void ClearCache(); + + public virtual void LogProgress(ILogger logger) + { + logger.Debug($"{GetType().Name}: CacheRatio={CacheHit}:{CacheMiss} SuccessRatio={Success}:{Fail}:{Invalid} DatabaseTime:{DatabaseStopwatch.Elapsed}"); + } + } +} \ No newline at end of file diff --git a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/TableLookupSwapper.cs b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/TableLookupSwapper.cs index 948b4b1b2..aec388bdc 100644 --- a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/TableLookupSwapper.cs +++ b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/TableLookupSwapper.cs @@ -4,17 +4,15 @@ using FAnsi.Discovery; using System; using System.Data.Common; +using Smi.Common; namespace Microservices.IdentifierMapper.Execution.Swappers { /// /// Connects to a database containing values to swap identifiers with. Keeps a single cache of the last seen value /// - public class TableLookupSwapper : ISwapIdentifiers + public class TableLookupSwapper : SwapIdentifiers { - public int TotalSwapCount { get; private set; } - public int TotalCachedSwapCount { get; private set; } - private readonly ILogger _logger = LogManager.GetCurrentClassLogger(); @@ -27,7 +25,7 @@ public class TableLookupSwapper : ISwapIdentifiers private string _lastVal; - public void Setup(IMappingTableOptions options) + public override void Setup(IMappingTableOptions options) { _options = options; _swapTable = options.Discover(); @@ -37,7 +35,7 @@ public void Setup(IMappingTableOptions options) throw new ArgumentException($"Swap table '{_swapTable.GetFullyQualifiedName()}' did not exist on server '{_server}'"); } - public string GetSubstitutionFor(string toSwap, out string reason) + public override string GetSubstitutionFor(string toSwap, out string reason) { reason = null; @@ -46,43 +44,47 @@ public string GetSubstitutionFor(string toSwap, out string reason) { _logger.Debug("Using cached swap value"); - ++TotalCachedSwapCount; - ++TotalSwapCount; + CacheHit++; + Success++; return _lastVal; } + CacheMiss++; + // Else fall through to the database lookup - using (DbConnection con = _server.GetConnection()) - { - con.Open(); + using(new TimeTracker(DatabaseStopwatch)) + using (DbConnection con = _server.GetConnection()) + { + con.Open(); - string sql = string.Format("SELECT {0} FROM {1} WHERE {2}=@val", - _options.ReplacementColumnName, - _swapTable.GetFullyQualifiedName(), - _options.SwapColumnName); + string sql = string.Format("SELECT {0} FROM {1} WHERE {2}=@val", + _options.ReplacementColumnName, + _swapTable.GetFullyQualifiedName(), + _options.SwapColumnName); - DbCommand cmd = _server.GetCommand(sql, con); - _server.AddParameterWithValueToCommand("@val", cmd, toSwap); + DbCommand cmd = _server.GetCommand(sql, con); + _server.AddParameterWithValueToCommand("@val", cmd, toSwap); - object result = cmd.ExecuteScalar(); + object result = cmd.ExecuteScalar(); - if (result == DBNull.Value || result == null) - { - reason = "No match found for '" + toSwap + "'"; - return null; - } + if (result == DBNull.Value || result == null) + { + reason = "No match found for '" + toSwap + "'"; + Fail++; + return null; + } - _lastKey = toSwap; - _lastVal = result.ToString(); + _lastKey = toSwap; + _lastVal = result.ToString(); - ++TotalSwapCount; + ++Success; - return _lastVal; - } + return _lastVal; + } } - public void ClearCache() + public override void ClearCache() { _lastVal = null; _logger.Debug("ClearCache called, single value cache cleared"); diff --git a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/TableLookupWithGuidFallbackSwapper.cs b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/TableLookupWithGuidFallbackSwapper.cs index 787d8dd8a..f7bd02457 100644 --- a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/TableLookupWithGuidFallbackSwapper.cs +++ b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/TableLookupWithGuidFallbackSwapper.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Text; using FAnsi.Discovery; +using NLog; using Smi.Common.Options; namespace Microservices.IdentifierMapper.Execution.Swappers @@ -13,7 +14,7 @@ namespace Microservices.IdentifierMapper.Execution.Swappers /// /// The Guid mapping table will be the mapping table name with the suffix (i.e. + suffix) /// - public class TableLookupWithGuidFallbackSwapper : ISwapIdentifiers + public class TableLookupWithGuidFallbackSwapper : SwapIdentifiers { /// /// Determines the name to give/expect for the substitution table when the lookup misses. The name will be @@ -37,7 +38,7 @@ public TableLookupWithGuidFallbackSwapper() } /// - public void Setup(IMappingTableOptions mappingTableOptions) + public override void Setup(IMappingTableOptions mappingTableOptions) { _tableSwapper.Setup(mappingTableOptions); @@ -68,7 +69,7 @@ public DiscoveredTable GetGuidTable(IMappingTableOptions options) /// /// /// - public string GetSubstitutionFor(string toSwap, out string reason) + public override string GetSubstitutionFor(string toSwap, out string reason) { //get answer from lookup table var answer = _tableSwapper.GetSubstitutionFor(toSwap, out reason); @@ -83,10 +84,16 @@ public string GetSubstitutionFor(string toSwap, out string reason) /// /// Calls on both wrapped swappers (guid and lookup) /// - public void ClearCache() + public override void ClearCache() { _tableSwapper.ClearCache(); _guidSwapper.ClearCache(); } + + public override void LogProgress(ILogger logger) + { + _tableSwapper.LogProgress(logger); + _guidSwapper.LogProgress(logger); + } } } diff --git a/tests/microservices/Microservices.DicomRelationalMapper.Tests/MicroservicesIntegrationTest.cs b/tests/microservices/Microservices.DicomRelationalMapper.Tests/MicroservicesIntegrationTest.cs index 353dc7082..0514f319f 100644 --- a/tests/microservices/Microservices.DicomRelationalMapper.Tests/MicroservicesIntegrationTest.cs +++ b/tests/microservices/Microservices.DicomRelationalMapper.Tests/MicroservicesIntegrationTest.cs @@ -543,7 +543,7 @@ private void DropMongoTestDb(string mongoDbHostName, int mongoDbHostPort) new MongoClient(new MongoClientSettings { Server = new MongoServerAddress(mongoDbHostName, mongoDbHostPort) }).DropDatabase(MongoTestDbName); } - private class SwapForFixedValueTester : ISwapIdentifiers + private class SwapForFixedValueTester : SwapIdentifiers { private readonly string _swapForString; @@ -554,15 +554,17 @@ public SwapForFixedValueTester(string swapForString) } - public void Setup(IMappingTableOptions mappingTableOptions) { } + public override void Setup(IMappingTableOptions mappingTableOptions) { } - public string GetSubstitutionFor(string toSwap, out string reason) + public override string GetSubstitutionFor(string toSwap, out string reason) { reason = null; + Success++; + CacheHit++; return _swapForString; } - public void ClearCache() { } + public override void ClearCache() { } } } } diff --git a/tests/microservices/Microservices.IdentifierMapper.Tests/IdentifierMapperTests.cs b/tests/microservices/Microservices.IdentifierMapper.Tests/IdentifierMapperTests.cs index 382e48d45..672d316f3 100644 --- a/tests/microservices/Microservices.IdentifierMapper.Tests/IdentifierMapperTests.cs +++ b/tests/microservices/Microservices.IdentifierMapper.Tests/IdentifierMapperTests.cs @@ -555,24 +555,24 @@ public void TestSwapCache() swapped = swapper.GetSubstitutionFor("CHI-1", out _); Assert.AreEqual("REP-1", swapped); - Assert.AreEqual(2, swapper.TotalSwapCount); - Assert.AreEqual(1, swapper.TotalCachedSwapCount); + Assert.AreEqual(2, swapper.Success); + Assert.AreEqual(1, swapper.CacheHit); swapped = swapper.GetSubstitutionFor("CHI-2", out _); Assert.AreEqual("REP-2", swapped); swapped = swapper.GetSubstitutionFor("CHI-2", out _); Assert.AreEqual("REP-2", swapped); - Assert.AreEqual(4, swapper.TotalSwapCount); - Assert.AreEqual(2, swapper.TotalCachedSwapCount); + Assert.AreEqual(4, swapper.Success); + Assert.AreEqual(2, swapper.CacheHit); // Just to make sure... swapped = swapper.GetSubstitutionFor("CHI-1", out _); Assert.AreEqual("REP-1", swapped); - Assert.AreEqual(5, swapper.TotalSwapCount); - Assert.AreEqual(2, swapper.TotalCachedSwapCount); + Assert.AreEqual(5, swapper.Success); + Assert.AreEqual(2, swapper.CacheHit); } } } diff --git a/tests/microservices/Microservices.IdentifierMapper.Tests/IdentifierMapperUnitTests.cs b/tests/microservices/Microservices.IdentifierMapper.Tests/IdentifierMapperUnitTests.cs new file mode 100644 index 000000000..901e43b6d --- /dev/null +++ b/tests/microservices/Microservices.IdentifierMapper.Tests/IdentifierMapperUnitTests.cs @@ -0,0 +1,30 @@ +using System.Linq; +using NLog; +using NLog.Targets; +using NUnit.Framework; + +namespace Microservices.IdentifierMapper.Tests +{ + public class IdentifierMapperUnitTests + { + [Test] + public void Test_IdentifierMapper_LoggingCounts() + { + MemoryTarget target = new MemoryTarget(); + target.Layout = "${message}"; + + var mapper = new SwapForFixedValueTester("fish"); + StringAssert.AreEqualIgnoringCase("fish",mapper.GetSubstitutionFor("heyyy", out _)); + + Assert.AreEqual(1,mapper.Success); + + NLog.Config.SimpleConfigurator.ConfigureForTargetLogging(target, LogLevel.Debug); + + Logger logger = LogManager.GetLogger("Example"); + + mapper.LogProgress(logger); + + StringAssert.StartsWith("SwapForFixedValueTester: CacheRatio=1:0 SuccessRatio=1:0:0",target.Logs.Single()); + } + } +} \ No newline at end of file diff --git a/tests/microservices/Microservices.IdentifierMapper.Tests/SwapForFixedValueTester.cs b/tests/microservices/Microservices.IdentifierMapper.Tests/SwapForFixedValueTester.cs index bef164a57..f1596ba47 100644 --- a/tests/microservices/Microservices.IdentifierMapper.Tests/SwapForFixedValueTester.cs +++ b/tests/microservices/Microservices.IdentifierMapper.Tests/SwapForFixedValueTester.cs @@ -1,11 +1,13 @@  +using System.Threading; using Microservices.IdentifierMapper.Execution.Swappers; +using Smi.Common; using Smi.Common.Options; namespace Microservices.IdentifierMapper.Tests { - internal class SwapForFixedValueTester : ISwapIdentifiers + internal class SwapForFixedValueTester : SwapIdentifiers { private readonly string _swapForString; @@ -16,14 +18,20 @@ public SwapForFixedValueTester(string swapForString) } - public void Setup(IMappingTableOptions mappingTableOptions) { } + public override void Setup(IMappingTableOptions mappingTableOptions) { } - public string GetSubstitutionFor(string toSwap, out string reason) + public override string GetSubstitutionFor(string toSwap, out string reason) { reason = null; + Success++; + CacheHit++; + + using(new TimeTracker(DatabaseStopwatch)) + Thread.Sleep(500); + return _swapForString; } - public void ClearCache() { } + public override void ClearCache() { } } } \ No newline at end of file From 8531659a82bd33f96026722ca843419b1eb50f9a Mon Sep 17 00:00:00 2001 From: Thomas Nind Date: Wed, 8 Jan 2020 11:04:04 +0000 Subject: [PATCH 2/9] Added LogLevel to ISwapIdentifiers.LogProgress --- .../Execution/IdentifierMapperHost.cs | 3 ++- .../Execution/Swappers/ISwapIdentifiers.cs | 3 ++- .../Execution/Swappers/SwapIdentifiers.cs | 4 ++-- .../Swappers/TableLookupWithGuidFallbackSwapper.cs | 6 +++--- .../IdentifierMapperUnitTests.cs | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/microservices/Microservices.IdentifierMapper/Execution/IdentifierMapperHost.cs b/src/microservices/Microservices.IdentifierMapper/Execution/IdentifierMapperHost.cs index 3735a3c08..085d05c6b 100644 --- a/src/microservices/Microservices.IdentifierMapper/Execution/IdentifierMapperHost.cs +++ b/src/microservices/Microservices.IdentifierMapper/Execution/IdentifierMapperHost.cs @@ -10,6 +10,7 @@ using FAnsi.Implementations.MySql; using FAnsi.Implementations.Oracle; using FAnsi.Implementations.PostgreSql; +using NLog; using RabbitMQ.Client.Exceptions; @@ -94,7 +95,7 @@ public override void Stop(string reason) } - _swapper?.LogProgress(Logger); + _swapper?.LogProgress(Logger, LogLevel.Info); base.Stop(reason); } diff --git a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/ISwapIdentifiers.cs b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/ISwapIdentifiers.cs index d4c92193b..8414f0599 100644 --- a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/ISwapIdentifiers.cs +++ b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/ISwapIdentifiers.cs @@ -29,6 +29,7 @@ public interface ISwapIdentifiers /// Report on the current number of swapped identifiers /// /// - void LogProgress(ILogger logger); + /// + void LogProgress(ILogger logger, LogLevel level); } } diff --git a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/SwapIdentifiers.cs b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/SwapIdentifiers.cs index 8c78170d1..76e8e7a76 100644 --- a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/SwapIdentifiers.cs +++ b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/SwapIdentifiers.cs @@ -21,9 +21,9 @@ public abstract class SwapIdentifiers: ISwapIdentifiers public abstract void ClearCache(); - public virtual void LogProgress(ILogger logger) + public virtual void LogProgress(ILogger logger, LogLevel level) { - logger.Debug($"{GetType().Name}: CacheRatio={CacheHit}:{CacheMiss} SuccessRatio={Success}:{Fail}:{Invalid} DatabaseTime:{DatabaseStopwatch.Elapsed}"); + logger.Log(level,$"{GetType().Name}: CacheRatio={CacheHit}:{CacheMiss} SuccessRatio={Success}:{Fail}:{Invalid} DatabaseTime:{DatabaseStopwatch.Elapsed}"); } } } \ No newline at end of file diff --git a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/TableLookupWithGuidFallbackSwapper.cs b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/TableLookupWithGuidFallbackSwapper.cs index f7bd02457..4cc0b2e16 100644 --- a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/TableLookupWithGuidFallbackSwapper.cs +++ b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/TableLookupWithGuidFallbackSwapper.cs @@ -90,10 +90,10 @@ public override void ClearCache() _guidSwapper.ClearCache(); } - public override void LogProgress(ILogger logger) + public override void LogProgress(ILogger logger, LogLevel level) { - _tableSwapper.LogProgress(logger); - _guidSwapper.LogProgress(logger); + _tableSwapper.LogProgress(logger, level); + _guidSwapper.LogProgress(logger, level); } } } diff --git a/tests/microservices/Microservices.IdentifierMapper.Tests/IdentifierMapperUnitTests.cs b/tests/microservices/Microservices.IdentifierMapper.Tests/IdentifierMapperUnitTests.cs index 901e43b6d..25699ddc0 100644 --- a/tests/microservices/Microservices.IdentifierMapper.Tests/IdentifierMapperUnitTests.cs +++ b/tests/microservices/Microservices.IdentifierMapper.Tests/IdentifierMapperUnitTests.cs @@ -22,7 +22,7 @@ public void Test_IdentifierMapper_LoggingCounts() Logger logger = LogManager.GetLogger("Example"); - mapper.LogProgress(logger); + mapper.LogProgress(logger, LogLevel.Info); StringAssert.StartsWith("SwapForFixedValueTester: CacheRatio=1:0 SuccessRatio=1:0:0",target.Logs.Single()); } From 742eae20126a52ebb4c79c97130aa08308dadd2d Mon Sep 17 00:00:00 2001 From: Thomas Nind <31306100+tznind@users.noreply.github.com> Date: Wed, 8 Jan 2020 11:53:32 +0000 Subject: [PATCH 3/9] Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48bcd20f0..3e80c09f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ... +### Added + +- Improved logging in IdentifierSwappers + + ## [1.2.2] - 2020-01-08 ### Fixed From 89b035a4f47148e86720d395593f21c275c97343 Mon Sep 17 00:00:00 2001 From: Ruairidh MacLeod <5160559+rkm@users.noreply.github.com> Date: Wed, 8 Jan 2020 12:11:53 +0000 Subject: [PATCH 4/9] RabbitMQAdapter: Ensure connections closed for any connection on startup --- src/common/Smi.Common/RabbitMQAdapter.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/common/Smi.Common/RabbitMQAdapter.cs b/src/common/Smi.Common/RabbitMQAdapter.cs index 2761b2a94..443474548 100644 --- a/src/common/Smi.Common/RabbitMQAdapter.cs +++ b/src/common/Smi.Common/RabbitMQAdapter.cs @@ -141,12 +141,13 @@ public Guid StartConsumer(ConsumerOptions consumerOptions, IConsumer consumer, b { subscription = new Subscription(model, consumerOptions.QueueName, consumerOptions.AutoAck, label); } - catch (OperationInterruptedException e) + catch (Exception e) { model.Close(200, "StartConsumer - Couldn't create subscription"); connection.Close(200, "StartConsumer - Couldn't create subscription"); - throw new ApplicationException("Error when creating subscription on queue \"" + consumerOptions.QueueName + "\"", e); + throw new ApplicationException( + "Error when creating subscription on queue \"" + consumerOptions.QueueName + "\"", e); } Guid taskId = Guid.NewGuid(); From 7ebcb43f7ac1b9a211a48ae1e32e8f8c9b8f2c05 Mon Sep 17 00:00:00 2001 From: Thomas Nind Date: Wed, 8 Jan 2020 13:09:15 +0000 Subject: [PATCH 5/9] Fixed length check in ForGuidIdentifierSwapper - Now inspects the SwapColumn length - Message for invalid lengths includes observed and maximum allowed lengths - Table columns lookup always happens even if table already existed --- CHANGELOG.md | 3 + .../Swappers/ForGuidIdentifierSwapper.cs | 23 ++++---- ...TableLookupWithGuidFallbackSwapperTests.cs | 57 +++++++++++++++++++ 3 files changed, 72 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e80c09f3..c930b03ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Improved logging in IdentifierSwappers +### Changed + +- Guid swapper no longer limits input identifiers to a maximum of 10 ## [1.2.2] - 2020-01-08 diff --git a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/ForGuidIdentifierSwapper.cs b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/ForGuidIdentifierSwapper.cs index cc982aa51..31eb6d363 100644 --- a/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/ForGuidIdentifierSwapper.cs +++ b/src/microservices/Microservices.IdentifierMapper/Execution/Swappers/ForGuidIdentifierSwapper.cs @@ -28,6 +28,7 @@ public class ForGuidIdentifierSwapper : SwapIdentifiers private readonly Dictionary _cachedAnswers = new Dictionary(); private readonly object _oCacheLock = new object(); + private int _swapColumnLength; public ForGuidIdentifierSwapper() { @@ -51,9 +52,9 @@ public override string GetSubstitutionFor(string toSwap, out string reason) { reason = null; - if (toSwap.Length > 10) + if (_swapColumnLength >0 && toSwap.Length > _swapColumnLength) { - reason = "Supplied value was too long (" + toSwap.Length + ")"; + reason = $"Supplied value was too long ({toSwap.Length}) - max allowed is ({_swapColumnLength})"; Invalid++; return null; } @@ -171,18 +172,18 @@ private void CreateTableIfNotExists() new DatabaseColumnRequest(_options.SwapColumnName, new DatabaseTypeRequest(typeof(string), 10), false){ IsPrimaryKey = true }, new DatabaseColumnRequest(_options.ReplacementColumnName,new DatabaseTypeRequest(typeof(string), 255), false)} ); + } - if (_table.Exists()) - _logger.Info("Guid mapping table exist (" + _table + ")"); - else - throw new Exception("Table creation did not result in table existing!"); + if (_table.Exists()) + _logger.Info("Guid mapping table exist (" + _table + ")"); + else + throw new Exception("Table creation did not result in table existing!"); - _logger.Info("Checking for column " + _options.SwapColumnName); - _table.DiscoverColumn(_options.SwapColumnName); + _logger.Info("Checking for column " + _options.SwapColumnName); + _swapColumnLength = _table.DiscoverColumn(_options.SwapColumnName).DataType.GetLengthIfString(); - _logger.Info("Checking for column " + _options.ReplacementColumnName); - _table.DiscoverColumn(_options.ReplacementColumnName); - } + _logger.Info("Checking for column " + _options.ReplacementColumnName); + _table.DiscoverColumn(_options.ReplacementColumnName); } catch (Exception e) { diff --git a/tests/microservices/Microservices.IdentifierMapper.Tests/TableLookupWithGuidFallbackSwapperTests.cs b/tests/microservices/Microservices.IdentifierMapper.Tests/TableLookupWithGuidFallbackSwapperTests.cs index 70fdb010d..e5e640415 100644 --- a/tests/microservices/Microservices.IdentifierMapper.Tests/TableLookupWithGuidFallbackSwapperTests.cs +++ b/tests/microservices/Microservices.IdentifierMapper.Tests/TableLookupWithGuidFallbackSwapperTests.cs @@ -8,6 +8,7 @@ using Smi.Common.Options; using Smi.Common.Tests; using Tests.Common; +using TypeGuesser; namespace Microservices.IdentifierMapper.Tests { @@ -93,5 +94,61 @@ public void Test_Cache1Hit1Miss(DatabaseType dbType) Assert.AreEqual("0B0B0B0B0B",swapper.GetSubstitutionFor("0202020202",out reason)); } + + [TestCase(DatabaseType.MySql,true)] + [TestCase(DatabaseType.MySql,false)] + [TestCase(DatabaseType.MicrosoftSQLServer,true)] + [TestCase(DatabaseType.MicrosoftSQLServer,false)] + public void Test_SwapValueTooLong(DatabaseType dbType, bool createGuidTableUpFront) + { + var db = GetCleanedServer(dbType); + + DiscoveredTable map; + + using (var dt = new DataTable()) + { + dt.Columns.Add("CHI"); + dt.Columns.Add("ECHI"); + + dt.Rows.Add("0101010101", "0A0A0A0A0A"); + map = db.CreateTable("Map",dt); + } + + using (var dt = new DataTable()) + { + dt.Columns.Add("CHI"); + dt.Columns.Add("guid"); + + } + + if(createGuidTableUpFront) + db.CreateTable("Map_guid",new DatabaseColumnRequest[] + { + new DatabaseColumnRequest("CHI",new DatabaseTypeRequest(typeof(string),30,null)), + new DatabaseColumnRequest("Guid",new DatabaseTypeRequest(typeof(string),30,null)), + }); + + + var options = new IdentifierMapperOptions() + { + MappingTableName = map.GetFullyQualifiedName(), + MappingConnectionString = db.Server.Builder.ConnectionString, + SwapColumnName = "CHI", + ReplacementColumnName = "ECHI", + MappingDatabaseType = db.Server.DatabaseType + }; + + var swapper = new TableLookupWithGuidFallbackSwapper(); + swapper.Setup(options); + + //cache hit + string answer = swapper.GetSubstitutionFor("010101010031002300020320402054240204022433040301",out string reason); + Assert.IsNull(answer); + + if(createGuidTableUpFront) + StringAssert.AreEqualIgnoringCase("Supplied value was too long (48) - max allowed is (30)",reason); + else + StringAssert.AreEqualIgnoringCase("Supplied value was too long (48) - max allowed is (10)",reason); + } } } \ No newline at end of file From 12a803ec82bc1b3fcf860e1972d3d32b94d9bec4 Mon Sep 17 00:00:00 2001 From: Thomas Nind Date: Wed, 8 Jan 2020 13:13:48 +0000 Subject: [PATCH 6/9] Typo fix - Fixed changelog typo - Gave test columns different lengths to improve test robustness --- CHANGELOG.md | 2 +- .../TableLookupWithGuidFallbackSwapperTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c930b03ae..8a85f7ba6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Changed -- Guid swapper no longer limits input identifiers to a maximum of 10 +- Guid swapper no longer limits input identifiers to a maximum of 10 characters ## [1.2.2] - 2020-01-08 diff --git a/tests/microservices/Microservices.IdentifierMapper.Tests/TableLookupWithGuidFallbackSwapperTests.cs b/tests/microservices/Microservices.IdentifierMapper.Tests/TableLookupWithGuidFallbackSwapperTests.cs index e5e640415..f95155c2c 100644 --- a/tests/microservices/Microservices.IdentifierMapper.Tests/TableLookupWithGuidFallbackSwapperTests.cs +++ b/tests/microservices/Microservices.IdentifierMapper.Tests/TableLookupWithGuidFallbackSwapperTests.cs @@ -125,7 +125,7 @@ public void Test_SwapValueTooLong(DatabaseType dbType, bool createGuidTableUpFro db.CreateTable("Map_guid",new DatabaseColumnRequest[] { new DatabaseColumnRequest("CHI",new DatabaseTypeRequest(typeof(string),30,null)), - new DatabaseColumnRequest("Guid",new DatabaseTypeRequest(typeof(string),30,null)), + new DatabaseColumnRequest("Guid",new DatabaseTypeRequest(typeof(string),36,null)), }); From bf39fece3f03cbb842aa32fc936e092ce8b237f1 Mon Sep 17 00:00:00 2001 From: Thomas Nind Date: Thu, 9 Jan 2020 11:51:41 +0000 Subject: [PATCH 7/9] MigrateRawToStaging now clears remnant STAGING tables --- CHANGELOG.md | 4 +++ ...ateRawToStagingWithSelectIntoStatements.cs | 28 ++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a85f7ba6..5b0068b87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Guid swapper no longer limits input identifiers to a maximum of 10 characters +### Fixed + +- Fixed DicomRelationalMapper not cleaning up STAGING table remnants from previously failed loads (leading to crash) + ## [1.2.2] - 2020-01-08 ### Fixed diff --git a/src/microservices/Microservices.DicomRelationalMapper/Execution/MigrateRawToStagingWithSelectIntoStatements.cs b/src/microservices/Microservices.DicomRelationalMapper/Execution/MigrateRawToStagingWithSelectIntoStatements.cs index be486a4c5..f81037417 100644 --- a/src/microservices/Microservices.DicomRelationalMapper/Execution/MigrateRawToStagingWithSelectIntoStatements.cs +++ b/src/microservices/Microservices.DicomRelationalMapper/Execution/MigrateRawToStagingWithSelectIntoStatements.cs @@ -29,13 +29,33 @@ public override ExitCodeType Run(IDataLoadJob job, GracefulCancellationToken can var configuration = job.Configuration; var namer = configuration.DatabaseNamer; - // To be on the safe side, we will create/destroy the staging tables on a per-load basis - var cloner = new DatabaseCloner(configuration); - job.CreateTablesInStage(cloner, LoadBubble.Staging); - DiscoveredServer server = job.LoadMetadata.GetDistinctLiveDatabaseServer(); server.EnableAsync(); + //Drop any STAGING tables that already exist + foreach (var table in job.RegularTablesToLoad) + { + string stagingDbName = table.GetDatabaseRuntimeName(LoadStage.AdjustStaging, namer); + string stagingTableName = table.GetRuntimeName(LoadStage.AdjustStaging, namer); + + var stagingDb = server.ExpectDatabase(stagingDbName); + var stagingTable = stagingDb.ExpectTable(stagingTableName); + + if (stagingDb.Exists()) + { + if (stagingTable.Exists()) + { + job.OnNotify(this,new NotifyEventArgs(ProgressEventType.Information,$"Dropping existing STAGING table remnant {stagingTable.GetFullyQualifiedName()}")); + stagingTable.Drop(); + } + } + } + + //Now create STAGING tables (empty) + var cloner = new DatabaseCloner(configuration); + job.CreateTablesInStage(cloner, LoadBubble.Staging); + + using (DbConnection con = server.GetConnection()) { con.Open(); From 0544b2180079a29858e0d1b1e00f53bb97465c6f Mon Sep 17 00:00:00 2001 From: Ruairidh MacLeod <5160559+rkm@users.noreply.github.com> Date: Thu, 9 Jan 2020 12:05:07 +0000 Subject: [PATCH 8/9] Subscription timeout handling (#73) * Improve handling of timeouts on connection startup * Update CHANGELOG --- CHANGELOG.md | 4 ++- src/common/Smi.Common/RabbitMQAdapter.cs | 44 ++++++++++++++++++------ 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a85f7ba6..3924b052e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] -... +### Changed + +- RabbitMQAdapter: Improve handling of timeouts on connection startup ### Added diff --git a/src/common/Smi.Common/RabbitMQAdapter.cs b/src/common/Smi.Common/RabbitMQAdapter.cs index 443474548..502cecaf0 100644 --- a/src/common/Smi.Common/RabbitMQAdapter.cs +++ b/src/common/Smi.Common/RabbitMQAdapter.cs @@ -54,6 +54,8 @@ public bool HasConsumers private const int MinRabbitServerVersionMinor = 7; private const int MinRabbitServerVersionPatch = 0; + private const int MaxSubscriptionAttempts = 5; + /// /// @@ -135,19 +137,41 @@ public Guid StartConsumer(ConsumerOptions consumerOptions, IConsumer consumer, b throw new ApplicationException("Already a consumer on queue " + consumerOptions.QueueName + " and solo consumer was specified"); } - Subscription subscription; + Subscription subscription = null; + var connected = false; + var failed = 0; - try - { - subscription = new Subscription(model, consumerOptions.QueueName, consumerOptions.AutoAck, label); - } - catch (Exception e) + while (!connected) { - model.Close(200, "StartConsumer - Couldn't create subscription"); - connection.Close(200, "StartConsumer - Couldn't create subscription"); + try + { + subscription = new Subscription(model, consumerOptions.QueueName, consumerOptions.AutoAck, label); + connected = true; + } + catch (TimeoutException) + { + if (++failed >= MaxSubscriptionAttempts) + { + _logger.Warn("Retries exceeded, throwing exception"); + throw; + } - throw new ApplicationException( - "Error when creating subscription on queue \"" + consumerOptions.QueueName + "\"", e); + _logger.Warn($"Timeout when creating Subscription, retrying in 5s..."); + Thread.Sleep(TimeSpan.FromSeconds(5)); + } + catch (OperationInterruptedException e) + { + throw new ApplicationException( + "Error when creating subscription on queue \"" + consumerOptions.QueueName + "\"", e); + } + finally + { + if (!connected) + { + model.Close(200, "StartConsumer - Couldn't create subscription"); + connection.Close(200, "StartConsumer - Couldn't create subscription"); + } + } } Guid taskId = Guid.NewGuid(); From 23863c98808a587ae7fec1f98ae6ceb02456f0b9 Mon Sep 17 00:00:00 2001 From: Thomas Nind Date: Thu, 9 Jan 2020 13:47:29 +0000 Subject: [PATCH 9/9] Bumped version to 1.2.3 --- CHANGELOG.md | 5 ++++- README.md | 2 +- src/SharedAssemblyInfo.cs | 6 +++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5cd6adbb..4b0a5af9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [1.2.3] - 2020-01-09 + ### Changed - RabbitMQAdapter: Improve handling of timeouts on connection startup @@ -114,7 +116,8 @@ First stable release after importing the repository from the private [SMIPlugin] - Anonymous `MappingTableName` must now be fully specified to pass validation (e.g. `mydb.mytbl`). Previously skipping database portion was supported. -[Unreleased]: https://github.com/SMI/SmiServices/compare/v1.2.2...develop +[Unreleased]: https://github.com/SMI/SmiServices/compare/v1.2.3...develop +[1.2.3]: https://github.com/SMI/SmiServices/compare/v1.2.2...v1.2.3 [1.2.2]: https://github.com/SMI/SmiServices/compare/v1.2.1...v1.2.2 [1.2.1]: https://github.com/SMI/SmiServices/compare/1.2.0...v1.2.1 [1.2.0]: https://github.com/SMI/SmiServices/compare/1.1.0-rc1...1.2.0 diff --git a/README.md b/README.md index 0bbc34916..c3946f94a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ![GitHub](https://img.shields.io/github/license/SMI/SmiServices) [![Total alerts](https://img.shields.io/lgtm/alerts/g/SMI/SmiServices.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/SMI/SmiServices/alerts/) -Version: `1.2.2` +Version: `1.2.3` # SMI Services diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs index a389a0afc..427027fb0 100644 --- a/src/SharedAssemblyInfo.cs +++ b/src/SharedAssemblyInfo.cs @@ -7,6 +7,6 @@ [assembly: AssemblyCulture("")] // These should be overwritten by release builds -[assembly: AssemblyVersion("1.2.2")] -[assembly: AssemblyFileVersion("1.2.2")] -[assembly: AssemblyInformationalVersion("1.2.2")] // This one can have the extra build info after it +[assembly: AssemblyVersion("1.2.3")] +[assembly: AssemblyFileVersion("1.2.3")] +[assembly: AssemblyInformationalVersion("1.2.3")] // This one can have the extra build info after it