aliyun-net-sdk-dybaseapi/Dybaseapi/MNS/Runtime/Internal/Util/Metrics.cs (344 lines of code) (raw):

using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Text; using Aliyun.Acs.Dybaseapi.MNS.Util; namespace Aliyun.Acs.Dybaseapi.MNS.Runtime.Internal.Util { public class RequestMetrics { #region Private members private object metricsLock = new object(); private Stopwatch stopWatch; private Dictionary<Metric, Timing> inFlightTimings; private List<MetricError> errors = new List<MetricError>(); private long CurrentTime { get { return stopWatch.GetElapsedDateTimeTicks(); } } private void LogError_Locked(Metric metric, string messageFormat, params object[] args) { errors.Add(new MetricError(metric, messageFormat, args)); } private static void Log(StringBuilder builder, Metric metric, object metricValue) { LogHelper(builder, metric, metricValue); } private static void Log(StringBuilder builder, Metric metric, List<object> metricValues) { if (metricValues == null || metricValues.Count == 0) LogHelper(builder, metric); else LogHelper(builder, metric, metricValues.ToArray()); } private static void LogHelper(StringBuilder builder, Metric metric, params object[] metricValues) { builder.AppendFormat(CultureInfo.InvariantCulture, "{0} = ", metric); if (metricValues == null) { builder.Append(ObjectToString(metricValues)); } else { for (int i = 0; i < metricValues.Length; i++) { object metricValue = metricValues[i]; string metricValueString = ObjectToString(metricValue); if (i > 0) builder.Append(", "); builder.Append(metricValueString); } } builder.Append("; "); } private static string ObjectToString(object data) { if (data == null) return "NULL"; return data.ToString(); } #endregion #region Properties /// <summary> /// Collection of properties being tracked /// </summary> private Dictionary<Metric, List<object>> Properties { get; set; } /// <summary> /// Timings for metrics being tracked /// </summary> private Dictionary<Metric, List<Timing>> Timings { get; set; } /// <summary> /// Counters being tracked /// </summary> private Dictionary<Metric, long> Counters { get; set; } /// <summary> /// Whether metrics are enabled for the request /// </summary> public static bool IsEnabled { get; internal set; } #endregion #region Constructor /// <summary> /// Constructs an empty, disabled metrics object /// </summary> public RequestMetrics() { stopWatch = Stopwatch.StartNew(); Properties = new Dictionary<Metric, List<object>>(); Timings = new Dictionary<Metric, List<Timing>>(); Counters = new Dictionary<Metric, long>(); inFlightTimings = new Dictionary<Metric, Timing>(); } #endregion #region Internal methods /// <summary> /// Starts timing an event. Logs an exception if an event /// of the same type was started but not stopped. /// </summary> /// <param name="metric"></param> /// <returns></returns> internal void StartEvent(Metric metric) { if (IsEnabled) { lock(metricsLock) { if (inFlightTimings.ContainsKey(metric)) LogError_Locked(metric, "Starting multiple events for the same metric"); inFlightTimings[metric] = new Timing(CurrentTime); } } } /// <summary> /// Stops timing an event. Logs an exception if the event wasn't started. /// </summary> /// <param name="metric"></param> /// <returns></returns> internal void StopEvent(Metric metric) { if (IsEnabled) { Timing timing; lock(metricsLock) { if (!inFlightTimings.TryGetValue(metric, out timing)) { LogError_Locked(metric, "Trying to stop event that has not been started"); return; } inFlightTimings.Remove(metric); timing.Stop(CurrentTime); if (IsEnabled) { List<Timing> list; if (!Timings.TryGetValue(metric, out list)) { list = new List<Timing>(); Timings[metric] = list; } list.Add(timing); } } return; } else { return; } } /// <summary> /// Adds a property for a metric. If there are multiple, the /// object is added as a new item in a list. /// </summary> /// <param name="metric"></param> /// <param name="property"></param> internal void AddProperty(Metric metric, object property) { if (!IsEnabled) return; List<object> list; lock(metricsLock) { if (!Properties.TryGetValue(metric, out list)) { list = new List<object>(); Properties[metric] = list; } list.Add(property); } } /// <summary> /// Sets a counter for a specific metric. /// </summary> /// <param name="metric"></param> /// <param name="value"></param> internal void SetCounter(Metric metric, long value) { if (!IsEnabled) return; lock(metricsLock) { Counters[metric] = value; } } /// <summary> /// Increments a specific metric counter. /// If counter doesn't exist yet, it is set to 1. /// </summary> /// <param name="metric"></param> internal void IncrementCounter(Metric metric) { if (!IsEnabled) return; lock(metricsLock) { long value; if (!Counters.TryGetValue(metric, out value)) { value = 1; } else { value++; } Counters[metric] = value; } } /// <summary> /// Returns errors associated with the metric, including /// if there are still any timing events in-flight. /// </summary> /// <returns></returns> internal string GetErrors() { if (!IsEnabled) return null; using(StringWriter writer = new StringWriter(CultureInfo.InvariantCulture)) { lock(metricsLock) { if (inFlightTimings.Count > 0) { string inFlightTimingsValue = string.Join(", ", inFlightTimings.Keys.Select(k => k.ToString()).ToArray()); writer.Write("Timings are still in flight: {0}.", inFlightTimingsValue); } if (errors.Count > 0) { writer.Write("Logged {0} metrics errors: ", errors.Count); foreach (MetricError error in errors) { // skip empty errors if (error.Exception == null && string.IsNullOrEmpty(error.Message)) continue; writer.Write("{0} - {1} - ", error.Time.ToString(AliyunSDKUtils.ISO8601DateFormat, CultureInfo.InvariantCulture), error.Metric); if (!string.IsNullOrEmpty(error.Message)) { writer.Write(error.Message); writer.Write(";"); } if (error.Exception != null) { writer.Write(error.Exception); writer.Write("; "); } } } } return writer.ToString(); } } #endregion #region Overrides /// <summary> /// Returns a string representation of the current metrics. /// </summary> /// <returns></returns> public override string ToString() { if (!IsEnabled) { return "Metrics logging disabled"; } StringBuilder builder = new StringBuilder(); lock(metricsLock) { foreach (var kvp in Properties) { Metric metric = kvp.Key; List<object> values = kvp.Value; Log(builder, metric, values); } foreach (var kvp in Timings) { Metric metric = kvp.Key; List<Timing> list = kvp.Value; foreach (var timing in list) { if (timing.IsFinished) Log(builder, metric, timing.ElapsedTime); } } foreach (var kvp in Counters) { Metric metric = kvp.Key; long value = kvp.Value; Log(builder, metric, value); } } builder.Replace("\r", "\\r").Replace("\n", "\\n"); return builder.ToString(); } #endregion } // Set of predefined Metrics. internal enum Metric { // response enums ErrorCode, RequestId, BytesProcessed, Exception, RedirectLocation, RequestMarshallTime, ResponseProcessingTime, ResponseUnmarshallTime, ResponseReadTime, StatusCode, // request enums AttemptCount, CredentialsRequestTime, HttpRequestTime, ProxyHost, ProxyPort, RequestSigningTime, RetryPauseTime, StringToSign, CanonicalRequest, // overall enums AsyncCall, ClientExecuteTime, MethodName, ServiceEndpoint, ServiceName, RequestSize, } /// <summary> /// Timing information for a metric /// </summary> internal class Timing { private long startTime; private long endTime; /// <summary> /// Empty, stopped timing object /// </summary> public Timing() { startTime = endTime = 0; IsFinished = true; } /// <summary> /// Timing object in a started state /// </summary> /// <param name="currentTime"></param> public Timing(long currentTime) { startTime = currentTime; IsFinished = false; } /// <summary> /// Stops timing /// </summary> /// <param name="currentTime"></param> public void Stop(long currentTime) { endTime = currentTime; IsFinished = true; } /// <summary> /// Whether the timing has been stopped /// </summary> public bool IsFinished { get; private set; } /// <summary> /// Elapsed ticks from start to stop. /// If timing hasn't been stopped yet, returns 0. /// </summary> public long ElapsedTicks { get { return !IsFinished ? 0 : endTime - startTime; } } /// <summary> /// Elapsed time from start to stop. /// If timing hasn't been stopped yet, returns TimeSpan.Zero /// </summary> public TimeSpan ElapsedTime { get { return TimeSpan.FromTicks(ElapsedTicks); } } } /// <summary> /// Timing event, stops timing of a metric when disposed /// </summary> internal class TimingEvent : IDisposable { private Metric metric; private RequestMetrics metrics; private bool disposed; internal TimingEvent(RequestMetrics metrics, Metric metric) { this.metrics = metrics; this.metric = metric; } #region Dispose Pattern Implementation /// <summary> /// Implements the Dispose pattern /// </summary> /// <param name="disposing">Whether this object is being disposed via a call to Dispose /// or garbage collected.</param> protected virtual void Dispose(bool disposing) { if (!this.disposed) { if (disposing) { metrics.StopEvent(metric); } this.disposed = true; } } /// <summary> /// Disposes of all managed and unmanaged resources. /// </summary> public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } /// <summary> /// The destructor for the client class. /// </summary> ~TimingEvent() { this.Dispose(false); } #endregion } // Error encountered in metrics logging internal class MetricError { public Metric Metric { get; private set; } public string Message { get; private set; } public Exception Exception { get; private set; } public DateTime Time { get; private set; } public MetricError(Metric metric, string messageFormat, params object[] args) : this(metric, null, messageFormat, args) { } public MetricError(Metric metric, Exception exception, string messageFormat, params object[] args) { Time = DateTime.UtcNow; try { Message = string.Format(CultureInfo.InvariantCulture, messageFormat, args); } catch { Message = string.Format(CultureInfo.InvariantCulture, "Error message: {0}", messageFormat); } Exception = exception; Metric = metric; } } }