Example #1
0
        /// <summary>
        /// Loads a state set from the Redis database.
        /// </summary>
        /// <param name="historian">The historian to load the state set from.</param>
        /// <param name="name">The name of the state set to load.</param>
        /// <param name="cancellationToken">The cancellation token for the request.</param>
        /// <returns>
        /// A task that will return the loaded state set.
        /// </returns>
        internal static async Task <StateSet> Load(RedisHistorian historian, string name, CancellationToken cancellationToken)
        {
            var values = await historian.Connection.GetDatabase().HashGetAllAsync(historian.GetKeyForStateSetDefinition(name)).ConfigureAwait(false);

            cancellationToken.ThrowIfCancellationRequested();

            string description = null;
            var    states      = new List <StateSetItem>();

            foreach (var item in values)
            {
                if (item.Name == "DESC")
                {
                    description = item.Value;
                    continue;
                }

                var hName = item.Name.ToString();
                if (hName.StartsWith("S_"))
                {
                    states.Add(new StateSetItem(hName.Substring(2), (int)item.Value));
                }
            }

            return(new StateSet(name, description, states));
        }
Example #2
0
        /// <summary>
        /// Loads all tag definitions for the specified historian.
        /// </summary>
        /// <param name="historian">The historian.</param>
        /// <param name="callback">A callback function that is invoked every time a tag definition is loaded from Redis.</param>
        /// <param name="cancellationToken">The cancellation token for the request.</param>
        /// <returns>
        /// A task that will load all tags into the historian.
        /// </returns>
        internal static async Task LoadAll(RedisHistorian historian, Action <RedisTagDefinition> callback, CancellationToken cancellationToken)
        {
            var key = historian.GetKeyForTagIdsList();

            const int pageSize = 100;
            var       page     = 0;
            bool      @continue;

            // Load tag definitions 100 at a time.
            do
            {
                @continue = false;
                ++page;

                long start = (page - 1) * pageSize;
                long end   = start + pageSize - 1; // -1 because right-most item is included when getting the list range.

                var tagIds = await historian.Connection.GetDatabase().ListRangeAsync(key, start, end).ConfigureAwait(false);

                @continue = tagIds.Length == pageSize;
                if (tagIds.Length == 0)
                {
                    continue;
                }

                var tasks = tagIds.Select(x => Task.Run(async() => {
                    var tag = await Load(historian, x, cancellationToken).ConfigureAwait(false);
                    callback(tag);
                })).ToArray();

                await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(-1, cancellationToken)).ConfigureAwait(false);

                cancellationToken.ThrowIfCancellationRequested();
            } while (@continue);
        }
Example #3
0
        /// <summary>
        /// Saves a state set definition to the Redis database.
        /// </summary>
        /// <param name="historian">The historian to load the state set from.</param>
        /// <param name="stateSet">The state set to save.</param>
        /// <param name="addToMasterList">
        ///   Specify <see langword="true"/> when the state set is being created and <see langword="false"/>
        ///   when it is being updated.
        /// </param>
        /// <param name="cancellationToken">The cancellation token for the request.</param>
        /// <returns>
        /// A task that will save the tag definition to the Redis database.
        /// </returns>
        internal static async Task Save(RedisHistorian historian, StateSet stateSet, bool addToMasterList, CancellationToken cancellationToken)
        {
            var key = historian.GetKeyForStateSetDefinition(stateSet.Name);

            var tasks = new List <Task>();
            var db    = historian.Connection.GetDatabase();

            var hashes = new List <HashEntry>()
            {
                new HashEntry("DESC", stateSet.Description)
            };

            hashes.AddRange(stateSet.Select(x => new HashEntry($"S_{x.Name}", x.Value)));

            tasks.Add(db.HashSetAsync(key, hashes.ToArray()));
            if (addToMasterList)
            {
                var listKey = historian.GetKeyForStateSetNamesList();
                tasks.Add(db.ListRightPushAsync(listKey, stateSet.Name));
            }

            await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(-1, cancellationToken)).ConfigureAwait(false);

            cancellationToken.ThrowIfCancellationRequested();
        }
