PageRenderTime 38ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/Assets/AWSSDK/src/Core/Amazon.Runtime/Internal/Util/Metrics.cs

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