PageRenderTime 37ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/sdk/src/Core/Amazon.Runtime/Internal/Util/Metrics.cs

https://gitlab.com/CORP-RESELLER/aws-sdk-net
C# | 550 lines | 385 code | 52 blank | 113 comment | 43 complexity | 7a626f006dde8279b3844d4ed445e8da MD5 | raw file
  1. /*
  2. * Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License").
  5. * You may not use this file except in compliance with the License.
  6. * A copy of the License is located at
  7. *
  8. * http://aws.amazon.com/apache2.0
  9. *
  10. * or in the "license" file accompanying this file. This file is distributed
  11. * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
  12. * express or implied. See the License for the specific language governing
  13. * permissions and limitations under the License.
  14. */
  15. using System;
  16. using System.Collections.Generic;
  17. using System.Diagnostics;
  18. using System.Globalization;
  19. using System.IO;
  20. using System.Linq;
  21. using System.Text;
  22. using Amazon.Util;
  23. using Amazon.Runtime.Internal.Util;
  24. using ThirdParty.Json.LitJson;
  25. namespace Amazon.Runtime.Internal.Util
  26. {
  27. public class RequestMetrics : IRequestMetrics
  28. {
  29. #region Private members
  30. private object metricsLock = new object();
  31. private Stopwatch stopWatch;
  32. private Dictionary<Metric, Timing> inFlightTimings;
  33. private List<MetricError> errors = new List<MetricError>();
  34. private long CurrentTime { get { return stopWatch.GetElapsedDateTimeTicks(); } }
  35. private void LogError_Locked(Metric metric, string messageFormat, params object[] args)
  36. {
  37. errors.Add(new MetricError(metric, messageFormat, args));
  38. }
  39. private static void Log(StringBuilder builder, Metric metric, object metricValue)
  40. {
  41. LogHelper(builder, metric, metricValue);
  42. }
  43. private static void Log(StringBuilder builder, Metric metric, List<object> metricValues)
  44. {
  45. if (metricValues == null || metricValues.Count == 0)
  46. LogHelper(builder, metric);
  47. else
  48. LogHelper(builder, metric, metricValues.ToArray());
  49. }
  50. private static void LogHelper(StringBuilder builder, Metric metric, params object[] metricValues)
  51. {
  52. builder.AppendFormat(CultureInfo.InvariantCulture, "{0} = ", metric);
  53. if (metricValues == null)
  54. {
  55. builder.Append(ObjectToString(metricValues));
  56. }
  57. else
  58. {
  59. for (int i = 0; i < metricValues.Length; i++)
  60. {
  61. object metricValue = metricValues[i];
  62. string metricValueString = ObjectToString(metricValue);
  63. if (i > 0)
  64. builder.Append(", ");
  65. builder.Append(metricValueString);
  66. }
  67. }
  68. builder.Append("; ");
  69. }
  70. private static string ObjectToString(object data)
  71. {
  72. if (data == null)
  73. return "NULL";
  74. return data.ToString();
  75. }
  76. #endregion
  77. #region Properties
  78. /// <summary>
  79. /// Collection of properties being tracked
  80. /// </summary>
  81. public Dictionary<Metric, List<object>> Properties { get; set; }
  82. /// <summary>
  83. /// Timings for metrics being tracked
  84. /// </summary>
  85. public Dictionary<Metric, List<IMetricsTiming>> Timings { get; set; }
  86. /// <summary>
  87. /// Counters being tracked
  88. /// </summary>
  89. public Dictionary<Metric, long> Counters { get; set; }
  90. /// <summary>
  91. /// Whether metrics are enabled for the request
  92. /// </summary>
  93. public bool IsEnabled { get; internal set; }
  94. #endregion
  95. #region Constructor
  96. /// <summary>
  97. /// Constructs an empty, disabled metrics object
  98. /// </summary>
  99. public RequestMetrics()
  100. {
  101. stopWatch = Stopwatch.StartNew();
  102. Properties = new Dictionary<Metric, List<object>>();
  103. Timings = new Dictionary<Metric, List<IMetricsTiming>>();
  104. Counters = new Dictionary<Metric, long>();
  105. inFlightTimings = new Dictionary<Metric, Timing>();
  106. IsEnabled = false;
  107. }
  108. #endregion
  109. #region Internal methods
  110. /// <summary>
  111. /// Starts timing an event. Logs an exception if an event
  112. /// of the same type was started but not stopped.
  113. /// </summary>
  114. /// <param name="metric"></param>
  115. /// <returns></returns>
  116. public TimingEvent StartEvent(Metric metric)
  117. {
  118. lock (metricsLock)
  119. {
  120. if (inFlightTimings.ContainsKey(metric))
  121. LogError_Locked(metric, "Starting multiple events for the same metric");
  122. inFlightTimings[metric] = new Timing(CurrentTime);
  123. }
  124. return new TimingEvent(this, metric);
  125. }
  126. /// <summary>
  127. /// Stops timing an event. Logs an exception if the event wasn't started.
  128. /// </summary>
  129. /// <param name="metric"></param>
  130. /// <returns></returns>
  131. public Timing StopEvent(Metric metric)
  132. {
  133. Timing timing;
  134. lock (metricsLock)
  135. {
  136. if (!inFlightTimings.TryGetValue(metric, out timing))
  137. {
  138. LogError_Locked(metric, "Trying to stop event that has not been started");
  139. return new Timing();
  140. }
  141. inFlightTimings.Remove(metric);
  142. timing.Stop(CurrentTime);
  143. if (IsEnabled)
  144. {
  145. List<IMetricsTiming> list;
  146. if (!Timings.TryGetValue(metric, out list))
  147. {
  148. list = new List<IMetricsTiming>();
  149. Timings[metric] = list;
  150. }
  151. list.Add(timing);
  152. }
  153. }
  154. return timing;
  155. }
  156. /// <summary>
  157. /// Adds a property for a metric. If there are multiple, the
  158. /// object is added as a new item in a list.
  159. /// </summary>
  160. /// <param name="metric"></param>
  161. /// <param name="property"></param>
  162. public void AddProperty(Metric metric, object property)
  163. {
  164. if (!IsEnabled) return;
  165. List<object> list;
  166. lock (metricsLock)
  167. {
  168. if (!Properties.TryGetValue(metric, out list))
  169. {
  170. list = new List<object>();
  171. Properties[metric] = list;
  172. }
  173. list.Add(property);
  174. }
  175. }
  176. /// <summary>
  177. /// Sets a counter for a specific metric.
  178. /// </summary>
  179. /// <param name="metric"></param>
  180. /// <param name="value"></param>
  181. public void SetCounter(Metric metric, long value)
  182. {
  183. if (!IsEnabled) return;
  184. lock (metricsLock)
  185. {
  186. Counters[metric] = value;
  187. }
  188. }
  189. /// <summary>
  190. /// Increments a specific metric counter.
  191. /// If counter doesn't exist yet, it is set to 1.
  192. /// </summary>
  193. /// <param name="metric"></param>
  194. public void IncrementCounter(Metric metric)
  195. {
  196. if (!IsEnabled) return;
  197. lock (metricsLock)
  198. {
  199. long value;
  200. if (!Counters.TryGetValue(metric, out value))
  201. {
  202. value = 1;
  203. }
  204. else
  205. {
  206. value++;
  207. }
  208. Counters[metric] = value;
  209. }
  210. }
  211. /// <summary>
  212. /// Returns errors associated with the metric, including
  213. /// if there are still any timing events in-flight.
  214. /// </summary>
  215. /// <returns></returns>
  216. public string GetErrors()
  217. {
  218. using (StringWriter writer = new StringWriter(CultureInfo.InvariantCulture))
  219. {
  220. lock (metricsLock)
  221. {
  222. if (inFlightTimings.Count > 0)
  223. {
  224. string inFlightTimingsValue = string.Join(", ", inFlightTimings.Keys.Select(k => k.ToString()).ToArray());
  225. writer.Write("Timings are still in flight: {0}.", inFlightTimingsValue);
  226. }
  227. if (errors.Count > 0)
  228. {
  229. writer.Write("Logged {0} metrics errors: ", errors.Count);
  230. foreach (MetricError error in errors)
  231. {
  232. // skip empty errors
  233. if (error.Exception == null && string.IsNullOrEmpty(error.Message))
  234. continue;
  235. writer.Write("{0} - {1} - ",
  236. error.Time.ToString(AWSSDKUtils.ISO8601DateFormat, CultureInfo.InvariantCulture),
  237. error.Metric);
  238. if (!string.IsNullOrEmpty(error.Message))
  239. {
  240. writer.Write(error.Message);
  241. writer.Write(";");
  242. }
  243. if (error.Exception != null)
  244. {
  245. writer.Write(error.Exception);
  246. writer.Write("; ");
  247. }
  248. }
  249. }
  250. }
  251. return writer.ToString();
  252. }
  253. }
  254. #endregion
  255. #region Overrides
  256. /// <summary>
  257. /// Returns a string representation of the current metrics.
  258. /// </summary>
  259. /// <returns></returns>
  260. public override string ToString()
  261. {
  262. if (!IsEnabled)
  263. return "Metrics logging disabled";
  264. StringBuilder builder = new StringBuilder();
  265. // Check for a custom formatter
  266. if (AWSConfigs.LoggingConfig.LogMetricsCustomFormatter != null)
  267. {
  268. try
  269. {
  270. lock (metricsLock)
  271. {
  272. builder.Append(AWSConfigs.LoggingConfig.LogMetricsCustomFormatter.FormatMetrics(this));
  273. }
  274. return builder.ToString();
  275. }
  276. catch (Exception e)
  277. {
  278. builder.Append("[Custom metrics formatter failed: ")
  279. .Append(e.Message).Append("] ");
  280. }
  281. }
  282. // If no custom formatter, or formatter fails, check to see if JSON is configured
  283. if (AWSConfigs.LoggingConfig.LogMetricsFormat == LogMetricsFormatOption.JSON)
  284. {
  285. lock (metricsLock)
  286. {
  287. builder.Append(this.ToJSON());
  288. }
  289. return builder.ToString();
  290. }
  291. // Standard format.
  292. lock (metricsLock)
  293. {
  294. foreach (var kvp in Properties)
  295. {
  296. Metric metric = kvp.Key;
  297. List<object> values = kvp.Value;
  298. Log(builder, metric, values);
  299. }
  300. foreach (var kvp in Timings)
  301. {
  302. Metric metric = kvp.Key;
  303. List<IMetricsTiming> list = kvp.Value;
  304. foreach (var timing in list)
  305. {
  306. if (timing.IsFinished)
  307. Log(builder, metric, timing.ElapsedTime);
  308. }
  309. }
  310. foreach (var kvp in Counters)
  311. {
  312. Metric metric = kvp.Key;
  313. long value = kvp.Value;
  314. Log(builder, metric, value);
  315. }
  316. }
  317. builder.Replace("\r", "\\r").Replace("\n", "\\n");
  318. return builder.ToString();
  319. }
  320. /// <summary>
  321. /// Return a JSON represenation of the current metrics
  322. /// </summary>
  323. /// <returns></returns>
  324. public string ToJSON()
  325. {
  326. if (!this.IsEnabled)
  327. return "{ }";
  328. var sb = new StringBuilder();
  329. var jw = new JsonWriter(sb);
  330. jw.WriteObjectStart();
  331. jw.WritePropertyName("properties");
  332. jw.WriteObjectStart();
  333. foreach (var kvp in this.Properties)
  334. {
  335. jw.WritePropertyName(kvp.Key.ToString());
  336. var properties = kvp.Value;
  337. if (properties.Count > 1)
  338. jw.WriteArrayStart();
  339. foreach (var obj in properties)
  340. {
  341. if (obj == null)
  342. jw.Write(null);
  343. else
  344. jw.Write(obj.ToString());
  345. }
  346. if (properties.Count > 1)
  347. jw.WriteArrayEnd();
  348. }
  349. jw.WriteObjectEnd();
  350. jw.WritePropertyName("timings");
  351. jw.WriteObjectStart();
  352. foreach (var kvp in this.Timings)
  353. {
  354. jw.WritePropertyName(kvp.Key.ToString());
  355. var timings = kvp.Value;
  356. if (timings.Count > 1)
  357. jw.WriteArrayStart();
  358. foreach (var timing in kvp.Value)
  359. {
  360. if (timing.IsFinished)
  361. jw.Write(timing.ElapsedTime.TotalMilliseconds);
  362. }
  363. if (timings.Count > 1)
  364. jw.WriteArrayEnd();
  365. }
  366. jw.WriteObjectEnd();
  367. jw.WritePropertyName("counters");
  368. jw.WriteObjectStart();
  369. foreach (var kvp in this.Counters)
  370. {
  371. jw.WritePropertyName(kvp.Key.ToString());
  372. jw.Write(kvp.Value);
  373. }
  374. jw.WriteObjectEnd();
  375. jw.WriteObjectEnd();
  376. return sb.ToString();
  377. }
  378. #endregion
  379. }
  380. /// <summary>
  381. /// Timing information for a metric
  382. /// </summary>
  383. public class Timing : IMetricsTiming
  384. {
  385. private long startTime;
  386. private long endTime;
  387. /// <summary>
  388. /// Empty, stopped timing object
  389. /// </summary>
  390. public Timing()
  391. {
  392. startTime = endTime = 0;
  393. IsFinished = true;
  394. }
  395. /// <summary>
  396. /// Timing object in a started state
  397. /// </summary>
  398. /// <param name="currentTime"></param>
  399. public Timing(long currentTime)
  400. {
  401. startTime = currentTime;
  402. IsFinished = false;
  403. }
  404. /// <summary>
  405. /// Stops timing
  406. /// </summary>
  407. /// <param name="currentTime"></param>
  408. public void Stop(long currentTime)
  409. {
  410. endTime = currentTime;
  411. IsFinished = true;
  412. }
  413. /// <summary>
  414. /// Whether the timing has been stopped
  415. /// </summary>
  416. public bool IsFinished { get; private set; }
  417. /// <summary>
  418. /// Elapsed ticks from start to stop.
  419. /// If timing hasn't been stopped yet, returns 0.
  420. /// </summary>
  421. public long ElapsedTicks { get { return !IsFinished ? 0 : endTime - startTime; } }
  422. /// <summary>
  423. /// Elapsed time from start to stop.
  424. /// If timing hasn't been stopped yet, returns TimeSpan.Zero
  425. /// </summary>
  426. public TimeSpan ElapsedTime { get { return TimeSpan.FromTicks(ElapsedTicks); } }
  427. }
  428. /// <summary>
  429. /// Timing event, stops timing of a metric when disposed
  430. /// </summary>
  431. public class TimingEvent : IDisposable
  432. {
  433. private Metric metric;
  434. private RequestMetrics metrics;
  435. private bool disposed;
  436. internal TimingEvent(RequestMetrics metrics, Metric metric)
  437. {
  438. this.metrics = metrics;
  439. this.metric = metric;
  440. }
  441. #region Dispose Pattern Implementation
  442. /// <summary>
  443. /// Implements the Dispose pattern
  444. /// </summary>
  445. /// <param name="disposing">Whether this object is being disposed via a call to Dispose
  446. /// or garbage collected.</param>
  447. protected virtual void Dispose(bool disposing)
  448. {
  449. if (!this.disposed)
  450. {
  451. if (disposing)
  452. {
  453. metrics.StopEvent(metric);
  454. }
  455. this.disposed = true;
  456. }
  457. }
  458. /// <summary>
  459. /// Disposes of all managed and unmanaged resources.
  460. /// </summary>
  461. public void Dispose()
  462. {
  463. this.Dispose(true);
  464. GC.SuppressFinalize(this);
  465. }
  466. /// <summary>
  467. /// The destructor for the client class.
  468. /// </summary>
  469. ~TimingEvent()
  470. {
  471. this.Dispose(false);
  472. }
  473. #endregion
  474. }
  475. // Error encountered in metrics logging
  476. public class MetricError
  477. {
  478. public Metric Metric { get; private set; }
  479. public string Message { get; private set; }
  480. public Exception Exception { get; private set; }
  481. public DateTime Time { get; private set; }
  482. public MetricError(Metric metric, string messageFormat, params object[] args) : this(metric, null, messageFormat, args) { }
  483. public MetricError(Metric metric, Exception exception, string messageFormat, params object[] args)
  484. {
  485. Time = DateTime.Now;
  486. try
  487. {
  488. Message = string.Format(CultureInfo.InvariantCulture, messageFormat, args);
  489. }
  490. catch
  491. {
  492. Message = string.Format(CultureInfo.InvariantCulture, "Error message: {0}", messageFormat);
  493. }
  494. Exception = exception;
  495. Metric = metric;
  496. }
  497. }
  498. }