Example #4
0
        /// <summary>
        /// Creates a new <see cref="RedisTagDefinition"/> object.
        /// </summary>
        /// <param name="historian">The owning historian.</param>
        /// <param name="id">The tag ID.</param>
        /// <param name="settings">The tag settings.</param>
        /// <param name="metadata">The metadata for the tag.</param>
        /// <param name="initialTagValues">The initial tag values, used to prime the exception and compression filters for the tag.</param>
        /// <param name="changeHistory">The change history for the tag.</param>
        private RedisTagDefinition(RedisHistorian historian, string id, TagSettings settings, TagMetadata metadata, InitialTagValues initialTagValues, IEnumerable <TagChangeHistoryEntry> changeHistory) : base(historian, id, settings, metadata, CreateTagSecurity(), initialTagValues, changeHistory)
        {
            _historian           = historian ?? throw new ArgumentNullException(nameof(historian));
            _tagDefinitionKey    = _historian.GetKeyForTagDefinition(Id);
            _snapshotKey         = _historian.GetKeyForSnapshotData(Id);
            _archiveKey          = _historian.GetKeyForRawData(Id);
            _archiveCandidateKey = _historian.GetKeyForArchiveCandidateData(Id);

            _archiveCandidateValue = new ArchiveCandidateValue(initialTagValues?.LastExceptionValue, initialTagValues?.CompressionAngleMinimum ?? Double.NaN, initialTagValues?.CompressionAngleMaximum ?? Double.NaN);

            Updated += TagUpdated;
        }
Example #5
0
        /// <summary>
        /// Creates a new <see cref="RedisTagDefinition"/>.
        /// </summary>
        /// <param name="historian">The historian for the tag.</param>
        /// <param name="settings">The tag settings.</param>
        /// <param name="creator">The tag's creator.</param>
        /// <param name="cancellationToken">The cancellation token for the request.</param>
        /// <returns>
        /// A task that will create a new <see cref="RedisTagDefinition"/> and save it to the
        /// historian's Redis server.
        /// </returns>
        internal static async Task <RedisTagDefinition> Create(RedisHistorian historian, TagSettings settings, ClaimsPrincipal creator, CancellationToken cancellationToken)
        {
            var now    = DateTime.UtcNow;
            var result = new RedisTagDefinition(historian, null, settings, new TagMetadata(DateTime.UtcNow, creator?.Identity.Name), null, new[] { TagChangeHistoryEntry.Created(creator) });
            var key    = historian.GetKeyForTagIdsList();

            await Task.WhenAny(Task.WhenAll(result.Save(cancellationToken), historian.Connection.GetDatabase().ListRightPushAsync(key, result.Id)), Task.Delay(-1, cancellationToken)).ConfigureAwait(false);

            cancellationToken.ThrowIfCancellationRequested();

            return(result);
        }
Example #6
0
        /// <summary>
        /// Loads the last-archived value for the specified tag.
        /// </summary>
        /// <param name="historian">The historian.</param>
        /// <param name="tagId">The tag ID.</param>
        /// <param name="cancellationToken">The cancellation token for the request.</param>
        /// <returns>
        /// A task that will return the last-archived value for the tag.
        /// </returns>
        private static async Task <TagValue> LoadLastArchivedValue(RedisHistorian historian, string tagId, CancellationToken cancellationToken)
        {
            var key = historian.GetKeyForRawData(tagId);
            var lastArchivedValueTask = historian.Connection.GetDatabase().SortedSetRangeByScoreAsync(key, Double.NegativeInfinity, Double.PositiveInfinity, Exclude.None, Order.Descending, 0, 1, CommandFlags.None);

            await Task.WhenAny(lastArchivedValueTask, Task.Delay(-1, cancellationToken)).ConfigureAwait(false);

            cancellationToken.ThrowIfCancellationRequested();

            var value = lastArchivedValueTask.Result.FirstOrDefault();

            return(value == RedisValue.Null
                ? null
                : JsonConvert.DeserializeObject <RedisTagValue>(value)?.ToTagValue());
        }
