private 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 (!BosunValidation.IsValidTagName(key)) { throw new Exception($"\"{key}\" is not a valid Bosun tag name."); } if (TagValueConverter != null) { defaultTags[key] = TagValueConverter(key, defaultTags[key]); } if (!BosunValidation.IsValidTagValue(defaultTags[key])) { throw new Exception($"\"{defaultTags[key]}\" is not a valid Bosun tag value."); } } return(new ReadOnlyDictionary <string, string>(defaultTags)); }
public MetricsCollector(BosunOptions options) { MetricsNamePrefix = options.MetricsNamePrefix ?? ""; if (MetricsNamePrefix != "" && !BosunValidation.IsValidMetricName(MetricsNamePrefix)) { throw new Exception("\"" + MetricsNamePrefix + "\" is not a valid metric name prefix."); } GetBosunUrl = options.GetBosunUrl; BosunUrl = GetBosunUrl == null ? options.BosunUrl : GetBosunUrl(); MaxQueueLength = options.MaxQueueLength; BatchSize = options.BatchSize; ThrowOnPostFail = options.ThrowOnPostFail; ThrowOnQueueFull = options.ThrowOnQueueFull; ReportingInterval = options.ReportingInterval; PropertyToTagName = options.PropertyToTagName; TagValueConverter = options.TagValueConverter; DefaultTags = ValidateDefaultTags(options.DefaultTags); // start continuous queue-flushing _flushTimer = new Timer(Flush, true, 1000, 1000); // start reporting timer var interval = TimeSpan.FromSeconds(ReportingInterval); _reportingTimer = new Timer(Snapshot, true, interval, interval); // metadata timer - wait 30 seconds to start (so there is some time for metrics to be delcared) if (options.MetaDataReportingInterval > 0) { _metaDataTimer = new Timer(PostMetaData, true, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(options.MetaDataReportingInterval)); } }
/// <summary> /// Instantiates a new collector (the primary class of BosunReporter). You should typically only instantiate one collector for the lifetime of your /// application. It will manage the serialization of metrics and sending data to Bosun. /// </summary> /// <param name="options"></param> /// <param name="exceptionHandler"></param> public MetricsCollector(BosunOptions options, Action <Exception> exceptionHandler) { if (exceptionHandler == null) { throw new ArgumentNullException(nameof(exceptionHandler)); } ExceptionHandler = exceptionHandler; _localMetricsQueue = new PayloadQueue(QueueType.Local); _externalCounterQueue = new PayloadQueue(QueueType.ExternalCounters); _localMetricsQueue.PayloadDropped += OnPayloadDropped; _externalCounterQueue.PayloadDropped += OnPayloadDropped; // these two setters actually update the queues themselves MaxPayloadSize = options.MaxPayloadSize; MaxPendingPayloads = options.MaxPendingPayloads; MetricsNamePrefix = options.MetricsNamePrefix ?? ""; if (MetricsNamePrefix != "" && !BosunValidation.IsValidMetricName(MetricsNamePrefix)) { throw new Exception("\"" + MetricsNamePrefix + "\" is not a valid metric name prefix."); } GetBosunUrl = options.GetBosunUrl; BosunUrl = GetBosunUrl == null ? options.BosunUrl : GetBosunUrl(); _accessToken = options.AccessToken; _getAccessToken = options.GetAccessToken; ThrowOnPostFail = options.ThrowOnPostFail; ThrowOnQueueFull = options.ThrowOnQueueFull; ReportingInterval = options.ReportingInterval; EnableExternalCounters = options.EnableExternalCounters; PropertyToTagName = options.PropertyToTagName; TagValueConverter = options.TagValueConverter; DefaultTags = ValidateDefaultTags(options.DefaultTags); // start continuous queue-flushing _flushTimer = new Timer(Flush, true, 1000, 1000); // start reporting timer var interval = TimeSpan.FromSeconds(ReportingInterval); _reportingTimer = new Timer(Snapshot, true, interval, interval); // metadata timer - wait 30 seconds to start (so there is some time for metrics to be delcared) if (options.MetaDataReportingInterval > 0) { _metaDataTimer = new Timer(PostMetaData, true, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(options.MetaDataReportingInterval)); } }
/// <summary> /// Instantiates a new collector (the primary class of BosunReporter). You should typically only instantiate one collector for the lifetime of your /// application. It will manage the serialization of metrics and sending data to Bosun. /// </summary> /// <param name="options"></param> public MetricsCollector(BosunOptions options) { ExceptionHandler = options.ExceptionHandler; if (options.SnapshotInterval < TimeSpan.FromSeconds(1)) { throw new InvalidOperationException("options.SnapshotInterval cannot be less than one second"); } _localMetricsQueue = new PayloadQueue(QueueType.Local); _externalCounterQueue = new PayloadQueue(QueueType.ExternalCounters); _localMetricsQueue.PayloadDropped += OnPayloadDropped; _externalCounterQueue.PayloadDropped += OnPayloadDropped; // these two setters actually update the queues themselves MaxPayloadSize = options.MaxPayloadSize; MaxPendingPayloads = options.MaxPendingPayloads; MetricsNamePrefix = options.MetricsNamePrefix ?? ""; if (MetricsNamePrefix != "" && !BosunValidation.IsValidMetricName(MetricsNamePrefix)) { throw new Exception("\"" + MetricsNamePrefix + "\" is not a valid metric name prefix."); } _getUrlDynamic = options.GetBosunUrl; _fixedUrl = options.BosunUrl; _accessToken = options.AccessToken; _getAccessToken = options.GetAccessToken; ThrowOnPostFail = options.ThrowOnPostFail; ThrowOnQueueFull = options.ThrowOnQueueFull; ReportingInterval = options.SnapshotInterval; EnableExternalCounters = options.EnableExternalCounters; PropertyToTagName = options.PropertyToTagName; TagValueConverter = options.TagValueConverter; DefaultTags = ValidateDefaultTags(options.DefaultTags); // start continuous queue-flushing _flushTimer = new Timer(Flush, true, 1000, 1000); // start reporting timer _reportingTimer = new Timer(Snapshot, true, ReportingInterval, ReportingInterval); }
private T GetMetricInternal <T>(string name, bool addPrefix, string unit, string description, T metric, bool mustBeNew) where T : BosunMetric { 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) { RootMetricInfo rmi; if (_rootNameToInfo.TryGetValue(name, out 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 (!BosunValidation.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 {metric.TagsJson}."); } return((T)_rootNameAndTagsToMetric[key]); } // metric doesn't exist yet. _rootNameAndTagsToMetric[key] = metric; metric.IsAttached = true; var needsPreSerialize = metric.NeedsPreSerializeCalled(); if (needsPreSerialize) { _metricsNeedingPreSerialize.Add(metric); } var isExternal = metric.IsExternalCounter(); if (isExternal) { _externalCounterMetrics.Add(metric); } else { _localMetrics.Add(metric); } if (metric.SerializeInitialValue) { MetricWriter writer = null; try { var queue = isExternal ? _externalCounterQueue : _localMetricsQueue; writer = queue.GetWriter(); if (needsPreSerialize) { metric.PreSerializeInternal(); } metric.SerializeInternal(writer, DateTime.UtcNow); } finally { writer?.EndBatch(); } } return(metric); } }