FeatureController.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. using molilian.core;
  2. using dodohold.core;
  3. using Microsoft.AspNetCore.Mvc;
  4. using System.Text;
  5. using System.Text.Json;
  6. using System.Collections.Concurrent;
  7. using System.Diagnostics;
  8. namespace molilian.api.Controllers
  9. {
  10. [ApiController]
  11. [Route("[controller]/[action]")]
  12. public class FeatureController : ControllerBase
  13. {
  14. protected IHttpContextAccessor _accessor;
  15. public FeatureController(IHttpContextAccessor accessor)
  16. {
  17. _accessor = accessor;
  18. }
  19. [HttpGet]
  20. public async Task<ActionResult> CSRedis(int thread, int durationSeconds = 30)
  21. {
  22. var watch = System.Diagnostics.Stopwatch.StartNew();
  23. var cts = new CancellationTokenSource();
  24. var performanceLogger = new RedisPerformanceLogger();
  25. long totalRequests = 0;
  26. long successCount = 0;
  27. long failureCount = 0;
  28. var metricsQueue = new ConcurrentQueue<RedisOperationMetrics>();
  29. var tasks = new List<Task>();
  30. for (int i = 0; i < thread; i++)
  31. {
  32. tasks.Add(Task.Run(() =>
  33. {
  34. while (!cts.Token.IsCancellationRequested)
  35. {
  36. var metrics = new RedisOperationMetrics();
  37. try
  38. {
  39. // 写入操作
  40. var sw = Stopwatch.StartNew();
  41. var testValue = "test value";
  42. var key = $"test:csredis:{Guid.NewGuid()}";
  43. RedisHelper.Set(key, testValue, 60);
  44. sw.Stop();
  45. metrics.WriteTimeMs = sw.ElapsedMilliseconds;
  46. metrics.KeySize = Encoding.UTF8.GetByteCount(key);
  47. metrics.ValueSize = Encoding.UTF8.GetByteCount(testValue);
  48. // IncrBy操作
  49. sw.Restart();
  50. RedisHelper.IncrBy("counter:csredis", 1);
  51. sw.Stop();
  52. metrics.IncrByTimeMs = sw.ElapsedMilliseconds;
  53. // 读取操作
  54. sw.Restart();
  55. var value = RedisHelper.Get("counter:csredis");
  56. sw.Stop();
  57. metrics.ReadTimeMs = sw.ElapsedMilliseconds;
  58. metrics.IsSuccess = true;
  59. Interlocked.Increment(ref successCount);
  60. }
  61. catch (Exception ex)
  62. {
  63. metrics.IsSuccess = false;
  64. metrics.ErrorMessage = ex.Message;
  65. Interlocked.Increment(ref failureCount);
  66. string message = $"{ex.Message}\n{ex.StackTrace}";
  67. NotifyCore.Notify(new NifyMessage
  68. {
  69. message = message,
  70. priority = NifyMessagePriority.high,
  71. tags = ["red_circle"]
  72. });
  73. new LoggerLibrary("Feature").Info(message).SaveAsync();
  74. }
  75. finally
  76. {
  77. metricsQueue.Enqueue(metrics);
  78. Interlocked.Increment(ref totalRequests);
  79. }
  80. }
  81. }, cts.Token));
  82. }
  83. await Task.Delay(TimeSpan.FromSeconds(durationSeconds));
  84. cts.Cancel();
  85. try
  86. {
  87. await Task.WhenAll(tasks);
  88. }
  89. catch (OperationCanceledException)
  90. {
  91. // 预期的取消异常
  92. }
  93. watch.Stop();
  94. var elapsedMs = watch.ElapsedMilliseconds;
  95. var qps = totalRequests * 1000.0 / elapsedMs;
  96. var metrics = metricsQueue.ToList();
  97. var writeStats = RedisOperationMetrics.CalculateStatistics(metrics, m => m.WriteTimeMs);
  98. var incrByStats = RedisOperationMetrics.CalculateStatistics(metrics, m => m.IncrByTimeMs);
  99. var readStats = RedisOperationMetrics.CalculateStatistics(metrics, m => m.ReadTimeMs);
  100. var resultData = new Dictionary<string, object>
  101. {
  102. ["ConcurrentThreads"] = thread,
  103. ["DurationSeconds"] = durationSeconds,
  104. ["TotalTimeMs"] = elapsedMs,
  105. ["TotalRequests"] = totalRequests,
  106. ["SuccessCount"] = successCount,
  107. ["FailureCount"] = failureCount,
  108. ["QPS"] = Math.Round(qps, 2),
  109. ["SuccessRate"] = Math.Round((double)successCount / totalRequests * 100, 2),
  110. ["RedisMetrics"] = new
  111. {
  112. Write = writeStats,
  113. IncrBy = incrByStats,
  114. Read = readStats
  115. }
  116. };
  117. await performanceLogger.LogPerformanceResult("CSRedis", resultData);
  118. return new APIResult(resultData);
  119. }
  120. [HttpGet]
  121. public async Task<ActionResult> FreeRedis(int thread, int durationSeconds = 30)
  122. {
  123. var watch = System.Diagnostics.Stopwatch.StartNew();
  124. var cts = new CancellationTokenSource();
  125. var performanceLogger = new RedisPerformanceLogger();
  126. long totalRequests = 0;
  127. long successCount = 0;
  128. long failureCount = 0;
  129. var metricsQueue = new ConcurrentQueue<RedisOperationMetrics>();
  130. var redis = YunhuiKit.RedisKit.Instance;
  131. var tasks = new List<Task>();
  132. for (int i = 0; i < thread; i++)
  133. {
  134. tasks.Add(Task.Run(() =>
  135. {
  136. while (!cts.Token.IsCancellationRequested)
  137. {
  138. var metrics = new RedisOperationMetrics();
  139. try
  140. {
  141. // 写入操作
  142. var sw = Stopwatch.StartNew();
  143. var testValue = "test value";
  144. var key = $"test:freeredis:{Guid.NewGuid()}";
  145. redis.Set(key, testValue, 60);
  146. sw.Stop();
  147. metrics.WriteTimeMs = sw.ElapsedMilliseconds;
  148. metrics.KeySize = Encoding.UTF8.GetByteCount(key);
  149. metrics.ValueSize = Encoding.UTF8.GetByteCount(testValue);
  150. // IncrBy操作
  151. sw.Restart();
  152. redis.IncrBy("counter:freeredis", 1);
  153. sw.Stop();
  154. metrics.IncrByTimeMs = sw.ElapsedMilliseconds;
  155. // 读取操作
  156. sw.Restart();
  157. var value = redis.Get("counter:freeredis");
  158. sw.Stop();
  159. metrics.ReadTimeMs = sw.ElapsedMilliseconds;
  160. metrics.IsSuccess = true;
  161. Interlocked.Increment(ref successCount);
  162. }
  163. catch (Exception ex)
  164. {
  165. metrics.IsSuccess = false;
  166. metrics.ErrorMessage = ex.Message;
  167. Interlocked.Increment(ref failureCount);
  168. string message = $"{ex.Message}\n{ex.StackTrace}";
  169. NotifyCore.Notify(new NifyMessage
  170. {
  171. message = message,
  172. priority = NifyMessagePriority.high,
  173. tags = ["red_circle"]
  174. });
  175. new LoggerLibrary("Feature").Info(message).SaveAsync();
  176. }
  177. finally
  178. {
  179. metricsQueue.Enqueue(metrics);
  180. Interlocked.Increment(ref totalRequests);
  181. }
  182. }
  183. }, cts.Token));
  184. }
  185. await Task.Delay(TimeSpan.FromSeconds(durationSeconds));
  186. cts.Cancel();
  187. try
  188. {
  189. await Task.WhenAll(tasks);
  190. }
  191. catch (OperationCanceledException)
  192. {
  193. // 预期的取消异常
  194. }
  195. watch.Stop();
  196. var elapsedMs = watch.ElapsedMilliseconds;
  197. var qps = totalRequests * 1000.0 / elapsedMs;
  198. var metrics = metricsQueue.ToList();
  199. var writeStats = RedisOperationMetrics.CalculateStatistics(metrics, m => m.WriteTimeMs);
  200. var incrByStats = RedisOperationMetrics.CalculateStatistics(metrics, m => m.IncrByTimeMs);
  201. var readStats = RedisOperationMetrics.CalculateStatistics(metrics, m => m.ReadTimeMs);
  202. var resultData = new Dictionary<string, object>
  203. {
  204. ["ConcurrentThreads"] = thread,
  205. ["DurationSeconds"] = durationSeconds,
  206. ["TotalTimeMs"] = elapsedMs,
  207. ["TotalRequests"] = totalRequests,
  208. ["SuccessCount"] = successCount,
  209. ["FailureCount"] = failureCount,
  210. ["QPS"] = Math.Round(qps, 2),
  211. ["SuccessRate"] = Math.Round((double)successCount / totalRequests * 100, 2),
  212. ["RedisMetrics"] = new
  213. {
  214. Write = writeStats,
  215. IncrBy = incrByStats,
  216. Read = readStats
  217. }
  218. };
  219. await performanceLogger.LogPerformanceResult("FreeRedis", resultData);
  220. return new APIResult(resultData);
  221. }
  222. }
  223. public class RedisPerformanceLogger
  224. {
  225. private readonly LoggerLibrary _logger;
  226. private const string LoggerName = "RedisPerformance";
  227. public RedisPerformanceLogger()
  228. {
  229. _logger = new LoggerLibrary(LoggerName);
  230. }
  231. public async Task LogPerformanceResult(string clientType, Dictionary<string, object> metrics)
  232. {
  233. var logMessage = new StringBuilder();
  234. logMessage.AppendLine($"Redis Performance Test Result - {clientType}");
  235. logMessage.AppendLine($"DateTime: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
  236. foreach (var kvp in metrics)
  237. {
  238. logMessage.AppendLine($"{kvp.Key}: {JsonSerializer.Serialize(kvp.Value, new JsonSerializerOptions
  239. {
  240. WriteIndented = true
  241. })}");
  242. }
  243. await _logger.Info(logMessage.ToString()).SaveAsync();
  244. }
  245. }
  246. public class RedisOperationMetrics
  247. {
  248. public long WriteTimeMs { get; set; }
  249. public long IncrByTimeMs { get; set; }
  250. public long ReadTimeMs { get; set; }
  251. public DateTime OperationTime { get; set; } = DateTime.Now;
  252. public string OperationType { get; set; }
  253. public bool IsSuccess { get; set; }
  254. public string ErrorMessage { get; set; }
  255. public int RetryCount { get; set; }
  256. public long KeySize { get; set; }
  257. public long ValueSize { get; set; }
  258. public static double CalculatePercentile(List<long> times, double percentile)
  259. {
  260. if (times == null || times.Count == 0) return 0;
  261. var sortedTimes = times.OrderBy(t => t).ToList();
  262. var index = (int)Math.Ceiling(percentile / 100.0 * sortedTimes.Count) - 1;
  263. return sortedTimes[Math.Max(0, index)];
  264. }
  265. public static OperationStatistics CalculateStatistics(IEnumerable<RedisOperationMetrics> metrics, Func<RedisOperationMetrics, long> timeSelector)
  266. {
  267. var times = metrics.Select(timeSelector).ToList();
  268. return new OperationStatistics
  269. {
  270. Average = times.Any() ? Math.Round(times.Average(), 2) : 0,
  271. Median = CalculatePercentile(times, 50),
  272. P95 = CalculatePercentile(times, 95),
  273. P99 = CalculatePercentile(times, 99),
  274. Max = times.Any() ? times.Max() : 0,
  275. Min = times.Any() ? times.Min() : 0,
  276. Count = times.Count,
  277. TotalTime = times.Sum()
  278. };
  279. }
  280. }
  281. public class OperationStatistics
  282. {
  283. public double Average { get; set; }
  284. public double Median { get; set; }
  285. public double P95 { get; set; }
  286. public double P99 { get; set; }
  287. public long Max { get; set; }
  288. public long Min { get; set; }
  289. public int Count { get; set; }
  290. public long TotalTime { get; set; }
  291. }
  292. }