//----------------------------------------------------------------------------- // // Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). // You may not use this file except in compliance with the License. // A copy of the License is located at // // http://aws.amazon.com/apache2.0 // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either // express or implied. See the License for the specific language governing // permissions and limitations under the License. // //----------------------------------------------------------------------------- using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; using Amazon.XRay.Recorder.Core.Exceptions; using Amazon.XRay.Recorder.Core.Internal.Utils; using Amazon.XRay.Recorder.Core.Sampling; namespace Amazon.XRay.Recorder.Core.Internal.Entities { /// /// Represents the common part for both Segment and Subsegment. /// [Serializable] public abstract class Entity { private const string DefaultMetadataNamespace = "default"; private const int SegmentIdHexDigits = 16; // Number of hex digits in segment id private readonly Lazy> _lazySubsegments = new Lazy>(); private readonly Lazy> _lazyHttp = new Lazy>(); private readonly Lazy _lazyAnnotations = new Lazy(); private readonly Lazy> _lazySql = new Lazy>(); private readonly Lazy> _lazyMetadata = new Lazy>(); private readonly Lazy> _lazyAws = new Lazy>(); private string _traceId; private string _id; private string _name; private string _parentId; private long _referenceCounter; // Reference count /// /// Initializes a new instance of the class. /// /// The name. public Entity(string name) { Id = GenerateId(); IsInProgress = true; this.Name = name; IncrementReferenceCounter(); } /// /// Gets or sets the unique id for the trace. /// /// Trace id is invalid. - value public string TraceId { get { return _traceId; } set { if (!Entities.TraceId.IsIdValid(value)) { throw new ArgumentException("Trace id is invalid.", nameof(value)); } _traceId = value; } } /// /// Gets or sets the unique id of segment. /// /// /// The unique for Entity. /// /// The id is invalid. - value public string Id { get { return _id; } set { if (value!=null && !IsIdValid(value)) { throw new ArgumentException("The id is invalid.", nameof(value)); } _id = value; } } /// /// Gets or sets the start time of this segment with Unix time in seconds. /// public decimal StartTime { get; set; } /// /// Gets or sets the end time of this segment with Unix time in seconds. /// public decimal EndTime { get; set; } /// /// Gets or sets the name of the service component. /// /// /// The name. /// /// Thrown when value is null. public string Name { get { return _name; } set { _name = value ?? throw new ArgumentNullException(nameof(value)); } } /// /// Gets a readonly copy of the subsegment list. /// public List Subsegments { get { return _lazySubsegments.Value; } } /// /// Gets a value indicating whether any subsegments have been added. /// /// /// true if there are subsegments added; otherwise, false. /// public bool IsSubsegmentsAdded { get { return _lazySubsegments.IsValueCreated && _lazySubsegments.Value.Any(); } } /// /// Gets or sets the unique id of upstream segment /// /// /// The unique id for parent Entity. /// /// The parent id is invalid. - value public string ParentId { get { return _parentId; } set { if (!IsIdValid(value)) { throw new ArgumentException("The parent id is invalid.", nameof(value)); } _parentId = value; } } /// /// Gets the annotations of the segment /// public Annotations Annotations { get { return _lazyAnnotations.Value; } } /// /// Gets a value indicating whether any annotations have been added. /// /// /// true if annotations have been added; otherwise, false. /// public bool IsAnnotationsAdded { get { return _lazyAnnotations.IsValueCreated && _lazyAnnotations.Value.Any(); } } /// /// Gets or sets a value indicating whether the segment has faulted or failed /// public bool HasFault { get; set; } /// /// Gets or sets a value indicating whether the segment has errored /// public bool HasError { get; set; } /// /// Gets or sets a value indicating whether the remote segment is throttled /// public bool IsThrottled { get; set; } /// /// Gets the cause of fault or error /// public Cause Cause { get; private set; } /// /// Gets or sets a value indicating whether the segment is in progress /// public bool IsInProgress { get; set; } /// /// Gets or sets a value indicating whether the entity has been streamed /// public bool HasStreamed { get; set; } /// /// Gets reference of this instance of segment /// public long Reference { get { return Interlocked.Read(ref _referenceCounter); } } /// /// Gets the http attribute /// public IDictionary Http { get { return _lazyHttp.Value; } } /// /// Gets a value indicating whether any HTTP information has been added. /// /// /// true if HTTP information has been added; otherwise, false. /// public bool IsHttpAdded { get { return _lazyHttp.IsValueCreated && !_lazyHttp.Value.IsEmpty; } } /// /// Gets the SQL information /// public IDictionary Sql { get { return _lazySql.Value; } } /// /// Gets a value indicating whether any SQL information has been added. /// /// /// true if SQL information has been added; otherwise, false. /// public bool IsSqlAdded { get { return _lazySql.IsValueCreated && !_lazySql.Value.IsEmpty; } } /// /// Gets the metadata. /// public IDictionary Metadata { get { return _lazyMetadata.Value; } } /// /// Gets a value indicating whether any metadata has been added. /// /// /// true if metadata has been added; otherwise, false. /// public bool IsMetadataAdded { get { return _lazyMetadata.IsValueCreated && !_lazyMetadata.Value.IsEmpty; } } /// /// Gets or sets the sample decision /// public SampleDecision Sampled { get; set; } /// /// Gets or sets the root segment /// public Segment RootSegment { get; set; } /// /// Gets aws information /// public IDictionary Aws { get { return _lazyAws.Value; } } /// /// Gets a value indicating whether aws information has been added. /// /// /// true if aws information has added; otherwise, false. /// public bool IsAwsAdded { get { return _lazyAws.IsValueCreated && !_lazyAws.Value.IsEmpty; } } /// /// Validate the segment id /// /// The segment id to be validate /// A value indicates if the id is valid public static bool IsIdValid(string id) { return id.Length == SegmentIdHexDigits && long.TryParse(id, NumberStyles.HexNumber, null, out _); } /// /// Generates the id for entity. /// /// An id for entity. public static string GenerateId() { return ThreadSafeRandom.GenerateHexNumber(SegmentIdHexDigits); } /// /// Set start time of the entity to current time /// public void SetStartTimeToNow() { StartTime = DateTime.UtcNow.ToUnixTimeSeconds(); } /// /// Set end time of the entity to current time /// public void SetEndTimeToNow() { EndTime = DateTime.UtcNow.ToUnixTimeSeconds(); } /// /// Sets start time of the entity to the provided timestamp. /// public void SetStartTime(decimal timestamp) { StartTime = timestamp; } /// /// Sets end time of the entity to the provided timestamp. /// public void SetEndTime(decimal timestamp) { EndTime = timestamp; } /// /// Sets start time of the entity to the provided timestamp. /// public void SetStartTime(DateTime timestamp) { StartTime = timestamp.ToUnixTimeSeconds(); } /// /// Sets end time of the entity to the provided timestamp. /// public void SetEndTime(DateTime timestamp) { EndTime = timestamp.ToUnixTimeSeconds(); } /// /// Adds the specified key and value as annotation to current segment. /// The type of value is restricted. Only , , , /// and are supported. /// /// The key of the annotation to add /// The value of the annotation to add /// Key cannot be null or empty - key /// value /// The annotation to be added is invalid. public void AddAnnotation(string key, object value) { if (string.IsNullOrEmpty(key)) { throw new ArgumentException("Key cannot be null or empty", nameof(key)); } if (value == null) { throw new ArgumentNullException(nameof(value)); } if (value is int) { _lazyAnnotations.Value.Add(key, (int)value); return; } if (value is long) { _lazyAnnotations.Value.Add(key, (long)value); return; } string stringValue = value as string; if (stringValue != null) { _lazyAnnotations.Value.Add(key, stringValue); return; } if (value is double) { _lazyAnnotations.Value.Add(key, (double)value); return; } if (value is bool) { _lazyAnnotations.Value.Add(key, (bool)value); return; } throw new InvalidAnnotationException(string.Format(CultureInfo.InvariantCulture, "Failed to add key={0}, value={1}, valueType={2} because the type is not supported.", key, value.ToString(), value.GetType().ToString())); } /// /// Add a subsegment /// /// The subsegment to add /// Cannot add subsegment to a completed segment. public void AddSubsegment(Subsegment subsegment) { if (!IsInProgress) { throw new EntityNotAvailableException("Cannot add subsegment to a completed segment."); } lock (_lazySubsegments.Value) { _lazySubsegments.Value.Add(subsegment); } IncrementReferenceCounter(); subsegment.Parent = this; subsegment.RootSegment = RootSegment; RootSegment.IncrementSize(); } /// /// Adds the exception to cause and set this segment to has fault. /// /// The exception to be added. public void AddException(Exception e) { HasFault = true; Cause = new Cause(); Cause.AddException(AWSXRayRecorder.Instance.ExceptionSerializationStrategy.DescribeException(e, Subsegments)); } /// /// Adds the specific key and value to metadata under default namespace. /// /// The key. /// The value. public void AddMetadata(string key, object value) { AddMetadata(DefaultMetadataNamespace, key, value); } /// /// Adds the specific key and value to metadata under given namespace. /// /// The name space. /// The key. /// The value. public void AddMetadata(string nameSpace, string key, object value) { _lazyMetadata.Value.GetOrAdd(nameSpace, new ConcurrentDictionary())[key] = value; } /// /// Check if this segment or the root segment that this segment belongs to is ok to emit /// /// If the segment is ready to emit public abstract bool IsEmittable(); /// /// Release reference to this instance of segment /// /// Reference count after release public abstract long Release(); /// /// Release reference to this instance of segment /// /// Reference count after release protected long DecrementReferenceCounter() { return Interlocked.Decrement(ref _referenceCounter); } /// /// Add reference to this instance of segment /// /// Reference count after add protected long IncrementReferenceCounter() { return Interlocked.Increment(ref _referenceCounter); } } }