Example #7
0
        /// <summary>
        /// Loads the archive candidate value for the specified tag from Redis.
        /// </summary>
        /// <param name="historian">The historian.</param>
        /// <param name="tagId">The tag ID.</param>
        /// <param name="cancellationToken">The cancellation token for the request.</param>
        /// <returns>
        /// A task that will return the archive candidate value.
        /// </returns>
        private static async Task <ArchiveCandidateValue> LoadArchiveCandidateValue(RedisHistorian historian, string tagId, CancellationToken cancellationToken)
        {
            var key        = historian.GetKeyForArchiveCandidateData(tagId);
            var valuesTask = historian.Connection.GetDatabase().HashGetAllAsync(key);
            await Task.WhenAny(valuesTask, Task.Delay(-1, cancellationToken)).ConfigureAwait(false);

            cancellationToken.ThrowIfCancellationRequested();

            if (valuesTask.Result.Length == 0)
            {
                return(null);
            }

            return(GetArchiveCandidateValueFromHashValues(valuesTask.Result));
        }
Example #8
0
        /// <summary>
        /// Deletes a state set definition.
        /// </summary>
        /// <param name="historian">The historian to delete the state set from.</param>
        /// <param name="name">The name of the state set to delete.</param>
        /// <param name="cancellationToken">The cancellation token for the request.</param>
        /// <returns>
        /// A task that will delete the tag.
        /// </returns>
        internal static async Task Delete(RedisHistorian historian, string name, CancellationToken cancellationToken)
        {
            var tasks = new List <Task>();

            // Delete the state set definition.
            tasks.Add(historian.Connection.GetDatabase().KeyDeleteAsync(historian.GetKeyForStateSetDefinition(name)));

            // Remove the name from the list of state set names.
            var nameListKey = historian.GetKeyForStateSetNamesList();

            tasks.Add(historian.Connection.GetDatabase().ListRemoveAsync(nameListKey, name));

            await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(-1, cancellationToken)).ConfigureAwait(false);

            cancellationToken.ThrowIfCancellationRequested();
        }
Example #9
0
 /// <summary>
 /// Gets the Redis key for the list that contains all of the defined tag IDs.
 /// </summary>
 /// <param name="historian">The historian.</param>
 /// <returns>
 /// The Redis key for the tag IDs list.
 /// </returns>
 /// <remarks>
 /// We use a separate list to store the tag IDs rather than doing a wildcard match on keys (e.g.
 /// searching for keys matching "aika:tags:*:definition"),
 /// because this involves searching through all keys on a specific server, rather than on the
 /// database in general (which may be split across multiple nodes).  See here for an
 /// explanation: https://stackexchange.github.io/StackExchange.Redis/KeysScan
 /// </remarks>
 internal static string GetKeyForStateSetNamesList(this RedisHistorian historian)
 {
     return(historian.GetKeyPrefixForStateSet("names"));
 }
Example #10
0
 /// <summary>
 /// Gets the Redis key for the hash that defines the state set with the provided name.
 /// </summary>
 /// <param name="historian">The historian.</param>
 /// <param name="name">The state set name.</param>
 /// <returns>
 /// The Redis key for the state set definition hash.
 /// </returns>
 internal static string GetKeyForStateSetDefinition(this RedisHistorian historian, string name)
 {
     return($"{historian.GetKeyPrefixForStateSet(name)}:definition");
 }
