opencost_codecs_test.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  1. package opencost
  2. import (
  3. "bytes"
  4. "io"
  5. "os"
  6. "sync/atomic"
  7. "testing"
  8. "time"
  9. )
  10. type UnmarshalFunc func(BingenUnmarshalable, []byte) error
  11. func RunAllocation_BinaryEncodingTest(t *testing.T, unmarshal UnmarshalFunc) {
  12. // TODO niko
  13. }
  14. func RunAllocationSet_BinaryEncodingTest(t *testing.T, unmarshal UnmarshalFunc) {
  15. end := time.Now().UTC().Truncate(day)
  16. start := end.Add(-day * 10)
  17. for start.Before(end) {
  18. set0 := GenerateMockAllocationSetClusterIdle(start)
  19. bytes, err := set0.MarshalBinary()
  20. if err != nil {
  21. t.Fatalf("Failed to AllocationSet.MarshalBinary: %s", err)
  22. return
  23. }
  24. set1 := new(AllocationSet)
  25. err = unmarshal(set1, bytes)
  26. if err != nil {
  27. t.Fatalf("Failed to AllocationSet.UnmarshalBinary: %s", err)
  28. return
  29. }
  30. for key, alloc := range set1.Allocations {
  31. other, ok := set0.Allocations[key]
  32. if !ok {
  33. t.Fatalf("Failed to match Allocation for key: %s", key)
  34. return
  35. }
  36. if !alloc.Equal(other) {
  37. t.Fatalf("allocations for key: %s did not match", key)
  38. }
  39. }
  40. start = start.Add(day)
  41. }
  42. }
  43. func BenchmarkAllocationSetRange_BinaryEncoding(b *testing.B) {
  44. endYesterday := time.Now().UTC().Truncate(day)
  45. startYesterday := endYesterday.Add(-day)
  46. startD2 := startYesterday
  47. startD1 := startD2.Add(-day)
  48. startD0 := startD1.Add(-day)
  49. var asr0, asr1 *AllocationSetRange
  50. var bs []byte
  51. var err error
  52. asr0 = NewAllocationSetRange(
  53. GenerateMockAllocationSetClusterIdle(startD0),
  54. GenerateMockAllocationSetClusterIdle(startD1),
  55. GenerateMockAllocationSetClusterIdle(startD2),
  56. )
  57. for it := 0; it < b.N; it++ {
  58. bs, err = asr0.MarshalBinary()
  59. if err != nil {
  60. b.Fatalf("AllocationSetRange.Binary: unexpected error: %s", err)
  61. return
  62. }
  63. asr1 = &AllocationSetRange{}
  64. err = asr1.UnmarshalBinary(bs)
  65. if err != nil {
  66. b.Fatalf("AllocationSetRange.Binary: unexpected error: %s", err)
  67. return
  68. }
  69. if asr0.Length() != asr1.Length() {
  70. b.Fatalf("AllocationSetRange.Binary: expected %d; found %d", asr0.Length(), asr1.Length())
  71. }
  72. if !asr0.Window().Equal(asr1.Window()) {
  73. b.Fatalf("AllocationSetRange.Binary: expected %s; found %s", asr0.Window(), asr1.Window())
  74. }
  75. for i, as0 := range asr0.Allocations {
  76. as1, err := asr1.Get(i)
  77. if err != nil {
  78. b.Fatalf("AllocationSetRange.Binary: unexpected error: %s", err)
  79. }
  80. if as0.Length() != as1.Length() {
  81. b.Fatalf("AllocationSetRange.Binary: expected %d; found %d", as0.Length(), as1.Length())
  82. }
  83. if !as0.Window.Equal(as1.Window) {
  84. b.Fatalf("AllocationSetRange.Binary: expected %s; found %s", as0.Window, as1.Window)
  85. }
  86. for k, a0 := range as0.Allocations {
  87. a1 := as1.Get(k)
  88. if a1 == nil {
  89. b.Fatalf("AllocationSetRange.Binary: missing Allocation: %s", a0)
  90. }
  91. if !a0.Equal(a1) {
  92. b.Fatalf("AllocationSetRange.Binary: unequal Allocations \"%s\": expected %s; found %s", k, a0, a1)
  93. }
  94. }
  95. }
  96. }
  97. }
  98. func RunAllocationSetRange_BinaryEncodingTest(t *testing.T, unmarshal UnmarshalFunc) {
  99. endYesterday := time.Now().UTC().Truncate(day)
  100. startYesterday := endYesterday.Add(-day)
  101. startD2 := startYesterday
  102. startD1 := startD2.Add(-day)
  103. startD0 := startD1.Add(-day)
  104. var asr0, asr1 *AllocationSetRange
  105. var err error
  106. asr0 = NewAllocationSetRange(
  107. GenerateMockAllocationSetClusterIdle(startD0),
  108. GenerateMockAllocationSetClusterIdle(startD1),
  109. GenerateMockAllocationSetClusterIdle(startD2),
  110. )
  111. asrSets0 := [][]byte{}
  112. for _, as := range asr0.Allocations {
  113. bytes, err := as.MarshalBinary()
  114. if err != nil {
  115. t.Fatalf("Failed to marshal allocation set into []byte: %s", err)
  116. return
  117. }
  118. asrSets0 = append(asrSets0, bytes)
  119. }
  120. asrSets1 := []*AllocationSet{}
  121. for _, bytes := range asrSets0 {
  122. allocSet := new(AllocationSet)
  123. err = unmarshal(allocSet, bytes)
  124. if err != nil {
  125. t.Fatalf("AllocationSet.Binary: unexpected error: %s", err)
  126. return
  127. }
  128. asrSets1 = append(asrSets1, allocSet)
  129. }
  130. asr1 = NewAllocationSetRange(asrSets1...)
  131. if asr0.Length() != asr1.Length() {
  132. t.Fatalf("AllocationSetRange.Binary: expected %d; found %d", asr0.Length(), asr1.Length())
  133. }
  134. if !asr0.Window().Equal(asr1.Window()) {
  135. t.Fatalf("AllocationSetRange.Binary: expected %s; found %s", asr0.Window(), asr1.Window())
  136. }
  137. for i, as0 := range asr0.Allocations {
  138. as1, err := asr1.Get(i)
  139. if err != nil {
  140. t.Fatalf("AllocationSetRange.Binary: unexpected error: %s", err)
  141. }
  142. if as0.Length() != as1.Length() {
  143. t.Fatalf("AllocationSetRange.Binary: expected %d; found %d", as0.Length(), as1.Length())
  144. }
  145. if !as0.Window.Equal(as1.Window) {
  146. t.Fatalf("AllocationSetRange.Binary: expected %s; found %s", as0.Window, as1.Window)
  147. }
  148. for k, a0 := range as0.Allocations {
  149. a1 := as1.Get(k)
  150. if a1 == nil {
  151. t.Fatalf("AllocationSetRange.Binary: missing Allocation: %s", a0)
  152. }
  153. // TODO Sean: fix JSON marshaling of PVs
  154. a1.PVs = a0.PVs
  155. if !a0.Equal(a1) {
  156. t.Fatalf("AllocationSetRange.Binary: unequal Allocations \"%s\": expected \"%s\"; found \"%s\"", k, a0, a1)
  157. }
  158. }
  159. }
  160. }
  161. func RunAny_BinaryEncodingTest(t *testing.T, unmarshal UnmarshalFunc) {
  162. start := time.Date(2020, time.September, 16, 0, 0, 0, 0, time.UTC)
  163. end := start.Add(24 * time.Hour)
  164. window := NewWindow(&start, &end)
  165. var a0, a1 *Any
  166. var bs []byte
  167. var err error
  168. a0 = NewAsset(*window.start, *window.end, window)
  169. a0.SetProperties(&AssetProperties{
  170. Name: "any1",
  171. Cluster: "cluster1",
  172. ProviderID: "世界",
  173. })
  174. a0.Cost = 123.45
  175. a0.SetAdjustment(1.23)
  176. bs, err = a0.MarshalBinary()
  177. if err != nil {
  178. t.Fatalf("Any.Binary: unexpected error: %s", err)
  179. }
  180. a1 = &Any{}
  181. err = unmarshal(a1, bs)
  182. if err != nil {
  183. t.Fatalf("Any.Binary: unexpected error: %s", err)
  184. }
  185. if a1.Properties.Name != a0.Properties.Name {
  186. t.Fatalf("Any.Binary: expected %s, found %s", a0.Properties.Name, a1.Properties.Name)
  187. }
  188. if a1.Properties.Cluster != a0.Properties.Cluster {
  189. t.Fatalf("Any.Binary: expected %s, found %s", a0.Properties.Cluster, a1.Properties.Cluster)
  190. }
  191. if a1.Properties.ProviderID != a0.Properties.ProviderID {
  192. t.Fatalf("Any.Binary: expected %s, found %s", a0.Properties.ProviderID, a1.Properties.ProviderID)
  193. }
  194. if a1.Adjustment != a0.Adjustment {
  195. t.Fatalf("Any.Binary: expected %f, found %f", a0.Adjustment, a1.Adjustment)
  196. }
  197. if a1.TotalCost() != a0.TotalCost() {
  198. t.Fatalf("Any.Binary: expected %f, found %f", a0.TotalCost(), a1.TotalCost())
  199. }
  200. if !a1.Window.Equal(a0.Window) {
  201. t.Fatalf("Any.Binary: expected %s, found %s", a0.Window, a1.Window)
  202. }
  203. }
  204. func RunAsset_BinaryEncodingTest(t *testing.T, unmarshal UnmarshalFunc) {
  205. // TODO niko
  206. }
  207. func RunAssetSet_BinaryEncodingTest(t *testing.T, unmarshal UnmarshalFunc) {
  208. // TODO niko
  209. }
  210. func RunAssetSetRange_BinaryEncodingTest(t *testing.T, unmarshal UnmarshalFunc) {
  211. endYesterday := time.Now().UTC().Truncate(day)
  212. startYesterday := endYesterday.Add(-day)
  213. startD2 := startYesterday
  214. startD1 := startD2.Add(-day)
  215. startD0 := startD1.Add(-day)
  216. var asr0, asr1 *AssetSetRange
  217. var bs []byte
  218. var err error
  219. asr0 = NewAssetSetRange(
  220. GenerateMockAssetSet(startD0, day),
  221. GenerateMockAssetSet(startD1, day),
  222. GenerateMockAssetSet(startD2, day),
  223. )
  224. bs, err = asr0.MarshalBinary()
  225. if err != nil {
  226. t.Fatalf("AssetSetRange.Binary: unexpected error: %s", err)
  227. return
  228. }
  229. asr1 = &AssetSetRange{}
  230. err = unmarshal(asr1, bs)
  231. if err != nil {
  232. t.Fatalf("AssetSetRange.Binary: unexpected error: %s", err)
  233. return
  234. }
  235. if asr0.Length() != asr1.Length() {
  236. t.Fatalf("AssetSetRange.Binary: expected %d; found %d", asr0.Length(), asr1.Length())
  237. }
  238. if !asr0.Window().Equal(asr1.Window()) {
  239. t.Fatalf("AssetSetRange.Binary: expected %s; found %s", asr0.Window(), asr1.Window())
  240. }
  241. for i, as0 := range asr0.Assets {
  242. as1, err := asr1.Get(i)
  243. if err != nil {
  244. t.Fatalf("AssetSetRange.Binary: unexpected error: %s", err)
  245. }
  246. if as0.Length() != as1.Length() {
  247. t.Fatalf("AssetSetRange.Binary: expected %d; found %d", as0.Length(), as1.Length())
  248. }
  249. if !as0.Window.Equal(as1.Window) {
  250. t.Fatalf("AssetSetRange.Binary: expected %s; found %s", as0.Window, as1.Window)
  251. }
  252. for k, a0 := range as0.Assets {
  253. a1, ok := as1.Get(k)
  254. if !ok {
  255. t.Fatalf("AssetSetRange.Binary: missing Asset: %s", a0)
  256. }
  257. if !a0.Equal(a1) {
  258. t.Fatalf("AssetSetRange.Binary: unequal Assets \"%s\": expected %s; found %s", k, a0, a1)
  259. }
  260. }
  261. }
  262. }
  263. func RunBreakdown_BinaryEncodingTest(t *testing.T, unmarshal UnmarshalFunc) {
  264. var b0, b1 *Breakdown
  265. var bs []byte
  266. var err error
  267. b0 = &Breakdown{
  268. Idle: 0.75,
  269. Other: 0.1,
  270. System: 0.0,
  271. User: 0.15,
  272. }
  273. bs, err = b0.MarshalBinary()
  274. if err != nil {
  275. t.Fatalf("Breakdown.Binary: unexpected error: %s", err)
  276. }
  277. b1 = &Breakdown{}
  278. err = unmarshal(b1, bs)
  279. if err != nil {
  280. t.Fatalf("Breakdown.Binary: unexpected error: %s", err)
  281. }
  282. if b1.Idle != b0.Idle {
  283. t.Fatalf("Breakdown.Binary: expected %f, found %f", b0.Idle, b1.Idle)
  284. }
  285. if b1.Other != b0.Other {
  286. t.Fatalf("Breakdown.Binary: expected %f, found %f", b0.Other, b1.Other)
  287. }
  288. if b1.System != b0.System {
  289. t.Fatalf("Breakdown.Binary: expected %f, found %f", b0.System, b1.System)
  290. }
  291. if b1.User != b0.User {
  292. t.Fatalf("Breakdown.Binary: expected %f, found %f", b0.User, b1.User)
  293. }
  294. }
  295. func RunCloudAny_BinaryEncodingTest(t *testing.T, unmarshal UnmarshalFunc) {
  296. ws := time.Date(2020, time.September, 16, 0, 0, 0, 0, time.UTC)
  297. we := ws.Add(24 * time.Hour)
  298. window := NewWindow(&ws, &we)
  299. var a0, a1 *Cloud
  300. var bs []byte
  301. var err error
  302. a0 = NewCloud(ComputeCategory, "providerid1", *window.start, *window.end, window)
  303. a0.Cost = 6.09
  304. a0.SetAdjustment(-1.23)
  305. bs, err = a0.MarshalBinary()
  306. if err != nil {
  307. t.Fatalf("CloudAny.Binary: unexpected error: %s", err)
  308. }
  309. a1 = &Cloud{}
  310. err = unmarshal(a1, bs)
  311. if err != nil {
  312. t.Fatalf("CloudAny.Binary: unexpected error: %s", err)
  313. }
  314. if !a0.Equal(a1) {
  315. t.Fatalf("CloudAny.Binary: expected %v, found %v", a0, a1)
  316. }
  317. }
  318. func RunClusterManagement_BinaryEncodingTest(t *testing.T, unmarshal UnmarshalFunc) {
  319. ws := time.Date(2020, time.September, 16, 0, 0, 0, 0, time.UTC)
  320. we := ws.Add(24 * time.Hour)
  321. window := NewWindow(&ws, &we)
  322. var a0, a1 *ClusterManagement
  323. var bs []byte
  324. var err error
  325. a0 = NewClusterManagement(AWSProvider, "cluster1", window)
  326. a0.Cost = 4.003
  327. a0.SetAdjustment(-3.23)
  328. bs, err = a0.MarshalBinary()
  329. if err != nil {
  330. t.Fatalf("ClusterManagement.Binary: unexpected error: %s", err)
  331. }
  332. a1 = &ClusterManagement{}
  333. err = a1.UnmarshalBinary(bs)
  334. if err != nil {
  335. t.Fatalf("ClusterManagement.Binary: unexpected error: %s", err)
  336. }
  337. if !a0.Equal(a1) {
  338. t.Fatalf("ClusterManagement.Binary: expected %v, found %v", a0, a1)
  339. }
  340. }
  341. func RunDisk_BinaryEncodingTest(t *testing.T, unmarshal UnmarshalFunc) {
  342. ws := time.Date(2020, time.September, 16, 0, 0, 0, 0, time.UTC)
  343. we := ws.Add(24 * time.Hour)
  344. window := NewWindow(&ws, &we)
  345. hours := window.Duration().Hours()
  346. start := time.Date(2020, time.September, 16, 3, 0, 0, 0, time.UTC)
  347. end := time.Date(2020, time.September, 16, 15, 12, 0, 0, time.UTC)
  348. var a0, a1 *Disk
  349. var bs []byte
  350. var err error
  351. a0 = NewDisk("any1", "cluster1", "世界", start, end, window)
  352. a0.ByteHours = 100 * 1024 * 1024 * 1024 * hours
  353. a0.Cost = 4.003
  354. a0.Local = 0.4
  355. a0.Breakdown = &Breakdown{
  356. Idle: 0.9,
  357. Other: 0.05,
  358. System: 0.05,
  359. User: 0.0,
  360. }
  361. a0.SetAdjustment(-3.23)
  362. bs, err = a0.MarshalBinary()
  363. if err != nil {
  364. t.Fatalf("Disk.Binary: unexpected error: %s", err)
  365. }
  366. a1 = &Disk{}
  367. err = unmarshal(a1, bs)
  368. if err != nil {
  369. t.Fatalf("Disk.Binary: unexpected error: %s", err)
  370. }
  371. if !a0.Equal(a1) {
  372. t.Fatalf("Disk.Binary: expected %v, found %v", a0, a1)
  373. }
  374. }
  375. func RunNode_BinaryEncodingTest(t *testing.T, unmarshal UnmarshalFunc) {
  376. ws := time.Date(2020, time.September, 16, 0, 0, 0, 0, time.UTC)
  377. we := ws.Add(24 * time.Hour)
  378. window := NewWindow(&ws, &we)
  379. hours := window.Duration().Hours()
  380. start := time.Date(2020, time.September, 16, 3, 0, 0, 0, time.UTC)
  381. end := time.Date(2020, time.September, 16, 15, 12, 0, 0, time.UTC)
  382. var a0, a1 *Node
  383. var bs []byte
  384. var err error
  385. a0 = NewNode("any1", "cluster1", "世界", start, end, window)
  386. a0.NodeType = "n2-standard"
  387. a0.Preemptible = 1.0
  388. a0.CPUCoreHours = 2.0 * hours
  389. a0.RAMByteHours = 12.0 * gb * hours
  390. a0.CPUCost = 1.50
  391. a0.GPUCost = 30.44
  392. a0.RAMCost = 15.0
  393. a0.Discount = 0.9
  394. a0.CPUBreakdown = &Breakdown{
  395. Idle: 0.9,
  396. Other: 0.05,
  397. System: 0.05,
  398. User: 0.0,
  399. }
  400. a0.RAMBreakdown = &Breakdown{
  401. Idle: 0.4,
  402. Other: 0.05,
  403. System: 0.05,
  404. User: 0.5,
  405. }
  406. a0.SetAdjustment(1.23)
  407. bs, err = a0.MarshalBinary()
  408. if err != nil {
  409. t.Fatalf("Node.Binary: unexpected error: %s", err)
  410. }
  411. a1 = &Node{}
  412. err = unmarshal(a1, bs)
  413. if err != nil {
  414. t.Fatalf("Node.Binary: unexpected error: %s", err)
  415. }
  416. if !a0.Equal(a1) {
  417. t.Fatalf("Node.Binary: expected %v, found %v", a0, a1)
  418. }
  419. }
  420. func RunProperties_BinaryEncodingTest(t *testing.T, unmarshal UnmarshalFunc) {
  421. var p0, p1 *AllocationProperties
  422. var bs []byte
  423. var err error
  424. // empty properties
  425. p0 = &AllocationProperties{}
  426. bs, err = p0.MarshalBinary()
  427. if err != nil {
  428. t.Fatalf("AllocationProperties.Binary: unexpected error: %s", err)
  429. }
  430. p1 = &AllocationProperties{}
  431. err = unmarshal(p1, bs)
  432. if err != nil {
  433. t.Fatalf("AllocationProperties.Binary: unexpected error: %s", err)
  434. }
  435. if !p0.Equal(p1) {
  436. t.Fatalf("AllocationProperties.Binary: expected %s; found %s", p0, p1)
  437. }
  438. // complete properties
  439. p0 = &AllocationProperties{}
  440. p0.Cluster = "cluster1"
  441. p0.Container = "container-abc-1"
  442. p0.Controller = "daemonset-abc"
  443. p0.ControllerKind = "daemonset"
  444. p0.Namespace = "namespace1"
  445. p0.NamespaceLabels = map[string]string{
  446. "app": "cost-analyzer-namespace",
  447. "kubernetes.io/name": "cost-analyzer",
  448. }
  449. p0.NamespaceAnnotations = map[string]string{
  450. "com.kubernetes.io/managed-by": "helm",
  451. "kubernetes.io/last-applied-configuration": "cost-analyzer",
  452. }
  453. p0.Node = "node1"
  454. p0.Pod = "daemonset-abc-123"
  455. p0.Labels = map[string]string{
  456. "app": "cost-analyzer",
  457. "tier": "frontend",
  458. }
  459. p0.Services = []string{"kubecost-frontend"}
  460. bs, err = p0.MarshalBinary()
  461. if err != nil {
  462. t.Fatalf("AllocationProperties.Binary: unexpected error: %s", err)
  463. }
  464. p1 = &AllocationProperties{}
  465. err = unmarshal(p1, bs)
  466. if err != nil {
  467. t.Fatalf("AllocationProperties.Binary: unexpected error: %s", err)
  468. }
  469. if !p0.Equal(p1) {
  470. t.Fatalf("AllocationProperties.Binary: expected %s; found %s", p0, p1)
  471. }
  472. // incomplete properties
  473. p0 = &AllocationProperties{}
  474. p0.Cluster = ("cluster1")
  475. p0.Controller = "daemonset-abc"
  476. p0.ControllerKind = "daemonset"
  477. p0.Namespace = "namespace1"
  478. p0.NamespaceAnnotations = map[string]string{
  479. "com.kubernetes.io/managed-by": "helm",
  480. "kubernetes.io/last-applied-configuration": "cost-analyzer",
  481. }
  482. p0.Services = []string{}
  483. bs, err = p0.MarshalBinary()
  484. if err != nil {
  485. t.Fatalf("AllocationProperties.Binary: unexpected error: %s", err)
  486. }
  487. p1 = &AllocationProperties{}
  488. err = unmarshal(p1, bs)
  489. if err != nil {
  490. t.Fatalf("AllocationProperties.Binary: unexpected error: %s", err)
  491. }
  492. if !p0.Equal(p1) {
  493. t.Fatalf("AllocationProperties.Binary: expected %s; found %s", p0, p1)
  494. }
  495. }
  496. func RunShared_BinaryEncodingTest(t *testing.T, unmarshal UnmarshalFunc) {
  497. ws := time.Date(2020, time.September, 16, 0, 0, 0, 0, time.UTC)
  498. we := ws.Add(24 * time.Hour)
  499. window := NewWindow(&ws, &we)
  500. var a0, a1 *SharedAsset
  501. var bs []byte
  502. var err error
  503. a0 = NewSharedAsset("any1", window)
  504. a0.Cost = 4.04
  505. a0.SetAdjustment(1.23)
  506. bs, err = a0.MarshalBinary()
  507. if err != nil {
  508. t.Fatalf("SharedAsset.Binary: unexpected error: %s", err)
  509. }
  510. a1 = &SharedAsset{}
  511. err = unmarshal(a1, bs)
  512. if err != nil {
  513. t.Fatalf("SharedAsset.Binary: unexpected error: %s", err)
  514. }
  515. if !a0.Equal(a1) {
  516. t.Fatalf("SharedAsset.Binary: expected %v, found %v", a0, a1)
  517. }
  518. }
  519. func RunWindow_BinaryEncodingTest(t *testing.T, unmarshal UnmarshalFunc) {
  520. var w0, w1 Window
  521. var bs []byte
  522. var err error
  523. // Window (nil, nil)
  524. w0 = NewWindow(nil, nil)
  525. bs, err = w0.MarshalBinary()
  526. if err != nil {
  527. t.Fatalf("Window.Binary: unexpected error: %s", err)
  528. }
  529. err = unmarshal(&w1, bs)
  530. if err != nil {
  531. t.Fatalf("Window.Binary: unexpected error: %s", err)
  532. }
  533. if w1.Start() != w0.Start() {
  534. t.Fatalf("Window.Binary: expected %v; found %v", w0.Start(), w1.Start())
  535. }
  536. if w1.End() != w0.End() {
  537. t.Fatalf("Window.Binary: expected %v; found %v", w0.End(), w1.End())
  538. }
  539. // Window (time, nil)
  540. ts := time.Now()
  541. w0 = NewWindow(&ts, nil)
  542. bs, err = w0.MarshalBinary()
  543. if err != nil {
  544. t.Fatalf("Window.Binary: unexpected error: %s", err)
  545. }
  546. err = unmarshal(&w1, bs)
  547. if err != nil {
  548. t.Fatalf("Window.Binary: unexpected error: %s", err)
  549. }
  550. if !w1.Start().Equal(*w0.Start()) {
  551. t.Fatalf("Window.Binary: expected %v; found %v", w0.Start(), w1.Start())
  552. }
  553. if w1.End() != w0.End() {
  554. t.Fatalf("Window.Binary: expected %v; found %v", w0.End(), w1.End())
  555. }
  556. // Window (nil, time)
  557. te := time.Now()
  558. w0 = NewWindow(nil, &te)
  559. bs, err = w0.MarshalBinary()
  560. if err != nil {
  561. t.Fatalf("Window.Binary: unexpected error: %s", err)
  562. }
  563. err = unmarshal(&w1, bs)
  564. if err != nil {
  565. t.Fatalf("Window.Binary: unexpected error: %s", err)
  566. }
  567. if w1.Start() != w0.Start() {
  568. t.Fatalf("Window.Binary: expected %v; found %v", w0.Start(), w1.Start())
  569. }
  570. if !w1.End().Equal(*w0.End()) {
  571. t.Fatalf("Window.Binary: expected %v; found %v", w0.End(), w1.End())
  572. }
  573. // Window (time, time)
  574. ts, te = time.Now(), time.Now()
  575. w0 = NewWindow(&ts, &te)
  576. bs, err = w0.MarshalBinary()
  577. if err != nil {
  578. t.Fatalf("Window.Binary: unexpected error: %s", err)
  579. }
  580. err = unmarshal(&w1, bs)
  581. if err != nil {
  582. t.Fatalf("Window.Binary: unexpected error: %s", err)
  583. }
  584. if !w1.Start().Equal(*w0.Start()) {
  585. t.Fatalf("Window.Binary: expected %v; found %v", w0.Start(), w1.Start())
  586. }
  587. if !w1.End().Equal(*w0.End()) {
  588. t.Fatalf("Window.Binary: expected %v; found %v", w0.End(), w1.End())
  589. }
  590. }
  591. type BingenUnmarshalable interface {
  592. UnmarshalBinary([]byte) error
  593. UnmarshalBinaryFromReader(io.Reader) error
  594. }
  595. func UnmarshalBingenBytes(value BingenUnmarshalable, b []byte) error {
  596. return value.UnmarshalBinary(b)
  597. }
  598. func UnmarshalBingenReader(value BingenUnmarshalable, b []byte) error {
  599. // convert bytes to reader in order to leverage io.Reader string table
  600. reader := bytes.NewReader(b)
  601. return value.UnmarshalBinaryFromReader(reader)
  602. }
  603. func RunAllOpencostBingenCodecTests(t *testing.T, unmarshal UnmarshalFunc) {
  604. tests := []struct {
  605. name string
  606. f func(*testing.T, UnmarshalFunc)
  607. }{
  608. {
  609. name: "RunAllocation_BinaryEncodingTest",
  610. f: RunAllocation_BinaryEncodingTest,
  611. },
  612. {
  613. name: "RunAllocationSet_BinaryEncodingTest",
  614. f: RunAllocationSet_BinaryEncodingTest,
  615. },
  616. {
  617. name: "RunAllocationSetRange_BinaryEncodingTest",
  618. f: RunAllocationSetRange_BinaryEncodingTest,
  619. },
  620. {
  621. name: "RunAny_BinaryEncodingTest",
  622. f: RunAny_BinaryEncodingTest,
  623. },
  624. {
  625. name: "RunAsset_BinaryEncodingTest",
  626. f: RunAsset_BinaryEncodingTest,
  627. },
  628. {
  629. name: "RunAssetSet_BinaryEncodingTest",
  630. f: RunAssetSet_BinaryEncodingTest,
  631. },
  632. {
  633. name: "RunAssetSetRange_BinaryEncodingTest",
  634. f: RunAssetSetRange_BinaryEncodingTest,
  635. },
  636. {
  637. name: "RunBreakdown_BinaryEncodingTest",
  638. f: RunBreakdown_BinaryEncodingTest,
  639. },
  640. {
  641. name: "RunCloudAny_BinaryEncodingTest",
  642. f: RunCloudAny_BinaryEncodingTest,
  643. },
  644. {
  645. name: "RunClusterManagement_BinaryEncodingTest",
  646. f: RunClusterManagement_BinaryEncodingTest,
  647. },
  648. {
  649. name: "RunDisk_BinaryEncodingTest",
  650. f: RunDisk_BinaryEncodingTest,
  651. },
  652. {
  653. name: "RunNode_BinaryEncodingTest",
  654. f: RunNode_BinaryEncodingTest,
  655. },
  656. {
  657. name: "RunProperties_BinaryEncodingTest",
  658. f: RunProperties_BinaryEncodingTest,
  659. },
  660. {
  661. name: "RunShared_BinaryEncodingTest",
  662. f: RunShared_BinaryEncodingTest,
  663. },
  664. {
  665. name: "RunWindow_BinaryEncodingTest",
  666. f: RunWindow_BinaryEncodingTest,
  667. },
  668. }
  669. for _, test := range tests {
  670. t.Run(test.name, func(tt *testing.T) {
  671. test.f(tt, unmarshal)
  672. })
  673. }
  674. }
  675. func TestOpencostBingenDefaultsWithBytes(t *testing.T) {
  676. config := DefaultBingenConfiguration()
  677. ConfigureBingen(config)
  678. RunAllOpencostBingenCodecTests(t, UnmarshalBingenBytes)
  679. }
  680. func TestOpencostBingenFileStringTableEnabledWithBytes(t *testing.T) {
  681. // This test _should_ still run the slice based string table because raw []byte
  682. // data always uses the string slice table
  683. config := DefaultBingenConfiguration()
  684. config.FileBackedStringTableEnabled = true
  685. config.FileBackedStringTableDir = t.TempDir()
  686. ConfigureBingen(config)
  687. // reset configuration to default on completion
  688. defer ConfigureBingen(DefaultBingenConfiguration())
  689. RunAllOpencostBingenCodecTests(t, UnmarshalBingenBytes)
  690. }
  691. func TestOpencostBingenDefaultsWithReader(t *testing.T) {
  692. // This test _should_ still run the slice based string table because we haven't configured
  693. // bingen to use the file string table
  694. config := DefaultBingenConfiguration()
  695. ConfigureBingen(config)
  696. // we use the reader to unmarshal instead of []bytes
  697. RunAllOpencostBingenCodecTests(t, UnmarshalBingenReader)
  698. }
  699. func TestOpencostBingenFileStringTableEnabledWithReader(t *testing.T) {
  700. // This test _should_ use the file backed string table because we have enabled it AND
  701. // we're using a reader
  702. config := DefaultBingenConfiguration()
  703. config.FileBackedStringTableEnabled = true
  704. config.FileBackedStringTableDir = t.TempDir()
  705. ConfigureBingen(config)
  706. // reset configuration to default on completion
  707. defer ConfigureBingen(DefaultBingenConfiguration())
  708. RunAllOpencostBingenCodecTests(t, UnmarshalBingenReader)
  709. }
  710. func TestFileStringTableReaderAt_UsesMemoCache(t *testing.T) {
  711. tmp, err := os.CreateTemp("", "opencost-bgst-test-*")
  712. if err != nil {
  713. t.Fatalf("create temp file: %v", err)
  714. }
  715. defer os.Remove(tmp.Name())
  716. defer tmp.Close()
  717. if _, err := tmp.Write([]byte("hello")); err != nil {
  718. t.Fatalf("write temp data: %v", err)
  719. }
  720. reader := &FileStringTableReader{
  721. f: tmp,
  722. refs: []fileStringRef{
  723. {off: 0, length: 5},
  724. },
  725. memo: make([]atomic.Pointer[string], 1),
  726. memoMaxBytes: 16,
  727. }
  728. defer reader.Close()
  729. if got := reader.At(0); got != "hello" {
  730. t.Fatalf("baseline string mismatch, got %q", got)
  731. }
  732. if err := tmp.Truncate(0); err != nil {
  733. t.Fatalf("truncate temp file: %v", err)
  734. }
  735. if got := reader.At(0); got != "hello" {
  736. t.Fatalf("expected memoized value after truncate, got %q", got)
  737. }
  738. }
  739. func TestFileStringTableReader_EvictLeastUsedMemoEntries(t *testing.T) {
  740. s1 := "aaaa"
  741. s2 := "bbbb"
  742. s3 := "cccc"
  743. s4 := "dddd"
  744. reader := &FileStringTableReader{
  745. refs: []fileStringRef{
  746. {length: len(s1)},
  747. {length: len(s2)},
  748. {length: len(s3)},
  749. {length: len(s4)},
  750. },
  751. memo: make([]atomic.Pointer[string], 4),
  752. memoHits: make([]atomic.Uint64, 4),
  753. evictScratch: make([]memoEvictionCandidate, 4),
  754. memoMaxBytes: 16,
  755. }
  756. reader.memo[0].Store(&s1)
  757. reader.memo[1].Store(&s2)
  758. reader.memo[2].Store(&s3)
  759. reader.memo[3].Store(&s4)
  760. reader.memoHits[0].Store(10)
  761. reader.memoHits[1].Store(1)
  762. reader.memoHits[2].Store(3)
  763. reader.memoHits[3].Store(2)
  764. reader.memoBytes.Store(int64(len(s1) + len(s2) + len(s3) + len(s4)))
  765. reader.evictLeastUsedMemoEntries(0.10, 0.40)
  766. if got := reader.memo[1].Load(); got != nil {
  767. t.Fatalf("expected index 1 to be evicted first, got %q", *got)
  768. }
  769. if got := reader.memo[3].Load(); got == nil {
  770. t.Fatalf("expected index 3 to remain cached")
  771. }
  772. }