diff --git a/src/ServiceInsight.FunctionalTests/Services/FakeServiceControl.cs b/src/ServiceInsight.FunctionalTests/Services/FakeServiceControl.cs index 32ab47267..725aa7ed7 100644 --- a/src/ServiceInsight.FunctionalTests/Services/FakeServiceControl.cs +++ b/src/ServiceInsight.FunctionalTests/Services/FakeServiceControl.cs @@ -33,11 +33,6 @@ public Uri CreateServiceInsightUri(StoredMessage message) return new Uri(string.Format("si://{0}/api{1}", Address, message.GetURIQuery())); } - public bool HasSagaChanged(Guid sagaId) - { - return true; - } - public SagaData GetSagaById(Guid sagaId) { return new SagaData(); @@ -82,7 +77,7 @@ public IEnumerable<Endpoint> GetEndpoints() return Get<List<Endpoint>>("GetEndpoints"); } - public IEnumerable<KeyValuePair<string, string>> GetMessageData(Guid messageId) + public IEnumerable<KeyValuePair<string, string>> GetMessageData(SagaMessage messageId) { return new List<KeyValuePair<string, string>>(); } diff --git a/src/ServiceInsight.sln.DotSettings b/src/ServiceInsight.sln.DotSettings index 4664b5b2c..1f309a6b0 100644 --- a/src/ServiceInsight.sln.DotSettings +++ b/src/ServiceInsight.sln.DotSettings @@ -582,6 +582,7 @@ II.2.12 <HandlesEvent />
 <s:String x:Key="/Default/CodeStyle/Naming/VBNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue"><Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /></s:String> <s:String x:Key="/Default/CodeStyle/Naming/VBNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpFileLayoutPatternsUpgrade/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EJavaScript_002ECodeStyle_002ESettingsUpgrade_002EJsCodeFormatterSettingsUpgrader/@EntryIndexedValue">True</s:Boolean> diff --git a/src/ServiceInsight/Saga/SagaMessage.cs b/src/ServiceInsight/Saga/SagaMessage.cs index c48dad852..05b894709 100644 --- a/src/ServiceInsight/Saga/SagaMessage.cs +++ b/src/ServiceInsight/Saga/SagaMessage.cs @@ -92,9 +92,12 @@ public bool ShowData internal void RefreshData(IServiceControl serviceControl) { - if (Data != null) return; + if (Data != null) + { + return; + } - Data = serviceControl.GetMessageData(MessageId).Select(kvp => new SagaMessageDataItem { Key = kvp.Key, Value = kvp.Value }).ToList(); + Data = serviceControl.GetMessageData(this).Select(kvp => new SagaMessageDataItem { Key = kvp.Key, Value = kvp.Value }).ToList(); } } diff --git a/src/ServiceInsight/Saga/SagaWindowViewModel.cs b/src/ServiceInsight/Saga/SagaWindowViewModel.cs index b19fdc54c..ea51d158e 100644 --- a/src/ServiceInsight/Saga/SagaWindowViewModel.cs +++ b/src/ServiceInsight/Saga/SagaWindowViewModel.cs @@ -73,10 +73,10 @@ void RefreshMessageProperties(IEnumerable<SagaMessage> messages) public void Handle(SelectedMessageChanged @event) { var message = @event.Message; - RefreshSaga(message, a => true); + RefreshSaga(message); } - void RefreshSaga(StoredMessage message, Func<Guid, bool> HasChanged) + void RefreshSaga(StoredMessage message) { currentMessage = message; ShowSagaNotFoundWarning = false; @@ -91,10 +91,7 @@ void RefreshSaga(StoredMessage message, Func<Guid, bool> HasChanged) if (originatingSaga != null) { - if (HasChanged(originatingSaga.SagaId)) - { - RefreshSaga(originatingSaga); - } + RefreshSaga(originatingSaga); } } } @@ -184,7 +181,7 @@ public void ShowFlow() public void RefreshSaga() { - RefreshSaga(currentMessage, serviceControl.HasSagaChanged); + RefreshSaga(currentMessage); } public SagaMessage SelectedMessage diff --git a/src/ServiceInsight/ServiceControl/DefaultServiceControl.cs b/src/ServiceInsight/ServiceControl/DefaultServiceControl.cs index 379ef49b3..39dbfc435 100644 --- a/src/ServiceInsight/ServiceControl/DefaultServiceControl.cs +++ b/src/ServiceInsight/ServiceControl/DefaultServiceControl.cs @@ -2,22 +2,24 @@ { using System; using System.Collections.Generic; + using System.Collections.Specialized; using System.Linq; using System.Net; + using System.Runtime.Caching; using System.Xml; using System.Xml.Linq; using Anotar.Serilog; using Caliburn.Micro; - using Models; using Particular.ServiceInsight.Desktop.Framework.Events; using Particular.ServiceInsight.Desktop.Framework.MessageDecoders; using Particular.ServiceInsight.Desktop.Framework.Settings; + using Particular.ServiceInsight.Desktop.Models; + using Particular.ServiceInsight.Desktop.Saga; + using Particular.ServiceInsight.Desktop.Settings; using RestSharp; using RestSharp.Contrib; using RestSharp.Deserializers; - using Saga; using Serilog; - using Settings; public class DefaultServiceControl : IServiceControl { @@ -32,6 +34,7 @@ public class DefaultServiceControl : IServiceControl private const string SagaEndpoint = "sagas/{0}"; ServiceControlConnectionProvider connection; + MemoryCache cache; IEventAggregator eventAggregator; ProfilerSettings settings; @@ -43,6 +46,7 @@ public DefaultServiceControl( this.connection = connection; this.eventAggregator = eventAggregator; settings = settingsProvider.GetSettings<ProfilerSettings>(); + cache = new MemoryCache("ServiceControlReponses", new NameValueCollection(1) { { "cacheMemoryLimitMegabytes", settings.CacheSize.ToString() } }); } public bool IsAlive() @@ -52,15 +56,14 @@ public bool IsAlive() public string GetVersion() { - var request = new RestRequest(); + var request = new RestRequestWithCache(RestRequestWithCache.CacheStyle.Immutable); - LogRequest(request); - - var response = CreateClient().Execute(request); - var header = ProcessResponse(restResponse => restResponse.Headers.Single(x => x.Name == ServiceControlHeaders.ParticularVersion), response); + var header = Execute(request, restResponse => restResponse.Headers.Single(x => x.Name == ServiceControlHeaders.ParticularVersion)); if (header == null) + { return null; + } return header.Value.ToString(); } @@ -68,8 +71,8 @@ public string GetVersion() public void RetryMessage(string messageId) { var url = string.Format(RetryEndpoint, messageId); - var request = new RestRequest(url, Method.POST); - Execute(request, HasSucceeded); + var request = new RestRequestWithCache(url, Method.POST); + Execute(request, _ => true); } public Uri CreateServiceInsightUri(StoredMessage message) @@ -78,14 +81,9 @@ public Uri CreateServiceInsightUri(StoredMessage message) return new Uri(string.Format("si://{0}:{1}/api{2}", connectionUri.Host, connectionUri.Port, message.GetURIQuery())); } - public bool HasSagaChanged(Guid sagaId) - { - return HasChanged(CreateSagaRequest(sagaId)); - } - public SagaData GetSagaById(Guid sagaId) { - return GetModel<SagaData>(CreateSagaRequest(sagaId)) ?? new SagaData(); + return GetModel<SagaData>(new RestRequestWithCache(string.Format(SagaEndpoint, sagaId), RestRequestWithCache.CacheStyle.IfNotModified)) ?? new SagaData(); } public PagedResult<StoredMessage> Search(string searchQuery, int pageIndex = 1, string orderBy = null, bool ascending = false) @@ -120,7 +118,7 @@ public PagedResult<StoredMessage> GetAuditMessages(Endpoint endpoint, string sea public IEnumerable<StoredMessage> GetConversationById(string conversationId) { - var request = new RestRequest(string.Format(ConversationEndpoint, conversationId)); + var request = new RestRequestWithCache(String.Format(ConversationEndpoint, conversationId), RestRequestWithCache.CacheStyle.IfNotModified); var messages = GetModel<List<StoredMessage>>(request) ?? new List<StoredMessage>(); return messages; @@ -128,30 +126,38 @@ public IEnumerable<StoredMessage> GetConversationById(string conversationId) public IEnumerable<Endpoint> GetEndpoints() { - var request = new RestRequest(EndpointsEndpoint); + var request = new RestRequestWithCache(EndpointsEndpoint, RestRequestWithCache.CacheStyle.IfNotModified); var messages = GetModel<List<Endpoint>>(request); return messages ?? new List<Endpoint>(); } - public IEnumerable<KeyValuePair<string, string>> GetMessageData(Guid messageId) + public IEnumerable<KeyValuePair<string, string>> GetMessageData(SagaMessage message) { - var request = new RestRequest(String.Format(MessageBodyEndpoint, messageId)); + var request = new RestRequestWithCache(String.Format(MessageBodyEndpoint, message.MessageId), message.Status == MessageStatus.Successful ? RestRequestWithCache.CacheStyle.Immutable : RestRequestWithCache.CacheStyle.IfNotModified); + + var body = Execute(request, response => response.Content); + + if (body == null) + { + return Enumerable.Empty<KeyValuePair<string, string>>(); + } - return Execute(request, response => - response.Content.StartsWith("<?xml") ? - GetXmlData(response.Content) : - JsonPropertiesHelper.ProcessValues(response.Content, CleanupBodyString)) - ?? Enumerable.Empty<KeyValuePair<string, string>>(); + return body.StartsWith("<?xml") ? + GetXmlData(body) : + JsonPropertiesHelper.ProcessValues(body, CleanupBodyString); } public void LoadBody(StoredMessage message) { - var client = message.BodyUrl.StartsWith("http") ? CreateClient(message.BodyUrl) : CreateClient(); - - var request = new RestRequest(message.BodyUrl, Method.GET); + var request = new RestRequestWithCache(message.BodyUrl, message.Status == MessageStatus.Successful ? RestRequestWithCache.CacheStyle.Immutable : RestRequestWithCache.CacheStyle.IfNotModified); - message.Body = Execute(client, request, response => response.Content); + var baseUrl = message.BodyUrl; + if (!baseUrl.StartsWith("http")) + { + baseUrl = null; // We use the default + } + message.Body = Execute(request, response => response.Content, baseUrl); } void AppendSystemMessages(IRestRequest request) @@ -177,14 +183,9 @@ void AppendSearchQuery(IRestRequest request, string searchQuery) request.Resource += string.Format("search/{0}", HttpUtility.UrlEncode(searchQuery)); } - IRestClient CreateClient() - { - return CreateClient(connection.Url); - } - - IRestClient CreateClient(string url) + IRestClient CreateClient(string baseUrl = null) { - var client = new RestClient(url); + var client = new RestClient(baseUrl ?? connection.Url); var deserializer = new JsonMessageDeserializer(); var xdeserializer = new XmlDeserializer(); client.ClearHandlers(); @@ -202,96 +203,191 @@ IRestClient CreateClient(string url) return client; } - static RestRequest CreateSagaRequest(Guid sagaId) + static RestRequestWithCache CreateMessagesRequest(string endpointName = null) { - return new RestRequest(string.Format(SagaEndpoint, sagaId)); + return endpointName != null + ? new RestRequestWithCache(String.Format(EndpointMessagesEndpoint, endpointName), RestRequestWithCache.CacheStyle.IfNotModified) + : new RestRequestWithCache(MessagesEndpoint, RestRequestWithCache.CacheStyle.IfNotModified); } - static RestRequest CreateMessagesRequest(string endpointName = null) + PagedResult<T> GetPagedResult<T>(RestRequestWithCache request) where T : class, new() { - return endpointName != null ? new RestRequest(string.Format(EndpointMessagesEndpoint, endpointName)) : new RestRequest(MessagesEndpoint); + var result = Execute<PagedResult<T>, List<T>>(request, response => new PagedResult<T> + { + Result = response.Data, + TotalCount = int.Parse(response.Headers.First(x => x.Name == ServiceControlHeaders.TotalCount).Value.ToString()) + }); + + return result; } - bool HasChanged(IRestRequest request) + T GetModel<T>(RestRequestWithCache request) + where T : class, new() { - if (System.Runtime.Caching.MemoryCache.Default.Any(c => c.Key == request.Resource)) + return Execute<T, T>(request, response => response.Data); + } + + T Execute<T>(RestRequestWithCache request, Func<IRestResponse, T> selector, string baseUrl = null) + { + var cacheStyle = request.CacheSyle; + var restClient = CreateClient(baseUrl); + + switch (cacheStyle) { - var method = request.Method; - try - { - request.Method = Method.HEAD; - var response = CreateClient().Execute(request); + case RestRequestWithCache.CacheStyle.None: + break; + case RestRequestWithCache.CacheStyle.Immutable: + var item = cache.Get(CacheKey(restClient, request)); - var etag = response.Headers.FirstOrDefault(h => h.Name == "ETag"); - if (etag == null) return true; - return !System.Runtime.Caching.MemoryCache.Default.Any(c => c.Key == request.Resource && - string.Equals(((RestSharp.Parameter)c.Value).Value, etag.Value)); - } - finally - { - request.Method = method; - } - } + if (item != null) + { + return (T) item; + } - return true; - } + break; + case RestRequestWithCache.CacheStyle.IfNotModified: + var obj = cache.Get(CacheKey(restClient, request)); + + if (obj != null) + { + var tuple = (Tuple<string, T>) obj; + request.AddHeader("If-None-Match", tuple.Item1); + } + + break; + } - PagedResult<T> GetPagedResult<T>(IRestRequest request) where T : class, new() - { LogRequest(request); - var response = CreateClient().Execute<List<T>>(request); + var response = restClient.Execute(request); - if (HasSucceeded(response)) - { - LogResponse(response); - return new PagedResult<T> - { - Result = response.Data, - TotalCount = int.Parse(response.Headers.First(x => x.Name == ServiceControlHeaders.TotalCount).Value.ToString()) - }; - } - else + var data = default(T); + + switch (cacheStyle) { - LogError(response); - return new PagedResult<T>(); + case RestRequestWithCache.CacheStyle.Immutable: + if (response.StatusCode == HttpStatusCode.OK) + { + data = ProcessResponse(selector, response); + cache.Set(CacheKey(restClient, request), data, new CacheItemPolicy()); + } + break; + + case RestRequestWithCache.CacheStyle.IfNotModified: + switch (response.StatusCode) + { + case HttpStatusCode.OK: + data = ProcessResponse(selector, response); + var etag = response.Headers.FirstOrDefault(h => h.Name == "ETag"); + if (etag != null) + { + cache.Set(CacheKey(restClient, request), Tuple.Create(etag, data), new CacheItemPolicy()); + } + break; + case HttpStatusCode.NotModified: + LogResponse(response); + + var obj = cache.Get(CacheKey(restClient, request)); + + if (obj != null) + { + var tuple = (Tuple<string, T>) obj; + data = tuple.Item2; + } + break; + } + break; + default: + data = ProcessResponse(selector, response); + break; } + + return data; } - T GetModel<T>(IRestRequest request) + T Execute<T, T2>(RestRequestWithCache request, Func<IRestResponse<T2>, T> selector) where T : class, new() + where T2 : class, new() { - return Execute<T>(request, response => { CacheResponse(response); return response.Data; }); - } + var cacheStyle = request.CacheSyle; + var restClient = CreateClient(); - T Execute<T>(IRestRequest request, Func<IRestResponse, T> selector) - { - return Execute(CreateClient(), request, selector); - } + switch (cacheStyle) + { + case RestRequestWithCache.CacheStyle.None: + break; + case RestRequestWithCache.CacheStyle.Immutable: + var item = cache.Get(CacheKey(restClient, request)); - T Execute<T>(IRestClient client, IRestRequest request, Func<IRestResponse, T> selector) - { - LogRequest(request); + if (item != null) + { + return (T)item; + } - var response = client.Execute(request); - return ProcessResponse(selector, response); - } + break; + case RestRequestWithCache.CacheStyle.IfNotModified: + var obj = cache.Get(CacheKey(restClient, request)); + + if (obj != null) + { + var tuple = (Tuple<string, T>)obj; + request.AddHeader("If-None-Match", tuple.Item1); + } + + break; + } - T Execute<T>(IRestRequest request, Func<IRestResponse<T>, T> selector) - where T : class, new() - { LogRequest(request); - var response = CreateClient().Execute<T>(request); - return ProcessResponse(selector, response); - } + var response = restClient.Execute<T2>(request); - static void CacheResponse(IRestResponse response) - { - if (response.Request.Resource != null && response.Headers.Any(h => h.Name == "ETag")) + var data = default(T); + + switch (cacheStyle) { - System.Runtime.Caching.MemoryCache.Default.Add(new System.Runtime.Caching.CacheItem(response.Request.Resource, response.Headers.FirstOrDefault(h => h.Name == "ETag")), new System.Runtime.Caching.CacheItemPolicy()); + case RestRequestWithCache.CacheStyle.Immutable: + if (response.StatusCode == HttpStatusCode.OK) + { + data = ProcessResponse(selector, response); + cache.Set(CacheKey(restClient, request), data, new CacheItemPolicy()); + } + break; + + case RestRequestWithCache.CacheStyle.IfNotModified: + switch (response.StatusCode) + { + case HttpStatusCode.OK: + data = ProcessResponse(selector, response); + var etag = response.Headers.FirstOrDefault(h => h.Name == "ETag"); + if (etag != null) + { + cache.Set(CacheKey(restClient, request), Tuple.Create(etag.Value.ToString(), data), new CacheItemPolicy()); + } + break; + case HttpStatusCode.NotModified: + LogResponse(response); + + var obj = cache.Get(CacheKey(restClient, request)); + + if (obj != null) + { + var tuple = (Tuple<string, T>)obj; + data = tuple.Item2; + } + break; + } + break; + default: + data = ProcessResponse(selector, response); + break; } + + return data; + } + + static string CacheKey(IRestClient restClient, IRestRequest request) + { + return restClient.BuildUri(request).AbsoluteUri; } T ProcessResponse<T>(Func<IRestResponse, T> selector, IRestResponse response) @@ -301,25 +397,21 @@ T ProcessResponse<T>(Func<IRestResponse, T> selector, IRestResponse response) LogResponse(response); return selector(response); } - else - { - LogError(response); - return default(T); - } + + LogError(response); + return default(T); } - T ProcessResponse<T>(Func<IRestResponse<T>, T> selector, IRestResponse<T> response) + T ProcessResponse<T, T2>(Func<IRestResponse<T2>, T> selector, IRestResponse<T2> response) { if (HasSucceeded(response)) { LogResponse(response); return selector(response); } - else - { - LogError(response); - return default(T); - } + + LogError(response); + return default(T); } IEnumerable<KeyValuePair<string, string>> GetXmlData(string bodyString) @@ -351,12 +443,12 @@ static string CleanupBodyString(string bodyString) return bodyString.Replace("\u005c", string.Empty).Replace("\uFEFF", string.Empty).TrimStart("[\"".ToCharArray()).TrimEnd("]\"".ToCharArray()); } - void LogRequest(IRestRequest request) + void LogRequest(RestRequestWithCache request) { var resource = request.Resource != null ? request.Resource.TrimStart('/') : string.Empty; var url = connection.Url != null ? connection.Url.TrimEnd('/') : string.Empty; - LogTo.Information("HTTP {Method} {url:l}/{resource:l}", request.Method, url, resource); + LogTo.Information("HTTP {Method} {url:l}/{resource:l} ({CacheSyle})", request.Method, url, resource, request.CacheSyle); foreach (var parameter in request.Parameters) { @@ -395,6 +487,6 @@ static bool HasSucceeded(IRestResponse response) return SuccessCodes.Any(x => response != null && x == response.StatusCode && response.ErrorException == null); } - static IEnumerable<HttpStatusCode> SuccessCodes = new[] { HttpStatusCode.OK, HttpStatusCode.Accepted }; + static IEnumerable<HttpStatusCode> SuccessCodes = new[] { HttpStatusCode.OK, HttpStatusCode.Accepted, HttpStatusCode.NotModified }; } } \ No newline at end of file diff --git a/src/ServiceInsight/ServiceControl/IServiceControl.cs b/src/ServiceInsight/ServiceControl/IServiceControl.cs index 29aaf5d0f..ad57f6ba0 100644 --- a/src/ServiceInsight/ServiceControl/IServiceControl.cs +++ b/src/ServiceInsight/ServiceControl/IServiceControl.cs @@ -15,8 +15,6 @@ public interface IServiceControl Uri CreateServiceInsightUri(StoredMessage message); - bool HasSagaChanged(Guid sagaId); - SagaData GetSagaById(Guid sagaId); PagedResult<StoredMessage> Search(string searchQuery, int pageIndex = 1, string orderBy = null, bool ascending = false); @@ -27,7 +25,7 @@ public interface IServiceControl IEnumerable<Endpoint> GetEndpoints(); - IEnumerable<KeyValuePair<string, string>> GetMessageData(Guid messageId); + IEnumerable<KeyValuePair<string, string>> GetMessageData(SagaMessage messageId); void LoadBody(StoredMessage message); } diff --git a/src/ServiceInsight/ServiceControl/RestRequestWithCache.cs b/src/ServiceInsight/ServiceControl/RestRequestWithCache.cs new file mode 100644 index 000000000..3f4cfb03d --- /dev/null +++ b/src/ServiceInsight/ServiceControl/RestRequestWithCache.cs @@ -0,0 +1,32 @@ +namespace Particular.ServiceInsight.Desktop.ServiceControl +{ + using RestSharp; + + public class RestRequestWithCache : RestRequest + { + public RestRequestWithCache(CacheStyle cacheStyle) + { + CacheSyle = cacheStyle; + } + + public RestRequestWithCache(string resource, CacheStyle cacheStyle) + : this(resource, Method.GET, cacheStyle) + { + } + + public RestRequestWithCache(string resource, Method method, CacheStyle cacheStyle = CacheStyle.None) + : base(resource, method) + { + CacheSyle = cacheStyle; + } + + public CacheStyle CacheSyle { get; set; } + + public enum CacheStyle + { + None, + Immutable, + IfNotModified + } + } +} \ No newline at end of file diff --git a/src/ServiceInsight/ServiceInsight.csproj b/src/ServiceInsight/ServiceInsight.csproj index b52e8be4b..00871e4a5 100644 --- a/src/ServiceInsight/ServiceInsight.csproj +++ b/src/ServiceInsight/ServiceInsight.csproj @@ -250,6 +250,7 @@ <Compile Include="ExtensionMethods\ViewModelExtensions.cs" /> <Compile Include="LogWindow\LogMessage.cs" /> <Compile Include="SequenceDiagram\MessageLineConverter.cs" /> + <Compile Include="ServiceControl\RestRequestWithCache.cs" /> <Compile Include="ValueConverters\BoolToStrokeDashArrayConverter.cs" /> <Compile Include="SequenceDiagram\EndpointInfo.cs" /> <Compile Include="SequenceDiagram\EndpointPositionConverter.cs" /> diff --git a/src/ServiceInsight/Settings/ProfilerSettings.cs b/src/ServiceInsight/Settings/ProfilerSettings.cs index 80149c1eb..337f66b65 100644 --- a/src/ServiceInsight/Settings/ProfilerSettings.cs +++ b/src/ServiceInsight/Settings/ProfilerSettings.cs @@ -8,7 +8,7 @@ // Properties without a DisplayNameAttribute aren't automatically added to the options dialog. public class ProfilerSettings : PropertyChangedBase { - int autoRefresh; + int autoRefresh, cacheSize; public ProfilerSettings() { @@ -16,6 +16,15 @@ public ProfilerSettings() RecentServiceControlEntries = new ObservableCollection<string>(); } + [DefaultValue(20)] + [DisplayName("In-Memory Cache Size")] + [Description("Sets the maximum size to use for caching data in-memory in MB")] + public int CacheSize + { + get { return Math.Max(5, cacheSize); } + set { cacheSize = value; } + } + [DefaultValue(15)] [DisplayName("AutoRefresh Timer")] [Description("Auto refresh time in seconds")]