//-----------------------------------------------------------------------------
//
// 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);
}
}
}