private IReadOnlyDictionary <string, string> ValidateDefaultTags(IReadOnlyDictionary <string, string> tags) { var defaultTags = tags?.ToImmutableDictionary() ?? ImmutableDictionary <string, string> .Empty; var defaultTagBuilder = defaultTags.ToBuilder(); foreach (var key in defaultTags.Keys.ToArray()) { if (!MetricValidation.IsValidTagName(key)) { throw new Exception($"\"{key}\" is not a valid tag name."); } if (TagValueConverter != null) { defaultTagBuilder[key] = TagValueConverter(key, defaultTags[key]); } if (!MetricValidation.IsValidTagValue(defaultTags[key])) { throw new Exception($"\"{defaultTags[key]}\" is not a valid tag value."); } } return(defaultTagBuilder.ToImmutable()); }
ReadOnlyDictionary <string, string> ValidateDefaultTags(Dictionary <string, string> tags) { var defaultTags = tags == null ? new Dictionary <string, string>() : new Dictionary <string, string>(tags); foreach (var key in defaultTags.Keys.ToArray()) { if (!MetricValidation.IsValidTagName(key)) { throw new Exception($"\"{key}\" is not a valid tag name."); } if (TagValueConverter != null) { defaultTags[key] = TagValueConverter(key, defaultTags[key]); } if (!MetricValidation.IsValidTagValue(defaultTags[key])) { throw new Exception($"\"{defaultTags[key]}\" is not a valid tag value."); } } return(new ReadOnlyDictionary <string, string>(defaultTags)); }
private T GetMetricInternal <T>(string name, bool addPrefix, string unit, string description, T metric, bool mustBeNew) where T : MetricBase { if (addPrefix) { name = MetricsNamePrefix + name; } var metricType = typeof(T); if (metric == null) { // if the type has a constructor without params, then create an instance var constructor = metricType.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); if (constructor == null) { throw new ArgumentNullException(nameof(metric), metricType.FullName + " has no public default constructor. Therefore the metric parameter cannot be null."); } metric = (T)constructor.Invoke(new object[0]); } metric.Collector = this; metric.Name = name; metric.Description = description; metric.Unit = unit; metric.LoadSuffixes(); lock (_metricsLock) { if (_rootNameToInfo.TryGetValue(name, out var rmi)) { if (rmi.Type != metricType) { throw new Exception( $"Attempted to create metric name \"{name}\" with Type {metricType.FullName}. This metric name has already been bound to Type {rmi.Type.FullName}."); } if (rmi.Unit != unit) { throw new Exception( $"Cannot bind metric name \"{name}\" to unit \"{unit}\". It has already been bound to \"{rmi.Unit}\""); } } else if (_nameAndSuffixToRootName.ContainsKey(name)) { throw new Exception( $"Attempted to create metric name \"{name}\" with Type {metricType.FullName}. " + $"This metric name is already in use as a suffix of Type {_rootNameToInfo[_nameAndSuffixToRootName[name]].Type.FullName}."); } // claim all suffixes. Do this in two passes (check then add) so we don't end up in an inconsistent state. foreach (var s in metric.SuffixesArray) { var ns = name + s; // verify this is a valid metric name at all (it should be, since both parts are pre-validated, but just in case). if (!MetricValidation.IsValidMetricName(ns)) { throw new Exception($"\"{ns}\" is not a valid metric name"); } if (_nameAndSuffixToRootName.ContainsKey(ns) && _nameAndSuffixToRootName[ns] != name) { throw new Exception( $"Attempted to create metric name \"{ns}\" with Type {metricType.FullName}. " + $"This metric name is already in use as a suffix of Type {_rootNameToInfo[_nameAndSuffixToRootName[ns]].Type.FullName}."); } } foreach (var s in metric.SuffixesArray) { _nameAndSuffixToRootName[name + s] = name; } // claim the root type _rootNameToInfo[name] = new RootMetricInfo { Type = metricType, Unit = unit }; // see if this metric name and tag combination already exists var key = metric.GetMetricKey(); if (_rootNameAndTagsToMetric.ContainsKey(key)) { if (mustBeNew) { throw new Exception($"Attempted to create duplicate metric with name \"{name}\" and tags {string.Join(", ", metric.Tags.Keys)}."); } return((T)_rootNameAndTagsToMetric[key]); } // metric doesn't exist yet. _rootNameAndTagsToMetric[key] = metric; metric.IsAttached = true; _hasNewMetadata = true; var needsPreSerialize = metric.NeedsPreSerializeCalled(); if (needsPreSerialize) { _metricsNeedingPreSerialize.Add(metric); } _metrics.Add(metric); if (metric.SerializeInitialValue) { if (needsPreSerialize) { metric.PreSerializeInternal(); } foreach (var endpoint in _endpoints) { using (var batch = endpoint.Handler.BeginBatch()) { try { metric.SerializeInternal(batch, DateTime.UtcNow); } catch (Exception ex) { SendExceptionToHandler(ex); } } } } return(metric); } }
/// <summary> /// Instantiates a new collector. You should typically only instantiate one collector for the lifetime of your /// application. It will manage the serialization of metrics and sending data to metric handlers. /// </summary> /// <param name="options"> /// <see cref="MetricsCollectorOptions" /> representing the options to use for this collector. /// </param> public MetricsCollector(MetricsCollectorOptions options) { ExceptionHandler = options.ExceptionHandler ?? (_ => { }); MetricsNamePrefix = options.MetricsNamePrefix ?? ""; if (MetricsNamePrefix != "" && !MetricValidation.IsValidMetricName(MetricsNamePrefix)) { throw new Exception("\"" + MetricsNamePrefix + "\" is not a valid metric name prefix."); } _endpoints = options.Endpoints?.ToImmutableArray() ?? ImmutableArray <MetricEndpoint> .Empty; _sets = options.Sets?.ToImmutableArray() ?? ImmutableArray <IMetricSet> .Empty; ThrowOnPostFail = options.ThrowOnPostFail; ThrowOnQueueFull = options.ThrowOnQueueFull; ReportingInterval = options.SnapshotInterval; FlushInterval = options.FlushInterval; RetryInterval = options.RetryInterval; PropertyToTagName = options.PropertyToTagName; TagValueConverter = options.TagValueConverter; DefaultTags = ValidateDefaultTags(options.DefaultTags); _maxRetries = 3; _shutdownTokenSource = new CancellationTokenSource(); // initialize any metric sets foreach (var set in _sets) { set.Initialize(this); } // start continuous queue-flushing _flushTask = Task.Run( async() => { while (!_shutdownTokenSource.IsCancellationRequested) { await Task.Delay(FlushInterval); try { await FlushAsync(true); } catch (Exception ex) { SendExceptionToHandler(ex); } } }); // start reporting timer _reportingTask = Task.Run( async() => { while (!_shutdownTokenSource.IsCancellationRequested) { await Task.Delay(ReportingInterval); try { await SnapshotAsync(true); } catch (Exception ex) { SendExceptionToHandler(ex); } } }); }