From fe83cf269f4386dc8c15cbb3676d11e964478ee4 Mon Sep 17 00:00:00 2001 From: Qinyouzeng <102203523+Qinyouzeng@users.noreply.github.com> Date: Thu, 30 Jun 2022 15:30:51 +0800 Subject: [PATCH] Feat: Add Prometheus http api query client sdk (#78) * feat: add promethus http api query client * feat: add test project * feat: api query add * feat: update code and add tests * docs: add docs * feat: add convert json value to object * feat: fix enume and comments * feat: code styles and other specifications update * feat: update callerprovider inject * feat: update jsonelement convert to obj * feat: update extensions * feat: update internal to public * feat: update directory * feat: test refrence update * chore: comments and filename update * chore: remove not exists project id * chore: update docs and code style * chore: enums update * chore: doc code style update * chore: dosc update * chore: docs update * feat: add promethus http api query client * feat: add test project * feat: api query add * feat: update code and add tests * docs: add docs * feat: add convert json value to object * feat: fix enume and comments * feat: code styles and other specifications update * feat: update callerprovider inject * feat: update jsonelement convert to obj * feat: update extensions * feat: update internal to public * feat: update directory * feat: test refrence update * chore: comments and filename update * chore: remove not exists project id * chore: update docs and code style * chore: enums update * chore: doc code style update * chore: dosc update * chore: docs update * chore: name update and remove no used files * chore: folder name update * chore: file rename * chore: class split --- Masa.Utils.sln | 16 +- .../Enums/ResultStatuses.cs | 10 + .../Enums/ResultTypes.cs | 15 ++ .../Extensions/CallerProviderExtensions.cs | 15 ++ .../Extensions/ObjectExtensions.cs | 142 +++++++++++++++ .../Extensions/StringExtensions.cs | 20 ++ .../IMasaPrometheusClient.cs | 19 ++ .../Masa.Utils.Data.Prometheus.csproj | 14 ++ .../MasaPrometheusClient.cs | 127 +++++++++++++ .../Reponse/Exemplar/ExemplarDataModel.cs | 13 ++ .../Model/Reponse/Exemplar/ExemplarModel.cs | 11 ++ .../Exemplar/ExemplarResultResponse.cs | 9 + .../Reponse/MetaData/LabelResultResponse.cs | 9 + .../Reponse/MetaData/SeriesResultResponse.cs | 9 + .../Query/QueryResultCommonResponse.cs | 9 + .../Reponse/Query/QueryResultDataResponse.cs | 11 ++ .../Query/QueryResultInstantVectorResponse.cs | 11 ++ .../Query/QueryResultMatrixRangeResponse.cs | 11 ++ .../Model/Reponse/ResultBaseResponse.cs | 15 ++ .../Model/Request/LableValueQueryRequest.cs | 9 + .../Model/Request/MetaDataQueryRequest.cs | 13 ++ .../Model/Request/QueryExemplarRequest.cs | 14 ++ .../Model/Request/QueryRangeRequest.cs | 17 ++ .../Model/Request/QueryRequest.cs | 13 ++ src/Data/Masa.Utils.Data.Prometheus/README.md | 75 ++++++++ .../README.zh-CN.md | 75 ++++++++ .../ServiceCollectionExtensions.cs | 39 ++++ .../Masa.Utils.Data.Prometheus/_Imports.cs | 15 ++ .../Extensions/ObjectExtensionsTests.cs | 171 ++++++++++++++++++ .../Extensions/StringExtensionsTests.cs | 30 +++ .../Masa.Utils.Data.Prometheus.Test.csproj | 21 +++ .../MasaPrometheusClientTests.cs | 116 ++++++++++++ .../UserStruct.cs | 13 ++ .../_Imports.cs | 12 ++ 34 files changed, 1118 insertions(+), 1 deletion(-) create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Enums/ResultStatuses.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Enums/ResultTypes.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Extensions/CallerProviderExtensions.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Extensions/ObjectExtensions.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Extensions/StringExtensions.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/IMasaPrometheusClient.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Masa.Utils.Data.Prometheus.csproj create mode 100644 src/Data/Masa.Utils.Data.Prometheus/MasaPrometheusClient.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Exemplar/ExemplarDataModel.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Exemplar/ExemplarModel.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Exemplar/ExemplarResultResponse.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/MetaData/LabelResultResponse.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/MetaData/SeriesResultResponse.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Query/QueryResultCommonResponse.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Query/QueryResultDataResponse.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Query/QueryResultInstantVectorResponse.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Query/QueryResultMatrixRangeResponse.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/ResultBaseResponse.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Model/Request/LableValueQueryRequest.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Model/Request/MetaDataQueryRequest.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Model/Request/QueryExemplarRequest.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Model/Request/QueryRangeRequest.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/Model/Request/QueryRequest.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/README.md create mode 100644 src/Data/Masa.Utils.Data.Prometheus/README.zh-CN.md create mode 100644 src/Data/Masa.Utils.Data.Prometheus/ServiceCollectionExtensions.cs create mode 100644 src/Data/Masa.Utils.Data.Prometheus/_Imports.cs create mode 100644 test/Masa.Utils.Data.Prometheus.Test/Extensions/ObjectExtensionsTests.cs create mode 100644 test/Masa.Utils.Data.Prometheus.Test/Extensions/StringExtensionsTests.cs create mode 100644 test/Masa.Utils.Data.Prometheus.Test/Masa.Utils.Data.Prometheus.Test.csproj create mode 100644 test/Masa.Utils.Data.Prometheus.Test/MasaPrometheusClientTests.cs create mode 100644 test/Masa.Utils.Data.Prometheus.Test/UserStruct.cs create mode 100644 test/Masa.Utils.Data.Prometheus.Test/_Imports.cs diff --git a/Masa.Utils.sln b/Masa.Utils.sln index cc2d590..3b75c81 100644 --- a/Masa.Utils.sln +++ b/Masa.Utils.sln @@ -101,7 +101,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Validations", "Validations" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Utils.Extensions.Validations.FluentValidation", "src\Extensions\Validations\Masa.Utils.Extensions.Validations.FluentValidation\Masa.Utils.Extensions.Validations.FluentValidation.csproj", "{B0E3CA19-C101-4E30-9401-C017B7088F4E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Utils.Security.Authentication.OpenIdConnect", "src\Security\Masa.Utils.Security.Authentication.OpenIdConnect\Masa.Utils.Security.Authentication.OpenIdConnect.csproj", "{D17B5B36-5773-4827-9D5D-44390DF666CA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Utils.Security.Authentication.OpenIdConnect", "src\Security\Masa.Utils.Security.Authentication.OpenIdConnect\Masa.Utils.Security.Authentication.OpenIdConnect.csproj", "{D17B5B36-5773-4827-9D5D-44390DF666CA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Utils.Data.Prometheus", "src\Data\Masa.Utils.Data.Prometheus\Masa.Utils.Data.Prometheus.csproj", "{81E8A8F5-91EA-43F2-9B19-83B85EED7DB7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Masa.Utils.Data.Prometheus.Test", "test\Masa.Utils.Data.Prometheus.Test\Masa.Utils.Data.Prometheus.Test.csproj", "{CAB4132B-82E4-4B37-AF05-7F446DE3AF04}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -249,6 +253,14 @@ Global {D17B5B36-5773-4827-9D5D-44390DF666CA}.Debug|Any CPU.Build.0 = Debug|Any CPU {D17B5B36-5773-4827-9D5D-44390DF666CA}.Release|Any CPU.ActiveCfg = Release|Any CPU {D17B5B36-5773-4827-9D5D-44390DF666CA}.Release|Any CPU.Build.0 = Release|Any CPU + {81E8A8F5-91EA-43F2-9B19-83B85EED7DB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81E8A8F5-91EA-43F2-9B19-83B85EED7DB7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81E8A8F5-91EA-43F2-9B19-83B85EED7DB7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81E8A8F5-91EA-43F2-9B19-83B85EED7DB7}.Release|Any CPU.Build.0 = Release|Any CPU + {CAB4132B-82E4-4B37-AF05-7F446DE3AF04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CAB4132B-82E4-4B37-AF05-7F446DE3AF04}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CAB4132B-82E4-4B37-AF05-7F446DE3AF04}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CAB4132B-82E4-4B37-AF05-7F446DE3AF04}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -299,6 +311,8 @@ Global {533BCE80-61BC-443A-95D3-2C6328921ED8} = {B2DA607D-4A39-4F0C-A9B3-DD9A061B0B4E} {B0E3CA19-C101-4E30-9401-C017B7088F4E} = {533BCE80-61BC-443A-95D3-2C6328921ED8} {D17B5B36-5773-4827-9D5D-44390DF666CA} = {4FB3BD6D-D4C1-4BEF-AD62-8FD6EAEEB4DF} + {81E8A8F5-91EA-43F2-9B19-83B85EED7DB7} = {F844C2A1-C36D-400E-A0D8-7658EF9C3B93} + {CAB4132B-82E4-4B37-AF05-7F446DE3AF04} = {4F908878-0EB8-43E4-96E4-8B1F32E9B635} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D7DAA0E6-098F-4B18-8775-64FDA96F1FF0} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Enums/ResultStatuses.cs b/src/Data/Masa.Utils.Data.Prometheus/Enums/ResultStatuses.cs new file mode 100644 index 0000000..b4f33b1 --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Enums/ResultStatuses.cs @@ -0,0 +1,10 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Enums; + +public enum ResultStatuses +{ + Success = 1, + Error +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Enums/ResultTypes.cs b/src/Data/Masa.Utils.Data.Prometheus/Enums/ResultTypes.cs new file mode 100644 index 0000000..35391ff --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Enums/ResultTypes.cs @@ -0,0 +1,15 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Enums; + +/// +/// reference https://prometheus.io/docs/prometheus/latest/querying/api/#expression-query-result-formats +/// +public enum ResultTypes +{ + Matrix = 1, + Vector, + Scalar, + String +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Extensions/CallerProviderExtensions.cs b/src/Data/Masa.Utils.Data.Prometheus/Extensions/CallerProviderExtensions.cs new file mode 100644 index 0000000..e0d49e8 --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Extensions/CallerProviderExtensions.cs @@ -0,0 +1,15 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +[assembly: InternalsVisibleTo("Masa.Utils.Data.Prometheus.Test")] +namespace Masa.Utils.Caller.Core; + +internal static class CallerProviderExtensions +{ + public static async Task GetAsync(this ICallerProvider caller, string url, object data) + { + var request = new HttpRequestMessage(HttpMethod.Get, $"{url}?{data.ToUrlParam()}"); + var response = await caller.SendAsync(request); + return await response.Content.ReadAsStringAsync(); + } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Extensions/ObjectExtensions.cs b/src/Data/Masa.Utils.Data.Prometheus/Extensions/ObjectExtensions.cs new file mode 100644 index 0000000..040a876 --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Extensions/ObjectExtensions.cs @@ -0,0 +1,142 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace System; + +public static class ObjectExtensions +{ + /// + /// Currently supported types: class, struct and types implementing the IEnumerable interface, + /// struct and class use public get properties and fields by default, + /// The IEnumerable type is directly converted to: key[]=value1&key[]=value2 + /// enum uses strings by default. If you need to use numeric values, please set isEnumString=false + /// + /// + /// + /// + /// + /// + public static string? ToUrlParam(this object? obj, bool isEnumString = true, bool isCamelCase = true, bool isUrlEncode = true) + { + return GetValue(obj, string.Empty, isEnumString, isCamelCase, isUrlEncode); + } + + private static string? GetValue(object? obj, string preStr, bool isEnumString = false, bool isCamelCase = true, bool isUrlEncode = true) + { + if (obj == null) return null; + var type = obj.GetType(); + if (type == typeof(string)) + { + var str = (string)obj; + return AppendValue(preStr, str, "=", isUrlEncode); + } + + if (type.IsValueType) + { + if (type.IsEnum) + { + var str = isEnumString ? obj.ToString() : Convert.ToInt32(obj).ToString(); + return AppendValue(preStr, str, "=", isUrlEncode); + } + + //sample value + if (type.IsPrimitive) + { + var str = obj.ToString(); + return AppendValue(preStr, str, "=", isUrlEncode); + } + + //struct + return GetObjValue(type, obj, preStr, isEnumString, isCamelCase, isUrlEncode); + } + + if (type.IsArray || type.GetInterfaces().Any(t => t.Name.IndexOf("IEnumerable") == 0)) + return GetEnumerableValue(obj, preStr, isEnumString, isCamelCase, isUrlEncode); + + if (type.IsClass) + return GetObjValue(type, obj, preStr, isEnumString, isCamelCase, isUrlEncode); + + //current type not suport + return null; + } + + private static string GetObjValue(Type type, object obj, string preStr, bool isEnumString = false, bool isCamelCase = true, bool isUrlEncode = true) + { + var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty); + var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetField); + var list = new List(); + + foreach (var item in properties) + { + var str = GetMemerInfoValue(item, item.GetValue(obj), preStr, isEnumString, isCamelCase, isUrlEncode); + if (string.IsNullOrEmpty(str)) + continue; + list.Add(str); + } + + foreach (var item in fields) + { + var str = GetMemerInfoValue(item, item.GetValue(obj), preStr, isEnumString, isCamelCase, isUrlEncode); + if (string.IsNullOrEmpty(str)) + continue; + list.Add(str); + } + + if (!list.Any()) + return default!; + + list.Sort(); + return string.Join('&', list); + } + + private static string? GetMemerInfoValue(MemberInfo info, object? value, string preStr, bool isEnumString = false, bool isCamelCase = true, bool isUrlEncode = true) + { + if (value == null) + return null; + + var name = info.Name; + if (isCamelCase) + name = name.ToCamelCase(); + + return GetValue(value, AppendValue(preStr, name, ".", isUrlEncode) ?? default!, isEnumString, isCamelCase, isUrlEncode); + } + + private static string? GetEnumerableValue(object obj, string preStr, bool isEnumString = false, bool isCamelCase = true, bool isUrlEncode = true) + { + var list = new List(); + foreach (var item in (IEnumerable)obj) + { + if (item is KeyValuePair keyValue) + { + var name = keyValue.Key; + if (isCamelCase) + name = name.ToCamelCase(); + var str = GetValue(keyValue.Value, AppendValue(preStr, name, ".", isUrlEncode) ?? default!, isEnumString, isCamelCase, isUrlEncode); + if (!string.IsNullOrEmpty(str)) + list.Add(str); + } + else + { + var str = GetValue(item, $"{preStr}{(isUrlEncode ? HttpUtility.UrlEncode("[]", Encoding.UTF8) : "[]")}", isEnumString, isCamelCase, isUrlEncode); + if (!string.IsNullOrEmpty(str)) + list.Add(str); + } + } + if (!list.Any()) + return default!; + + list.Sort(); + return string.Join('&', list); + } + + private static string? AppendValue(string preStr, string? value, string splitChar, bool isUrlEncode) + { + if (string.IsNullOrEmpty(preStr) || string.IsNullOrEmpty(value)) + return value; + + if (isUrlEncode) + return $"{preStr}{splitChar}{HttpUtility.UrlEncode(value, Encoding.UTF8)}"; + else + return $"{preStr}{splitChar}{value}"; + } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Extensions/StringExtensions.cs b/src/Data/Masa.Utils.Data.Prometheus/Extensions/StringExtensions.cs new file mode 100644 index 0000000..a484b7f --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Extensions/StringExtensions.cs @@ -0,0 +1,20 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace System; + +internal static class StringExtensions +{ + public static string ToCamelCase(this string str) + { + if (string.IsNullOrEmpty(str)) + return default!; + + var span = new ReadOnlySpan(str.ToArray()); + var c = span[0]; + if (c - 'A' >= 0 && c - 'Z' <= 0) + return $"{(char)(c + 32)}{span[1..]}"; + + return str; + } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/IMasaPrometheusClient.cs b/src/Data/Masa.Utils.Data.Prometheus/IMasaPrometheusClient.cs new file mode 100644 index 0000000..cc8c5d5 --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/IMasaPrometheusClient.cs @@ -0,0 +1,19 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus; + +public interface IMasaPrometheusClient +{ + Task QueryAsync(QueryRequest query); + + Task QueryRangeAsync(QueryRangeRequest query); + + Task SeriesQueryAsync(MetaDataQueryRequest query); + + Task LabelsQueryAsync(MetaDataQueryRequest query); + + Task LabelValuesQueryAsync(LableValueQueryRequest query); + + Task ExemplarQueryAsync(QueryExemplarRequest query); +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Masa.Utils.Data.Prometheus.csproj b/src/Data/Masa.Utils.Data.Prometheus/Masa.Utils.Data.Prometheus.csproj new file mode 100644 index 0000000..d3bd13a --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Masa.Utils.Data.Prometheus.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + enable + enable + + + + + + + + diff --git a/src/Data/Masa.Utils.Data.Prometheus/MasaPrometheusClient.cs b/src/Data/Masa.Utils.Data.Prometheus/MasaPrometheusClient.cs new file mode 100644 index 0000000..e243e99 --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/MasaPrometheusClient.cs @@ -0,0 +1,127 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +[assembly: InternalsVisibleTo("Masa.Utils.Data.Prometheus.Test")] +namespace Masa.Utils.Data.Prometheus; + +internal class MasaPrometheusClient : IMasaPrometheusClient +{ + private readonly ICallerProvider _caller; + private readonly JsonSerializerOptions _jsonSerializerOptions; + + public MasaPrometheusClient(ICallerProvider caller, JsonSerializerOptions jsonSerializerOptions) + { + _caller = caller; + _jsonSerializerOptions = jsonSerializerOptions; + } + + public async Task ExemplarQueryAsync(QueryExemplarRequest query) + { + return await QueryDataAsync("/api/v1/query_exemplars", query); + } + + public async Task LabelsQueryAsync(MetaDataQueryRequest query) + { + return await QueryDataAsync("/api/v1/labels", query); + } + + public async Task LabelValuesQueryAsync(LableValueQueryRequest query) + { + var name = query.Lable; + query.Lable = null; + return await QueryDataAsync($"/api/v1/label/{name}/values", query); + } + + public async Task QueryAsync(QueryRequest query) + { + return await QueryDataAsync("/api/v1/query", query); + } + + public async Task QueryRangeAsync(QueryRangeRequest query) + { + return await QueryDataAsync("/api/v1/query_range", query); + } + + public async Task SeriesQueryAsync(MetaDataQueryRequest query) + { + return await QueryDataAsync("/api/v1/series", query); + } + + private async Task QueryDataAsync(string url, object data) where T : ResultBaseResponse + { + var str = await _caller.GetAsync(url, data); + if (string.IsNullOrEmpty(str)) + return default!; + + var baseResult = JsonSerializer.Deserialize(str, _jsonSerializerOptions); + + if (baseResult == null || baseResult.Status != ResultStatuses.Success) + { + return baseResult ?? default!; + } + + if (typeof(T) == typeof(QueryResultCommonResponse)) + { + var result = baseResult as QueryResultCommonResponse; + if (result == null || result.Data == null) + return baseResult; + switch (result.Data.ResultType) + { + case ResultTypes.Matrix: + { + var temp = JsonSerializer.Serialize(result.Data.Result, _jsonSerializerOptions); + result.Data.Result = JsonSerializer.Deserialize(temp, _jsonSerializerOptions); + if (result.Data.Result != null && result.Data.Result.Any()) + { + foreach (QueryResultMatrixRangeResponse item in result.Data.Result) + { + if (item.Values == null || !item.Values.Any()) + continue; + var array = item.Values.ToArray(); + int i = 0, max = array.Length - 1; + do + { + array[i] = ConvertJsonToObjValue(array[i]); + i++; + } + while (max - i >= 0); + item.Values = array; + } + } + return result as T ?? default!; + } + case ResultTypes.Vector: + { + var temp = JsonSerializer.Serialize(result.Data.Result, _jsonSerializerOptions); + result.Data.Result = JsonSerializer.Deserialize(temp, _jsonSerializerOptions); + if (result.Data.Result != null && result.Data.Result.Any()) + { + foreach (QueryResultInstantVectorResponse item in result.Data.Result) + { + item.Value = ConvertJsonToObjValue(item.Value); + } + } + return result as T ?? default!; + } + default: + { + if (result.Data.Result != null && result.Data.Result.Any()) + { + result.Data.Result = ConvertJsonToObjValue(result.Data.Result); + } + } + break; + } + } + + return baseResult; + } + + private static object[] ConvertJsonToObjValue(object[]? values) + { + if (values == null || values.Length - 2 < 0) + return default!; + + return new object[] { ((JsonElement)values[0]).GetDouble(), ((JsonElement)values[1]).GetString() ?? default! }; + } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Exemplar/ExemplarDataModel.cs b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Exemplar/ExemplarDataModel.cs new file mode 100644 index 0000000..2777b07 --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Exemplar/ExemplarDataModel.cs @@ -0,0 +1,13 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Model; + +public class ExemplarModel +{ + public IDictionary? Labels { get; set; } + + public string? Value { get; set; } + + public float TimeStamp { get; set; } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Exemplar/ExemplarModel.cs b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Exemplar/ExemplarModel.cs new file mode 100644 index 0000000..84ff5c6 --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Exemplar/ExemplarModel.cs @@ -0,0 +1,11 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Model; + +public class ExemplarDataModel +{ + public IDictionary? SeriesLabels { get; set; } + + public IEnumerable? Exemplars { get; set; } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Exemplar/ExemplarResultResponse.cs b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Exemplar/ExemplarResultResponse.cs new file mode 100644 index 0000000..c03e8ee --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Exemplar/ExemplarResultResponse.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Model; + +public class ExemplarResultResponse : ResultBaseResponse +{ + public IEnumerable? Data { get; set; } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/MetaData/LabelResultResponse.cs b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/MetaData/LabelResultResponse.cs new file mode 100644 index 0000000..a20a25c --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/MetaData/LabelResultResponse.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Model; + +public class LabelResultResponse : ResultBaseResponse +{ + public IEnumerable? Data { get; set; } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/MetaData/SeriesResultResponse.cs b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/MetaData/SeriesResultResponse.cs new file mode 100644 index 0000000..6422449 --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/MetaData/SeriesResultResponse.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Model; + +public class SeriesResultResponse : ResultBaseResponse +{ + public IEnumerable>? Data { get; set; } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Query/QueryResultCommonResponse.cs b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Query/QueryResultCommonResponse.cs new file mode 100644 index 0000000..1859c35 --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Query/QueryResultCommonResponse.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Model; + +public class QueryResultCommonResponse: ResultBaseResponse +{ + public QueryResultDataResponse? Data { get; set; } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Query/QueryResultDataResponse.cs b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Query/QueryResultDataResponse.cs new file mode 100644 index 0000000..209f6d5 --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Query/QueryResultDataResponse.cs @@ -0,0 +1,11 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Model; + +public class QueryResultDataResponse +{ + public ResultTypes ResultType { get; set; } + + public object[]? Result { get; set; } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Query/QueryResultInstantVectorResponse.cs b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Query/QueryResultInstantVectorResponse.cs new file mode 100644 index 0000000..976986e --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Query/QueryResultInstantVectorResponse.cs @@ -0,0 +1,11 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Model; + +public class QueryResultInstantVectorResponse +{ + public IDictionary? Metric { get; set; } + + public object[]? Value { get; set; } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Query/QueryResultMatrixRangeResponse.cs b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Query/QueryResultMatrixRangeResponse.cs new file mode 100644 index 0000000..810473d --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/Query/QueryResultMatrixRangeResponse.cs @@ -0,0 +1,11 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Model; + +public class QueryResultMatrixRangeResponse +{ + public IDictionary? Metric { get; set; } + + public IEnumerable? Values { get; set; } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/ResultBaseResponse.cs b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/ResultBaseResponse.cs new file mode 100644 index 0000000..33381e6 --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Model/Reponse/ResultBaseResponse.cs @@ -0,0 +1,15 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Model; + +public class ResultBaseResponse +{ + public ResultStatuses Status { get; set; } + + public string? Error { get; set; } + + public string? ErrorType { get; set; } + + public IEnumerable? Warnings { get; set; } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Model/Request/LableValueQueryRequest.cs b/src/Data/Masa.Utils.Data.Prometheus/Model/Request/LableValueQueryRequest.cs new file mode 100644 index 0000000..b1089a1 --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Model/Request/LableValueQueryRequest.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Model; + +public class LableValueQueryRequest: MetaDataQueryRequest +{ + public string Lable { get; set; } = "__name__"; +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Model/Request/MetaDataQueryRequest.cs b/src/Data/Masa.Utils.Data.Prometheus/Model/Request/MetaDataQueryRequest.cs new file mode 100644 index 0000000..00c3af5 --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Model/Request/MetaDataQueryRequest.cs @@ -0,0 +1,13 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Model; + +public class MetaDataQueryRequest +{ + public IEnumerable? Match { get; set; } + + public string? Start { get; set; } + + public string? End { get; set; } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Model/Request/QueryExemplarRequest.cs b/src/Data/Masa.Utils.Data.Prometheus/Model/Request/QueryExemplarRequest.cs new file mode 100644 index 0000000..b6b9a4b --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Model/Request/QueryExemplarRequest.cs @@ -0,0 +1,14 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Model; + +public class QueryExemplarRequest +{ + public string? Query { get; set; } + + public string? Start { get; set; } + + public string? End { get; set; } +} + diff --git a/src/Data/Masa.Utils.Data.Prometheus/Model/Request/QueryRangeRequest.cs b/src/Data/Masa.Utils.Data.Prometheus/Model/Request/QueryRangeRequest.cs new file mode 100644 index 0000000..9d49261 --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Model/Request/QueryRangeRequest.cs @@ -0,0 +1,17 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Model; + +public class QueryRangeRequest +{ + public string? Query { get; set; } + + public string? Start { get; set; } + + public string? End { get; set; } + + public string? Step { get; set; } + + public string? TimeOut { get; set; } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/Model/Request/QueryRequest.cs b/src/Data/Masa.Utils.Data.Prometheus/Model/Request/QueryRequest.cs new file mode 100644 index 0000000..b177958 --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/Model/Request/QueryRequest.cs @@ -0,0 +1,13 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Model; + +public class QueryRequest +{ + public string? Query { get; set; } + + public string? Time { get; set; } + + public string? TimeOut { get; set; } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/README.md b/src/Data/Masa.Utils.Data.Prometheus/README.md new file mode 100644 index 0000000..94ad400 --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/README.md @@ -0,0 +1,75 @@ +[中](README.zh-CN.md) | EN + +## Masa.Utils.Data.Prometheus + +[Prometheus Http Api](https://www.prometheus.io/docs/prometheus/latest/querying/api/) Client Library + +## Install: +```c# +Install-Package Masa.Utils.Data.Prometheus +``` + +### Example: + +1. Inject + +```` C# +builder.Services.AddPrometheusClient("http://127.0.0.1:9090"); +```` + +2. Query Example + +```C# +public class SampleService +{ + + private IMasaPrometheusClient _client; + + public SampleService(IMasaPrometheusClient client) + { + _client=client; + } + + public async Task QueryAsync() + { + var query= new QueryRequest { + Query = "up", //metric name + Time = "2022-06-01T09:00:00.000Z" //standard time format or unix timestamp, such as: 1654045200 or 1654045200.000 + }; + var result = await _client.QueryAsync(query); + if(result.Status == ResultStatuses.Success) + { + switch(result.Data.ResultType) + { + case ResultTypes.Vector: + { + var data=result.Data.Result as QueryResultInstantVectorResponse[]; + ... + } + break; + case ResultTypes.Matrix: + { + var data=result.Data.Result as QueryResultMatrixRangeResponse[]; + ... + } + break; + default: + { + var timeSpan=(double)result.Data.Result[0]; + var value=(string)result.Data.Result[1]; + } + break; + } + } + } +} +``` + +### Current suports: + +- [query](https://www.prometheus.io/docs/prometheus/latest/querying/api/#instant-queries) +- [query_range](https://www.prometheus.io/docs/prometheus/latest/querying/api/#range-queries) +- [series](https://www.prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers) +- [lables](https://www.prometheus.io/docs/prometheus/latest/querying/api/#getting-label-names) +- [lable value](https://www.prometheus.io/docs/prometheus/latest/querying/api/#querying-label-values) +- [exemplars](https://www.prometheus.io/docs/prometheus/latest/querying/api/#querying-exemplars) diff --git a/src/Data/Masa.Utils.Data.Prometheus/README.zh-CN.md b/src/Data/Masa.Utils.Data.Prometheus/README.zh-CN.md new file mode 100644 index 0000000..b8c14ab --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/README.zh-CN.md @@ -0,0 +1,75 @@ +[EN](README.md) | 中 + +## Masa.Utils.Data.Prometheus + +[Prometheus Http Api](https://www.prometheus.io/docs/prometheus/latest/querying/api/) 客户端类库 + +## 安装: +```c# +Install-Package Masa.Utils.Data.Prometheus +``` + +### 示例: + +1. 注册 + +```` C# +builder.Services.AddPrometheusClient("http://127.0.0.1:9090"); +```` + +2. 查询样例 + +```C# +public class SampleService +{ + + private IMasaPrometheusClient _client; + + public SampleService(IMasaPrometheusClient client) + { + _client=client; + } + + public async Task QueryAsync() + { + var query= new QueryRequest { + Query = "up", //metric name + Time = "2022-06-01T09:00:00.000Z" //标准时间格式或unix时间戳,如:1654045200或1654045200.000 + }; + var result = await _client.QueryAsync(query); + if(result.Status == ResultStatuses.Success) + { + switch(result.Data.ResultType) + { + case ResultTypes.Vector: + { + var data=result.Data.Result as QueryResultInstantVectorResponse[]; + ... + } + break; + case ResultTypes.Matrix: + { + var data=result.Data.Result as QueryResultMatrixRangeResponse[]; + ... + } + break; + default: + { + var timeSpan=(double)result.Data.Result[0]; + var value=(string)result.Data.Result[1]; + } + break; + } + } + } +} +``` + +### 目前只支持以下api: + +- [query](https://www.prometheus.io/docs/prometheus/latest/querying/api/#instant-queries) +- [query_range](https://www.prometheus.io/docs/prometheus/latest/querying/api/#range-queries) +- [series](https://www.prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers) +- [lables](https://www.prometheus.io/docs/prometheus/latest/querying/api/#getting-label-names) +- [lable value](https://www.prometheus.io/docs/prometheus/latest/querying/api/#querying-label-values) +- [exemplars](https://www.prometheus.io/docs/prometheus/latest/querying/api/#querying-exemplars) diff --git a/src/Data/Masa.Utils.Data.Prometheus/ServiceCollectionExtensions.cs b/src/Data/Masa.Utils.Data.Prometheus/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..3b1ca05 --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/ServiceCollectionExtensions.cs @@ -0,0 +1,39 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus; + +public static class ServiceCollectionExtensions +{ + private const string PROMETHEUS_HTTP_CLIENT_NAME = "prometheus_client_name"; + + public static IServiceCollection AddPrometheusClient(this IServiceCollection services, string url) + { + ArgumentNullException.ThrowIfNull(url, nameof(url)); + + if (services.Any(service => service.GetType() == typeof(IMasaPrometheusClient))) + return services; + + services.AddCaller(builder => + { + builder.UseHttpClient(options => + { + options.BaseAddress = url; + options.Name = PROMETHEUS_HTTP_CLIENT_NAME; + }); + }); + + var jsonOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + jsonOptions.Converters.Add(new JsonStringEnumConverter()); + + services.AddScoped(ServiceProvider => + { + var caller = ServiceProvider.GetRequiredService().CreateClient(PROMETHEUS_HTTP_CLIENT_NAME); + return new MasaPrometheusClient(caller, jsonOptions); + }); + return services; + } +} diff --git a/src/Data/Masa.Utils.Data.Prometheus/_Imports.cs b/src/Data/Masa.Utils.Data.Prometheus/_Imports.cs new file mode 100644 index 0000000..0d8a7ec --- /dev/null +++ b/src/Data/Masa.Utils.Data.Prometheus/_Imports.cs @@ -0,0 +1,15 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +global using Masa.Utils.Caller.Core; +global using Masa.Utils.Caller.HttpClient; +global using Masa.Utils.Data.Prometheus.Enums; +global using Masa.Utils.Data.Prometheus.Model; +global using Microsoft.Extensions.DependencyInjection; +global using System.Collections; +global using System.Reflection; +global using System.Runtime.CompilerServices; +global using System.Text; +global using System.Text.Json; +global using System.Text.Json.Serialization; +global using System.Web; diff --git a/test/Masa.Utils.Data.Prometheus.Test/Extensions/ObjectExtensionsTests.cs b/test/Masa.Utils.Data.Prometheus.Test/Extensions/ObjectExtensionsTests.cs new file mode 100644 index 0000000..988a782 --- /dev/null +++ b/test/Masa.Utils.Data.Prometheus.Test/Extensions/ObjectExtensionsTests.cs @@ -0,0 +1,171 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Test; + +[TestClass] +public class ObjectExtensionsTests +{ + [TestMethod] + [DataRow(1)] + [DataRow((byte)1)] + [DataRow((char)2)] + [DataRow((uint)3)] + [DataRow((long)4)] + [DataRow((float)5.6789)] + [DataRow(5.6789)] + [DataRow("string")] + public void SampleValueTest(object value) + { + var result = value.ToUrlParam(); + var str = value.ToString(); + Assert.AreEqual(str, result); + } + + [TestMethod] + public void StructTest() + { + var user = new UserStruct + { + Name = "Bob", + Age = 30, + Gender = "Male" + }; + + var result = user.ToUrlParam(); + var str = $"age={user.Age}&gender={user.Gender}&name={user.Name}"; + Assert.AreEqual(str, result); + + user.Name = "王占山"; + str = $"age={user.Age}&gender={user.Gender}&name={System.Web.HttpUtility.UrlEncode(user.Name, Encoding.UTF8)}"; + result = user.ToUrlParam(); + Assert.AreEqual(str, result); + } + + [TestMethod] + public void ClassTest() + { + var obj = new + { + a = "test", + d = (float)34.56, + ch = "中文说明", + t = ResultTypes.Scalar + }; + + var result = obj.ToUrlParam(isEnumString: true); + var str = $"a={obj.a}&ch={System.Web.HttpUtility.UrlEncode(obj.ch, Encoding.UTF8)}&d={obj.d}&t={obj.t}"; + Assert.AreEqual(str, result); + + result = obj.ToUrlParam(isEnumString: false); + str = $"a={obj.a}&ch={System.Web.HttpUtility.UrlEncode(obj.ch, Encoding.UTF8)}&d={obj.d}&t={(int)obj.t}"; + Assert.AreEqual(str, result); + + result = obj.ToUrlParam(isEnumString: false, isUrlEncode: false); + str = $"a={obj.a}&ch={obj.ch}&d={obj.d}&t={(int)obj.t}"; + Assert.AreEqual(str, result); + } + + [TestMethod] + public void ArrayTest() + { + var array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }; + var result = array.ToUrlParam(isUrlEncode: false); + var str = string.Join("&[]=", array); + str = $"[]={str}"; + Assert.AreEqual(str, result); + } + + [TestMethod] + public void ObjArrayTest() + { + var array = new QueryRequest[] { + new QueryRequest{ + Query="where1", + Time="2021-01-02", + TimeOut="5s" + }, + new QueryRequest{ + Query="where2", + Time="2021-01-01", + TimeOut="5s" + } + }; + + var result = array.ToUrlParam(isUrlEncode: false); + var str = $"[].query={array[0].Query}&[].time={array[0].Time}&[].timeOut={array[0].TimeOut}"; + str += $"&[].query={array[1].Query}&[].time={array[1].Time}&[].timeOut={array[1].TimeOut}"; + Assert.AreEqual(str, result); + + var obj = new + { + Values = array + }; + result = obj.ToUrlParam(isUrlEncode: false); + str = $"values[].query={array[0].Query}&values[].time={array[0].Time}&values[].timeOut={array[0].TimeOut}"; + str += $"&values[].query={array[1].Query}&values[].time={array[1].Time}&values[].timeOut={array[1].TimeOut}"; + Assert.AreEqual(str, result); + } + + [TestMethod] + public void IEnumberTest() + { + var list = new List { 1, 2, 3, 4, 5, 6, 7, 8 }; + var result = list.ToUrlParam(isUrlEncode: false); + var str = string.Join("&[]=", list); + str = $"[]={str}"; + Assert.AreEqual(str, result); + } + + [TestMethod] + public void ObjListTest() + { + var array = new List { + new QueryRequest{ + Query="where1", + Time="2021-01-02", + TimeOut="5s" + }, + new QueryRequest{ + Query="where2", + Time="2021-01-01", + TimeOut="5s" + } + }; + + var result = array.ToUrlParam(isUrlEncode: false); + var str = $"[].query={array[0].Query}&[].time={array[0].Time}&[].timeOut={array[0].TimeOut}"; + str += $"&[].query={array[1].Query}&[].time={array[1].Time}&[].timeOut={array[1].TimeOut}"; + Assert.AreEqual(str, result); + + var obj = new + { + Values = array + }; + result = obj.ToUrlParam(isUrlEncode: false); + str = $"values[].query={array[0].Query}&values[].time={array[0].Time}&values[].timeOut={array[0].TimeOut}"; + str += $"&values[].query={array[1].Query}&values[].time={array[1].Time}&values[].timeOut={array[1].TimeOut}"; + Assert.AreEqual(str, result); + } + + [TestMethod] + public void KeyValueTest() + { + var dic = new Dictionary { + { "Name","David"}, + {"Age",30 }, + {"Sex","Male" } + }; + + var result = dic.ToUrlParam(); + var builder = new StringBuilder(); + var keys = dic.Keys.ToList(); + keys.Sort(); + foreach (var key in keys) + { + builder.Append($"&{key.ToCamelCase()}={dic[key]}"); + } + builder.Remove(0, 1); + Assert.AreEqual(builder.ToString(), result); + } +} diff --git a/test/Masa.Utils.Data.Prometheus.Test/Extensions/StringExtensionsTests.cs b/test/Masa.Utils.Data.Prometheus.Test/Extensions/StringExtensionsTests.cs new file mode 100644 index 0000000..6c167dc --- /dev/null +++ b/test/Masa.Utils.Data.Prometheus.Test/Extensions/StringExtensionsTests.cs @@ -0,0 +1,30 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Test; + +[TestClass] +public class StringExtensionsTests +{ + [TestMethod] + [DataRow(null)] + [DataRow("")] + [DataRow("name")] + [DataRow("Name")] + [DataRow("N")] + [DataRow("FirstName")] + public void CamelCaseTest(string str) + { + var result = str.ToCamelCase(); + if (string.IsNullOrEmpty(str)) + { + Assert.IsNull(result); + } + else + { + var camelStr = $"{str[0].ToString().ToLower()}{(str.Length - 1 > 0 ? str[1..] : "")}"; + Assert.AreEqual(camelStr, result); + } + + } +} diff --git a/test/Masa.Utils.Data.Prometheus.Test/Masa.Utils.Data.Prometheus.Test.csproj b/test/Masa.Utils.Data.Prometheus.Test/Masa.Utils.Data.Prometheus.Test.csproj new file mode 100644 index 0000000..2b72548 --- /dev/null +++ b/test/Masa.Utils.Data.Prometheus.Test/Masa.Utils.Data.Prometheus.Test.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + enable + + false + + + + + + + + + + + + + + diff --git a/test/Masa.Utils.Data.Prometheus.Test/MasaPrometheusClientTests.cs b/test/Masa.Utils.Data.Prometheus.Test/MasaPrometheusClientTests.cs new file mode 100644 index 0000000..ad48fdf --- /dev/null +++ b/test/Masa.Utils.Data.Prometheus.Test/MasaPrometheusClientTests.cs @@ -0,0 +1,116 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Test; + +[TestClass] +public class MasaPrometheusClientTests +{ + private IMasaPrometheusClient _client; + + [TestInitialize] + public void Initialize() + { + IServiceCollection service = new ServiceCollection(); + service.AddPrometheusClient("http://localhost:9090"); + _client = service.BuildServiceProvider().GetService(); + } + + [TestMethod] + public async Task TestQueryAsync() + { + var result = await _client.QueryAsync(new QueryRequest + { + Query = "up" + }); + Assert.IsNotNull(result); + Assert.AreEqual(result.Status, ResultStatuses.Success); + Assert.IsNotNull(result.Data); + } + + [TestMethod] + public async Task TestQueryVectorAsync() + { + var result = await _client.QueryAsync(new QueryRequest + { + Query = "up" + }); + + if (result != null && result.Data != null && result.Data.Result != null) + { + var data = result.Data.Result as QueryResultInstantVectorResponse[]; + + Assert.IsNotNull(data); + Assert.IsNotNull(data[0].Metric); + Assert.IsNotNull(data[0].Value); + Assert.IsNotNull(data[0].Metric.Keys); + Assert.AreEqual(2, data[0].Value.Length); + } + } + + [TestMethod] + public async Task TestQueryRangeAsync() + { + var result = await _client.QueryRangeAsync(new QueryRangeRequest + { + Query = "up", + Start = "2022-06-17T02:00:00.000Z", + End = "2022-06-17T02:30:00.000Z", + Step = "300s", + }); + Assert.IsNotNull(result); + Assert.AreEqual(result.Status, ResultStatuses.Success); + Assert.IsNotNull(result.Data); + if (result.Data.ResultType == ResultTypes.Matrix) + { + var data = result.Data.Result as QueryResultMatrixRangeResponse[]; + Assert.IsNotNull(data); + Assert.IsNotNull(data[0].Metric); + Assert.IsNotNull(data[0].Values); + } + } + + [TestMethod] + public async Task TestSeriesQueryAsync() + { + var result = await _client.SeriesQueryAsync(new MetaDataQueryRequest + { + Match = new string[] { "up" }, + Start = "2022-06-17T02:00:00.000Z", + End = "2022-06-17T02:30:00.000Z" + }); + Assert.IsNotNull(result); + Assert.AreEqual(result.Status, ResultStatuses.Success); + } + + [TestMethod] + public async Task TestLabelsQueryAsync() + { + var result = await _client.LabelsQueryAsync(default!); + Assert.IsNotNull(result); + Assert.AreEqual(result.Status, ResultStatuses.Success); + } + + [TestMethod] + public async Task TestLabelValuesQueryAsync() + { + var result = await _client.LabelValuesQueryAsync(new LableValueQueryRequest()); + Assert.IsNotNull(result); + Assert.AreEqual(result.Status, ResultStatuses.Success); + } + + [TestMethod] + [DataRow()] + public async Task TestExemplarQueryAsync() + { + var param = new QueryExemplarRequest + { + Query = "up", + Start = "2022-06-17T02:00:00.000Z", + End = "2022-06-17T02:30:00.000Z" + }; + var result = await _client.ExemplarQueryAsync(param); + Assert.IsNotNull(result); + Assert.AreEqual(result.Status, ResultStatuses.Success); + } +} diff --git a/test/Masa.Utils.Data.Prometheus.Test/UserStruct.cs b/test/Masa.Utils.Data.Prometheus.Test/UserStruct.cs new file mode 100644 index 0000000..a1ea1f6 --- /dev/null +++ b/test/Masa.Utils.Data.Prometheus.Test/UserStruct.cs @@ -0,0 +1,13 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +namespace Masa.Utils.Data.Prometheus.Test; + +public struct UserStruct +{ + public string Name { get; set; } + + public int Age { get; set; } + + public string Gender { get; set; } +} diff --git a/test/Masa.Utils.Data.Prometheus.Test/_Imports.cs b/test/Masa.Utils.Data.Prometheus.Test/_Imports.cs new file mode 100644 index 0000000..3b98df1 --- /dev/null +++ b/test/Masa.Utils.Data.Prometheus.Test/_Imports.cs @@ -0,0 +1,12 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +global using Masa.Utils.Data.Prometheus.Enums; +global using Masa.Utils.Data.Prometheus.Model; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using System; +global using System.Collections.Generic; +global using System.Linq; +global using System.Text; +global using System.Threading.Tasks;