//-----------------------------------------------------------------------------
//
// Copyright 2016 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.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using Amazon.Runtime.Internal.Util;
using Amazon.XRay.Recorder.Core.Exceptions;
using Amazon.XRay.Recorder.Core.Internal.Utils;
using ThirdParty.LitJson;
namespace Amazon.XRay.Recorder.Core.Sampling.Local
{
///
/// This strategy loads the sample Rules from local JSON file, and make the sample decision locally
/// according to the Rules.
///
public class LocalizedSamplingStrategy : ISamplingStrategy
{
private const string DefaultSamplingConfigurationResourceName = "Amazon.XRay.Recorder.Core.Sampling.Local.DefaultSamplingRule.json";
private int[] SupportedSamplingConfigurationVersion = {1,2};
private static readonly Logger _logger = Logger.GetLogger(typeof(LocalizedSamplingStrategy));
private SamplingRule _defaultRule;
///
/// Initializes a new instance of the class.
///
public LocalizedSamplingStrategy()
{
InitWithDefaultSamplingRules();
}
///
/// Initializes a new instance of the class.
///
/// Path to the file which sampling configuration.
public LocalizedSamplingStrategy(string path)
{
if (string.IsNullOrEmpty(path))
{
_logger.DebugFormat("Initializing with default sampling rules.");
InitWithDefaultSamplingRules();
}
else
{
_logger.DebugFormat("Initializing with custom sampling configuration : {0}", path);
using (Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read))
{
Init(stream);
}
}
}
///
/// Gets or sets the default sampling rule.
///
public SamplingRule DefaultRule
{
get
{
return _defaultRule;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
if (value.Host != null || value.HttpMethod != null || value.UrlPath != null ||
value.FixedTarget == -1 || value.Rate == -1d)
{
throw new InvalidSamplingConfigurationException(
string.Format(
CultureInfo.InvariantCulture,
"You are either missing required field or including extra fields ({0}). \"fixed_target\" and \"rate\" are required.",
value));
}
// Enforce default rule to match all
value.Host = "*";
value.UrlPath = "*";
value.HttpMethod = "*";
_defaultRule = value;
}
}
///
/// Gets the list of sampling Rules.
///
public IList Rules { get; private set; }
///
/// Apply the default sampling rule to make the sample decision
///
/// Name of the service.
/// The path of request.
/// The HTTP method.
///
/// The sample decision made for this call
///
private SampleDecision Sample(string host, string path, string method)
{
var firstMatchRule = Rules.FirstOrDefault(r => r.IsMatch(host, path, method));
if (firstMatchRule == null)
{
_logger.DebugFormat("Can't match a rule for host = {0}, path = {1}, method = {2}", host, path, method);
return ApplyRule(DefaultRule);
}
_logger.DebugFormat("Found a matching rule : ({0}) for host = {1}, path = {2}, method = {3}", firstMatchRule.ToString(), host, path, method);
return ApplyRule(firstMatchRule);
}
///
/// Perform sampling decison based on .
///
/// Instance of .
/// Instance of .
public SamplingResponse ShouldTrace(SamplingInput input)
{
SampleDecision sampleDecision = Sample(input.Host,input.Url,input.Method);
return new SamplingResponse(sampleDecision);
}
private static SampleDecision ApplyRule(SamplingRule rule)
{
if (rule.RateLimiter.Request())
{
return SampleDecision.Sampled;
}
return ThreadSafeRandom.NextDouble() <= rule.Rate ? SampleDecision.Sampled : SampleDecision.NotSampled;
}
private void InitWithDefaultSamplingRules()
{
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(DefaultSamplingConfigurationResourceName))
{
Init(stream);
}
}
private void Init(Stream stream)
{
using (var reader = new StreamReader(stream))
{
SamplingConfiguration samplingConfiguration = JsonMapper.ToObject(reader);
if (samplingConfiguration == null)
{
throw new InvalidSamplingConfigurationException("The provided sampling configuration had invalid JSON format and cannot be parsed correctly.");
}
if (samplingConfiguration.Default == null)
{
throw new InvalidSamplingConfigurationException("No default sampling rule is provided. A default sampling rule is required.");
}
if ( !SupportedSamplingConfigurationVersion.Contains(samplingConfiguration.Version))
{
throw new InvalidSamplingConfigurationException(
string.Format(
CultureInfo.InvariantCulture,
"The version of provided sampling configuration is not supported. Provided version = {0}, supported versions = {1}",
samplingConfiguration.Version, String.Join(", ", SupportedSamplingConfigurationVersion)));
}
DefaultRule = samplingConfiguration.Default;
var rules = new List();
if (samplingConfiguration.Rules != null)
{
foreach (var rule in samplingConfiguration.Rules) // contains supported versions.
{
if (LocalizedSamplingStrategy.IsValidVersion1(samplingConfiguration, rule))
{
rule.Host = rule.ServiceName;
}
else
{
LocalizedSamplingStrategy.ValidateVersion2(samplingConfiguration, rule); // rule.Host already parsed in rule.
}
rules.Add(rule);
}
}
Rules = rules;
}
}
private static bool IsValidVersion1(SamplingConfiguration samplingConfiguration, SamplingRule rule)
{
if (samplingConfiguration.Version == 1)
{
if (rule.ServiceName == null || rule.HttpMethod == null || rule.UrlPath == null ||
rule.FixedTarget == -1 || rule.Rate == -1d || rule.Host != null)
{
throw new InvalidSamplingConfigurationException(
string.Format(
CultureInfo.InvariantCulture,
@"Missing required fields for sampling rules ({0}). ""service_name"", ""http_method"", ""url_path"", ""fixed_target"", ""rate"" are required.",
rule));
}
return true;
}
return false;
}
private static void ValidateVersion2(SamplingConfiguration samplingConfiguration, SamplingRule rule)
{
if (rule.Host == null || rule.HttpMethod == null || rule.UrlPath == null ||
rule.FixedTarget == -1 || rule.Rate == -1d || rule.ServiceName != null)
{
throw new InvalidSamplingConfigurationException(
string.Format(
CultureInfo.InvariantCulture,
@"Missing required fields for sampling rules ({0}). ""host"", ""http_method"", ""url_path"", ""fixed_target"", ""rate"" are required.",
rule));
}
}
}
}