Example #11
0
        /// <summary>
        /// Loads a tag definition from the Redis database.
        /// </summary>
        /// <param name="historian">The Redis historian to load the tag from.</param>
        /// <param name="tagId">The ID of the tag to load.</param>
        /// <param name="cancellationToken">The cancellation token for the request.</param>
        /// <returns>
        /// A task that will return the loaded tag definition.
        /// </returns>
        internal static async Task <RedisTagDefinition> Load(RedisHistorian historian, string tagId, CancellationToken cancellationToken)
        {
            var values = await historian.Connection.GetDatabase().HashGetAllAsync(historian.GetKeyForTagDefinition(tagId)).ConfigureAwait(false);

            cancellationToken.ThrowIfCancellationRequested();

            string      name        = null;
            string      description = null;
            string      units       = null;
            TagDataType dataType    = default(TagDataType);
            string      stateSet    = null;

            bool exceptionFilterEnabled = false;
            TagValueFilterDeviationType exceptionFilterLimitType = default(TagValueFilterDeviationType);
            double   exceptionFilterLimit      = 0;
            TimeSpan exceptionFilterWindowSize = default(TimeSpan);

            bool compressionFilterEnabled = false;
            TagValueFilterDeviationType compressionFilterLimitType = default(TagValueFilterDeviationType);
            double   compressionFilterLimit      = 0;
            TimeSpan compressionFilterWindowSize = default(TimeSpan);

            DateTime createdAt  = DateTime.MinValue;
            string   creator    = null;
            DateTime modifiedAt = DateTime.MinValue;
            string   modifiedBy = null;

            foreach (var item in values)
            {
                switch (item.Name.ToString())
                {
                case "NAME":
                    name = item.Value;
                    break;

                case "DESC":
                    description = item.Value;
                    break;

                case "UNITS":
                    units = item.Value;
                    break;

                case "TYPE":
                    dataType = (TagDataType)((int)item.Value);
                    break;

                case "SSET":
                    stateSet = item.Value;
                    break;

                case "EXC_ENABLED":
                    exceptionFilterEnabled = Convert.ToBoolean((int)item.Value);
                    break;

                case "EXC_LIMIT_TYPE":
                    exceptionFilterLimitType = (TagValueFilterDeviationType)((int)item.Value);
                    break;

                case "EXC_LIMIT":
                    exceptionFilterLimit = (double)item.Value;
                    break;

                case "EXC_WINDOW":
                    exceptionFilterWindowSize = TimeSpan.Parse(item.Value);
                    break;

                case "COM_ENABLED":
                    compressionFilterEnabled = Convert.ToBoolean((int)item.Value);
                    break;

                case "COM_LIMIT_TYPE":
                    compressionFilterLimitType = (TagValueFilterDeviationType)((int)item.Value);
                    break;

                case "COM_LIMIT":
                    compressionFilterLimit = (double)item.Value;
                    break;

                case "COM_WINDOW":
                    compressionFilterWindowSize = TimeSpan.Parse(item.Value);
                    break;

                case "MD_CREATEDAT":
                    createdAt = new DateTime((long)item.Value, DateTimeKind.Utc);
                    break;

                case "MD_CREATEDBY":
                    creator = item.Value;
                    break;

                case "MD_MODIFIEDAT":
                    modifiedAt = new DateTime((long)item.Value, DateTimeKind.Utc);
                    break;

                case "MD_MODIFIEDBY":
                    modifiedBy = item.Value;
                    break;
                }
            }

            if (String.IsNullOrWhiteSpace(name))
            {
                name = tagId;
            }

            var settings = new TagSettings()
            {
                Name                    = name,
                Description             = description,
                Units                   = units,
                DataType                = dataType,
                StateSet                = stateSet,
                ExceptionFilterSettings = new TagValueFilterSettingsUpdate()
                {
                    IsEnabled  = exceptionFilterEnabled,
                    LimitType  = exceptionFilterLimitType,
                    Limit      = exceptionFilterLimit,
                    WindowSize = exceptionFilterWindowSize
                },
                CompressionFilterSettings = new TagValueFilterSettingsUpdate()
                {
                    IsEnabled  = compressionFilterEnabled,
                    LimitType  = compressionFilterLimitType,
                    Limit      = compressionFilterLimit,
                    WindowSize = compressionFilterWindowSize
                }
            };

            var metadata = new TagMetadata(createdAt, creator, modifiedAt, modifiedBy);

            var snapshotTask         = LoadSnapshotValue(historian, tagId, cancellationToken);
            var lastArchivedTask     = LoadLastArchivedValue(historian, tagId, cancellationToken);
            var archiveCandidateTask = LoadArchiveCandidateValue(historian, tagId, cancellationToken);

            await Task.WhenAll(snapshotTask, lastArchivedTask, archiveCandidateTask).ConfigureAwait(false);

            cancellationToken.ThrowIfCancellationRequested();

            var initialValues = new InitialTagValues(snapshotTask.Result, lastArchivedTask.Result, archiveCandidateTask.Result.Value, archiveCandidateTask.Result.CompressionAngleMinimum, archiveCandidateTask.Result.CompressionAngleMaximum);

            var result = new RedisTagDefinition(historian,
                                                tagId,
                                                settings,
                                                metadata,
                                                initialValues,
                                                null);

            return(result);
        }
