metric_value_ut.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. #include "metric_value.h"
  2. #include <library/cpp/testing/unittest/registar.h>
  3. using namespace NMonitoring;
  4. Y_UNIT_TEST_SUITE(TMetricValueTest) {
  5. class TTestHistogram: public IHistogramSnapshot {
  6. public:
  7. TTestHistogram(ui32 count = 1)
  8. : Count_{count}
  9. {}
  10. private:
  11. ui32 Count() const override {
  12. return Count_;
  13. }
  14. TBucketBound UpperBound(ui32 /*index*/) const override {
  15. return 1234.56;
  16. }
  17. TBucketValue Value(ui32 /*index*/) const override {
  18. return 42;
  19. }
  20. ui32 Count_{0};
  21. };
  22. IHistogramSnapshotPtr MakeHistogramSnapshot() {
  23. return MakeIntrusive<TTestHistogram>();
  24. }
  25. ISummaryDoubleSnapshotPtr MakeSummarySnapshot(ui64 count = 0u) {
  26. return MakeIntrusive<TSummaryDoubleSnapshot>(0.0, 0.0, 0.0, 0.0, count);
  27. }
  28. TLogHistogramSnapshotPtr MakeLogHistogram(ui64 count = 0) {
  29. TVector<double> buckets;
  30. for (ui64 i = 0; i < count; ++i) {
  31. buckets.push_back(i);
  32. }
  33. return MakeIntrusive<TLogHistogramSnapshot>(1.5, 0u, 0, buckets);
  34. }
  35. Y_UNIT_TEST(Sorted) {
  36. auto ts1 = TInstant::Now();
  37. auto ts2 = ts1 + TDuration::Seconds(1);
  38. TMetricTimeSeries timeSeries;
  39. timeSeries.Add(ts1, 3.14159);
  40. timeSeries.Add(ts1, 6.28318);
  41. timeSeries.Add(ts2, 2.71828);
  42. UNIT_ASSERT_EQUAL(timeSeries.Size(), 3);
  43. timeSeries.SortByTs();
  44. UNIT_ASSERT_EQUAL(timeSeries.Size(), 2);
  45. UNIT_ASSERT_EQUAL(ts1, timeSeries[0].GetTime());
  46. UNIT_ASSERT_DOUBLES_EQUAL(6.28318, timeSeries[0].GetValue().AsDouble(), Min<double>());
  47. UNIT_ASSERT_EQUAL(ts2, timeSeries[1].GetTime());
  48. UNIT_ASSERT_DOUBLES_EQUAL(2.71828, timeSeries[1].GetValue().AsDouble(), Min<double>());
  49. }
  50. Y_UNIT_TEST(Histograms) {
  51. auto ts = TInstant::Now();
  52. auto histogram = MakeIntrusive<TTestHistogram>();
  53. UNIT_ASSERT_VALUES_EQUAL(1, histogram->RefCount());
  54. {
  55. TMetricTimeSeries timeSeries;
  56. timeSeries.Add(ts, histogram.Get());
  57. UNIT_ASSERT_VALUES_EQUAL(2, histogram->RefCount());
  58. }
  59. UNIT_ASSERT_VALUES_EQUAL(1, histogram->RefCount());
  60. }
  61. Y_UNIT_TEST(Summary) {
  62. auto ts = TInstant::Now();
  63. auto summary = MakeSummarySnapshot();
  64. UNIT_ASSERT_VALUES_EQUAL(1, summary->RefCount());
  65. {
  66. TMetricTimeSeries timeSeries;
  67. timeSeries.Add(ts, summary.Get());
  68. UNIT_ASSERT_VALUES_EQUAL(2, summary->RefCount());
  69. }
  70. UNIT_ASSERT_VALUES_EQUAL(1, summary->RefCount());
  71. }
  72. Y_UNIT_TEST(LogHistogram) {
  73. auto ts = TInstant::Now();
  74. auto logHist = MakeLogHistogram();
  75. UNIT_ASSERT_VALUES_EQUAL(1, logHist->RefCount());
  76. {
  77. TMetricTimeSeries timeSeries;
  78. timeSeries.Add(ts, logHist.Get());
  79. UNIT_ASSERT_VALUES_EQUAL(2, logHist->RefCount());
  80. }
  81. UNIT_ASSERT_VALUES_EQUAL(1, logHist->RefCount());
  82. }
  83. Y_UNIT_TEST(TimeSeriesMovable) {
  84. auto ts = TInstant::Now();
  85. auto histogram = MakeIntrusive<TTestHistogram>();
  86. UNIT_ASSERT_VALUES_EQUAL(1, histogram->RefCount());
  87. {
  88. TMetricTimeSeries timeSeriesA;
  89. timeSeriesA.Add(ts, histogram.Get());
  90. UNIT_ASSERT_VALUES_EQUAL(2, histogram->RefCount());
  91. TMetricTimeSeries timeSeriesB = std::move(timeSeriesA);
  92. UNIT_ASSERT_VALUES_EQUAL(2, histogram->RefCount());
  93. UNIT_ASSERT_VALUES_EQUAL(1, timeSeriesB.Size());
  94. UNIT_ASSERT_EQUAL(EMetricValueType::HISTOGRAM, timeSeriesB.GetValueType());
  95. UNIT_ASSERT_VALUES_EQUAL(0, timeSeriesA.Size());
  96. UNIT_ASSERT_EQUAL(EMetricValueType::UNKNOWN, timeSeriesA.GetValueType());
  97. }
  98. UNIT_ASSERT_VALUES_EQUAL(1, histogram->RefCount());
  99. }
  100. Y_UNIT_TEST(HistogramsUnique) {
  101. auto ts1 = TInstant::Now();
  102. auto ts2 = ts1 + TDuration::Seconds(1);
  103. auto ts3 = ts2 + TDuration::Seconds(1);
  104. auto h1 = MakeIntrusive<TTestHistogram>();
  105. auto h2 = MakeIntrusive<TTestHistogram>();
  106. auto h3 = MakeIntrusive<TTestHistogram>();
  107. UNIT_ASSERT_VALUES_EQUAL(1, h1->RefCount());
  108. UNIT_ASSERT_VALUES_EQUAL(1, h2->RefCount());
  109. UNIT_ASSERT_VALUES_EQUAL(1, h3->RefCount());
  110. {
  111. TMetricTimeSeries timeSeries;
  112. timeSeries.Add(ts1, h1.Get()); // drop at the head
  113. timeSeries.Add(ts1, h1.Get());
  114. timeSeries.Add(ts1, h1.Get());
  115. timeSeries.Add(ts2, h2.Get()); // drop in the middle
  116. timeSeries.Add(ts2, h2.Get());
  117. timeSeries.Add(ts2, h2.Get());
  118. timeSeries.Add(ts3, h3.Get()); // drop at the end
  119. timeSeries.Add(ts3, h3.Get());
  120. timeSeries.Add(ts3, h3.Get());
  121. UNIT_ASSERT_EQUAL(timeSeries.Size(), 9);
  122. UNIT_ASSERT_VALUES_EQUAL(4, h1->RefCount());
  123. UNIT_ASSERT_VALUES_EQUAL(4, h2->RefCount());
  124. UNIT_ASSERT_VALUES_EQUAL(4, h3->RefCount());
  125. timeSeries.SortByTs();
  126. UNIT_ASSERT_EQUAL(timeSeries.Size(), 3);
  127. UNIT_ASSERT_VALUES_EQUAL(2, h1->RefCount());
  128. UNIT_ASSERT_VALUES_EQUAL(2, h2->RefCount());
  129. UNIT_ASSERT_VALUES_EQUAL(2, h3->RefCount());
  130. }
  131. UNIT_ASSERT_VALUES_EQUAL(1, h1->RefCount());
  132. UNIT_ASSERT_VALUES_EQUAL(1, h2->RefCount());
  133. UNIT_ASSERT_VALUES_EQUAL(1, h3->RefCount());
  134. }
  135. Y_UNIT_TEST(LogHistogramsUnique) {
  136. auto ts1 = TInstant::Now();
  137. auto ts2 = ts1 + TDuration::Seconds(1);
  138. auto ts3 = ts2 + TDuration::Seconds(1);
  139. auto h1 = MakeLogHistogram();
  140. auto h2 = MakeLogHistogram();
  141. auto h3 = MakeLogHistogram();
  142. UNIT_ASSERT_VALUES_EQUAL(1, h1->RefCount());
  143. UNIT_ASSERT_VALUES_EQUAL(1, h2->RefCount());
  144. UNIT_ASSERT_VALUES_EQUAL(1, h3->RefCount());
  145. {
  146. TMetricTimeSeries timeSeries;
  147. timeSeries.Add(ts1, h1.Get()); // drop at the head
  148. timeSeries.Add(ts1, h1.Get());
  149. timeSeries.Add(ts1, h1.Get());
  150. timeSeries.Add(ts2, h2.Get()); // drop in the middle
  151. timeSeries.Add(ts2, h2.Get());
  152. timeSeries.Add(ts2, h2.Get());
  153. timeSeries.Add(ts3, h3.Get()); // drop at the end
  154. timeSeries.Add(ts3, h3.Get());
  155. timeSeries.Add(ts3, h3.Get());
  156. UNIT_ASSERT_EQUAL(timeSeries.Size(), 9);
  157. UNIT_ASSERT_VALUES_EQUAL(4, h1->RefCount());
  158. UNIT_ASSERT_VALUES_EQUAL(4, h2->RefCount());
  159. UNIT_ASSERT_VALUES_EQUAL(4, h3->RefCount());
  160. timeSeries.SortByTs();
  161. UNIT_ASSERT_EQUAL(timeSeries.Size(), 3);
  162. UNIT_ASSERT_VALUES_EQUAL(2, h1->RefCount());
  163. UNIT_ASSERT_VALUES_EQUAL(2, h2->RefCount());
  164. UNIT_ASSERT_VALUES_EQUAL(2, h3->RefCount());
  165. }
  166. UNIT_ASSERT_VALUES_EQUAL(1, h1->RefCount());
  167. UNIT_ASSERT_VALUES_EQUAL(1, h2->RefCount());
  168. UNIT_ASSERT_VALUES_EQUAL(1, h3->RefCount());
  169. }
  170. Y_UNIT_TEST(SummaryUnique) {
  171. auto ts1 = TInstant::Now();
  172. auto ts2 = ts1 + TDuration::Seconds(1);
  173. auto ts3 = ts2 + TDuration::Seconds(1);
  174. auto h1 = MakeSummarySnapshot();
  175. auto h2 = MakeSummarySnapshot();
  176. auto h3 = MakeSummarySnapshot();
  177. UNIT_ASSERT_VALUES_EQUAL(1, h1->RefCount());
  178. UNIT_ASSERT_VALUES_EQUAL(1, h2->RefCount());
  179. UNIT_ASSERT_VALUES_EQUAL(1, h3->RefCount());
  180. {
  181. TMetricTimeSeries timeSeries;
  182. timeSeries.Add(ts1, h1.Get()); // drop at the head
  183. timeSeries.Add(ts1, h1.Get());
  184. timeSeries.Add(ts1, h1.Get());
  185. timeSeries.Add(ts2, h2.Get()); // drop in the middle
  186. timeSeries.Add(ts2, h2.Get());
  187. timeSeries.Add(ts2, h2.Get());
  188. timeSeries.Add(ts3, h3.Get()); // drop at the end
  189. timeSeries.Add(ts3, h3.Get());
  190. timeSeries.Add(ts3, h3.Get());
  191. UNIT_ASSERT_EQUAL(timeSeries.Size(), 9);
  192. UNIT_ASSERT_VALUES_EQUAL(4, h1->RefCount());
  193. UNIT_ASSERT_VALUES_EQUAL(4, h2->RefCount());
  194. UNIT_ASSERT_VALUES_EQUAL(4, h3->RefCount());
  195. timeSeries.SortByTs();
  196. UNIT_ASSERT_EQUAL(timeSeries.Size(), 3);
  197. UNIT_ASSERT_VALUES_EQUAL(2, h1->RefCount());
  198. UNIT_ASSERT_VALUES_EQUAL(2, h2->RefCount());
  199. UNIT_ASSERT_VALUES_EQUAL(2, h3->RefCount());
  200. }
  201. UNIT_ASSERT_VALUES_EQUAL(1, h1->RefCount());
  202. UNIT_ASSERT_VALUES_EQUAL(1, h2->RefCount());
  203. UNIT_ASSERT_VALUES_EQUAL(1, h3->RefCount());
  204. }
  205. Y_UNIT_TEST(HistogramsUnique2) {
  206. auto ts1 = TInstant::Now();
  207. auto ts2 = ts1 + TDuration::Seconds(1);
  208. auto ts3 = ts2 + TDuration::Seconds(1);
  209. auto ts4 = ts3 + TDuration::Seconds(1);
  210. auto ts5 = ts4 + TDuration::Seconds(1);
  211. auto h1 = MakeIntrusive<TTestHistogram>(1u);
  212. auto h2 = MakeIntrusive<TTestHistogram>(2u);
  213. auto h3 = MakeIntrusive<TTestHistogram>(3u);
  214. auto h4 = MakeIntrusive<TTestHistogram>(4u);
  215. auto h5 = MakeIntrusive<TTestHistogram>(5u);
  216. auto h6 = MakeIntrusive<TTestHistogram>(6u);
  217. auto h7 = MakeIntrusive<TTestHistogram>(7u);
  218. {
  219. TMetricTimeSeries timeSeries;
  220. timeSeries.Add(ts1, h1.Get());
  221. timeSeries.Add(ts1, h2.Get());
  222. timeSeries.Add(ts2, h3.Get());
  223. timeSeries.Add(ts3, h4.Get());
  224. timeSeries.Add(ts3, h5.Get());
  225. timeSeries.Add(ts4, h6.Get());
  226. timeSeries.Add(ts5, h7.Get());
  227. timeSeries.SortByTs();
  228. UNIT_ASSERT_EQUAL(timeSeries.Size(), 5);
  229. UNIT_ASSERT_EQUAL(timeSeries[0].GetValue().AsHistogram()->Count(), 2);
  230. UNIT_ASSERT_EQUAL(timeSeries[1].GetValue().AsHistogram()->Count(), 3);
  231. UNIT_ASSERT_EQUAL(timeSeries[2].GetValue().AsHistogram()->Count(), 5);
  232. UNIT_ASSERT_EQUAL(timeSeries[3].GetValue().AsHistogram()->Count(), 6);
  233. UNIT_ASSERT_EQUAL(timeSeries[4].GetValue().AsHistogram()->Count(), 7);
  234. }
  235. }
  236. Y_UNIT_TEST(LogHistogramsUnique2) {
  237. auto ts1 = TInstant::Now();
  238. auto ts2 = ts1 + TDuration::Seconds(1);
  239. auto ts3 = ts2 + TDuration::Seconds(1);
  240. auto ts4 = ts3 + TDuration::Seconds(1);
  241. auto ts5 = ts4 + TDuration::Seconds(1);
  242. auto h1 = MakeLogHistogram(1u);
  243. auto h2 = MakeLogHistogram(2u);
  244. auto h3 = MakeLogHistogram(3u);
  245. auto h4 = MakeLogHistogram(4u);
  246. auto h5 = MakeLogHistogram(5u);
  247. auto h6 = MakeLogHistogram(6u);
  248. auto h7 = MakeLogHistogram(7u);
  249. {
  250. TMetricTimeSeries timeSeries;
  251. timeSeries.Add(ts1, h1.Get());
  252. timeSeries.Add(ts1, h2.Get());
  253. timeSeries.Add(ts2, h3.Get());
  254. timeSeries.Add(ts3, h4.Get());
  255. timeSeries.Add(ts3, h5.Get());
  256. timeSeries.Add(ts4, h6.Get());
  257. timeSeries.Add(ts5, h7.Get());
  258. timeSeries.SortByTs();
  259. UNIT_ASSERT_EQUAL(timeSeries.Size(), 5);
  260. UNIT_ASSERT_EQUAL(timeSeries[0].GetValue().AsLogHistogram()->Count(), 2);
  261. UNIT_ASSERT_EQUAL(timeSeries[1].GetValue().AsLogHistogram()->Count(), 3);
  262. UNIT_ASSERT_EQUAL(timeSeries[2].GetValue().AsLogHistogram()->Count(), 5);
  263. UNIT_ASSERT_EQUAL(timeSeries[3].GetValue().AsLogHistogram()->Count(), 6);
  264. UNIT_ASSERT_EQUAL(timeSeries[4].GetValue().AsLogHistogram()->Count(), 7);
  265. }
  266. }
  267. Y_UNIT_TEST(SummaryUnique2) {
  268. auto ts1 = TInstant::Now();
  269. auto ts2 = ts1 + TDuration::Seconds(1);
  270. auto ts3 = ts2 + TDuration::Seconds(1);
  271. auto ts4 = ts3 + TDuration::Seconds(1);
  272. auto ts5 = ts4 + TDuration::Seconds(1);
  273. auto h1 = MakeSummarySnapshot(1u);
  274. auto h2 = MakeSummarySnapshot(2u);
  275. auto h3 = MakeSummarySnapshot(3u);
  276. auto h4 = MakeSummarySnapshot(4u);
  277. auto h5 = MakeSummarySnapshot(5u);
  278. auto h6 = MakeSummarySnapshot(6u);
  279. auto h7 = MakeSummarySnapshot(7u);
  280. {
  281. TMetricTimeSeries timeSeries;
  282. timeSeries.Add(ts1, h1.Get());
  283. timeSeries.Add(ts1, h2.Get());
  284. timeSeries.Add(ts2, h3.Get());
  285. timeSeries.Add(ts3, h4.Get());
  286. timeSeries.Add(ts3, h5.Get());
  287. timeSeries.Add(ts4, h6.Get());
  288. timeSeries.Add(ts5, h7.Get());
  289. timeSeries.SortByTs();
  290. UNIT_ASSERT_EQUAL(timeSeries.Size(), 5);
  291. UNIT_ASSERT_EQUAL(timeSeries[0].GetValue().AsSummaryDouble()->GetCount(), 2);
  292. UNIT_ASSERT_EQUAL(timeSeries[1].GetValue().AsSummaryDouble()->GetCount(), 3);
  293. UNIT_ASSERT_EQUAL(timeSeries[2].GetValue().AsSummaryDouble()->GetCount(), 5);
  294. UNIT_ASSERT_EQUAL(timeSeries[3].GetValue().AsSummaryDouble()->GetCount(), 6);
  295. UNIT_ASSERT_EQUAL(timeSeries[4].GetValue().AsSummaryDouble()->GetCount(), 7);
  296. }
  297. }
  298. Y_UNIT_TEST(TMetricValueWithType) {
  299. // correct usage
  300. {
  301. double value = 1.23;
  302. TMetricValueWithType v{value};
  303. UNIT_ASSERT_VALUES_EQUAL(v.GetType(), EMetricValueType::DOUBLE);
  304. UNIT_ASSERT_VALUES_EQUAL(v.AsDouble(), value);
  305. }
  306. {
  307. ui64 value = 12;
  308. TMetricValueWithType v{value};
  309. UNIT_ASSERT_VALUES_EQUAL(v.GetType(), EMetricValueType::UINT64);
  310. UNIT_ASSERT_VALUES_EQUAL(v.AsUint64(), value);
  311. }
  312. {
  313. i64 value = i64(-12);
  314. TMetricValueWithType v{value};
  315. UNIT_ASSERT_VALUES_EQUAL(v.GetType(), EMetricValueType::INT64);
  316. UNIT_ASSERT_VALUES_EQUAL(v.AsInt64(), value);
  317. }
  318. {
  319. auto h = MakeHistogramSnapshot();
  320. UNIT_ASSERT_VALUES_EQUAL(h.RefCount(), 1);
  321. {
  322. auto value = h.Get();
  323. TMetricValueWithType v{value};
  324. UNIT_ASSERT_VALUES_EQUAL(h.RefCount(), 2);
  325. UNIT_ASSERT_VALUES_EQUAL(v.GetType(), EMetricValueType::HISTOGRAM);
  326. UNIT_ASSERT_VALUES_EQUAL(v.AsHistogram(), value);
  327. }
  328. UNIT_ASSERT_VALUES_EQUAL(h.RefCount(), 1);
  329. }
  330. {
  331. auto s = MakeSummarySnapshot();
  332. auto value = s.Get();
  333. UNIT_ASSERT_VALUES_EQUAL(s.RefCount(), 1);
  334. {
  335. TMetricValueWithType v{value};
  336. UNIT_ASSERT_VALUES_EQUAL(s.RefCount(), 2);
  337. UNIT_ASSERT_VALUES_EQUAL(v.GetType(), EMetricValueType::SUMMARY);
  338. UNIT_ASSERT_VALUES_EQUAL(v.AsSummaryDouble(), value);
  339. }
  340. UNIT_ASSERT_VALUES_EQUAL(s.RefCount(), 1);
  341. }
  342. {
  343. auto s = MakeSummarySnapshot();
  344. auto value = s.Get();
  345. UNIT_ASSERT_VALUES_EQUAL(s.RefCount(), 1);
  346. {
  347. TMetricValueWithType v{value};
  348. UNIT_ASSERT_VALUES_EQUAL(s.RefCount(), 2);
  349. v.Clear();
  350. UNIT_ASSERT_VALUES_EQUAL(s.RefCount(), 1);
  351. }
  352. UNIT_ASSERT_VALUES_EQUAL(s.RefCount(), 1);
  353. }
  354. {
  355. auto s = MakeSummarySnapshot();
  356. auto value = s.Get();
  357. {
  358. TMetricValueWithType v1{ui64{1}};
  359. UNIT_ASSERT_VALUES_EQUAL(s.RefCount(), 1);
  360. {
  361. TMetricValueWithType v2{value};
  362. UNIT_ASSERT_VALUES_EQUAL(s.RefCount(), 2);
  363. v1 = std::move(v2);
  364. UNIT_ASSERT_VALUES_EQUAL(s.RefCount(), 2);
  365. UNIT_ASSERT_VALUES_EQUAL(v1.AsSummaryDouble(), value);
  366. UNIT_ASSERT_VALUES_EQUAL(v1.GetType(), EMetricValueType::SUMMARY);
  367. UNIT_ASSERT_VALUES_EQUAL(v2.GetType(), EMetricValueType::UNKNOWN);
  368. }
  369. UNIT_ASSERT_VALUES_EQUAL(s.RefCount(), 2);
  370. }
  371. UNIT_ASSERT_VALUES_EQUAL(s.RefCount(), 1);
  372. }
  373. // incorrect usage
  374. {
  375. TMetricValueWithType v{1.23};
  376. UNIT_ASSERT_EXCEPTION(v.AsHistogram(), yexception);
  377. UNIT_ASSERT_EXCEPTION(v.AsSummaryDouble(), yexception);
  378. }
  379. {
  380. auto h = MakeHistogramSnapshot();
  381. TMetricValueWithType v{h.Get()};
  382. UNIT_ASSERT_EXCEPTION(v.AsUint64(), yexception);
  383. UNIT_ASSERT_EXCEPTION(v.AsInt64(), yexception);
  384. UNIT_ASSERT_EXCEPTION(v.AsDouble(), yexception);
  385. UNIT_ASSERT_EXCEPTION(v.AsSummaryDouble(), yexception);
  386. }
  387. {
  388. auto s = MakeSummarySnapshot();
  389. TMetricValueWithType v{s.Get()};
  390. UNIT_ASSERT_EXCEPTION(v.AsUint64(), yexception);
  391. UNIT_ASSERT_EXCEPTION(v.AsInt64(), yexception);
  392. UNIT_ASSERT_EXCEPTION(v.AsDouble(), yexception);
  393. UNIT_ASSERT_EXCEPTION(v.AsHistogram(), yexception);
  394. }
  395. }
  396. }