Example #12
0
 /// <summary>
 /// Gets the Redis key for the list that contains all of the defined tag IDs.
 /// </summary>
 /// <param name="historian">The historian.</param>
 /// <returns>
 /// The Redis key for the tag IDs list.
 /// </returns>
 /// <remarks>
 /// We use a separate list to store the tag IDs rather than doing a wildcard match on keys (e.g.
 /// searching for keys matching "aika:tags:*:definition"),
 /// because this involves searching through all keys on a specific server, rather than on the
 /// database in general (which may be split across multiple nodes).  See here for an
 /// explanation: https://stackexchange.github.io/StackExchange.Redis/KeysScan
 /// </remarks>
 internal static string GetKeyForTagIdsList(this RedisHistorian historian)
 {
     return(historian.GetKeyPrefixForTag("tagIds"));
 }
Example #13
0
 /// <summary>
 /// Gets the Redis key prefix that is used for all things related to a single tag (the definition, the snapshot value, and the raw history).
 /// </summary>
 /// <param name="historian">The historian.</param>
 /// <param name="tagId">The tag ID.</param>
 /// <returns>The key prefix.</returns>
 internal static string GetKeyPrefixForTag(this RedisHistorian historian, string tagId)
 {
     return($"{historian.RedisKeyPrefix}:tags:{tagId}");
 }
Example #14
0
 /// <summary>
 /// Gets the Redis key prefix that is used for all things related to a state set.
 /// </summary>
 /// <param name="historian">The historian.</param>
 /// <param name="name">The state set name.</param>
 /// <returns>
 /// The key prefix.
 /// </returns>
 internal static string GetKeyPrefixForStateSet(this RedisHistorian historian, string name)
 {
     return($"{historian.RedisKeyPrefix}:stateSets:{name}");
 }
Example #15
0
 /// <summary>
 /// Gets the Redis key for the tag's current archive candidate value.
 /// </summary>
 /// <param name="historian">The historian.</param>
 /// <param name="tagId">The tag ID.</param>
 /// <returns>
 /// The Redis key for the tag's snapshot value.
 /// </returns>
 internal static string GetKeyForArchiveCandidateData(this RedisHistorian historian, string tagId)
 {
     return($"{historian.GetKeyPrefixForTag(tagId)}:archiveCandidate");
 }
Example #16
0
 /// <summary>
 /// Gets the Redis key for the tag's snapshot value.
 /// </summary>
 /// <param name="historian">The historian.</param>
 /// <param name="tagId">The tag ID.</param>
 /// <returns>
 /// The Redis key for the tag's snapshot value.
 /// </returns>
 internal static string GetKeyForSnapshotData(this RedisHistorian historian, string tagId)
 {
     return($"{historian.GetKeyPrefixForTag(tagId)}:snapshot");
 }
Example #17
0
 /// <summary>
 /// Gets the Redis key for the hash that defines the tag with the provided tag ID.
 /// </summary>
 /// <param name="historian">The historian.</param>
 /// <param name="tagId">The tag ID.</param>
 /// <returns>
 /// The Redis key for the tag definition hash.
 /// </returns>
 internal static string GetKeyForTagDefinition(this RedisHistorian historian, string tagId)
 {
     return($"{historian.GetKeyPrefixForTag(tagId)}:definition");
 }