From 7fa47a962a6415748566f71f453378644de2cc69 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Tue, 16 Jul 2024 08:14:19 +0200 Subject: [PATCH 01/64] Add basic nvidia-smi parser --- psutil/gpu/gpu.go | 33 + psutil/gpu/nvidia/fixtures/data1.xml | 725 ++++++++++++++++++++++ psutil/gpu/nvidia/fixtures/data2.xml | 890 +++++++++++++++++++++++++++ psutil/gpu/nvidia/fixtures/data3.xml | 242 ++++++++ psutil/gpu/nvidia/nvidia.go | 284 +++++++++ psutil/gpu/nvidia/nvidia_test.go | 102 +++ psutil/psutil.go | 55 ++ resources/resources_test.go | 8 + 8 files changed, 2339 insertions(+) create mode 100644 psutil/gpu/gpu.go create mode 100644 psutil/gpu/nvidia/fixtures/data1.xml create mode 100644 psutil/gpu/nvidia/fixtures/data2.xml create mode 100644 psutil/gpu/nvidia/fixtures/data3.xml create mode 100644 psutil/gpu/nvidia/nvidia.go create mode 100644 psutil/gpu/nvidia/nvidia_test.go diff --git a/psutil/gpu/gpu.go b/psutil/gpu/gpu.go new file mode 100644 index 00000000..7feb19bd --- /dev/null +++ b/psutil/gpu/gpu.go @@ -0,0 +1,33 @@ +package gpu + +import "errors" + +type Process struct { + PID int32 + Memory uint64 +} + +type Stats struct { + Name string + Architecture string + + MemoryTotal uint64 + MemoryUsed uint64 + + Usage float64 + MemoryUsage float64 + EncoderUsage float64 + DecoderUsage float64 + + Process []Process + + Extension interface{} +} + +type GPU interface { + Count() (int, error) + Stats() ([]Stats, error) + Process(pid int32) (Process, error) +} + +var ErrProcessNotFound = errors.New("process not found") diff --git a/psutil/gpu/nvidia/fixtures/data1.xml b/psutil/gpu/nvidia/fixtures/data1.xml new file mode 100644 index 00000000..10b35010 --- /dev/null +++ b/psutil/gpu/nvidia/fixtures/data1.xml @@ -0,0 +1,725 @@ + + + + Mon Jul 15 13:50:34 2024 + 495.29.05 + 11.5 + 1 + + NVIDIA GeForce GTX 1080 + GeForce + Pascal + Disabled + Disabled + Disabled + + N/A + N/A + + + None + + Disabled + 4000 + + N/A + N/A + + N/A + GPU-d8249424-2ed0-0499-2d47-8c6905e3ef5b + 0 + 86.04.17.00.01 + No + 0x100 + N/A + 0 + + G001.0000.01.03 + 1.1 + N/A + N/A + + + N/A + N/A + + N/A + + None + N/A + + + N/A + + + 01 + 00 + 0000 + 1B8010DE + 00000000:01:00.0 + 119E10DE + + + 3 + 3 + + + 16x + 16x + + + + N/A + N/A + + 0 + 0 + 106000 KB/s + 309000 KB/s + + 44 % + P2 + + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + + + 8119 MiB + 918 MiB + 7201 MiB + + + 256 MiB + 2 MiB + 254 MiB + + Default + + 15 % + 7 % + 3 % + 0 % + + + 0 + 0 + 0 + + + 0 + 0 + 0 + + + N/A + N/A + + + + + N/A + N/A + N/A + N/A + N/A + N/A + N/A + N/A + + + N/A + N/A + N/A + N/A + N/A + N/A + N/A + N/A + + + + + N/A + N/A + N/A + N/A + N/A + N/A + N/A + N/A + + + N/A + N/A + N/A + N/A + N/A + N/A + N/A + N/A + + + + + + N/A + N/A + + + N/A + N/A + + N/A + N/A + + N/A + + 55 C + 99 C + 96 C + N/A + 83 C + N/A + N/A + + + 60 C + 92 C + + + P2 + Supported + 42.64 W + 180.00 W + 180.00 W + 180.00 W + 90.00 W + 180.00 W + + + 1607 MHz + 1607 MHz + 4513 MHz + 1442 MHz + + + N/A + N/A + + + N/A + N/A + + + 1911 MHz + 1911 MHz + 5005 MHz + 1708 MHz + + + N/A + + + N/A + N/A + + + N/A + + + + 5005 MHz + 1911 MHz + 1898 MHz + 1885 MHz + 1873 MHz + 1860 MHz + 1847 MHz + 1835 MHz + 1822 MHz + 1809 MHz + 1797 MHz + 1784 MHz + 1771 MHz + 1759 MHz + 1746 MHz + 1733 MHz + 1721 MHz + 1708 MHz + 1695 MHz + 1683 MHz + 1670 MHz + 1657 MHz + 1645 MHz + 1632 MHz + 1620 MHz + 1607 MHz + 1594 MHz + 1582 MHz + 1569 MHz + 1556 MHz + 1544 MHz + 1531 MHz + 1518 MHz + 1506 MHz + 1493 MHz + 1480 MHz + 1468 MHz + 1455 MHz + 1442 MHz + 1430 MHz + 1417 MHz + 1404 MHz + 1392 MHz + 1379 MHz + 1366 MHz + 1354 MHz + 1341 MHz + 1328 MHz + 1316 MHz + 1303 MHz + 1290 MHz + 1278 MHz + 1265 MHz + 1252 MHz + 1240 MHz + 1227 MHz + 1215 MHz + 1202 MHz + 1189 MHz + 1177 MHz + 1164 MHz + 1151 MHz + 1139 MHz + 1126 MHz + 1113 MHz + 1101 MHz + 1088 MHz + 1075 MHz + 1063 MHz + 1050 MHz + 1037 MHz + 1025 MHz + 1012 MHz + 999 MHz + 987 MHz + 974 MHz + 961 MHz + 949 MHz + 936 MHz + 923 MHz + 911 MHz + 898 MHz + 885 MHz + 873 MHz + 860 MHz + 847 MHz + 835 MHz + 822 MHz + 810 MHz + 797 MHz + 784 MHz + 772 MHz + 759 MHz + 746 MHz + 734 MHz + 721 MHz + 708 MHz + 696 MHz + 683 MHz + 670 MHz + 658 MHz + 645 MHz + 632 MHz + 620 MHz + 607 MHz + 594 MHz + 582 MHz + 569 MHz + 556 MHz + 544 MHz + 531 MHz + 518 MHz + 506 MHz + 493 MHz + 480 MHz + 468 MHz + 455 MHz + 442 MHz + 430 MHz + 417 MHz + 405 MHz + 392 MHz + 379 MHz + 367 MHz + 354 MHz + 341 MHz + 329 MHz + 316 MHz + 303 MHz + 291 MHz + 278 MHz + 265 MHz + 253 MHz + 240 MHz + 227 MHz + 215 MHz + 202 MHz + 189 MHz + 177 MHz + 164 MHz + 151 MHz + 139 MHz + + + 4513 MHz + 1911 MHz + 1898 MHz + 1885 MHz + 1873 MHz + 1860 MHz + 1847 MHz + 1835 MHz + 1822 MHz + 1809 MHz + 1797 MHz + 1784 MHz + 1771 MHz + 1759 MHz + 1746 MHz + 1733 MHz + 1721 MHz + 1708 MHz + 1695 MHz + 1683 MHz + 1670 MHz + 1657 MHz + 1645 MHz + 1632 MHz + 1620 MHz + 1607 MHz + 1594 MHz + 1582 MHz + 1569 MHz + 1556 MHz + 1544 MHz + 1531 MHz + 1518 MHz + 1506 MHz + 1493 MHz + 1480 MHz + 1468 MHz + 1455 MHz + 1442 MHz + 1430 MHz + 1417 MHz + 1404 MHz + 1392 MHz + 1379 MHz + 1366 MHz + 1354 MHz + 1341 MHz + 1328 MHz + 1316 MHz + 1303 MHz + 1290 MHz + 1278 MHz + 1265 MHz + 1252 MHz + 1240 MHz + 1227 MHz + 1215 MHz + 1202 MHz + 1189 MHz + 1177 MHz + 1164 MHz + 1151 MHz + 1139 MHz + 1126 MHz + 1113 MHz + 1101 MHz + 1088 MHz + 1075 MHz + 1063 MHz + 1050 MHz + 1037 MHz + 1025 MHz + 1012 MHz + 999 MHz + 987 MHz + 974 MHz + 961 MHz + 949 MHz + 936 MHz + 923 MHz + 911 MHz + 898 MHz + 885 MHz + 873 MHz + 860 MHz + 847 MHz + 835 MHz + 822 MHz + 810 MHz + 797 MHz + 784 MHz + 772 MHz + 759 MHz + 746 MHz + 734 MHz + 721 MHz + 708 MHz + 696 MHz + 683 MHz + 670 MHz + 658 MHz + 645 MHz + 632 MHz + 620 MHz + 607 MHz + 594 MHz + 582 MHz + 569 MHz + 556 MHz + 544 MHz + 531 MHz + 518 MHz + 506 MHz + 493 MHz + 480 MHz + 468 MHz + 455 MHz + 442 MHz + 430 MHz + 417 MHz + 405 MHz + 392 MHz + 379 MHz + 367 MHz + 354 MHz + 341 MHz + 329 MHz + 316 MHz + 303 MHz + 291 MHz + 278 MHz + 265 MHz + 253 MHz + 240 MHz + 227 MHz + 215 MHz + 202 MHz + 189 MHz + 177 MHz + 164 MHz + 151 MHz + 139 MHz + + + 810 MHz + 1911 MHz + 1898 MHz + 1885 MHz + 1873 MHz + 1860 MHz + 1847 MHz + 1835 MHz + 1822 MHz + 1809 MHz + 1797 MHz + 1784 MHz + 1771 MHz + 1759 MHz + 1746 MHz + 1733 MHz + 1721 MHz + 1708 MHz + 1695 MHz + 1683 MHz + 1670 MHz + 1657 MHz + 1645 MHz + 1632 MHz + 1620 MHz + 1607 MHz + 1594 MHz + 1582 MHz + 1569 MHz + 1556 MHz + 1544 MHz + 1531 MHz + 1518 MHz + 1506 MHz + 1493 MHz + 1480 MHz + 1468 MHz + 1455 MHz + 1442 MHz + 1430 MHz + 1417 MHz + 1404 MHz + 1392 MHz + 1379 MHz + 1366 MHz + 1354 MHz + 1341 MHz + 1328 MHz + 1316 MHz + 1303 MHz + 1290 MHz + 1278 MHz + 1265 MHz + 1252 MHz + 1240 MHz + 1227 MHz + 1215 MHz + 1202 MHz + 1189 MHz + 1177 MHz + 1164 MHz + 1151 MHz + 1139 MHz + 1126 MHz + 1113 MHz + 1101 MHz + 1088 MHz + 1075 MHz + 1063 MHz + 1050 MHz + 1037 MHz + 1025 MHz + 1012 MHz + 999 MHz + 987 MHz + 974 MHz + 961 MHz + 949 MHz + 936 MHz + 923 MHz + 911 MHz + 898 MHz + 885 MHz + 873 MHz + 860 MHz + 847 MHz + 835 MHz + 822 MHz + 810 MHz + 797 MHz + 784 MHz + 772 MHz + 759 MHz + 746 MHz + 734 MHz + 721 MHz + 708 MHz + 696 MHz + 683 MHz + 670 MHz + 658 MHz + 645 MHz + 632 MHz + 620 MHz + 607 MHz + 594 MHz + 582 MHz + 569 MHz + 556 MHz + 544 MHz + 531 MHz + 518 MHz + 506 MHz + 493 MHz + 480 MHz + 468 MHz + 455 MHz + 442 MHz + 430 MHz + 417 MHz + 405 MHz + 392 MHz + 379 MHz + 367 MHz + 354 MHz + 341 MHz + 329 MHz + 316 MHz + 303 MHz + 291 MHz + 278 MHz + 265 MHz + 253 MHz + 240 MHz + 227 MHz + 215 MHz + 202 MHz + 189 MHz + 177 MHz + 164 MHz + 151 MHz + 139 MHz + + + 405 MHz + 607 MHz + 594 MHz + 582 MHz + 569 MHz + 556 MHz + 544 MHz + 531 MHz + 518 MHz + 506 MHz + 493 MHz + 480 MHz + 468 MHz + 455 MHz + 442 MHz + 430 MHz + 417 MHz + 405 MHz + 392 MHz + 379 MHz + 367 MHz + 354 MHz + 341 MHz + 329 MHz + 316 MHz + 303 MHz + 291 MHz + 278 MHz + 265 MHz + 253 MHz + 240 MHz + 227 MHz + 215 MHz + 202 MHz + 189 MHz + 177 MHz + 164 MHz + 151 MHz + 139 MHz + + + + + N/A + N/A + 18179 + C + /usr/local/bin/ffmpeg + 916 MiB + + + + + + + \ No newline at end of file diff --git a/psutil/gpu/nvidia/fixtures/data2.xml b/psutil/gpu/nvidia/fixtures/data2.xml new file mode 100644 index 00000000..cd45d707 --- /dev/null +++ b/psutil/gpu/nvidia/fixtures/data2.xml @@ -0,0 +1,890 @@ + + + + Mon Jul 15 13:41:56 2024 + 555.42.06 + 12.5 + 2 + + NVIDIA L4 + NVIDIA + Ada Lovelace + Enabled + Disabled + Disabled + None + + N/A + N/A + + + None + + Disabled + 4000 + + N/A + N/A + + 1654523003308 + GPU-c5533cd4-5a60-059e-348d-b6d7466932e4 + 1 + 95.04.29.00.06 + No + 0x100 + 900-2G193-0000-001 + 27B8-895-A1 + N/A + 1 + + G193.0200.00.01 + 2.1 + 6.16 + N/A + + + N/A + N/A + + + N/A + N/A + + N/A + + None + N/A + N/A + + + No + N/A + + 555.42.06 + + N/A + + + 01 + 00 + 0000 + 3 + 2 + 27B810DE + 00000000:01:00.0 + 16CA10DE + + + 4 + 4 + 4 + 4 + 5 + + + 16x + 16x + + + + N/A + N/A + + 0 + 0 + 0 KB/s + 0 KB/s + N/A + N/A + + N/A + P0 + + Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + + N/A + + 23034 MiB + 434 MiB + 1 MiB + 22601 MiB + + + 32768 MiB + 1 MiB + 32767 MiB + + + 0 MiB + 0 MiB + 0 MiB + + Default + + 2 % + 0 % + 0 % + 0 % + 0 % + 0 % + + + 0 + 0 + 0 + + + 0 + 0 + 0 + + + Enabled + Enabled + + + + 0 + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + No + + + 0 + 0 + 0 + 0 + 0 + + + + + N/A + N/A + + + N/A + N/A + + N/A + N/A + + + 0 + 0 + No + No + + 96 bank(s) + 0 bank(s) + 0 bank(s) + 0 bank(s) + 0 bank(s) + + + + 45 C + 39 C + -5 C + -2 C + 0 C + N/A + N/A + N/A + + + N/A + N/A + + + P0 + 27.22 W + 72.00 W + 72.00 W + 72.00 W + 40.00 W + 72.00 W + + + N/A + + + P0 + N/A + N/A + N/A + N/A + N/A + N/A + + + 2040 MHz + 2040 MHz + 6250 MHz + 1770 MHz + + + 2040 MHz + 6251 MHz + + + 2040 MHz + 6251 MHz + + + N/A + + + 2040 MHz + 2040 MHz + 6251 MHz + 1770 MHz + + + 2040 MHz + + + N/A + N/A + + + 885.000 mV + + + N/A + N/A + N/A + N/A + + N/A + + + + + 6251 MHz + 2040 MHz + 2025 MHz + 2010 MHz + 1995 MHz + 1980 MHz + 1965 MHz + 1950 MHz + 1935 MHz + 1920 MHz + 1905 MHz + 1890 MHz + 1875 MHz + 1860 MHz + 1845 MHz + 1830 MHz + 1815 MHz + 1800 MHz + 1785 MHz + 1770 MHz + 1755 MHz + 1740 MHz + 1725 MHz + 1710 MHz + 1695 MHz + 1680 MHz + 1665 MHz + 1650 MHz + 1635 MHz + 1620 MHz + 1605 MHz + 1590 MHz + 1575 MHz + 1560 MHz + 1545 MHz + 1530 MHz + 1515 MHz + 1500 MHz + 1485 MHz + 1470 MHz + 1455 MHz + 1440 MHz + 1425 MHz + 1410 MHz + 1395 MHz + 1380 MHz + 1365 MHz + 1350 MHz + 1335 MHz + 1320 MHz + 1305 MHz + 1290 MHz + 1275 MHz + 1260 MHz + 1245 MHz + 1230 MHz + 1215 MHz + 1200 MHz + 1185 MHz + 1170 MHz + 1155 MHz + 1140 MHz + 1125 MHz + 1110 MHz + 1095 MHz + 1080 MHz + 1065 MHz + 1050 MHz + 1035 MHz + 1020 MHz + 1005 MHz + 990 MHz + 975 MHz + 960 MHz + 945 MHz + 930 MHz + 915 MHz + 900 MHz + 885 MHz + 870 MHz + 855 MHz + 840 MHz + 825 MHz + 810 MHz + 795 MHz + 780 MHz + 765 MHz + 750 MHz + 735 MHz + 720 MHz + 705 MHz + 690 MHz + 675 MHz + 660 MHz + 645 MHz + 630 MHz + 615 MHz + 600 MHz + 585 MHz + 570 MHz + 555 MHz + 540 MHz + 525 MHz + 510 MHz + 495 MHz + 480 MHz + 465 MHz + 450 MHz + 435 MHz + 420 MHz + 405 MHz + 390 MHz + 375 MHz + 360 MHz + 345 MHz + 330 MHz + 315 MHz + 300 MHz + 285 MHz + 270 MHz + 255 MHz + 240 MHz + 225 MHz + 210 MHz + + + 405 MHz + 645 MHz + 630 MHz + 615 MHz + 600 MHz + 585 MHz + 570 MHz + 555 MHz + 540 MHz + 525 MHz + 510 MHz + 495 MHz + 480 MHz + 465 MHz + 450 MHz + 435 MHz + 420 MHz + 405 MHz + 390 MHz + 375 MHz + 360 MHz + 345 MHz + 330 MHz + 315 MHz + 300 MHz + 285 MHz + 270 MHz + 255 MHz + 240 MHz + 225 MHz + 210 MHz + + + + + + + + disabled + + + + + NVIDIA L4 + NVIDIA + Ada Lovelace + Enabled + Disabled + Disabled + None + + N/A + N/A + + + None + + Disabled + 4000 + + N/A + N/A + + 1654523001128 + GPU-128ab6fb-6ec9-fd74-b479-4a5fd14f55bd + 0 + 95.04.29.00.06 + No + 0xc100 + 900-2G193-0000-001 + 27B8-895-A1 + N/A + 1 + + G193.0200.00.01 + 2.1 + 6.16 + N/A + + + N/A + N/A + + + N/A + N/A + + N/A + + None + N/A + N/A + + + No + N/A + + 555.42.06 + + N/A + + + C1 + 00 + 0000 + 3 + 2 + 27B810DE + 00000000:C1:00.0 + 16CA10DE + + + 4 + 4 + 4 + 4 + 5 + + + 16x + 1x + + + + N/A + N/A + + 0 + 0 + 0 KB/s + 0 KB/s + N/A + N/A + + N/A + P0 + + Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + + N/A + + 23034 MiB + 434 MiB + 1 MiB + 22601 MiB + + + 32768 MiB + 1 MiB + 32767 MiB + + + 0 MiB + 0 MiB + 0 MiB + + Default + + 3 % + 0 % + 0 % + 0 % + 0 % + 0 % + + + 0 + 0 + 0 + + + 0 + 0 + 0 + + + Enabled + Enabled + + + + 0 + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + No + + + 0 + 0 + 0 + 0 + 0 + + + + + N/A + N/A + + + N/A + N/A + + N/A + N/A + + + 0 + 0 + No + No + + 96 bank(s) + 0 bank(s) + 0 bank(s) + 0 bank(s) + 0 bank(s) + + + + 40 C + 43 C + -5 C + -2 C + 0 C + N/A + N/A + N/A + + + N/A + N/A + + + P0 + 29.54 W + 72.00 W + 72.00 W + 72.00 W + 40.00 W + 72.00 W + + + N/A + + + P0 + N/A + N/A + N/A + N/A + N/A + N/A + + + 2040 MHz + 2040 MHz + 6250 MHz + 1770 MHz + + + 2040 MHz + 6251 MHz + + + 2040 MHz + 6251 MHz + + + N/A + + + 2040 MHz + 2040 MHz + 6251 MHz + 1770 MHz + + + 2040 MHz + + + N/A + N/A + + + 910.000 mV + + + N/A + N/A + N/A + N/A + + N/A + + + + + 6251 MHz + 2040 MHz + 2025 MHz + 2010 MHz + 1995 MHz + 1980 MHz + 1965 MHz + 1950 MHz + 1935 MHz + 1920 MHz + 1905 MHz + 1890 MHz + 1875 MHz + 1860 MHz + 1845 MHz + 1830 MHz + 1815 MHz + 1800 MHz + 1785 MHz + 1770 MHz + 1755 MHz + 1740 MHz + 1725 MHz + 1710 MHz + 1695 MHz + 1680 MHz + 1665 MHz + 1650 MHz + 1635 MHz + 1620 MHz + 1605 MHz + 1590 MHz + 1575 MHz + 1560 MHz + 1545 MHz + 1530 MHz + 1515 MHz + 1500 MHz + 1485 MHz + 1470 MHz + 1455 MHz + 1440 MHz + 1425 MHz + 1410 MHz + 1395 MHz + 1380 MHz + 1365 MHz + 1350 MHz + 1335 MHz + 1320 MHz + 1305 MHz + 1290 MHz + 1275 MHz + 1260 MHz + 1245 MHz + 1230 MHz + 1215 MHz + 1200 MHz + 1185 MHz + 1170 MHz + 1155 MHz + 1140 MHz + 1125 MHz + 1110 MHz + 1095 MHz + 1080 MHz + 1065 MHz + 1050 MHz + 1035 MHz + 1020 MHz + 1005 MHz + 990 MHz + 975 MHz + 960 MHz + 945 MHz + 930 MHz + 915 MHz + 900 MHz + 885 MHz + 870 MHz + 855 MHz + 840 MHz + 825 MHz + 810 MHz + 795 MHz + 780 MHz + 765 MHz + 750 MHz + 735 MHz + 720 MHz + 705 MHz + 690 MHz + 675 MHz + 660 MHz + 645 MHz + 630 MHz + 615 MHz + 600 MHz + 585 MHz + 570 MHz + 555 MHz + 540 MHz + 525 MHz + 510 MHz + 495 MHz + 480 MHz + 465 MHz + 450 MHz + 435 MHz + 420 MHz + 405 MHz + 390 MHz + 375 MHz + 360 MHz + 345 MHz + 330 MHz + 315 MHz + 300 MHz + 285 MHz + 270 MHz + 255 MHz + 240 MHz + 225 MHz + 210 MHz + + + 405 MHz + 645 MHz + 630 MHz + 615 MHz + 600 MHz + 585 MHz + 570 MHz + 555 MHz + 540 MHz + 525 MHz + 510 MHz + 495 MHz + 480 MHz + 465 MHz + 450 MHz + 435 MHz + 420 MHz + 405 MHz + 390 MHz + 375 MHz + 360 MHz + 345 MHz + 330 MHz + 315 MHz + 300 MHz + 285 MHz + 270 MHz + 255 MHz + 240 MHz + 225 MHz + 210 MHz + + + + + + + + disabled + + + + \ No newline at end of file diff --git a/psutil/gpu/nvidia/fixtures/data3.xml b/psutil/gpu/nvidia/fixtures/data3.xml new file mode 100644 index 00000000..86d6ec33 --- /dev/null +++ b/psutil/gpu/nvidia/fixtures/data3.xml @@ -0,0 +1,242 @@ + + + + Mon Jul 15 15:24:14 2024 + 440.33.01 + 10.2 + 1 + + GeForce GTX 1080 + GeForce + Disabled + Disabled + Disabled + Disabled + 4000 + + N/A + N/A + + N/A + GPU-bf6e9a3a-e0bb-c253-45b4-34c99ec25512 + 0 + 86.04.17.00.01 + No + 0x100 + N/A + + G001.0000.01.03 + 1.1 + N/A + N/A + + + N/A + N/A + + + None + N/A + + + N/A + + + 01 + 00 + 0000 + 1B8010DE + 00000000:01:00.0 + 119E10DE + + + 3 + 3 + + + 16x + 16x + + + + N/A + N/A + + 0 + 0 + 783000 KB/s + 1269000 KB/s + + 53 % + P2 + + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + + + 8119 MiB + 2006 MiB + 6113 MiB + + + 256 MiB + 2 MiB + 254 MiB + + Default + + 32 % + 11 % + 17 % + 25 % + + + 0 + 0 + 0 + + + 0 + 0 + 0 + + + N/A + N/A + + + + + N/A + N/A + N/A + N/A + N/A + N/A + N/A + N/A + + + N/A + N/A + N/A + N/A + N/A + N/A + N/A + N/A + + + + + N/A + N/A + N/A + N/A + N/A + N/A + N/A + N/A + + + N/A + N/A + N/A + N/A + N/A + N/A + N/A + N/A + + + + + + N/A + N/A + + + N/A + N/A + + N/A + N/A + + + 65 C + 99 C + 96 C + N/A + N/A + N/A + + + P2 + Supported + 89.74 W + 180.00 W + 180.00 W + 180.00 W + 90.00 W + 180.00 W + + + 1885 MHz + 1885 MHz + 4513 MHz + 1695 MHz + + + N/A + N/A + + + N/A + N/A + + + 1911 MHz + 1911 MHz + 5005 MHz + 1708 MHz + + + N/A + + + N/A + N/A + + N/A + + + 10131 + C + ffmpeg + 389 MiB + + + 13597 + C + ffmpeg + 1054 MiB + + + 16870 + C + ffmpeg + 549 MiB + + + + + + + \ No newline at end of file diff --git a/psutil/gpu/nvidia/nvidia.go b/psutil/gpu/nvidia/nvidia.go new file mode 100644 index 00000000..ba45e2fa --- /dev/null +++ b/psutil/gpu/nvidia/nvidia.go @@ -0,0 +1,284 @@ +package nvidia + +import ( + "bytes" + "context" + "encoding/xml" + "fmt" + "os/exec" + "sync" + "time" + + "github.com/datarhei/core/v16/psutil/gpu" +) + +var Default gpu.GPU + +func init() { + Default = New("") +} + +type Megabytes uint64 + +func (m *Megabytes) UnmarshalText(text []byte) error { + value := uint64(0) + _, err := fmt.Sscanf(string(text), "%d MiB", &value) + if err != nil { + return err + } + + *m = Megabytes(value * 1024 * 1024) + + return nil +} + +type Utilization float64 + +func (u *Utilization) UnmarshalText(text []byte) error { + value := float64(0) + _, err := fmt.Sscanf(string(text), "%f %%", &value) + if err != nil { + return err + } + + *u = Utilization(value) + + return nil +} + +type Process struct { + PID int32 `xml:"pid"` + Memory Megabytes `xml:"used_memory"` +} + +type GPUStats struct { + Name string `xml:"product_name"` + Architecture string `xml:"product_architecture"` + + MemoryTotal Megabytes `xml:"fb_memory_usage>total"` + MemoryUsed Megabytes `xml:"fb_memory_usage>used"` + + Usage Utilization `xml:"utilization>gpu_util"` + MemoryUsage Utilization `xml:"utilization>memory_util"` + EncoderUsage Utilization `xml:"utilization>encoder_util"` + DecoderUsage Utilization `xml:"utilization>decoder_util"` + + Process []Process `xml:"processes>process_info"` +} + +type Stats struct { + GPU []GPUStats `xml:"gpu"` +} + +func parse(data []byte) (Stats, error) { + nv := Stats{} + + err := xml.Unmarshal(data, &nv) + if err != nil { + return nv, fmt.Errorf("parsing report: %w", err) + } + + return nv, nil +} + +type nvidia struct { + cmd *exec.Cmd + wr *writer + + lock sync.RWMutex + cancel context.CancelFunc + stats Stats + process map[int32]Process + err error +} + +type dummy struct{} + +func (d *dummy) Count() (int, error) { return 0, nil } +func (d *dummy) Stats() ([]gpu.Stats, error) { return nil, nil } +func (d *dummy) Process(pid int32) (gpu.Process, error) { return gpu.Process{}, gpu.ErrProcessNotFound } + +type writer struct { + buf bytes.Buffer + ch chan Stats +} + +var terminator = []byte("\n") + +func (w *writer) Write(data []byte) (int, error) { + n, err := w.buf.Write(data) + if err != nil { + return n, err + } + + for { + idx := bytes.Index(w.buf.Bytes(), terminator) + if idx == -1 { + break + } + + content := make([]byte, idx+len(terminator)) + n, err := w.buf.Read(content) + if err != nil || n != len(content) { + break + } + + s, err := parse(content) + if err != nil { + continue + } + + w.ch <- s + } + + return n, nil +} + +func New(path string) gpu.GPU { + if len(path) == 0 { + path = "nvidia-smi" + } + + _, err := exec.LookPath(path) + if err != nil { + return &dummy{} + } + + n := &nvidia{ + wr: &writer{ + ch: make(chan Stats, 1), + }, + process: map[int32]Process{}, + } + + ctx, cancel := context.WithCancel(context.Background()) + n.cancel = cancel + + go n.runner(ctx, path) + go n.reader(ctx) + + return n +} + +func (n *nvidia) reader(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case stats := <-n.wr.ch: + n.lock.Lock() + n.stats = stats + n.process = map[int32]Process{} + for _, g := range n.stats.GPU { + for _, p := range g.Process { + n.process[p.PID] = p + } + } + n.lock.Unlock() + } + } +} + +func (n *nvidia) runner(ctx context.Context, path string) { + for { + n.cmd = exec.Command(path, "-q", "-x", "-l", "1") + n.cmd.Stdout = n.wr + err := n.cmd.Start() + if err != nil { + n.lock.Lock() + n.err = err + n.lock.Unlock() + + time.Sleep(3 * time.Second) + continue + } + + err = n.cmd.Wait() + + n.lock.Lock() + n.err = err + n.lock.Unlock() + + select { + case <-ctx.Done(): + return + default: + } + } +} + +func (n *nvidia) Count() (int, error) { + n.lock.RLock() + defer n.lock.RUnlock() + + if n.err != nil { + return 0, n.err + } + + return len(n.stats.GPU), nil +} + +func (n *nvidia) Stats() ([]gpu.Stats, error) { + s := []gpu.Stats{} + + n.lock.RLock() + defer n.lock.RUnlock() + + if n.err != nil { + return s, n.err + } + + for _, nv := range n.stats.GPU { + stats := gpu.Stats{ + Name: nv.Name, + Architecture: nv.Architecture, + MemoryTotal: uint64(nv.MemoryTotal), + MemoryUsed: uint64(nv.MemoryUsed), + Usage: float64(nv.Usage), + MemoryUsage: float64(nv.MemoryUsage), + EncoderUsage: float64(nv.EncoderUsage), + DecoderUsage: float64(nv.DecoderUsage), + Process: []gpu.Process{}, + } + + for _, p := range nv.Process { + stats.Process = append(stats.Process, gpu.Process{ + PID: p.PID, + Memory: uint64(p.Memory), + }) + } + + s = append(s, stats) + } + + return s, nil +} + +func (n *nvidia) Process(pid int32) (gpu.Process, error) { + n.lock.RLock() + defer n.lock.RUnlock() + + p, hasProcess := n.process[pid] + if !hasProcess { + return gpu.Process{}, gpu.ErrProcessNotFound + } + + return gpu.Process{ + PID: p.PID, + Memory: uint64(p.Memory), + }, nil +} + +func (n *nvidia) Close() { + n.lock.Lock() + defer n.lock.Unlock() + + if n.cancel == nil { + return + } + + n.cancel() + n.cancel = nil + + n.cmd.Process.Kill() +} diff --git a/psutil/gpu/nvidia/nvidia_test.go b/psutil/gpu/nvidia/nvidia_test.go new file mode 100644 index 00000000..f18310b2 --- /dev/null +++ b/psutil/gpu/nvidia/nvidia_test.go @@ -0,0 +1,102 @@ +package nvidia + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseNV(t *testing.T) { + data, err := os.ReadFile("./fixtures/data1.xml") + require.NoError(t, err) + + nv, err := parse(data) + require.NoError(t, err) + + require.Equal(t, Stats{ + GPU: []GPUStats{ + { + Name: "NVIDIA GeForce GTX 1080", + Architecture: "Pascal", + MemoryTotal: 8119 * 1024 * 1024, + MemoryUsed: 918 * 1024 * 1024, + Usage: 15, + MemoryUsage: 7, + EncoderUsage: 3, + DecoderUsage: 0, + Process: []Process{ + { + PID: 18179, + Memory: 916 * 1024 * 1024, + }, + }, + }, + }, + }, nv) + + data, err = os.ReadFile("./fixtures/data2.xml") + require.NoError(t, err) + + nv, err = parse(data) + require.NoError(t, err) + + require.Equal(t, Stats{ + GPU: []GPUStats{ + { + Name: "NVIDIA L4", + Architecture: "Ada Lovelace", + MemoryTotal: 23034 * 1024 * 1024, + MemoryUsed: 1 * 1024 * 1024, + Usage: 2, + MemoryUsage: 0, + EncoderUsage: 0, + DecoderUsage: 0, + }, + { + Name: "NVIDIA L4", + Architecture: "Ada Lovelace", + MemoryTotal: 23034 * 1024 * 1024, + MemoryUsed: 1 * 1024 * 1024, + Usage: 3, + MemoryUsage: 0, + EncoderUsage: 0, + DecoderUsage: 0, + }, + }, + }, nv) + + data, err = os.ReadFile("./fixtures/data3.xml") + require.NoError(t, err) + + nv, err = parse(data) + require.NoError(t, err) + + require.Equal(t, Stats{ + GPU: []GPUStats{ + { + Name: "GeForce GTX 1080", + MemoryTotal: 8119 * 1024 * 1024, + MemoryUsed: 2006 * 1024 * 1024, + Usage: 32, + MemoryUsage: 11, + EncoderUsage: 17, + DecoderUsage: 25, + Process: []Process{ + { + PID: 10131, + Memory: 389 * 1024 * 1024, + }, + { + PID: 13597, + Memory: 1054 * 1024 * 1024, + }, + { + PID: 16870, + Memory: 549 * 1024 * 1024, + }, + }, + }, + }, + }, nv) +} diff --git a/psutil/psutil.go b/psutil/psutil.go index f6b95934..73c47a67 100644 --- a/psutil/psutil.go +++ b/psutil/psutil.go @@ -13,6 +13,8 @@ import ( "sync" "time" + "github.com/datarhei/core/v16/psutil/gpu/nvidia" + "github.com/shirou/gopsutil/v3/cpu" "github.com/shirou/gopsutil/v3/disk" "github.com/shirou/gopsutil/v3/mem" @@ -58,6 +60,18 @@ type CPUInfoStat struct { Other float64 // percent 0-100 } +type GPUInfoStat struct { + Name string + + MemoryTotal uint64 // bytes + MemoryUsed uint64 // bytes + + Usage float64 // percent 0-100 + MemoryUsage float64 // percent 0-100 + EncoderUsage float64 // percent 0-100 + DecoderUsage float64 // percent 0-100 +} + type cpuTimesStat struct { total float64 // seconds system float64 // seconds @@ -73,12 +87,16 @@ type Util interface { // CPUCounts returns the number of cores, either logical or physical. CPUCounts(logical bool) (float64, error) + // GPUCounts returns the number of GPU cores. + GPUCounts() (float64, error) + // CPUPercent returns the current CPU load in percent. The values range // from 0 to 100, independently of the number of logical cores. CPUPercent() (*CPUInfoStat, error) DiskUsage(path string) (*disk.UsageStat, error) VirtualMemory() (*MemoryInfoStat, error) NetIOCounters(pernic bool) ([]net.IOCountersStat, error) + GPUStats() ([]GPUInfoStat, error) // Process returns a process observer for a process with the given pid. Process(pid int32) (Process, error) @@ -282,6 +300,16 @@ func CPUCounts(logical bool) (float64, error) { return DefaultUtil.CPUCounts(logical) } +func (u *util) GPUCounts() (float64, error) { + count, err := nvidia.Default.Count() + + return float64(count), err +} + +func GPUCounts() (float64, error) { + return DefaultUtil.GPUCounts() +} + // cpuTimes returns the current cpu usage times in seconds. func (u *util) cpuTimes() (*cpuTimesStat, error) { if u.hasCgroup && u.cpuLimit > 0 { @@ -534,3 +562,30 @@ func cpuTotal(c *cpu.TimesStat) float64 { return c.User + c.System + c.Idle + c.Nice + c.Iowait + c.Irq + c.Softirq + c.Steal + c.Guest + c.GuestNice } + +func (u *util) GPUStats() ([]GPUInfoStat, error) { + nvstats, err := nvidia.Default.Stats() + if err != nil { + return nil, err + } + + stats := []GPUInfoStat{} + + for _, nv := range nvstats { + stats = append(stats, GPUInfoStat{ + Name: nv.Name, + MemoryTotal: nv.MemoryTotal, + MemoryUsed: nv.MemoryUsed, + Usage: nv.Usage, + MemoryUsage: nv.MemoryUsage, + EncoderUsage: nv.EncoderUsage, + DecoderUsage: nv.DecoderUsage, + }) + } + + return stats, nil +} + +func GPUStats() ([]GPUInfoStat, error) { + return DefaultUtil.GPUStats() +} diff --git a/resources/resources_test.go b/resources/resources_test.go index 0158c7f7..3d26c40c 100644 --- a/resources/resources_test.go +++ b/resources/resources_test.go @@ -21,6 +21,10 @@ func (u *util) CPUCounts(logical bool) (float64, error) { return 2, nil } +func (u *util) GPUCounts() (float64, error) { + return 0, nil +} + func (u *util) CPUPercent() (*psutil.CPUInfoStat, error) { return &psutil.CPUInfoStat{ System: 10, @@ -46,6 +50,10 @@ func (u *util) NetIOCounters(pernic bool) ([]net.IOCountersStat, error) { return nil, nil } +func (u *util) GPUStats() ([]psutil.GPUInfoStat, error) { + return nil, nil +} + func (u *util) Process(pid int32) (psutil.Process, error) { return nil, nil } From 72a3b8c17d556e39afefa235edfe43295bf5f97b Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Mon, 19 Aug 2024 12:03:34 +0200 Subject: [PATCH 02/64] Update detendencies --- go.mod | 34 +- go.sum | 67 +- vendor/github.com/adhocore/gronx/batch.go | 2 +- vendor/github.com/datarhei/gosrt/README.md | 49 +- .../github.com/datarhei/gosrt/conn_request.go | 421 ++ vendor/github.com/datarhei/gosrt/dial.go | 8 + vendor/github.com/datarhei/gosrt/listen.go | 458 +- vendor/github.com/datarhei/gosrt/server.go | 30 +- .../gabriel-vasile/mimetype/README.md | 2 +- .../mimetype/internal/json/json.go | 27 +- .../mimetype/internal/magic/archive.go | 31 +- .../mimetype/internal/magic/ms_office.go | 70 +- .../mimetype/internal/magic/text.go | 2 +- .../mimetype/internal/magic/zip.go | 13 +- .../gabriel-vasile/mimetype/mimetype.gif | Bin 1343793 -> 0 bytes .../mimetype/supported_mimes.md | 2 +- .../gabriel-vasile/mimetype/tree.go | 2 +- vendor/github.com/mholt/acmez/v2/acme/ari.go | 8 +- .../mholt/acmez/v2/acme/certificate.go | 10 + vendor/github.com/miekg/dns/README.md | 1 + vendor/github.com/miekg/dns/edns.go | 62 +- vendor/github.com/miekg/dns/types.go | 14 + vendor/github.com/miekg/dns/version.go | 2 +- vendor/github.com/miekg/dns/zduplicate.go | 9 + vendor/github.com/miekg/dns/zmsg.go | 11 + vendor/github.com/miekg/dns/ztypes.go | 12 + .../minio/minio-go/v7/api-bucket-cors.go | 136 + vendor/github.com/minio/minio-go/v7/api.go | 2 +- vendor/github.com/minio/minio-go/v7/core.go | 3 +- .../minio/minio-go/v7/functional_tests.go | 1102 +++- .../minio/minio-go/v7/pkg/cors/cors.go | 91 + .../github.com/minio/minio-go/v7/s3-error.go | 1 + .../prometheus/client_golang/NOTICE | 5 - .../internal/github.com/golang/gddo/LICENSE | 27 + .../golang/gddo/httputil/header/header.go | 145 + .../golang/gddo/httputil/negotiate.go | 36 + .../client_golang/prometheus/go_collector.go | 55 +- .../prometheus/go_collector_latest.go | 19 +- .../client_golang/prometheus/histogram.go | 226 +- .../internal/go_collector_options.go | 2 + .../client_golang/prometheus/metric.go | 2 +- .../prometheus/process_collector.go | 27 +- .../prometheus/process_collector_other.go | 14 + .../prometheus/promhttp/delegator.go | 6 + .../client_golang/prometheus/promhttp/http.go | 111 +- .../client_golang/prometheus/registry.go | 17 +- .../client_golang/prometheus/summary.go | 42 + .../client_golang/prometheus/vec.go | 2 +- vendor/github.com/zeebo/blake3/.gitignore | 1 + vendor/github.com/zeebo/blake3/Makefile | 19 +- vendor/github.com/zeebo/blake3/digest.go | 2 +- .../zeebo/blake3/internal/consts/cpu.go | 2 + .../blake3/internal/consts/cpu_purego.go | 8 + vendor/golang.org/x/crypto/LICENSE | 4 +- .../golang.org/x/crypto/sha3/keccakf_amd64.s | 5787 +++++++++++++++-- vendor/golang.org/x/mod/LICENSE | 4 +- vendor/golang.org/x/net/LICENSE | 4 +- vendor/golang.org/x/sync/LICENSE | 4 +- vendor/golang.org/x/sys/LICENSE | 4 +- vendor/golang.org/x/sys/cpu/cpu.go | 2 + vendor/golang.org/x/sys/cpu/cpu_arm64.go | 12 + .../golang.org/x/sys/cpu/cpu_linux_arm64.go | 5 + vendor/golang.org/x/sys/unix/mkerrors.sh | 1 + .../golang.org/x/sys/unix/syscall_darwin.go | 12 + vendor/golang.org/x/sys/unix/syscall_linux.go | 1 + .../golang.org/x/sys/unix/syscall_openbsd.go | 1 + .../x/sys/unix/zerrors_darwin_amd64.go | 5 + .../x/sys/unix/zerrors_darwin_arm64.go | 5 + vendor/golang.org/x/sys/unix/zerrors_linux.go | 38 +- .../x/sys/unix/zerrors_linux_386.go | 2 + .../x/sys/unix/zerrors_linux_amd64.go | 2 + .../x/sys/unix/zerrors_linux_arm.go | 2 + .../x/sys/unix/zerrors_linux_arm64.go | 2 + .../x/sys/unix/zerrors_linux_loong64.go | 2 + .../x/sys/unix/zerrors_linux_mips.go | 2 + .../x/sys/unix/zerrors_linux_mips64.go | 2 + .../x/sys/unix/zerrors_linux_mips64le.go | 2 + .../x/sys/unix/zerrors_linux_mipsle.go | 2 + .../x/sys/unix/zerrors_linux_ppc.go | 2 + .../x/sys/unix/zerrors_linux_ppc64.go | 2 + .../x/sys/unix/zerrors_linux_ppc64le.go | 2 + .../x/sys/unix/zerrors_linux_riscv64.go | 2 + .../x/sys/unix/zerrors_linux_s390x.go | 2 + .../x/sys/unix/zerrors_linux_sparc64.go | 2 + .../x/sys/unix/zsyscall_darwin_amd64.go | 48 + .../x/sys/unix/zsyscall_darwin_amd64.s | 10 + .../x/sys/unix/zsyscall_darwin_arm64.go | 48 + .../x/sys/unix/zsyscall_darwin_arm64.s | 10 + .../golang.org/x/sys/unix/zsyscall_linux.go | 16 + .../x/sys/unix/zsyscall_openbsd_386.go | 24 + .../x/sys/unix/zsyscall_openbsd_386.s | 5 + .../x/sys/unix/zsyscall_openbsd_amd64.go | 24 + .../x/sys/unix/zsyscall_openbsd_amd64.s | 5 + .../x/sys/unix/zsyscall_openbsd_arm.go | 24 + .../x/sys/unix/zsyscall_openbsd_arm.s | 5 + .../x/sys/unix/zsyscall_openbsd_arm64.go | 24 + .../x/sys/unix/zsyscall_openbsd_arm64.s | 5 + .../x/sys/unix/zsyscall_openbsd_mips64.go | 24 + .../x/sys/unix/zsyscall_openbsd_mips64.s | 5 + .../x/sys/unix/zsyscall_openbsd_ppc64.go | 24 + .../x/sys/unix/zsyscall_openbsd_ppc64.s | 6 + .../x/sys/unix/zsyscall_openbsd_riscv64.go | 24 + .../x/sys/unix/zsyscall_openbsd_riscv64.s | 5 + .../x/sys/unix/zsysnum_linux_386.go | 1 + .../x/sys/unix/zsysnum_linux_amd64.go | 1 + .../x/sys/unix/zsysnum_linux_arm.go | 1 + .../x/sys/unix/zsysnum_linux_arm64.go | 1 + .../x/sys/unix/zsysnum_linux_loong64.go | 1 + .../x/sys/unix/zsysnum_linux_mips.go | 1 + .../x/sys/unix/zsysnum_linux_mips64.go | 1 + .../x/sys/unix/zsysnum_linux_mips64le.go | 1 + .../x/sys/unix/zsysnum_linux_mipsle.go | 1 + .../x/sys/unix/zsysnum_linux_ppc.go | 1 + .../x/sys/unix/zsysnum_linux_ppc64.go | 1 + .../x/sys/unix/zsysnum_linux_ppc64le.go | 1 + .../x/sys/unix/zsysnum_linux_riscv64.go | 1 + .../x/sys/unix/zsysnum_linux_s390x.go | 1 + .../x/sys/unix/zsysnum_linux_sparc64.go | 1 + vendor/golang.org/x/sys/unix/ztypes_linux.go | 10 +- .../x/sys/windows/security_windows.go | 2 +- .../x/sys/windows/syscall_windows.go | 12 +- .../golang.org/x/sys/windows/types_windows.go | 71 +- .../x/sys/windows/zsyscall_windows.go | 49 +- vendor/golang.org/x/text/LICENSE | 4 +- vendor/golang.org/x/time/LICENSE | 4 +- vendor/golang.org/x/tools/LICENSE | 4 +- .../x/tools/go/packages/packages.go | 11 +- .../golang.org/x/tools/go/packages/visit.go | 9 + .../x/tools/go/types/objectpath/objectpath.go | 40 +- .../x/tools/internal/aliases/aliases_go121.go | 12 +- .../x/tools/internal/aliases/aliases_go122.go | 36 + .../x/tools/internal/pkgbits/decoder.go | 4 - .../x/tools/internal/versions/constraint.go | 13 + .../internal/versions/constraint_go121.go | 14 + vendor/modules.txt | 37 +- 135 files changed, 8895 insertions(+), 1157 deletions(-) create mode 100644 vendor/github.com/datarhei/gosrt/conn_request.go delete mode 100644 vendor/github.com/gabriel-vasile/mimetype/mimetype.gif create mode 100644 vendor/github.com/minio/minio-go/v7/api-bucket-cors.go create mode 100644 vendor/github.com/minio/minio-go/v7/pkg/cors/cors.go create mode 100644 vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/LICENSE create mode 100644 vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header/header.go create mode 100644 vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/negotiate.go create mode 100644 vendor/github.com/zeebo/blake3/internal/consts/cpu_purego.go create mode 100644 vendor/golang.org/x/tools/internal/versions/constraint.go create mode 100644 vendor/golang.org/x/tools/internal/versions/constraint_go121.go diff --git a/go.mod b/go.mod index a19cd943..50e55ef3 100644 --- a/go.mod +++ b/go.mod @@ -7,11 +7,11 @@ toolchain go1.22.1 require ( github.com/99designs/gqlgen v0.17.49 github.com/Masterminds/semver/v3 v3.2.1 - github.com/adhocore/gronx v1.8.1 + github.com/adhocore/gronx v1.19.0 github.com/andybalholm/brotli v1.1.0 github.com/atrox/haikunatorgo/v2 v2.0.1 github.com/caddyserver/certmagic v0.21.3 - github.com/datarhei/gosrt v0.6.0 + github.com/datarhei/gosrt v0.7.0 github.com/datarhei/joy4 v0.0.0-20240603190808-b1407345907e github.com/fujiwara/shapeio v1.0.0 github.com/go-playground/validator/v10 v10.22.0 @@ -32,9 +32,9 @@ require ( github.com/lestrrat-go/strftime v1.0.6 github.com/lithammer/shortuuid/v4 v4.0.0 github.com/mattn/go-isatty v0.0.20 - github.com/minio/minio-go/v7 v7.0.74 + github.com/minio/minio-go/v7 v7.0.75 github.com/prep/average v0.0.0-20200506183628-d26c465f48c3 - github.com/prometheus/client_golang v1.19.1 + github.com/prometheus/client_golang v1.20.0 github.com/puzpuzpuz/xsync/v3 v3.4.0 github.com/shirou/gopsutil/v3 v3.24.5 github.com/stretchr/testify v1.9.0 @@ -46,12 +46,10 @@ require ( go.etcd.io/bbolt v1.3.10 go.uber.org/automaxprocs v1.5.3 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.25.0 - golang.org/x/mod v0.19.0 + golang.org/x/crypto v0.26.0 + golang.org/x/mod v0.20.0 ) -//replace github.com/datarhei/core-client-go/v16 => ../core-client-go - require ( github.com/KyleBanks/depth v1.2.1 // indirect github.com/agnivade/levenshtein v1.1.1 // indirect @@ -65,7 +63,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/color v1.17.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.4 // indirect + github.com/gabriel-vasile/mimetype v1.4.5 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect @@ -89,8 +87,8 @@ require ( github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mholt/acmez/v2 v2.0.1 // indirect - github.com/miekg/dns v1.1.61 // indirect + github.com/mholt/acmez/v2 v2.0.2 // indirect + github.com/miekg/dns v1.1.62 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -113,14 +111,14 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - github.com/zeebo/blake3 v0.2.3 // indirect + github.com/zeebo/blake3 v0.2.4 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.23.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/time v0.6.0 // indirect + golang.org/x/tools v0.24.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 4d098652..4213378d 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,8 @@ github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0 github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE= github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk= -github.com/adhocore/gronx v1.8.1 h1:F2mLTG5sB11z7vplwD4iydz3YCEjstSfYmCrdSm3t6A= -github.com/adhocore/gronx v1.8.1/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= +github.com/adhocore/gronx v1.19.0 h1:GrEvNMPDwXND+YFadCyFVQPC+/xxoGJaQzu+duNf6aU= +github.com/adhocore/gronx v1.19.0/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -46,8 +46,8 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/datarhei/gosrt v0.6.0 h1:HrrXAw90V78ok4WMIhX6se1aTHPCn82Sg2hj+PhdmGc= -github.com/datarhei/gosrt v0.6.0/go.mod h1:fsOWdLSHUHShHjgi/46h6wjtdQrtnSdAQFnlas8ONxs= +github.com/datarhei/gosrt v0.7.0 h1:1/IY66HVVgqGA9zkmL5l6jUFuI8t/76WkuamSkJqHqs= +github.com/datarhei/gosrt v0.7.0/go.mod h1:wTDoyog1z4au8Fd/QJBQAndzvccuxjqUL/qMm0EyJxE= github.com/datarhei/joy4 v0.0.0-20240603190808-b1407345907e h1:Qc/0D4xvXrazFkoi/4UGqO15yQ1JN5I8h7RwdzCLgTY= github.com/datarhei/joy4 v0.0.0-20240603190808-b1407345907e/go.mod h1:Jcw/6jZDQQmPx8A7INEkXmuEF7E9jjBbSTfVSLwmiQw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -63,8 +63,8 @@ github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fujiwara/shapeio v1.0.0 h1:xG5D9oNqCSUUbryZ/jQV3cqe1v2suEjwPIcEg1gKM8M= github.com/fujiwara/shapeio v1.0.0/go.mod h1:LmEmu6L/8jetyj1oewewFb7bZCNRwE7wLCUNzDLaLVA= -github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= -github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= +github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= +github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= @@ -158,7 +158,6 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -170,6 +169,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= @@ -198,14 +199,14 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k= -github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U= -github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs= -github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ= +github.com/mholt/acmez/v2 v2.0.2 h1:OmK6xckte2JfKGPz4OAA8aNHTiLvGp8tLzmrd/wfSyw= +github.com/mholt/acmez/v2 v2.0.2/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U= +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= +github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.74 h1:fTo/XlPBTSpo3BAMshlwKL5RspXRv9us5UeHEGYCFe0= -github.com/minio/minio-go/v7 v7.0.74/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8= +github.com/minio/minio-go/v7 v7.0.75 h1:0uLrB6u6teY2Jt+cJUVi9cTvDRuBKWSRzSAcznRkwlE= +github.com/minio/minio-go/v7 v7.0.75/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -233,8 +234,8 @@ github.com/prep/average v0.0.0-20200506183628-d26c465f48c3/go.mod h1:0ZE5gcyWKS1 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI= +github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -310,8 +311,8 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= -github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= +github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= @@ -326,19 +327,19 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -355,16 +356,16 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= diff --git a/vendor/github.com/adhocore/gronx/batch.go b/vendor/github.com/adhocore/gronx/batch.go index 63d85ec2..37fddb94 100644 --- a/vendor/github.com/adhocore/gronx/batch.go +++ b/vendor/github.com/adhocore/gronx/batch.go @@ -7,9 +7,9 @@ import ( // Expr represents an item in array for batch check type Expr struct { + Err error Expr string Due bool - Err error } // BatchDue checks if multiple expressions are due for given time (or now). diff --git a/vendor/github.com/datarhei/gosrt/README.md b/vendor/github.com/datarhei/gosrt/README.md index 25681a47..f6133c7e 100644 --- a/vendor/github.com/datarhei/gosrt/README.md +++ b/vendor/github.com/datarhei/gosrt/README.md @@ -42,7 +42,7 @@ The parts that are implemented are based on what has been published in the SRT R ## Requirements -A Go version of 1.18+ is required. +A Go version of 1.20+ is required. ## Installation @@ -89,39 +89,42 @@ if err != nil { } for { - conn, mode, err := ln.Accept(func(req ConnRequest) ConnType { - // check connection request - return srt.REJECT - }) + req, err := ln.Accept2() if err != nil { // handle error } - if mode == srt.REJECT { - // rejected connection, ignore - continue - } + go func(req ConnRequest) { + // check connection request by inspecting the connection request + // and either rejecting it ... - if mode == srt.PUBLISH { - go handlePublish(conn) - } else { // srt.SUBSCRIBE - go handleSubscribe(conn) - } + if somethingIsWrong { + req.Reject(srt.REJ_PEER) + return + } + + // ... or accepting it ... + + conn, err := req.Accept() + if err != nil { + return + } + + // ... and decide whether it is a publishing or subscribing connection. + + if publish { + handlePublish(conn) + } else { + handleSubscribe(conn) + } + }(req) } ``` In the `contrib/server` directory you'll find a complete example of a SRT server. For your convenience -this modules provides the `Server` type which is a light framework for creating your own SRT server. The +this module provides the `Server` type which is a light framework for creating your own SRT server. The example server is based on this type. -### PUBLISH / SUBSCRIBE - -The `Accept` function from the `Listener` expects a function that handles the connection requests. It can -return 3 different values: `srt.PUBLISH`, `srt.SUBSCRIBE`, and `srt.REJECT`. `srt.PUBLISH` means that the -server expects the caller to send data, whereas `srt.SUBSCRIBE` means that the server will send data to -the caller. This is opiniated towards a streaming server, however in your implementation of a listener -you are free to handle connections requests to your liking. - ## Contributed client In the `contrib/client` directory you'll find an example implementation of a SRT client. diff --git a/vendor/github.com/datarhei/gosrt/conn_request.go b/vendor/github.com/datarhei/gosrt/conn_request.go new file mode 100644 index 00000000..80576955 --- /dev/null +++ b/vendor/github.com/datarhei/gosrt/conn_request.go @@ -0,0 +1,421 @@ +package srt + +import ( + "fmt" + "net" + "time" + + "github.com/datarhei/gosrt/crypto" + "github.com/datarhei/gosrt/packet" +) + +// ConnRequest is an incoming connection request +type ConnRequest interface { + // RemoteAddr returns the address of the peer. The returned net.Addr + // is a copy and can be used at will. + RemoteAddr() net.Addr + + // Version returns the handshake version of the incoming request. Currently + // known versions are 4 and 5. With version 4 the StreamId will always be + // empty and IsEncrypted will always return false. An incoming version 4 + // connection will always be publishing. + Version() uint32 + + // StreamId returns the streamid of the requesting connection. Use this + // to decide what to do with the connection. + StreamId() string + + // IsEncrypted returns whether the connection is encrypted. If it is + // encrypted, use SetPassphrase to set the passphrase for decrypting. + IsEncrypted() bool + + // SetPassphrase sets the passphrase in order to decrypt the incoming + // data. Returns an error if the passphrase did not work or the connection + // is not encrypted. + SetPassphrase(p string) error + + // SetRejectionReason sets the rejection reason for the connection. If + // no set, REJ_PEER will be used. + // + // Deprecated: replaced by Reject(). + SetRejectionReason(r RejectionReason) + + // Accept accepts the request and returns a connection. + Accept() (Conn, error) + + // Reject rejects the request. + Reject(r RejectionReason) +} + +// connRequest implements the ConnRequest interface +type connRequest struct { + ln *listener + addr net.Addr + start time.Time + socketId uint32 + timestamp uint32 + config Config + handshake *packet.CIFHandshake + crypto crypto.Crypto + passphrase string + rejectionReason RejectionReason +} + +func newConnRequest(ln *listener, p packet.Packet) *connRequest { + cif := &packet.CIFHandshake{} + + err := p.UnmarshalCIF(cif) + + ln.log("handshake:recv:dump", func() string { return p.Dump() }) + ln.log("handshake:recv:cif", func() string { return cif.String() }) + + if err != nil { + ln.log("handshake:recv:error", func() string { return err.Error() }) + return nil + } + + // Assemble the response (4.3.1. Caller-Listener Handshake) + + p.Header().ControlType = packet.CTRLTYPE_HANDSHAKE + p.Header().SubType = 0 + p.Header().TypeSpecific = 0 + p.Header().Timestamp = uint32(time.Since(ln.start).Microseconds()) + p.Header().DestinationSocketId = cif.SRTSocketId + + cif.PeerIP.FromNetAddr(ln.addr) + + // Create a copy of the configuration for the connection + config := ln.config + + if cif.HandshakeType == packet.HSTYPE_INDUCTION { + // cif + cif.Version = 5 + cif.EncryptionField = 0 // Don't advertise any specific encryption method + cif.ExtensionField = 0x4A17 + //cif.initialPacketSequenceNumber = newCircular(0, MAX_SEQUENCENUMBER) + //cif.maxTransmissionUnitSize = 0 + //cif.maxFlowWindowSize = 0 + //cif.SRTSocketId = 0 + cif.SynCookie = ln.syncookie.Get(p.Header().Addr.String()) + + p.MarshalCIF(cif) + + ln.log("handshake:send:dump", func() string { return p.Dump() }) + ln.log("handshake:send:cif", func() string { return cif.String() }) + + ln.send(p) + } else if cif.HandshakeType == packet.HSTYPE_CONCLUSION { + // Verify the SYN cookie + if !ln.syncookie.Verify(cif.SynCookie, p.Header().Addr.String()) { + cif.HandshakeType = packet.HandshakeType(REJ_ROGUE) + ln.log("handshake:recv:error", func() string { return "invalid SYN cookie" }) + p.MarshalCIF(cif) + ln.log("handshake:send:dump", func() string { return p.Dump() }) + ln.log("handshake:send:cif", func() string { return cif.String() }) + ln.send(p) + + return nil + } + + // Peer is advertising a too big MSS + if cif.MaxTransmissionUnitSize > MAX_MSS_SIZE { + cif.HandshakeType = packet.HandshakeType(REJ_ROGUE) + ln.log("handshake:recv:error", func() string { return fmt.Sprintf("MTU is too big (%d bytes)", cif.MaxTransmissionUnitSize) }) + p.MarshalCIF(cif) + ln.log("handshake:send:dump", func() string { return p.Dump() }) + ln.log("handshake:send:cif", func() string { return cif.String() }) + ln.send(p) + + return nil + } + + // If the peer has a smaller MTU size, adjust to it + if cif.MaxTransmissionUnitSize < config.MSS { + config.MSS = cif.MaxTransmissionUnitSize + config.PayloadSize = config.MSS - SRT_HEADER_SIZE - UDP_HEADER_SIZE + + if config.PayloadSize < MIN_PAYLOAD_SIZE { + cif.HandshakeType = packet.HandshakeType(REJ_ROGUE) + ln.log("handshake:recv:error", func() string { return fmt.Sprintf("payload size is too small (%d bytes)", config.PayloadSize) }) + p.MarshalCIF(cif) + ln.log("handshake:send:dump", func() string { return p.Dump() }) + ln.log("handshake:send:cif", func() string { return cif.String() }) + ln.send(p) + } + } + + // We only support HSv4 and HSv5 + if cif.Version == 4 { + // Check if the type (encryption field + extension field) has the value 2 + if cif.EncryptionField != 0 || cif.ExtensionField != 2 { + cif.HandshakeType = packet.HandshakeType(REJ_ROGUE) + ln.log("handshake:recv:error", func() string { return "invalid type, expecting a value of 2 (UDT_DGRAM)" }) + p.MarshalCIF(cif) + ln.log("handshake:send:dump", func() string { return p.Dump() }) + ln.log("handshake:send:cif", func() string { return cif.String() }) + ln.send(p) + + return nil + } + } else if cif.Version == 5 { + if cif.SRTHS == nil { + cif.HandshakeType = packet.HandshakeType(REJ_ROGUE) + ln.log("handshake:recv:error", func() string { return "missing handshake extension" }) + p.MarshalCIF(cif) + ln.log("handshake:send:dump", func() string { return p.Dump() }) + ln.log("handshake:send:cif", func() string { return cif.String() }) + ln.send(p) + + return nil + } + + // Check if the peer version is sufficient + if cif.SRTHS.SRTVersion < config.MinVersion { + cif.HandshakeType = packet.HandshakeType(REJ_VERSION) + ln.log("handshake:recv:error", func() string { + return fmt.Sprintf("peer version insufficient (%#06x), expecting at least %#06x", cif.SRTHS.SRTVersion, config.MinVersion) + }) + p.MarshalCIF(cif) + ln.log("handshake:send:dump", func() string { return p.Dump() }) + ln.log("handshake:send:cif", func() string { return cif.String() }) + ln.send(p) + + return nil + } + + // Check the required SRT flags + if !cif.SRTHS.SRTFlags.TSBPDSND || !cif.SRTHS.SRTFlags.TSBPDRCV || !cif.SRTHS.SRTFlags.TLPKTDROP || !cif.SRTHS.SRTFlags.PERIODICNAK || !cif.SRTHS.SRTFlags.REXMITFLG { + cif.HandshakeType = packet.HandshakeType(REJ_ROGUE) + ln.log("handshake:recv:error", func() string { return "not all required flags are set" }) + p.MarshalCIF(cif) + ln.log("handshake:send:dump", func() string { return p.Dump() }) + ln.log("handshake:send:cif", func() string { return cif.String() }) + ln.send(p) + + return nil + } + + // We only support live streaming + if cif.SRTHS.SRTFlags.STREAM { + cif.HandshakeType = packet.HandshakeType(REJ_MESSAGEAPI) + ln.log("handshake:recv:error", func() string { return "only live streaming is supported" }) + p.MarshalCIF(cif) + ln.log("handshake:send:dump", func() string { return p.Dump() }) + ln.log("handshake:send:cif", func() string { return cif.String() }) + ln.send(p) + + return nil + } + } else { + cif.HandshakeType = packet.HandshakeType(REJ_ROGUE) + ln.log("handshake:recv:error", func() string { return fmt.Sprintf("only HSv4 and HSv5 are supported (got HSv%d)", cif.Version) }) + p.MarshalCIF(cif) + ln.log("handshake:send:dump", func() string { return p.Dump() }) + ln.log("handshake:send:cif", func() string { return cif.String() }) + ln.send(p) + + return nil + } + + req := &connRequest{ + ln: ln, + addr: p.Header().Addr, + start: time.Now(), + socketId: cif.SRTSocketId, + timestamp: p.Header().Timestamp, + config: config, + handshake: cif, + } + + if cif.SRTKM != nil { + cr, err := crypto.New(int(cif.SRTKM.KLen)) + if err != nil { + cif.HandshakeType = packet.HandshakeType(REJ_ROGUE) + ln.log("handshake:recv:error", func() string { return fmt.Sprintf("crypto: %s", err) }) + p.MarshalCIF(cif) + ln.log("handshake:send:dump", func() string { return p.Dump() }) + ln.log("handshake:send:cif", func() string { return cif.String() }) + ln.send(p) + + return nil + } + + req.crypto = cr + } + + ln.lock.Lock() + _, exists := ln.connReqs[cif.SRTSocketId] + if !exists { + ln.connReqs[cif.SRTSocketId] = req + } + ln.lock.Unlock() + + // we received a duplicate request: reject silently + if exists { + return nil + } + + return req + } else { + if cif.HandshakeType.IsRejection() { + ln.log("handshake:recv:error", func() string { return fmt.Sprintf("connection rejected: %s", cif.HandshakeType.String()) }) + } else { + ln.log("handshake:recv:error", func() string { return fmt.Sprintf("unsupported handshake: %s", cif.HandshakeType.String()) }) + } + } + + return nil +} + +func (req *connRequest) RemoteAddr() net.Addr { + addr, _ := net.ResolveUDPAddr("udp", req.addr.String()) + return addr +} + +func (req *connRequest) Version() uint32 { + return req.handshake.Version +} + +func (req *connRequest) StreamId() string { + return req.handshake.StreamId +} + +func (req *connRequest) IsEncrypted() bool { + return req.crypto != nil +} + +func (req *connRequest) SetPassphrase(passphrase string) error { + if req.handshake.Version == 5 { + if req.crypto == nil { + return fmt.Errorf("listen: request without encryption") + } + + if err := req.crypto.UnmarshalKM(req.handshake.SRTKM, passphrase); err != nil { + return err + } + } + + req.passphrase = passphrase + + return nil +} + +func (req *connRequest) SetRejectionReason(reason RejectionReason) { + req.rejectionReason = reason +} + +func (req *connRequest) Reject(reason RejectionReason) { + req.ln.lock.Lock() + defer req.ln.lock.Unlock() + + if _, hasReq := req.ln.connReqs[req.socketId]; !hasReq { + return + } + + p := packet.NewPacket(req.addr) + p.Header().IsControlPacket = true + p.Header().ControlType = packet.CTRLTYPE_HANDSHAKE + p.Header().SubType = 0 + p.Header().TypeSpecific = 0 + p.Header().Timestamp = uint32(time.Since(req.ln.start).Microseconds()) + p.Header().DestinationSocketId = req.socketId + req.handshake.HandshakeType = packet.HandshakeType(reason) + p.MarshalCIF(req.handshake) + req.ln.log("handshake:send:dump", func() string { return p.Dump() }) + req.ln.log("handshake:send:cif", func() string { return req.handshake.String() }) + req.ln.send(p) + + delete(req.ln.connReqs, req.socketId) +} + +func (req *connRequest) Accept() (Conn, error) { + if req.crypto != nil && len(req.passphrase) == 0 { + req.Reject(REJ_BADSECRET) + return nil, fmt.Errorf("passphrase is missing") + } + + req.ln.lock.Lock() + defer req.ln.lock.Unlock() + + if _, hasReq := req.ln.connReqs[req.socketId]; !hasReq { + return nil, fmt.Errorf("connection already accepted") + } + + // Create a new socket ID + socketId := uint32(time.Since(req.ln.start).Microseconds()) + + // Select the largest TSBPD delay advertised by the caller, but at least 120ms + recvTsbpdDelay := uint16(req.config.ReceiverLatency.Milliseconds()) + sendTsbpdDelay := uint16(req.config.PeerLatency.Milliseconds()) + + if req.handshake.Version == 5 { + if req.handshake.SRTHS.SendTSBPDDelay > recvTsbpdDelay { + recvTsbpdDelay = req.handshake.SRTHS.SendTSBPDDelay + } + + if req.handshake.SRTHS.RecvTSBPDDelay > sendTsbpdDelay { + sendTsbpdDelay = req.handshake.SRTHS.RecvTSBPDDelay + } + + req.config.StreamId = req.handshake.StreamId + } + + req.config.Passphrase = req.passphrase + + // Create a new connection + conn := newSRTConn(srtConnConfig{ + version: req.handshake.Version, + localAddr: req.ln.addr, + remoteAddr: req.addr, + config: req.config, + start: req.start, + socketId: socketId, + peerSocketId: req.handshake.SRTSocketId, + tsbpdTimeBase: uint64(req.timestamp), + tsbpdDelay: uint64(recvTsbpdDelay) * 1000, + peerTsbpdDelay: uint64(sendTsbpdDelay) * 1000, + initialPacketSequenceNumber: req.handshake.InitialPacketSequenceNumber, + crypto: req.crypto, + keyBaseEncryption: packet.EvenKeyEncrypted, + onSend: req.ln.send, + onShutdown: req.ln.handleShutdown, + logger: req.config.Logger, + }) + + req.ln.log("connection:new", func() string { return fmt.Sprintf("%#08x (%s)", conn.SocketId(), conn.StreamId()) }) + + req.handshake.SRTSocketId = socketId + req.handshake.SynCookie = 0 + + if req.handshake.Version == 5 { + // 3.2.1.1.1. Handshake Extension Message Flags + req.handshake.SRTHS.SRTVersion = SRT_VERSION + req.handshake.SRTHS.SRTFlags.TSBPDSND = true + req.handshake.SRTHS.SRTFlags.TSBPDRCV = true + req.handshake.SRTHS.SRTFlags.CRYPT = true + req.handshake.SRTHS.SRTFlags.TLPKTDROP = true + req.handshake.SRTHS.SRTFlags.PERIODICNAK = true + req.handshake.SRTHS.SRTFlags.REXMITFLG = true + req.handshake.SRTHS.SRTFlags.STREAM = false + req.handshake.SRTHS.SRTFlags.PACKET_FILTER = false + req.handshake.SRTHS.RecvTSBPDDelay = recvTsbpdDelay + req.handshake.SRTHS.SendTSBPDDelay = sendTsbpdDelay + } + + p := packet.NewPacket(req.addr) + p.Header().IsControlPacket = true + p.Header().ControlType = packet.CTRLTYPE_HANDSHAKE + p.Header().SubType = 0 + p.Header().TypeSpecific = 0 + p.Header().Timestamp = uint32(time.Since(req.start).Microseconds()) + p.Header().DestinationSocketId = req.socketId + p.MarshalCIF(req.handshake) + req.ln.log("handshake:send:dump", func() string { return p.Dump() }) + req.ln.log("handshake:send:cif", func() string { return req.handshake.String() }) + req.ln.send(p) + + req.ln.conns[socketId] = conn + delete(req.ln.connReqs, req.socketId) + + return conn, nil +} diff --git a/vendor/github.com/datarhei/gosrt/dial.go b/vendor/github.com/datarhei/gosrt/dial.go index 115ac869..902edc5b 100644 --- a/vendor/github.com/datarhei/gosrt/dial.go +++ b/vendor/github.com/datarhei/gosrt/dial.go @@ -425,6 +425,14 @@ func (dl *dialer) handleHandshake(p packet.Packet) { sendTsbpdDelay := uint16(dl.config.PeerLatency.Milliseconds()) if cif.Version == 5 { + if cif.SRTHS == nil { + dl.connChan <- connResponse{ + conn: nil, + err: fmt.Errorf("missing handshake extension"), + } + return + } + // Check if the peer version is sufficient if cif.SRTHS.SRTVersion < dl.config.MinVersion { dl.sendShutdown(cif.SRTSocketId) diff --git a/vendor/github.com/datarhei/gosrt/listen.go b/vendor/github.com/datarhei/gosrt/listen.go index 1385f4ef..024d10fd 100644 --- a/vendor/github.com/datarhei/gosrt/listen.go +++ b/vendor/github.com/datarhei/gosrt/listen.go @@ -10,7 +10,6 @@ import ( "sync" "time" - "github.com/datarhei/gosrt/crypto" srtnet "github.com/datarhei/gosrt/net" "github.com/datarhei/gosrt/packet" ) @@ -85,87 +84,6 @@ const ( REJX_NOROOM RejectionReason = 1507 // The data stream cannot be archived due to lacking storage space. This is in case when the request type was to send a file or the live stream to be archived. ) -// ConnRequest is an incoming connection request -type ConnRequest interface { - // RemoteAddr returns the address of the peer. The returned net.Addr - // is a copy and can be used at will. - RemoteAddr() net.Addr - - // Version returns the handshake version of the incoming request. Currently - // known versions are 4 and 5. With version 4 the StreamId will always be - // empty and IsEncrypted will always return false. An incoming version 4 - // connection will always be publishing. - Version() uint32 - - // StreamId returns the streamid of the requesting connection. Use this - // to decide what to do with the connection. - StreamId() string - - // IsEncrypted returns whether the connection is encrypted. If it is - // encrypted, use SetPassphrase to set the passphrase for decrypting. - IsEncrypted() bool - - // SetPassphrase sets the passphrase in order to decrypt the incoming - // data. Returns an error if the passphrase did not work or the connection - // is not encrypted. - SetPassphrase(p string) error - - // SetRejectionReason sets the rejection reason for the connection. If - // no set, REJ_PEER will be used. - SetRejectionReason(r RejectionReason) -} - -// connRequest implements the ConnRequest interface -type connRequest struct { - addr net.Addr - start time.Time - socketId uint32 - timestamp uint32 - - config Config - handshake *packet.CIFHandshake - crypto crypto.Crypto - passphrase string - rejectionReason RejectionReason -} - -func (req *connRequest) RemoteAddr() net.Addr { - addr, _ := net.ResolveUDPAddr("udp", req.addr.String()) - return addr -} - -func (req *connRequest) Version() uint32 { - return req.handshake.Version -} - -func (req *connRequest) StreamId() string { - return req.handshake.StreamId -} - -func (req *connRequest) IsEncrypted() bool { - return req.crypto != nil -} - -func (req *connRequest) SetPassphrase(passphrase string) error { - if req.handshake.Version == 5 { - if req.crypto == nil { - return fmt.Errorf("listen: request without encryption") - } - - if err := req.crypto.UnmarshalKM(req.handshake.SRTKM, passphrase); err != nil { - return err - } - } - - req.passphrase = passphrase - - return nil -} - -func (req *connRequest) SetRejectionReason(reason RejectionReason) { - req.rejectionReason = reason -} - // ErrListenerClosed is returned when the listener is about to shutdown. var ErrListenerClosed = errors.New("srt: listener closed") @@ -175,11 +93,17 @@ type AcceptFunc func(req ConnRequest) ConnType // Listener waits for new connections type Listener interface { + // Accept2 waits for new connections. + // On closing the err will be ErrListenerClosed. + Accept2() (ConnRequest, error) + // Accept waits for new connections. For each new connection the AcceptFunc // gets called. Conn is a new connection if AcceptFunc is PUBLISH or SUBSCRIBE. // If AcceptFunc returns REJECT, Conn is nil. In case of failure error is not // nil, Conn is nil and ConnType is REJECT. On closing the listener err will // be ErrListenerClosed and ConnType is REJECT. + // + // Deprecated: replaced by Accept2(). Accept(AcceptFunc) (Conn, ConnType, error) // Close closes the listener. It will stop accepting new connections and @@ -197,9 +121,10 @@ type listener struct { config Config - backlog chan connRequest - conns map[uint32]*srtConn - lock sync.RWMutex + backlog chan packet.Packet + connReqs map[uint32]*connRequest + conns map[uint32]*srtConn + lock sync.RWMutex start time.Time @@ -265,9 +190,10 @@ func Listen(network, address string, config Config) (Listener, error) { return nil, fmt.Errorf("listen: no local address") } + ln.connReqs = make(map[uint32]*connRequest) ln.conns = make(map[uint32]*srtConn) - ln.backlog = make(chan connRequest, 128) + ln.backlog = make(chan packet.Packet, 128) ln.rcvQueue = make(chan packet.Packet, 2048) @@ -328,108 +254,57 @@ func Listen(network, address string, config Config) (Listener, error) { return ln, nil } -func (ln *listener) Accept(acceptFn AcceptFunc) (Conn, ConnType, error) { +func (ln *listener) Accept2() (ConnRequest, error) { if ln.isShutdown() { - return nil, REJECT, ErrListenerClosed + return nil, ErrListenerClosed } - select { - case <-ln.doneChan: - return nil, REJECT, ln.error() - case request := <-ln.backlog: - if acceptFn == nil { - ln.reject(request, REJ_PEER) - break - } + for { + select { + case <-ln.doneChan: + return nil, ln.error() - mode := acceptFn(&request) - if mode != PUBLISH && mode != SUBSCRIBE { - // Figure out the reason - reason := REJ_PEER - if request.rejectionReason > 0 { - reason = request.rejectionReason + case p := <-ln.backlog: + req := newConnRequest(ln, p) + if req == nil { + break } - ln.reject(request, reason) - break - } - if request.crypto != nil && len(request.passphrase) == 0 { - ln.reject(request, REJ_BADSECRET) - break + return req, nil } + } +} - // Create a new socket ID - socketId := uint32(time.Since(ln.start).Microseconds()) - - // Select the largest TSBPD delay advertised by the caller, but at least 120ms - recvTsbpdDelay := uint16(request.config.ReceiverLatency.Milliseconds()) - sendTsbpdDelay := uint16(request.config.PeerLatency.Milliseconds()) +func (ln *listener) Accept(acceptFn AcceptFunc) (Conn, ConnType, error) { + for { + req, err := ln.Accept2() + if err != nil { + return nil, REJECT, err + } - if request.handshake.Version == 5 { - if request.handshake.SRTHS.SendTSBPDDelay > recvTsbpdDelay { - recvTsbpdDelay = request.handshake.SRTHS.SendTSBPDDelay - } + if acceptFn == nil { + req.Reject(REJ_PEER) + continue + } - if request.handshake.SRTHS.RecvTSBPDDelay > sendTsbpdDelay { - sendTsbpdDelay = request.handshake.SRTHS.RecvTSBPDDelay + mode := acceptFn(req) + if mode != PUBLISH && mode != SUBSCRIBE { + // Figure out the reason + reason := REJ_PEER + if req.(*connRequest).rejectionReason > 0 { + reason = req.(*connRequest).rejectionReason } - - request.config.StreamId = request.handshake.StreamId + req.Reject(reason) + continue } - request.config.Passphrase = request.passphrase - - // Create a new connection - conn := newSRTConn(srtConnConfig{ - version: request.handshake.Version, - localAddr: ln.addr, - remoteAddr: request.addr, - config: request.config, - start: request.start, - socketId: socketId, - peerSocketId: request.handshake.SRTSocketId, - tsbpdTimeBase: uint64(request.timestamp), - tsbpdDelay: uint64(recvTsbpdDelay) * 1000, - peerTsbpdDelay: uint64(sendTsbpdDelay) * 1000, - initialPacketSequenceNumber: request.handshake.InitialPacketSequenceNumber, - crypto: request.crypto, - keyBaseEncryption: packet.EvenKeyEncrypted, - onSend: ln.send, - onShutdown: ln.handleShutdown, - logger: request.config.Logger, - }) - - ln.log("connection:new", func() string { return fmt.Sprintf("%#08x (%s) %s", conn.SocketId(), conn.StreamId(), mode) }) - - request.handshake.SRTSocketId = socketId - request.handshake.SynCookie = 0 - - if request.handshake.Version == 5 { - // 3.2.1.1.1. Handshake Extension Message Flags - request.handshake.SRTHS.SRTVersion = SRT_VERSION - request.handshake.SRTHS.SRTFlags.TSBPDSND = true - request.handshake.SRTHS.SRTFlags.TSBPDRCV = true - request.handshake.SRTHS.SRTFlags.CRYPT = true - request.handshake.SRTHS.SRTFlags.TLPKTDROP = true - request.handshake.SRTHS.SRTFlags.PERIODICNAK = true - request.handshake.SRTHS.SRTFlags.REXMITFLG = true - request.handshake.SRTHS.SRTFlags.STREAM = false - request.handshake.SRTHS.SRTFlags.PACKET_FILTER = false - request.handshake.SRTHS.RecvTSBPDDelay = recvTsbpdDelay - request.handshake.SRTHS.SendTSBPDDelay = sendTsbpdDelay + conn, err := req.Accept() + if err != nil { + continue } - ln.accept(request) - - // Add the connection to the list of known connections - ln.lock.Lock() - ln.conns[socketId] = conn - ln.lock.Unlock() - return conn, mode, nil } - - return nil, REJECT, nil } // markDone marks the listener as done by closing @@ -457,47 +332,6 @@ func (ln *listener) handleShutdown(socketId uint32) { ln.lock.Unlock() } -func (ln *listener) reject(request connRequest, reason RejectionReason) { - p := packet.NewPacket(request.addr) - p.Header().IsControlPacket = true - - p.Header().ControlType = packet.CTRLTYPE_HANDSHAKE - p.Header().SubType = 0 - p.Header().TypeSpecific = 0 - - p.Header().Timestamp = uint32(time.Since(ln.start).Microseconds()) - p.Header().DestinationSocketId = request.socketId - - request.handshake.HandshakeType = packet.HandshakeType(reason) - - p.MarshalCIF(request.handshake) - - ln.log("handshake:send:dump", func() string { return p.Dump() }) - ln.log("handshake:send:cif", func() string { return request.handshake.String() }) - - ln.send(p) -} - -func (ln *listener) accept(request connRequest) { - p := packet.NewPacket(request.addr) - - p.Header().IsControlPacket = true - - p.Header().ControlType = packet.CTRLTYPE_HANDSHAKE - p.Header().SubType = 0 - p.Header().TypeSpecific = 0 - - p.Header().Timestamp = uint32(time.Since(request.start).Microseconds()) - p.Header().DestinationSocketId = request.socketId - - p.MarshalCIF(request.handshake) - - ln.log("handshake:send:dump", func() string { return p.Dump() }) - ln.log("handshake:send:cif", func() string { return request.handshake.String() }) - - ln.send(p) -} - func (ln *listener) isShutdown() bool { ln.shutdownLock.RLock() defer ln.shutdownLock.RUnlock() @@ -555,9 +389,12 @@ func (ln *listener) reader(ctx context.Context) { if p.Header().DestinationSocketId == 0 { if p.Header().IsControlPacket && p.Header().ControlType == packet.CTRLTYPE_HANDSHAKE { - ln.handleHandshake(p) + select { + case ln.backlog <- p: + default: + ln.log("handshake:recv:error", func() string { return "backlog is full" }) + } } - break } @@ -601,199 +438,6 @@ func (ln *listener) send(p packet.Packet) { } } -func (ln *listener) handleHandshake(p packet.Packet) { - cif := &packet.CIFHandshake{} - - err := p.UnmarshalCIF(cif) - - ln.log("handshake:recv:dump", func() string { return p.Dump() }) - ln.log("handshake:recv:cif", func() string { return cif.String() }) - - if err != nil { - ln.log("handshake:recv:error", func() string { return err.Error() }) - return - } - - // Assemble the response (4.3.1. Caller-Listener Handshake) - - p.Header().ControlType = packet.CTRLTYPE_HANDSHAKE - p.Header().SubType = 0 - p.Header().TypeSpecific = 0 - p.Header().Timestamp = uint32(time.Since(ln.start).Microseconds()) - p.Header().DestinationSocketId = cif.SRTSocketId - - cif.PeerIP.FromNetAddr(ln.addr) - - // Create a copy of the configuration for the connection - config := ln.config - - if cif.HandshakeType == packet.HSTYPE_INDUCTION { - // cif - cif.Version = 5 - cif.EncryptionField = 0 // Don't advertise any specific encryption method - cif.ExtensionField = 0x4A17 - //cif.initialPacketSequenceNumber = newCircular(0, MAX_SEQUENCENUMBER) - //cif.maxTransmissionUnitSize = 0 - //cif.maxFlowWindowSize = 0 - //cif.SRTSocketId = 0 - cif.SynCookie = ln.syncookie.Get(p.Header().Addr.String()) - - p.MarshalCIF(cif) - - ln.log("handshake:send:dump", func() string { return p.Dump() }) - ln.log("handshake:send:cif", func() string { return cif.String() }) - - ln.send(p) - } else if cif.HandshakeType == packet.HSTYPE_CONCLUSION { - // Verify the SYN cookie - if !ln.syncookie.Verify(cif.SynCookie, p.Header().Addr.String()) { - cif.HandshakeType = packet.HandshakeType(REJ_ROGUE) - ln.log("handshake:recv:error", func() string { return "invalid SYN cookie" }) - p.MarshalCIF(cif) - ln.log("handshake:send:dump", func() string { return p.Dump() }) - ln.log("handshake:send:cif", func() string { return cif.String() }) - ln.send(p) - - return - } - - // Peer is advertising a too big MSS - if cif.MaxTransmissionUnitSize > MAX_MSS_SIZE { - cif.HandshakeType = packet.HandshakeType(REJ_ROGUE) - ln.log("handshake:recv:error", func() string { return fmt.Sprintf("MTU is too big (%d bytes)", cif.MaxTransmissionUnitSize) }) - p.MarshalCIF(cif) - ln.log("handshake:send:dump", func() string { return p.Dump() }) - ln.log("handshake:send:cif", func() string { return cif.String() }) - ln.send(p) - - return - } - - // If the peer has a smaller MTU size, adjust to it - if cif.MaxTransmissionUnitSize < config.MSS { - config.MSS = cif.MaxTransmissionUnitSize - config.PayloadSize = config.MSS - SRT_HEADER_SIZE - UDP_HEADER_SIZE - - if config.PayloadSize < MIN_PAYLOAD_SIZE { - cif.HandshakeType = packet.HandshakeType(REJ_ROGUE) - ln.log("handshake:recv:error", func() string { return fmt.Sprintf("payload size is too small (%d bytes)", config.PayloadSize) }) - p.MarshalCIF(cif) - ln.log("handshake:send:dump", func() string { return p.Dump() }) - ln.log("handshake:send:cif", func() string { return cif.String() }) - ln.send(p) - } - } - - // We only support HSv4 and HSv5 - if cif.Version == 4 { - // Check if the type (encryption field + extension field) has the value 2 - if cif.EncryptionField != 0 || cif.ExtensionField != 2 { - cif.HandshakeType = packet.HandshakeType(REJ_ROGUE) - ln.log("handshake:recv:error", func() string { return "invalid type, expecting a value of 2 (UDT_DGRAM)" }) - p.MarshalCIF(cif) - ln.log("handshake:send:dump", func() string { return p.Dump() }) - ln.log("handshake:send:cif", func() string { return cif.String() }) - ln.send(p) - - return - } - } else if cif.Version == 5 { - // Check if the peer version is sufficient - if cif.SRTHS.SRTVersion < config.MinVersion { - cif.HandshakeType = packet.HandshakeType(REJ_VERSION) - ln.log("handshake:recv:error", func() string { - return fmt.Sprintf("peer version insufficient (%#06x), expecting at least %#06x", cif.SRTHS.SRTVersion, config.MinVersion) - }) - p.MarshalCIF(cif) - ln.log("handshake:send:dump", func() string { return p.Dump() }) - ln.log("handshake:send:cif", func() string { return cif.String() }) - ln.send(p) - - return - } - - // Check the required SRT flags - if !cif.SRTHS.SRTFlags.TSBPDSND || !cif.SRTHS.SRTFlags.TSBPDRCV || !cif.SRTHS.SRTFlags.TLPKTDROP || !cif.SRTHS.SRTFlags.PERIODICNAK || !cif.SRTHS.SRTFlags.REXMITFLG { - cif.HandshakeType = packet.HandshakeType(REJ_ROGUE) - ln.log("handshake:recv:error", func() string { return "not all required flags are set" }) - p.MarshalCIF(cif) - ln.log("handshake:send:dump", func() string { return p.Dump() }) - ln.log("handshake:send:cif", func() string { return cif.String() }) - ln.send(p) - - return - } - - // We only support live streaming - if cif.SRTHS.SRTFlags.STREAM { - cif.HandshakeType = packet.HandshakeType(REJ_MESSAGEAPI) - ln.log("handshake:recv:error", func() string { return "only live streaming is supported" }) - p.MarshalCIF(cif) - ln.log("handshake:send:dump", func() string { return p.Dump() }) - ln.log("handshake:send:cif", func() string { return cif.String() }) - ln.send(p) - - return - } - } else { - cif.HandshakeType = packet.HandshakeType(REJ_ROGUE) - ln.log("handshake:recv:error", func() string { return fmt.Sprintf("only HSv4 and HSv5 are supported (got HSv%d)", cif.Version) }) - p.MarshalCIF(cif) - ln.log("handshake:send:dump", func() string { return p.Dump() }) - ln.log("handshake:send:cif", func() string { return cif.String() }) - ln.send(p) - - return - } - - // Fill up a connection request with all relevant data and put it into the backlog - - c := connRequest{ - addr: p.Header().Addr, - start: time.Now(), - socketId: cif.SRTSocketId, - timestamp: p.Header().Timestamp, - config: config, - - handshake: cif, - } - - if cif.SRTKM != nil { - cr, err := crypto.New(int(cif.SRTKM.KLen)) - if err != nil { - cif.HandshakeType = packet.HandshakeType(REJ_ROGUE) - ln.log("handshake:recv:error", func() string { return fmt.Sprintf("crypto: %s", err) }) - p.MarshalCIF(cif) - ln.log("handshake:send:dump", func() string { return p.Dump() }) - ln.log("handshake:send:cif", func() string { return cif.String() }) - ln.send(p) - - return - } - - c.crypto = cr - } - - // If the backlog is full, reject the connection - select { - case ln.backlog <- c: - default: - cif.HandshakeType = packet.HandshakeType(REJ_BACKLOG) - ln.log("handshake:recv:error", func() string { return "backlog is full" }) - p.MarshalCIF(cif) - ln.log("handshake:send:dump", func() string { return p.Dump() }) - ln.log("handshake:send:cif", func() string { return cif.String() }) - ln.send(p) - } - } else { - if cif.HandshakeType.IsRejection() { - ln.log("handshake:recv:error", func() string { return fmt.Sprintf("connection rejected: %s", cif.HandshakeType.String()) }) - } else { - ln.log("handshake:recv:error", func() string { return fmt.Sprintf("unsupported handshake: %s", cif.HandshakeType.String()) }) - } - } -} - func (ln *listener) log(topic string, message func() string) { if ln.config.Logger == nil { return diff --git a/vendor/github.com/datarhei/gosrt/server.go b/vendor/github.com/datarhei/gosrt/server.go index 4f5e0f1d..61466662 100644 --- a/vendor/github.com/datarhei/gosrt/server.go +++ b/vendor/github.com/datarhei/gosrt/server.go @@ -75,7 +75,7 @@ func (s *Server) Listen() error { func (s *Server) Serve() error { for { // Wait for connections. - conn, mode, err := s.ln.Accept(s.HandleConnect) + req, err := s.ln.Accept2() if err != nil { if err == ErrListenerClosed { return ErrServerClosed @@ -84,16 +84,30 @@ func (s *Server) Serve() error { return err } - if conn == nil { - // rejected connection, ignore + if s.HandleConnect == nil { + req.Reject(REJ_PEER) continue } - if mode == PUBLISH { - go s.HandlePublish(conn) - } else { - go s.HandleSubscribe(conn) - } + go func(req ConnRequest) { + mode := s.HandleConnect(req) + if mode == REJECT { + req.Reject(REJ_PEER) + return + } + + conn, err := req.Accept() + if err != nil { + // rejected connection, ignore + return + } + + if mode == PUBLISH { + s.HandlePublish(conn) + } else { + s.HandleSubscribe(conn) + } + }(req) } } diff --git a/vendor/github.com/gabriel-vasile/mimetype/README.md b/vendor/github.com/gabriel-vasile/mimetype/README.md index fd6c533e..aa88b4bd 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/README.md +++ b/vendor/github.com/gabriel-vasile/mimetype/README.md @@ -81,7 +81,7 @@ To prevent loading entire files into memory, when detecting from a or from a [file](https://pkg.go.dev/github.com/gabriel-vasile/mimetype#DetectFile) **mimetype** limits itself to reading only the header of the input.
- structure + how project is structured
## Performance diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/json/json.go b/vendor/github.com/gabriel-vasile/mimetype/internal/json/json.go index ee39349a..5b2ecee4 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/json/json.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/json/json.go @@ -34,6 +34,7 @@ package json import ( "fmt" + "sync" ) type ( @@ -73,10 +74,31 @@ type ( } ) +var scannerPool = sync.Pool{ + New: func() any { + return &scanner{} + }, +} + +func newScanner() *scanner { + s := scannerPool.Get().(*scanner) + s.reset() + return s +} + +func freeScanner(s *scanner) { + // Avoid hanging on to too much memory in extreme cases. + if len(s.parseState) > 1024 { + s.parseState = nil + } + scannerPool.Put(s) +} + // Scan returns the number of bytes scanned and if there was any error // in trying to reach the end of data. func Scan(data []byte) (int, error) { - s := &scanner{} + s := newScanner() + defer freeScanner(s) _ = checkValid(data, s) return s.index, s.err } @@ -84,7 +106,6 @@ func Scan(data []byte) (int, error) { // checkValid verifies that data is valid JSON-encoded data. // scan is passed in for use by checkValid to avoid an allocation. func checkValid(data []byte, scan *scanner) error { - scan.reset() for _, c := range data { scan.index++ if scan.step(scan, c) == scanError { @@ -105,6 +126,8 @@ func (s *scanner) reset() { s.step = stateBeginValue s.parseState = s.parseState[0:0] s.err = nil + s.endTop = false + s.index = 0 } // eof tells the scanner that the end of input has been reached. diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/archive.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/archive.go index 554ac4d4..b59042c6 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/archive.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/archive.go @@ -3,7 +3,6 @@ package magic import ( "bytes" "encoding/binary" - "strconv" ) var ( @@ -110,8 +109,8 @@ func Tar(raw []byte, _ uint32) bool { } // Get the checksum recorded into the file. - recsum, err := tarParseOctal(raw[148:156]) - if err != nil { + recsum := tarParseOctal(raw[148:156]) + if recsum == -1 { return false } sum1, sum2 := tarChksum(raw) @@ -119,28 +118,26 @@ func Tar(raw []byte, _ uint32) bool { } // tarParseOctal converts octal string to decimal int. -func tarParseOctal(b []byte) (int64, error) { +func tarParseOctal(b []byte) int64 { // Because unused fields are filled with NULs, we need to skip leading NULs. // Fields may also be padded with spaces or NULs. // So we remove leading and trailing NULs and spaces to be sure. b = bytes.Trim(b, " \x00") if len(b) == 0 { - return 0, nil + return -1 } - x, err := strconv.ParseUint(tarParseString(b), 8, 64) - if err != nil { - return 0, err - } - return int64(x), nil -} - -// tarParseString converts a NUL ended bytes slice to a string. -func tarParseString(b []byte) string { - if i := bytes.IndexByte(b, 0); i >= 0 { - return string(b[:i]) + ret := int64(0) + for _, b := range b { + if b == 0 { + break + } + if !(b >= '0' && b <= '7') { + return -1 + } + ret = (ret << 3) | int64(b-'0') } - return string(b) + return ret } // tarChksum computes the checksum for the header block b. diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/ms_office.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/ms_office.go index 5964ce59..a1180173 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/ms_office.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/ms_office.go @@ -6,41 +6,41 @@ import ( ) var ( - xlsxSigFiles = []string{ - "xl/worksheets/", - "xl/drawings/", - "xl/theme/", - "xl/_rels/", - "xl/styles.xml", - "xl/workbook.xml", - "xl/sharedStrings.xml", - } - docxSigFiles = []string{ - "word/media/", - "word/_rels/document.xml.rels", - "word/document.xml", - "word/styles.xml", - "word/fontTable.xml", - "word/settings.xml", - "word/numbering.xml", - "word/header", - "word/footer", - } - pptxSigFiles = []string{ - "ppt/slides/", - "ppt/media/", - "ppt/slideLayouts/", - "ppt/theme/", - "ppt/slideMasters/", - "ppt/tags/", - "ppt/notesMasters/", - "ppt/_rels/", - "ppt/handoutMasters/", - "ppt/notesSlides/", - "ppt/presentation.xml", - "ppt/tableStyles.xml", - "ppt/presProps.xml", - "ppt/viewProps.xml", + xlsxSigFiles = [][]byte{ + []byte("xl/worksheets/"), + []byte("xl/drawings/"), + []byte("xl/theme/"), + []byte("xl/_rels/"), + []byte("xl/styles.xml"), + []byte("xl/workbook.xml"), + []byte("xl/sharedStrings.xml"), + } + docxSigFiles = [][]byte{ + []byte("word/media/"), + []byte("word/_rels/document.xml.rels"), + []byte("word/document.xml"), + []byte("word/styles.xml"), + []byte("word/fontTable.xml"), + []byte("word/settings.xml"), + []byte("word/numbering.xml"), + []byte("word/header"), + []byte("word/footer"), + } + pptxSigFiles = [][]byte{ + []byte("ppt/slides/"), + []byte("ppt/media/"), + []byte("ppt/slideLayouts/"), + []byte("ppt/theme/"), + []byte("ppt/slideMasters/"), + []byte("ppt/tags/"), + []byte("ppt/notesMasters/"), + []byte("ppt/_rels/"), + []byte("ppt/handoutMasters/"), + []byte("ppt/notesSlides/"), + []byte("ppt/presentation.xml"), + []byte("ppt/tableStyles.xml"), + []byte("ppt/presProps.xml"), + []byte("ppt/viewProps.xml"), } ) diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/text.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/text.go index 9f1a637b..cf644639 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/text.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/text.go @@ -120,7 +120,7 @@ var ( []byte("/usr/bin/env wish"), ) // Rtf matches a Rich Text Format file. - Rtf = prefix([]byte("{\\rtf1")) + Rtf = prefix([]byte("{\\rtf")) ) // Text matches a plain text file. diff --git a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/zip.go b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/zip.go index dabee947..aaa27559 100644 --- a/vendor/github.com/gabriel-vasile/mimetype/internal/magic/zip.go +++ b/vendor/github.com/gabriel-vasile/mimetype/internal/magic/zip.go @@ -3,7 +3,6 @@ package magic import ( "bytes" "encoding/binary" - "strings" ) var ( @@ -43,7 +42,7 @@ func Zip(raw []byte, limit uint32) bool { // Jar matches a Java archive file. func Jar(raw []byte, limit uint32) bool { - return zipContains(raw, "META-INF/MANIFEST.MF") + return zipContains(raw, []byte("META-INF/MANIFEST.MF")) } // zipTokenizer holds the source zip file and scanned index. @@ -54,7 +53,7 @@ type zipTokenizer struct { // next returns the next file name from the zip headers. // https://web.archive.org/web/20191129114319/https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip.html -func (t *zipTokenizer) next() (fileName string) { +func (t *zipTokenizer) next() (fileName []byte) { if t.i > len(t.in) { return } @@ -74,15 +73,15 @@ func (t *zipTokenizer) next() (fileName string) { return } t.i += fNameOffset + fNameLen - return string(in[fNameOffset : fNameOffset+fNameLen]) + return in[fNameOffset : fNameOffset+fNameLen] } // zipContains returns true if the zip file headers from in contain any of the paths. -func zipContains(in []byte, paths ...string) bool { +func zipContains(in []byte, paths ...[]byte) bool { t := zipTokenizer{in: in} - for i, tok := 0, t.next(); tok != ""; i, tok = i+1, t.next() { + for tok := t.next(); len(tok) != 0; tok = t.next() { for p := range paths { - if strings.HasPrefix(tok, paths[p]) { + if bytes.HasPrefix(tok, paths[p]) { return true } } diff --git a/vendor/github.com/gabriel-vasile/mimetype/mimetype.gif b/vendor/github.com/gabriel-vasile/mimetype/mimetype.gif deleted file mode 100644 index c3e80876738f11cdbdb4d4328df05fe72bf48a23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1343793 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
      &w}F3~$$iOK@;@dqU>B;3Iwi_N6iL91*i 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�s~C-aV|sR>uz6LT&MeA>r?IE<)Kuc-0?`> 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

      *3bCbrv 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

      fbTlxi?709hrCnn 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: This CORS request is not allowed. This is usually because the evalution of Origin, request method / Access-Control-Request-Method or Access-Control-Request-Headers are not whitelisted` + testCases := []struct { + name string + + // Cors rules to apply + applyCorsRules []cors.Rule + + // Outbound request info + method string + url string + headers map[string]string + + // Wanted response + wantStatus int + wantHeaders map[string]string + wantBodyContains string + }{ + { + name: "apply bucket rules", + applyCorsRules: []cors.Rule{ + { + AllowedOrigin: []string{"https"}, // S3 documents 'https' origin, but it does not actually work, see test below. + AllowedMethod: []string{"PUT"}, + AllowedHeader: []string{"*"}, + }, + { + AllowedOrigin: []string{"http://www.example1.com"}, + AllowedMethod: []string{"PUT"}, + AllowedHeader: []string{"*"}, + ExposeHeader: []string{"x-amz-server-side-encryption", "x-amz-request-id"}, + MaxAgeSeconds: 3600, + }, + { + AllowedOrigin: []string{"http://www.example2.com"}, + AllowedMethod: []string{"POST"}, + AllowedHeader: []string{"X-My-Special-Header"}, + ExposeHeader: []string{"X-AMZ-Request-ID"}, + }, + { + AllowedOrigin: []string{"http://www.example3.com"}, + AllowedMethod: []string{"PUT"}, + AllowedHeader: []string{"X-Example-3-Special-Header"}, + MaxAgeSeconds: 10, + }, + { + AllowedOrigin: []string{"*"}, + AllowedMethod: []string{"GET"}, + AllowedHeader: []string{"*"}, + ExposeHeader: []string{"x-amz-request-id", "X-AMZ-server-side-encryption"}, + MaxAgeSeconds: 3600, + }, + { + AllowedOrigin: []string{"http://multiplemethodstest.com"}, + AllowedMethod: []string{"POST", "PUT", "DELETE"}, + AllowedHeader: []string{"x-abc-*", "x-def-*"}, + }, + { + AllowedOrigin: []string{"http://UPPERCASEEXAMPLE.com"}, + AllowedMethod: []string{"DELETE"}, + }, + { + AllowedOrigin: []string{"https://*"}, + AllowedMethod: []string{"DELETE"}, + AllowedHeader: []string{"x-abc-*", "x-def-*"}, + }, + }, + }, + { + name: "preflight to object url matches example1 rule", + method: http.MethodOptions, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.example1.com", + "Access-Control-Request-Method": "PUT", + "Access-Control-Request-Headers": "x-another-header,x-could-be-anything", + }, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Origin": "http://www.example1.com", + "Access-Control-Allow-Methods": "PUT", + "Access-Control-Allow-Headers": "x-another-header,x-could-be-anything", + "Access-Control-Allow-Credentials": "true", + "Access-Control-Max-Age": "3600", + "Content-Length": "0", + // S3 additionally sets the following headers here, MinIO follows fetch spec and does not: + // "Access-Control-Expose-Headers": "", + }, + }, + { + name: "preflight to bucket url matches example1 rule", + method: http.MethodOptions, + url: bucketURL, + headers: map[string]string{ + "Origin": "http://www.example1.com", + "Access-Control-Request-Method": "PUT", + "Access-Control-Request-Headers": "x-another-header,x-could-be-anything", + }, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Origin": "http://www.example1.com", + "Access-Control-Allow-Methods": "PUT", + "Access-Control-Allow-Headers": "x-another-header,x-could-be-anything", + "Access-Control-Allow-Credentials": "true", + "Access-Control-Max-Age": "3600", + "Content-Length": "0", + }, + }, + { + name: "preflight matches example2 rule with header given", + method: http.MethodOptions, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.example2.com", + "Access-Control-Request-Method": "POST", + "Access-Control-Request-Headers": "X-My-Special-Header", + }, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Origin": "http://www.example2.com", + "Access-Control-Allow-Methods": "POST", + "Access-Control-Allow-Headers": "x-my-special-header", + "Access-Control-Allow-Credentials": "true", + "Access-Control-Max-Age": "", + "Content-Length": "0", + }, + }, + { + name: "preflight matches example2 rule with no header given", + method: http.MethodOptions, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.example2.com", + "Access-Control-Request-Method": "POST", + }, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Origin": "http://www.example2.com", + "Access-Control-Allow-Methods": "POST", + "Access-Control-Allow-Headers": "", + "Access-Control-Allow-Credentials": "true", + "Access-Control-Max-Age": "", + "Content-Length": "0", + }, + }, + { + name: "preflight matches wildcard origin rule", + method: http.MethodOptions, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.couldbeanything.com", + "Access-Control-Request-Method": "GET", + "Access-Control-Request-Headers": "x-custom-header,x-other-custom-header", + }, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET", + "Access-Control-Allow-Headers": "x-custom-header,x-other-custom-header", + "Access-Control-Allow-Credentials": "", + "Access-Control-Max-Age": "3600", + "Content-Length": "0", + }, + }, + { + name: "preflight does not match any rule", + method: http.MethodOptions, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.couldbeanything.com", + "Access-Control-Request-Method": "DELETE", + }, + wantStatus: http.StatusForbidden, + wantBodyContains: errStrAccessForbidden, + }, + { + name: "preflight does not match example1 rule because of method", + method: http.MethodOptions, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.example1.com", + "Access-Control-Request-Method": "POST", + }, + wantStatus: http.StatusForbidden, + wantBodyContains: errStrAccessForbidden, + }, + { + name: "s3 processes cors rules even when request is not preflight if cors headers present test get", + method: http.MethodGet, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.example1.com", + "Access-Control-Request-Headers": "x-another-header,x-could-be-anything", + "Access-Control-Request-Method": "PUT", + }, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Origin": "http://www.example1.com", + "Access-Control-Expose-Headers": "x-amz-server-side-encryption,x-amz-request-id", + // S3 additionally sets the following headers here, MinIO follows fetch spec and does not: + // "Access-Control-Allow-Headers": "x-another-header,x-could-be-anything", + // "Access-Control-Allow-Methods": "PUT", + // "Access-Control-Max-Age": "3600", + }, + }, + { + name: "s3 processes cors rules even when request is not preflight if cors headers present test put", + method: http.MethodPut, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.example1.com", + "Access-Control-Request-Method": "GET", + }, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Credentials": "", + "Access-Control-Allow-Origin": "*", + "Access-Control-Expose-Headers": "x-amz-request-id,x-amz-server-side-encryption", + // S3 additionally sets the following headers here, MinIO follows fetch spec and does not: + // "Access-Control-Allow-Headers": "x-another-header,x-could-be-anything", + // "Access-Control-Allow-Methods": "PUT", + // "Access-Control-Max-Age": "3600", + }, + }, + { + name: "s3 processes cors rules even when request is not preflight but there is no rule match", + method: http.MethodGet, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.example1.com", + "Access-Control-Request-Headers": "x-another-header,x-could-be-anything", + "Access-Control-Request-Method": "DELETE", + }, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Methods": "", + "Access-Control-Allow-Origin": "", + "Access-Control-Allow-Headers": "", + "Access-Control-Allow-Credentials": "", + "Access-Control-Expose-Headers": "", + "Access-Control-Max-Age": "", + }, + }, + { + name: "get request matches wildcard origin rule and returns cors headers", + method: http.MethodGet, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.example1.com", + }, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Credentials": "", + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": "", + "Access-Control-Expose-Headers": "x-amz-request-id,X-AMZ-server-side-encryption", + // S3 returns the following headers, MinIO follows fetch spec and does not: + // "Access-Control-Max-Age": "3600", + // "Access-Control-Allow-Methods": "GET", + }, + }, + { + name: "head request does not match rule and returns no cors headers", + method: http.MethodHead, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.nomatchingdomainfound.com", + }, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Credentials": "", + "Access-Control-Allow-Methods": "", + "Access-Control-Allow-Origin": "", + "Access-Control-Allow-Headers": "", + "Access-Control-Expose-Headers": "", + "Access-Control-Max-Age": "", + }, + }, + { + name: "put request with origin does not match rule and returns no cors headers", + method: http.MethodPut, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.nomatchingdomainfound.com", + }, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Credentials": "", + "Access-Control-Allow-Methods": "", + "Access-Control-Allow-Origin": "", + "Access-Control-Allow-Headers": "", + "Access-Control-Expose-Headers": "", + "Access-Control-Max-Age": "", + }, + }, + { + name: "put request with no origin does not match rule and returns no cors headers", + method: http.MethodPut, + url: objectURL, + headers: map[string]string{}, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Credentials": "", + "Access-Control-Allow-Methods": "", + "Access-Control-Allow-Origin": "", + "Access-Control-Allow-Headers": "", + "Access-Control-Expose-Headers": "", + "Access-Control-Max-Age": "", + }, + }, + { + name: "preflight for delete request with wildcard origin does not match", + method: http.MethodOptions, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.notsecureexample.com", + "Access-Control-Request-Method": "DELETE", + }, + wantStatus: http.StatusForbidden, + wantBodyContains: errStrAccessForbidden, + }, + { + name: "preflight for delete request with wildcard https origin matches secureexample", + method: http.MethodOptions, + url: objectURL, + headers: map[string]string{ + "Origin": "https://www.secureexample.com", + "Access-Control-Request-Method": "DELETE", + }, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Methods": "DELETE", + "Access-Control-Allow-Origin": "https://www.secureexample.com", + "Access-Control-Allow-Headers": "", + "Access-Control-Expose-Headers": "", + "Access-Control-Max-Age": "", + }, + }, + { + name: "preflight for delete request matches secureexample with wildcard https origin and request headers", + method: http.MethodOptions, + url: objectURL, + headers: map[string]string{ + "Origin": "https://www.secureexample.com", + "Access-Control-Request-Method": "DELETE", + "Access-Control-Request-Headers": "x-abc-1,x-abc-second,x-def-1", + }, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Methods": "DELETE", + "Access-Control-Allow-Origin": "https://www.secureexample.com", + "Access-Control-Allow-Headers": "x-abc-1,x-abc-second,x-def-1", + "Access-Control-Expose-Headers": "", + "Access-Control-Max-Age": "", + }, + }, + { + name: "preflight for delete request matches secureexample rejected because request header does not match", + method: http.MethodOptions, + url: objectURL, + headers: map[string]string{ + "Origin": "https://www.secureexample.com", + "Access-Control-Request-Method": "DELETE", + "Access-Control-Request-Headers": "x-abc-1,x-abc-second,x-def-1,x-does-not-match", + }, + wantStatus: http.StatusForbidden, + wantBodyContains: errStrAccessForbidden, + }, + { + name: "preflight with https origin is documented by s3 as matching but it does not match", + method: http.MethodOptions, + url: objectURL, + headers: map[string]string{ + "Origin": "https://www.securebutdoesnotmatch.com", + "Access-Control-Request-Method": "PUT", + }, + wantStatus: http.StatusForbidden, + wantBodyContains: errStrAccessForbidden, + }, + { + name: "put no origin no match returns no cors headers", + method: http.MethodPut, + url: objectURL, + headers: map[string]string{}, + wantStatus: http.StatusOK, + + wantHeaders: map[string]string{ + "Access-Control-Allow-Credentials": "", + "Access-Control-Allow-Methods": "", + "Access-Control-Allow-Origin": "", + "Access-Control-Allow-Headers": "", + "Access-Control-Expose-Headers": "", + "Access-Control-Max-Age": "", + }, + }, + { + name: "put with origin match example1 returns cors headers", + method: http.MethodPut, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.example1.com", + }, + wantStatus: http.StatusOK, + + wantHeaders: map[string]string{ + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Origin": "http://www.example1.com", + "Access-Control-Allow-Headers": "", + "Access-Control-Expose-Headers": "x-amz-server-side-encryption,x-amz-request-id", + // S3 returns the following headers, MinIO follows fetch spec and does not: + // "Access-Control-Max-Age": "3600", + // "Access-Control-Allow-Methods": "PUT", + }, + }, + { + name: "put with origin and header match example1 returns cors headers", + method: http.MethodPut, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.example1.com", + "x-could-be-anything": "myvalue", + }, + wantStatus: http.StatusOK, + + wantHeaders: map[string]string{ + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Origin": "http://www.example1.com", + "Access-Control-Allow-Headers": "", + "Access-Control-Expose-Headers": "x-amz-server-side-encryption,x-amz-request-id", + // S3 returns the following headers, MinIO follows fetch spec and does not: + // "Access-Control-Max-Age": "3600", + // "Access-Control-Allow-Methods": "PUT", + }, + }, + { + name: "put no match found returns no cors headers", + method: http.MethodPut, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.unmatchingdomain.com", + }, + wantStatus: http.StatusOK, + + wantHeaders: map[string]string{ + "Access-Control-Allow-Credentials": "", + "Access-Control-Allow-Methods": "", + "Access-Control-Allow-Origin": "", + "Access-Control-Allow-Headers": "", + "Access-Control-Expose-Headers": "", + "Access-Control-Max-Age": "", + }, + }, + { + name: "put with origin match example3 returns cors headers", + method: http.MethodPut, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.example3.com", + "X-My-Special-Header": "myvalue", + }, + wantStatus: http.StatusOK, + + wantHeaders: map[string]string{ + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Origin": "http://www.example3.com", + "Access-Control-Allow-Headers": "", + "Access-Control-Expose-Headers": "", + // S3 returns the following headers, MinIO follows fetch spec and does not: + // "Access-Control-Max-Age": "10", + // "Access-Control-Allow-Methods": "PUT", + }, + }, + { + name: "preflight matches example1 rule headers case is incorrect", + method: http.MethodOptions, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.example1.com", + "Access-Control-Request-Method": "PUT", + // Fetch standard guarantees that these are sent lowercase, here we test what happens when they are not. + "Access-Control-Request-Headers": "X-Another-Header,X-Could-Be-Anything", + }, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Origin": "http://www.example1.com", + "Access-Control-Allow-Methods": "PUT", + "Access-Control-Allow-Headers": "x-another-header,x-could-be-anything", + "Access-Control-Allow-Credentials": "true", + "Access-Control-Max-Age": "3600", + "Content-Length": "0", + // S3 returns the following headers, MinIO follows fetch spec and does not: + // "Access-Control-Expose-Headers": "x-amz-server-side-encryption,x-amz-request-id", + }, + }, + { + name: "preflight matches example1 rule headers are not sorted", + method: http.MethodOptions, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.example1.com", + "Access-Control-Request-Method": "PUT", + // Fetch standard guarantees that these are sorted, test what happens when they are not. + "Access-Control-Request-Headers": "a-customer-header,b-should-be-last", + }, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Origin": "http://www.example1.com", + "Access-Control-Allow-Methods": "PUT", + "Access-Control-Allow-Headers": "a-customer-header,b-should-be-last", + "Access-Control-Allow-Credentials": "true", + "Access-Control-Max-Age": "3600", + "Content-Length": "0", + // S3 returns the following headers, MinIO follows fetch spec and does not: + // "Access-Control-Expose-Headers": "x-amz-server-side-encryption,x-amz-request-id", + }, + }, + { + name: "preflight with case sensitivity in origin matches uppercase", + method: http.MethodOptions, + url: objectURL, + headers: map[string]string{ + "Origin": "http://UPPERCASEEXAMPLE.com", + "Access-Control-Request-Method": "DELETE", + }, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Methods": "DELETE", + "Access-Control-Allow-Origin": "http://UPPERCASEEXAMPLE.com", + "Access-Control-Allow-Headers": "", + "Access-Control-Expose-Headers": "", + "Access-Control-Max-Age": "", + }, + }, + { + name: "preflight with case sensitivity in origin does not match when lowercase", + method: http.MethodOptions, + url: objectURL, + headers: map[string]string{ + "Origin": "http://uppercaseexample.com", + "Access-Control-Request-Method": "DELETE", + }, + wantStatus: http.StatusForbidden, + wantBodyContains: errStrAccessForbidden, + }, + { + name: "preflight match upper case with unknown header but no header restrictions", + method: http.MethodOptions, + url: objectURL, + headers: map[string]string{ + "Origin": "http://UPPERCASEEXAMPLE.com", + "Access-Control-Request-Method": "DELETE", + "Access-Control-Request-Headers": "x-unknown-1", + }, + wantStatus: http.StatusForbidden, + wantBodyContains: errStrAccessForbidden, + }, + { + name: "preflight for delete request matches multiplemethodstest.com origin and request headers", + method: http.MethodOptions, + url: objectURL, + headers: map[string]string{ + "Origin": "http://multiplemethodstest.com", + "Access-Control-Request-Method": "DELETE", + "Access-Control-Request-Headers": "x-abc-1", + }, + wantStatus: http.StatusOK, + wantHeaders: map[string]string{ + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Origin": "http://multiplemethodstest.com", + "Access-Control-Allow-Headers": "x-abc-1", + "Access-Control-Expose-Headers": "", + "Access-Control-Max-Age": "", + // S3 returns POST, PUT, DELETE here, MinIO does not as spec does not require it. + // "Access-Control-Allow-Methods": "DELETE", + }, + }, + { + name: "delete request goes ahead because cors is only for browsers and does not block on the server side", + method: http.MethodDelete, + url: objectURL, + headers: map[string]string{ + "Origin": "http://www.justrandom.com", + }, + wantStatus: http.StatusNoContent, + }, + } + + for i, test := range testCases { + testName := fmt.Sprintf("%s_%d_%s", testName, i+1, strings.ReplaceAll(test.name, " ", "_")) + + // Apply the CORS rules + if test.applyCorsRules != nil { + corsConfig := &cors.Config{ + CORSRules: test.applyCorsRules, + } + err = c.SetBucketCors(ctx, bucketName, corsConfig) + if err != nil { + logFailure(testName, function, args, startTime, "", "SetBucketCors failed to apply", err) + return + } + } + + // Make request + if test.method != "" && test.url != "" { + req, err := http.NewRequestWithContext(ctx, test.method, test.url, nil) + if err != nil { + logFailure(testName, function, args, startTime, "", "HTTP request creation failed", err) + return + } + req.Header.Set("User-Agent", "MinIO-go-FunctionalTest/"+appVersion) + + for k, v := range test.headers { + req.Header.Set(k, v) + } + resp, err := httpClient.Do(req) + if err != nil { + logFailure(testName, function, args, startTime, "", "HTTP request failed", err) + return + } + defer resp.Body.Close() + + // Check returned status code + if resp.StatusCode != test.wantStatus { + errStr := fmt.Sprintf(" incorrect status code in response, want: %d, got: %d", test.wantStatus, resp.StatusCode) + logFailure(testName, function, args, startTime, "", errStr, nil) + return + } + + // Check returned body + if test.wantBodyContains != "" { + body, err := io.ReadAll(resp.Body) + if err != nil { + logFailure(testName, function, args, startTime, "", "Failed to read response body", err) + return + } + if !strings.Contains(string(body), test.wantBodyContains) { + errStr := fmt.Sprintf(" incorrect body in response, want: %s, in got: %s", test.wantBodyContains, string(body)) + logFailure(testName, function, args, startTime, "", errStr, nil) + return + } + } + + // Check returned response headers + for k, v := range test.wantHeaders { + gotVal := resp.Header.Get(k) + if k == "Access-Control-Expose-Headers" { + // MinIO returns this in canonical form, S3 does not. + gotVal = strings.ToLower(gotVal) + v = strings.ToLower(v) + } + // Remove all spaces, S3 adds spaces after CSV values in headers, MinIO does not. + gotVal = strings.ReplaceAll(gotVal, " ", "") + if gotVal != v { + errStr := fmt.Sprintf(" incorrect header in response, want: %s: '%s', got: '%s'", k, v, gotVal) + logFailure(testName, function, args, startTime, "", errStr, nil) + return + } + } + } + logSuccess(testName, function, args, startTime) + } + logSuccess(testName, function, args, startTime) +} + +func testCorsSetGetDelete() { + ctx := context.Background() + startTime := time.Now() + testName := getFuncName() + function := "SetBucketCors(bucketName, cors)" + args := map[string]interface{}{ + "bucketName": "", + "cors": "", + } + + // Instantiate new minio client object + c, err := minio.New(os.Getenv(serverEndpoint), + &minio.Options{ + Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), + Transport: createHTTPTransport(), + Secure: mustParseBool(os.Getenv(enableHTTPS)), + }) + if err != nil { + logFailure(testName, function, args, startTime, "", "MinIO client object creation failed", err) + return + } + + // Enable tracing, write to stderr. + // c.TraceOn(os.Stderr) + + // Set user agent. + c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) + + // Generate a new random bucket name. + bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") + args["bucketName"] = bucketName + + // Make a new bucket. + err = c.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: "us-east-1"}) + if err != nil { + logFailure(testName, function, args, startTime, "", "MakeBucket failed", err) + return + } + defer cleanupBucket(bucketName, c) + + // Set the CORS rules on the new bucket + corsRules := []cors.Rule{ + { + AllowedOrigin: []string{"http://www.example1.com"}, + AllowedMethod: []string{"PUT"}, + AllowedHeader: []string{"*"}, + }, + { + AllowedOrigin: []string{"http://www.example2.com"}, + AllowedMethod: []string{"POST"}, + AllowedHeader: []string{"X-My-Special-Header"}, + }, + { + AllowedOrigin: []string{"*"}, + AllowedMethod: []string{"GET"}, + AllowedHeader: []string{"*"}, + }, + } + corsConfig := cors.NewConfig(corsRules) + err = c.SetBucketCors(ctx, bucketName, corsConfig) + if err != nil { + logFailure(testName, function, args, startTime, "", "SetBucketCors failed to apply", err) + return + } + + // Get the rules and check they match what we set + gotCorsConfig, err := c.GetBucketCors(ctx, bucketName) + if err != nil { + logFailure(testName, function, args, startTime, "", "GetBucketCors failed", err) + return + } + if !reflect.DeepEqual(corsConfig, gotCorsConfig) { + msg := fmt.Sprintf("GetBucketCors returned unexpected rules, expected: %+v, got: %+v", corsConfig, gotCorsConfig) + logFailure(testName, function, args, startTime, "", msg, nil) + return + } + + // Delete the rules + err = c.SetBucketCors(ctx, bucketName, nil) + if err != nil { + logFailure(testName, function, args, startTime, "", "SetBucketCors failed to delete", err) + return + } + + // Get the rules and check they are now empty + gotCorsConfig, err = c.GetBucketCors(ctx, bucketName) + if err != nil { + logFailure(testName, function, args, startTime, "", "GetBucketCors failed", err) + return + } + if gotCorsConfig != nil { + logFailure(testName, function, args, startTime, "", "GetBucketCors returned unexpected rules", nil) + return + } + + logSuccess(testName, function, args, startTime) +} + // Test deleting multiple objects with object retention set in Governance mode func testRemoveObjects() { // initialize logging params @@ -13627,6 +14457,245 @@ func testRemoveObjects() { logSuccess(testName, function, args, startTime) } +// Test get bucket tags +func testGetBucketTagging() { + // initialize logging params + startTime := time.Now() + testName := getFuncName() + function := "GetBucketTagging(bucketName)" + args := map[string]interface{}{ + "bucketName": "", + } + // Seed random based on current time. + rand.Seed(time.Now().Unix()) + + // Instantiate new minio client object. + c, err := minio.New(os.Getenv(serverEndpoint), + &minio.Options{ + Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), + Transport: createHTTPTransport(), + Secure: mustParseBool(os.Getenv(enableHTTPS)), + }) + if err != nil { + logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err) + return + } + + // Enable tracing, write to stderr. + // c.TraceOn(os.Stderr) + + // Set user agent. + c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) + + // Generate a new random bucket name. + bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") + args["bucketName"] = bucketName + + // Make a new bucket. + err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true}) + if err != nil { + logError(testName, function, args, startTime, "", "MakeBucket failed", err) + return + } + + _, err = c.GetBucketTagging(context.Background(), bucketName) + if minio.ToErrorResponse(err).Code != "NoSuchTagSet" { + logError(testName, function, args, startTime, "", "Invalid error from server failed", err) + return + } + + if err = cleanupVersionedBucket(bucketName, c); err != nil { + logError(testName, function, args, startTime, "", "CleanupBucket failed", err) + return + } + + logSuccess(testName, function, args, startTime) +} + +// Test setting tags for bucket +func testSetBucketTagging() { + // initialize logging params + startTime := time.Now() + testName := getFuncName() + function := "SetBucketTagging(bucketName, tags)" + args := map[string]interface{}{ + "bucketName": "", + "tags": "", + } + // Seed random based on current time. + rand.Seed(time.Now().Unix()) + + // Instantiate new minio client object. + c, err := minio.New(os.Getenv(serverEndpoint), + &minio.Options{ + Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), + Transport: createHTTPTransport(), + Secure: mustParseBool(os.Getenv(enableHTTPS)), + }) + if err != nil { + logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err) + return + } + + // Enable tracing, write to stderr. + // c.TraceOn(os.Stderr) + + // Set user agent. + c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) + + // Generate a new random bucket name. + bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") + args["bucketName"] = bucketName + + // Make a new bucket. + err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true}) + if err != nil { + logError(testName, function, args, startTime, "", "MakeBucket failed", err) + return + } + + _, err = c.GetBucketTagging(context.Background(), bucketName) + if minio.ToErrorResponse(err).Code != "NoSuchTagSet" { + logError(testName, function, args, startTime, "", "Invalid error from server", err) + return + } + + tag := randString(60, rand.NewSource(time.Now().UnixNano()), "") + expectedValue := randString(60, rand.NewSource(time.Now().UnixNano()), "") + + t, err := tags.MapToBucketTags(map[string]string{ + tag: expectedValue, + }) + args["tags"] = t.String() + if err != nil { + logError(testName, function, args, startTime, "", "tags.MapToBucketTags failed", err) + return + } + + err = c.SetBucketTagging(context.Background(), bucketName, t) + if err != nil { + logError(testName, function, args, startTime, "", "SetBucketTagging failed", err) + return + } + + tagging, err := c.GetBucketTagging(context.Background(), bucketName) + if err != nil { + logError(testName, function, args, startTime, "", "GetBucketTagging failed", err) + return + } + + if tagging.ToMap()[tag] != expectedValue { + msg := fmt.Sprintf("Tag %s; got value %s; wanted %s", tag, tagging.ToMap()[tag], expectedValue) + logError(testName, function, args, startTime, "", msg, err) + return + } + + // Delete all objects and buckets + if err = cleanupVersionedBucket(bucketName, c); err != nil { + logError(testName, function, args, startTime, "", "CleanupBucket failed", err) + return + } + + logSuccess(testName, function, args, startTime) +} + +// Test removing bucket tags +func testRemoveBucketTagging() { + // initialize logging params + startTime := time.Now() + testName := getFuncName() + function := "RemoveBucketTagging(bucketName)" + args := map[string]interface{}{ + "bucketName": "", + } + // Seed random based on current time. + rand.Seed(time.Now().Unix()) + + // Instantiate new minio client object. + c, err := minio.New(os.Getenv(serverEndpoint), + &minio.Options{ + Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), + Transport: createHTTPTransport(), + Secure: mustParseBool(os.Getenv(enableHTTPS)), + }) + if err != nil { + logError(testName, function, args, startTime, "", "MinIO client v4 object creation failed", err) + return + } + + // Enable tracing, write to stderr. + // c.TraceOn(os.Stderr) + + // Set user agent. + c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) + + // Generate a new random bucket name. + bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") + args["bucketName"] = bucketName + + // Make a new bucket. + err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1", ObjectLocking: true}) + if err != nil { + logError(testName, function, args, startTime, "", "MakeBucket failed", err) + return + } + + _, err = c.GetBucketTagging(context.Background(), bucketName) + if minio.ToErrorResponse(err).Code != "NoSuchTagSet" { + logError(testName, function, args, startTime, "", "Invalid error from server", err) + return + } + + tag := randString(60, rand.NewSource(time.Now().UnixNano()), "") + expectedValue := randString(60, rand.NewSource(time.Now().UnixNano()), "") + + t, err := tags.MapToBucketTags(map[string]string{ + tag: expectedValue, + }) + if err != nil { + logError(testName, function, args, startTime, "", "tags.MapToBucketTags failed", err) + return + } + + err = c.SetBucketTagging(context.Background(), bucketName, t) + if err != nil { + logError(testName, function, args, startTime, "", "SetBucketTagging failed", err) + return + } + + tagging, err := c.GetBucketTagging(context.Background(), bucketName) + if err != nil { + logError(testName, function, args, startTime, "", "GetBucketTagging failed", err) + return + } + + if tagging.ToMap()[tag] != expectedValue { + msg := fmt.Sprintf("Tag %s; got value %s; wanted %s", tag, tagging.ToMap()[tag], expectedValue) + logError(testName, function, args, startTime, "", msg, err) + return + } + + err = c.RemoveBucketTagging(context.Background(), bucketName) + if err != nil { + logError(testName, function, args, startTime, "", "RemoveBucketTagging failed", err) + return + } + + _, err = c.GetBucketTagging(context.Background(), bucketName) + if minio.ToErrorResponse(err).Code != "NoSuchTagSet" { + logError(testName, function, args, startTime, "", "Invalid error from server", err) + return + } + + // Delete all objects and buckets + if err = cleanupVersionedBucket(bucketName, c); err != nil { + logError(testName, function, args, startTime, "", "CleanupBucket failed", err) + return + } + + logSuccess(testName, function, args, startTime) +} + // Convert string to bool and always return false if any error func mustParseBool(str string) bool { b, err := strconv.ParseBool(str) @@ -13660,6 +14729,8 @@ func main() { // execute tests if isFullMode() { + testCorsSetGetDelete() + testCors() testListMultipartUpload() testGetObjectAttributes() testGetObjectAttributesErrorCases() @@ -13731,6 +14802,9 @@ func main() { testObjectTaggingWithVersioning() testTrailingChecksums() testPutObjectWithAutomaticChecksums() + testGetBucketTagging() + testSetBucketTagging() + testRemoveBucketTagging() // SSE-C tests will only work over TLS connection. if tls { diff --git a/vendor/github.com/minio/minio-go/v7/pkg/cors/cors.go b/vendor/github.com/minio/minio-go/v7/pkg/cors/cors.go new file mode 100644 index 00000000..e71864ee --- /dev/null +++ b/vendor/github.com/minio/minio-go/v7/pkg/cors/cors.go @@ -0,0 +1,91 @@ +/* + * MinIO Go Library for Amazon S3 Compatible Cloud Storage + * Copyright 2015-2024 MinIO, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cors + +import ( + "encoding/xml" + "fmt" + "io" + "strings" + + "github.com/dustin/go-humanize" +) + +const defaultXMLNS = "http://s3.amazonaws.com/doc/2006-03-01/" + +// Config is the container for a CORS configuration for a bucket. +type Config struct { + XMLNS string `xml:"xmlns,attr,omitempty"` + XMLName xml.Name `xml:"CORSConfiguration"` + CORSRules []Rule `xml:"CORSRule"` +} + +// Rule is a single rule in a CORS configuration. +type Rule struct { + AllowedHeader []string `xml:"AllowedHeader,omitempty"` + AllowedMethod []string `xml:"AllowedMethod,omitempty"` + AllowedOrigin []string `xml:"AllowedOrigin,omitempty"` + ExposeHeader []string `xml:"ExposeHeader,omitempty"` + ID string `xml:"ID,omitempty"` + MaxAgeSeconds int `xml:"MaxAgeSeconds,omitempty"` +} + +// NewConfig creates a new CORS configuration with the given rules. +func NewConfig(rules []Rule) *Config { + return &Config{ + XMLNS: defaultXMLNS, + XMLName: xml.Name{ + Local: "CORSConfiguration", + Space: defaultXMLNS, + }, + CORSRules: rules, + } +} + +// ParseBucketCorsConfig parses a CORS configuration in XML from an io.Reader. +func ParseBucketCorsConfig(reader io.Reader) (*Config, error) { + var c Config + + // Max size of cors document is 64KiB according to https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketCors.html + // This limiter is just for safety so has a max of 128KiB + err := xml.NewDecoder(io.LimitReader(reader, 128*humanize.KiByte)).Decode(&c) + if err != nil { + return nil, fmt.Errorf("decoding xml: %w", err) + } + if c.XMLNS == "" { + c.XMLNS = defaultXMLNS + } + for i, rule := range c.CORSRules { + for j, method := range rule.AllowedMethod { + c.CORSRules[i].AllowedMethod[j] = strings.ToUpper(method) + } + } + return &c, nil +} + +// ToXML marshals the CORS configuration to XML. +func (c Config) ToXML() ([]byte, error) { + if c.XMLNS == "" { + c.XMLNS = defaultXMLNS + } + data, err := xml.Marshal(&c) + if err != nil { + return nil, fmt.Errorf("marshaling xml: %w", err) + } + return append([]byte(xml.Header), data...), nil +} diff --git a/vendor/github.com/minio/minio-go/v7/s3-error.go b/vendor/github.com/minio/minio-go/v7/s3-error.go index f365157e..f7fad19f 100644 --- a/vendor/github.com/minio/minio-go/v7/s3-error.go +++ b/vendor/github.com/minio/minio-go/v7/s3-error.go @@ -57,5 +57,6 @@ var s3ErrorResponseMap = map[string]string{ "BucketAlreadyOwnedByYou": "Your previous request to create the named bucket succeeded and you already own it.", "InvalidDuration": "Duration provided in the request is invalid.", "XAmzContentSHA256Mismatch": "The provided 'x-amz-content-sha256' header does not match what was computed.", + "NoSuchCORSConfiguration": "The specified bucket does not have a CORS configuration.", // Add new API errors here. } diff --git a/vendor/github.com/prometheus/client_golang/NOTICE b/vendor/github.com/prometheus/client_golang/NOTICE index dd878a30..b9cc55ab 100644 --- a/vendor/github.com/prometheus/client_golang/NOTICE +++ b/vendor/github.com/prometheus/client_golang/NOTICE @@ -16,8 +16,3 @@ Go support for Protocol Buffers - Google's data interchange format http://github.com/golang/protobuf/ Copyright 2010 The Go Authors See source code for license details. - -Support for streaming Protocol Buffer messages for the Go language (golang). -https://github.com/matttproud/golang_protobuf_extensions -Copyright 2013 Matt T. Proud -Licensed under the Apache License, Version 2.0 diff --git a/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/LICENSE b/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/LICENSE new file mode 100644 index 00000000..65d761bc --- /dev/null +++ b/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header/header.go b/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header/header.go new file mode 100644 index 00000000..8547c8df --- /dev/null +++ b/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header/header.go @@ -0,0 +1,145 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd. + +// Package header provides functions for parsing HTTP headers. +package header + +import ( + "net/http" + "strings" +) + +// Octet types from RFC 2616. +var octetTypes [256]octetType + +type octetType byte + +const ( + isToken octetType = 1 << iota + isSpace +) + +func init() { + // OCTET = + // CHAR = + // CTL = + // CR = + // LF = + // SP = + // HT = + // <"> = + // CRLF = CR LF + // LWS = [CRLF] 1*( SP | HT ) + // TEXT = + // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> + // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT + // token = 1* + // qdtext = > + + for c := 0; c < 256; c++ { + var t octetType + isCtl := c <= 31 || c == 127 + isChar := 0 <= c && c <= 127 + isSeparator := strings.ContainsRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) + if strings.ContainsRune(" \t\r\n", rune(c)) { + t |= isSpace + } + if isChar && !isCtl && !isSeparator { + t |= isToken + } + octetTypes[c] = t + } +} + +// AcceptSpec describes an Accept* header. +type AcceptSpec struct { + Value string + Q float64 +} + +// ParseAccept parses Accept* headers. +func ParseAccept(header http.Header, key string) (specs []AcceptSpec) { +loop: + for _, s := range header[key] { + for { + var spec AcceptSpec + spec.Value, s = expectTokenSlash(s) + if spec.Value == "" { + continue loop + } + spec.Q = 1.0 + s = skipSpace(s) + if strings.HasPrefix(s, ";") { + s = skipSpace(s[1:]) + if !strings.HasPrefix(s, "q=") { + continue loop + } + spec.Q, s = expectQuality(s[2:]) + if spec.Q < 0.0 { + continue loop + } + } + specs = append(specs, spec) + s = skipSpace(s) + if !strings.HasPrefix(s, ",") { + continue loop + } + s = skipSpace(s[1:]) + } + } + return +} + +func skipSpace(s string) (rest string) { + i := 0 + for ; i < len(s); i++ { + if octetTypes[s[i]]&isSpace == 0 { + break + } + } + return s[i:] +} + +func expectTokenSlash(s string) (token, rest string) { + i := 0 + for ; i < len(s); i++ { + b := s[i] + if (octetTypes[b]&isToken == 0) && b != '/' { + break + } + } + return s[:i], s[i:] +} + +func expectQuality(s string) (q float64, rest string) { + switch { + case len(s) == 0: + return -1, "" + case s[0] == '0': + q = 0 + case s[0] == '1': + q = 1 + default: + return -1, "" + } + s = s[1:] + if !strings.HasPrefix(s, ".") { + return q, s + } + s = s[1:] + i := 0 + n := 0 + d := 1 + for ; i < len(s); i++ { + b := s[i] + if b < '0' || b > '9' { + break + } + n = n*10 + int(b) - '0' + d *= 10 + } + return q + float64(n)/float64(d), s[i:] +} diff --git a/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/negotiate.go b/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/negotiate.go new file mode 100644 index 00000000..2e45780b --- /dev/null +++ b/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/negotiate.go @@ -0,0 +1,36 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd. + +package httputil + +import ( + "net/http" + + "github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header" +) + +// NegotiateContentEncoding returns the best offered content encoding for the +// request's Accept-Encoding header. If two offers match with equal weight and +// then the offer earlier in the list is preferred. If no offers are +// acceptable, then "" is returned. +func NegotiateContentEncoding(r *http.Request, offers []string) string { + bestOffer := "identity" + bestQ := -1.0 + specs := header.ParseAccept(r.Header, "Accept-Encoding") + for _, offer := range offers { + for _, spec := range specs { + if spec.Q > bestQ && + (spec.Value == "*" || spec.Value == offer) { + bestQ = spec.Q + bestOffer = offer + } + } + } + if bestQ == 0 { + bestOffer = "" + } + return bestOffer +} diff --git a/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go b/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go index ad9a71a5..520cbd7d 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go @@ -22,13 +22,13 @@ import ( // goRuntimeMemStats provides the metrics initially provided by runtime.ReadMemStats. // From Go 1.17 those similar (and better) statistics are provided by runtime/metrics, so // while eval closure works on runtime.MemStats, the struct from Go 1.17+ is -// populated using runtime/metrics. +// populated using runtime/metrics. Those are the defaults we can't alter. func goRuntimeMemStats() memStatsMetrics { return memStatsMetrics{ { desc: NewDesc( memstatNamespace("alloc_bytes"), - "Number of bytes allocated and still in use.", + "Number of bytes allocated in heap and currently in use. Equals to /memory/classes/heap/objects:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.Alloc) }, @@ -36,7 +36,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("alloc_bytes_total"), - "Total number of bytes allocated, even if freed.", + "Total number of bytes allocated in heap until now, even if released already. Equals to /gc/heap/allocs:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.TotalAlloc) }, @@ -44,23 +44,16 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("sys_bytes"), - "Number of bytes obtained from system.", + "Number of bytes obtained from system. Equals to /memory/classes/total:byte.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.Sys) }, valType: GaugeValue, - }, { - desc: NewDesc( - memstatNamespace("lookups_total"), - "Total number of pointer lookups.", - nil, nil, - ), - eval: func(ms *runtime.MemStats) float64 { return float64(ms.Lookups) }, - valType: CounterValue, }, { desc: NewDesc( memstatNamespace("mallocs_total"), - "Total number of mallocs.", + // TODO(bwplotka): We could add go_memstats_heap_objects, probably useful for discovery. Let's gather more feedback, kind of a waste of bytes for everybody for compatibility reasons to keep both, and we can't really rename/remove useful metric. + "Total number of heap objects allocated, both live and gc-ed. Semantically a counter version for go_memstats_heap_objects gauge. Equals to /gc/heap/allocs:objects + /gc/heap/tiny/allocs:objects.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.Mallocs) }, @@ -68,7 +61,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("frees_total"), - "Total number of frees.", + "Total number of heap objects frees. Equals to /gc/heap/frees:objects + /gc/heap/tiny/allocs:objects.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.Frees) }, @@ -76,7 +69,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("heap_alloc_bytes"), - "Number of heap bytes allocated and still in use.", + "Number of heap bytes allocated and currently in use, same as go_memstats_alloc_bytes. Equals to /memory/classes/heap/objects:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapAlloc) }, @@ -84,7 +77,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("heap_sys_bytes"), - "Number of heap bytes obtained from system.", + "Number of heap bytes obtained from system. Equals to /memory/classes/heap/objects:bytes + /memory/classes/heap/unused:bytes + /memory/classes/heap/released:bytes + /memory/classes/heap/free:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapSys) }, @@ -92,7 +85,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("heap_idle_bytes"), - "Number of heap bytes waiting to be used.", + "Number of heap bytes waiting to be used. Equals to /memory/classes/heap/released:bytes + /memory/classes/heap/free:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapIdle) }, @@ -100,7 +93,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("heap_inuse_bytes"), - "Number of heap bytes that are in use.", + "Number of heap bytes that are in use. Equals to /memory/classes/heap/objects:bytes + /memory/classes/heap/unused:bytes", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapInuse) }, @@ -108,7 +101,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("heap_released_bytes"), - "Number of heap bytes released to OS.", + "Number of heap bytes released to OS. Equals to /memory/classes/heap/released:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapReleased) }, @@ -116,7 +109,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("heap_objects"), - "Number of allocated objects.", + "Number of currently allocated objects. Equals to /gc/heap/objects:objects.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapObjects) }, @@ -124,7 +117,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("stack_inuse_bytes"), - "Number of bytes in use by the stack allocator.", + "Number of bytes obtained from system for stack allocator in non-CGO environments. Equals to /memory/classes/heap/stacks:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.StackInuse) }, @@ -132,7 +125,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("stack_sys_bytes"), - "Number of bytes obtained from system for stack allocator.", + "Number of bytes obtained from system for stack allocator. Equals to /memory/classes/heap/stacks:bytes + /memory/classes/os-stacks:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.StackSys) }, @@ -140,7 +133,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("mspan_inuse_bytes"), - "Number of bytes in use by mspan structures.", + "Number of bytes in use by mspan structures. Equals to /memory/classes/metadata/mspan/inuse:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.MSpanInuse) }, @@ -148,7 +141,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("mspan_sys_bytes"), - "Number of bytes used for mspan structures obtained from system.", + "Number of bytes used for mspan structures obtained from system. Equals to /memory/classes/metadata/mspan/inuse:bytes + /memory/classes/metadata/mspan/free:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.MSpanSys) }, @@ -156,7 +149,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("mcache_inuse_bytes"), - "Number of bytes in use by mcache structures.", + "Number of bytes in use by mcache structures. Equals to /memory/classes/metadata/mcache/inuse:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.MCacheInuse) }, @@ -164,7 +157,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("mcache_sys_bytes"), - "Number of bytes used for mcache structures obtained from system.", + "Number of bytes used for mcache structures obtained from system. Equals to /memory/classes/metadata/mcache/inuse:bytes + /memory/classes/metadata/mcache/free:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.MCacheSys) }, @@ -172,7 +165,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("buck_hash_sys_bytes"), - "Number of bytes used by the profiling bucket hash table.", + "Number of bytes used by the profiling bucket hash table. Equals to /memory/classes/profiling/buckets:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.BuckHashSys) }, @@ -180,7 +173,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("gc_sys_bytes"), - "Number of bytes used for garbage collection system metadata.", + "Number of bytes used for garbage collection system metadata. Equals to /memory/classes/metadata/other:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.GCSys) }, @@ -188,7 +181,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("other_sys_bytes"), - "Number of bytes used for other system allocations.", + "Number of bytes used for other system allocations. Equals to /memory/classes/other:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.OtherSys) }, @@ -196,7 +189,7 @@ func goRuntimeMemStats() memStatsMetrics { }, { desc: NewDesc( memstatNamespace("next_gc_bytes"), - "Number of heap bytes when next garbage collection will take place.", + "Number of heap bytes when next garbage collection will take place. Equals to /gc/heap/goal:bytes.", nil, nil, ), eval: func(ms *runtime.MemStats) float64 { return float64(ms.NextGC) }, @@ -225,7 +218,7 @@ func newBaseGoCollector() baseGoCollector { nil, nil), gcDesc: NewDesc( "go_gc_duration_seconds", - "A summary of the pause duration of garbage collection cycles.", + "A summary of the wall-time pause (stop-the-world) duration in garbage collection cycles.", nil, nil), gcLastTimeDesc: NewDesc( "go_memstats_last_gc_time_seconds", diff --git a/vendor/github.com/prometheus/client_golang/prometheus/go_collector_latest.go b/vendor/github.com/prometheus/client_golang/prometheus/go_collector_latest.go index 2d8d9f64..51174641 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/go_collector_latest.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/go_collector_latest.go @@ -17,6 +17,7 @@ package prometheus import ( + "fmt" "math" "runtime" "runtime/metrics" @@ -153,7 +154,8 @@ func defaultGoCollectorOptions() internal.GoCollectorOptions { "/gc/heap/frees-by-size:bytes": goGCHeapFreesBytes, }, RuntimeMetricRules: []internal.GoCollectorRule{ - //{Matcher: regexp.MustCompile("")}, + // Recommended metrics we want by default from runtime/metrics. + {Matcher: internal.GoCollectorDefaultRuntimeMetrics}, }, } } @@ -203,6 +205,7 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector { // to fail here. This condition is tested in TestExpectedRuntimeMetrics. continue } + help := attachOriginalName(d.Description.Description, d.Name) sampleBuf = append(sampleBuf, metrics.Sample{Name: d.Name}) sampleMap[d.Name] = &sampleBuf[len(sampleBuf)-1] @@ -214,7 +217,7 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector { m = newBatchHistogram( NewDesc( BuildFQName(namespace, subsystem, name), - d.Description.Description, + help, nil, nil, ), @@ -226,7 +229,7 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector { Namespace: namespace, Subsystem: subsystem, Name: name, - Help: d.Description.Description, + Help: help, }, ) } else { @@ -234,7 +237,7 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector { Namespace: namespace, Subsystem: subsystem, Name: name, - Help: d.Description.Description, + Help: help, }) } metricSet = append(metricSet, m) @@ -284,6 +287,10 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector { } } +func attachOriginalName(desc, origName string) string { + return fmt.Sprintf("%s Sourced from %s", desc, origName) +} + // Describe returns all descriptions of the collector. func (c *goCollector) Describe(ch chan<- *Desc) { c.base.Describe(ch) @@ -376,13 +383,13 @@ func unwrapScalarRMValue(v metrics.Value) float64 { // // This should never happen because we always populate our metric // set from the runtime/metrics package. - panic("unexpected unsupported metric") + panic("unexpected bad kind metric") default: // Unsupported metric kind. // // This should never happen because we check for this during initialization // and flag and filter metrics whose kinds we don't understand. - panic("unexpected unsupported metric kind") + panic(fmt.Sprintf("unexpected unsupported metric: %v", v.Kind())) } } diff --git a/vendor/github.com/prometheus/client_golang/prometheus/histogram.go b/vendor/github.com/prometheus/client_golang/prometheus/histogram.go index b5c8bcb3..8d35f2d8 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/histogram.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/histogram.go @@ -440,7 +440,7 @@ type HistogramOpts struct { // constant (or any negative float value). NativeHistogramZeroThreshold float64 - // The remaining fields define a strategy to limit the number of + // The next three fields define a strategy to limit the number of // populated sparse buckets. If NativeHistogramMaxBucketNumber is left // at zero, the number of buckets is not limited. (Note that this might // lead to unbounded memory consumption if the values observed by the @@ -473,6 +473,22 @@ type HistogramOpts struct { NativeHistogramMinResetDuration time.Duration NativeHistogramMaxZeroThreshold float64 + // NativeHistogramMaxExemplars limits the number of exemplars + // that are kept in memory for each native histogram. If you leave it at + // zero, a default value of 10 is used. If no exemplars should be kept specifically + // for native histograms, set it to a negative value. (Scrapers can + // still use the exemplars exposed for classic buckets, which are managed + // independently.) + NativeHistogramMaxExemplars int + // NativeHistogramExemplarTTL is only checked once + // NativeHistogramMaxExemplars is exceeded. In that case, the + // oldest exemplar is removed if it is older than NativeHistogramExemplarTTL. + // Otherwise, the older exemplar in the pair of exemplars that are closest + // together (on an exponential scale) is removed. + // If NativeHistogramExemplarTTL is left at its zero value, a default value of + // 5m is used. To always delete the oldest exemplar, set it to a negative value. + NativeHistogramExemplarTTL time.Duration + // now is for testing purposes, by default it's time.Now. now func() time.Time @@ -532,6 +548,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr if opts.afterFunc == nil { opts.afterFunc = time.AfterFunc } + h := &histogram{ desc: desc, upperBounds: opts.Buckets, @@ -556,6 +573,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr h.nativeHistogramZeroThreshold = DefNativeHistogramZeroThreshold } // Leave h.nativeHistogramZeroThreshold at 0 otherwise. h.nativeHistogramSchema = pickSchema(opts.NativeHistogramBucketFactor) + h.nativeExemplars = makeNativeExemplars(opts.NativeHistogramExemplarTTL, opts.NativeHistogramMaxExemplars) } for i, upperBound := range h.upperBounds { if i < len(h.upperBounds)-1 { @@ -725,7 +743,8 @@ type histogram struct { // resetScheduled is protected by mtx. It is true if a reset is // scheduled for a later time (when nativeHistogramMinResetDuration has // passed). - resetScheduled bool + resetScheduled bool + nativeExemplars nativeExemplars // now is for testing purposes, by default it's time.Now. now func() time.Time @@ -742,6 +761,9 @@ func (h *histogram) Observe(v float64) { h.observe(v, h.findBucket(v)) } +// ObserveWithExemplar should not be called in a high-frequency setting +// for a native histogram with configured exemplars. For this case, +// the implementation isn't lock-free and might suffer from lock contention. func (h *histogram) ObserveWithExemplar(v float64, e Labels) { i := h.findBucket(v) h.observe(v, i) @@ -821,6 +843,15 @@ func (h *histogram) Write(out *dto.Metric) error { Length: proto.Uint32(0), }} } + + // If exemplars are not configured, the cap will be 0. + // So append is not needed in this case. + if cap(h.nativeExemplars.exemplars) > 0 { + h.nativeExemplars.Lock() + his.Exemplars = append(his.Exemplars, h.nativeExemplars.exemplars...) + h.nativeExemplars.Unlock() + } + } addAndResetCounts(hotCounts, coldCounts) return nil @@ -1091,8 +1122,10 @@ func (h *histogram) resetCounts(counts *histogramCounts) { deleteSyncMap(&counts.nativeHistogramBucketsPositive) } -// updateExemplar replaces the exemplar for the provided bucket. With empty -// labels, it's a no-op. It panics if any of the labels is invalid. +// updateExemplar replaces the exemplar for the provided classic bucket. +// With empty labels, it's a no-op. It panics if any of the labels is invalid. +// If histogram is native, the exemplar will be cached into nativeExemplars, +// which has a limit, and will remove one exemplar when limit is reached. func (h *histogram) updateExemplar(v float64, bucket int, l Labels) { if l == nil { return @@ -1102,6 +1135,10 @@ func (h *histogram) updateExemplar(v float64, bucket int, l Labels) { panic(err) } h.exemplars[bucket].Store(e) + doSparse := h.nativeHistogramSchema > math.MinInt32 && !math.IsNaN(v) + if doSparse { + h.nativeExemplars.addExemplar(e) + } } // HistogramVec is a Collector that bundles a set of Histograms that all share the @@ -1336,6 +1373,48 @@ func MustNewConstHistogram( return m } +// NewConstHistogramWithCreatedTimestamp does the same thing as NewConstHistogram but sets the created timestamp. +func NewConstHistogramWithCreatedTimestamp( + desc *Desc, + count uint64, + sum float64, + buckets map[float64]uint64, + ct time.Time, + labelValues ...string, +) (Metric, error) { + if desc.err != nil { + return nil, desc.err + } + if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil { + return nil, err + } + return &constHistogram{ + desc: desc, + count: count, + sum: sum, + buckets: buckets, + labelPairs: MakeLabelPairs(desc, labelValues), + createdTs: timestamppb.New(ct), + }, nil +} + +// MustNewConstHistogramWithCreatedTimestamp is a version of NewConstHistogramWithCreatedTimestamp that panics where +// NewConstHistogramWithCreatedTimestamp would have returned an error. +func MustNewConstHistogramWithCreatedTimestamp( + desc *Desc, + count uint64, + sum float64, + buckets map[float64]uint64, + ct time.Time, + labelValues ...string, +) Metric { + m, err := NewConstHistogramWithCreatedTimestamp(desc, count, sum, buckets, ct, labelValues...) + if err != nil { + panic(err) + } + return m +} + type buckSort []*dto.Bucket func (s buckSort) Len() int { @@ -1575,3 +1654,142 @@ func addAndResetCounts(hot, cold *histogramCounts) { atomic.AddUint64(&hot.nativeHistogramZeroBucket, atomic.LoadUint64(&cold.nativeHistogramZeroBucket)) atomic.StoreUint64(&cold.nativeHistogramZeroBucket, 0) } + +type nativeExemplars struct { + sync.Mutex + + ttl time.Duration + exemplars []*dto.Exemplar +} + +func makeNativeExemplars(ttl time.Duration, maxCount int) nativeExemplars { + if ttl == 0 { + ttl = 5 * time.Minute + } + + if maxCount == 0 { + maxCount = 10 + } + + if maxCount < 0 { + maxCount = 0 + } + + return nativeExemplars{ + ttl: ttl, + exemplars: make([]*dto.Exemplar, 0, maxCount), + } +} + +func (n *nativeExemplars) addExemplar(e *dto.Exemplar) { + if cap(n.exemplars) == 0 { + return + } + + n.Lock() + defer n.Unlock() + + // The index where to insert the new exemplar. + var nIdx int = -1 + + // When the number of exemplars has not yet exceeded or + // is equal to cap(n.exemplars), then + // insert the new exemplar directly. + if len(n.exemplars) < cap(n.exemplars) { + for nIdx = 0; nIdx < len(n.exemplars); nIdx++ { + if *e.Value < *n.exemplars[nIdx].Value { + break + } + } + n.exemplars = append(n.exemplars[:nIdx], append([]*dto.Exemplar{e}, n.exemplars[nIdx:]...)...) + return + } + + // When the number of exemplars exceeds the limit, remove one exemplar. + var ( + rIdx int // The index where to remove the old exemplar. + + ot = time.Now() // Oldest timestamp seen. + otIdx = -1 // Index of the exemplar with the oldest timestamp. + + md = -1.0 // Logarithm of the delta of the closest pair of exemplars. + mdIdx = -1 // Index of the older exemplar within the closest pair. + cLog float64 // Logarithm of the current exemplar. + pLog float64 // Logarithm of the previous exemplar. + ) + + for i, exemplar := range n.exemplars { + // Find the exemplar with the oldest timestamp. + if otIdx == -1 || exemplar.Timestamp.AsTime().Before(ot) { + ot = exemplar.Timestamp.AsTime() + otIdx = i + } + + // Find the index at which to insert new the exemplar. + if *e.Value <= *exemplar.Value && nIdx == -1 { + nIdx = i + } + + // Find the two closest exemplars and pick the one the with older timestamp. + pLog = cLog + cLog = math.Log(exemplar.GetValue()) + if i == 0 { + continue + } + diff := math.Abs(cLog - pLog) + if md == -1 || diff < md { + md = diff + if n.exemplars[i].Timestamp.AsTime().Before(n.exemplars[i-1].Timestamp.AsTime()) { + mdIdx = i + } else { + mdIdx = i - 1 + } + } + + } + + // If all existing exemplar are smaller than new exemplar, + // then the exemplar should be inserted at the end. + if nIdx == -1 { + nIdx = len(n.exemplars) + } + + if otIdx != -1 && e.Timestamp.AsTime().Sub(ot) > n.ttl { + rIdx = otIdx + } else { + // In the previous for loop, when calculating the closest pair of exemplars, + // we did not take into account the newly inserted exemplar. + // So we need to calculate with the newly inserted exemplar again. + elog := math.Log(e.GetValue()) + if nIdx > 0 { + diff := math.Abs(elog - math.Log(n.exemplars[nIdx-1].GetValue())) + if diff < md { + md = diff + mdIdx = nIdx + if n.exemplars[nIdx-1].Timestamp.AsTime().Before(e.Timestamp.AsTime()) { + mdIdx = nIdx - 1 + } + } + } + if nIdx < len(n.exemplars) { + diff := math.Abs(math.Log(n.exemplars[nIdx].GetValue()) - elog) + if diff < md { + mdIdx = nIdx + if n.exemplars[nIdx].Timestamp.AsTime().Before(e.Timestamp.AsTime()) { + mdIdx = nIdx + } + } + } + rIdx = mdIdx + } + + // Adjust the slice according to rIdx and nIdx. + switch { + case rIdx == nIdx: + n.exemplars[nIdx] = e + case rIdx < nIdx: + n.exemplars = append(n.exemplars[:rIdx], append(n.exemplars[rIdx+1:nIdx], append([]*dto.Exemplar{e}, n.exemplars[nIdx:]...)...)...) + case rIdx > nIdx: + n.exemplars = append(n.exemplars[:nIdx], append([]*dto.Exemplar{e}, append(n.exemplars[nIdx:rIdx], n.exemplars[rIdx+1:]...)...)...) + } +} diff --git a/vendor/github.com/prometheus/client_golang/prometheus/internal/go_collector_options.go b/vendor/github.com/prometheus/client_golang/prometheus/internal/go_collector_options.go index 723b45d6..a4fa6eab 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/internal/go_collector_options.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/internal/go_collector_options.go @@ -30,3 +30,5 @@ type GoCollectorOptions struct { RuntimeMetricSumForHist map[string]string RuntimeMetricRules []GoCollectorRule } + +var GoCollectorDefaultRuntimeMetrics = regexp.MustCompile(`/gc/gogc:percent|/gc/gomemlimit:bytes|/sched/gomaxprocs:threads`) diff --git a/vendor/github.com/prometheus/client_golang/prometheus/metric.go b/vendor/github.com/prometheus/client_golang/prometheus/metric.go index f018e572..9d9b81ab 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/metric.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/metric.go @@ -234,7 +234,7 @@ func NewMetricWithExemplars(m Metric, exemplars ...Exemplar) (Metric, error) { ) for i, e := range exemplars { ts := e.Timestamp - if ts == (time.Time{}) { + if ts.IsZero() { ts = now } exs[i], err = newExemplar(e.Value, ts, e.Labels) diff --git a/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go b/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go index 8548dd18..efbc3ea8 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go @@ -22,14 +22,15 @@ import ( ) type processCollector struct { - collectFn func(chan<- Metric) - pidFn func() (int, error) - reportErrors bool - cpuTotal *Desc - openFDs, maxFDs *Desc - vsize, maxVsize *Desc - rss *Desc - startTime *Desc + collectFn func(chan<- Metric) + pidFn func() (int, error) + reportErrors bool + cpuTotal *Desc + openFDs, maxFDs *Desc + vsize, maxVsize *Desc + rss *Desc + startTime *Desc + inBytes, outBytes *Desc } // ProcessCollectorOpts defines the behavior of a process metrics collector @@ -100,6 +101,16 @@ func NewProcessCollector(opts ProcessCollectorOpts) Collector { "Start time of the process since unix epoch in seconds.", nil, nil, ), + inBytes: NewDesc( + ns+"process_network_receive_bytes_total", + "Number of bytes received by the process over the network.", + nil, nil, + ), + outBytes: NewDesc( + ns+"process_network_transmit_bytes_total", + "Number of bytes sent by the process over the network.", + nil, nil, + ), } if opts.PidFn == nil { diff --git a/vendor/github.com/prometheus/client_golang/prometheus/process_collector_other.go b/vendor/github.com/prometheus/client_golang/prometheus/process_collector_other.go index 8c1136ce..14d56d2d 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/process_collector_other.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/process_collector_other.go @@ -63,4 +63,18 @@ func (c *processCollector) processCollect(ch chan<- Metric) { } else { c.reportError(ch, nil, err) } + + if netstat, err := p.Netstat(); err == nil { + var inOctets, outOctets float64 + if netstat.IpExt.InOctets != nil { + inOctets = *netstat.IpExt.InOctets + } + if netstat.IpExt.OutOctets != nil { + outOctets = *netstat.IpExt.OutOctets + } + ch <- MustNewConstMetric(c.inBytes, CounterValue, inOctets) + ch <- MustNewConstMetric(c.outBytes, CounterValue, outOctets) + } else { + c.reportError(ch, nil, err) + } } diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go index 9819917b..315eab5f 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go @@ -76,6 +76,12 @@ func (r *responseWriterDelegator) Write(b []byte) (int, error) { return n, err } +// Unwrap lets http.ResponseController get the underlying http.ResponseWriter, +// by implementing the [rwUnwrapper](https://cs.opensource.google/go/go/+/refs/tags/go1.21.4:src/net/http/responsecontroller.go;l=42-44) interface. +func (r *responseWriterDelegator) Unwrap() http.ResponseWriter { + return r.ResponseWriter +} + type ( closeNotifierDelegator struct{ *responseWriterDelegator } flusherDelegator struct{ *responseWriterDelegator } diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go index 09b8d2fb..2e0b9a86 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go @@ -38,12 +38,13 @@ import ( "io" "net/http" "strconv" - "strings" "sync" "time" + "github.com/klauspost/compress/zstd" "github.com/prometheus/common/expfmt" + "github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil" "github.com/prometheus/client_golang/prometheus" ) @@ -54,6 +55,18 @@ const ( processStartTimeHeader = "Process-Start-Time-Unix" ) +// Compression represents the content encodings handlers support for the HTTP +// responses. +type Compression string + +const ( + Identity Compression = "identity" + Gzip Compression = "gzip" + Zstd Compression = "zstd" +) + +var defaultCompressionFormats = []Compression{Identity, Gzip, Zstd} + var gzipPool = sync.Pool{ New: func() interface{} { return gzip.NewWriter(nil) @@ -122,6 +135,18 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO } } + // Select compression formats to offer based on default or user choice. + var compressions []string + if !opts.DisableCompression { + offers := defaultCompressionFormats + if len(opts.OfferedCompressions) > 0 { + offers = opts.OfferedCompressions + } + for _, comp := range offers { + compressions = append(compressions, string(comp)) + } + } + h := http.HandlerFunc(func(rsp http.ResponseWriter, req *http.Request) { if !opts.ProcessStartTime.IsZero() { rsp.Header().Set(processStartTimeHeader, strconv.FormatInt(opts.ProcessStartTime.Unix(), 10)) @@ -165,20 +190,20 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO } else { contentType = expfmt.Negotiate(req.Header) } - header := rsp.Header() - header.Set(contentTypeHeader, string(contentType)) + rsp.Header().Set(contentTypeHeader, string(contentType)) - w := io.Writer(rsp) - if !opts.DisableCompression && gzipAccepted(req.Header) { - header.Set(contentEncodingHeader, "gzip") - gz := gzipPool.Get().(*gzip.Writer) - defer gzipPool.Put(gz) + w, encodingHeader, closeWriter, err := negotiateEncodingWriter(req, rsp, compressions) + if err != nil { + if opts.ErrorLog != nil { + opts.ErrorLog.Println("error getting writer", err) + } + w = io.Writer(rsp) + encodingHeader = string(Identity) + } - gz.Reset(w) - defer gz.Close() + defer closeWriter() - w = gz - } + rsp.Header().Set(contentEncodingHeader, encodingHeader) enc := expfmt.NewEncoder(w, contentType) @@ -343,9 +368,19 @@ type HandlerOpts struct { // no effect on the HTTP status code because ErrorHandling is set to // ContinueOnError. Registry prometheus.Registerer - // If DisableCompression is true, the handler will never compress the - // response, even if requested by the client. + // DisableCompression disables the response encoding (compression) and + // encoding negotiation. If true, the handler will + // never compress the response, even if requested + // by the client and the OfferedCompressions field is set. DisableCompression bool + // OfferedCompressions is a set of encodings (compressions) handler will + // try to offer when negotiating with the client. This defaults to identity, gzip + // and zstd. + // NOTE: If handler can't agree with the client on the encodings or + // unsupported or empty encodings are set in OfferedCompressions, + // handler always fallbacks to no compression (identity), for + // compatibility reasons. In such cases ErrorLog will be used if set. + OfferedCompressions []Compression // The number of concurrent HTTP requests is limited to // MaxRequestsInFlight. Additional requests are responded to with 503 // Service Unavailable and a suitable message in the body. If @@ -381,19 +416,6 @@ type HandlerOpts struct { ProcessStartTime time.Time } -// gzipAccepted returns whether the client will accept gzip-encoded content. -func gzipAccepted(header http.Header) bool { - a := header.Get(acceptEncodingHeader) - parts := strings.Split(a, ",") - for _, part := range parts { - part = strings.TrimSpace(part) - if part == "gzip" || strings.HasPrefix(part, "gzip;") { - return true - } - } - return false -} - // httpError removes any content-encoding header and then calls http.Error with // the provided error and http.StatusInternalServerError. Error contents is // supposed to be uncompressed plain text. Same as with a plain http.Error, this @@ -406,3 +428,38 @@ func httpError(rsp http.ResponseWriter, err error) { http.StatusInternalServerError, ) } + +// negotiateEncodingWriter reads the Accept-Encoding header from a request and +// selects the right compression based on an allow-list of supported +// compressions. It returns a writer implementing the compression and an the +// correct value that the caller can set in the response header. +func negotiateEncodingWriter(r *http.Request, rw io.Writer, compressions []string) (_ io.Writer, encodingHeaderValue string, closeWriter func(), _ error) { + if len(compressions) == 0 { + return rw, string(Identity), func() {}, nil + } + + // TODO(mrueg): Replace internal/github.com/gddo once https://github.com/golang/go/issues/19307 is implemented. + selected := httputil.NegotiateContentEncoding(r, compressions) + + switch selected { + case "zstd": + // TODO(mrueg): Replace klauspost/compress with stdlib implementation once https://github.com/golang/go/issues/62513 is implemented. + z, err := zstd.NewWriter(rw, zstd.WithEncoderLevel(zstd.SpeedFastest)) + if err != nil { + return nil, "", func() {}, err + } + + z.Reset(rw) + return z, selected, func() { _ = z.Close() }, nil + case "gzip": + gz := gzipPool.Get().(*gzip.Writer) + gz.Reset(rw) + return gz, selected, func() { _ = gz.Close(); gzipPool.Put(gz) }, nil + case "identity": + // This means the content is not compressed. + return rw, selected, func() {}, nil + default: + // The content encoding was not implemented yet. + return nil, "", func() {}, fmt.Errorf("content compression format not recognized: %s. Valid formats are: %s", selected, defaultCompressionFormats) + } +} diff --git a/vendor/github.com/prometheus/client_golang/prometheus/registry.go b/vendor/github.com/prometheus/client_golang/prometheus/registry.go index 5e2ced25..c6fd2f58 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/registry.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/registry.go @@ -314,16 +314,17 @@ func (r *Registry) Register(c Collector) error { if dimHash != desc.dimHash { return fmt.Errorf("a previously registered descriptor with the same fully-qualified name as %s has different label names or a different help string", desc) } - } else { - // ...then check the new descriptors already seen. - if dimHash, exists := newDimHashesByName[desc.fqName]; exists { - if dimHash != desc.dimHash { - return fmt.Errorf("descriptors reported by collector have inconsistent label names or help strings for the same fully-qualified name, offender is %s", desc) - } - } else { - newDimHashesByName[desc.fqName] = desc.dimHash + continue + } + + // ...then check the new descriptors already seen. + if dimHash, exists := newDimHashesByName[desc.fqName]; exists { + if dimHash != desc.dimHash { + return fmt.Errorf("descriptors reported by collector have inconsistent label names or help strings for the same fully-qualified name, offender is %s", desc) } + continue } + newDimHashesByName[desc.fqName] = desc.dimHash } // A Collector yielding no Desc at all is considered unchecked. if len(newDescIDs) == 0 { diff --git a/vendor/github.com/prometheus/client_golang/prometheus/summary.go b/vendor/github.com/prometheus/client_golang/prometheus/summary.go index 14627044..1ab0e479 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/summary.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/summary.go @@ -783,3 +783,45 @@ func MustNewConstSummary( } return m } + +// NewConstSummaryWithCreatedTimestamp does the same thing as NewConstSummary but sets the created timestamp. +func NewConstSummaryWithCreatedTimestamp( + desc *Desc, + count uint64, + sum float64, + quantiles map[float64]float64, + ct time.Time, + labelValues ...string, +) (Metric, error) { + if desc.err != nil { + return nil, desc.err + } + if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil { + return nil, err + } + return &constSummary{ + desc: desc, + count: count, + sum: sum, + quantiles: quantiles, + labelPairs: MakeLabelPairs(desc, labelValues), + createdTs: timestamppb.New(ct), + }, nil +} + +// MustNewConstSummaryWithCreatedTimestamp is a version of NewConstSummaryWithCreatedTimestamp that panics where +// NewConstSummaryWithCreatedTimestamp would have returned an error. +func MustNewConstSummaryWithCreatedTimestamp( + desc *Desc, + count uint64, + sum float64, + quantiles map[float64]float64, + ct time.Time, + labelValues ...string, +) Metric { + m, err := NewConstSummaryWithCreatedTimestamp(desc, count, sum, quantiles, ct, labelValues...) + if err != nil { + panic(err) + } + return m +} diff --git a/vendor/github.com/prometheus/client_golang/prometheus/vec.go b/vendor/github.com/prometheus/client_golang/prometheus/vec.go index 955cfd59..2c808eec 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/vec.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/vec.go @@ -507,7 +507,7 @@ func (m *metricMap) getOrCreateMetricWithLabelValues( return metric } -// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value +// getOrCreateMetricWithLabels retrieves the metric by hash and label value // or creates it and returns the new one. // // This function holds the mutex. diff --git a/vendor/github.com/zeebo/blake3/.gitignore b/vendor/github.com/zeebo/blake3/.gitignore index c6bfdf2c..80c08326 100644 --- a/vendor/github.com/zeebo/blake3/.gitignore +++ b/vendor/github.com/zeebo/blake3/.gitignore @@ -4,3 +4,4 @@ *.out /upstream +/go.work diff --git a/vendor/github.com/zeebo/blake3/Makefile b/vendor/github.com/zeebo/blake3/Makefile index b96623be..a88ed17c 100644 --- a/vendor/github.com/zeebo/blake3/Makefile +++ b/vendor/github.com/zeebo/blake3/Makefile @@ -21,14 +21,11 @@ test: .PHONY: vet vet: - GOOS=linux GOARCH=386 GO386=softfloat go vet ./... - GOOS=windows GOARCH=386 GO386=softfloat go vet ./... - GOOS=linux GOARCH=amd64 go vet ./... - GOOS=windows GOARCH=amd64 go vet ./... - GOOS=darwin GOARCH=amd64 go vet ./... - GOOS=linux GOARCH=arm go vet ./... - GOOS=linux GOARCH=arm64 go vet ./... - GOOS=windows GOARCH=arm64 go vet ./... - GOOS=darwin GOARCH=arm64 go vet ./... - GOOS=js GOARCH=wasm go vet ./... - GOOS=linux GOARCH=mips go vet ./... \ No newline at end of file + go tool dist list \ + | sed -e 's#/# #g' \ + | while read goos goarch; \ + do \ + echo $$goos $$goarch; \ + GOOS=$$goos GOARCH=$$goarch CGO_ENABLED=1 GO386=softfloat go vet ./...; \ + GOOS=$$goos GOARCH=$$goarch CGO_ENABLED=1 GO386=softfloat go vet -tags=purego ./...; \ + done diff --git a/vendor/github.com/zeebo/blake3/digest.go b/vendor/github.com/zeebo/blake3/digest.go index 4c511fbd..2578c2b8 100644 --- a/vendor/github.com/zeebo/blake3/digest.go +++ b/vendor/github.com/zeebo/blake3/digest.go @@ -22,7 +22,7 @@ type Digest struct { bufn int } -// Read reads data frm the hasher into out. It always fills the entire buffer and +// Read reads data from the hasher into out. It always fills the entire buffer and // never errors. The stream will wrap around when reading past 2^64 bytes. func (d *Digest) Read(p []byte) (n int, err error) { n = len(p) diff --git a/vendor/github.com/zeebo/blake3/internal/consts/cpu.go b/vendor/github.com/zeebo/blake3/internal/consts/cpu.go index 20d67f18..0146899a 100644 --- a/vendor/github.com/zeebo/blake3/internal/consts/cpu.go +++ b/vendor/github.com/zeebo/blake3/internal/consts/cpu.go @@ -1,3 +1,5 @@ +//go:build !purego + package consts import ( diff --git a/vendor/github.com/zeebo/blake3/internal/consts/cpu_purego.go b/vendor/github.com/zeebo/blake3/internal/consts/cpu_purego.go new file mode 100644 index 00000000..e80e9591 --- /dev/null +++ b/vendor/github.com/zeebo/blake3/internal/consts/cpu_purego.go @@ -0,0 +1,8 @@ +//go:build purego + +package consts + +const ( + HasAVX2 = false + HasSSE41 = false +) diff --git a/vendor/golang.org/x/crypto/LICENSE b/vendor/golang.org/x/crypto/LICENSE index 6a66aea5..2a7cf70d 100644 --- a/vendor/golang.org/x/crypto/LICENSE +++ b/vendor/golang.org/x/crypto/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. +Copyright 2009 The Go Authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer. copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Google Inc. nor the names of its + * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/vendor/golang.org/x/crypto/sha3/keccakf_amd64.s b/vendor/golang.org/x/crypto/sha3/keccakf_amd64.s index 1f539388..99e2f16e 100644 --- a/vendor/golang.org/x/crypto/sha3/keccakf_amd64.s +++ b/vendor/golang.org/x/crypto/sha3/keccakf_amd64.s @@ -1,390 +1,5419 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Code generated by command: go run keccakf_amd64_asm.go -out ../keccakf_amd64.s -pkg sha3. DO NOT EDIT. //go:build amd64 && !purego && gc -// This code was translated into a form compatible with 6a from the public -// domain sources at https://github.com/gvanas/KeccakCodePackage - -// Offsets in state -#define _ba (0*8) -#define _be (1*8) -#define _bi (2*8) -#define _bo (3*8) -#define _bu (4*8) -#define _ga (5*8) -#define _ge (6*8) -#define _gi (7*8) -#define _go (8*8) -#define _gu (9*8) -#define _ka (10*8) -#define _ke (11*8) -#define _ki (12*8) -#define _ko (13*8) -#define _ku (14*8) -#define _ma (15*8) -#define _me (16*8) -#define _mi (17*8) -#define _mo (18*8) -#define _mu (19*8) -#define _sa (20*8) -#define _se (21*8) -#define _si (22*8) -#define _so (23*8) -#define _su (24*8) - -// Temporary registers -#define rT1 AX - -// Round vars -#define rpState DI -#define rpStack SP - -#define rDa BX -#define rDe CX -#define rDi DX -#define rDo R8 -#define rDu R9 - -#define rBa R10 -#define rBe R11 -#define rBi R12 -#define rBo R13 -#define rBu R14 - -#define rCa SI -#define rCe BP -#define rCi rBi -#define rCo rBo -#define rCu R15 - -#define MOVQ_RBI_RCE MOVQ rBi, rCe -#define XORQ_RT1_RCA XORQ rT1, rCa -#define XORQ_RT1_RCE XORQ rT1, rCe -#define XORQ_RBA_RCU XORQ rBa, rCu -#define XORQ_RBE_RCU XORQ rBe, rCu -#define XORQ_RDU_RCU XORQ rDu, rCu -#define XORQ_RDA_RCA XORQ rDa, rCa -#define XORQ_RDE_RCE XORQ rDe, rCe - -#define mKeccakRound(iState, oState, rc, B_RBI_RCE, G_RT1_RCA, G_RT1_RCE, G_RBA_RCU, K_RT1_RCA, K_RT1_RCE, K_RBA_RCU, M_RT1_RCA, M_RT1_RCE, M_RBE_RCU, S_RDU_RCU, S_RDA_RCA, S_RDE_RCE) \ - /* Prepare round */ \ - MOVQ rCe, rDa; \ - ROLQ $1, rDa; \ - \ - MOVQ _bi(iState), rCi; \ - XORQ _gi(iState), rDi; \ - XORQ rCu, rDa; \ - XORQ _ki(iState), rCi; \ - XORQ _mi(iState), rDi; \ - XORQ rDi, rCi; \ - \ - MOVQ rCi, rDe; \ - ROLQ $1, rDe; \ - \ - MOVQ _bo(iState), rCo; \ - XORQ _go(iState), rDo; \ - XORQ rCa, rDe; \ - XORQ _ko(iState), rCo; \ - XORQ _mo(iState), rDo; \ - XORQ rDo, rCo; \ - \ - MOVQ rCo, rDi; \ - ROLQ $1, rDi; \ - \ - MOVQ rCu, rDo; \ - XORQ rCe, rDi; \ - ROLQ $1, rDo; \ - \ - MOVQ rCa, rDu; \ - XORQ rCi, rDo; \ - ROLQ $1, rDu; \ - \ - /* Result b */ \ - MOVQ _ba(iState), rBa; \ - MOVQ _ge(iState), rBe; \ - XORQ rCo, rDu; \ - MOVQ _ki(iState), rBi; \ - MOVQ _mo(iState), rBo; \ - MOVQ _su(iState), rBu; \ - XORQ rDe, rBe; \ - ROLQ $44, rBe; \ - XORQ rDi, rBi; \ - XORQ rDa, rBa; \ - ROLQ $43, rBi; \ - \ - MOVQ rBe, rCa; \ - MOVQ rc, rT1; \ - ORQ rBi, rCa; \ - XORQ rBa, rT1; \ - XORQ rT1, rCa; \ - MOVQ rCa, _ba(oState); \ - \ - XORQ rDu, rBu; \ - ROLQ $14, rBu; \ - MOVQ rBa, rCu; \ - ANDQ rBe, rCu; \ - XORQ rBu, rCu; \ - MOVQ rCu, _bu(oState); \ - \ - XORQ rDo, rBo; \ - ROLQ $21, rBo; \ - MOVQ rBo, rT1; \ - ANDQ rBu, rT1; \ - XORQ rBi, rT1; \ - MOVQ rT1, _bi(oState); \ - \ - NOTQ rBi; \ - ORQ rBa, rBu; \ - ORQ rBo, rBi; \ - XORQ rBo, rBu; \ - XORQ rBe, rBi; \ - MOVQ rBu, _bo(oState); \ - MOVQ rBi, _be(oState); \ - B_RBI_RCE; \ - \ - /* Result g */ \ - MOVQ _gu(iState), rBe; \ - XORQ rDu, rBe; \ - MOVQ _ka(iState), rBi; \ - ROLQ $20, rBe; \ - XORQ rDa, rBi; \ - ROLQ $3, rBi; \ - MOVQ _bo(iState), rBa; \ - MOVQ rBe, rT1; \ - ORQ rBi, rT1; \ - XORQ rDo, rBa; \ - MOVQ _me(iState), rBo; \ - MOVQ _si(iState), rBu; \ - ROLQ $28, rBa; \ - XORQ rBa, rT1; \ - MOVQ rT1, _ga(oState); \ - G_RT1_RCA; \ - \ - XORQ rDe, rBo; \ - ROLQ $45, rBo; \ - MOVQ rBi, rT1; \ - ANDQ rBo, rT1; \ - XORQ rBe, rT1; \ - MOVQ rT1, _ge(oState); \ - G_RT1_RCE; \ - \ - XORQ rDi, rBu; \ - ROLQ $61, rBu; \ - MOVQ rBu, rT1; \ - ORQ rBa, rT1; \ - XORQ rBo, rT1; \ - MOVQ rT1, _go(oState); \ - \ - ANDQ rBe, rBa; \ - XORQ rBu, rBa; \ - MOVQ rBa, _gu(oState); \ - NOTQ rBu; \ - G_RBA_RCU; \ - \ - ORQ rBu, rBo; \ - XORQ rBi, rBo; \ - MOVQ rBo, _gi(oState); \ - \ - /* Result k */ \ - MOVQ _be(iState), rBa; \ - MOVQ _gi(iState), rBe; \ - MOVQ _ko(iState), rBi; \ - MOVQ _mu(iState), rBo; \ - MOVQ _sa(iState), rBu; \ - XORQ rDi, rBe; \ - ROLQ $6, rBe; \ - XORQ rDo, rBi; \ - ROLQ $25, rBi; \ - MOVQ rBe, rT1; \ - ORQ rBi, rT1; \ - XORQ rDe, rBa; \ - ROLQ $1, rBa; \ - XORQ rBa, rT1; \ - MOVQ rT1, _ka(oState); \ - K_RT1_RCA; \ - \ - XORQ rDu, rBo; \ - ROLQ $8, rBo; \ - MOVQ rBi, rT1; \ - ANDQ rBo, rT1; \ - XORQ rBe, rT1; \ - MOVQ rT1, _ke(oState); \ - K_RT1_RCE; \ - \ - XORQ rDa, rBu; \ - ROLQ $18, rBu; \ - NOTQ rBo; \ - MOVQ rBo, rT1; \ - ANDQ rBu, rT1; \ - XORQ rBi, rT1; \ - MOVQ rT1, _ki(oState); \ - \ - MOVQ rBu, rT1; \ - ORQ rBa, rT1; \ - XORQ rBo, rT1; \ - MOVQ rT1, _ko(oState); \ - \ - ANDQ rBe, rBa; \ - XORQ rBu, rBa; \ - MOVQ rBa, _ku(oState); \ - K_RBA_RCU; \ - \ - /* Result m */ \ - MOVQ _ga(iState), rBe; \ - XORQ rDa, rBe; \ - MOVQ _ke(iState), rBi; \ - ROLQ $36, rBe; \ - XORQ rDe, rBi; \ - MOVQ _bu(iState), rBa; \ - ROLQ $10, rBi; \ - MOVQ rBe, rT1; \ - MOVQ _mi(iState), rBo; \ - ANDQ rBi, rT1; \ - XORQ rDu, rBa; \ - MOVQ _so(iState), rBu; \ - ROLQ $27, rBa; \ - XORQ rBa, rT1; \ - MOVQ rT1, _ma(oState); \ - M_RT1_RCA; \ - \ - XORQ rDi, rBo; \ - ROLQ $15, rBo; \ - MOVQ rBi, rT1; \ - ORQ rBo, rT1; \ - XORQ rBe, rT1; \ - MOVQ rT1, _me(oState); \ - M_RT1_RCE; \ - \ - XORQ rDo, rBu; \ - ROLQ $56, rBu; \ - NOTQ rBo; \ - MOVQ rBo, rT1; \ - ORQ rBu, rT1; \ - XORQ rBi, rT1; \ - MOVQ rT1, _mi(oState); \ - \ - ORQ rBa, rBe; \ - XORQ rBu, rBe; \ - MOVQ rBe, _mu(oState); \ - \ - ANDQ rBa, rBu; \ - XORQ rBo, rBu; \ - MOVQ rBu, _mo(oState); \ - M_RBE_RCU; \ - \ - /* Result s */ \ - MOVQ _bi(iState), rBa; \ - MOVQ _go(iState), rBe; \ - MOVQ _ku(iState), rBi; \ - XORQ rDi, rBa; \ - MOVQ _ma(iState), rBo; \ - ROLQ $62, rBa; \ - XORQ rDo, rBe; \ - MOVQ _se(iState), rBu; \ - ROLQ $55, rBe; \ - \ - XORQ rDu, rBi; \ - MOVQ rBa, rDu; \ - XORQ rDe, rBu; \ - ROLQ $2, rBu; \ - ANDQ rBe, rDu; \ - XORQ rBu, rDu; \ - MOVQ rDu, _su(oState); \ - \ - ROLQ $39, rBi; \ - S_RDU_RCU; \ - NOTQ rBe; \ - XORQ rDa, rBo; \ - MOVQ rBe, rDa; \ - ANDQ rBi, rDa; \ - XORQ rBa, rDa; \ - MOVQ rDa, _sa(oState); \ - S_RDA_RCA; \ - \ - ROLQ $41, rBo; \ - MOVQ rBi, rDe; \ - ORQ rBo, rDe; \ - XORQ rBe, rDe; \ - MOVQ rDe, _se(oState); \ - S_RDE_RCE; \ - \ - MOVQ rBo, rDi; \ - MOVQ rBu, rDo; \ - ANDQ rBu, rDi; \ - ORQ rBa, rDo; \ - XORQ rBi, rDi; \ - XORQ rBo, rDo; \ - MOVQ rDi, _si(oState); \ - MOVQ rDo, _so(oState) \ - // func keccakF1600(a *[25]uint64) -TEXT ·keccakF1600(SB), 0, $200-8 - MOVQ a+0(FP), rpState +TEXT ·keccakF1600(SB), $200-8 + MOVQ a+0(FP), DI // Convert the user state into an internal state - NOTQ _be(rpState) - NOTQ _bi(rpState) - NOTQ _go(rpState) - NOTQ _ki(rpState) - NOTQ _mi(rpState) - NOTQ _sa(rpState) + NOTQ 8(DI) + NOTQ 16(DI) + NOTQ 64(DI) + NOTQ 96(DI) + NOTQ 136(DI) + NOTQ 160(DI) // Execute the KeccakF permutation - MOVQ _ba(rpState), rCa - MOVQ _be(rpState), rCe - MOVQ _bu(rpState), rCu - - XORQ _ga(rpState), rCa - XORQ _ge(rpState), rCe - XORQ _gu(rpState), rCu - - XORQ _ka(rpState), rCa - XORQ _ke(rpState), rCe - XORQ _ku(rpState), rCu - - XORQ _ma(rpState), rCa - XORQ _me(rpState), rCe - XORQ _mu(rpState), rCu - - XORQ _sa(rpState), rCa - XORQ _se(rpState), rCe - MOVQ _si(rpState), rDi - MOVQ _so(rpState), rDo - XORQ _su(rpState), rCu - - mKeccakRound(rpState, rpStack, $0x0000000000000001, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x0000000000008082, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x800000000000808a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x8000000080008000, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x000000000000808b, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x0000000080000001, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x8000000080008081, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x8000000000008009, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x000000000000008a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x0000000000000088, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x0000000080008009, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x000000008000000a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x000000008000808b, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x800000000000008b, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x8000000000008089, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x8000000000008003, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x8000000000008002, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x8000000000000080, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x000000000000800a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x800000008000000a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x8000000080008081, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x8000000000008080, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x0000000080000001, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x8000000080008008, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP) + MOVQ (DI), SI + MOVQ 8(DI), BP + MOVQ 32(DI), R15 + XORQ 40(DI), SI + XORQ 48(DI), BP + XORQ 72(DI), R15 + XORQ 80(DI), SI + XORQ 88(DI), BP + XORQ 112(DI), R15 + XORQ 120(DI), SI + XORQ 128(DI), BP + XORQ 152(DI), R15 + XORQ 160(DI), SI + XORQ 168(DI), BP + MOVQ 176(DI), DX + MOVQ 184(DI), R8 + XORQ 192(DI), R15 - // Revert the internal state to the user state - NOTQ _be(rpState) - NOTQ _bi(rpState) - NOTQ _go(rpState) - NOTQ _ki(rpState) - NOTQ _mi(rpState) - NOTQ _sa(rpState) + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x0000000000000001, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x0000000000008082, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x800000000000808a, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000080008000, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x000000000000808b, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x0000000080000001, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000080008081, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000000008009, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x000000000000008a, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x0000000000000088, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x0000000080008009, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x000000008000000a, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x000000008000808b, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x800000000000008b, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000000008089, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000000008003, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000000008002, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000000000080, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x000000000000800a, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x800000008000000a, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000080008081, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000000008080, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x0000000080000001, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000080008008, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + NOP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + NOP + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + NOP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + NOP + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + NOP + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + NOP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + NOP + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + NOP + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + NOP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + NOP + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + NOP + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + NOP + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + NOP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Revert the internal state to the user state + NOTQ 8(DI) + NOTQ 16(DI) + NOTQ 64(DI) + NOTQ 96(DI) + NOTQ 136(DI) + NOTQ 160(DI) RET diff --git a/vendor/golang.org/x/mod/LICENSE b/vendor/golang.org/x/mod/LICENSE index 6a66aea5..2a7cf70d 100644 --- a/vendor/golang.org/x/mod/LICENSE +++ b/vendor/golang.org/x/mod/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. +Copyright 2009 The Go Authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer. copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Google Inc. nor the names of its + * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/vendor/golang.org/x/net/LICENSE b/vendor/golang.org/x/net/LICENSE index 6a66aea5..2a7cf70d 100644 --- a/vendor/golang.org/x/net/LICENSE +++ b/vendor/golang.org/x/net/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. +Copyright 2009 The Go Authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer. copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Google Inc. nor the names of its + * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/vendor/golang.org/x/sync/LICENSE b/vendor/golang.org/x/sync/LICENSE index 6a66aea5..2a7cf70d 100644 --- a/vendor/golang.org/x/sync/LICENSE +++ b/vendor/golang.org/x/sync/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. +Copyright 2009 The Go Authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer. copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Google Inc. nor the names of its + * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/vendor/golang.org/x/sys/LICENSE b/vendor/golang.org/x/sys/LICENSE index 6a66aea5..2a7cf70d 100644 --- a/vendor/golang.org/x/sys/LICENSE +++ b/vendor/golang.org/x/sys/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. +Copyright 2009 The Go Authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer. copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Google Inc. nor the names of its + * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/vendor/golang.org/x/sys/cpu/cpu.go b/vendor/golang.org/x/sys/cpu/cpu.go index 8fa707aa..ec07aab0 100644 --- a/vendor/golang.org/x/sys/cpu/cpu.go +++ b/vendor/golang.org/x/sys/cpu/cpu.go @@ -105,6 +105,8 @@ var ARM64 struct { HasSVE bool // Scalable Vector Extensions HasSVE2 bool // Scalable Vector Extensions 2 HasASIMDFHM bool // Advanced SIMD multiplication FP16 to FP32 + HasDIT bool // Data Independent Timing support + HasI8MM bool // Advanced SIMD Int8 matrix multiplication instructions _ CacheLinePad } diff --git a/vendor/golang.org/x/sys/cpu/cpu_arm64.go b/vendor/golang.org/x/sys/cpu/cpu_arm64.go index 0e27a21e..af2aa99f 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_arm64.go +++ b/vendor/golang.org/x/sys/cpu/cpu_arm64.go @@ -38,6 +38,8 @@ func initOptions() { {Name: "dcpop", Feature: &ARM64.HasDCPOP}, {Name: "asimddp", Feature: &ARM64.HasASIMDDP}, {Name: "asimdfhm", Feature: &ARM64.HasASIMDFHM}, + {Name: "dit", Feature: &ARM64.HasDIT}, + {Name: "i8mm", Feature: &ARM64.HasI8MM}, } } @@ -145,6 +147,11 @@ func parseARM64SystemRegisters(isar0, isar1, pfr0 uint64) { ARM64.HasLRCPC = true } + switch extractBits(isar1, 52, 55) { + case 1: + ARM64.HasI8MM = true + } + // ID_AA64PFR0_EL1 switch extractBits(pfr0, 16, 19) { case 0: @@ -168,6 +175,11 @@ func parseARM64SystemRegisters(isar0, isar1, pfr0 uint64) { parseARM64SVERegister(getzfr0()) } + + switch extractBits(pfr0, 48, 51) { + case 1: + ARM64.HasDIT = true + } } func parseARM64SVERegister(zfr0 uint64) { diff --git a/vendor/golang.org/x/sys/cpu/cpu_linux_arm64.go b/vendor/golang.org/x/sys/cpu/cpu_linux_arm64.go index 3d386d0f..08f35ea1 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_linux_arm64.go +++ b/vendor/golang.org/x/sys/cpu/cpu_linux_arm64.go @@ -35,8 +35,10 @@ const ( hwcap_SHA512 = 1 << 21 hwcap_SVE = 1 << 22 hwcap_ASIMDFHM = 1 << 23 + hwcap_DIT = 1 << 24 hwcap2_SVE2 = 1 << 1 + hwcap2_I8MM = 1 << 13 ) // linuxKernelCanEmulateCPUID reports whether we're running @@ -106,9 +108,12 @@ func doinit() { ARM64.HasSHA512 = isSet(hwCap, hwcap_SHA512) ARM64.HasSVE = isSet(hwCap, hwcap_SVE) ARM64.HasASIMDFHM = isSet(hwCap, hwcap_ASIMDFHM) + ARM64.HasDIT = isSet(hwCap, hwcap_DIT) + // HWCAP2 feature bits ARM64.HasSVE2 = isSet(hwCap2, hwcap2_SVE2) + ARM64.HasI8MM = isSet(hwCap2, hwcap2_I8MM) } func isSet(hwc uint, value uint) bool { diff --git a/vendor/golang.org/x/sys/unix/mkerrors.sh b/vendor/golang.org/x/sys/unix/mkerrors.sh index 4ed2e488..d07dd09e 100644 --- a/vendor/golang.org/x/sys/unix/mkerrors.sh +++ b/vendor/golang.org/x/sys/unix/mkerrors.sh @@ -58,6 +58,7 @@ includes_Darwin=' #define _DARWIN_USE_64_BIT_INODE #define __APPLE_USE_RFC_3542 #include +#include #include #include #include diff --git a/vendor/golang.org/x/sys/unix/syscall_darwin.go b/vendor/golang.org/x/sys/unix/syscall_darwin.go index 4cc7b005..2d15200a 100644 --- a/vendor/golang.org/x/sys/unix/syscall_darwin.go +++ b/vendor/golang.org/x/sys/unix/syscall_darwin.go @@ -402,6 +402,18 @@ func IoctlSetIfreqMTU(fd int, ifreq *IfreqMTU) error { return ioctlPtr(fd, SIOCSIFMTU, unsafe.Pointer(ifreq)) } +//sys renamexNp(from string, to string, flag uint32) (err error) + +func RenamexNp(from string, to string, flag uint32) (err error) { + return renamexNp(from, to, flag) +} + +//sys renameatxNp(fromfd int, from string, tofd int, to string, flag uint32) (err error) + +func RenameatxNp(fromfd int, from string, tofd int, to string, flag uint32) (err error) { + return renameatxNp(fromfd, from, tofd, to, flag) +} + //sys sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) = SYS_SYSCTL func Uname(uname *Utsname) error { diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go index 5682e262..3f1d3d4c 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux.go @@ -2592,3 +2592,4 @@ func SchedGetAttr(pid int, flags uint) (*SchedAttr, error) { } //sys Cachestat(fd uint, crange *CachestatRange, cstat *Cachestat_t, flags uint) (err error) +//sys Mseal(b []byte, flags uint) (err error) diff --git a/vendor/golang.org/x/sys/unix/syscall_openbsd.go b/vendor/golang.org/x/sys/unix/syscall_openbsd.go index b25343c7..b86ded54 100644 --- a/vendor/golang.org/x/sys/unix/syscall_openbsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_openbsd.go @@ -293,6 +293,7 @@ func Uname(uname *Utsname) error { //sys Mkfifoat(dirfd int, path string, mode uint32) (err error) //sys Mknod(path string, mode uint32, dev int) (err error) //sys Mknodat(dirfd int, path string, mode uint32, dev int) (err error) +//sys Mount(fsType string, dir string, flags int, data unsafe.Pointer) (err error) //sys Nanosleep(time *Timespec, leftover *Timespec) (err error) //sys Open(path string, mode int, perm uint32) (fd int, err error) //sys Openat(dirfd int, path string, mode int, perm uint32) (fd int, err error) diff --git a/vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go b/vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go index e40fa852..4308ac17 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go @@ -1169,6 +1169,11 @@ const ( PT_WRITE_D = 0x5 PT_WRITE_I = 0x4 PT_WRITE_U = 0x6 + RENAME_EXCL = 0x4 + RENAME_NOFOLLOW_ANY = 0x10 + RENAME_RESERVED1 = 0x8 + RENAME_SECLUDE = 0x1 + RENAME_SWAP = 0x2 RLIMIT_AS = 0x5 RLIMIT_CORE = 0x4 RLIMIT_CPU = 0x0 diff --git a/vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go b/vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go index bb02aa6c..c8068a7a 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go @@ -1169,6 +1169,11 @@ const ( PT_WRITE_D = 0x5 PT_WRITE_I = 0x4 PT_WRITE_U = 0x6 + RENAME_EXCL = 0x4 + RENAME_NOFOLLOW_ANY = 0x10 + RENAME_RESERVED1 = 0x8 + RENAME_SECLUDE = 0x1 + RENAME_SWAP = 0x2 RLIMIT_AS = 0x5 RLIMIT_CORE = 0x4 RLIMIT_CPU = 0x0 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux.go b/vendor/golang.org/x/sys/unix/zerrors_linux.go index 877a62b4..01a70b24 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux.go @@ -457,6 +457,7 @@ const ( B600 = 0x8 B75 = 0x2 B9600 = 0xd + BCACHEFS_SUPER_MAGIC = 0xca451a4e BDEVFS_MAGIC = 0x62646576 BINDERFS_SUPER_MAGIC = 0x6c6f6f70 BINFMTFS_MAGIC = 0x42494e4d @@ -928,6 +929,7 @@ const ( EPOLL_CTL_ADD = 0x1 EPOLL_CTL_DEL = 0x2 EPOLL_CTL_MOD = 0x3 + EPOLL_IOC_TYPE = 0x8a EROFS_SUPER_MAGIC_V1 = 0xe0f5e1e2 ESP_V4_FLOW = 0xa ESP_V6_FLOW = 0xc @@ -941,9 +943,6 @@ const ( ETHTOOL_FEC_OFF = 0x4 ETHTOOL_FEC_RS = 0x8 ETHTOOL_FLAG_ALL = 0x7 - ETHTOOL_FLAG_COMPACT_BITSETS = 0x1 - ETHTOOL_FLAG_OMIT_REPLY = 0x2 - ETHTOOL_FLAG_STATS = 0x4 ETHTOOL_FLASHDEV = 0x33 ETHTOOL_FLASH_MAX_FILENAME = 0x80 ETHTOOL_FWVERS_LEN = 0x20 @@ -1705,6 +1704,7 @@ const ( KEXEC_ARCH_S390 = 0x160000 KEXEC_ARCH_SH = 0x2a0000 KEXEC_ARCH_X86_64 = 0x3e0000 + KEXEC_CRASH_HOTPLUG_SUPPORT = 0x8 KEXEC_FILE_DEBUG = 0x8 KEXEC_FILE_NO_INITRAMFS = 0x4 KEXEC_FILE_ON_CRASH = 0x2 @@ -1780,6 +1780,7 @@ const ( KEY_SPEC_USER_KEYRING = -0x4 KEY_SPEC_USER_SESSION_KEYRING = -0x5 LANDLOCK_ACCESS_FS_EXECUTE = 0x1 + LANDLOCK_ACCESS_FS_IOCTL_DEV = 0x8000 LANDLOCK_ACCESS_FS_MAKE_BLOCK = 0x800 LANDLOCK_ACCESS_FS_MAKE_CHAR = 0x40 LANDLOCK_ACCESS_FS_MAKE_DIR = 0x80 @@ -1861,6 +1862,19 @@ const ( MAP_FILE = 0x0 MAP_FIXED = 0x10 MAP_FIXED_NOREPLACE = 0x100000 + MAP_HUGE_16GB = 0x88000000 + MAP_HUGE_16KB = 0x38000000 + MAP_HUGE_16MB = 0x60000000 + MAP_HUGE_1GB = 0x78000000 + MAP_HUGE_1MB = 0x50000000 + MAP_HUGE_256MB = 0x70000000 + MAP_HUGE_2GB = 0x7c000000 + MAP_HUGE_2MB = 0x54000000 + MAP_HUGE_32MB = 0x64000000 + MAP_HUGE_512KB = 0x4c000000 + MAP_HUGE_512MB = 0x74000000 + MAP_HUGE_64KB = 0x40000000 + MAP_HUGE_8MB = 0x5c000000 MAP_HUGE_MASK = 0x3f MAP_HUGE_SHIFT = 0x1a MAP_PRIVATE = 0x2 @@ -2498,6 +2512,23 @@ const ( PR_PAC_GET_ENABLED_KEYS = 0x3d PR_PAC_RESET_KEYS = 0x36 PR_PAC_SET_ENABLED_KEYS = 0x3c + PR_PPC_DEXCR_CTRL_CLEAR = 0x4 + PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC = 0x10 + PR_PPC_DEXCR_CTRL_EDITABLE = 0x1 + PR_PPC_DEXCR_CTRL_MASK = 0x1f + PR_PPC_DEXCR_CTRL_SET = 0x2 + PR_PPC_DEXCR_CTRL_SET_ONEXEC = 0x8 + PR_PPC_DEXCR_IBRTPD = 0x1 + PR_PPC_DEXCR_NPHIE = 0x3 + PR_PPC_DEXCR_SBHE = 0x0 + PR_PPC_DEXCR_SRAPD = 0x2 + PR_PPC_GET_DEXCR = 0x48 + PR_PPC_SET_DEXCR = 0x49 + PR_RISCV_CTX_SW_FENCEI_OFF = 0x1 + PR_RISCV_CTX_SW_FENCEI_ON = 0x0 + PR_RISCV_SCOPE_PER_PROCESS = 0x0 + PR_RISCV_SCOPE_PER_THREAD = 0x1 + PR_RISCV_SET_ICACHE_FLUSH_CTX = 0x47 PR_RISCV_V_GET_CONTROL = 0x46 PR_RISCV_V_SET_CONTROL = 0x45 PR_RISCV_V_VSTATE_CTRL_CUR_MASK = 0x3 @@ -3192,6 +3223,7 @@ const ( STATX_MTIME = 0x40 STATX_NLINK = 0x4 STATX_SIZE = 0x200 + STATX_SUBVOL = 0x8000 STATX_TYPE = 0x1 STATX_UID = 0x8 STATX__RESERVED = 0x80000000 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go index e4bc0bd5..684a5168 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go @@ -78,6 +78,8 @@ const ( ECHOPRT = 0x400 EFD_CLOEXEC = 0x80000 EFD_NONBLOCK = 0x800 + EPIOCGPARAMS = 0x80088a02 + EPIOCSPARAMS = 0x40088a01 EPOLL_CLOEXEC = 0x80000 EXTPROC = 0x10000 FF1 = 0x8000 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go index 689317af..61d74b59 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go @@ -78,6 +78,8 @@ const ( ECHOPRT = 0x400 EFD_CLOEXEC = 0x80000 EFD_NONBLOCK = 0x800 + EPIOCGPARAMS = 0x80088a02 + EPIOCSPARAMS = 0x40088a01 EPOLL_CLOEXEC = 0x80000 EXTPROC = 0x10000 FF1 = 0x8000 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go index 5cca668a..a28c9e3e 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go @@ -78,6 +78,8 @@ const ( ECHOPRT = 0x400 EFD_CLOEXEC = 0x80000 EFD_NONBLOCK = 0x800 + EPIOCGPARAMS = 0x80088a02 + EPIOCSPARAMS = 0x40088a01 EPOLL_CLOEXEC = 0x80000 EXTPROC = 0x10000 FF1 = 0x8000 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go index 14270508..ab5d1fe8 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go @@ -78,6 +78,8 @@ const ( ECHOPRT = 0x400 EFD_CLOEXEC = 0x80000 EFD_NONBLOCK = 0x800 + EPIOCGPARAMS = 0x80088a02 + EPIOCSPARAMS = 0x40088a01 EPOLL_CLOEXEC = 0x80000 ESR_MAGIC = 0x45535201 EXTPROC = 0x10000 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go index 28e39afd..c523090e 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go @@ -78,6 +78,8 @@ const ( ECHOPRT = 0x400 EFD_CLOEXEC = 0x80000 EFD_NONBLOCK = 0x800 + EPIOCGPARAMS = 0x80088a02 + EPIOCSPARAMS = 0x40088a01 EPOLL_CLOEXEC = 0x80000 EXTPROC = 0x10000 FF1 = 0x8000 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go index cd66e92c..01e6ea78 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go @@ -78,6 +78,8 @@ const ( ECHOPRT = 0x400 EFD_CLOEXEC = 0x80000 EFD_NONBLOCK = 0x80 + EPIOCGPARAMS = 0x40088a02 + EPIOCSPARAMS = 0x80088a01 EPOLL_CLOEXEC = 0x80000 EXTPROC = 0x10000 FF1 = 0x8000 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go index c1595eba..7aa610b1 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go @@ -78,6 +78,8 @@ const ( ECHOPRT = 0x400 EFD_CLOEXEC = 0x80000 EFD_NONBLOCK = 0x80 + EPIOCGPARAMS = 0x40088a02 + EPIOCSPARAMS = 0x80088a01 EPOLL_CLOEXEC = 0x80000 EXTPROC = 0x10000 FF1 = 0x8000 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go index ee9456b0..92af771b 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go @@ -78,6 +78,8 @@ const ( ECHOPRT = 0x400 EFD_CLOEXEC = 0x80000 EFD_NONBLOCK = 0x80 + EPIOCGPARAMS = 0x40088a02 + EPIOCSPARAMS = 0x80088a01 EPOLL_CLOEXEC = 0x80000 EXTPROC = 0x10000 FF1 = 0x8000 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go index 8cfca81e..b27ef5e6 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go @@ -78,6 +78,8 @@ const ( ECHOPRT = 0x400 EFD_CLOEXEC = 0x80000 EFD_NONBLOCK = 0x80 + EPIOCGPARAMS = 0x40088a02 + EPIOCSPARAMS = 0x80088a01 EPOLL_CLOEXEC = 0x80000 EXTPROC = 0x10000 FF1 = 0x8000 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go index 60b0deb3..237a2cef 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go @@ -78,6 +78,8 @@ const ( ECHOPRT = 0x20 EFD_CLOEXEC = 0x80000 EFD_NONBLOCK = 0x800 + EPIOCGPARAMS = 0x40088a02 + EPIOCSPARAMS = 0x80088a01 EPOLL_CLOEXEC = 0x80000 EXTPROC = 0x10000000 FF1 = 0x4000 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go index f90aa728..4a5c555a 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go @@ -78,6 +78,8 @@ const ( ECHOPRT = 0x20 EFD_CLOEXEC = 0x80000 EFD_NONBLOCK = 0x800 + EPIOCGPARAMS = 0x40088a02 + EPIOCSPARAMS = 0x80088a01 EPOLL_CLOEXEC = 0x80000 EXTPROC = 0x10000000 FF1 = 0x4000 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go index ba9e0150..a02fb49a 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go @@ -78,6 +78,8 @@ const ( ECHOPRT = 0x20 EFD_CLOEXEC = 0x80000 EFD_NONBLOCK = 0x800 + EPIOCGPARAMS = 0x40088a02 + EPIOCSPARAMS = 0x80088a01 EPOLL_CLOEXEC = 0x80000 EXTPROC = 0x10000000 FF1 = 0x4000 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go index 07cdfd6e..e26a7c61 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go @@ -78,6 +78,8 @@ const ( ECHOPRT = 0x400 EFD_CLOEXEC = 0x80000 EFD_NONBLOCK = 0x800 + EPIOCGPARAMS = 0x80088a02 + EPIOCSPARAMS = 0x40088a01 EPOLL_CLOEXEC = 0x80000 EXTPROC = 0x10000 FF1 = 0x8000 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go index 2f1dd214..c48f7c21 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go @@ -78,6 +78,8 @@ const ( ECHOPRT = 0x400 EFD_CLOEXEC = 0x80000 EFD_NONBLOCK = 0x800 + EPIOCGPARAMS = 0x80088a02 + EPIOCSPARAMS = 0x40088a01 EPOLL_CLOEXEC = 0x80000 EXTPROC = 0x10000 FF1 = 0x8000 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go index f40519d9..ad4b9aac 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go @@ -82,6 +82,8 @@ const ( EFD_CLOEXEC = 0x400000 EFD_NONBLOCK = 0x4000 EMT_TAGOVF = 0x1 + EPIOCGPARAMS = 0x40088a02 + EPIOCSPARAMS = 0x80088a01 EPOLL_CLOEXEC = 0x400000 EXTPROC = 0x10000 FF1 = 0x8000 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go index 07642c30..b622533e 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go @@ -740,6 +740,54 @@ func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func renamexNp(from string, to string, flag uint32) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(from) + if err != nil { + return + } + var _p1 *byte + _p1, err = BytePtrFromString(to) + if err != nil { + return + } + _, _, e1 := syscall_syscall(libc_renamex_np_trampoline_addr, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(flag)) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_renamex_np_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_renamex_np renamex_np "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func renameatxNp(fromfd int, from string, tofd int, to string, flag uint32) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(from) + if err != nil { + return + } + var _p1 *byte + _p1, err = BytePtrFromString(to) + if err != nil { + return + } + _, _, e1 := syscall_syscall6(libc_renameatx_np_trampoline_addr, uintptr(fromfd), uintptr(unsafe.Pointer(_p0)), uintptr(tofd), uintptr(unsafe.Pointer(_p1)), uintptr(flag), 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_renameatx_np_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_renameatx_np renameatx_np "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { var _p0 unsafe.Pointer if len(mib) > 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s index 923e08cb..cfe6646b 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s @@ -223,6 +223,16 @@ TEXT libc_ioctl_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_ioctl_trampoline_addr(SB), RODATA, $8 DATA ·libc_ioctl_trampoline_addr(SB)/8, $libc_ioctl_trampoline<>(SB) +TEXT libc_renamex_np_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_renamex_np(SB) +GLOBL ·libc_renamex_np_trampoline_addr(SB), RODATA, $8 +DATA ·libc_renamex_np_trampoline_addr(SB)/8, $libc_renamex_np_trampoline<>(SB) + +TEXT libc_renameatx_np_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_renameatx_np(SB) +GLOBL ·libc_renameatx_np_trampoline_addr(SB), RODATA, $8 +DATA ·libc_renameatx_np_trampoline_addr(SB)/8, $libc_renameatx_np_trampoline<>(SB) + TEXT libc_sysctl_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_sysctl(SB) GLOBL ·libc_sysctl_trampoline_addr(SB), RODATA, $8 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go index 7d73dda6..13f624f6 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go @@ -740,6 +740,54 @@ func ioctlPtr(fd int, req uint, arg unsafe.Pointer) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func renamexNp(from string, to string, flag uint32) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(from) + if err != nil { + return + } + var _p1 *byte + _p1, err = BytePtrFromString(to) + if err != nil { + return + } + _, _, e1 := syscall_syscall(libc_renamex_np_trampoline_addr, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(flag)) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_renamex_np_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_renamex_np renamex_np "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func renameatxNp(fromfd int, from string, tofd int, to string, flag uint32) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(from) + if err != nil { + return + } + var _p1 *byte + _p1, err = BytePtrFromString(to) + if err != nil { + return + } + _, _, e1 := syscall_syscall6(libc_renameatx_np_trampoline_addr, uintptr(fromfd), uintptr(unsafe.Pointer(_p0)), uintptr(tofd), uintptr(unsafe.Pointer(_p1)), uintptr(flag), 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_renameatx_np_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_renameatx_np renameatx_np "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { var _p0 unsafe.Pointer if len(mib) > 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s index 05770011..fe222b75 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s @@ -223,6 +223,16 @@ TEXT libc_ioctl_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_ioctl_trampoline_addr(SB), RODATA, $8 DATA ·libc_ioctl_trampoline_addr(SB)/8, $libc_ioctl_trampoline<>(SB) +TEXT libc_renamex_np_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_renamex_np(SB) +GLOBL ·libc_renamex_np_trampoline_addr(SB), RODATA, $8 +DATA ·libc_renamex_np_trampoline_addr(SB)/8, $libc_renamex_np_trampoline<>(SB) + +TEXT libc_renameatx_np_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_renameatx_np(SB) +GLOBL ·libc_renameatx_np_trampoline_addr(SB), RODATA, $8 +DATA ·libc_renameatx_np_trampoline_addr(SB)/8, $libc_renameatx_np_trampoline<>(SB) + TEXT libc_sysctl_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_sysctl(SB) GLOBL ·libc_sysctl_trampoline_addr(SB), RODATA, $8 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux.go b/vendor/golang.org/x/sys/unix/zsyscall_linux.go index 87d8612a..1bc1a5ad 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux.go @@ -2229,3 +2229,19 @@ func Cachestat(fd uint, crange *CachestatRange, cstat *Cachestat_t, flags uint) } return } + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + +func Mseal(b []byte, flags uint) (err error) { + var _p0 unsafe.Pointer + if len(b) > 0 { + _p0 = unsafe.Pointer(&b[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + _, _, e1 := Syscall(SYS_MSEAL, uintptr(_p0), uintptr(len(b)), uintptr(flags)) + if e1 != 0 { + err = errnoErr(e1) + } + return +} diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go index 9dc42410..1851df14 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go @@ -1493,6 +1493,30 @@ var libc_mknodat_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func Mount(fsType string, dir string, flags int, data unsafe.Pointer) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(fsType) + if err != nil { + return + } + var _p1 *byte + _p1, err = BytePtrFromString(dir) + if err != nil { + return + } + _, _, e1 := syscall_syscall6(libc_mount_trampoline_addr, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(flags), uintptr(data), 0, 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_mount_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_mount mount "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Nanosleep(time *Timespec, leftover *Timespec) (err error) { _, _, e1 := syscall_syscall(libc_nanosleep_trampoline_addr, uintptr(unsafe.Pointer(time)), uintptr(unsafe.Pointer(leftover)), 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.s b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.s index 41b56173..0b43c693 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.s @@ -463,6 +463,11 @@ TEXT libc_mknodat_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_mknodat_trampoline_addr(SB), RODATA, $4 DATA ·libc_mknodat_trampoline_addr(SB)/4, $libc_mknodat_trampoline<>(SB) +TEXT libc_mount_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_mount(SB) +GLOBL ·libc_mount_trampoline_addr(SB), RODATA, $4 +DATA ·libc_mount_trampoline_addr(SB)/4, $libc_mount_trampoline<>(SB) + TEXT libc_nanosleep_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_nanosleep(SB) GLOBL ·libc_nanosleep_trampoline_addr(SB), RODATA, $4 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go index 0d3a0751..e1ec0dbe 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go @@ -1493,6 +1493,30 @@ var libc_mknodat_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func Mount(fsType string, dir string, flags int, data unsafe.Pointer) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(fsType) + if err != nil { + return + } + var _p1 *byte + _p1, err = BytePtrFromString(dir) + if err != nil { + return + } + _, _, e1 := syscall_syscall6(libc_mount_trampoline_addr, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(flags), uintptr(data), 0, 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_mount_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_mount mount "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Nanosleep(time *Timespec, leftover *Timespec) (err error) { _, _, e1 := syscall_syscall(libc_nanosleep_trampoline_addr, uintptr(unsafe.Pointer(time)), uintptr(unsafe.Pointer(leftover)), 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.s b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.s index 4019a656..880c6d6e 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.s @@ -463,6 +463,11 @@ TEXT libc_mknodat_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_mknodat_trampoline_addr(SB), RODATA, $8 DATA ·libc_mknodat_trampoline_addr(SB)/8, $libc_mknodat_trampoline<>(SB) +TEXT libc_mount_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_mount(SB) +GLOBL ·libc_mount_trampoline_addr(SB), RODATA, $8 +DATA ·libc_mount_trampoline_addr(SB)/8, $libc_mount_trampoline<>(SB) + TEXT libc_nanosleep_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_nanosleep(SB) GLOBL ·libc_nanosleep_trampoline_addr(SB), RODATA, $8 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go index c39f7776..7c8452a6 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go @@ -1493,6 +1493,30 @@ var libc_mknodat_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func Mount(fsType string, dir string, flags int, data unsafe.Pointer) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(fsType) + if err != nil { + return + } + var _p1 *byte + _p1, err = BytePtrFromString(dir) + if err != nil { + return + } + _, _, e1 := syscall_syscall6(libc_mount_trampoline_addr, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(flags), uintptr(data), 0, 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_mount_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_mount mount "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Nanosleep(time *Timespec, leftover *Timespec) (err error) { _, _, e1 := syscall_syscall(libc_nanosleep_trampoline_addr, uintptr(unsafe.Pointer(time)), uintptr(unsafe.Pointer(leftover)), 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.s b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.s index ac4af24f..b8ef95b0 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.s @@ -463,6 +463,11 @@ TEXT libc_mknodat_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_mknodat_trampoline_addr(SB), RODATA, $4 DATA ·libc_mknodat_trampoline_addr(SB)/4, $libc_mknodat_trampoline<>(SB) +TEXT libc_mount_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_mount(SB) +GLOBL ·libc_mount_trampoline_addr(SB), RODATA, $4 +DATA ·libc_mount_trampoline_addr(SB)/4, $libc_mount_trampoline<>(SB) + TEXT libc_nanosleep_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_nanosleep(SB) GLOBL ·libc_nanosleep_trampoline_addr(SB), RODATA, $4 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go index 57571d07..2ffdf861 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go @@ -1493,6 +1493,30 @@ var libc_mknodat_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func Mount(fsType string, dir string, flags int, data unsafe.Pointer) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(fsType) + if err != nil { + return + } + var _p1 *byte + _p1, err = BytePtrFromString(dir) + if err != nil { + return + } + _, _, e1 := syscall_syscall6(libc_mount_trampoline_addr, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(flags), uintptr(data), 0, 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_mount_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_mount mount "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Nanosleep(time *Timespec, leftover *Timespec) (err error) { _, _, e1 := syscall_syscall(libc_nanosleep_trampoline_addr, uintptr(unsafe.Pointer(time)), uintptr(unsafe.Pointer(leftover)), 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.s b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.s index f77d5321..2af3b5c7 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.s @@ -463,6 +463,11 @@ TEXT libc_mknodat_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_mknodat_trampoline_addr(SB), RODATA, $8 DATA ·libc_mknodat_trampoline_addr(SB)/8, $libc_mknodat_trampoline<>(SB) +TEXT libc_mount_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_mount(SB) +GLOBL ·libc_mount_trampoline_addr(SB), RODATA, $8 +DATA ·libc_mount_trampoline_addr(SB)/8, $libc_mount_trampoline<>(SB) + TEXT libc_nanosleep_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_nanosleep(SB) GLOBL ·libc_nanosleep_trampoline_addr(SB), RODATA, $8 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go index e62963e6..1da08d52 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go @@ -1493,6 +1493,30 @@ var libc_mknodat_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func Mount(fsType string, dir string, flags int, data unsafe.Pointer) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(fsType) + if err != nil { + return + } + var _p1 *byte + _p1, err = BytePtrFromString(dir) + if err != nil { + return + } + _, _, e1 := syscall_syscall6(libc_mount_trampoline_addr, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(flags), uintptr(data), 0, 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_mount_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_mount mount "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Nanosleep(time *Timespec, leftover *Timespec) (err error) { _, _, e1 := syscall_syscall(libc_nanosleep_trampoline_addr, uintptr(unsafe.Pointer(time)), uintptr(unsafe.Pointer(leftover)), 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.s b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.s index fae140b6..b7a25135 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.s @@ -463,6 +463,11 @@ TEXT libc_mknodat_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_mknodat_trampoline_addr(SB), RODATA, $8 DATA ·libc_mknodat_trampoline_addr(SB)/8, $libc_mknodat_trampoline<>(SB) +TEXT libc_mount_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_mount(SB) +GLOBL ·libc_mount_trampoline_addr(SB), RODATA, $8 +DATA ·libc_mount_trampoline_addr(SB)/8, $libc_mount_trampoline<>(SB) + TEXT libc_nanosleep_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_nanosleep(SB) GLOBL ·libc_nanosleep_trampoline_addr(SB), RODATA, $8 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go index 00831354..6e85b0aa 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go @@ -1493,6 +1493,30 @@ var libc_mknodat_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func Mount(fsType string, dir string, flags int, data unsafe.Pointer) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(fsType) + if err != nil { + return + } + var _p1 *byte + _p1, err = BytePtrFromString(dir) + if err != nil { + return + } + _, _, e1 := syscall_syscall6(libc_mount_trampoline_addr, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(flags), uintptr(data), 0, 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_mount_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_mount mount "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Nanosleep(time *Timespec, leftover *Timespec) (err error) { _, _, e1 := syscall_syscall(libc_nanosleep_trampoline_addr, uintptr(unsafe.Pointer(time)), uintptr(unsafe.Pointer(leftover)), 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.s b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.s index 9d1e0ff0..f15dadf0 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.s @@ -555,6 +555,12 @@ TEXT libc_mknodat_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_mknodat_trampoline_addr(SB), RODATA, $8 DATA ·libc_mknodat_trampoline_addr(SB)/8, $libc_mknodat_trampoline<>(SB) +TEXT libc_mount_trampoline<>(SB),NOSPLIT,$0-0 + CALL libc_mount(SB) + RET +GLOBL ·libc_mount_trampoline_addr(SB), RODATA, $8 +DATA ·libc_mount_trampoline_addr(SB)/8, $libc_mount_trampoline<>(SB) + TEXT libc_nanosleep_trampoline<>(SB),NOSPLIT,$0-0 CALL libc_nanosleep(SB) RET diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go index 79029ed5..28b487df 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go @@ -1493,6 +1493,30 @@ var libc_mknodat_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func Mount(fsType string, dir string, flags int, data unsafe.Pointer) (err error) { + var _p0 *byte + _p0, err = BytePtrFromString(fsType) + if err != nil { + return + } + var _p1 *byte + _p1, err = BytePtrFromString(dir) + if err != nil { + return + } + _, _, e1 := syscall_syscall6(libc_mount_trampoline_addr, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(flags), uintptr(data), 0, 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_mount_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_mount mount "libc.so" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Nanosleep(time *Timespec, leftover *Timespec) (err error) { _, _, e1 := syscall_syscall(libc_nanosleep_trampoline_addr, uintptr(unsafe.Pointer(time)), uintptr(unsafe.Pointer(leftover)), 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.s b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.s index da115f9a..1e7f321e 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.s @@ -463,6 +463,11 @@ TEXT libc_mknodat_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_mknodat_trampoline_addr(SB), RODATA, $8 DATA ·libc_mknodat_trampoline_addr(SB)/8, $libc_mknodat_trampoline<>(SB) +TEXT libc_mount_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_mount(SB) +GLOBL ·libc_mount_trampoline_addr(SB), RODATA, $8 +DATA ·libc_mount_trampoline_addr(SB)/8, $libc_mount_trampoline<>(SB) + TEXT libc_nanosleep_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_nanosleep(SB) GLOBL ·libc_nanosleep_trampoline_addr(SB), RODATA, $8 diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go index 53aef5dc..524b0820 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go @@ -457,4 +457,5 @@ const ( SYS_LSM_GET_SELF_ATTR = 459 SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 + SYS_MSEAL = 462 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go index 71d52476..d3e38f68 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go @@ -379,4 +379,5 @@ const ( SYS_LSM_GET_SELF_ATTR = 459 SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 + SYS_MSEAL = 462 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go index c7477061..70b35bf3 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go @@ -421,4 +421,5 @@ const ( SYS_LSM_GET_SELF_ATTR = 459 SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 + SYS_MSEAL = 462 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go index f96e214f..6c778c23 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go @@ -324,4 +324,5 @@ const ( SYS_LSM_GET_SELF_ATTR = 459 SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 + SYS_MSEAL = 462 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go index 28425346..37281cf5 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go @@ -318,4 +318,5 @@ const ( SYS_LSM_GET_SELF_ATTR = 459 SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 + SYS_MSEAL = 462 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go index d0953018..7e567f1e 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go @@ -441,4 +441,5 @@ const ( SYS_LSM_GET_SELF_ATTR = 4459 SYS_LSM_SET_SELF_ATTR = 4460 SYS_LSM_LIST_MODULES = 4461 + SYS_MSEAL = 4462 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go index 295c7f4b..38ae55e5 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go @@ -371,4 +371,5 @@ const ( SYS_LSM_GET_SELF_ATTR = 5459 SYS_LSM_SET_SELF_ATTR = 5460 SYS_LSM_LIST_MODULES = 5461 + SYS_MSEAL = 5462 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go index d1a9eaca..55e92e60 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go @@ -371,4 +371,5 @@ const ( SYS_LSM_GET_SELF_ATTR = 5459 SYS_LSM_SET_SELF_ATTR = 5460 SYS_LSM_LIST_MODULES = 5461 + SYS_MSEAL = 5462 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go index bec157c3..60658d6a 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go @@ -441,4 +441,5 @@ const ( SYS_LSM_GET_SELF_ATTR = 4459 SYS_LSM_SET_SELF_ATTR = 4460 SYS_LSM_LIST_MODULES = 4461 + SYS_MSEAL = 4462 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go index 7ee7bdc4..e203e8a7 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go @@ -448,4 +448,5 @@ const ( SYS_LSM_GET_SELF_ATTR = 459 SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 + SYS_MSEAL = 462 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go index fad1f25b..5944b97d 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go @@ -420,4 +420,5 @@ const ( SYS_LSM_GET_SELF_ATTR = 459 SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 + SYS_MSEAL = 462 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go index 7d3e1635..c66d416d 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go @@ -420,4 +420,5 @@ const ( SYS_LSM_GET_SELF_ATTR = 459 SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 + SYS_MSEAL = 462 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go index 0ed53ad9..9889f6a5 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go @@ -325,4 +325,5 @@ const ( SYS_LSM_GET_SELF_ATTR = 459 SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 + SYS_MSEAL = 462 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go index 2fba04ad..01d86825 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go @@ -386,4 +386,5 @@ const ( SYS_LSM_GET_SELF_ATTR = 459 SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 + SYS_MSEAL = 462 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go index 621d00d7..7b703e77 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go @@ -399,4 +399,5 @@ const ( SYS_LSM_GET_SELF_ATTR = 459 SYS_LSM_SET_SELF_ATTR = 460 SYS_LSM_LIST_MODULES = 461 + SYS_MSEAL = 462 ) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go index 4740b834..7f1961b9 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go @@ -110,7 +110,8 @@ type Statx_t struct { Mnt_id uint64 Dio_mem_align uint32 Dio_offset_align uint32 - _ [12]uint64 + Subvol uint64 + _ [11]uint64 } type Fsid struct { @@ -3473,7 +3474,7 @@ const ( DEVLINK_PORT_FN_ATTR_STATE = 0x2 DEVLINK_PORT_FN_ATTR_OPSTATE = 0x3 DEVLINK_PORT_FN_ATTR_CAPS = 0x4 - DEVLINK_PORT_FUNCTION_ATTR_MAX = 0x5 + DEVLINK_PORT_FUNCTION_ATTR_MAX = 0x6 ) type FsverityDigest struct { @@ -3806,6 +3807,9 @@ const ( ETHTOOL_MSG_PSE_GET_REPLY = 0x25 ETHTOOL_MSG_RSS_GET_REPLY = 0x26 ETHTOOL_MSG_KERNEL_MAX = 0x2b + ETHTOOL_FLAG_COMPACT_BITSETS = 0x1 + ETHTOOL_FLAG_OMIT_REPLY = 0x2 + ETHTOOL_FLAG_STATS = 0x4 ETHTOOL_A_HEADER_UNSPEC = 0x0 ETHTOOL_A_HEADER_DEV_INDEX = 0x1 ETHTOOL_A_HEADER_DEV_NAME = 0x2 @@ -3975,7 +3979,7 @@ const ( ETHTOOL_A_TSINFO_TX_TYPES = 0x3 ETHTOOL_A_TSINFO_RX_FILTERS = 0x4 ETHTOOL_A_TSINFO_PHC_INDEX = 0x5 - ETHTOOL_A_TSINFO_MAX = 0x5 + ETHTOOL_A_TSINFO_MAX = 0x6 ETHTOOL_A_CABLE_TEST_UNSPEC = 0x0 ETHTOOL_A_CABLE_TEST_HEADER = 0x1 ETHTOOL_A_CABLE_TEST_MAX = 0x1 diff --git a/vendor/golang.org/x/sys/windows/security_windows.go b/vendor/golang.org/x/sys/windows/security_windows.go index 97651b5b..b6e1ab76 100644 --- a/vendor/golang.org/x/sys/windows/security_windows.go +++ b/vendor/golang.org/x/sys/windows/security_windows.go @@ -1179,7 +1179,7 @@ type OBJECTS_AND_NAME struct { //sys makeSelfRelativeSD(absoluteSD *SECURITY_DESCRIPTOR, selfRelativeSD *SECURITY_DESCRIPTOR, selfRelativeSDSize *uint32) (err error) = advapi32.MakeSelfRelativeSD //sys setEntriesInAcl(countExplicitEntries uint32, explicitEntries *EXPLICIT_ACCESS, oldACL *ACL, newACL **ACL) (ret error) = advapi32.SetEntriesInAclW -//sys GetAce(acl *ACL, aceIndex uint32, pAce **ACCESS_ALLOWED_ACE) (ret error) = advapi32.GetAce +//sys GetAce(acl *ACL, aceIndex uint32, pAce **ACCESS_ALLOWED_ACE) (err error) = advapi32.GetAce // Control returns the security descriptor control bits. func (sd *SECURITY_DESCRIPTOR) Control() (control SECURITY_DESCRIPTOR_CONTROL, revision uint32, err error) { diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go index 6525c62f..1fa34fd1 100644 --- a/vendor/golang.org/x/sys/windows/syscall_windows.go +++ b/vendor/golang.org/x/sys/windows/syscall_windows.go @@ -17,8 +17,10 @@ import ( "unsafe" ) -type Handle uintptr -type HWND uintptr +type ( + Handle uintptr + HWND uintptr +) const ( InvalidHandle = ^Handle(0) @@ -211,6 +213,10 @@ func NewCallbackCDecl(fn interface{}) uintptr { //sys OpenProcess(desiredAccess uint32, inheritHandle bool, processId uint32) (handle Handle, err error) //sys ShellExecute(hwnd Handle, verb *uint16, file *uint16, args *uint16, cwd *uint16, showCmd int32) (err error) [failretval<=32] = shell32.ShellExecuteW //sys GetWindowThreadProcessId(hwnd HWND, pid *uint32) (tid uint32, err error) = user32.GetWindowThreadProcessId +//sys LoadKeyboardLayout(name *uint16, flags uint32) (hkl Handle, err error) [failretval==0] = user32.LoadKeyboardLayoutW +//sys UnloadKeyboardLayout(hkl Handle) (err error) = user32.UnloadKeyboardLayout +//sys GetKeyboardLayout(tid uint32) (hkl Handle) = user32.GetKeyboardLayout +//sys ToUnicodeEx(vkey uint32, scancode uint32, keystate *byte, pwszBuff *uint16, cchBuff int32, flags uint32, hkl Handle) (ret int32) = user32.ToUnicodeEx //sys GetShellWindow() (shellWindow HWND) = user32.GetShellWindow //sys MessageBox(hwnd HWND, text *uint16, caption *uint16, boxtype uint32) (ret int32, err error) [failretval==0] = user32.MessageBoxW //sys ExitWindowsEx(flags uint32, reason uint32) (err error) = user32.ExitWindowsEx @@ -1368,9 +1374,11 @@ func SetsockoptLinger(fd Handle, level, opt int, l *Linger) (err error) { func SetsockoptInet4Addr(fd Handle, level, opt int, value [4]byte) (err error) { return Setsockopt(fd, int32(level), int32(opt), (*byte)(unsafe.Pointer(&value[0])), 4) } + func SetsockoptIPMreq(fd Handle, level, opt int, mreq *IPMreq) (err error) { return Setsockopt(fd, int32(level), int32(opt), (*byte)(unsafe.Pointer(mreq)), int32(unsafe.Sizeof(*mreq))) } + func SetsockoptIPv6Mreq(fd Handle, level, opt int, mreq *IPv6Mreq) (err error) { return syscall.EWINDOWS } diff --git a/vendor/golang.org/x/sys/windows/types_windows.go b/vendor/golang.org/x/sys/windows/types_windows.go index d8cb71db..3f03b3d5 100644 --- a/vendor/golang.org/x/sys/windows/types_windows.go +++ b/vendor/golang.org/x/sys/windows/types_windows.go @@ -2003,7 +2003,21 @@ const ( MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x20 ) -const GAA_FLAG_INCLUDE_PREFIX = 0x00000010 +// Flags for GetAdaptersAddresses, see +// https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses. +const ( + GAA_FLAG_SKIP_UNICAST = 0x1 + GAA_FLAG_SKIP_ANYCAST = 0x2 + GAA_FLAG_SKIP_MULTICAST = 0x4 + GAA_FLAG_SKIP_DNS_SERVER = 0x8 + GAA_FLAG_INCLUDE_PREFIX = 0x10 + GAA_FLAG_SKIP_FRIENDLY_NAME = 0x20 + GAA_FLAG_INCLUDE_WINS_INFO = 0x40 + GAA_FLAG_INCLUDE_GATEWAYS = 0x80 + GAA_FLAG_INCLUDE_ALL_INTERFACES = 0x100 + GAA_FLAG_INCLUDE_ALL_COMPARTMENTS = 0x200 + GAA_FLAG_INCLUDE_TUNNEL_BINDINGORDER = 0x400 +) const ( IF_TYPE_OTHER = 1 @@ -2017,6 +2031,50 @@ const ( IF_TYPE_IEEE1394 = 144 ) +// Enum NL_PREFIX_ORIGIN for [IpAdapterUnicastAddress], see +// https://learn.microsoft.com/en-us/windows/win32/api/nldef/ne-nldef-nl_prefix_origin +const ( + IpPrefixOriginOther = 0 + IpPrefixOriginManual = 1 + IpPrefixOriginWellKnown = 2 + IpPrefixOriginDhcp = 3 + IpPrefixOriginRouterAdvertisement = 4 + IpPrefixOriginUnchanged = 1 << 4 +) + +// Enum NL_SUFFIX_ORIGIN for [IpAdapterUnicastAddress], see +// https://learn.microsoft.com/en-us/windows/win32/api/nldef/ne-nldef-nl_suffix_origin +const ( + NlsoOther = 0 + NlsoManual = 1 + NlsoWellKnown = 2 + NlsoDhcp = 3 + NlsoLinkLayerAddress = 4 + NlsoRandom = 5 + IpSuffixOriginOther = 0 + IpSuffixOriginManual = 1 + IpSuffixOriginWellKnown = 2 + IpSuffixOriginDhcp = 3 + IpSuffixOriginLinkLayerAddress = 4 + IpSuffixOriginRandom = 5 + IpSuffixOriginUnchanged = 1 << 4 +) + +// Enum NL_DAD_STATE for [IpAdapterUnicastAddress], see +// https://learn.microsoft.com/en-us/windows/win32/api/nldef/ne-nldef-nl_dad_state +const ( + NldsInvalid = 0 + NldsTentative = 1 + NldsDuplicate = 2 + NldsDeprecated = 3 + NldsPreferred = 4 + IpDadStateInvalid = 0 + IpDadStateTentative = 1 + IpDadStateDuplicate = 2 + IpDadStateDeprecated = 3 + IpDadStatePreferred = 4 +) + type SocketAddress struct { Sockaddr *syscall.RawSockaddrAny SockaddrLength int32 @@ -3404,3 +3462,14 @@ type DCB struct { EvtChar byte wReserved1 uint16 } + +// Keyboard Layout Flags. +// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadkeyboardlayoutw +const ( + KLF_ACTIVATE = 0x00000001 + KLF_SUBSTITUTE_OK = 0x00000002 + KLF_REORDER = 0x00000008 + KLF_REPLACELANG = 0x00000010 + KLF_NOTELLSHELL = 0x00000080 + KLF_SETFORPROCESS = 0x00000100 +) diff --git a/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/zsyscall_windows.go index eba76101..9bb979a3 100644 --- a/vendor/golang.org/x/sys/windows/zsyscall_windows.go +++ b/vendor/golang.org/x/sys/windows/zsyscall_windows.go @@ -478,12 +478,16 @@ var ( procGetDesktopWindow = moduser32.NewProc("GetDesktopWindow") procGetForegroundWindow = moduser32.NewProc("GetForegroundWindow") procGetGUIThreadInfo = moduser32.NewProc("GetGUIThreadInfo") + procGetKeyboardLayout = moduser32.NewProc("GetKeyboardLayout") procGetShellWindow = moduser32.NewProc("GetShellWindow") procGetWindowThreadProcessId = moduser32.NewProc("GetWindowThreadProcessId") procIsWindow = moduser32.NewProc("IsWindow") procIsWindowUnicode = moduser32.NewProc("IsWindowUnicode") procIsWindowVisible = moduser32.NewProc("IsWindowVisible") + procLoadKeyboardLayoutW = moduser32.NewProc("LoadKeyboardLayoutW") procMessageBoxW = moduser32.NewProc("MessageBoxW") + procToUnicodeEx = moduser32.NewProc("ToUnicodeEx") + procUnloadKeyboardLayout = moduser32.NewProc("UnloadKeyboardLayout") procCreateEnvironmentBlock = moduserenv.NewProc("CreateEnvironmentBlock") procDestroyEnvironmentBlock = moduserenv.NewProc("DestroyEnvironmentBlock") procGetUserProfileDirectoryW = moduserenv.NewProc("GetUserProfileDirectoryW") @@ -789,6 +793,14 @@ func FreeSid(sid *SID) (err error) { return } +func GetAce(acl *ACL, aceIndex uint32, pAce **ACCESS_ALLOWED_ACE) (err error) { + r1, _, e1 := syscall.Syscall(procGetAce.Addr(), 3, uintptr(unsafe.Pointer(acl)), uintptr(aceIndex), uintptr(unsafe.Pointer(pAce))) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func GetLengthSid(sid *SID) (len uint32) { r0, _, _ := syscall.Syscall(procGetLengthSid.Addr(), 1, uintptr(unsafe.Pointer(sid)), 0, 0) len = uint32(r0) @@ -1225,14 +1237,6 @@ func setEntriesInAcl(countExplicitEntries uint32, explicitEntries *EXPLICIT_ACCE return } -func GetAce(acl *ACL, aceIndex uint32, pAce **ACCESS_ALLOWED_ACE) (ret error) { - r0, _, _ := syscall.Syscall(procGetAce.Addr(), 3, uintptr(unsafe.Pointer(acl)), uintptr(aceIndex), uintptr(unsafe.Pointer(pAce))) - if r0 == 0 { - ret = GetLastError() - } - return -} - func SetKernelObjectSecurity(handle Handle, securityInformation SECURITY_INFORMATION, securityDescriptor *SECURITY_DESCRIPTOR) (err error) { r1, _, e1 := syscall.Syscall(procSetKernelObjectSecurity.Addr(), 3, uintptr(handle), uintptr(securityInformation), uintptr(unsafe.Pointer(securityDescriptor))) if r1 == 0 { @@ -4082,6 +4086,12 @@ func GetGUIThreadInfo(thread uint32, info *GUIThreadInfo) (err error) { return } +func GetKeyboardLayout(tid uint32) (hkl Handle) { + r0, _, _ := syscall.Syscall(procGetKeyboardLayout.Addr(), 1, uintptr(tid), 0, 0) + hkl = Handle(r0) + return +} + func GetShellWindow() (shellWindow HWND) { r0, _, _ := syscall.Syscall(procGetShellWindow.Addr(), 0, 0, 0, 0) shellWindow = HWND(r0) @@ -4115,6 +4125,15 @@ func IsWindowVisible(hwnd HWND) (isVisible bool) { return } +func LoadKeyboardLayout(name *uint16, flags uint32) (hkl Handle, err error) { + r0, _, e1 := syscall.Syscall(procLoadKeyboardLayoutW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(flags), 0) + hkl = Handle(r0) + if hkl == 0 { + err = errnoErr(e1) + } + return +} + func MessageBox(hwnd HWND, text *uint16, caption *uint16, boxtype uint32) (ret int32, err error) { r0, _, e1 := syscall.Syscall6(procMessageBoxW.Addr(), 4, uintptr(hwnd), uintptr(unsafe.Pointer(text)), uintptr(unsafe.Pointer(caption)), uintptr(boxtype), 0, 0) ret = int32(r0) @@ -4124,6 +4143,20 @@ func MessageBox(hwnd HWND, text *uint16, caption *uint16, boxtype uint32) (ret i return } +func ToUnicodeEx(vkey uint32, scancode uint32, keystate *byte, pwszBuff *uint16, cchBuff int32, flags uint32, hkl Handle) (ret int32) { + r0, _, _ := syscall.Syscall9(procToUnicodeEx.Addr(), 7, uintptr(vkey), uintptr(scancode), uintptr(unsafe.Pointer(keystate)), uintptr(unsafe.Pointer(pwszBuff)), uintptr(cchBuff), uintptr(flags), uintptr(hkl), 0, 0) + ret = int32(r0) + return +} + +func UnloadKeyboardLayout(hkl Handle) (err error) { + r1, _, e1 := syscall.Syscall(procUnloadKeyboardLayout.Addr(), 1, uintptr(hkl), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func CreateEnvironmentBlock(block **uint16, token Token, inheritExisting bool) (err error) { var _p0 uint32 if inheritExisting { diff --git a/vendor/golang.org/x/text/LICENSE b/vendor/golang.org/x/text/LICENSE index 6a66aea5..2a7cf70d 100644 --- a/vendor/golang.org/x/text/LICENSE +++ b/vendor/golang.org/x/text/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. +Copyright 2009 The Go Authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer. copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Google Inc. nor the names of its + * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/vendor/golang.org/x/time/LICENSE b/vendor/golang.org/x/time/LICENSE index 6a66aea5..2a7cf70d 100644 --- a/vendor/golang.org/x/time/LICENSE +++ b/vendor/golang.org/x/time/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. +Copyright 2009 The Go Authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer. copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Google Inc. nor the names of its + * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/vendor/golang.org/x/tools/LICENSE b/vendor/golang.org/x/tools/LICENSE index 6a66aea5..2a7cf70d 100644 --- a/vendor/golang.org/x/tools/LICENSE +++ b/vendor/golang.org/x/tools/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. +Copyright 2009 The Go Authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer. copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Google Inc. nor the names of its + * Neither the name of Google LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go index 34306ddd..0b6bfaff 100644 --- a/vendor/golang.org/x/tools/go/packages/packages.go +++ b/vendor/golang.org/x/tools/go/packages/packages.go @@ -46,7 +46,6 @@ import ( // // Unfortunately there are a number of open bugs related to // interactions among the LoadMode bits: -// - https://github.com/golang/go/issues/48226 // - https://github.com/golang/go/issues/56633 // - https://github.com/golang/go/issues/56677 // - https://github.com/golang/go/issues/58726 @@ -76,7 +75,7 @@ const ( // NeedTypes adds Types, Fset, and IllTyped. NeedTypes - // NeedSyntax adds Syntax. + // NeedSyntax adds Syntax and Fset. NeedSyntax // NeedTypesInfo adds TypesInfo. @@ -961,12 +960,14 @@ func (ld *loader) refine(response *DriverResponse) ([]*Package, error) { } if ld.requestedMode&NeedTypes == 0 { ld.pkgs[i].Types = nil - ld.pkgs[i].Fset = nil ld.pkgs[i].IllTyped = false } if ld.requestedMode&NeedSyntax == 0 { ld.pkgs[i].Syntax = nil } + if ld.requestedMode&NeedTypes == 0 && ld.requestedMode&NeedSyntax == 0 { + ld.pkgs[i].Fset = nil + } if ld.requestedMode&NeedTypesInfo == 0 { ld.pkgs[i].TypesInfo = nil } @@ -1499,6 +1500,10 @@ func impliedLoadMode(loadMode LoadMode) LoadMode { // All these things require knowing the import graph. loadMode |= NeedImports } + if loadMode&NeedTypes != 0 { + // Types require the GoVersion from Module. + loadMode |= NeedModule + } return loadMode } diff --git a/vendor/golang.org/x/tools/go/packages/visit.go b/vendor/golang.org/x/tools/go/packages/visit.go index a1dcc40b..df14ffd9 100644 --- a/vendor/golang.org/x/tools/go/packages/visit.go +++ b/vendor/golang.org/x/tools/go/packages/visit.go @@ -49,11 +49,20 @@ func Visit(pkgs []*Package, pre func(*Package) bool, post func(*Package)) { // PrintErrors returns the number of errors printed. func PrintErrors(pkgs []*Package) int { var n int + errModules := make(map[*Module]bool) Visit(pkgs, nil, func(pkg *Package) { for _, err := range pkg.Errors { fmt.Fprintln(os.Stderr, err) n++ } + + // Print pkg.Module.Error once if present. + mod := pkg.Module + if mod != nil && mod.Error != nil && !errModules[mod] { + errModules[mod] = true + fmt.Fprintln(os.Stderr, mod.Error.Err) + n++ + } }) return n } diff --git a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go index d648c3d0..9ada1777 100644 --- a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go +++ b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go @@ -51,7 +51,7 @@ type Path string // // PO package->object Package.Scope.Lookup // OT object->type Object.Type -// TT type->type Type.{Elem,Key,{,{,Recv}Type}Params,Results,Underlying} [EKPRUTrC] +// TT type->type Type.{Elem,Key,{,{,Recv}Type}Params,Results,Underlying,Rhs} [EKPRUTrCa] // TO type->object Type.{At,Field,Method,Obj} [AFMO] // // All valid paths start with a package and end at an object @@ -63,7 +63,7 @@ type Path string // - The only PO operator is Package.Scope.Lookup, which requires an identifier. // - The only OT operator is Object.Type, // which we encode as '.' because dot cannot appear in an identifier. -// - The TT operators are encoded as [EKPRUTrC]; +// - The TT operators are encoded as [EKPRUTrCa]; // two of these ({,Recv}TypeParams) require an integer operand, // which is encoded as a string of decimal digits. // - The TO operators are encoded as [AFMO]; @@ -106,6 +106,7 @@ const ( opTypeParam = 'T' // .TypeParams.At(i) (Named, Signature) opRecvTypeParam = 'r' // .RecvTypeParams.At(i) (Signature) opConstraint = 'C' // .Constraint() (TypeParam) + opRhs = 'a' // .Rhs() (Alias) // type->object operators opAt = 'A' // .At(i) (Tuple) @@ -279,21 +280,26 @@ func (enc *Encoder) For(obj types.Object) (Path, error) { path = append(path, opType) T := o.Type() + if alias, ok := T.(*aliases.Alias); ok { + if r := findTypeParam(obj, aliases.TypeParams(alias), path, opTypeParam, nil); r != nil { + return Path(r), nil + } + if r := find(obj, aliases.Rhs(alias), append(path, opRhs), nil); r != nil { + return Path(r), nil + } - if tname.IsAlias() { - // type alias + } else if tname.IsAlias() { + // legacy alias if r := find(obj, T, path, nil); r != nil { return Path(r), nil } - } else { - if named, _ := T.(*types.Named); named != nil { - if r := findTypeParam(obj, named.TypeParams(), path, opTypeParam, nil); r != nil { - // generic named type - return Path(r), nil - } - } + + } else if named, ok := T.(*types.Named); ok { // defined (named) type - if r := find(obj, T.Underlying(), append(path, opUnderlying), nil); r != nil { + if r := findTypeParam(obj, named.TypeParams(), path, opTypeParam, nil); r != nil { + return Path(r), nil + } + if r := find(obj, named.Underlying(), append(path, opUnderlying), nil); r != nil { return Path(r), nil } } @@ -657,6 +663,16 @@ func Object(pkg *types.Package, p Path) (types.Object, error) { } t = named.Underlying() + case opRhs: + if alias, ok := t.(*aliases.Alias); ok { + t = aliases.Rhs(alias) + } else if false && aliases.Enabled() { + // The Enabled check is too expensive, so for now we + // simply assume that aliases are not enabled. + // TODO(adonovan): replace with "if true {" when go1.24 is assured. + return nil, fmt.Errorf("cannot apply %q to %s (got %T, want alias)", code, t, t) + } + case opTypeParam: hasTypeParams, ok := t.(hasTypeParams) // Named, Signature if !ok { diff --git a/vendor/golang.org/x/tools/internal/aliases/aliases_go121.go b/vendor/golang.org/x/tools/internal/aliases/aliases_go121.go index c027b9f3..6652f7db 100644 --- a/vendor/golang.org/x/tools/internal/aliases/aliases_go121.go +++ b/vendor/golang.org/x/tools/internal/aliases/aliases_go121.go @@ -15,10 +15,14 @@ import ( // It will never be created by go/types. type Alias struct{} -func (*Alias) String() string { panic("unreachable") } -func (*Alias) Underlying() types.Type { panic("unreachable") } -func (*Alias) Obj() *types.TypeName { panic("unreachable") } -func Rhs(alias *Alias) types.Type { panic("unreachable") } +func (*Alias) String() string { panic("unreachable") } +func (*Alias) Underlying() types.Type { panic("unreachable") } +func (*Alias) Obj() *types.TypeName { panic("unreachable") } +func Rhs(alias *Alias) types.Type { panic("unreachable") } +func TypeParams(alias *Alias) *types.TypeParamList { panic("unreachable") } +func SetTypeParams(alias *Alias, tparams []*types.TypeParam) { panic("unreachable") } +func TypeArgs(alias *Alias) *types.TypeList { panic("unreachable") } +func Origin(alias *Alias) *Alias { panic("unreachable") } // Unalias returns the type t for go <=1.21. func Unalias(t types.Type) types.Type { return t } diff --git a/vendor/golang.org/x/tools/internal/aliases/aliases_go122.go b/vendor/golang.org/x/tools/internal/aliases/aliases_go122.go index b3299548..3ef1afeb 100644 --- a/vendor/golang.org/x/tools/internal/aliases/aliases_go122.go +++ b/vendor/golang.org/x/tools/internal/aliases/aliases_go122.go @@ -28,6 +28,42 @@ func Rhs(alias *Alias) types.Type { return Unalias(alias) } +// TypeParams returns the type parameter list of the alias. +func TypeParams(alias *Alias) *types.TypeParamList { + if alias, ok := any(alias).(interface{ TypeParams() *types.TypeParamList }); ok { + return alias.TypeParams() // go1.23+ + } + return nil +} + +// SetTypeParams sets the type parameters of the alias type. +func SetTypeParams(alias *Alias, tparams []*types.TypeParam) { + if alias, ok := any(alias).(interface { + SetTypeParams(tparams []*types.TypeParam) + }); ok { + alias.SetTypeParams(tparams) // go1.23+ + } else if len(tparams) > 0 { + panic("cannot set type parameters of an Alias type in go1.22") + } +} + +// TypeArgs returns the type arguments used to instantiate the Alias type. +func TypeArgs(alias *Alias) *types.TypeList { + if alias, ok := any(alias).(interface{ TypeArgs() *types.TypeList }); ok { + return alias.TypeArgs() // go1.23+ + } + return nil // empty (go1.22) +} + +// Origin returns the generic Alias type of which alias is an instance. +// If alias is not an instance of a generic alias, Origin returns alias. +func Origin(alias *Alias) *Alias { + if alias, ok := any(alias).(interface{ Origin() *types.Alias }); ok { + return alias.Origin() // go1.23+ + } + return alias // not an instance of a generic alias (go1.22) +} + // Unalias is a wrapper of types.Unalias. func Unalias(t types.Type) types.Type { return types.Unalias(t) } diff --git a/vendor/golang.org/x/tools/internal/pkgbits/decoder.go b/vendor/golang.org/x/tools/internal/pkgbits/decoder.go index 2acd8585..b92e8e6e 100644 --- a/vendor/golang.org/x/tools/internal/pkgbits/decoder.go +++ b/vendor/golang.org/x/tools/internal/pkgbits/decoder.go @@ -23,9 +23,6 @@ type PkgDecoder struct { // version is the file format version. version uint32 - // aliases determines whether types.Aliases should be created - aliases bool - // sync indicates whether the file uses sync markers. sync bool @@ -76,7 +73,6 @@ func (pr *PkgDecoder) SyncMarkers() bool { return pr.sync } func NewPkgDecoder(pkgPath, input string) PkgDecoder { pr := PkgDecoder{ pkgPath: pkgPath, - //aliases: aliases.Enabled(), } // TODO(mdempsky): Implement direct indexing of input string to diff --git a/vendor/golang.org/x/tools/internal/versions/constraint.go b/vendor/golang.org/x/tools/internal/versions/constraint.go new file mode 100644 index 00000000..179063d4 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/versions/constraint.go @@ -0,0 +1,13 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package versions + +import "go/build/constraint" + +// ConstraintGoVersion is constraint.GoVersion (if built with go1.21+). +// Otherwise nil. +// +// Deprecate once x/tools is after go1.21. +var ConstraintGoVersion func(x constraint.Expr) string diff --git a/vendor/golang.org/x/tools/internal/versions/constraint_go121.go b/vendor/golang.org/x/tools/internal/versions/constraint_go121.go new file mode 100644 index 00000000..38011407 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/versions/constraint_go121.go @@ -0,0 +1,14 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.21 +// +build go1.21 + +package versions + +import "go/build/constraint" + +func init() { + ConstraintGoVersion = constraint.GoVersion +} diff --git a/vendor/modules.txt b/vendor/modules.txt index ff1e8cfe..8fb33cf5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -30,7 +30,7 @@ github.com/KyleBanks/depth # github.com/Masterminds/semver/v3 v3.2.1 ## explicit; go 1.18 github.com/Masterminds/semver/v3 -# github.com/adhocore/gronx v1.8.1 +# github.com/adhocore/gronx v1.19.0 ## explicit; go 1.13 github.com/adhocore/gronx # github.com/agnivade/levenshtein v1.1.1 @@ -67,7 +67,7 @@ github.com/cespare/xxhash/v2 # github.com/cpuguy83/go-md2man/v2 v2.0.4 ## explicit; go 1.11 github.com/cpuguy83/go-md2man/v2/md2man -# github.com/datarhei/gosrt v0.6.0 +# github.com/datarhei/gosrt v0.7.0 ## explicit; go 1.20 github.com/datarhei/gosrt github.com/datarhei/gosrt/circular @@ -115,7 +115,7 @@ github.com/fatih/color # github.com/fujiwara/shapeio v1.0.0 ## explicit; go 1.16 github.com/fujiwara/shapeio -# github.com/gabriel-vasile/mimetype v1.4.4 +# github.com/gabriel-vasile/mimetype v1.4.5 ## explicit; go 1.20 github.com/gabriel-vasile/mimetype github.com/gabriel-vasile/mimetype/internal/charset @@ -284,19 +284,20 @@ github.com/mattn/go-colorable # github.com/mattn/go-isatty v0.0.20 ## explicit; go 1.15 github.com/mattn/go-isatty -# github.com/mholt/acmez/v2 v2.0.1 +# github.com/mholt/acmez/v2 v2.0.2 ## explicit; go 1.20 github.com/mholt/acmez/v2 github.com/mholt/acmez/v2/acme -# github.com/miekg/dns v1.1.61 +# github.com/miekg/dns v1.1.62 ## explicit; go 1.19 github.com/miekg/dns # github.com/minio/md5-simd v1.1.2 ## explicit; go 1.14 github.com/minio/md5-simd -# github.com/minio/minio-go/v7 v7.0.74 +# github.com/minio/minio-go/v7 v7.0.75 ## explicit; go 1.21 github.com/minio/minio-go/v7 +github.com/minio/minio-go/v7/pkg/cors github.com/minio/minio-go/v7/pkg/credentials github.com/minio/minio-go/v7/pkg/encrypt github.com/minio/minio-go/v7/pkg/lifecycle @@ -325,8 +326,10 @@ github.com/power-devops/perfstat # github.com/prep/average v0.0.0-20200506183628-d26c465f48c3 ## explicit github.com/prep/average -# github.com/prometheus/client_golang v1.19.1 +# github.com/prometheus/client_golang v1.20.0 ## explicit; go 1.20 +github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil +github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header github.com/prometheus/client_golang/prometheus github.com/prometheus/client_golang/prometheus/internal github.com/prometheus/client_golang/prometheus/promhttp @@ -418,8 +421,8 @@ github.com/xrash/smetrics # github.com/yusufpapurcu/wmi v1.2.4 ## explicit; go 1.16 github.com/yusufpapurcu/wmi -# github.com/zeebo/blake3 v0.2.3 -## explicit; go 1.13 +# github.com/zeebo/blake3 v0.2.4 +## explicit; go 1.18 github.com/zeebo/blake3 github.com/zeebo/blake3/internal/alg github.com/zeebo/blake3/internal/alg/compress @@ -452,7 +455,7 @@ go.uber.org/zap/internal/exit go.uber.org/zap/internal/pool go.uber.org/zap/internal/stacktrace go.uber.org/zap/zapcore -# golang.org/x/crypto v0.25.0 +# golang.org/x/crypto v0.26.0 ## explicit; go 1.20 golang.org/x/crypto/acme golang.org/x/crypto/acme/autocert @@ -464,12 +467,12 @@ golang.org/x/crypto/ocsp golang.org/x/crypto/pbkdf2 golang.org/x/crypto/scrypt golang.org/x/crypto/sha3 -# golang.org/x/mod v0.19.0 +# golang.org/x/mod v0.20.0 ## explicit; go 1.18 golang.org/x/mod/internal/lazyregexp golang.org/x/mod/module golang.org/x/mod/semver -# golang.org/x/net v0.27.0 +# golang.org/x/net v0.28.0 ## explicit; go 1.18 golang.org/x/net/bpf golang.org/x/net/html @@ -484,16 +487,16 @@ golang.org/x/net/internal/socket golang.org/x/net/ipv4 golang.org/x/net/ipv6 golang.org/x/net/publicsuffix -# golang.org/x/sync v0.7.0 +# golang.org/x/sync v0.8.0 ## explicit; go 1.18 golang.org/x/sync/errgroup -# golang.org/x/sys v0.22.0 +# golang.org/x/sys v0.24.0 ## explicit; go 1.18 golang.org/x/sys/cpu golang.org/x/sys/unix golang.org/x/sys/windows golang.org/x/sys/windows/registry -# golang.org/x/text v0.16.0 +# golang.org/x/text v0.17.0 ## explicit; go 1.18 golang.org/x/text/cases golang.org/x/text/internal @@ -505,10 +508,10 @@ golang.org/x/text/secure/bidirule golang.org/x/text/transform golang.org/x/text/unicode/bidi golang.org/x/text/unicode/norm -# golang.org/x/time v0.5.0 +# golang.org/x/time v0.6.0 ## explicit; go 1.18 golang.org/x/time/rate -# golang.org/x/tools v0.23.0 +# golang.org/x/tools v0.24.0 ## explicit; go 1.19 golang.org/x/tools/go/ast/astutil golang.org/x/tools/go/buildutil From 1650b17e0505ca93654c4e459968d82ae1a5559f Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Mon, 19 Aug 2024 15:21:24 +0200 Subject: [PATCH 03/64] Simply return error as-is, check process list length --- cluster/node/manager.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/cluster/node/manager.go b/cluster/node/manager.go index 5518a59e..cc1072ea 100644 --- a/cluster/node/manager.go +++ b/cluster/node/manager.go @@ -154,7 +154,7 @@ func (p *Manager) NodeGet(id string) (*Node, error) { node, ok := p.nodes[id] if !ok { - return nil, fmt.Errorf("node not found") + return nil, fmt.Errorf("node not found: %s", id) } return node, nil @@ -538,7 +538,7 @@ func (p *Manager) ProcessList(options client.ProcessListOptions) []api.Process { func (p *Manager) ProcessGet(nodeid string, id app.ProcessID, filter []string) (api.Process, error) { node, err := p.NodeGet(nodeid) if err != nil { - return api.Process{}, fmt.Errorf("node not found: %w", err) + return api.Process{}, err } list, err := node.Core().ProcessList(client.ProcessListOptions{ @@ -550,13 +550,17 @@ func (p *Manager) ProcessGet(nodeid string, id app.ProcessID, filter []string) ( return api.Process{}, err } + if len(list) == 0 { + return api.Process{}, fmt.Errorf("process not found") + } + return list[0], nil } func (p *Manager) ProcessAdd(nodeid string, config *app.Config, metadata map[string]interface{}) error { node, err := p.NodeGet(nodeid) if err != nil { - return fmt.Errorf("node not found: %w", err) + return err } return node.Core().ProcessAdd(config, metadata) @@ -565,7 +569,7 @@ func (p *Manager) ProcessAdd(nodeid string, config *app.Config, metadata map[str func (p *Manager) ProcessDelete(nodeid string, id app.ProcessID) error { node, err := p.NodeGet(nodeid) if err != nil { - return fmt.Errorf("node not found: %w", err) + return err } return node.Core().ProcessDelete(id) @@ -574,7 +578,7 @@ func (p *Manager) ProcessDelete(nodeid string, id app.ProcessID) error { func (p *Manager) ProcessUpdate(nodeid string, id app.ProcessID, config *app.Config, metadata map[string]interface{}) error { node, err := p.NodeGet(nodeid) if err != nil { - return fmt.Errorf("node not found: %w", err) + return err } return node.Core().ProcessUpdate(id, config, metadata) @@ -583,7 +587,7 @@ func (p *Manager) ProcessUpdate(nodeid string, id app.ProcessID, config *app.Con func (p *Manager) ProcessReportSet(nodeid string, id app.ProcessID, report *app.Report) error { node, err := p.NodeGet(nodeid) if err != nil { - return fmt.Errorf("node not found: %w", err) + return err } return node.Core().ProcessReportSet(id, report) @@ -592,7 +596,7 @@ func (p *Manager) ProcessReportSet(nodeid string, id app.ProcessID, report *app. func (p *Manager) ProcessCommand(nodeid string, id app.ProcessID, command string) error { node, err := p.NodeGet(nodeid) if err != nil { - return fmt.Errorf("node not found: %w", err) + return err } return node.Core().ProcessCommand(id, command) @@ -604,7 +608,7 @@ func (p *Manager) ProcessProbe(nodeid string, id app.ProcessID) (api.Probe, erro probe := api.Probe{ Log: []string{fmt.Sprintf("the node %s where the process %s should reside on, doesn't exist", nodeid, id.String())}, } - return probe, fmt.Errorf("node not found: %w", err) + return probe, err } return node.Core().ProcessProbe(id) @@ -616,7 +620,7 @@ func (p *Manager) ProcessProbeConfig(nodeid string, config *app.Config) (api.Pro probe := api.Probe{ Log: []string{fmt.Sprintf("the node %s where the process config should be probed on, doesn't exist", nodeid)}, } - return probe, fmt.Errorf("node not found: %w", err) + return probe, err } return node.Core().ProcessProbeConfig(config) From 0b1601542d698fc81f163d31d461eaf97004e554 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Mon, 19 Aug 2024 15:22:24 +0200 Subject: [PATCH 04/64] Wait for follower and leader loops to finish --- cluster/cluster.go | 4 ---- cluster/leader.go | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/cluster/cluster.go b/cluster/cluster.go index 590b8c06..69551277 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -648,10 +648,6 @@ func (c *cluster) Shutdown() error { c.raft.Shutdown() } - // TODO: here might some situations, where the manager is still need from the synchronize loop and will run into a panic - c.manager = nil - c.raft = nil - return nil } diff --git a/cluster/leader.go b/cluster/leader.go index 60ab84d4..9d555f0e 100644 --- a/cluster/leader.go +++ b/cluster/leader.go @@ -180,6 +180,22 @@ func (c *cluster) monitorLeadership() { c.leaderLock.Unlock() } case <-c.shutdownCh: + if weAreFollowerCh != nil { + close(weAreFollowerCh) + } + + if weAreLeaderCh != nil { + close(weAreLeaderCh) + } + + if weAreEmergencyLeaderCh != nil { + close(weAreEmergencyLeaderCh) + } + + leaderLoop.Wait() + emergencyLeaderLoop.Wait() + followerLoop.Wait() + return } } From 68607ed932c091ba802abd53bc04f3b97d17babf Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Tue, 20 Aug 2024 11:55:08 +0200 Subject: [PATCH 05/64] Add basic resource information in about response --- app/api/api.go | 1 + http/api/about.go | 40 +++++++++++++++++++++----------- http/handler/api/about.go | 30 ++++++++++++++++++------ http/handler/api/about_test.go | 2 +- http/handler/api/cluster_node.go | 2 +- http/server.go | 3 +++ 6 files changed, 56 insertions(+), 22 deletions(-) diff --git a/app/api/api.go b/app/api/api.go index 90ad6ea5..da5aeeac 100644 --- a/app/api/api.go +++ b/app/api/api.go @@ -1500,6 +1500,7 @@ func (a *api) start(ctx context.Context) error { return false }, + Resources: a.resources, } mainserverhandler, err := http.NewServer(serverConfig) diff --git a/http/api/about.go b/http/api/about.go index 0203ae1d..5887423a 100644 --- a/http/api/about.go +++ b/http/api/about.go @@ -2,17 +2,18 @@ package api // About is some general information about the API type About struct { - App string `json:"app"` - Auths []string `json:"auths"` - Name string `json:"name"` - ID string `json:"id"` - CreatedAt string `json:"created_at"` // RFC3339 - Uptime uint64 `json:"uptime_seconds"` - Version Version `json:"version"` + App string `json:"app"` + Auths []string `json:"auths"` + Name string `json:"name"` + ID string `json:"id"` + CreatedAt string `json:"created_at"` // RFC3339 + Uptime uint64 `json:"uptime_seconds"` + Version AboutVersion `json:"version"` + Resources AboutResources `json:"resources"` } -// Version is some information about the binary -type Version struct { +// AboutVersion is some information about the binary +type AboutVersion struct { Number string `json:"number"` Commit string `json:"repository_commit"` Branch string `json:"repository_branch"` @@ -21,13 +22,26 @@ type Version struct { Compiler string `json:"compiler"` } +// AboutResources holds information about the current resource usage +type AboutResources struct { + IsThrottling bool // Whether this core is currently throttling + NCPU float64 // Number of CPU on this node + CPU float64 // Current CPU load, 0-100*ncpu + CPULimit float64 // Defined CPU load limit, 0-100*ncpu + CPUCore float64 // Current CPU load of the core itself, 0-100*ncpu + Mem uint64 // Currently used memory in bytes + MemLimit uint64 // Defined memory limit in bytes + MemTotal uint64 // Total available memory in bytes + MemCore uint64 // Current used memory of the core itself in bytes +} + // MinimalAbout is the minimal information about the API type MinimalAbout struct { - App string `json:"app"` - Auths []string `json:"auths"` - Version VersionMinimal `json:"version"` + App string `json:"app"` + Auths []string `json:"auths"` + Version AboutVersionMinimal `json:"version"` } -type VersionMinimal struct { +type AboutVersionMinimal struct { Number string `json:"number"` } diff --git a/http/handler/api/about.go b/http/handler/api/about.go index a9add8ca..cba674dd 100644 --- a/http/handler/api/about.go +++ b/http/handler/api/about.go @@ -6,6 +6,7 @@ import ( "github.com/datarhei/core/v16/app" "github.com/datarhei/core/v16/http/api" + "github.com/datarhei/core/v16/resources" "github.com/datarhei/core/v16/restream" "github.com/labstack/echo/v4" @@ -14,15 +15,17 @@ import ( // The AboutHandler type provides handler functions for retrieving details // about the API version and build infos. type AboutHandler struct { - restream restream.Restreamer - auths func() []string + restream restream.Restreamer + resources resources.Resources + auths func() []string } // NewAbout returns a new About type -func NewAbout(restream restream.Restreamer, auths func() []string) *AboutHandler { +func NewAbout(restream restream.Restreamer, resources resources.Resources, auths func() []string) *AboutHandler { return &AboutHandler{ - restream: restream, - auths: auths, + restream: restream, + resources: resources, + auths: auths, } } @@ -41,7 +44,7 @@ func (p *AboutHandler) About(c echo.Context) error { return c.JSON(http.StatusOK, api.MinimalAbout{ App: app.Name, Auths: p.auths(), - Version: api.VersionMinimal{ + Version: api.AboutVersionMinimal{ Number: app.Version.MajorString(), }, }) @@ -56,7 +59,7 @@ func (p *AboutHandler) About(c echo.Context) error { ID: p.restream.ID(), CreatedAt: createdAt.Format(time.RFC3339), Uptime: uint64(time.Since(createdAt).Seconds()), - Version: api.Version{ + Version: api.AboutVersion{ Number: app.Version.String(), Commit: app.Commit, Branch: app.Branch, @@ -66,5 +69,18 @@ func (p *AboutHandler) About(c echo.Context) error { }, } + if p.resources != nil { + res := p.resources.Info() + about.Resources.IsThrottling = res.CPU.Throttling + about.Resources.NCPU = res.CPU.NCPU + about.Resources.CPU = (100 - res.CPU.Idle) * res.CPU.NCPU + about.Resources.CPULimit = res.CPU.Limit * res.CPU.NCPU + about.Resources.CPUCore = res.CPU.Core * res.CPU.NCPU + about.Resources.Mem = res.Mem.Total - res.Mem.Available + about.Resources.MemLimit = res.Mem.Limit + about.Resources.MemTotal = res.Mem.Total + about.Resources.MemCore = res.Mem.Core + } + return c.JSON(http.StatusOK, about) } diff --git a/http/handler/api/about_test.go b/http/handler/api/about_test.go index a2f039b5..192b5e98 100644 --- a/http/handler/api/about_test.go +++ b/http/handler/api/about_test.go @@ -19,7 +19,7 @@ func getDummyAboutRouter() (*echo.Echo, error) { return nil, err } - handler := NewAbout(rs, func() []string { return []string{} }) + handler := NewAbout(rs, nil, func() []string { return []string{} }) router.Add("GET", "/", handler.About) diff --git a/http/handler/api/cluster_node.go b/http/handler/api/cluster_node.go index c3bfee05..51f4c463 100644 --- a/http/handler/api/cluster_node.go +++ b/http/handler/api/cluster_node.go @@ -100,7 +100,7 @@ func (h *ClusterHandler) NodeGetVersion(c echo.Context) error { v := peer.CoreAbout() - version := api.Version{ + version := api.AboutVersion{ Number: v.Version.Number, Commit: v.Version.Commit, Branch: v.Version.Branch, diff --git a/http/server.go b/http/server.go index adde251a..959165bd 100644 --- a/http/server.go +++ b/http/server.go @@ -51,6 +51,7 @@ import ( "github.com/datarhei/core/v16/monitor" "github.com/datarhei/core/v16/net" "github.com/datarhei/core/v16/prometheus" + "github.com/datarhei/core/v16/resources" "github.com/datarhei/core/v16/restream" "github.com/datarhei/core/v16/rtmp" "github.com/datarhei/core/v16/session" @@ -100,6 +101,7 @@ type Config struct { Cluster cluster.Cluster IAM iam.IAM IAMSkipper func(ip string) bool + Resources resources.Resources } type CorsConfig struct { @@ -251,6 +253,7 @@ func NewServer(config Config) (serverhandler.Server, error) { s.handler.about = api.NewAbout( config.Restream, + config.Resources, func() []string { return config.IAM.Validators() }, ) From 1327fd6e2114304a1acc8415efe069520fa6ca86 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Tue, 20 Aug 2024 14:14:47 +0200 Subject: [PATCH 06/64] Add memfs storage based on dolthub's swiss maps --- go.mod | 2 + go.sum | 4 + io/fs/fs_test.go | 10 +- io/fs/mem.go | 2 + io/fs/mem_storage.go | 89 +++++ io/fs/mem_test.go | 108 +++--- vendor/github.com/dolthub/maphash/.gitignore | 2 + vendor/github.com/dolthub/maphash/LICENSE | 201 ++++++++++ vendor/github.com/dolthub/maphash/README.md | 4 + vendor/github.com/dolthub/maphash/hasher.go | 48 +++ vendor/github.com/dolthub/maphash/runtime.go | 111 ++++++ vendor/github.com/dolthub/swiss/.gitignore | 5 + vendor/github.com/dolthub/swiss/LICENSE | 201 ++++++++++ vendor/github.com/dolthub/swiss/README.md | 54 +++ vendor/github.com/dolthub/swiss/bits.go | 58 +++ vendor/github.com/dolthub/swiss/bits_amd64.go | 50 +++ vendor/github.com/dolthub/swiss/map.go | 359 ++++++++++++++++++ vendor/github.com/dolthub/swiss/simd/match.s | 19 + .../dolthub/swiss/simd/match_amd64.go | 9 + vendor/modules.txt | 7 + 20 files changed, 1293 insertions(+), 50 deletions(-) create mode 100644 vendor/github.com/dolthub/maphash/.gitignore create mode 100644 vendor/github.com/dolthub/maphash/LICENSE create mode 100644 vendor/github.com/dolthub/maphash/README.md create mode 100644 vendor/github.com/dolthub/maphash/hasher.go create mode 100644 vendor/github.com/dolthub/maphash/runtime.go create mode 100644 vendor/github.com/dolthub/swiss/.gitignore create mode 100644 vendor/github.com/dolthub/swiss/LICENSE create mode 100644 vendor/github.com/dolthub/swiss/README.md create mode 100644 vendor/github.com/dolthub/swiss/bits.go create mode 100644 vendor/github.com/dolthub/swiss/bits_amd64.go create mode 100644 vendor/github.com/dolthub/swiss/map.go create mode 100644 vendor/github.com/dolthub/swiss/simd/match.s create mode 100644 vendor/github.com/dolthub/swiss/simd/match_amd64.go diff --git a/go.mod b/go.mod index 50e55ef3..c9b6f090 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/caddyserver/certmagic v0.21.3 github.com/datarhei/gosrt v0.7.0 github.com/datarhei/joy4 v0.0.0-20240603190808-b1407345907e + github.com/dolthub/swiss v0.2.1 github.com/fujiwara/shapeio v1.0.0 github.com/go-playground/validator/v10 v10.22.0 github.com/gobwas/glob v0.2.3 @@ -61,6 +62,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dolthub/maphash v0.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/color v1.17.0 // indirect github.com/gabriel-vasile/mimetype v1.4.5 // indirect diff --git a/go.sum b/go.sum index 4213378d..381e7d01 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ= +github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4= +github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw= +github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= diff --git a/io/fs/fs_test.go b/io/fs/fs_test.go index 9d52dddb..7e7c2362 100644 --- a/io/fs/fs_test.go +++ b/io/fs/fs_test.go @@ -59,8 +59,14 @@ func TestFilesystem(t *testing.T) { os.RemoveAll("./testing/") filesystems := map[string]func(string) (Filesystem, error){ - "memfs": func(name string) (Filesystem, error) { - return NewMemFilesystem(MemConfig{}) + "memfs-map": func(name string) (Filesystem, error) { + return NewMemFilesystem(MemConfig{Storage: "map"}) + }, + "memfs-xsync": func(name string) (Filesystem, error) { + return NewMemFilesystem(MemConfig{Storage: "xsync"}) + }, + "memfs-swiss": func(name string) (Filesystem, error) { + return NewMemFilesystem(MemConfig{Storage: "swiss"}) }, "diskfs": func(name string) (Filesystem, error) { return NewRootedDiskFilesystem(RootedDiskConfig{ diff --git a/io/fs/mem.go b/io/fs/mem.go index 2033d349..dee83c78 100644 --- a/io/fs/mem.go +++ b/io/fs/mem.go @@ -208,6 +208,8 @@ func NewMemFilesystem(config MemConfig) (Filesystem, error) { if config.Storage == "map" { fs.storage = newMapStorage() + } else if config.Storage == "swiss" { + fs.storage = newSwissMapStorage() } else { fs.storage = newMapOfStorage() } diff --git a/io/fs/mem_storage.go b/io/fs/mem_storage.go index e6bc2362..5df8f946 100644 --- a/io/fs/mem_storage.go +++ b/io/fs/mem_storage.go @@ -4,6 +4,7 @@ import ( "bytes" "sync" + "github.com/dolthub/swiss" "github.com/puzpuzpuz/xsync/v3" ) @@ -182,3 +183,91 @@ func (m *mapStorage) Range(f func(key string, value *memFile) bool) { } } } + +type swissMapStorage struct { + lock *xsync.RBMutex + files *swiss.Map[string, *memFile] +} + +func newSwissMapStorage() memStorage { + m := &swissMapStorage{ + lock: xsync.NewRBMutex(), + files: swiss.NewMap[string, *memFile](128), + } + + return m +} + +func (m *swissMapStorage) Delete(key string) (*memFile, bool) { + m.lock.Lock() + defer m.lock.Unlock() + + file, hasFile := m.files.Get(key) + if !hasFile { + return nil, false + } + + m.files.Delete(key) + + return file, true +} + +func (m *swissMapStorage) Store(key string, value *memFile) (*memFile, bool) { + m.lock.Lock() + defer m.lock.Unlock() + + file, hasFile := m.files.Get(key) + m.files.Put(key, value) + + return file, hasFile +} + +func (m *swissMapStorage) Load(key string) (*memFile, bool) { + token := m.lock.RLock() + defer m.lock.RUnlock(token) + + return m.files.Get(key) +} + +func (m *swissMapStorage) LoadAndCopy(key string) (*memFile, bool) { + token := m.lock.RLock() + defer m.lock.RUnlock(token) + + v, ok := m.files.Get(key) + if !ok { + return nil, false + } + + f := &memFile{ + memFileInfo: memFileInfo{ + name: v.name, + size: v.size, + dir: v.dir, + lastMod: v.lastMod, + linkTo: v.linkTo, + }, + r: nil, + } + + if v.data != nil { + f.data = bytes.NewBuffer(v.data.Bytes()) + } + + return f, true +} + +func (m *swissMapStorage) Has(key string) bool { + token := m.lock.RLock() + defer m.lock.RUnlock(token) + + return m.files.Has(key) +} + +func (m *swissMapStorage) Range(f func(key string, value *memFile) bool) { + token := m.lock.RLock() + defer m.lock.RUnlock(token) + + m.files.Iter(func(key string, value *memFile) bool { + return !f(key, value) + }) +} diff --git a/io/fs/mem_test.go b/io/fs/mem_test.go index 0f8c1758..1e9f43fb 100644 --- a/io/fs/mem_test.go +++ b/io/fs/mem_test.go @@ -30,53 +30,89 @@ func TestMemFromDir(t *testing.T) { }, names) } -func BenchmarkMemList(b *testing.B) { - mem, err := NewMemFilesystem(MemConfig{}) - require.NoError(b, err) +func TestWriteWhileRead(t *testing.T) { + fs, err := NewMemFilesystem(MemConfig{}) + require.NoError(t, err) + + _, _, err = fs.WriteFile("/foobar", []byte("xxxxx")) + require.NoError(t, err) + + file := fs.Open("/foobar") + require.NotNil(t, file) + + _, _, err = fs.WriteFile("/foobar", []byte("yyyyy")) + require.NoError(t, err) + data, err := io.ReadAll(file) + require.NoError(t, err) + require.Equal(t, []byte("xxxxx"), data) +} + +func BenchmarkMemStorages(b *testing.B) { + storages := []string{ + "map", + "xsync", + "swiss", + } + + benchmarks := map[string]func(*testing.B, Filesystem){ + "list": benchmarkMemList, + "removeList": benchmarkMemRemoveList, + "readFile": benchmarkMemReadFile, + "writeFile": benchmarkMemWriteFile, + "readWhileWrite": benchmarkMemReadFileWhileWriting, + } + + for name, fn := range benchmarks { + for _, storage := range storages { + mem, err := NewMemFilesystem(MemConfig{Storage: storage}) + require.NoError(b, err) + + b.Run(name+"-"+storage, func(b *testing.B) { + fn(b, mem) + }) + } + } +} + +func benchmarkMemList(b *testing.B, fs Filesystem) { for i := 0; i < 1000; i++ { id := rand.StringAlphanumeric(8) path := fmt.Sprintf("/%d/%s.dat", i, id) - mem.WriteFile(path, []byte("foobar")) + fs.WriteFile(path, []byte("foobar")) } b.ResetTimer() for i := 0; i < b.N; i++ { - mem.List("/", ListOptions{ + fs.List("/", ListOptions{ Pattern: "/5/**", }) } } -func BenchmarkMemRemoveList(b *testing.B) { - mem, err := NewMemFilesystem(MemConfig{}) - require.NoError(b, err) - +func benchmarkMemRemoveList(b *testing.B, fs Filesystem) { for i := 0; i < 1000; i++ { id := rand.StringAlphanumeric(8) path := fmt.Sprintf("/%d/%s.dat", i, id) - mem.WriteFile(path, []byte("foobar")) + fs.WriteFile(path, []byte("foobar")) } b.ResetTimer() for i := 0; i < b.N; i++ { - mem.RemoveList("/", ListOptions{ + fs.RemoveList("/", ListOptions{ Pattern: "/5/**", }) } } -func BenchmarkMemReadFile(b *testing.B) { - mem, err := NewMemFilesystem(MemConfig{}) - require.NoError(b, err) - +func benchmarkMemReadFile(b *testing.B, fs Filesystem) { nFiles := 1000 for i := 0; i < nFiles; i++ { path := fmt.Sprintf("/%d.dat", i) - mem.WriteFile(path, []byte(rand.StringAlphanumeric(2*1024))) + fs.WriteFile(path, []byte(rand.StringAlphanumeric(2*1024))) } r := gorand.New(gorand.NewSource(42)) @@ -85,52 +121,28 @@ func BenchmarkMemReadFile(b *testing.B) { for i := 0; i < b.N; i++ { num := r.Intn(nFiles) - f := mem.Open("/" + strconv.Itoa(num) + ".dat") + f := fs.Open("/" + strconv.Itoa(num) + ".dat") f.Close() } } -func TestWriteWhileRead(t *testing.T) { - fs, err := NewMemFilesystem(MemConfig{}) - require.NoError(t, err) - - _, _, err = fs.WriteFile("/foobar", []byte("xxxxx")) - require.NoError(t, err) - - file := fs.Open("/foobar") - require.NotNil(t, file) - - _, _, err = fs.WriteFile("/foobar", []byte("yyyyy")) - require.NoError(t, err) - - data, err := io.ReadAll(file) - require.NoError(t, err) - require.Equal(t, []byte("xxxxx"), data) -} - -func BenchmarkMemWriteFile(b *testing.B) { - mem, err := NewMemFilesystem(MemConfig{}) - require.NoError(b, err) - +func benchmarkMemWriteFile(b *testing.B, fs Filesystem) { nFiles := 50000 for i := 0; i < nFiles; i++ { path := fmt.Sprintf("/%d.dat", i) - mem.WriteFile(path, []byte(rand.StringAlphanumeric(1))) + fs.WriteFile(path, []byte(rand.StringAlphanumeric(1))) } b.ResetTimer() for i := 0; i < b.N; i++ { path := fmt.Sprintf("/%d.dat", i%nFiles) - mem.WriteFile(path, []byte(rand.StringAlphanumeric(1))) + fs.WriteFile(path, []byte(rand.StringAlphanumeric(1))) } } -func BenchmarkMemReadFileWhileWriting(b *testing.B) { - mem, err := NewMemFilesystem(MemConfig{}) - require.NoError(b, err) - +func benchmarkMemReadFileWhileWriting(b *testing.B, fs Filesystem) { nReaders := 500 nWriters := 1000 nFiles := 30 @@ -148,7 +160,7 @@ func BenchmarkMemReadFileWhileWriting(b *testing.B) { go func(ctx context.Context, from int) { for i := 0; i < nFiles; i++ { path := fmt.Sprintf("/%d.dat", from+i) - mem.WriteFile(path, data) + fs.WriteFile(path, data) } ticker := time.NewTicker(40 * time.Millisecond) @@ -163,7 +175,7 @@ func BenchmarkMemReadFileWhileWriting(b *testing.B) { case <-ticker.C: num := gorand.Intn(nFiles) + from path := fmt.Sprintf("/%d.dat", num) - mem.WriteFile(path, data) + fs.WriteFile(path, data) } } }(ctx, i*nFiles) @@ -183,7 +195,7 @@ func BenchmarkMemReadFileWhileWriting(b *testing.B) { for i := 0; i < b.N; i++ { num := gorand.Intn(nWriters * nFiles) - f := mem.Open("/" + strconv.Itoa(num) + ".dat") + f := fs.Open("/" + strconv.Itoa(num) + ".dat") f.Close() } }() diff --git a/vendor/github.com/dolthub/maphash/.gitignore b/vendor/github.com/dolthub/maphash/.gitignore new file mode 100644 index 00000000..977a7cad --- /dev/null +++ b/vendor/github.com/dolthub/maphash/.gitignore @@ -0,0 +1,2 @@ +*.idea +*.test \ No newline at end of file diff --git a/vendor/github.com/dolthub/maphash/LICENSE b/vendor/github.com/dolthub/maphash/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/vendor/github.com/dolthub/maphash/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/dolthub/maphash/README.md b/vendor/github.com/dolthub/maphash/README.md new file mode 100644 index 00000000..d91530f9 --- /dev/null +++ b/vendor/github.com/dolthub/maphash/README.md @@ -0,0 +1,4 @@ +# maphash + +Hash any `comparable` type using Golang's fast runtime hash. +Uses [AES](https://en.wikipedia.org/wiki/AES_instruction_set) instructions when available. \ No newline at end of file diff --git a/vendor/github.com/dolthub/maphash/hasher.go b/vendor/github.com/dolthub/maphash/hasher.go new file mode 100644 index 00000000..ef53596a --- /dev/null +++ b/vendor/github.com/dolthub/maphash/hasher.go @@ -0,0 +1,48 @@ +// Copyright 2022 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package maphash + +import "unsafe" + +// Hasher hashes values of type K. +// Uses runtime AES-based hashing. +type Hasher[K comparable] struct { + hash hashfn + seed uintptr +} + +// NewHasher creates a new Hasher[K] with a random seed. +func NewHasher[K comparable]() Hasher[K] { + return Hasher[K]{ + hash: getRuntimeHasher[K](), + seed: newHashSeed(), + } +} + +// NewSeed returns a copy of |h| with a new hash seed. +func NewSeed[K comparable](h Hasher[K]) Hasher[K] { + return Hasher[K]{ + hash: h.hash, + seed: newHashSeed(), + } +} + +// Hash hashes |key|. +func (h Hasher[K]) Hash(key K) uint64 { + // promise to the compiler that pointer + // |p| does not escape the stack. + p := noescape(unsafe.Pointer(&key)) + return uint64(h.hash(p, h.seed)) +} diff --git a/vendor/github.com/dolthub/maphash/runtime.go b/vendor/github.com/dolthub/maphash/runtime.go new file mode 100644 index 00000000..29cd6a8e --- /dev/null +++ b/vendor/github.com/dolthub/maphash/runtime.go @@ -0,0 +1,111 @@ +// Copyright 2022 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// This file incorporates work covered by the following copyright and +// permission notice: +// +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.18 || go1.19 +// +build go1.18 go1.19 + +package maphash + +import ( + "math/rand" + "unsafe" +) + +type hashfn func(unsafe.Pointer, uintptr) uintptr + +func getRuntimeHasher[K comparable]() (h hashfn) { + a := any(make(map[K]struct{})) + i := (*mapiface)(unsafe.Pointer(&a)) + h = i.typ.hasher + return +} + +func newHashSeed() uintptr { + return uintptr(rand.Int()) +} + +// noescape hides a pointer from escape analysis. It is the identity function +// but escape analysis doesn't think the output depends on the input. +// noescape is inlined and currently compiles down to zero instructions. +// USE CAREFULLY! +// This was copied from the runtime (via pkg "strings"); see issues 23382 and 7921. +// +//go:nosplit +//go:nocheckptr +func noescape(p unsafe.Pointer) unsafe.Pointer { + x := uintptr(p) + return unsafe.Pointer(x ^ 0) +} + +type mapiface struct { + typ *maptype + val *hmap +} + +// go/src/runtime/type.go +type maptype struct { + typ _type + key *_type + elem *_type + bucket *_type + // function for hashing keys (ptr to key, seed) -> hash + hasher func(unsafe.Pointer, uintptr) uintptr + keysize uint8 + elemsize uint8 + bucketsize uint16 + flags uint32 +} + +// go/src/runtime/map.go +type hmap struct { + count int + flags uint8 + B uint8 + noverflow uint16 + // hash seed + hash0 uint32 + buckets unsafe.Pointer + oldbuckets unsafe.Pointer + nevacuate uintptr + // true type is *mapextra + // but we don't need this data + extra unsafe.Pointer +} + +// go/src/runtime/type.go +type tflag uint8 +type nameOff int32 +type typeOff int32 + +// go/src/runtime/type.go +type _type struct { + size uintptr + ptrdata uintptr + hash uint32 + tflag tflag + align uint8 + fieldAlign uint8 + kind uint8 + equal func(unsafe.Pointer, unsafe.Pointer) bool + gcdata *byte + str nameOff + ptrToThis typeOff +} diff --git a/vendor/github.com/dolthub/swiss/.gitignore b/vendor/github.com/dolthub/swiss/.gitignore new file mode 100644 index 00000000..1f9adf93 --- /dev/null +++ b/vendor/github.com/dolthub/swiss/.gitignore @@ -0,0 +1,5 @@ +**/.idea/ +.vscode +.run +venv +.DS_Store \ No newline at end of file diff --git a/vendor/github.com/dolthub/swiss/LICENSE b/vendor/github.com/dolthub/swiss/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/vendor/github.com/dolthub/swiss/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/dolthub/swiss/README.md b/vendor/github.com/dolthub/swiss/README.md new file mode 100644 index 00000000..71c6f7dd --- /dev/null +++ b/vendor/github.com/dolthub/swiss/README.md @@ -0,0 +1,54 @@ +# SwissMap + +SwissMap is a hash table adapated from the "SwissTable" family of hash tables from [Abseil](https://abseil.io/blog/20180927-swisstables). It uses [AES](https://github.com/dolthub/maphash) instructions for fast-hashing and performs key lookups in parallel using [SSE](https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions) instructions. Because of these optimizations, SwissMap is faster and more memory efficient than Golang's built-in `map`. If you'd like to learn more about its design and implementation, check out this [blog post](https://www.dolthub.com/blog/2023-03-28-swiss-map/) announcing its release. + + +## Example + +SwissMap exposes the same interface as the built-in `map`. Give it a try using this [Go playground](https://go.dev/play/p/JPDC5WhYN7g). + +```go +package main + +import "github.com/dolthub/swiss" + +func main() { + m := swiss.NewMap[string, int](42) + + m.Put("foo", 1) + m.Put("bar", 2) + + m.Iter(func(k string, v int) (stop bool) { + println("iter", k, v) + return false // continue + }) + + if x, ok := m.Get("foo"); ok { + println(x) + } + if m.Has("bar") { + x, _ := m.Get("bar") + println(x) + } + + m.Put("foo", -1) + m.Delete("bar") + + if x, ok := m.Get("foo"); ok { + println(x) + } + if m.Has("bar") { + x, _ := m.Get("bar") + println(x) + } + + m.Clear() + + // Output: + // iter foo 1 + // iter bar 2 + // 1 + // 2 + // -1 +} +``` diff --git a/vendor/github.com/dolthub/swiss/bits.go b/vendor/github.com/dolthub/swiss/bits.go new file mode 100644 index 00000000..f435b6dc --- /dev/null +++ b/vendor/github.com/dolthub/swiss/bits.go @@ -0,0 +1,58 @@ +// Copyright 2023 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !amd64 || nosimd + +package swiss + +import ( + "math/bits" + "unsafe" +) + +const ( + groupSize = 8 + maxAvgGroupLoad = 7 + + loBits uint64 = 0x0101010101010101 + hiBits uint64 = 0x8080808080808080 +) + +type bitset uint64 + +func metaMatchH2(m *metadata, h h2) bitset { + // https://graphics.stanford.edu/~seander/bithacks.html##ValueInWord + return hasZeroByte(castUint64(m) ^ (loBits * uint64(h))) +} + +func metaMatchEmpty(m *metadata) bitset { + return hasZeroByte(castUint64(m) ^ hiBits) +} + +func nextMatch(b *bitset) uint32 { + s := uint32(bits.TrailingZeros64(uint64(*b))) + *b &= ^(1 << s) // clear bit |s| + return s >> 3 // div by 8 +} + +func hasZeroByte(x uint64) bitset { + return bitset(((x - loBits) & ^(x)) & hiBits) +} + +func castUint64(m *metadata) uint64 { + return *(*uint64)((unsafe.Pointer)(m)) +} + +//go:linkname fastrand runtime.fastrand +func fastrand() uint32 diff --git a/vendor/github.com/dolthub/swiss/bits_amd64.go b/vendor/github.com/dolthub/swiss/bits_amd64.go new file mode 100644 index 00000000..8b91f57c --- /dev/null +++ b/vendor/github.com/dolthub/swiss/bits_amd64.go @@ -0,0 +1,50 @@ +// Copyright 2023 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build amd64 && !nosimd + +package swiss + +import ( + "math/bits" + _ "unsafe" + + "github.com/dolthub/swiss/simd" +) + +const ( + groupSize = 16 + maxAvgGroupLoad = 14 +) + +type bitset uint16 + +func metaMatchH2(m *metadata, h h2) bitset { + b := simd.MatchMetadata((*[16]int8)(m), int8(h)) + return bitset(b) +} + +func metaMatchEmpty(m *metadata) bitset { + b := simd.MatchMetadata((*[16]int8)(m), empty) + return bitset(b) +} + +func nextMatch(b *bitset) (s uint32) { + s = uint32(bits.TrailingZeros16(uint16(*b))) + *b &= ^(1 << s) // clear bit |s| + return +} + +//go:linkname fastrand runtime.fastrand +func fastrand() uint32 diff --git a/vendor/github.com/dolthub/swiss/map.go b/vendor/github.com/dolthub/swiss/map.go new file mode 100644 index 00000000..e5ad2038 --- /dev/null +++ b/vendor/github.com/dolthub/swiss/map.go @@ -0,0 +1,359 @@ +// Copyright 2023 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package swiss + +import ( + "github.com/dolthub/maphash" +) + +const ( + maxLoadFactor = float32(maxAvgGroupLoad) / float32(groupSize) +) + +// Map is an open-addressing hash map +// based on Abseil's flat_hash_map. +type Map[K comparable, V any] struct { + ctrl []metadata + groups []group[K, V] + hash maphash.Hasher[K] + resident uint32 + dead uint32 + limit uint32 +} + +// metadata is the h2 metadata array for a group. +// find operations first probe the controls bytes +// to filter candidates before matching keys +type metadata [groupSize]int8 + +// group is a group of 16 key-value pairs +type group[K comparable, V any] struct { + keys [groupSize]K + values [groupSize]V +} + +const ( + h1Mask uint64 = 0xffff_ffff_ffff_ff80 + h2Mask uint64 = 0x0000_0000_0000_007f + empty int8 = -128 // 0b1000_0000 + tombstone int8 = -2 // 0b1111_1110 +) + +// h1 is a 57 bit hash prefix +type h1 uint64 + +// h2 is a 7 bit hash suffix +type h2 int8 + +// NewMap constructs a Map. +func NewMap[K comparable, V any](sz uint32) (m *Map[K, V]) { + groups := numGroups(sz) + m = &Map[K, V]{ + ctrl: make([]metadata, groups), + groups: make([]group[K, V], groups), + hash: maphash.NewHasher[K](), + limit: groups * maxAvgGroupLoad, + } + for i := range m.ctrl { + m.ctrl[i] = newEmptyMetadata() + } + return +} + +// Has returns true if |key| is present in |m|. +func (m *Map[K, V]) Has(key K) (ok bool) { + hi, lo := splitHash(m.hash.Hash(key)) + g := probeStart(hi, len(m.groups)) + for { // inlined find loop + matches := metaMatchH2(&m.ctrl[g], lo) + for matches != 0 { + s := nextMatch(&matches) + if key == m.groups[g].keys[s] { + ok = true + return + } + } + // |key| is not in group |g|, + // stop probing if we see an empty slot + matches = metaMatchEmpty(&m.ctrl[g]) + if matches != 0 { + ok = false + return + } + g += 1 // linear probing + if g >= uint32(len(m.groups)) { + g = 0 + } + } +} + +// Get returns the |value| mapped by |key| if one exists. +func (m *Map[K, V]) Get(key K) (value V, ok bool) { + hi, lo := splitHash(m.hash.Hash(key)) + g := probeStart(hi, len(m.groups)) + for { // inlined find loop + matches := metaMatchH2(&m.ctrl[g], lo) + for matches != 0 { + s := nextMatch(&matches) + if key == m.groups[g].keys[s] { + value, ok = m.groups[g].values[s], true + return + } + } + // |key| is not in group |g|, + // stop probing if we see an empty slot + matches = metaMatchEmpty(&m.ctrl[g]) + if matches != 0 { + ok = false + return + } + g += 1 // linear probing + if g >= uint32(len(m.groups)) { + g = 0 + } + } +} + +// Put attempts to insert |key| and |value| +func (m *Map[K, V]) Put(key K, value V) { + if m.resident >= m.limit { + m.rehash(m.nextSize()) + } + hi, lo := splitHash(m.hash.Hash(key)) + g := probeStart(hi, len(m.groups)) + for { // inlined find loop + matches := metaMatchH2(&m.ctrl[g], lo) + for matches != 0 { + s := nextMatch(&matches) + if key == m.groups[g].keys[s] { // update + m.groups[g].keys[s] = key + m.groups[g].values[s] = value + return + } + } + // |key| is not in group |g|, + // stop probing if we see an empty slot + matches = metaMatchEmpty(&m.ctrl[g]) + if matches != 0 { // insert + s := nextMatch(&matches) + m.groups[g].keys[s] = key + m.groups[g].values[s] = value + m.ctrl[g][s] = int8(lo) + m.resident++ + return + } + g += 1 // linear probing + if g >= uint32(len(m.groups)) { + g = 0 + } + } +} + +// Delete attempts to remove |key|, returns true successful. +func (m *Map[K, V]) Delete(key K) (ok bool) { + hi, lo := splitHash(m.hash.Hash(key)) + g := probeStart(hi, len(m.groups)) + for { + matches := metaMatchH2(&m.ctrl[g], lo) + for matches != 0 { + s := nextMatch(&matches) + if key == m.groups[g].keys[s] { + ok = true + // optimization: if |m.ctrl[g]| contains any empty + // metadata bytes, we can physically delete |key| + // rather than placing a tombstone. + // The observation is that any probes into group |g| + // would already be terminated by the existing empty + // slot, and therefore reclaiming slot |s| will not + // cause premature termination of probes into |g|. + if metaMatchEmpty(&m.ctrl[g]) != 0 { + m.ctrl[g][s] = empty + m.resident-- + } else { + m.ctrl[g][s] = tombstone + m.dead++ + } + var k K + var v V + m.groups[g].keys[s] = k + m.groups[g].values[s] = v + return + } + } + // |key| is not in group |g|, + // stop probing if we see an empty slot + matches = metaMatchEmpty(&m.ctrl[g]) + if matches != 0 { // |key| absent + ok = false + return + } + g += 1 // linear probing + if g >= uint32(len(m.groups)) { + g = 0 + } + } +} + +// Iter iterates the elements of the Map, passing them to the callback. +// It guarantees that any key in the Map will be visited only once, and +// for un-mutated Maps, every key will be visited once. If the Map is +// Mutated during iteration, mutations will be reflected on return from +// Iter, but the set of keys visited by Iter is non-deterministic. +func (m *Map[K, V]) Iter(cb func(k K, v V) (stop bool)) { + // take a consistent view of the table in case + // we rehash during iteration + ctrl, groups := m.ctrl, m.groups + // pick a random starting group + g := randIntN(len(groups)) + for n := 0; n < len(groups); n++ { + for s, c := range ctrl[g] { + if c == empty || c == tombstone { + continue + } + k, v := groups[g].keys[s], groups[g].values[s] + if stop := cb(k, v); stop { + return + } + } + g++ + if g >= uint32(len(groups)) { + g = 0 + } + } +} + +// Clear removes all elements from the Map. +func (m *Map[K, V]) Clear() { + for i, c := range m.ctrl { + for j := range c { + m.ctrl[i][j] = empty + } + } + var k K + var v V + for i := range m.groups { + g := &m.groups[i] + for i := range g.keys { + g.keys[i] = k + g.values[i] = v + } + } + m.resident, m.dead = 0, 0 +} + +// Count returns the number of elements in the Map. +func (m *Map[K, V]) Count() int { + return int(m.resident - m.dead) +} + +// Capacity returns the number of additional elements +// the can be added to the Map before resizing. +func (m *Map[K, V]) Capacity() int { + return int(m.limit - m.resident) +} + +// find returns the location of |key| if present, or its insertion location if absent. +// for performance, find is manually inlined into public methods. +func (m *Map[K, V]) find(key K, hi h1, lo h2) (g, s uint32, ok bool) { + g = probeStart(hi, len(m.groups)) + for { + matches := metaMatchH2(&m.ctrl[g], lo) + for matches != 0 { + s = nextMatch(&matches) + if key == m.groups[g].keys[s] { + return g, s, true + } + } + // |key| is not in group |g|, + // stop probing if we see an empty slot + matches = metaMatchEmpty(&m.ctrl[g]) + if matches != 0 { + s = nextMatch(&matches) + return g, s, false + } + g += 1 // linear probing + if g >= uint32(len(m.groups)) { + g = 0 + } + } +} + +func (m *Map[K, V]) nextSize() (n uint32) { + n = uint32(len(m.groups)) * 2 + if m.dead >= (m.resident / 2) { + n = uint32(len(m.groups)) + } + return +} + +func (m *Map[K, V]) rehash(n uint32) { + groups, ctrl := m.groups, m.ctrl + m.groups = make([]group[K, V], n) + m.ctrl = make([]metadata, n) + for i := range m.ctrl { + m.ctrl[i] = newEmptyMetadata() + } + m.hash = maphash.NewSeed(m.hash) + m.limit = n * maxAvgGroupLoad + m.resident, m.dead = 0, 0 + for g := range ctrl { + for s := range ctrl[g] { + c := ctrl[g][s] + if c == empty || c == tombstone { + continue + } + m.Put(groups[g].keys[s], groups[g].values[s]) + } + } +} + +func (m *Map[K, V]) loadFactor() float32 { + slots := float32(len(m.groups) * groupSize) + return float32(m.resident-m.dead) / slots +} + +// numGroups returns the minimum number of groups needed to store |n| elems. +func numGroups(n uint32) (groups uint32) { + groups = (n + maxAvgGroupLoad - 1) / maxAvgGroupLoad + if groups == 0 { + groups = 1 + } + return +} + +func newEmptyMetadata() (meta metadata) { + for i := range meta { + meta[i] = empty + } + return +} + +func splitHash(h uint64) (h1, h2) { + return h1((h & h1Mask) >> 7), h2(h & h2Mask) +} + +func probeStart(hi h1, groups int) uint32 { + return fastModN(uint32(hi), uint32(groups)) +} + +// lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ +func fastModN(x, n uint32) uint32 { + return uint32((uint64(x) * uint64(n)) >> 32) +} + +// randIntN returns a random number in the interval [0, n). +func randIntN(n int) uint32 { + return fastModN(fastrand(), uint32(n)) +} diff --git a/vendor/github.com/dolthub/swiss/simd/match.s b/vendor/github.com/dolthub/swiss/simd/match.s new file mode 100644 index 00000000..4ae29e77 --- /dev/null +++ b/vendor/github.com/dolthub/swiss/simd/match.s @@ -0,0 +1,19 @@ +// Code generated by command: go run asm.go -out match.s -stubs match_amd64.go. DO NOT EDIT. + +//go:build amd64 + +#include "textflag.h" + +// func MatchMetadata(metadata *[16]int8, hash int8) uint16 +// Requires: SSE2, SSSE3 +TEXT ·MatchMetadata(SB), NOSPLIT, $0-18 + MOVQ metadata+0(FP), AX + MOVBLSX hash+8(FP), CX + MOVD CX, X0 + PXOR X1, X1 + PSHUFB X1, X0 + MOVOU (AX), X1 + PCMPEQB X1, X0 + PMOVMSKB X0, AX + MOVW AX, ret+16(FP) + RET diff --git a/vendor/github.com/dolthub/swiss/simd/match_amd64.go b/vendor/github.com/dolthub/swiss/simd/match_amd64.go new file mode 100644 index 00000000..538c8e12 --- /dev/null +++ b/vendor/github.com/dolthub/swiss/simd/match_amd64.go @@ -0,0 +1,9 @@ +// Code generated by command: go run asm.go -out match.s -stubs match_amd64.go. DO NOT EDIT. + +//go:build amd64 + +package simd + +// MatchMetadata performs a 16-way probe of |metadata| using SSE instructions +// nb: |metadata| must be an aligned pointer +func MatchMetadata(metadata *[16]int8, hash int8) uint16 diff --git a/vendor/modules.txt b/vendor/modules.txt index 8fb33cf5..b7461958 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -106,6 +106,13 @@ github.com/datarhei/joy4/utils/bits/pio # github.com/davecgh/go-spew v1.1.1 ## explicit github.com/davecgh/go-spew/spew +# github.com/dolthub/maphash v0.1.0 +## explicit; go 1.18 +github.com/dolthub/maphash +# github.com/dolthub/swiss v0.2.1 +## explicit; go 1.18 +github.com/dolthub/swiss +github.com/dolthub/swiss/simd # github.com/dustin/go-humanize v1.0.1 ## explicit; go 1.16 github.com/dustin/go-humanize From 3756ce4977372b2495ec435a0a97404a10a8b5ae Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Tue, 20 Aug 2024 17:34:50 +0200 Subject: [PATCH 07/64] Add AppendFileReader to filesystem, allows session logging with less I/O --- io/fs/disk.go | 25 ++++++++++++++++++++++ io/fs/fs.go | 4 ++++ io/fs/fs_test.go | 32 ++++++++++++++++++++++++++++ io/fs/mem.go | 40 +++++++++++++++++++++++++++++++++++ io/fs/mem_storage.go | 16 ++++++++++++++ io/fs/s3.go | 19 +++++++++++++++++ io/fs/sized.go | 50 +++++++++++++++++++++++++------------------- session/registry.go | 12 +++-------- 8 files changed, 167 insertions(+), 31 deletions(-) diff --git a/io/fs/disk.go b/io/fs/disk.go index 6cac9d40..b9f76b77 100644 --- a/io/fs/disk.go +++ b/io/fs/disk.go @@ -403,6 +403,31 @@ func (fs *diskFilesystem) WriteFileSafe(path string, data []byte) (int64, bool, return int64(size), !replace, nil } +func (fs *diskFilesystem) AppendFileReader(path string, r io.Reader, sizeHint int) (int64, error) { + path = fs.cleanPath(path) + + dir := filepath.Dir(path) + if err := os.MkdirAll(dir, 0755); err != nil { + return -1, fmt.Errorf("creating file failed: %w", err) + } + + f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + return -1, err + } + + defer f.Close() + + size, err := f.ReadFrom(r) + if err != nil { + return -1, fmt.Errorf("reading data failed: %w", err) + } + + fs.lastSizeCheck = time.Time{} + + return size, nil +} + func (fs *diskFilesystem) Rename(src, dst string) error { src = fs.cleanPath(src) dst = fs.cleanPath(dst) diff --git a/io/fs/fs.go b/io/fs/fs.go index 4cc8e201..973be194 100644 --- a/io/fs/fs.go +++ b/io/fs/fs.go @@ -108,6 +108,10 @@ type WriteFilesystem interface { // an error adding the file and error is not nil. WriteFileSafe(path string, data []byte) (int64, bool, error) + // AppendFileReader appends the contents from reader to the file at path. If the file doesn't + // exist, it will be created. The number of written bytes will be returned, -1 otherwise. + AppendFileReader(path string, r io.Reader, size int) (int64, error) + // MkdirAll creates a directory named path, along with any necessary parents, and returns nil, // or else returns an error. The permission bits perm (before umask) are used for all directories // that MkdirAll creates. If path is already a directory, MkdirAll does nothing and returns nil. diff --git a/io/fs/fs_test.go b/io/fs/fs_test.go index 7e7c2362..17d79e3c 100644 --- a/io/fs/fs_test.go +++ b/io/fs/fs_test.go @@ -115,6 +115,8 @@ func TestFilesystem(t *testing.T) { "symlinkErrors": testSymlinkErrors, "symlinkOpenStat": testSymlinkOpenStat, "open": testOpen, + "append": testAppend, + "appendCreate": testAppendCreate, } for fsname, fs := range filesystems { @@ -125,6 +127,11 @@ func TestFilesystem(t *testing.T) { } filesystem, err := fs(name) require.NoError(t, err) + + if fsname == "s3fs" { + filesystem.RemoveList("/", ListOptions{Pattern: "/**"}) + } + test(t, filesystem) }) } @@ -859,3 +866,28 @@ func testSymlinkErrors(t *testing.T, fs Filesystem) { err = fs.Symlink("/bazfoo", "/barfoo") require.Error(t, err) } + +func testAppend(t *testing.T, fs Filesystem) { + _, _, err := fs.WriteFileReader("/foobar", strings.NewReader("part1"), -1) + require.NoError(t, err) + + _, err = fs.AppendFileReader("/foobar", strings.NewReader("part2"), -1) + require.NoError(t, err) + + file := fs.Open("/foobar") + require.NotNil(t, file) + + data, err := io.ReadAll(file) + require.Equal(t, []byte("part1part2"), data) +} + +func testAppendCreate(t *testing.T, fs Filesystem) { + _, err := fs.AppendFileReader("/foobar", strings.NewReader("part1"), -1) + require.NoError(t, err) + + file := fs.Open("/foobar") + require.NotNil(t, file) + + data, err := io.ReadAll(file) + require.Equal(t, []byte("part1"), data) +} diff --git a/io/fs/mem.go b/io/fs/mem.go index dee83c78..de8b3cb7 100644 --- a/io/fs/mem.go +++ b/io/fs/mem.go @@ -525,6 +525,46 @@ func (fs *memFilesystem) WriteFileSafe(path string, data []byte) (int64, bool, e return fs.WriteFileReader(path, bytes.NewReader(data), len(data)) } +func (fs *memFilesystem) AppendFileReader(path string, r io.Reader, sizeHint int) (int64, error) { + path = fs.cleanPath(path) + + file, hasFile := fs.storage.LoadAndCopy(path) + if !hasFile { + size, _, err := fs.WriteFileReader(path, r, sizeHint) + return size, err + } + + size, err := copyToBufferFromReader(file.data, r, 8*1024) + if err != nil { + fs.logger.WithFields(log.Fields{ + "path": path, + "filesize_bytes": size, + "error": err, + }).Warn().Log("Incomplete file") + + file.Close() + + return -1, fmt.Errorf("incomplete file") + } + + file.size += size + + fs.storage.Store(path, file) + + fs.sizeLock.Lock() + defer fs.sizeLock.Unlock() + + fs.currentSize += size + + fs.logger.Debug().WithFields(log.Fields{ + "path": file.name, + "filesize_bytes": file.size, + "size_bytes": fs.currentSize, + }).Log("Appended to file") + + return size, nil +} + func (fs *memFilesystem) Purge(size int64) int64 { files := []*memFile{} diff --git a/io/fs/mem_storage.go b/io/fs/mem_storage.go index 5df8f946..044b375e 100644 --- a/io/fs/mem_storage.go +++ b/io/fs/mem_storage.go @@ -9,11 +9,27 @@ import ( ) type memStorage interface { + // Delete deletes a file from the storage. Delete(key string) (*memFile, bool) + + // Store stores a file to the storage. If there's already a file with + // the same key, that value will be returned and replaced with the + // new file. Store(key string, value *memFile) (*memFile, bool) + + // Load loads a file from the storage. This is a references to the file, + // i.e. all changes to the file will be reflected on the storage. Load(key string) (value *memFile, ok bool) + + // LoadAndCopy loads a file from the storage. The returned file is a copy + // and can be modified without modifying the file on the storage. LoadAndCopy(key string) (value *memFile, ok bool) + + // Has checks whether a file exists at path. Has(key string) bool + + // Range ranges over all files on the storage. The callback needs to return + // false in order to stop the iteration. Range(f func(key string, value *memFile) bool) } diff --git a/io/fs/s3.go b/io/fs/s3.go index c75162ed..ed9d1ade 100644 --- a/io/fs/s3.go +++ b/io/fs/s3.go @@ -360,6 +360,25 @@ func (fs *s3Filesystem) WriteFileSafe(path string, data []byte) (int64, bool, er return fs.WriteFileReader(path, bytes.NewReader(data), len(data)) } +func (fs *s3Filesystem) AppendFileReader(path string, r io.Reader, sizeHint int) (int64, error) { + path = fs.cleanPath(path) + + ctx := context.Background() + + object, err := fs.client.GetObject(ctx, fs.bucket, path, minio.GetObjectOptions{}) + if err != nil { + size, _, err := fs.write(path, r) + return size, err + } + + buffer := bytes.Buffer{} + buffer.ReadFrom(object) + buffer.ReadFrom(r) + + size, _, err := fs.write(path, &buffer) + return size, err +} + func (fs *s3Filesystem) Rename(src, dst string) error { src = fs.cleanPath(src) dst = fs.cleanPath(dst) diff --git a/io/fs/sized.go b/io/fs/sized.go index aa6b552b..cfa159eb 100644 --- a/io/fs/sized.go +++ b/io/fs/sized.go @@ -135,34 +135,40 @@ func (r *sizedFilesystem) WriteFileSafe(path string, data []byte) (int64, bool, return r.Filesystem.WriteFileSafe(path, data) } -func (r *sizedFilesystem) Purge(size int64) int64 { - if purger, ok := r.Filesystem.(PurgeFilesystem); ok { - return purger.Purge(size) +func (r *sizedFilesystem) AppendFileReader(path string, rd io.Reader, sizeHint int) (int64, error) { + currentSize, maxSize := r.Size() + if maxSize <= 0 { + return r.Filesystem.AppendFileReader(path, rd, sizeHint) } - return 0 - /* - files := r.Filesystem.List("/", "") + data := bytes.Buffer{} + size, err := copyToBufferFromReader(&data, rd, 8*1024) + if err != nil { + return -1, err + } - sort.Slice(files, func(i, j int) bool { - return files[i].ModTime().Before(files[j].ModTime()) - }) + // Calculate the new size of the filesystem + newSize := currentSize + size - var freed int64 = 0 + // If the the new size is larger than the allowed size, we have to free + // some space. + if newSize > maxSize { + if !r.purge { + return -1, fmt.Errorf("not enough space on device") + } - for _, f := range files { - r.Filesystem.Remove(f.Name()) - size -= f.Size() - freed += f.Size() - r.currentSize -= f.Size() + if r.Purge(size) < size { + return -1, fmt.Errorf("not enough space on device") + } + } - if size <= 0 { - break - } - } + return r.Filesystem.AppendFileReader(path, &data, int(size)) +} - files = nil +func (r *sizedFilesystem) Purge(size int64) int64 { + if purger, ok := r.Filesystem.(PurgeFilesystem); ok { + return purger.Purge(size) + } - return freed - */ + return 0 } diff --git a/session/registry.go b/session/registry.go index 0dd54af6..1a08b3f0 100644 --- a/session/registry.go +++ b/session/registry.go @@ -199,12 +199,6 @@ func (r *registry) sessionPersister(pattern *strftime.Strftime, bufferDuration t buffer := &bytes.Buffer{} path := pattern.FormatString(time.Now()) - file := r.persist.fs.Open(path) - if file != nil { - buffer.ReadFrom(file) - file.Close() - } - enc := json.NewEncoder(buffer) ticker := time.NewTicker(bufferDuration) @@ -222,7 +216,7 @@ loop: currentPath := pattern.FormatString(session.ClosedAt) if currentPath != path && session.ClosedAt.After(splitTime) { if buffer.Len() > 0 { - _, _, err := r.persist.fs.WriteFileSafe(path, buffer.Bytes()) + _, err := r.persist.fs.AppendFileReader(path, buffer, -1) if err != nil { r.logger.Error().WithError(err).WithField("path", path).Log("") } @@ -239,7 +233,7 @@ loop: enc.Encode(&session) case t := <-ticker.C: if buffer.Len() > 0 { - _, _, err := r.persist.fs.WriteFileSafe(path, buffer.Bytes()) + _, err := r.persist.fs.AppendFileReader(path, buffer, -1) if err != nil { r.logger.Error().WithError(err).WithField("path", path).Log("") } else { @@ -260,7 +254,7 @@ loop: } if buffer.Len() > 0 { - _, _, err := r.persist.fs.WriteFileSafe(path, buffer.Bytes()) + _, err := r.persist.fs.AppendFileReader(path, buffer, -1) if err != nil { r.logger.Error().WithError(err).WithField("path", path).Log("") } else { From 1c56d53a6bfbe65cab272212df0d5eb859bad016 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Wed, 21 Aug 2024 17:02:22 +0200 Subject: [PATCH 08/64] Adjust comments --- process/limiter.go | 1 + process/process.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/process/limiter.go b/process/limiter.go index 79f791e3..ea5df9c2 100644 --- a/process/limiter.go +++ b/process/limiter.go @@ -425,6 +425,7 @@ func (l *limiter) Limit(cpu, memory bool) error { // limitCPU will limit the CPU usage of this process. The limit is the max. CPU usage // normed to 0-1. The interval defines how long a time slot is that will be splitted // into sleeping and working. +// Inspired by https://github.com/opsengine/cpulimit func (l *limiter) limitCPU(ctx context.Context, limit float64, interval time.Duration) { defer func() { l.lock.Lock() diff --git a/process/process.go b/process/process.go index c6bc01c8..0fe0d45f 100644 --- a/process/process.go +++ b/process/process.go @@ -43,7 +43,7 @@ type Process interface { // running or not. IsRunning() bool - // Limit enabled or disables CPU and memory limiting. CPU will be throttled + // Limit enables or disables CPU and memory limiting. CPU will be throttled // into the configured limit. If memory consumption is above the configured // limit, the process will be killed. Limit(cpu, memory bool) error From 9947ba822b31ec2e999ed4f2e30e0fe471a1b9f7 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Wed, 21 Aug 2024 20:31:22 +0200 Subject: [PATCH 09/64] Add missing JSON tags --- http/api/about.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/http/api/about.go b/http/api/about.go index 5887423a..332420a9 100644 --- a/http/api/about.go +++ b/http/api/about.go @@ -24,15 +24,15 @@ type AboutVersion struct { // AboutResources holds information about the current resource usage type AboutResources struct { - IsThrottling bool // Whether this core is currently throttling - NCPU float64 // Number of CPU on this node - CPU float64 // Current CPU load, 0-100*ncpu - CPULimit float64 // Defined CPU load limit, 0-100*ncpu - CPUCore float64 // Current CPU load of the core itself, 0-100*ncpu - Mem uint64 // Currently used memory in bytes - MemLimit uint64 // Defined memory limit in bytes - MemTotal uint64 // Total available memory in bytes - MemCore uint64 // Current used memory of the core itself in bytes + IsThrottling bool `json:"is_throttling"` // Whether this core is currently throttling + NCPU float64 `json:"ncpu"` // Number of CPU on this node + CPU float64 `json:"cpu_used"` // Current CPU load, 0-100*ncpu + CPULimit float64 `json:"cpu_limit"` // Defined CPU load limit, 0-100*ncpu + CPUCore float64 `json:"cpu_core"` // Current CPU load of the core itself, 0-100*ncpu + Mem uint64 `json:"memory_used_bytes"` // Currently used memory in bytes + MemLimit uint64 `json:"memory_limit_bytes"` // Defined memory limit in bytes + MemTotal uint64 `json:"memory_total_bytes"` // Total available memory in bytes + MemCore uint64 `json:"memory_core_bytes"` // Current used memory of the core itself in bytes } // MinimalAbout is the minimal information about the API From bebef61e55866bbc177dfb647b4fdcfac0720737 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 22 Aug 2024 13:40:38 +0200 Subject: [PATCH 10/64] Add /v3/cluster/events endpoint to gather events from all nodes --- cluster/node/core.go | 16 +++- cluster/node/manager.go | 23 +++++ http/api/event.go | 20 +++- http/client/client.go | 60 ++++++------ http/handler/api/cluster_events.go | 144 +++++++++++++++++++++++++++++ http/server.go | 2 + 6 files changed, 228 insertions(+), 37 deletions(-) create mode 100644 http/handler/api/cluster_events.go diff --git a/cluster/node/core.go b/cluster/node/core.go index 2af52762..5341db06 100644 --- a/cluster/node/core.go +++ b/cluster/node/core.go @@ -164,8 +164,9 @@ func (n *Core) connect() error { Address: u.String(), Client: &http.Client{ Transport: tr, - Timeout: 5 * time.Second, + Timeout: 0, }, + Timeout: 5 * time.Second, }) if err != nil { return fmt.Errorf("creating client failed (%s): %w", address, err) @@ -267,7 +268,6 @@ type CoreVersion struct { } func (n *Core) About() (CoreAbout, error) { - n.lock.RLock() client := n.client n.lock.RUnlock() @@ -808,3 +808,15 @@ func (n *Core) ClusterProcessList() ([]Process, error) { return processes, nil } + +func (n *Core) Events(ctx context.Context, filters api.EventFilters) (<-chan api.Event, error) { + n.lock.RLock() + client := n.client + n.lock.RUnlock() + + if client == nil { + return nil, ErrNoPeer + } + + return client.Events(ctx, filters) +} diff --git a/cluster/node/manager.go b/cluster/node/manager.go index cc1072ea..d9eefc3e 100644 --- a/cluster/node/manager.go +++ b/cluster/node/manager.go @@ -1,6 +1,7 @@ package node import ( + "context" "errors" "fmt" "io" @@ -625,3 +626,25 @@ func (p *Manager) ProcessProbeConfig(nodeid string, config *app.Config) (api.Pro return node.Core().ProcessProbeConfig(config) } + +func (p *Manager) Events(ctx context.Context, filters api.EventFilters) (<-chan api.Event, error) { + eventChan := make(chan api.Event, 128) + + p.lock.RLock() + for _, n := range p.nodes { + go func(node *Node, e chan<- api.Event) { + eventChan, err := node.Core().Events(ctx, filters) + if err != nil { + return + } + + for event := range eventChan { + event.CoreID = node.id + e <- event + } + }(n, eventChan) + } + p.lock.RUnlock() + + return eventChan, nil +} diff --git a/http/api/event.go b/http/api/event.go index 416e37d5..0685cb82 100644 --- a/http/api/event.go +++ b/http/api/event.go @@ -15,6 +15,7 @@ type Event struct { Component string `json:"event"` Message string `json:"message"` Caller string `json:"caller"` + CoreID string `json:"core_id,omitempty"` Data map[string]string `json:"data"` } @@ -66,12 +67,18 @@ func (e *Event) Filter(ef *EventFilter) bool { } } - if ef.reCaller != nil { + if len(e.Caller) != 0 && ef.reCaller != nil { if !ef.reCaller.MatchString(e.Caller) { return false } } + if len(e.CoreID) != 0 && ef.reCoreID != nil { + if !ef.reCoreID.MatchString(e.CoreID) { + return false + } + } + for k, r := range ef.reData { v, ok := e.Data[k] if !ok { @@ -91,11 +98,13 @@ type EventFilter struct { Message string `json:"message"` Level string `json:"level"` Caller string `json:"caller"` + CoreID string `json:"core_id"` Data map[string]string `json:"data"` reMessage *regexp.Regexp reLevel *regexp.Regexp reCaller *regexp.Regexp + reCoreID *regexp.Regexp reData map[string]*regexp.Regexp } @@ -131,6 +140,15 @@ func (ef *EventFilter) Compile() error { ef.reCaller = r } + if len(ef.CoreID) != 0 { + r, err := regexp.Compile("(?i)" + ef.CoreID) + if err != nil { + return err + } + + ef.reCoreID = r + } + ef.reData = make(map[string]*regexp.Regexp) for k, v := range ef.Data { diff --git a/http/client/client.go b/http/client/client.go index d73fab98..ab235c0d 100644 --- a/http/client/client.go +++ b/http/client/client.go @@ -167,8 +167,13 @@ type Config struct { // Auth0Token is a valid Auth0 token to authorize access to the API. Auth0Token string - // Client is a HTTPClient that will be used for the API calls. Optional. + // Client is a HTTPClient that will be used for the API calls. Optional. Don't + // set a timeout in the client if you want to use the timeout in this config. Client HTTPClient + + // Timeout is the timeout for the whole connection. Don't set a timeout in + // the optional HTTPClient as it will override this timeout. + Timeout time.Duration } type apiconstraint struct { @@ -178,16 +183,17 @@ type apiconstraint struct { // restclient implements the RestClient interface. type restclient struct { - address string - prefix string - accessToken Token - refreshToken Token - username string - password string - auth0Token string - client HTTPClient - about api.About - aboutLock sync.RWMutex + address string + prefix string + accessToken Token + refreshToken Token + username string + password string + auth0Token string + client HTTPClient + clientTimeout time.Duration + about api.About + aboutLock sync.RWMutex version struct { connectedCore *semver.Version @@ -199,12 +205,13 @@ type restclient struct { // in case of an error. func New(config Config) (RestClient, error) { r := &restclient{ - address: config.Address, - prefix: "/api", - username: config.Username, - password: config.Password, - auth0Token: config.Auth0Token, - client: config.Client, + address: config.Address, + prefix: "/api", + username: config.Username, + password: config.Password, + auth0Token: config.Auth0Token, + client: config.Client, + clientTimeout: config.Timeout, } if len(config.AccessToken) != 0 { @@ -806,26 +813,11 @@ func (r *restclient) info() (api.About, error) { } func (r *restclient) request(req *http.Request) (int, io.ReadCloser, error) { - /* - fmt.Printf("%s %s\n", req.Method, req.URL) - for key, value := range req.Header { - for _, v := range value { - fmt.Printf("%s: %s\n", key, v) - } - } - fmt.Printf("\n") - */ resp, err := r.client.Do(req) if err != nil { return -1, nil, err } - /* - for key, value := range resp.Header { - for _, v := range value { - fmt.Printf("%s: %s\n", key, v) - } - } - */ + reader := resp.Body contentEncoding := resp.Header.Get("Content-Encoding") @@ -923,7 +915,7 @@ func (r *restclient) stream(ctx context.Context, method, path string, query *url } func (r *restclient) call(method, path string, query *url.Values, header http.Header, contentType string, data io.Reader) ([]byte, error) { - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), r.clientTimeout) defer cancel() body, err := r.stream(ctx, method, path, query, header, contentType, data) diff --git a/http/handler/api/cluster_events.go b/http/handler/api/cluster_events.go new file mode 100644 index 00000000..7072a44c --- /dev/null +++ b/http/handler/api/cluster_events.go @@ -0,0 +1,144 @@ +package api + +import ( + "context" + "net/http" + "strings" + "time" + + "github.com/datarhei/core/v16/encoding/json" + "github.com/datarhei/core/v16/http/api" + "github.com/datarhei/core/v16/http/handler/util" + + "github.com/labstack/echo/v4" +) + +// Events returns a stream of event +// @Summary Stream of events +// @Description Stream of events of whats happening on each node in the cluster +// @ID cluster-3-events +// @Tags v16.?.? +// @Accept json +// @Produce text/event-stream +// @Produce json-stream +// @Param filters body api.EventFilters false "Event filters" +// @Success 200 {object} api.Event +// @Security ApiKeyAuth +// @Router /api/v3/cluster/events [post] +func (h *ClusterHandler) Events(c echo.Context) error { + filters := api.EventFilters{} + + if err := util.ShouldBindJSON(c, &filters); err != nil { + return api.Err(http.StatusBadRequest, "", "invalid JSON: %s", err.Error()) + } + + filter := map[string]*api.EventFilter{} + + for _, f := range filters.Filters { + f := f + + if err := f.Compile(); err != nil { + return api.Err(http.StatusBadRequest, "", "invalid filter: %s: %s", f.Component, err.Error()) + } + + component := strings.ToLower(f.Component) + filter[component] = &f + } + + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + req := c.Request() + reqctx := req.Context() + + contentType := "text/event-stream" + accept := req.Header.Get(echo.HeaderAccept) + if strings.Contains(accept, "application/x-json-stream") { + contentType = "application/x-json-stream" + } + + res := c.Response() + + res.Header().Set(echo.HeaderContentType, contentType+"; charset=UTF-8") + res.Header().Set(echo.HeaderCacheControl, "no-store") + res.Header().Set(echo.HeaderConnection, "close") + res.WriteHeader(http.StatusOK) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + evts, err := h.proxy.Events(ctx, filters) + if err != nil { + return api.Err(http.StatusInternalServerError, "", "%s", err.Error()) + } + + enc := json.NewEncoder(res) + enc.SetIndent("", "") + + done := make(chan error, 1) + + filterEvent := func(event *api.Event) bool { + if len(filter) == 0 { + return true + } + + f, ok := filter[event.Component] + if !ok { + return false + } + + return event.Filter(f) + } + + if contentType == "text/event-stream" { + res.Write([]byte(":keepalive\n\n")) + res.Flush() + + for { + select { + case err := <-done: + return err + case <-reqctx.Done(): + done <- nil + case <-ticker.C: + res.Write([]byte(":keepalive\n\n")) + res.Flush() + case event := <-evts: + if !filterEvent(&event) { + continue + } + + res.Write([]byte("event: " + event.Component + "\ndata: ")) + if err := enc.Encode(event); err != nil { + done <- err + } + res.Write([]byte("\n")) + res.Flush() + } + } + } else { + res.Write([]byte("{\"event\": \"keepalive\"}\n")) + res.Flush() + + for { + select { + case err := <-done: + return err + case <-reqctx.Done(): + done <- nil + case <-ticker.C: + res.Write([]byte("{\"event\": \"keepalive\"}\n")) + res.Flush() + case event := <-evts: + if !filterEvent(&event) { + continue + } + + if err := enc.Encode(event); err != nil { + done <- err + } + res.Flush() + } + } + } +} diff --git a/http/server.go b/http/server.go index 959165bd..bbfb2726 100644 --- a/http/server.go +++ b/http/server.go @@ -762,6 +762,8 @@ func (s *server) setRoutesV3(v3 *echo.Group) { v3.GET("/cluster/fs/:storage", s.v3handler.cluster.FilesystemListFiles) + v3.POST("/cluster/events", s.v3handler.cluster.Events) + if !s.readOnly { v3.PUT("/cluster/transfer/:id", s.v3handler.cluster.TransferLeadership) v3.PUT("/cluster/leave", s.v3handler.cluster.Leave) From 70ffb805b4a18e3a0e6ca0b9817768f5e06f7286 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Tue, 3 Sep 2024 16:39:19 +0200 Subject: [PATCH 11/64] Fix returning original config --- restream/task.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/restream/task.go b/restream/task.go index 611702ef..3073b506 100644 --- a/restream/task.go +++ b/restream/task.go @@ -448,11 +448,11 @@ func (t *task) Config() *app.Config { token := t.lock.RLock() defer t.lock.RUnlock(token) - if t.config == nil { + if t.process == nil { return nil } - return t.config.Clone() + return t.process.Config.Clone() } func (t *task) Destroy() { From 7831992936867a5b3da93bdc023597778b8d4e20 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 5 Sep 2024 13:33:01 +0200 Subject: [PATCH 12/64] Add parsing for track specific avstream progress data --- ffmpeg/parse/parser.go | 2 +- ffmpeg/parse/parser_test.go | 372 ++++++++++++++++++++++++++++++++++++ ffmpeg/parse/types.go | 167 +++++++++++----- 3 files changed, 493 insertions(+), 48 deletions(-) diff --git a/ffmpeg/parse/parser.go b/ffmpeg/parse/parser.go index a6bf5127..125e3ee1 100644 --- a/ffmpeg/parse/parser.go +++ b/ffmpeg/parse/parser.go @@ -653,7 +653,7 @@ func (p *parser) Progress() Progress { continue } - progress.Input[i].AVstream = av.export() + progress.Input[i].AVstream = av.export(io.Type) } progress.Started = p.stats.initialized diff --git a/ffmpeg/parse/parser_test.go b/ffmpeg/parse/parser_test.go index e041fc4f..9caf348d 100644 --- a/ffmpeg/parse/parser_test.go +++ b/ffmpeg/parse/parser_test.go @@ -991,6 +991,378 @@ func TestParserProgressPlayout(t *testing.T) { }, progress) } +func TestParserProgressPlayoutVideo(t *testing.T) { + parser := New(Config{ + LogLines: 20, + }).(*parser) + + parser.Parse([]byte(`ffmpeg.inputs:[{"url":"playout:https://cdn.livespotting.com/vpu/e9slfpe3/z60wzayk.m3u8","format":"playout","index":0,"stream":0,"type":"video","codec":"h264","coder":"h264","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":20.666666,"pix_fmt":"yuvj420p","width":1280,"height":720}]`)) + parser.Parse([]byte(`ffmpeg.outputs:[{"url":"/dev/null","format":"flv","index":0,"stream":0,"type":"video","codec":"h264","coder":"libx264","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":25.000000,"pix_fmt":"yuvj420p","width":1280,"height":720},{"url":"/dev/null","format":"mp4","index":1,"stream":0,"type":"video","codec":"h264","coder":"copy","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","fps":20.666666,"pix_fmt":"yuvj420p","width":1280,"height":720}]`)) + parser.Parse([]byte(`ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"frame":7,"keyframe":1,"packet":11,"size_kb":226,"size_bytes":42}],"outputs":[{"index":0,"stream":0,"frame":7,"keyframe":1,"packet":0,"q":0.0,"size_kb":0,"size_bytes":5,"extradata_size_bytes":32},{"index":1,"stream":0,"frame":11,"packet":11,"q":-1.0,"size_kb":226}],"frame":7,"packet":0,"q":0.0,"size_kb":226,"time":"0h0m0.56s","speed":0.4,"dup":0,"drop":0}`)) + parser.Parse([]byte(`avstream.progress:{"id":"playout:https://cdn.livespotting.com/vpu/e9slfpe3/z60wzayk.m3u8","url":"https://cdn.livespotting.com/vpu/e9slfpe3/z60wzayk.m3u8","stream":0,"queue":140,"aqueue":42,"dup":5,"drop":8,"enc":7,"looping":true,"duplicating":true,"gop":"key","mode":"live","input":{"state":"running","packet":148,"size_kb":1529,"time":5},"output":{"state":"running","packet":8,"size_kb":128,"time":1},"swap":{"url":"","status":"waiting","lasturl":"","lasterror":""},"video":{"queue":99,"dup":5,"drop":96,"enc":23,"input":{"state":"running","packet":248,"size_kb":1250,"time":8},"output":{"state":"running","packet":149,"size_kb":748,"time":5},"codec":"h264","profile":578,"level":31,"pix_fmt":"yuv420p","width":1280,"height":720},"audio":{"queue":175,"dup":0,"drop":0,"enc":1,"input":{"state":"running","packet":431,"size_kb":4,"time":8},"output":{"state":"running","packet":256,"size_kb":1,"time":5},"codec":"aac","profile":1,"level":-99,"sample_fmt":"fltp","sampling_hz":44100,"layout":"mono","channels":1}}`)) + + progress := parser.Progress() + + require.Equal(t, Progress{ + Started: true, + Input: []ProgressIO{ + { + Address: "playout:https://cdn.livespotting.com/vpu/e9slfpe3/z60wzayk.m3u8", + Index: 0, + Stream: 0, + Format: "playout", + Type: "video", + Codec: "h264", + Coder: "h264", + Frame: 7, + Keyframe: 1, + FPS: 0, + Packet: 11, + PPS: 0, + Size: 42, + Bitrate: 0, + Pixfmt: "yuvj420p", + Quantizer: 0, + Width: 1280, + Height: 720, + Sampling: 0, + Layout: "", + Channels: 0, + AVstream: &AVstream{ + Input: AVstreamIO{ + State: "running", + Packet: 248, + Time: 8, + Size: 1250 * 1024, + }, + Output: AVstreamIO{ + State: "running", + Packet: 149, + Time: 5, + Size: 748 * 1024, + }, + Aqueue: 0, + Queue: 99, + Dup: 5, + Drop: 96, + Enc: 23, + Looping: true, + Duplicating: true, + GOP: "key", + Mode: "live", + Swap: AVStreamSwap{ + URL: "", + Status: "waiting", + LastURL: "", + LastError: "", + }, + Codec: "h264", + Profile: 578, + Level: 31, + Pixfmt: "yuv420p", + Width: 1280, + Height: 720, + }, + }, + }, + Output: []ProgressIO{ + { + Address: "/dev/null", + Index: 0, + Stream: 0, + Format: "flv", + Type: "video", + Codec: "h264", + Coder: "libx264", + Frame: 7, + Keyframe: 1, + FPS: 0, + Packet: 0, + PPS: 0, + Size: 5, + Bitrate: 0, + Extradata: 32, + Pixfmt: "yuvj420p", + Quantizer: 0, + Width: 1280, + Height: 720, + Sampling: 0, + Layout: "", + Channels: 0, + AVstream: nil, + }, + { + Address: "/dev/null", + Index: 1, + Stream: 0, + Format: "mp4", + Type: "video", + Codec: "h264", + Coder: "copy", + Frame: 11, + FPS: 0, + Packet: 11, + PPS: 0, + Size: 231424, + Bitrate: 0, + Pixfmt: "yuvj420p", + Quantizer: -1, + Width: 1280, + Height: 720, + Sampling: 0, + Layout: "", + Channels: 0, + AVstream: nil, + }, + }, + Frame: 7, + Packet: 0, + FPS: 0, + PPS: 0, + Quantizer: 0, + Size: 231424, + Time: 0.56, + Bitrate: 0, + Speed: 0.4, + Drop: 0, + Dup: 0, + }, progress) +} + +func TestParserProgressPlayoutAudioVideo(t *testing.T) { + parser := New(Config{ + LogLines: 20, + }).(*parser) + + parser.Parse([]byte(`ffmpeg.inputs:[{"url":"playout:http://192.168.1.220/memfs/source.m3u8","format":"playout","index":0,"stream":0,"type":"video","codec":"h264","coder":"h264","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","profile":578,"level":31,"fps":25.000000,"pix_fmt":"yuv420p","width":1280,"height":720},{"url":"playout:http://192.168.1.220/memfs/source.m3u8","format":"playout","index":0,"stream":1,"type":"audio","codec":"aac","coder":"aac","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","profile":1,"level":-99,"sample_fmt":"fltp","sampling_hz":44100,"layout":"mono","channels":1}]`)) + parser.Parse([]byte(`ffmpeg.outputs:[{"url":"pipe:","format":"null","index":0,"stream":0,"type":"video","codec":"h264","coder":"copy","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","profile":578,"level":31,"fps":25.000000,"pix_fmt":"yuv420p","width":1280,"height":720},{"url":"pipe:","format":"null","index":0,"stream":1,"type":"audio","codec":"aac","coder":"copy","bitrate_kbps":0,"duration_sec":0.000000,"language":"und","profile":1,"level":-99,"sample_fmt":"fltp","sampling_hz":44100,"layout":"mono","channels":1}]`)) + parser.Parse([]byte(`ffmpeg.progress:{"inputs":[{"index":0,"stream":0,"framerate":{"min":25.000,"max":25.000,"avg":25.000},"gop":{"min":50.000,"max":50.000,"avg":50.000},"frame":101,"keyframe":3,"packet":101,"size_kb":518,"size_bytes":530273},{"index":0,"stream":1,"framerate":{"min":43.083,"max":43.083,"avg":43.083},"gop":{"min":1.000,"max":1.000,"avg":1.000},"frame":174,"keyframe":174,"packet":174,"size_kb":1,"size_bytes":713}],"outputs":[{"index":0,"stream":0,"framerate":{"min":25.000,"max":25.000,"avg":25.000},"gop":{"min":50.000,"max":50.000,"avg":50.000},"frame":101,"keyframe":3,"packet":101,"q":-1.0,"size_kb":518,"size_bytes":530273,"extradata_size_bytes":0},{"index":0,"stream":1,"framerate":{"min":43.083,"max":43.083,"avg":43.083},"gop":{"min":1.000,"max":1.000,"avg":1.000},"frame":174,"keyframe":174,"packet":174,"size_kb":1,"size_bytes":713,"extradata_size_bytes":0}],"frame":101,"packet":101,"q":-1.0,"size_kb":519,"size_bytes":530986,"time":"0h0m4.3s","speed":1,"dup":0,"drop":0}`)) + parser.Parse([]byte(`avstream.progress:{"id":"playout:http://192.168.1.220/memfs/source.m3u8","url":"http://192.168.1.220/memfs/source.m3u8","stream":0,"queue":124,"aqueue":218,"dup":0,"drop":0,"enc":0,"looping":true,"looping_runtime":42,"duplicating":true,"gop":"key","mode":"live","input":{"state":"running","packet":679,"size_kb":1255,"time":7},"output":{"state":"running","packet":337,"size_kb":628,"time":4},"video":{"queue":124,"dup":1,"drop":2,"enc":3,"input":{"state":"running","packet":248,"size_kb":1250,"time":7},"output":{"state":"running","packet":124,"size_kb":627,"time":4},"codec":"h264","profile":578,"level":31,"pix_fmt":"yuv420p","width":1280,"height":720},"audio":{"queue":218,"dup":5,"drop":6,"enc":1,"input":{"state":"running","packet":431,"size_kb":4,"time":7},"output":{"state":"running","packet":213,"size_kb":0,"time":4},"codec":"aac","profile":1,"level":-99,"sample_fmt":"fltp","sampling_hz":44100,"layout":"mono","channels":1},"swap":{"url":"","status":"waiting","lasturl":"","lasterror":""}}`)) + + progress := parser.Progress() + + require.Equal(t, Progress{ + Started: true, + Input: []ProgressIO{ + { + Address: "playout:http://192.168.1.220/memfs/source.m3u8", + Index: 0, + Stream: 0, + Format: "playout", + Type: "video", + Codec: "h264", + Coder: "h264", + Profile: 578, + Level: 31, + Frame: 101, + Keyframe: 3, + Framerate: struct { + Min float64 + Max float64 + Average float64 + }{25, 25, 25}, + FPS: 0, + Packet: 101, + PPS: 0, + Size: 530273, + Bitrate: 0, + Pixfmt: "yuv420p", + Quantizer: 0, + Width: 1280, + Height: 720, + Samplefmt: "", + Sampling: 0, + Layout: "", + Channels: 0, + AVstream: &AVstream{ + Input: AVstreamIO{ + State: "running", + Packet: 248, + Time: 7, + Size: 1250 * 1024, + }, + Output: AVstreamIO{ + State: "running", + Packet: 124, + Time: 4, + Size: 642048, + }, + Aqueue: 0, + Queue: 124, + Dup: 1, + Drop: 2, + Enc: 3, + Looping: true, + LoopingRuntime: 42, + Duplicating: true, + GOP: "key", + Mode: "live", + Swap: AVStreamSwap{ + URL: "", + Status: "waiting", + LastURL: "", + LastError: "", + }, + Codec: "h264", + Profile: 578, + Level: 31, + Pixfmt: "yuv420p", + Width: 1280, + Height: 720, + }, + }, + { + Address: "playout:http://192.168.1.220/memfs/source.m3u8", + Index: 0, + Stream: 1, + Format: "playout", + Type: "audio", + Codec: "aac", + Coder: "aac", + Profile: 1, + Level: -99, + Frame: 174, + Keyframe: 174, + Framerate: struct { + Min float64 + Max float64 + Average float64 + }{43.083, 43.083, 43.083}, + FPS: 0, + Packet: 174, + PPS: 0, + Size: 713, + Bitrate: 0, + Pixfmt: "", + Quantizer: 0, + Width: 0, + Height: 0, + Samplefmt: "fltp", + Sampling: 44100, + Layout: "mono", + Channels: 1, + AVstream: &AVstream{ + Input: AVstreamIO{ + State: "running", + Packet: 431, + Time: 7, + Size: 4096, + }, + Output: AVstreamIO{ + State: "running", + Packet: 213, + Time: 4, + Size: 0, + }, + Aqueue: 0, + Queue: 218, + Dup: 5, + Drop: 6, + Enc: 1, + Looping: true, + LoopingRuntime: 42, + Duplicating: true, + GOP: "key", + Mode: "live", + Swap: AVStreamSwap{ + URL: "", + Status: "waiting", + LastURL: "", + LastError: "", + }, + Codec: "aac", + Profile: 1, + Level: -99, + Pixfmt: "", + Width: 0, + Height: 0, + Samplefmt: "fltp", + Sampling: 44100, + Layout: "mono", + Channels: 1, + }, + }, + }, + Output: []ProgressIO{ + { + Address: "pipe:", + Index: 0, + Stream: 0, + Format: "null", + Type: "video", + Codec: "h264", + Coder: "copy", + Profile: 578, + Level: 31, + Frame: 101, + Keyframe: 3, + Framerate: struct { + Min float64 + Max float64 + Average float64 + }{25, 25, 25}, + FPS: 0, + Packet: 101, + PPS: 0, + Size: 530273, + Bitrate: 0, + Extradata: 0, + Pixfmt: "yuv420p", + Quantizer: -1, + Width: 1280, + Height: 720, + Sampling: 0, + Layout: "", + Channels: 0, + AVstream: nil, + }, + { + Address: "pipe:", + Index: 0, + Stream: 1, + Format: "null", + Type: "audio", + Codec: "aac", + Coder: "copy", + Profile: 1, + Level: -99, + Frame: 174, + Keyframe: 174, + Framerate: struct { + Min float64 + Max float64 + Average float64 + }{43.083, 43.083, 43.083}, + FPS: 0, + Packet: 174, + PPS: 0, + Size: 713, + Bitrate: 0, + Pixfmt: "", + Quantizer: 0, + Width: 0, + Height: 0, + Samplefmt: "fltp", + Sampling: 44100, + Layout: "mono", + Channels: 1, + AVstream: nil, + }, + }, + Frame: 101, + Packet: 101, + FPS: 0, + PPS: 0, + Quantizer: -1, + Size: 530986, + Time: 4.3, + Bitrate: 0, + Speed: 1, + Drop: 0, + Dup: 0, + }, progress) +} + func TestParserStreamMapping(t *testing.T) { parser := New(Config{ LogLines: 20, diff --git a/ffmpeg/parse/types.go b/ffmpeg/parse/types.go index 01ffcc78..a3eb31fc 100644 --- a/ffmpeg/parse/types.go +++ b/ffmpeg/parse/types.go @@ -74,43 +74,97 @@ func (avswap *ffmpegAVStreamSwap) export() AVStreamSwap { } } +type ffmpegAVStreamTrack struct { + Queue uint64 `json:"queue"` + Dup uint64 `json:"dup"` + Drop uint64 `json:"drop"` + Enc uint64 `json:"enc"` + Input ffmpegAVstreamIO `json:"input"` + Output ffmpegAVstreamIO `json:"output"` + Codec string `json:"codec"` + Profile int `json:"profile"` + Level int `json:"level"` + Pixfmt string `json:"pix_fmt"` + Width uint64 `json:"width"` + Height uint64 `json:"height"` + Samplefmt string `json:"sample_fmt"` + Sampling uint64 `json:"sampling_hz"` + Layout string `json:"layout"` + Channels uint64 `json:"channels"` +} + type ffmpegAVstream struct { - Input ffmpegAVstreamIO `json:"input"` - Output ffmpegAVstreamIO `json:"output"` - Address string `json:"id"` - URL string `json:"url"` - Stream uint64 `json:"stream"` - Aqueue uint64 `json:"aqueue"` - Queue uint64 `json:"queue"` - Dup uint64 `json:"dup"` - Drop uint64 `json:"drop"` - Enc uint64 `json:"enc"` - Looping bool `json:"looping"` - LoopingRuntime uint64 `json:"looping_runtime"` - Duplicating bool `json:"duplicating"` - GOP string `json:"gop"` - Mode string `json:"mode"` - Debug interface{} `json:"debug"` - Swap ffmpegAVStreamSwap `json:"swap"` -} - -func (av *ffmpegAVstream) export() *AVstream { - return &AVstream{ - Aqueue: av.Aqueue, - Queue: av.Queue, - Drop: av.Drop, - Dup: av.Dup, - Enc: av.Enc, + Input ffmpegAVstreamIO `json:"input"` + Output ffmpegAVstreamIO `json:"output"` + Audio ffmpegAVStreamTrack `json:"audio"` + Video ffmpegAVStreamTrack `json:"video"` + Address string `json:"id"` + URL string `json:"url"` + Stream uint64 `json:"stream"` + Aqueue uint64 `json:"aqueue"` + Queue uint64 `json:"queue"` + Dup uint64 `json:"dup"` + Drop uint64 `json:"drop"` + Enc uint64 `json:"enc"` + Looping bool `json:"looping"` + LoopingRuntime uint64 `json:"looping_runtime"` + Duplicating bool `json:"duplicating"` + GOP string `json:"gop"` + Mode string `json:"mode"` + Debug interface{} `json:"debug"` + Swap ffmpegAVStreamSwap `json:"swap"` +} + +func (av *ffmpegAVstream) export(trackType string) *AVstream { + avs := &AVstream{ Looping: av.Looping, LoopingRuntime: av.LoopingRuntime, Duplicating: av.Duplicating, GOP: av.GOP, Mode: av.Mode, - Input: av.Input.export(), - Output: av.Output.export(), Debug: av.Debug, Swap: av.Swap.export(), } + + hasTracks := len(av.Video.Codec) != 0 + + if hasTracks { + var track *ffmpegAVStreamTrack = nil + + if trackType == "audio" { + track = &av.Audio + } else { + track = &av.Video + } + + avs.Queue = track.Queue + avs.Drop = track.Drop + avs.Dup = track.Dup + avs.Enc = track.Enc + avs.Input = track.Input.export() + avs.Output = track.Output.export() + + avs.Codec = track.Codec + avs.Profile = track.Profile + avs.Level = track.Level + avs.Pixfmt = track.Pixfmt + avs.Width = track.Width + avs.Height = track.Height + avs.Samplefmt = track.Samplefmt + avs.Sampling = track.Sampling + avs.Layout = track.Layout + avs.Channels = track.Channels + } else { + avs.Queue = av.Queue + avs.Aqueue = av.Aqueue + avs.Drop = av.Drop + avs.Dup = av.Dup + avs.Enc = av.Enc + avs.Input = av.Input.export() + avs.Output = av.Output.export() + } + + return avs } type ffmpegProgressIO struct { @@ -218,6 +272,8 @@ type ffmpegProcessIO struct { Type string `json:"type"` Codec string `json:"codec"` Coder string `json:"coder"` + Profile int `json:"profile"` + Level int `json:"level"` // video Pixfmt string `json:"pix_fmt"` @@ -225,26 +281,30 @@ type ffmpegProcessIO struct { Height uint64 `json:"height"` // audio - Sampling uint64 `json:"sampling_hz"` - Layout string `json:"layout"` - Channels uint64 `json:"channels"` + Samplefmt string `json:"sample_fmt"` + Sampling uint64 `json:"sampling_hz"` + Layout string `json:"layout"` + Channels uint64 `json:"channels"` } func (io *ffmpegProcessIO) export() ProgressIO { return ProgressIO{ - Address: io.Address, - Format: io.Format, - Index: io.Index, - Stream: io.Stream, - Type: io.Type, - Codec: io.Codec, - Coder: io.Coder, - Pixfmt: io.Pixfmt, - Width: io.Width, - Height: io.Height, - Sampling: io.Sampling, - Layout: io.Layout, - Channels: io.Channels, + Address: io.Address, + Format: io.Format, + Index: io.Index, + Stream: io.Stream, + Type: io.Type, + Codec: io.Codec, + Coder: io.Coder, + Profile: io.Profile, + Level: io.Level, + Pixfmt: io.Pixfmt, + Width: io.Width, + Height: io.Height, + Samplefmt: io.Samplefmt, + Sampling: io.Sampling, + Layout: io.Layout, + Channels: io.Channels, } } @@ -422,6 +482,8 @@ type ProgressIO struct { Type string Codec string Coder string + Profile int + Level int Frame uint64 Keyframe uint64 Framerate struct { @@ -443,9 +505,10 @@ type ProgressIO struct { Height uint64 // Audio - Sampling uint64 - Layout string - Channels uint64 + Samplefmt string + Sampling uint64 // Hz + Layout string // mono, stereo, ... + Channels uint64 // avstream AVstream *AVstream @@ -498,6 +561,16 @@ type AVstream struct { Mode string Debug interface{} Swap AVStreamSwap + Codec string + Profile int + Level int + Pixfmt string + Width uint64 + Height uint64 + Samplefmt string + Sampling uint64 + Layout string + Channels uint64 } type Usage struct { From 1a51db01ea4d562f99ba7c772102ee483e9be101 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 5 Sep 2024 13:40:04 +0200 Subject: [PATCH 13/64] Add sample_fmt to API progress --- http/api/progress.go | 9 ++++++--- http/api/progress_test.go | 4 ++++ restream/app/progress.go | 9 ++++++--- restream/app/progress_test.go | 4 ++++ 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/http/api/progress.go b/http/api/progress.go index 93671b57..dc1bdd5b 100644 --- a/http/api/progress.go +++ b/http/api/progress.go @@ -40,9 +40,10 @@ type ProgressIO struct { Height uint64 `json:"height,omitempty" format:"uint64"` // Audio - Sampling uint64 `json:"sampling_hz,omitempty" format:"uint64"` - Layout string `json:"layout,omitempty"` - Channels uint64 `json:"channels,omitempty" format:"uint64"` + Samplefmt string `json:"sample_fmt,omitempty"` + Sampling uint64 `json:"sampling_hz,omitempty" format:"uint64"` + Layout string `json:"layout,omitempty"` + Channels uint64 `json:"channels,omitempty" format:"uint64"` // avstream AVstream *AVstream `json:"avstream" jsonschema:"anyof_type=null;object"` @@ -77,6 +78,7 @@ func (i *ProgressIO) Unmarshal(io *app.ProgressIO) { i.Quantizer = json.ToNumber(io.Quantizer) i.Width = io.Width i.Height = io.Height + i.Samplefmt = io.Samplefmt i.Sampling = io.Sampling i.Layout = io.Layout i.Channels = io.Channels @@ -105,6 +107,7 @@ func (i *ProgressIO) Marshal() app.ProgressIO { Pixfmt: i.Pixfmt, Width: i.Width, Height: i.Height, + Samplefmt: i.Samplefmt, Sampling: i.Sampling, Layout: i.Layout, Channels: i.Channels, diff --git a/http/api/progress_test.go b/http/api/progress_test.go index 812e434c..06837e10 100644 --- a/http/api/progress_test.go +++ b/http/api/progress_test.go @@ -114,6 +114,7 @@ func TestProgressIO(t *testing.T) { Quantizer: 494.2, Width: 10393, Height: 4933, + Samplefmt: "fltp", Sampling: 58483, Layout: "atmos", Channels: 4944, @@ -154,6 +155,7 @@ func TestProgressIOAVstream(t *testing.T) { Quantizer: 494.2, Width: 10393, Height: 4933, + Samplefmt: "fltp", Sampling: 58483, Layout: "atmos", Channels: 4944, @@ -220,6 +222,7 @@ func TestProgress(t *testing.T) { Quantizer: 494.2, Width: 10393, Height: 4933, + Samplefmt: "fltp", Sampling: 58483, Layout: "atmos", Channels: 4944, @@ -276,6 +279,7 @@ func TestProgress(t *testing.T) { Quantizer: 494.2, Width: 10393, Height: 4933, + Samplefmt: "fltp", Sampling: 58483, Layout: "atmos", Channels: 4944, diff --git a/restream/app/progress.go b/restream/app/progress.go index d747ba67..c98ad0fd 100644 --- a/restream/app/progress.go +++ b/restream/app/progress.go @@ -36,9 +36,10 @@ type ProgressIO struct { Height uint64 // Audio - Sampling uint64 - Layout string - Channels uint64 + Samplefmt string + Sampling uint64 + Layout string + Channels uint64 // avstream AVstream *AVstream @@ -65,6 +66,7 @@ func (p *ProgressIO) UnmarshalParser(pp *parse.ProgressIO) { p.Quantizer = pp.Quantizer p.Width = pp.Width p.Height = pp.Height + p.Samplefmt = pp.Samplefmt p.Sampling = pp.Sampling p.Layout = pp.Layout p.Channels = pp.Channels @@ -99,6 +101,7 @@ func (p *ProgressIO) MarshalParser() parse.ProgressIO { Quantizer: p.Quantizer, Width: p.Width, Height: p.Height, + Samplefmt: p.Samplefmt, Sampling: p.Sampling, Layout: p.Layout, Channels: p.Channels, diff --git a/restream/app/progress_test.go b/restream/app/progress_test.go index bfffa293..2cee5173 100644 --- a/restream/app/progress_test.go +++ b/restream/app/progress_test.go @@ -37,6 +37,7 @@ func TestProgressIO(t *testing.T) { Quantizer: 2.3, Width: 4848, Height: 9373, + Samplefmt: "fltp", Sampling: 4733, Layout: "atmos", Channels: 83, @@ -81,6 +82,7 @@ func TestProgressIOWithAVstream(t *testing.T) { Quantizer: 2.3, Width: 4848, Height: 9373, + Samplefmt: "fltp", Sampling: 4733, Layout: "atmos", Channels: 83, @@ -238,6 +240,7 @@ func TestProgress(t *testing.T) { Quantizer: 2.3, Width: 4848, Height: 9373, + Samplefmt: "fltp", Sampling: 4733, Layout: "atmos", Channels: 83, @@ -304,6 +307,7 @@ func TestProgress(t *testing.T) { Quantizer: 2.3, Width: 4848, Height: 9373, + Samplefmt: "fltp", Sampling: 4733, Layout: "atmos", Channels: 83, From bc1b2cf76b5d1fd8ac104113d0cdaacbb49b956a Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 5 Sep 2024 13:44:19 +0200 Subject: [PATCH 14/64] Add profile and level to progress IO --- http/api/progress.go | 6 ++++++ http/api/progress_test.go | 8 ++++++++ restream/app/progress.go | 6 ++++++ restream/app/progress_test.go | 8 ++++++++ 4 files changed, 28 insertions(+) diff --git a/http/api/progress.go b/http/api/progress.go index dc1bdd5b..05976060 100644 --- a/http/api/progress.go +++ b/http/api/progress.go @@ -23,6 +23,8 @@ type ProgressIO struct { Type string `json:"type"` Codec string `json:"codec"` Coder string `json:"coder"` + Profile int `json:"profile"` + Level int `json:"level"` Frame uint64 `json:"frame" format:"uint64"` Keyframe uint64 `json:"keyframe" format:"uint64"` Framerate ProgressIOFramerate `json:"framerate"` @@ -63,6 +65,8 @@ func (i *ProgressIO) Unmarshal(io *app.ProgressIO) { i.Type = io.Type i.Codec = io.Codec i.Coder = io.Coder + i.Profile = io.Profile + i.Level = io.Level i.Frame = io.Frame i.Keyframe = io.Keyframe i.Framerate.Min = json.ToNumber(io.Framerate.Min) @@ -99,6 +103,8 @@ func (i *ProgressIO) Marshal() app.ProgressIO { Type: i.Type, Codec: i.Codec, Coder: i.Coder, + Profile: i.Profile, + Level: i.Level, Frame: i.Frame, Keyframe: i.Keyframe, Packet: i.Packet, diff --git a/http/api/progress_test.go b/http/api/progress_test.go index 06837e10..3ccd7926 100644 --- a/http/api/progress_test.go +++ b/http/api/progress_test.go @@ -97,6 +97,8 @@ func TestProgressIO(t *testing.T) { Type: "video", Codec: "x", Coder: "y", + Profile: 848, + Level: 48, Frame: 133, Keyframe: 39, Framerate: app.ProgressIOFramerate{ @@ -138,6 +140,8 @@ func TestProgressIOAVstream(t *testing.T) { Type: "video", Codec: "x", Coder: "y", + Profile: 848, + Level: 48, Frame: 133, Keyframe: 39, Framerate: app.ProgressIOFramerate{ @@ -205,6 +209,8 @@ func TestProgress(t *testing.T) { Type: "video", Codec: "x", Coder: "y", + Profile: 848, + Level: 48, Frame: 133, Keyframe: 39, Framerate: app.ProgressIOFramerate{ @@ -262,6 +268,8 @@ func TestProgress(t *testing.T) { Type: "video", Codec: "x", Coder: "y", + Profile: 848, + Level: 48, Frame: 133, Keyframe: 39, Framerate: app.ProgressIOFramerate{ diff --git a/restream/app/progress.go b/restream/app/progress.go index c98ad0fd..e0681b01 100644 --- a/restream/app/progress.go +++ b/restream/app/progress.go @@ -19,6 +19,8 @@ type ProgressIO struct { Type string Codec string Coder string + Profile int + Level int Frame uint64 // counter Keyframe uint64 // counter Framerate ProgressIOFramerate @@ -53,6 +55,8 @@ func (p *ProgressIO) UnmarshalParser(pp *parse.ProgressIO) { p.Type = pp.Type p.Codec = pp.Codec p.Coder = pp.Coder + p.Profile = pp.Profile + p.Level = pp.Level p.Frame = pp.Frame p.Keyframe = pp.Keyframe p.Framerate = pp.Framerate @@ -88,6 +92,8 @@ func (p *ProgressIO) MarshalParser() parse.ProgressIO { Type: p.Type, Codec: p.Codec, Coder: p.Coder, + Profile: p.Profile, + Level: p.Level, Frame: p.Frame, Keyframe: p.Keyframe, Framerate: p.Framerate, diff --git a/restream/app/progress_test.go b/restream/app/progress_test.go index 2cee5173..b8cce1ba 100644 --- a/restream/app/progress_test.go +++ b/restream/app/progress_test.go @@ -16,6 +16,8 @@ func TestProgressIO(t *testing.T) { Type: "video", Codec: "h264", Coder: "libx264", + Profile: 848, + Level: 48, Frame: 39, Keyframe: 433, Framerate: struct { @@ -61,6 +63,8 @@ func TestProgressIOWithAVstream(t *testing.T) { Type: "video", Codec: "h264", Coder: "libx264", + Profile: 848, + Level: 48, Frame: 39, Keyframe: 433, Framerate: struct { @@ -219,6 +223,8 @@ func TestProgress(t *testing.T) { Type: "video", Codec: "h264", Coder: "libx264", + Profile: 848, + Level: 48, Frame: 39, Keyframe: 433, Framerate: struct { @@ -286,6 +292,8 @@ func TestProgress(t *testing.T) { Type: "video", Codec: "h264", Coder: "libx264", + Profile: 848, + Level: 48, Frame: 39, Keyframe: 433, Framerate: struct { From b0e932d77a486e75eb3e357a0e93d6e882cfe3e0 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 5 Sep 2024 13:56:22 +0200 Subject: [PATCH 15/64] Add avstream codec parameter --- http/api/avstream.go | 37 ++++++++++++++++++++++++++++++++-- http/api/avstream_test.go | 10 +++++++++ restream/app/avstream.go | 38 +++++++++++++++++++++++++++++++++-- restream/app/avstream_test.go | 10 +++++++++ 4 files changed, 91 insertions(+), 4 deletions(-) diff --git a/http/api/avstream.go b/http/api/avstream.go index 5af4e90d..5948d220 100644 --- a/http/api/avstream.go +++ b/http/api/avstream.go @@ -46,6 +46,18 @@ type AVstream struct { Duplicating bool `json:"duplicating"` GOP string `json:"gop"` Mode string `json:"mode"` + + // Codec parameter + Codec string `json:"codec"` + Profile int `json:"profile"` + Level int `json:"level"` + Pixfmt string `json:"pix_fmt"` + Width uint64 `json:"width" format:"uint64"` + Height uint64 `json:"height" format:"uint64"` + Samplefmt string `json:"sample_fmt"` + Sampling uint64 `json:"sampling_hz" format:"uint64"` + Layout string `json:"layout"` + Channels uint64 `json:"channels" format:"uint64"` } func (a *AVstream) Unmarshal(av *app.AVstream) { @@ -53,6 +65,9 @@ func (a *AVstream) Unmarshal(av *app.AVstream) { return } + a.Input.Unmarshal(&av.Input) + a.Output.Unmarshal(&av.Output) + a.Aqueue = av.Aqueue a.Queue = av.Queue a.Dup = av.Dup @@ -64,8 +79,16 @@ func (a *AVstream) Unmarshal(av *app.AVstream) { a.GOP = av.GOP a.Mode = av.Mode - a.Input.Unmarshal(&av.Input) - a.Output.Unmarshal(&av.Output) + a.Codec = av.Codec + a.Profile = av.Profile + a.Level = av.Level + a.Pixfmt = av.Pixfmt + a.Width = av.Width + a.Height = av.Height + a.Samplefmt = av.Samplefmt + a.Sampling = av.Sampling + a.Layout = av.Layout + a.Channels = av.Channels } func (a *AVstream) Marshal() *app.AVstream { @@ -82,6 +105,16 @@ func (a *AVstream) Marshal() *app.AVstream { Duplicating: a.Duplicating, GOP: a.GOP, Mode: a.Mode, + Codec: a.Codec, + Profile: a.Profile, + Level: a.Level, + Pixfmt: a.Pixfmt, + Width: a.Width, + Height: a.Height, + Samplefmt: a.Samplefmt, + Sampling: a.Sampling, + Layout: a.Layout, + Channels: a.Channels, } return av diff --git a/http/api/avstream_test.go b/http/api/avstream_test.go index f8bb4d73..198a9153 100644 --- a/http/api/avstream_test.go +++ b/http/api/avstream_test.go @@ -47,6 +47,16 @@ func TestAVStream(t *testing.T) { Duplicating: true, GOP: "gop", Mode: "mode", + Codec: "h264", + Profile: 858, + Level: 64, + Pixfmt: "yuv420p", + Width: 1920, + Height: 1080, + Samplefmt: "fltp", + Sampling: 44100, + Layout: "stereo", + Channels: 42, } p := AVstream{} diff --git a/restream/app/avstream.go b/restream/app/avstream.go index 2a5deb5e..9ff088ff 100644 --- a/restream/app/avstream.go +++ b/restream/app/avstream.go @@ -67,6 +67,18 @@ type AVstream struct { Mode string // "file" or "live" Debug interface{} Swap AVStreamSwap + + // Codec parameter + Codec string + Profile int + Level int + Pixfmt string + Width uint64 + Height uint64 + Samplefmt string + Sampling uint64 + Layout string + Channels uint64 } func (a *AVstream) UnmarshalParser(p *parse.AVstream) { @@ -74,6 +86,9 @@ func (a *AVstream) UnmarshalParser(p *parse.AVstream) { return } + a.Input.UnmarshalParser(&p.Input) + a.Output.UnmarshalParser(&p.Output) + a.Aqueue = p.Aqueue a.Queue = p.Queue a.Dup = p.Dup @@ -85,8 +100,17 @@ func (a *AVstream) UnmarshalParser(p *parse.AVstream) { a.GOP = p.GOP a.Mode = p.Mode a.Swap.UnmarshalParser(&p.Swap) - a.Input.UnmarshalParser(&p.Input) - a.Output.UnmarshalParser(&p.Output) + + a.Codec = p.Codec + a.Profile = p.Profile + a.Level = p.Level + a.Pixfmt = p.Pixfmt + a.Width = p.Width + a.Height = p.Height + a.Samplefmt = p.Samplefmt + a.Sampling = p.Sampling + a.Layout = p.Layout + a.Channels = p.Channels } func (a *AVstream) MarshalParser() *parse.AVstream { @@ -105,6 +129,16 @@ func (a *AVstream) MarshalParser() *parse.AVstream { Mode: a.Mode, Debug: a.Debug, Swap: a.Swap.MarshalParser(), + Codec: a.Codec, + Profile: a.Profile, + Level: a.Level, + Pixfmt: a.Pixfmt, + Width: a.Width, + Height: a.Height, + Samplefmt: a.Samplefmt, + Sampling: a.Sampling, + Layout: a.Layout, + Channels: a.Channels, } return p diff --git a/restream/app/avstream_test.go b/restream/app/avstream_test.go index 834aad0a..ea6a229d 100644 --- a/restream/app/avstream_test.go +++ b/restream/app/avstream_test.go @@ -68,6 +68,16 @@ func TestAVstream(t *testing.T) { LastURL: "fjfd", LastError: "none", }, + Codec: "h264", + Profile: 858, + Level: 64, + Pixfmt: "yuv420p", + Width: 1920, + Height: 1080, + Samplefmt: "fltp", + Sampling: 44100, + Layout: "stereo", + Channels: 42, } p := AVstream{} From 0327edcaf356919f220a4162fb12f2eb2c1ed33a Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 5 Sep 2024 13:58:59 +0200 Subject: [PATCH 16/64] Update openapi specification --- cluster/docs/ClusterAPI_docs.go | 117 +++++++++++---- cluster/docs/ClusterAPI_swagger.json | 114 +++++++++++---- cluster/docs/ClusterAPI_swagger.yaml | 77 +++++++--- docs/docs.go | 203 +++++++++++++++++++++++---- docs/swagger.json | 200 ++++++++++++++++++++++---- docs/swagger.yaml | 139 +++++++++++++++--- http/handler/api/cluster_node.go | 2 +- 7 files changed, 699 insertions(+), 153 deletions(-) diff --git a/cluster/docs/ClusterAPI_docs.go b/cluster/docs/ClusterAPI_docs.go index be2fd046..2adc2b3c 100644 --- a/cluster/docs/ClusterAPI_docs.go +++ b/cluster/docs/ClusterAPI_docs.go @@ -1,5 +1,4 @@ -// Code generated by swaggo/swag. DO NOT EDIT. - +// Package docs Code generated by swaggo/swag. DO NOT EDIT package docs import "github.com/swaggo/swag" @@ -769,6 +768,64 @@ const docTemplateClusterAPI = `{ } }, "/v1/process/{id}": { + "get": { + "description": "Get a process from the cluster DB", + "produces": [ + "application/json" + ], + "tags": [ + "v1.0.0" + ], + "summary": "Get a process", + "operationId": "cluster-1-get-process", + "parameters": [ + { + "type": "string", + "description": "Process ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Domain to act on", + "name": "domain", + "in": "query" + }, + { + "type": "string", + "description": "Origin ID of request", + "name": "X-Cluster-Origin", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/cluster.Error" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/cluster.Error" + } + }, + "508": { + "description": "Loop Detected", + "schema": { + "$ref": "#/definitions/cluster.Error" + } + } + } + }, "put": { "description": "Replace an existing process in the cluster DB", "consumes": [ @@ -1225,32 +1282,6 @@ const docTemplateClusterAPI = `{ } }, "definitions": { - "access.Policy": { - "type": "object", - "properties": { - "actions": { - "type": "array", - "items": { - "type": "string" - } - }, - "domain": { - "type": "string" - }, - "name": { - "type": "string" - }, - "resource": { - "type": "string" - }, - "types": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, "app.Config": { "type": "object", "properties": { @@ -1285,7 +1316,7 @@ const docTemplateClusterAPI = `{ "type": "integer" }, "logPatterns": { - "description": "will we interpreted as regular expressions", + "description": "will be interpreted as regular expressions", "type": "array", "items": { "type": "string" @@ -1444,7 +1475,7 @@ const docTemplateClusterAPI = `{ "policies": { "type": "array", "items": { - "$ref": "#/definitions/access.Policy" + "$ref": "#/definitions/policy.Policy" } } } @@ -2218,6 +2249,32 @@ const docTemplateClusterAPI = `{ } } }, + "policy.Policy": { + "type": "object", + "properties": { + "actions": { + "type": "array", + "items": { + "type": "string" + } + }, + "domain": { + "type": "string" + }, + "name": { + "type": "string" + }, + "resource": { + "type": "string" + }, + "types": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "skills.Codec": { "type": "object", "properties": { diff --git a/cluster/docs/ClusterAPI_swagger.json b/cluster/docs/ClusterAPI_swagger.json index 70cf3f4e..debab33c 100644 --- a/cluster/docs/ClusterAPI_swagger.json +++ b/cluster/docs/ClusterAPI_swagger.json @@ -761,6 +761,64 @@ } }, "/v1/process/{id}": { + "get": { + "description": "Get a process from the cluster DB", + "produces": [ + "application/json" + ], + "tags": [ + "v1.0.0" + ], + "summary": "Get a process", + "operationId": "cluster-1-get-process", + "parameters": [ + { + "type": "string", + "description": "Process ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Domain to act on", + "name": "domain", + "in": "query" + }, + { + "type": "string", + "description": "Origin ID of request", + "name": "X-Cluster-Origin", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/cluster.Error" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/cluster.Error" + } + }, + "508": { + "description": "Loop Detected", + "schema": { + "$ref": "#/definitions/cluster.Error" + } + } + } + }, "put": { "description": "Replace an existing process in the cluster DB", "consumes": [ @@ -1217,32 +1275,6 @@ } }, "definitions": { - "access.Policy": { - "type": "object", - "properties": { - "actions": { - "type": "array", - "items": { - "type": "string" - } - }, - "domain": { - "type": "string" - }, - "name": { - "type": "string" - }, - "resource": { - "type": "string" - }, - "types": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, "app.Config": { "type": "object", "properties": { @@ -1277,7 +1309,7 @@ "type": "integer" }, "logPatterns": { - "description": "will we interpreted as regular expressions", + "description": "will be interpreted as regular expressions", "type": "array", "items": { "type": "string" @@ -1436,7 +1468,7 @@ "policies": { "type": "array", "items": { - "$ref": "#/definitions/access.Policy" + "$ref": "#/definitions/policy.Policy" } } } @@ -2210,6 +2242,32 @@ } } }, + "policy.Policy": { + "type": "object", + "properties": { + "actions": { + "type": "array", + "items": { + "type": "string" + } + }, + "domain": { + "type": "string" + }, + "name": { + "type": "string" + }, + "resource": { + "type": "string" + }, + "types": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "skills.Codec": { "type": "object", "properties": { diff --git a/cluster/docs/ClusterAPI_swagger.yaml b/cluster/docs/ClusterAPI_swagger.yaml index c0628aca..1e6f1745 100644 --- a/cluster/docs/ClusterAPI_swagger.yaml +++ b/cluster/docs/ClusterAPI_swagger.yaml @@ -1,22 +1,5 @@ basePath: / definitions: - access.Policy: - properties: - actions: - items: - type: string - type: array - domain: - type: string - name: - type: string - resource: - type: string - types: - items: - type: string - type: array - type: object app.Config: properties: autostart: @@ -41,7 +24,7 @@ definitions: description: seconds type: integer logPatterns: - description: will we interpreted as regular expressions + description: will be interpreted as regular expressions items: type: string type: array @@ -145,7 +128,7 @@ definitions: properties: policies: items: - $ref: '#/definitions/access.Policy' + $ref: '#/definitions/policy.Policy' type: array type: object client.SetProcessCommandRequest: @@ -662,6 +645,23 @@ definitions: type: string type: array type: object + policy.Policy: + properties: + actions: + items: + type: string + type: array + domain: + type: string + name: + type: string + resource: + type: string + types: + items: + type: string + type: array + type: object skills.Codec: properties: decoders: @@ -1410,6 +1410,45 @@ paths: summary: Remove a process tags: - v1.0.0 + get: + description: Get a process from the cluster DB + operationId: cluster-1-get-process + parameters: + - description: Process ID + in: path + name: id + required: true + type: string + - description: Domain to act on + in: query + name: domain + type: string + - description: Origin ID of request + in: header + name: X-Cluster-Origin + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + type: string + "404": + description: Not Found + schema: + $ref: '#/definitions/cluster.Error' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/cluster.Error' + "508": + description: Loop Detected + schema: + $ref: '#/definitions/cluster.Error' + summary: Get a process + tags: + - v1.0.0 put: consumes: - application/json diff --git a/docs/docs.go b/docs/docs.go index ee197903..c8d44e9f 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,5 +1,4 @@ -// Code generated by swaggo/swag. DO NOT EDIT. - +// Package docs Code generated by swaggo/swag. DO NOT EDIT package docs import "github.com/swaggo/swag" @@ -404,6 +403,46 @@ const docTemplate = `{ } } }, + "/api/v3/cluster/events": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Stream of events of whats happening on each node in the cluster", + "consumes": [ + "application/json" + ], + "produces": [ + "text/event-stream", + "application/x-json-stream" + ], + "tags": [ + "v16.?.?" + ], + "summary": "Stream of events", + "operationId": "cluster-3-events", + "parameters": [ + { + "description": "Event filters", + "name": "filters", + "in": "body", + "schema": { + "$ref": "#/definitions/api.EventFilters" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.Event" + } + } + } + } + }, "/api/v3/cluster/fs/{storage}": { "get": { "security": [ @@ -1540,7 +1579,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api.Version" + "$ref": "#/definitions/api.AboutVersion" } }, "404": { @@ -5039,6 +5078,14 @@ const docTemplate = `{ "type": "integer", "format": "uint64" }, + "channels": { + "type": "integer", + "format": "uint64" + }, + "codec": { + "description": "Codec parameter", + "type": "string" + }, "drop": { "type": "integer", "format": "uint64" @@ -5057,9 +5104,19 @@ const docTemplate = `{ "gop": { "type": "string" }, + "height": { + "type": "integer", + "format": "uint64" + }, "input": { "$ref": "#/definitions/api.AVstreamIO" }, + "layout": { + "type": "string" + }, + "level": { + "type": "integer" + }, "looping": { "type": "boolean" }, @@ -5073,9 +5130,26 @@ const docTemplate = `{ "output": { "$ref": "#/definitions/api.AVstreamIO" }, + "pix_fmt": { + "type": "string" + }, + "profile": { + "type": "integer" + }, "queue": { "type": "integer", "format": "uint64" + }, + "sample_fmt": { + "type": "string" + }, + "sampling_hz": { + "type": "integer", + "format": "uint64" + }, + "width": { + "type": "integer", + "format": "uint64" } } }, @@ -5123,11 +5197,79 @@ const docTemplate = `{ "name": { "type": "string" }, + "resources": { + "$ref": "#/definitions/api.AboutResources" + }, "uptime_seconds": { "type": "integer" }, "version": { - "$ref": "#/definitions/api.Version" + "$ref": "#/definitions/api.AboutVersion" + } + } + }, + "api.AboutResources": { + "type": "object", + "properties": { + "cpu_core": { + "description": "Current CPU load of the core itself, 0-100*ncpu", + "type": "number" + }, + "cpu_limit": { + "description": "Defined CPU load limit, 0-100*ncpu", + "type": "number" + }, + "cpu_used": { + "description": "Current CPU load, 0-100*ncpu", + "type": "number" + }, + "is_throttling": { + "description": "Whether this core is currently throttling", + "type": "boolean" + }, + "memory_core_bytes": { + "description": "Current used memory of the core itself in bytes", + "type": "integer" + }, + "memory_limit_bytes": { + "description": "Defined memory limit in bytes", + "type": "integer" + }, + "memory_total_bytes": { + "description": "Total available memory in bytes", + "type": "integer" + }, + "memory_used_bytes": { + "description": "Currently used memory in bytes", + "type": "integer" + }, + "ncpu": { + "description": "Number of CPU on this node", + "type": "number" + } + } + }, + "api.AboutVersion": { + "type": "object", + "properties": { + "arch": { + "type": "string" + }, + "build_date": { + "description": "RFC3339", + "type": "string" + }, + "compiler": { + "type": "string" + }, + "number": { + "type": "string" + }, + "repository_branch": { + "type": "string" + }, + "repository_commit": { + "type": "string" } } }, @@ -5313,6 +5455,10 @@ const docTemplate = `{ "api.ClusterNodeResources": { "type": "object", "properties": { + "cpu_core": { + "description": "percent 0-100*ncpu", + "type": "number" + }, "cpu_limit": { "description": "percent 0-100*npcu", "type": "number" @@ -5327,10 +5473,18 @@ const docTemplate = `{ "is_throttling": { "type": "boolean" }, + "memory_core_bytes": { + "description": "bytes", + "type": "integer" + }, "memory_limit_bytes": { "description": "bytes", "type": "integer" }, + "memory_total_bytes": { + "description": "bytes", + "type": "integer" + }, "memory_used_bytes": { "description": "bytes", "type": "integer" @@ -6089,6 +6243,9 @@ const docTemplate = `{ "caller": { "type": "string" }, + "core_id": { + "type": "string" + }, "data": { "type": "object", "additionalProperties": { @@ -6116,6 +6273,9 @@ const docTemplate = `{ "caller": { "type": "string" }, + "core_id": { + "type": "string" + }, "data": { "type": "object", "additionalProperties": { @@ -7243,6 +7403,9 @@ const docTemplate = `{ "layout": { "type": "string" }, + "level": { + "type": "integer" + }, "packet": { "type": "integer", "format": "uint64" @@ -7254,11 +7417,17 @@ const docTemplate = `{ "pps": { "type": "number" }, + "profile": { + "type": "integer" + }, "q": { "type": "number" }, - "sampling_hz": { + "sample_fmt": { "description": "Audio", + "type": "string" + }, + "sampling_hz": { "type": "integer", "format": "uint64" }, @@ -8671,30 +8840,6 @@ const docTemplate = `{ } } }, - "api.Version": { - "type": "object", - "properties": { - "arch": { - "type": "string" - }, - "build_date": { - "description": "RFC3339", - "type": "string" - }, - "compiler": { - "type": "string" - }, - "number": { - "type": "string" - }, - "repository_branch": { - "type": "string" - }, - "repository_commit": { - "type": "string" - } - } - }, "api.WidgetProcess": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index ba1fbbe4..eab2ce04 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -396,6 +396,46 @@ } } }, + "/api/v3/cluster/events": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Stream of events of whats happening on each node in the cluster", + "consumes": [ + "application/json" + ], + "produces": [ + "text/event-stream", + "application/x-json-stream" + ], + "tags": [ + "v16.?.?" + ], + "summary": "Stream of events", + "operationId": "cluster-3-events", + "parameters": [ + { + "description": "Event filters", + "name": "filters", + "in": "body", + "schema": { + "$ref": "#/definitions/api.EventFilters" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/api.Event" + } + } + } + } + }, "/api/v3/cluster/fs/{storage}": { "get": { "security": [ @@ -1532,7 +1572,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api.Version" + "$ref": "#/definitions/api.AboutVersion" } }, "404": { @@ -5031,6 +5071,14 @@ "type": "integer", "format": "uint64" }, + "channels": { + "type": "integer", + "format": "uint64" + }, + "codec": { + "description": "Codec parameter", + "type": "string" + }, "drop": { "type": "integer", "format": "uint64" @@ -5049,9 +5097,19 @@ "gop": { "type": "string" }, + "height": { + "type": "integer", + "format": "uint64" + }, "input": { "$ref": "#/definitions/api.AVstreamIO" }, + "layout": { + "type": "string" + }, + "level": { + "type": "integer" + }, "looping": { "type": "boolean" }, @@ -5065,9 +5123,26 @@ "output": { "$ref": "#/definitions/api.AVstreamIO" }, + "pix_fmt": { + "type": "string" + }, + "profile": { + "type": "integer" + }, "queue": { "type": "integer", "format": "uint64" + }, + "sample_fmt": { + "type": "string" + }, + "sampling_hz": { + "type": "integer", + "format": "uint64" + }, + "width": { + "type": "integer", + "format": "uint64" } } }, @@ -5115,11 +5190,79 @@ "name": { "type": "string" }, + "resources": { + "$ref": "#/definitions/api.AboutResources" + }, "uptime_seconds": { "type": "integer" }, "version": { - "$ref": "#/definitions/api.Version" + "$ref": "#/definitions/api.AboutVersion" + } + } + }, + "api.AboutResources": { + "type": "object", + "properties": { + "cpu_core": { + "description": "Current CPU load of the core itself, 0-100*ncpu", + "type": "number" + }, + "cpu_limit": { + "description": "Defined CPU load limit, 0-100*ncpu", + "type": "number" + }, + "cpu_used": { + "description": "Current CPU load, 0-100*ncpu", + "type": "number" + }, + "is_throttling": { + "description": "Whether this core is currently throttling", + "type": "boolean" + }, + "memory_core_bytes": { + "description": "Current used memory of the core itself in bytes", + "type": "integer" + }, + "memory_limit_bytes": { + "description": "Defined memory limit in bytes", + "type": "integer" + }, + "memory_total_bytes": { + "description": "Total available memory in bytes", + "type": "integer" + }, + "memory_used_bytes": { + "description": "Currently used memory in bytes", + "type": "integer" + }, + "ncpu": { + "description": "Number of CPU on this node", + "type": "number" + } + } + }, + "api.AboutVersion": { + "type": "object", + "properties": { + "arch": { + "type": "string" + }, + "build_date": { + "description": "RFC3339", + "type": "string" + }, + "compiler": { + "type": "string" + }, + "number": { + "type": "string" + }, + "repository_branch": { + "type": "string" + }, + "repository_commit": { + "type": "string" } } }, @@ -5305,6 +5448,10 @@ "api.ClusterNodeResources": { "type": "object", "properties": { + "cpu_core": { + "description": "percent 0-100*ncpu", + "type": "number" + }, "cpu_limit": { "description": "percent 0-100*npcu", "type": "number" @@ -5319,10 +5466,18 @@ "is_throttling": { "type": "boolean" }, + "memory_core_bytes": { + "description": "bytes", + "type": "integer" + }, "memory_limit_bytes": { "description": "bytes", "type": "integer" }, + "memory_total_bytes": { + "description": "bytes", + "type": "integer" + }, "memory_used_bytes": { "description": "bytes", "type": "integer" @@ -6081,6 +6236,9 @@ "caller": { "type": "string" }, + "core_id": { + "type": "string" + }, "data": { "type": "object", "additionalProperties": { @@ -6108,6 +6266,9 @@ "caller": { "type": "string" }, + "core_id": { + "type": "string" + }, "data": { "type": "object", "additionalProperties": { @@ -7235,6 +7396,9 @@ "layout": { "type": "string" }, + "level": { + "type": "integer" + }, "packet": { "type": "integer", "format": "uint64" @@ -7246,11 +7410,17 @@ "pps": { "type": "number" }, + "profile": { + "type": "integer" + }, "q": { "type": "number" }, - "sampling_hz": { + "sample_fmt": { "description": "Audio", + "type": "string" + }, + "sampling_hz": { "type": "integer", "format": "uint64" }, @@ -8663,30 +8833,6 @@ } } }, - "api.Version": { - "type": "object", - "properties": { - "arch": { - "type": "string" - }, - "build_date": { - "description": "RFC3339", - "type": "string" - }, - "compiler": { - "type": "string" - }, - "number": { - "type": "string" - }, - "repository_branch": { - "type": "string" - }, - "repository_commit": { - "type": "string" - } - } - }, "api.WidgetProcess": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 61e9d8b2..5a5c0746 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -5,6 +5,12 @@ definitions: aqueue: format: uint64 type: integer + channels: + format: uint64 + type: integer + codec: + description: Codec parameter + type: string drop: format: uint64 type: integer @@ -18,8 +24,15 @@ definitions: type: integer gop: type: string + height: + format: uint64 + type: integer input: $ref: '#/definitions/api.AVstreamIO' + layout: + type: string + level: + type: integer looping: type: boolean looping_runtime: @@ -29,9 +42,21 @@ definitions: type: string output: $ref: '#/definitions/api.AVstreamIO' + pix_fmt: + type: string + profile: + type: integer queue: format: uint64 type: integer + sample_fmt: + type: string + sampling_hz: + format: uint64 + type: integer + width: + format: uint64 + type: integer type: object api.AVstreamIO: properties: @@ -63,10 +88,58 @@ definitions: type: string name: type: string + resources: + $ref: '#/definitions/api.AboutResources' uptime_seconds: type: integer version: - $ref: '#/definitions/api.Version' + $ref: '#/definitions/api.AboutVersion' + type: object + api.AboutResources: + properties: + cpu_core: + description: Current CPU load of the core itself, 0-100*ncpu + type: number + cpu_limit: + description: Defined CPU load limit, 0-100*ncpu + type: number + cpu_used: + description: Current CPU load, 0-100*ncpu + type: number + is_throttling: + description: Whether this core is currently throttling + type: boolean + memory_core_bytes: + description: Current used memory of the core itself in bytes + type: integer + memory_limit_bytes: + description: Defined memory limit in bytes + type: integer + memory_total_bytes: + description: Total available memory in bytes + type: integer + memory_used_bytes: + description: Currently used memory in bytes + type: integer + ncpu: + description: Number of CPU on this node + type: number + type: object + api.AboutVersion: + properties: + arch: + type: string + build_date: + description: RFC3339 + type: string + compiler: + type: string + number: + type: string + repository_branch: + type: string + repository_commit: + type: string type: object api.ClusterAbout: properties: @@ -188,6 +261,9 @@ definitions: type: object api.ClusterNodeResources: properties: + cpu_core: + description: percent 0-100*ncpu + type: number cpu_limit: description: percent 0-100*npcu type: number @@ -198,9 +274,15 @@ definitions: type: string is_throttling: type: boolean + memory_core_bytes: + description: bytes + type: integer memory_limit_bytes: description: bytes type: integer + memory_total_bytes: + description: bytes + type: integer memory_used_bytes: description: bytes type: integer @@ -710,6 +792,8 @@ definitions: properties: caller: type: string + core_id: + type: string data: additionalProperties: type: string @@ -728,6 +812,8 @@ definitions: properties: caller: type: string + core_id: + type: string data: additionalProperties: type: string @@ -1490,6 +1576,8 @@ definitions: type: integer layout: type: string + level: + type: integer packet: format: uint64 type: integer @@ -1498,10 +1586,14 @@ definitions: type: string pps: type: number + profile: + type: integer q: type: number - sampling_hz: + sample_fmt: description: Audio + type: string + sampling_hz: format: uint64 type: integer size_kb: @@ -2513,22 +2605,6 @@ definitions: $ref: '#/definitions/api.GraphMapping' type: array type: object - api.Version: - properties: - arch: - type: string - build_date: - description: RFC3339 - type: string - compiler: - type: string - number: - type: string - repository_branch: - type: string - repository_commit: - type: string - type: object api.WidgetProcess: properties: current_sessions: @@ -2838,6 +2914,31 @@ paths: summary: List of identities in the cluster tags: - v16.?.? + /api/v3/cluster/events: + post: + consumes: + - application/json + description: Stream of events of whats happening on each node in the cluster + operationId: cluster-3-events + parameters: + - description: Event filters + in: body + name: filters + schema: + $ref: '#/definitions/api.EventFilters' + produces: + - text/event-stream + - application/x-json-stream + responses: + "200": + description: OK + schema: + $ref: '#/definitions/api.Event' + security: + - ApiKeyAuth: [] + summary: Stream of events + tags: + - v16.?.? /api/v3/cluster/fs/{storage}: get: description: List all files on a filesystem. The listing can be ordered by name, @@ -3577,7 +3678,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/api.Version' + $ref: '#/definitions/api.AboutVersion' "404": description: Not Found schema: diff --git a/http/handler/api/cluster_node.go b/http/handler/api/cluster_node.go index 51f4c463..9276884b 100644 --- a/http/handler/api/cluster_node.go +++ b/http/handler/api/cluster_node.go @@ -86,7 +86,7 @@ func (h *ClusterHandler) NodeGet(c echo.Context) error { // @ID cluster-3-get-node-version // @Produce json // @Param id path string true "Node ID" -// @Success 200 {object} api.Version +// @Success 200 {object} api.AboutVersion // @Failure 404 {object} api.Error // @Security ApiKeyAuth // @Router /api/v3/cluster/node/{id}/version [get] From 385628382c3c70ddc7fe3399119d8f255865348f Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 5 Sep 2024 14:10:16 +0200 Subject: [PATCH 17/64] Fix linter error --- io/fs/fs_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/io/fs/fs_test.go b/io/fs/fs_test.go index 17d79e3c..2855e20f 100644 --- a/io/fs/fs_test.go +++ b/io/fs/fs_test.go @@ -877,7 +877,7 @@ func testAppend(t *testing.T, fs Filesystem) { file := fs.Open("/foobar") require.NotNil(t, file) - data, err := io.ReadAll(file) + data, _ := io.ReadAll(file) require.Equal(t, []byte("part1part2"), data) } @@ -888,6 +888,6 @@ func testAppendCreate(t *testing.T, fs Filesystem) { file := fs.Open("/foobar") require.NotNil(t, file) - data, err := io.ReadAll(file) + data, _ := io.ReadAll(file) require.Equal(t, []byte("part1"), data) } From a1f41bd202f24b090d58bf0cb8372dc5d6ada07c Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Mon, 9 Sep 2024 18:02:16 +0200 Subject: [PATCH 18/64] Fix using possibly unavailable value --- http/handler/api/cluster_node.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http/handler/api/cluster_node.go b/http/handler/api/cluster_node.go index 9276884b..87fd2981 100644 --- a/http/handler/api/cluster_node.go +++ b/http/handler/api/cluster_node.go @@ -373,7 +373,7 @@ func (h *ClusterHandler) NodeListProcesses(c echo.Context) error { processes := []api.Process{} for _, p := range procs { - if !h.iam.Enforce(ctxuser, domain, "process", p.Config.ID, "read") { + if !h.iam.Enforce(ctxuser, domain, "process", p.ID, "read") { continue } From 705c7fa946cd1c00039e53bafd8d5e1740738143 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Tue, 17 Sep 2024 11:55:31 +0200 Subject: [PATCH 19/64] Introduce budget for process relocation --- cluster/leader_relocate.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cluster/leader_relocate.go b/cluster/leader_relocate.go index 285a087f..dc5a057a 100644 --- a/cluster/leader_relocate.go +++ b/cluster/leader_relocate.go @@ -106,6 +106,7 @@ func relocate(have []node.Process, nodes map[string]node.About, relocateMap map[ haveReferenceAffinity := NewReferenceAffinity(have) opStack := []interface{}{} + opBudget := 100 // Check for any requested relocations. for processid, targetNodeid := range relocateMap { @@ -190,6 +191,8 @@ func relocate(have []node.Process, nodes map[string]node.About, relocateMap map[ order: process.Order, }) + opBudget -= 5 + // Adjust the resources. resources.Move(targetNodeid, sourceNodeid, process.CPU, process.Mem) @@ -199,7 +202,9 @@ func relocate(have []node.Process, nodes map[string]node.About, relocateMap map[ relocatedProcessIDs = append(relocatedProcessIDs, processid) // Move only one process at a time. - break + if opBudget <= 0 { + break + } } return opStack, resources.Map(), relocatedProcessIDs From 0f6d7949c4194f8a1af822c0a6ad3a45b9d0b249 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Tue, 17 Sep 2024 15:08:11 +0200 Subject: [PATCH 20/64] Fix deadlock in cluster shutdown --- cluster/cluster.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cluster/cluster.go b/cluster/cluster.go index 69551277..60e41235 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -620,7 +620,7 @@ func (c *cluster) CertManager() autocert.Manager { } func (c *cluster) Shutdown() error { - c.logger.Info().Log("Shutting down cluster") + c.logger.Info().Log("Shutting down cluster ...") c.shutdownLock.Lock() defer c.shutdownLock.Unlock() @@ -631,9 +631,14 @@ func (c *cluster) Shutdown() error { c.shutdown = true close(c.shutdownCh) + c.logger.Info().Log("Waiting for all routines to exit ...") + c.shutdownWg.Wait() + c.logger.Info().Log("All routines exited ...") + if c.manager != nil { + c.logger.Info().Log("Shutting down node manager ...") c.manager.NodesClear() } @@ -641,13 +646,18 @@ func (c *cluster) Shutdown() error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() + c.logger.Info().Log("Shutting down API ...") + c.api.Shutdown(ctx) } if c.raft != nil { + c.logger.Info().Log("Shutting down raft ...") c.raft.Shutdown() } + c.logger.Info().Log("Cluster stopped") + return nil } @@ -1029,7 +1039,7 @@ func (c *cluster) trackLeaderChanges() { if !isNodeInCluster { // We're not anymore part of the cluster, shutdown c.logger.Warn().WithField("id", c.nodeID).Log("This node left the cluster. Shutting down.") - c.Shutdown() + go c.Shutdown() } case <-c.shutdownCh: From d75c7d188f0a9aa01dbd9981dec969fc8d99f9c1 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 19 Sep 2024 15:05:11 +0200 Subject: [PATCH 21/64] Make audio/video specific fields optional --- http/api/avstream.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/http/api/avstream.go b/http/api/avstream.go index 5948d220..1b68faf4 100644 --- a/http/api/avstream.go +++ b/http/api/avstream.go @@ -51,13 +51,13 @@ type AVstream struct { Codec string `json:"codec"` Profile int `json:"profile"` Level int `json:"level"` - Pixfmt string `json:"pix_fmt"` - Width uint64 `json:"width" format:"uint64"` - Height uint64 `json:"height" format:"uint64"` - Samplefmt string `json:"sample_fmt"` - Sampling uint64 `json:"sampling_hz" format:"uint64"` - Layout string `json:"layout"` - Channels uint64 `json:"channels" format:"uint64"` + Pixfmt string `json:"pix_fmt,omitempty"` + Width uint64 `json:"width,omitempty" format:"uint64"` + Height uint64 `json:"height,omitempty" format:"uint64"` + Samplefmt string `json:"sample_fmt,omitempty"` + Sampling uint64 `json:"sampling_hz,omitempty" format:"uint64"` + Layout string `json:"layout,omitempty"` + Channels uint64 `json:"channels,omitempty" format:"uint64"` } func (a *AVstream) Unmarshal(av *app.AVstream) { From 4a999cf11a8d8a7429c537099fd9bec9764c3382 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Fri, 27 Sep 2024 12:27:25 +0200 Subject: [PATCH 22/64] Remove zstd from default compressions --- http/middleware/compress/compress.go | 2 +- http/server.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/http/middleware/compress/compress.go b/http/middleware/compress/compress.go index aa5017b2..6e5616eb 100644 --- a/http/middleware/compress/compress.go +++ b/http/middleware/compress/compress.go @@ -81,7 +81,7 @@ var DefaultConfig = Config{ Skipper: middleware.DefaultSkipper, Level: DefaultCompression, MinLength: 0, - Schemes: []Scheme{GzipScheme, ZstdScheme}, + Schemes: []Scheme{GzipScheme}, } // ContentTypesSkipper returns a Skipper based on the list of content types diff --git a/http/server.go b/http/server.go index bbfb2726..3c322255 100644 --- a/http/server.go +++ b/http/server.go @@ -488,9 +488,10 @@ func (s *server) HTTPStatus() map[int]uint64 { func (s *server) setRoutes() { gzipMiddleware := mwcompress.NewWithConfig(mwcompress.Config{ + Skipper: mwcompress.ContentTypeSkipper(nil), Level: mwcompress.BestSpeed, MinLength: 1000, - Skipper: mwcompress.ContentTypeSkipper(nil), + Schemes: []mwcompress.Scheme{mwcompress.GzipScheme}, }) // API router grouo @@ -532,6 +533,7 @@ func (s *server) setRoutes() { Skipper: mwcompress.ContentTypeSkipper(s.gzip.mimetypes), Level: mwcompress.BestSpeed, MinLength: 1000, + Schemes: []mwcompress.Scheme{mwcompress.GzipScheme}, })) } From 17b8289f87bddd333a263accdfa0b99b260a2f60 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Fri, 27 Sep 2024 16:38:26 +0200 Subject: [PATCH 23/64] Temporarly remove .m3u8 from gziping --- http/server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/http/server.go b/http/server.go index 3c322255..8f5c2ae1 100644 --- a/http/server.go +++ b/http/server.go @@ -380,8 +380,8 @@ func NewServer(config Config) (serverhandler.Server, error) { "text/html", "text/javascript", "application/json", - "application/x-mpegurl", - "application/vnd.apple.mpegurl", + //"application/x-mpegurl", + //"application/vnd.apple.mpegurl", "image/svg+xml", } From d2325d0832950f9329a5c9f2b3907df6390013ac Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Tue, 1 Oct 2024 15:16:30 +0200 Subject: [PATCH 24/64] Fix process cleanup --- restream/core.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/restream/core.go b/restream/core.go index e9effb7a..e3f64f9d 100644 --- a/restream/core.go +++ b/restream/core.go @@ -187,7 +187,7 @@ func (r *restream) Start() { t.Restore() // The filesystem cleanup rules can be set - r.setCleanup(id, t.Config()) + r.setCleanup(id, t.config) return true }) @@ -1168,7 +1168,7 @@ func (r *restream) updateProcess(id app.ProcessID, config *app.Config) error { r.tasks.Store(tid, t) // set filesystem cleanup rules - r.setCleanup(tid, t.Config()) + r.setCleanup(tid, t.config) t.Restore() @@ -1376,7 +1376,7 @@ func (r *restream) reloadProcess(id app.ProcessID) error { r.tasks.Store(tid, t) // set filesystem cleanup rules - r.setCleanup(tid, t.Config()) + r.setCleanup(tid, t.config) t.Restore() From e6af09b9829b8b559c1530cd9ea0c28a5860a9f4 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Tue, 1 Oct 2024 16:11:38 +0200 Subject: [PATCH 25/64] Add test for fs cleanup --- restream/core_test.go | 95 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/restream/core_test.go b/restream/core_test.go index aad54bec..3d9e1a68 100644 --- a/restream/core_test.go +++ b/restream/core_test.go @@ -1,6 +1,7 @@ package restream import ( + "bytes" "fmt" "math/rand" "os" @@ -1614,3 +1615,97 @@ func BenchmarkGetProcessState(b *testing.B) { rs.DeleteProcess(app.NewProcessID("test_"+strconv.Itoa(n), "")) } } + +func TestProcessCleanup(t *testing.T) { + rsi, err := getDummyRestreamer(nil, nil, nil, nil) + require.NoError(t, err) + + rsi.Start() + + rs := rsi.(*restream) + + memfs, ok := rs.fs.list[0].(fs.Filesystem) + require.True(t, ok) + + for i := 0; i < 10; i++ { + memfs.WriteFileReader(fmt.Sprintf("/foobar_%02d.dat", i), bytes.NewReader([]byte("hello")), -1) + } + + files := memfs.List("/", fs.ListOptions{ + Pattern: "/foobar_*", + }) + require.Equal(t, 10, len(files)) + + process := getDummyProcess() + process.ID = "foobar" + output := process.Output[0] + output.Cleanup = append(output.Cleanup, app.ConfigIOCleanup{ + Pattern: "mem:/{processid}_*", + MaxFiles: 5, + MaxFileAge: 0, + PurgeOnDelete: true, + }) + process.Output[0] = output + + err = rsi.AddProcess(process) + require.NoError(t, err) + + require.Eventually(t, func() bool { + files := memfs.List("/", fs.ListOptions{ + Pattern: "/foobar_*", + }) + + return len(files) == 5 + }, 15*time.Second, time.Second) + + rsi.Stop() + + for i := 0; i < 10; i++ { + memfs.WriteFileReader(fmt.Sprintf("/foobar_%02d.dat", i), bytes.NewReader([]byte("hello")), -1) + } + + files = memfs.List("/", fs.ListOptions{ + Pattern: "/foobar_*", + }) + require.Equal(t, 10, len(files)) + + rsi.ReloadProcess(app.ProcessID{ID: process.ID}) + + rsi.Start() + + require.Eventually(t, func() bool { + files := memfs.List("/", fs.ListOptions{ + Pattern: "/foobar_*", + }) + + return len(files) == 5 + }, 15*time.Second, time.Second) + + rsi.Stop() + + for i := 0; i < 10; i++ { + memfs.WriteFileReader(fmt.Sprintf("/foobar_%02d.dat", i), bytes.NewReader([]byte("hello")), -1) + } + + files = memfs.List("/", fs.ListOptions{ + Pattern: "/foobar_*", + }) + require.Equal(t, 10, len(files)) + + process.Reference = "foobar" + rsi.UpdateProcess(app.ProcessID{ID: process.ID}, process) + + rsi.Start() + + require.Eventually(t, func() bool { + files := memfs.List("/", fs.ListOptions{ + Pattern: "/foobar_*", + }) + + return len(files) == 5 + }, 15*time.Second, time.Second) + + rsi.Stop() + + //task, ok := rs.tasks.Load(app.ProcessID{ID: process.ID}) +} From b2a1909d799416d9b4536781fd92ac876ecefb60 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 3 Oct 2024 15:01:00 +0200 Subject: [PATCH 26/64] Use byte array on stack for copying data, limit size hint --- io/fs/mem.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/io/fs/mem.go b/io/fs/mem.go index de8b3cb7..d5bae91c 100644 --- a/io/fs/mem.go +++ b/io/fs/mem.go @@ -411,17 +411,9 @@ func (fs *memFilesystem) Symlink(oldname, newname string) error { return nil } -var chunkPool = sync.Pool{ - New: func() interface{} { - chunk := make([]byte, 128*1024) - return &chunk - }, -} - func copyToBufferFromReader(buf *bytes.Buffer, r io.Reader, _ int) (int64, error) { - chunkPtr := chunkPool.Get().(*[]byte) - chunk := *chunkPtr - defer chunkPool.Put(chunkPtr) + chunkData := [128 * 1024]byte{} + chunk := chunkData[0:] size := int64(0) @@ -466,7 +458,7 @@ func (fs *memFilesystem) WriteFileReader(path string, r io.Reader, sizeHint int) data: &bytes.Buffer{}, } - if sizeHint > 0 { + if sizeHint > 0 && sizeHint < 5*1024*1024 { newFile.data.Grow(sizeHint) } From 0a4118fa38a4b0c0e3c725de7be6edc4ca5ea35e Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 3 Oct 2024 21:30:53 +0200 Subject: [PATCH 27/64] Reset buffer --- Dockerfile | 2 +- io/fs/mem.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 409ee414..4c134725 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG GOLANG_IMAGE=golang:1.22-alpine3.20 +ARG GOLANG_IMAGE=golang:1.23-alpine3.20 ARG BUILD_IMAGE=alpine:3.20 # Cross-Compilation diff --git a/io/fs/mem.go b/io/fs/mem.go index d5bae91c..7edfa5e6 100644 --- a/io/fs/mem.go +++ b/io/fs/mem.go @@ -110,6 +110,7 @@ func (f *memFile) Close() error { } f.r = nil + f.data.Reset() f.data = nil return nil From 30af9e9c3640007810eb9f7765169e5995761c46 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 3 Oct 2024 21:35:08 +0200 Subject: [PATCH 28/64] Never indent JSON response --- http/json.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/http/json.go b/http/json.go index 83367a4a..fd2c1205 100644 --- a/http/json.go +++ b/http/json.go @@ -14,11 +14,8 @@ type GoJSONSerializer struct{} // Serialize converts an interface into a json and writes it to the response. // You can optionally use the indent parameter to produce pretty JSONs. -func (d GoJSONSerializer) Serialize(c echo.Context, i interface{}, indent string) error { +func (d GoJSONSerializer) Serialize(c echo.Context, i interface{}, _ string) error { enc := json.NewEncoder(c.Response()) - if indent != "" { - enc.SetIndent("", indent) - } return enc.Encode(i) } From fe2cbd4f60d8e4e794778a2251705cdf2b2ae199 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Tue, 8 Oct 2024 14:27:23 +0200 Subject: [PATCH 29/64] Add buffer pool for memfs --- io/fs/mem.go | 130 +++++++++++++++++------ io/fs/mem_storage.go | 31 ------ io/fs/mem_test.go | 28 +++++ io/fs/memtest/.gitignore | 1 + io/fs/memtest/memtest.go | 220 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 346 insertions(+), 64 deletions(-) create mode 100644 io/fs/memtest/.gitignore create mode 100644 io/fs/memtest/memtest.go diff --git a/io/fs/mem.go b/io/fs/mem.go index 7edfa5e6..97b45318 100644 --- a/io/fs/mem.go +++ b/io/fs/mem.go @@ -105,17 +105,60 @@ func (f *memFile) Seek(offset int64, whence int) (int64, error) { } func (f *memFile) Close() error { - if f.data == nil { + if f.r == nil { return io.EOF } f.r = nil - f.data.Reset() - f.data = nil return nil } +func (f *memFile) free() { + f.Close() + + if f.data == nil { + return + } + + pool.Put(f.data) + + f.data = nil +} + +type fileDataPool struct { + pool sync.Pool +} + +var pool *fileDataPool = nil + +func NewFileDataPool() *fileDataPool { + p := &fileDataPool{ + pool: sync.Pool{ + New: func() any { + return &bytes.Buffer{} + }, + }, + } + + return p +} + +func (p *fileDataPool) Get() *bytes.Buffer { + buf := p.pool.Get().(*bytes.Buffer) + buf.Reset() + + return buf +} + +func (p *fileDataPool) Put(buf *bytes.Buffer) { + p.pool.Put(buf) +} + +func init() { + pool = NewFileDataPool() +} + type memFilesystem struct { metadata map[string]string metaLock sync.RWMutex @@ -315,7 +358,7 @@ func (fs *memFilesystem) Files() int64 { func (fs *memFilesystem) Open(path string) File { path = fs.cleanPath(path) - file, ok := fs.storage.LoadAndCopy(path) + file, ok := fs.storage.Load(path) if !ok { return nil } @@ -323,24 +366,26 @@ func (fs *memFilesystem) Open(path string) File { newFile := &memFile{ memFileInfo: memFileInfo{ name: file.name, + size: file.size, + dir: file.dir, lastMod: file.lastMod, linkTo: file.linkTo, }, + data: file.data, } if len(file.linkTo) != 0 { - file.Close() - - file, ok = fs.storage.LoadAndCopy(file.linkTo) + file, ok := fs.storage.Load(file.linkTo) if !ok { return nil } + + newFile.lastMod = file.lastMod + newFile.data = file.data + newFile.size = file.size } - newFile.lastMod = file.lastMod - newFile.data = file.data - newFile.size = file.size - newFile.r = bytes.NewReader(file.data.Bytes()) + newFile.r = bytes.NewReader(newFile.data.Bytes()) return newFile } @@ -348,22 +393,22 @@ func (fs *memFilesystem) Open(path string) File { func (fs *memFilesystem) ReadFile(path string) ([]byte, error) { path = fs.cleanPath(path) - file, ok := fs.storage.LoadAndCopy(path) + file, ok := fs.storage.Load(path) if !ok { return nil, ErrNotExist } if len(file.linkTo) != 0 { - file.Close() - - file, ok = fs.storage.LoadAndCopy(file.linkTo) + file, ok = fs.storage.Load(file.linkTo) if !ok { return nil, ErrNotExist } } - defer file.Close() - return file.data.Bytes(), nil + data := make([]byte, file.data.Len()) + copy(data, file.data.Bytes()) + + return data, nil } func (fs *memFilesystem) Symlink(oldname, newname string) error { @@ -403,7 +448,7 @@ func (fs *memFilesystem) Symlink(oldname, newname string) error { defer fs.sizeLock.Unlock() if replaced { - oldFile.Close() + oldFile.free() fs.currentSize -= oldFile.size } @@ -456,7 +501,7 @@ func (fs *memFilesystem) WriteFileReader(path string, r io.Reader, sizeHint int) size: 0, lastMod: time.Now(), }, - data: &bytes.Buffer{}, + data: pool.Get(), } if sizeHint > 0 && sizeHint < 5*1024*1024 { @@ -471,7 +516,7 @@ func (fs *memFilesystem) WriteFileReader(path string, r io.Reader, sizeHint int) "error": err, }).Warn().Log("Incomplete file") - newFile.Close() + newFile.free() return -1, false, fmt.Errorf("incomplete file") } @@ -488,7 +533,7 @@ func (fs *memFilesystem) WriteFileReader(path string, r io.Reader, sizeHint int) defer fs.sizeLock.Unlock() if replace { - oldFile.Close() + oldFile.free() fs.currentSize -= oldFile.size } @@ -521,13 +566,26 @@ func (fs *memFilesystem) WriteFileSafe(path string, data []byte) (int64, bool, e func (fs *memFilesystem) AppendFileReader(path string, r io.Reader, sizeHint int) (int64, error) { path = fs.cleanPath(path) - file, hasFile := fs.storage.LoadAndCopy(path) + file, hasFile := fs.storage.Load(path) if !hasFile { size, _, err := fs.WriteFileReader(path, r, sizeHint) return size, err } - size, err := copyToBufferFromReader(file.data, r, 8*1024) + newFile := &memFile{ + memFileInfo: memFileInfo{ + name: path, + dir: false, + size: 0, + lastMod: time.Now(), + }, + data: pool.Get(), + } + + newFile.data.Grow(file.data.Len()) + newFile.data.Write(file.data.Bytes()) + + size, err := copyToBufferFromReader(newFile.data, r, 8*1024) if err != nil { fs.logger.WithFields(log.Fields{ "path": path, @@ -535,18 +593,22 @@ func (fs *memFilesystem) AppendFileReader(path string, r io.Reader, sizeHint int "error": err, }).Warn().Log("Incomplete file") - file.Close() + newFile.free() return -1, fmt.Errorf("incomplete file") } file.size += size - fs.storage.Store(path, file) + oldFile, replace := fs.storage.Store(path, newFile) fs.sizeLock.Lock() defer fs.sizeLock.Unlock() + if replace { + oldFile.free() + } + fs.currentSize += size fs.logger.Debug().WithFields(log.Fields{ @@ -583,7 +645,7 @@ func (fs *memFilesystem) Purge(size int64) int64 { fs.currentSize -= f.size fs.sizeLock.Unlock() - f.Close() + f.free() fs.logger.WithFields(log.Fields{ "path": f.name, @@ -643,7 +705,7 @@ func (fs *memFilesystem) Rename(src, dst string) error { defer fs.sizeLock.Unlock() if replace { - dstFile.Close() + dstFile.free() fs.currentSize -= dstFile.size } @@ -663,13 +725,12 @@ func (fs *memFilesystem) Copy(src, dst string) error { return os.ErrInvalid } - srcFile, ok := fs.storage.LoadAndCopy(src) + srcFile, ok := fs.storage.Load(src) if !ok { return ErrNotExist } if srcFile.dir { - srcFile.Close() return ErrNotExist } @@ -680,9 +741,12 @@ func (fs *memFilesystem) Copy(src, dst string) error { size: srcFile.size, lastMod: time.Now(), }, - data: srcFile.data, + data: pool.Get(), } + dstFile.data.Grow(srcFile.data.Len()) + dstFile.data.Write(srcFile.data.Bytes()) + f, replace := fs.storage.Store(dst, dstFile) if !replace { @@ -693,7 +757,7 @@ func (fs *memFilesystem) Copy(src, dst string) error { defer fs.sizeLock.Unlock() if replace { - f.Close() + f.free() fs.currentSize -= f.size } @@ -761,7 +825,7 @@ func (fs *memFilesystem) Remove(path string) int64 { func (fs *memFilesystem) remove(path string) int64 { file, ok := fs.storage.Delete(path) if ok { - file.Close() + file.free() fs.dirs.Remove(path) @@ -851,7 +915,7 @@ func (fs *memFilesystem) RemoveList(path string, options ListOptions) ([]string, fs.dirs.Remove(file.name) - file.Close() + file.free() } fs.sizeLock.Lock() diff --git a/io/fs/mem_storage.go b/io/fs/mem_storage.go index 044b375e..8267660a 100644 --- a/io/fs/mem_storage.go +++ b/io/fs/mem_storage.go @@ -21,10 +21,6 @@ type memStorage interface { // i.e. all changes to the file will be reflected on the storage. Load(key string) (value *memFile, ok bool) - // LoadAndCopy loads a file from the storage. The returned file is a copy - // and can be modified without modifying the file on the storage. - LoadAndCopy(key string) (value *memFile, ok bool) - // Has checks whether a file exists at path. Has(key string) bool @@ -68,33 +64,6 @@ func (m *mapOfStorage) Load(key string) (*memFile, bool) { return m.files.Load(key) } -func (m *mapOfStorage) LoadAndCopy(key string) (*memFile, bool) { - token := m.lock.RLock() - defer m.lock.RUnlock(token) - - v, ok := m.files.Load(key) - if !ok { - return nil, false - } - - f := &memFile{ - memFileInfo: memFileInfo{ - name: v.name, - size: v.size, - dir: v.dir, - lastMod: v.lastMod, - linkTo: v.linkTo, - }, - r: nil, - } - - if v.data != nil { - f.data = bytes.NewBuffer(v.data.Bytes()) - } - - return f, true -} - func (m *mapOfStorage) Has(key string) bool { token := m.lock.RLock() defer m.lock.RUnlock(token) diff --git a/io/fs/mem_test.go b/io/fs/mem_test.go index 1e9f43fb..63a8f0f3 100644 --- a/io/fs/mem_test.go +++ b/io/fs/mem_test.go @@ -48,6 +48,34 @@ func TestWriteWhileRead(t *testing.T) { require.Equal(t, []byte("xxxxx"), data) } +func TestCopy(t *testing.T) { + fs, err := NewMemFilesystem(MemConfig{}) + require.NoError(t, err) + + _, _, err = fs.WriteFile("/foobar", []byte("xxxxx")) + require.NoError(t, err) + + data, err := fs.ReadFile("/foobar") + require.NoError(t, err) + + require.Equal(t, []byte("xxxxx"), data) + + err = fs.Copy("/foobar", "/barfoo") + require.NoError(t, err) + + data, err = fs.ReadFile("/barfoo") + require.NoError(t, err) + + require.Equal(t, []byte("xxxxx"), data) + + fs.Remove("/foobar") + + data, err = fs.ReadFile("/barfoo") + require.NoError(t, err) + + require.Equal(t, []byte("xxxxx"), data) +} + func BenchmarkMemStorages(b *testing.B) { storages := []string{ "map", diff --git a/io/fs/memtest/.gitignore b/io/fs/memtest/.gitignore new file mode 100644 index 00000000..f43fea82 --- /dev/null +++ b/io/fs/memtest/.gitignore @@ -0,0 +1 @@ +memtest diff --git a/io/fs/memtest/memtest.go b/io/fs/memtest/memtest.go new file mode 100644 index 00000000..2ef879bb --- /dev/null +++ b/io/fs/memtest/memtest.go @@ -0,0 +1,220 @@ +package main + +import ( + "bytes" + "context" + "flag" + "fmt" + "io" + "log" + gorand "math/rand/v2" + "os" + "os/signal" + "runtime" + "runtime/debug" + "strconv" + "sync" + "time" + + "github.com/datarhei/core/v16/io/fs" + "github.com/datarhei/core/v16/math/rand" + + "github.com/google/gops/agent" +) + +func main() { + oStorage := "mapof" + oWriters := 500 + oReaders := 1000 + oFiles := 15 + oInterval := 1 // seconds + oSize := 2 // megabytes + oLimit := false + + flag.StringVar(&oStorage, "storage", "mapof", "type of mem storage implementation (mapof, map, swiss)") + flag.IntVar(&oWriters, "writers", 500, "number of concurrent writers") + flag.IntVar(&oReaders, "readers", 1000, "number of concurrent readers") + flag.IntVar(&oFiles, "files", 15, "number of files to keep per writer") + flag.IntVar(&oInterval, "interval", 1, "interval for writing files in seconds") + flag.IntVar(&oSize, "size", 2048, "size of files to write in kilobytes") + flag.BoolVar(&oLimit, "limit", false, "set memory limit") + + flag.Parse() + + estimatedSize := float64(oWriters*oFiles*oSize) / 1024 / 1024 + + fmt.Printf("Expecting effective memory consumption of %.1fGB\n", estimatedSize) + + if oLimit { + fmt.Printf("Setting memory limit to %.1fGB\n", estimatedSize*1.5) + debug.SetMemoryLimit(int64(estimatedSize * 1.5)) + } + + memfs, err := fs.NewMemFilesystem(fs.MemConfig{ + Storage: oStorage, + }) + + if err != nil { + log.Fatalf("acquiring new memfs: %s", err.Error()) + } + + err = agent.Listen(agent.Options{ + Addr: ":9000", + ReuseSocketAddrAndPort: true, + }) + + if err != nil { + log.Fatalf("starting agent: %s", err.Error()) + } + + fmt.Printf("Started agent on :9000\n") + + ctx, cancel := context.WithCancel(context.Background()) + + wgWriter := sync.WaitGroup{} + + for i := 0; i < oWriters; i++ { + fmt.Printf("%4d / %4d writer started\r", i+1, oWriters) + + wgWriter.Add(1) + + go func(ctx context.Context, memfs fs.Filesystem, index int, nfiles int64, interval time.Duration) { + defer wgWriter.Done() + + jitter := gorand.IntN(200) + interval += time.Duration(jitter) * time.Millisecond + + sequence := int64(0) + + buf := bytes.NewBufferString(rand.StringAlphanumeric(oSize * (1024 + jitter - 100))) + r := bytes.NewReader(buf.Bytes()) + + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + path := fmt.Sprintf("/foobar/test_%d_%06d.dat", index, sequence) + + // Write file to memfs + r.Seek(0, io.SeekStart) + memfs.WriteFileReader(path, r, -1) + + // Delete file from memfs + if sequence-nfiles >= 0 { + path = fmt.Sprintf("/foobar/test_%d_%06d.dat", index, sequence-nfiles) + memfs.Remove(path) + } + + path = fmt.Sprintf("/foobar/test_%d.last", index) + memfs.WriteFile(path, []byte(strconv.FormatInt(sequence, 10))) + + sequence++ + } + } + }(ctx, memfs, i, int64(oFiles), time.Duration(oInterval)*time.Second) + } + + fmt.Printf("\n") + + wgReader := sync.WaitGroup{} + + if oReaders > 0 { + for i := 0; i < oReaders; i++ { + fmt.Printf("%4d / %4d reader started\r", i+1, oReaders) + + wgReader.Add(1) + + go func(ctx context.Context, memfs fs.Filesystem, interval time.Duration) { + defer wgReader.Done() + + buf := bytes.Buffer{} + + jitter := gorand.IntN(200) + interval += time.Duration(jitter) * time.Millisecond + + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + index := gorand.IntN(oWriters) + + path := fmt.Sprintf("/foobar/test_%d.list", index) + data, err := memfs.ReadFile(path) + if err != nil { + continue + } + + sequence, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + continue + } + + path = fmt.Sprintf("/foobar/test_%d_%06d.dat", index, sequence) + file := memfs.Open(path) + + buf.ReadFrom(file) + buf.Reset() + } + } + }(ctx, memfs, time.Duration(oInterval)*time.Second) + } + + fmt.Printf("\n") + } + + go func(ctx context.Context, memfs fs.Filesystem) { + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + + nMallocs := uint64(0) + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + m := runtime.MemStats{} + runtime.ReadMemStats(&m) + + size, _ := memfs.Size() + fmt.Printf("%5.1fGB ", float64(size)/1024/1024/1024) + + listfiles := 0 + listsize := int64(0) + files := memfs.List("/", fs.ListOptions{}) + for _, f := range files { + listsize += f.Size() + } + listfiles = len(files) + + fmt.Printf("(%7d files with %5.1fGB) ", listfiles, float64(listsize)/1024/1024/1024) + + fmt.Printf("alloc=%5.1fGB (%8.1fGB) sys=%5.1fGB idle=%5.1fGB inuse=%5.1fGB mallocs=%d objects=%d\n", float64(m.HeapAlloc)/1024/1024/1024, float64(m.TotalAlloc)/1024/1024/1024, float64(m.HeapSys)/1024/1024/1024, float64(m.HeapIdle)/1024/1024/1024, float64(m.HeapInuse)/1024/1024/1024, m.Mallocs-nMallocs, m.Mallocs-m.Frees) + + nMallocs = m.Mallocs + } + } + }(ctx, memfs) + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + + cancel() + + fmt.Printf("Waiting for readers to stop ...\n") + wgReader.Wait() + + fmt.Printf("Waiting for writers to stop ...\n") + wgWriter.Wait() + + fmt.Printf("Done\n") +} From 1e9f7f647a27e0a13380c233a40a92c7d8bc2e0e Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Wed, 9 Oct 2024 10:29:33 +0200 Subject: [PATCH 30/64] Add -gc option --- io/fs/memtest/memtest.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/io/fs/memtest/memtest.go b/io/fs/memtest/memtest.go index 2ef879bb..771eb454 100644 --- a/io/fs/memtest/memtest.go +++ b/io/fs/memtest/memtest.go @@ -30,6 +30,7 @@ func main() { oInterval := 1 // seconds oSize := 2 // megabytes oLimit := false + oGC := 0 flag.StringVar(&oStorage, "storage", "mapof", "type of mem storage implementation (mapof, map, swiss)") flag.IntVar(&oWriters, "writers", 500, "number of concurrent writers") @@ -38,6 +39,7 @@ func main() { flag.IntVar(&oInterval, "interval", 1, "interval for writing files in seconds") flag.IntVar(&oSize, "size", 2048, "size of files to write in kilobytes") flag.BoolVar(&oLimit, "limit", false, "set memory limit") + flag.IntVar(&oGC, "gc", 0, "force garbage collector") flag.Parse() @@ -175,13 +177,13 @@ func main() { defer ticker.Stop() nMallocs := uint64(0) + m := runtime.MemStats{} for { select { case <-ctx.Done(): return case <-ticker.C: - m := runtime.MemStats{} runtime.ReadMemStats(&m) size, _ := memfs.Size() @@ -204,6 +206,22 @@ func main() { } }(ctx, memfs) + if oGC > 0 { + go func(ctx context.Context) { + ticker := time.NewTicker(time.Duration(oGC) * time.Second) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + debug.FreeOSMemory() + } + } + }(ctx) + } + quit := make(chan os.Signal, 1) signal.Notify(quit, os.Interrupt) <-quit From 4d6eb122b00e291c5952c143c13b7d9cf4c6de15 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Wed, 9 Oct 2024 10:50:04 +0200 Subject: [PATCH 31/64] Use stdlib json encoder/decoder, goccy is too buggy --- encoding/json/json.go | 3 +-- go.mod | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/encoding/json/json.go b/encoding/json/json.go index 12046c06..51d0f0e8 100644 --- a/encoding/json/json.go +++ b/encoding/json/json.go @@ -2,10 +2,9 @@ package json import ( + "encoding/json" "fmt" "io" - - "github.com/goccy/go-json" ) type UnmarshalTypeError = json.UnmarshalTypeError diff --git a/go.mod b/go.mod index c9b6f090..f1d41509 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,6 @@ require ( github.com/fujiwara/shapeio v1.0.0 github.com/go-playground/validator/v10 v10.22.0 github.com/gobwas/glob v0.2.3 - github.com/goccy/go-json v0.10.3 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/gops v0.3.28 @@ -75,6 +74,7 @@ require ( github.com/go-openapi/swag v0.23.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect From f97943b27593e3c406ef3da817566f1ddfcde13c Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Wed, 9 Oct 2024 14:25:42 +0200 Subject: [PATCH 32/64] Move content encoding in the beginning of the middleware chain, update dependencies --- app/api/api.go | 10 +- config/config.go | 21 +- config/data.go | 11 +- go.mod | 63 +- go.sum | 126 +- http/fs/fs.go | 1 - http/handler/api/config_test.go | 2 +- http/middleware/compress/brotli.go | 2 +- http/middleware/compress/compress.go | 240 +- http/middleware/compress/compress_test.go | 172 +- http/middleware/compress/gogzip.go | 55 + http/middleware/compress/gzip.go | 2 +- http/middleware/compress/zstd.go | 2 +- http/middleware/session/HLS.go | 4 +- http/server.go | 51 +- io/fs/mem.go | 32 +- math/rand/rand.go | 12 +- mem/buffer.go | 33 + .../github.com/99designs/gqlgen/.golangci.yml | 12 + .../github.com/99designs/gqlgen/CHANGELOG.md | 2079 ++++++- .../99designs/gqlgen/api/generate.go | 20 +- .../github.com/99designs/gqlgen/api/option.go | 25 +- .../99designs/gqlgen/codegen/args.go | 28 +- .../99designs/gqlgen/codegen/args.gotpl | 64 +- .../99designs/gqlgen/codegen/config/binder.go | 86 +- .../99designs/gqlgen/codegen/config/config.go | 71 +- .../gqlgen/codegen/config/initialisms.go | 4 +- .../99designs/gqlgen/codegen/data.go | 26 +- .../99designs/gqlgen/codegen/directive.go | 38 +- .../99designs/gqlgen/codegen/directives.gotpl | 42 +- .../99designs/gqlgen/codegen/field.go | 9 +- .../99designs/gqlgen/codegen/generated!.gotpl | 9 +- .../99designs/gqlgen/codegen/input.gotpl | 4 +- .../99designs/gqlgen/codegen/object.go | 30 +- .../99designs/gqlgen/codegen/root_.gotpl | 9 +- .../gqlgen/codegen/templates/templates.go | 10 +- .../99designs/gqlgen/codegen/type.gotpl | 4 +- .../99designs/gqlgen/graphql/bool.go | 2 + .../99designs/gqlgen/graphql/cache.go | 20 +- .../gqlgen/graphql/executor/executor.go | 9 +- .../99designs/gqlgen/graphql/float.go | 2 + .../gqlgen/graphql/handler/extension/apq.go | 6 +- .../gqlgen/graphql/handler/lru/lru.go | 16 +- .../gqlgen/graphql/handler/server.go | 7 +- .../graphql/handler/transport/http_graphql.go | 1 - .../graphql/handler/transport/websocket.go | 5 +- .../99designs/gqlgen/graphql/int.go | 6 + .../99designs/gqlgen/graphql/string.go | 2 +- .../99designs/gqlgen/graphql/uint.go | 6 + .../99designs/gqlgen/graphql/version.go | 2 +- .../gqlgen/init-templates/gqlgen.yml.gotmpl | 10 + .../99designs/gqlgen/internal/code/alias.go | 13 + .../gqlgen/internal/code/alias_1.23.go | 19 + .../99designs/gqlgen/internal/code/compare.go | 2 + .../gqlgen/plugin/federation/constants.go | 185 + .../gqlgen/plugin/federation/entity.go | 18 +- .../gqlgen/plugin/federation/federation.go | 781 +-- .../gqlgen/plugin/federation/federation.gotpl | 193 +- .../gqlgen/plugin/federation/readme.md | 4 +- .../gqlgen/plugin/modelgen/models.go | 227 +- .../99designs/gqlgen/plugin/plugin.go | 12 + .../gqlgen/plugin/resolvergen/resolver.go | 32 +- .../gqlgen/plugin/resolvergen/resolver.gotpl | 2 + .../Masterminds/semver/v3/CHANGELOG.md | 28 + .../github.com/Masterminds/semver/v3/Makefile | 3 +- .../Masterminds/semver/v3/README.md | 28 +- .../Masterminds/semver/v3/version.go | 64 +- vendor/github.com/adhocore/gronx/README.md | 8 + vendor/github.com/adhocore/gronx/gronx.go | 10 +- vendor/github.com/adhocore/gronx/validator.go | 2 +- .../agnivade/levenshtein/.travis.yml | 23 - .../github.com/agnivade/levenshtein/Makefile | 8 +- .../github.com/agnivade/levenshtein/README.md | 2 +- .../agnivade/levenshtein/levenshtein.go | 28 +- .../caddyserver/certmagic/account.go | 17 + .../caddyserver/certmagic/acmeclient.go | 152 +- .../caddyserver/certmagic/acmeissuer.go | 2 +- .../caddyserver/certmagic/certificates.go | 90 +- .../caddyserver/certmagic/config.go | 63 +- .../caddyserver/certmagic/filestorage.go | 24 +- .../caddyserver/certmagic/handshake.go | 2 +- .../certmagic/internal/atomicfile/README | 11 + .../certmagic/internal/atomicfile/file.go | 148 + .../caddyserver/certmagic/maintain.go | 22 +- .../caddyserver/certmagic/zerosslissuer.go | 2 +- .../go-playground/validator/v10/README.md | 2 +- .../go-playground/validator/v10/baked_in.go | 9 +- vendor/github.com/hashicorp/raft/raft.go | 3 +- .../github.com/klauspost/compress/README.md | 22 +- .../klauspost/compress/flate/deflate.go | 2 +- .../klauspost/compress/flate/inflate.go | 74 +- .../klauspost/compress/fse/decompress.go | 2 +- .../klauspost/compress/huff0/decompress.go | 4 +- .../klauspost/compress/s2/writer.go | 31 +- .../klauspost/compress/zstd/blockdec.go | 4 +- .../klauspost/compress/zstd/enc_better.go | 32 +- .../klauspost/compress/zstd/enc_dfast.go | 16 +- .../klauspost/compress/zstd/encoder.go | 19 +- .../klauspost/compress/zstd/framedec.go | 4 +- .../klauspost/compress/zstd/seqdec_amd64.go | 4 +- .../klauspost/compress/zstd/seqdec_amd64.s | 8 +- .../github.com/lestrrat-go/strftime/Changes | 7 + .../lestrrat-go/strftime/appenders.go | 22 +- .../strftime/internal/errors/errors_fmt.go | 18 - .../strftime/internal/errors/errors_pkg.go | 18 - .../lestrrat-go/strftime/specifications.go | 5 +- .../lestrrat-go/strftime/strftime.go | 14 +- vendor/github.com/mholt/acmez/v2/acme/ari.go | 53 +- .../minio-go/v7/api-put-object-multipart.go | 10 +- .../minio-go/v7/api-put-object-streaming.go | 82 +- .../minio/minio-go/v7/api-put-object.go | 44 +- .../minio-go/v7/api-putobject-snowball.go | 2 +- .../minio/minio-go/v7/api-s3-datatypes.go | 16 + vendor/github.com/minio/minio-go/v7/api.go | 15 +- .../github.com/minio/minio-go/v7/checksum.go | 13 + .../minio/minio-go/v7/functional_tests.go | 409 +- .../minio/minio-go/v7/post-policy.go | 19 + vendor/github.com/minio/minio-go/v7/retry.go | 5 +- vendor/github.com/pkg/errors/.gitignore | 24 - vendor/github.com/pkg/errors/.travis.yml | 10 - vendor/github.com/pkg/errors/LICENSE | 23 - vendor/github.com/pkg/errors/Makefile | 44 - vendor/github.com/pkg/errors/README.md | 59 - vendor/github.com/pkg/errors/appveyor.yml | 32 - vendor/github.com/pkg/errors/errors.go | 288 - vendor/github.com/pkg/errors/go113.go | 38 - vendor/github.com/pkg/errors/stack.go | 177 - .../client_golang/prometheus/histogram.go | 100 +- .../prometheus/process_collector.go | 2 + .../client_golang/prometheus/promhttp/http.go | 6 +- .../prometheus/common/expfmt/decode.go | 14 +- .../prometheus/common/expfmt/encode.go | 24 +- .../prometheus/common/expfmt/expfmt.go | 76 +- .../common/expfmt/openmetrics_create.go | 2 +- .../prometheus/common/expfmt/text_create.go | 4 +- .../prometheus/common/expfmt/text_parse.go | 162 +- .../prometheus/common/model/labels.go | 27 +- .../common/model/labelset_string.go | 2 - .../common/model/labelset_string_go120.go | 39 - .../prometheus/common/model/metric.go | 31 +- vendor/github.com/rs/xid/.gitignore | 3 + vendor/github.com/rs/xid/README.md | 10 +- vendor/github.com/rs/xid/hostid_darwin.go | 29 +- vendor/github.com/rs/xid/hostid_windows.go | 20 +- vendor/github.com/rs/xid/id.go | 13 +- .../github.com/tklauser/numcpus/.cirrus.yml | 4 +- vendor/github.com/tklauser/numcpus/numcpus.go | 23 + .../tklauser/numcpus/numcpus_linux.go | 91 +- .../numcpus/numcpus_list_unsupported.go | 33 + .../urfave/cli/v2/godoc-current.txt | 4 +- vendor/github.com/urfave/cli/v2/help.go | 13 +- vendor/github.com/urfave/cli/v2/template.go | 6 +- .../vektah/gqlparser/v2/ast/document.go | 7 +- .../validator/rules/fields_on_correct_type.go | 17 +- .../rules/fragments_on_composite_types.go | 17 +- .../validator/rules/known_argument_names.go | 13 +- .../v2/validator/rules/known_directives.go | 13 +- .../validator/rules/known_fragment_names.go | 13 +- .../v2/validator/rules/known_root_type.go | 13 +- .../v2/validator/rules/known_type_names.go | 13 +- .../rules/lone_anonymous_operation.go | 13 +- .../v2/validator/rules/no_fragment_cycles.go | 13 +- .../validator/rules/no_undefined_variables.go | 13 +- .../v2/validator/rules/no_unused_fragments.go | 13 +- .../v2/validator/rules/no_unused_variables.go | 13 +- .../rules/overlapping_fields_can_be_merged.go | 15 +- .../rules/possible_fragment_spreads.go | 13 +- .../rules/provided_required_arguments.go | 16 +- .../v2/validator/rules/scalar_leafs.go | 13 +- .../rules/single_field_subscriptions.go | 13 +- .../validator/rules/unique_argument_names.go | 13 +- .../rules/unique_directives_per_location.go | 13 +- .../validator/rules/unique_fragment_names.go | 13 +- .../rules/unique_input_field_names.go | 13 +- .../validator/rules/unique_operation_names.go | 13 +- .../validator/rules/unique_variable_names.go | 13 +- .../validator/rules/values_of_correct_type.go | 13 +- .../rules/variables_are_input_types.go | 13 +- .../rules/variables_in_allowed_position.go | 13 +- .../vektah/gqlparser/v2/validator/schema.go | 8 + .../gqlparser/v2/validator/validator.go | 26 +- .../vektah/gqlparser/v2/validator/vars.go | 2 +- vendor/go.etcd.io/bbolt/.go-version | 2 +- vendor/go.etcd.io/bbolt/Makefile | 13 + vendor/go.etcd.io/bbolt/db.go | 8 +- vendor/go.etcd.io/bbolt/freelist.go | 8 + vendor/go.etcd.io/bbolt/tx.go | 7 + .../internal/runtime/cpu_quota_linux.go | 14 +- .../internal/runtime/cpu_quota_unsupported.go | 2 +- .../automaxprocs/internal/runtime/runtime.go | 7 + .../automaxprocs/maxprocs/maxprocs.go | 21 +- .../automaxprocs/maxprocs/version.go | 2 +- .../golang.org/x/crypto/argon2/blamka_amd64.s | 2972 +++++++++- .../x/crypto/blake2b/blake2bAVX2_amd64.s | 5167 ++++++++++++++--- .../x/crypto/blake2b/blake2b_amd64.s | 1681 +++++- vendor/golang.org/x/crypto/sha3/shake.go | 4 +- vendor/golang.org/x/net/http2/config.go | 122 + vendor/golang.org/x/net/http2/config_go124.go | 61 + .../x/net/http2/config_pre_go124.go | 16 + vendor/golang.org/x/net/http2/http2.go | 53 +- vendor/golang.org/x/net/http2/server.go | 181 +- vendor/golang.org/x/net/http2/transport.go | 143 +- vendor/golang.org/x/net/http2/write.go | 10 + vendor/golang.org/x/sys/cpu/cpu.go | 19 + .../golang.org/x/sys/cpu/cpu_linux_noinit.go | 2 +- .../golang.org/x/sys/cpu/cpu_linux_riscv64.go | 137 + vendor/golang.org/x/sys/cpu/cpu_riscv64.go | 11 +- vendor/golang.org/x/sys/unix/README.md | 2 +- vendor/golang.org/x/sys/unix/mkerrors.sh | 5 +- vendor/golang.org/x/sys/unix/syscall_aix.go | 2 +- .../golang.org/x/sys/unix/syscall_darwin.go | 37 + vendor/golang.org/x/sys/unix/syscall_hurd.go | 1 + vendor/golang.org/x/sys/unix/syscall_linux.go | 63 +- .../x/sys/unix/syscall_linux_arm64.go | 2 + .../x/sys/unix/syscall_linux_loong64.go | 2 + .../x/sys/unix/syscall_linux_riscv64.go | 2 + .../golang.org/x/sys/unix/vgetrandom_linux.go | 13 + .../unix/vgetrandom_unsupported.go} | 11 +- .../x/sys/unix/zerrors_darwin_amd64.go | 7 + .../x/sys/unix/zerrors_darwin_arm64.go | 7 + vendor/golang.org/x/sys/unix/zerrors_linux.go | 13 +- .../x/sys/unix/zerrors_linux_386.go | 5 + .../x/sys/unix/zerrors_linux_amd64.go | 5 + .../x/sys/unix/zerrors_linux_arm.go | 5 + .../x/sys/unix/zerrors_linux_arm64.go | 5 + .../x/sys/unix/zerrors_linux_loong64.go | 5 + .../x/sys/unix/zerrors_linux_mips.go | 5 + .../x/sys/unix/zerrors_linux_mips64.go | 5 + .../x/sys/unix/zerrors_linux_mips64le.go | 5 + .../x/sys/unix/zerrors_linux_mipsle.go | 5 + .../x/sys/unix/zerrors_linux_ppc.go | 5 + .../x/sys/unix/zerrors_linux_ppc64.go | 5 + .../x/sys/unix/zerrors_linux_ppc64le.go | 5 + .../x/sys/unix/zerrors_linux_riscv64.go | 5 + .../x/sys/unix/zerrors_linux_s390x.go | 5 + .../x/sys/unix/zerrors_linux_sparc64.go | 5 + .../x/sys/unix/zerrors_zos_s390x.go | 2 + .../x/sys/unix/zsyscall_darwin_amd64.go | 20 + .../x/sys/unix/zsyscall_darwin_amd64.s | 5 + .../x/sys/unix/zsyscall_darwin_arm64.go | 20 + .../x/sys/unix/zsyscall_darwin_arm64.s | 5 + .../golang.org/x/sys/unix/zsyscall_linux.go | 17 - .../x/sys/unix/zsysnum_linux_amd64.go | 1 + .../x/sys/unix/zsysnum_linux_arm64.go | 2 +- .../x/sys/unix/zsysnum_linux_loong64.go | 2 + .../x/sys/unix/zsysnum_linux_riscv64.go | 2 +- .../x/sys/unix/ztypes_darwin_amd64.go | 13 + .../x/sys/unix/ztypes_darwin_arm64.go | 13 + .../x/sys/unix/ztypes_freebsd_386.go | 1 + .../x/sys/unix/ztypes_freebsd_amd64.go | 1 + .../x/sys/unix/ztypes_freebsd_arm.go | 1 + .../x/sys/unix/ztypes_freebsd_arm64.go | 1 + .../x/sys/unix/ztypes_freebsd_riscv64.go | 1 + vendor/golang.org/x/sys/unix/ztypes_linux.go | 90 +- .../x/sys/unix/ztypes_linux_riscv64.go | 33 + .../golang.org/x/sys/windows/dll_windows.go | 2 +- .../x/sys/windows/syscall_windows.go | 4 + .../golang.org/x/sys/windows/types_windows.go | 1 + .../x/sys/windows/zsyscall_windows.go | 38 + vendor/golang.org/x/time/rate/rate.go | 17 +- .../golang.org/x/tools/go/ast/astutil/util.go | 12 +- vendor/golang.org/x/tools/go/packages/doc.go | 15 +- .../x/tools/go/packages/external.go | 2 +- .../x/tools/go/packages/loadmode_string.go | 69 +- .../x/tools/go/packages/packages.go | 28 +- .../x/tools/go/types/objectpath/objectpath.go | 14 +- .../x/tools/go/types/typeutil/callee.go | 68 + .../x/tools/go/types/typeutil/imports.go | 30 + .../x/tools/go/types/typeutil/map.go | 517 ++ .../tools/go/types/typeutil/methodsetcache.go | 71 + .../x/tools/go/types/typeutil/ui.go | 53 + .../x/tools/internal/aliases/aliases.go | 10 +- .../x/tools/internal/aliases/aliases_go121.go | 35 - .../x/tools/internal/aliases/aliases_go122.go | 33 +- .../x/tools/internal/gcimporter/bimport.go | 61 - .../x/tools/internal/gcimporter/gcimporter.go | 11 +- .../x/tools/internal/gcimporter/iexport.go | 260 +- .../x/tools/internal/gcimporter/iimport.go | 31 +- .../internal/gcimporter/newInterface10.go | 22 - .../internal/gcimporter/newInterface11.go | 14 - .../tools/internal/gcimporter/predeclared.go | 91 + .../internal/gcimporter/support_go118.go | 34 - .../x/tools/internal/gcimporter/unified_no.go | 10 - .../tools/internal/gcimporter/unified_yes.go | 10 - .../tools/internal/gcimporter/ureader_yes.go | 44 +- .../x/tools/internal/gocommand/invoke.go | 18 +- .../x/tools/internal/imports/fix.go | 3 +- .../x/tools/internal/imports/imports.go | 4 +- .../x/tools/internal/imports/mod.go | 9 +- .../x/tools/internal/pkgbits/decoder.go | 34 +- .../x/tools/internal/pkgbits/encoder.go | 43 +- .../x/tools/internal/pkgbits/frames_go1.go | 21 - .../x/tools/internal/pkgbits/frames_go17.go | 28 - .../x/tools/internal/pkgbits/support.go | 2 +- .../x/tools/internal/pkgbits/sync.go | 23 + .../internal/pkgbits/syncmarker_string.go | 7 +- .../x/tools/internal/pkgbits/version.go | 85 + .../x/tools/internal/stdlib/manifest.go | 2 +- .../internal/tokeninternal/tokeninternal.go | 137 - .../x/tools/internal/typeparams/common.go | 140 + .../x/tools/internal/typeparams/coretype.go | 150 + .../x/tools/internal/typeparams/free.go | 118 + .../x/tools/internal/typeparams/normalize.go | 218 + .../x/tools/internal/typeparams/termlist.go | 163 + .../x/tools/internal/typeparams/typeterm.go | 169 + .../x/tools/internal/typesinternal/element.go | 133 + .../tools/internal/typesinternal/errorcode.go | 8 +- .../x/tools/internal/typesinternal/recv.go | 8 +- .../x/tools/internal/versions/toolchain.go | 14 - .../internal/versions/toolchain_go120.go | 14 - .../internal/versions/toolchain_go121.go | 14 - .../x/tools/internal/versions/types.go | 33 +- .../x/tools/internal/versions/types_go121.go | 30 - .../x/tools/internal/versions/types_go122.go | 41 - .../protobuf/internal/descopts/options.go | 20 +- .../protobuf/internal/filedesc/desc.go | 4 + .../protobuf/internal/filedesc/desc_init.go | 2 + .../protobuf/internal/filedesc/desc_lazy.go | 2 + .../protobuf/internal/filedesc/editions.go | 2 +- .../protobuf/internal/genid/doc.go | 2 +- .../internal/genid/go_features_gen.go | 15 +- .../protobuf/internal/genid/map_entry.go | 2 +- .../protobuf/internal/genid/wrappers.go | 2 +- .../protobuf/internal/impl/codec_extension.go | 11 +- .../protobuf/internal/impl/codec_field.go | 3 + .../protobuf/internal/impl/codec_message.go | 3 + .../protobuf/internal/impl/codec_reflect.go | 210 - .../protobuf/internal/impl/codec_unsafe.go | 3 - .../protobuf/internal/impl/convert.go | 2 +- .../protobuf/internal/impl/encode.go | 2 +- .../protobuf/internal/impl/equal.go | 224 + .../internal/impl/legacy_extension.go | 1 + .../protobuf/internal/impl/message.go | 4 +- .../protobuf/internal/impl/pointer_reflect.go | 215 - .../protobuf/internal/impl/pointer_unsafe.go | 3 - .../protobuf/internal/strs/strings_pure.go | 28 - .../internal/strs/strings_unsafe_go120.go | 3 +- .../internal/strs/strings_unsafe_go121.go | 3 +- .../protobuf/internal/version/version.go | 4 +- .../google.golang.org/protobuf/proto/equal.go | 9 + .../protobuf/proto/extension.go | 71 + .../protobuf/reflect/protoreflect/methods.go | 10 + .../reflect/protoreflect/value_pure.go | 60 - .../protoreflect/value_unsafe_go120.go | 3 +- .../protoreflect/value_unsafe_go121.go | 3 +- .../protobuf/runtime/protoiface/methods.go | 18 + .../types/known/timestamppb/timestamp.pb.go | 24 +- vendor/modules.txt | 94 +- 348 files changed, 18691 insertions(+), 5325 deletions(-) create mode 100644 http/middleware/compress/gogzip.go create mode 100644 mem/buffer.go create mode 100644 vendor/github.com/99designs/gqlgen/internal/code/alias.go create mode 100644 vendor/github.com/99designs/gqlgen/internal/code/alias_1.23.go create mode 100644 vendor/github.com/99designs/gqlgen/plugin/federation/constants.go delete mode 100644 vendor/github.com/agnivade/levenshtein/.travis.yml create mode 100644 vendor/github.com/caddyserver/certmagic/internal/atomicfile/README create mode 100644 vendor/github.com/caddyserver/certmagic/internal/atomicfile/file.go delete mode 100644 vendor/github.com/lestrrat-go/strftime/internal/errors/errors_fmt.go delete mode 100644 vendor/github.com/lestrrat-go/strftime/internal/errors/errors_pkg.go delete mode 100644 vendor/github.com/pkg/errors/.gitignore delete mode 100644 vendor/github.com/pkg/errors/.travis.yml delete mode 100644 vendor/github.com/pkg/errors/LICENSE delete mode 100644 vendor/github.com/pkg/errors/Makefile delete mode 100644 vendor/github.com/pkg/errors/README.md delete mode 100644 vendor/github.com/pkg/errors/appveyor.yml delete mode 100644 vendor/github.com/pkg/errors/errors.go delete mode 100644 vendor/github.com/pkg/errors/go113.go delete mode 100644 vendor/github.com/pkg/errors/stack.go delete mode 100644 vendor/github.com/prometheus/common/model/labelset_string_go120.go create mode 100644 vendor/github.com/rs/xid/.gitignore create mode 100644 vendor/github.com/tklauser/numcpus/numcpus_list_unsupported.go create mode 100644 vendor/golang.org/x/net/http2/config.go create mode 100644 vendor/golang.org/x/net/http2/config_go124.go create mode 100644 vendor/golang.org/x/net/http2/config_pre_go124.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/vgetrandom_linux.go rename vendor/golang.org/x/{tools/internal/versions/toolchain_go119.go => sys/unix/vgetrandom_unsupported.go} (56%) create mode 100644 vendor/golang.org/x/tools/go/types/typeutil/callee.go create mode 100644 vendor/golang.org/x/tools/go/types/typeutil/imports.go create mode 100644 vendor/golang.org/x/tools/go/types/typeutil/map.go create mode 100644 vendor/golang.org/x/tools/go/types/typeutil/methodsetcache.go create mode 100644 vendor/golang.org/x/tools/go/types/typeutil/ui.go delete mode 100644 vendor/golang.org/x/tools/internal/aliases/aliases_go121.go delete mode 100644 vendor/golang.org/x/tools/internal/gcimporter/newInterface10.go delete mode 100644 vendor/golang.org/x/tools/internal/gcimporter/newInterface11.go create mode 100644 vendor/golang.org/x/tools/internal/gcimporter/predeclared.go delete mode 100644 vendor/golang.org/x/tools/internal/gcimporter/support_go118.go delete mode 100644 vendor/golang.org/x/tools/internal/gcimporter/unified_no.go delete mode 100644 vendor/golang.org/x/tools/internal/gcimporter/unified_yes.go delete mode 100644 vendor/golang.org/x/tools/internal/pkgbits/frames_go1.go delete mode 100644 vendor/golang.org/x/tools/internal/pkgbits/frames_go17.go create mode 100644 vendor/golang.org/x/tools/internal/pkgbits/version.go delete mode 100644 vendor/golang.org/x/tools/internal/tokeninternal/tokeninternal.go create mode 100644 vendor/golang.org/x/tools/internal/typeparams/common.go create mode 100644 vendor/golang.org/x/tools/internal/typeparams/coretype.go create mode 100644 vendor/golang.org/x/tools/internal/typeparams/free.go create mode 100644 vendor/golang.org/x/tools/internal/typeparams/normalize.go create mode 100644 vendor/golang.org/x/tools/internal/typeparams/termlist.go create mode 100644 vendor/golang.org/x/tools/internal/typeparams/typeterm.go create mode 100644 vendor/golang.org/x/tools/internal/typesinternal/element.go delete mode 100644 vendor/golang.org/x/tools/internal/versions/toolchain.go delete mode 100644 vendor/golang.org/x/tools/internal/versions/toolchain_go120.go delete mode 100644 vendor/golang.org/x/tools/internal/versions/toolchain_go121.go delete mode 100644 vendor/golang.org/x/tools/internal/versions/types_go121.go delete mode 100644 vendor/golang.org/x/tools/internal/versions/types_go122.go delete mode 100644 vendor/google.golang.org/protobuf/internal/impl/codec_reflect.go create mode 100644 vendor/google.golang.org/protobuf/internal/impl/equal.go delete mode 100644 vendor/google.golang.org/protobuf/internal/impl/pointer_reflect.go delete mode 100644 vendor/google.golang.org/protobuf/internal/strs/strings_pure.go delete mode 100644 vendor/google.golang.org/protobuf/reflect/protoreflect/value_pure.go diff --git a/app/api/api.go b/app/api/api.go index da5aeeac..73e48d8b 100644 --- a/app/api/api.go +++ b/app/api/api.go @@ -1427,7 +1427,6 @@ func (a *api) start(ctx context.Context) error { Password: "", DefaultFile: "index.html", DefaultContentType: "text/html", - Gzip: true, Filesystem: a.diskfs, Cache: a.cache, }, @@ -1440,7 +1439,6 @@ func (a *api) start(ctx context.Context) error { Password: cfg.Storage.Memory.Auth.Password, DefaultFile: "", DefaultContentType: "application/data", - Gzip: true, Filesystem: a.memfs, Cache: nil, }, @@ -1456,7 +1454,6 @@ func (a *api) start(ctx context.Context) error { Password: s3.Auth.Password, DefaultFile: "", DefaultContentType: "application/data", - Gzip: true, Filesystem: a.s3fs[s3.Name], Cache: a.cache, }) @@ -1469,7 +1466,7 @@ func (a *api) start(ctx context.Context) error { Restream: a.restream, Metrics: a.metrics, Prometheus: a.prom, - MimeTypesFile: cfg.Storage.MimeTypes, + MimeTypesFile: cfg.Storage.MimeTypesFile, Filesystems: httpfilesystems, IPLimiter: iplimiter, Profiling: cfg.Debug.Profiling, @@ -1501,6 +1498,11 @@ func (a *api) start(ctx context.Context) error { return false }, Resources: a.resources, + Compress: http.CompressConfig{ + Encoding: cfg.Compress.Encoding, + MimeTypes: cfg.Compress.MimeTypes, + MinLength: cfg.Compress.MinLength, + }, } mainserverhandler, err := http.NewServer(serverConfig) diff --git a/config/config.go b/config/config.go index 317da336..e0364c03 100644 --- a/config/config.go +++ b/config/config.go @@ -94,6 +94,7 @@ func (d *Config) Clone() *Config { data.Log = d.Log data.DB = d.DB data.Host = d.Host + data.Compress = d.Compress data.API = d.API data.TLS = d.TLS data.Storage = d.Storage @@ -113,6 +114,9 @@ func (d *Config) Clone() *Config { data.Host.Name = slices.Copy(d.Host.Name) + data.Compress.Encoding = slices.Copy(d.Compress.Encoding) + data.Compress.MimeTypes = slices.Copy(d.Compress.MimeTypes) + data.API.Access.HTTP.Allow = slices.Copy(d.API.Access.HTTP.Allow) data.API.Access.HTTP.Block = slices.Copy(d.API.Access.HTTP.Block) data.API.Access.HTTPS.Allow = slices.Copy(d.API.Access.HTTPS.Allow) @@ -164,6 +168,21 @@ func (d *Config) init() { d.vars.Register(value.NewStringList(&d.Host.Name, []string{}, ","), "host.name", "CORE_HOST_NAME", nil, "Comma separated list of public host/domain names or IPs", false, false) d.vars.Register(value.NewBool(&d.Host.Auto, true), "host.auto", "CORE_HOST_AUTO", nil, "Enable detection of public IP addresses", false, false) + d.vars.Register(value.NewStringList(&d.Compress.Encoding, []string{"gzip"}, ","), "compress.encoding", "CORE_COMPRESS_ENCODING", nil, "Comma separated list of content encodings", false, false) + d.vars.Register(value.NewStringList(&d.Compress.MimeTypes, []string{ + "text/plain", + "text/html", + "text/css", + "text/javascript", + "application/json", + "application/x-mpegurl", + "application/vnd.apple.mpegurl", + "image/svg+xml", + "text/event-stream", + "application/x-json-stream", + }, ","), "compress.mimetypes", "CORE_COMPRESS_MIMETYPES", nil, "Comma separated list of mimetypes to compress", false, false) + d.vars.Register(value.NewInt(&d.Compress.MinLength, 1000), "compress.min_length", "CORE_COMPRESS_MIN_LENGTH", nil, "Minimum size before compression will be used", false, false) + // API d.vars.Register(value.NewBool(&d.API.ReadOnly, false), "api.read_only", "CORE_API_READ_ONLY", nil, "Allow only ready only access to the API", false, false) d.vars.Register(value.NewCIDRList(&d.API.Access.HTTP.Allow, []string{}, ","), "api.access.http.allow", "CORE_API_ACCESS_HTTP_ALLOW", nil, "List of IPs in CIDR notation (HTTP traffic)", false, false) @@ -193,7 +212,7 @@ func (d *Config) init() { d.vars.Register(value.NewFile(&d.TLS.KeyFile, "", d.fs), "tls.key_file", "CORE_TLS_KEY_FILE", []string{"CORE_TLS_KEYFILE"}, "Path to key file in PEM format", false, false) // Storage - d.vars.Register(value.NewFile(&d.Storage.MimeTypes, "./mime.types", d.fs), "storage.mimetypes_file", "CORE_STORAGE_MIMETYPES_FILE", []string{"CORE_MIMETYPES_FILE"}, "Path to file with mime-types", false, false) + d.vars.Register(value.NewFile(&d.Storage.MimeTypesFile, "./mime.types", d.fs), "storage.mimetypes_file", "CORE_STORAGE_MIMETYPES_FILE", []string{"CORE_MIMETYPES_FILE"}, "Path to file with mime-types", false, false) // Storage (Disk) d.vars.Register(value.NewMustDir(&d.Storage.Disk.Dir, "./data", d.fs), "storage.disk.dir", "CORE_STORAGE_DISK_DIR", nil, "Directory on disk, exposed on /", false, false) diff --git a/config/data.go b/config/data.go index 887c8e98..26c77054 100644 --- a/config/data.go +++ b/config/data.go @@ -32,6 +32,11 @@ type Data struct { Name []string `json:"name"` Auto bool `json:"auto"` } `json:"host"` + Compress struct { + Encoding []string `json:"encoding"` + MimeTypes []string `json:"mimetypes"` + MinLength int `json:"min_length" jsonschema:"minimum=0"` + } `json:"compress"` API struct { ReadOnly bool `json:"read_only"` Access struct { @@ -100,7 +105,7 @@ type Data struct { CORS struct { Origins []string `json:"origins"` } `json:"cors"` - MimeTypes string `json:"mimetypes_file"` + MimeTypesFile string `json:"mimetypes_file"` } `json:"storage"` RTMP struct { Enable bool `json:"enable"` @@ -259,7 +264,7 @@ func MergeV2toV3(data *Data, d *v2.Data) (*Data, error) { data.Router.BlockedPrefixes = slices.Copy(d.Router.BlockedPrefixes) data.Router.Routes = copy.StringMap(d.Router.Routes) - data.Storage.MimeTypes = d.Storage.MimeTypes + data.Storage.MimeTypesFile = d.Storage.MimeTypes data.Storage.CORS = d.Storage.CORS data.Storage.CORS.Origins = slices.Copy(d.Storage.CORS.Origins) @@ -367,7 +372,7 @@ func DowngradeV3toV2(d *Data) (*v2.Data, error) { data.TLS.CertFile = d.TLS.CertFile data.TLS.KeyFile = d.TLS.KeyFile - data.Storage.MimeTypes = d.Storage.MimeTypes + data.Storage.MimeTypes = d.Storage.MimeTypesFile data.Storage.CORS = d.Storage.CORS data.Storage.CORS.Origins = slices.Copy(d.Storage.CORS.Origins) diff --git a/go.mod b/go.mod index f1d41509..8e8ab4ff 100644 --- a/go.mod +++ b/go.mod @@ -1,58 +1,58 @@ module github.com/datarhei/core/v16 -go 1.22.0 +go 1.22.5 -toolchain go1.22.1 +toolchain go1.23.1 require ( - github.com/99designs/gqlgen v0.17.49 - github.com/Masterminds/semver/v3 v3.2.1 - github.com/adhocore/gronx v1.19.0 + github.com/99designs/gqlgen v0.17.55 + github.com/Masterminds/semver/v3 v3.3.0 + github.com/adhocore/gronx v1.19.1 github.com/andybalholm/brotli v1.1.0 github.com/atrox/haikunatorgo/v2 v2.0.1 - github.com/caddyserver/certmagic v0.21.3 + github.com/caddyserver/certmagic v0.21.4 github.com/datarhei/gosrt v0.7.0 github.com/datarhei/joy4 v0.0.0-20240603190808-b1407345907e github.com/dolthub/swiss v0.2.1 github.com/fujiwara/shapeio v1.0.0 - github.com/go-playground/validator/v10 v10.22.0 + github.com/go-playground/validator/v10 v10.22.1 github.com/gobwas/glob v0.2.3 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/gops v0.3.28 github.com/google/uuid v1.6.0 github.com/hashicorp/go-hclog v1.6.3 - github.com/hashicorp/raft v1.7.0 + github.com/hashicorp/raft v1.7.1 github.com/hashicorp/raft-boltdb/v2 v2.3.0 github.com/invopop/jsonschema v0.4.0 github.com/joho/godotenv v1.5.1 - github.com/klauspost/compress v1.17.9 + github.com/klauspost/compress v1.17.10 github.com/klauspost/cpuid/v2 v2.2.8 github.com/labstack/echo/v4 v4.12.0 - github.com/lestrrat-go/strftime v1.0.6 + github.com/lestrrat-go/strftime v1.1.0 github.com/lithammer/shortuuid/v4 v4.0.0 github.com/mattn/go-isatty v0.0.20 - github.com/minio/minio-go/v7 v7.0.75 + github.com/minio/minio-go/v7 v7.0.77 github.com/prep/average v0.0.0-20200506183628-d26c465f48c3 - github.com/prometheus/client_golang v1.20.0 + github.com/prometheus/client_golang v1.20.4 github.com/puzpuzpuz/xsync/v3 v3.4.0 github.com/shirou/gopsutil/v3 v3.24.5 github.com/stretchr/testify v1.9.0 github.com/swaggo/echo-swagger v1.4.1 github.com/swaggo/swag v1.16.3 github.com/tklauser/go-sysconf v0.3.14 - github.com/vektah/gqlparser/v2 v2.5.16 + github.com/vektah/gqlparser/v2 v2.5.17 github.com/xeipuuv/gojsonschema v1.2.0 - go.etcd.io/bbolt v1.3.10 - go.uber.org/automaxprocs v1.5.3 + go.etcd.io/bbolt v1.3.11 + go.uber.org/automaxprocs v1.6.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.26.0 - golang.org/x/mod v0.20.0 + golang.org/x/crypto v0.28.0 + golang.org/x/mod v0.21.0 ) require ( github.com/KyleBanks/depth v1.2.1 // indirect - github.com/agnivade/levenshtein v1.1.1 // indirect + github.com/agnivade/levenshtein v1.2.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -86,42 +86,41 @@ require ( github.com/labstack/gommon v0.4.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/libdns/libdns v0.2.2 // indirect - github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect + github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mholt/acmez/v2 v2.0.2 // indirect + github.com/mholt/acmez/v2 v2.0.3 // indirect github.com/miekg/dns v1.1.62 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.60.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/rs/xid v1.5.0 // indirect + github.com/rs/xid v1.6.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sosodev/duration v1.3.1 // indirect github.com/swaggo/files/v2 v2.0.1 // indirect - github.com/tklauser/numcpus v0.8.0 // indirect - github.com/urfave/cli/v2 v2.27.2 // indirect + github.com/tklauser/numcpus v0.9.0 // indirect + github.com/urfave/cli/v2 v2.27.4 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zeebo/blake3 v0.2.4 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.28.0 // indirect + golang.org/x/net v0.30.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.17.0 // indirect - golang.org/x/time v0.6.0 // indirect - golang.org/x/tools v0.24.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + golang.org/x/time v0.7.0 // indirect + golang.org/x/tools v0.26.0 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 381e7d01..8ea02977 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,16 @@ -github.com/99designs/gqlgen v0.17.49 h1:b3hNGexHd33fBSAd4NDT/c3NCcQzcAVkknhN9ym36YQ= -github.com/99designs/gqlgen v0.17.49/go.mod h1:tC8YFVZMed81x7UJ7ORUwXF4Kn6SXuucFqQBhN8+BU0= +github.com/99designs/gqlgen v0.17.55 h1:3vzrNWYyzSZjGDFo68e5j9sSauLxfKvLp+6ioRokVtM= +github.com/99designs/gqlgen v0.17.55/go.mod h1:3Bq768f8hgVPGZxL8aY9MaYmbxa6llPM/qu1IGH1EJo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE= -github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk= -github.com/adhocore/gronx v1.19.0 h1:GrEvNMPDwXND+YFadCyFVQPC+/xxoGJaQzu+duNf6aU= -github.com/adhocore/gronx v1.19.0/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= -github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= -github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/PuerkitoBio/goquery v1.9.3 h1:mpJr/ikUA9/GNJB/DBZcGeFDXUtosHRyRrwh7KGdTG0= +github.com/PuerkitoBio/goquery v1.9.3/go.mod h1:1ndLHPdTz+DyQPICCWYlYQMPl0oXZj0G6D4LCYA6u4U= +github.com/adhocore/gronx v1.19.1 h1:S4c3uVp5jPjnk00De0lslyTenGJ4nA3Ydbkj1SbdPVc= +github.com/adhocore/gronx v1.19.1/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg= +github.com/agnivade/levenshtein v1.2.0 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY= +github.com/agnivade/levenshtein v1.2.0/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -35,8 +35,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= -github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0= -github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI= +github.com/caddyserver/certmagic v0.21.4 h1:e7VobB8rffHv8ZZpSiZtEwnLDHUwLVYLWzWSa1FfKI0= +github.com/caddyserver/certmagic v0.21.4/go.mod h1:swUXjQ1T9ZtMv95qj7/InJvWLXURU85r+CfG0T+ZbDE= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -53,8 +53,8 @@ github.com/datarhei/joy4 v0.0.0-20240603190808-b1407345907e/go.mod h1:Jcw/6jZDQQ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= -github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ= github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4= github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw= @@ -94,8 +94,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= -github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= @@ -141,8 +141,8 @@ github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iP github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hashicorp/raft v1.7.0 h1:4u24Qn6lQ6uwziM++UgsyiT64Q8GyRn43CV41qPiz1o= -github.com/hashicorp/raft v1.7.0/go.mod h1:N1sKh6Vn47mrWvEArQgILTyng8GoDRNYlgKyK7PMjs0= +github.com/hashicorp/raft v1.7.1 h1:ytxsNx4baHsRZrhUcbt3+79zc4ly8qm7pi0393pSchY= +github.com/hashicorp/raft v1.7.1/go.mod h1:hUeiEwQQR/Nk2iKDD0dkEhklSsu3jcAcqvPzPoZSAEM= github.com/hashicorp/raft-boltdb v0.0.0-20230125174641-2a8082862702 h1:RLKEcCuKcZ+qp2VlaaZsYZfLOmIiuJNpEi48Rl8u9cQ= github.com/hashicorp/raft-boltdb v0.0.0-20230125174641-2a8082862702/go.mod h1:nTakvJ4XYq45UXtn0DbwR4aU9ZdjlnIenpbs6Cd+FM0= github.com/hashicorp/raft-boltdb/v2 v2.3.0 h1:fPpQR1iGEVYjZ2OELvUHX600VAK5qmdnDEv3eXOwZUA= @@ -159,8 +159,8 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0= +github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= @@ -183,14 +183,14 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= -github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ= -github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw= +github.com/lestrrat-go/strftime v1.1.0 h1:gMESpZy44/4pXLO/m+sL0yBd1W6LjgjrrD4a68Gapyg= +github.com/lestrrat-go/strftime v1.1.0/go.mod h1:uzeIB52CeUJenCo1syghlugshMysrqUT51HlxphXVeI= github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c= github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y= -github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI= -github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= +github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -203,14 +203,14 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mholt/acmez/v2 v2.0.2 h1:OmK6xckte2JfKGPz4OAA8aNHTiLvGp8tLzmrd/wfSyw= -github.com/mholt/acmez/v2 v2.0.2/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U= +github.com/mholt/acmez/v2 v2.0.3 h1:CgDBlEwg3QBp6s45tPQmFIBrkRIkBT4rW4orMM6p4sw= +github.com/mholt/acmez/v2 v2.0.3/go.mod h1:pQ1ysaDeGrIMvJ9dfJMk5kJNkn7L2sb3UhyrX6Q91cw= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.75 h1:0uLrB6u6teY2Jt+cJUVi9cTvDRuBKWSRzSAcznRkwlE= -github.com/minio/minio-go/v7 v7.0.75/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8= +github.com/minio/minio-go/v7 v7.0.77 h1:GaGghJRg9nwDVlNbwYjSDJT1rqltQkBFDsypWX1v3Bw= +github.com/minio/minio-go/v7 v7.0.77/go.mod h1:AVM3IUN6WwKzmwBxVdjzhH8xq+f57JSbbvzqvUzR6eg= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -224,8 +224,6 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -238,8 +236,8 @@ github.com/prep/average v0.0.0-20200506183628-d26c465f48c3/go.mod h1:0ZE5gcyWKS1 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI= -github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -247,8 +245,8 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= +github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= @@ -258,8 +256,8 @@ github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+ github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= @@ -291,17 +289,17 @@ github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= -github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= -github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= +github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= +github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= -github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= +github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8= +github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/vektah/gqlparser/v2 v2.5.16 h1:1gcmLTvs3JLKXckwCwlUagVn/IlV2bwqle0vJ0vy5p8= -github.com/vektah/gqlparser/v2 v2.5.16/go.mod h1:1lz1OeCqgQbQepsGxPVywrjdBHW2T08PUS3pJqepRww= +github.com/vektah/gqlparser/v2 v2.5.17 h1:9At7WblLV7/36nulgekUgIaqHZWn5hxqluxrxGUhOmI= +github.com/vektah/gqlparser/v2 v2.5.17/go.mod h1:1lz1OeCqgQbQepsGxPVywrjdBHW2T08PUS3pJqepRww= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -309,8 +307,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= -github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= @@ -319,10 +317,10 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= -go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= -go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= -go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= -go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -331,14 +329,14 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -360,19 +358,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/http/fs/fs.go b/http/fs/fs.go index 500ab733..d1f5cc74 100644 --- a/http/fs/fs.go +++ b/http/fs/fs.go @@ -17,7 +17,6 @@ type FS struct { DefaultFile string DefaultContentType string - Gzip bool Filesystem fs.Filesystem diff --git a/http/handler/api/config_test.go b/http/handler/api/config_test.go index 0757cfb0..69d67f9e 100644 --- a/http/handler/api/config_test.go +++ b/http/handler/api/config_test.go @@ -51,7 +51,7 @@ func TestConfigSetConflict(t *testing.T) { router, _ := getDummyConfigRouter(t) cfg := config.New(nil) - cfg.Storage.MimeTypes = "/path/to/mime.types" + cfg.Storage.MimeTypesFile = "/path/to/mime.types" var data bytes.Buffer diff --git a/http/middleware/compress/brotli.go b/http/middleware/compress/brotli.go index 1f692aa9..813657bc 100644 --- a/http/middleware/compress/brotli.go +++ b/http/middleware/compress/brotli.go @@ -15,7 +15,7 @@ func NewBrotli(level Level) Compression { brotliLevel := brotli.DefaultCompression if level == BestCompression { brotliLevel = brotli.BestCompression - } else { + } else if level == BestSpeed { brotliLevel = brotli.BestSpeed } diff --git a/http/middleware/compress/compress.go b/http/middleware/compress/compress.go index 6e5616eb..43688d2f 100644 --- a/http/middleware/compress/compress.go +++ b/http/middleware/compress/compress.go @@ -8,8 +8,9 @@ import ( "net" "net/http" "strings" - "sync" + "github.com/datarhei/core/v16/mem" + "github.com/datarhei/core/v16/slices" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" ) @@ -27,8 +28,11 @@ type Config struct { // is used. Optional. Default value 0 MinLength int - // Schemes is a list of enabled compressiond. Optional. Default [GzipScheme, ZstdScheme] - Schemes []Scheme + // Schemes is a list of enabled compressiond. Optional. Default [gzip] + Schemes []string + + // List of content type to compress. If empty, everything will be compressed + ContentTypes []string } type Compression interface { @@ -46,6 +50,7 @@ type Compressor interface { type compressResponseWriter struct { Compressor http.ResponseWriter + hasHeader bool wroteHeader bool wroteBody bool minLength int @@ -54,20 +59,10 @@ type compressResponseWriter struct { code int headerContentLength string scheme string + contentTypes []string + passThrough bool } -type Scheme string - -func (s Scheme) String() string { - return string(s) -} - -const ( - GzipScheme Scheme = "gzip" - BrotliScheme Scheme = "br" - ZstdScheme Scheme = "zstd" -) - type Level int const ( @@ -78,33 +73,11 @@ const ( // DefaultConfig is the default Gzip middleware config. var DefaultConfig = Config{ - Skipper: middleware.DefaultSkipper, - Level: DefaultCompression, - MinLength: 0, - Schemes: []Scheme{GzipScheme}, -} - -// ContentTypesSkipper returns a Skipper based on the list of content types -// that should be compressed. If the list is empty, all responses will be -// compressed. -func ContentTypeSkipper(contentTypes []string) middleware.Skipper { - return func(c echo.Context) bool { - // If no allowed content types are given, compress all - if len(contentTypes) == 0 { - return false - } - - // Iterate through the allowed content types and don't skip if the content type matches - responseContentType := c.Response().Header().Get(echo.HeaderContentType) - - for _, contentType := range contentTypes { - if strings.Contains(responseContentType, contentType) { - return false - } - } - - return true - } + Skipper: middleware.DefaultSkipper, + Level: DefaultCompression, + MinLength: 0, + Schemes: []string{"gzip"}, + ContentTypes: []string{}, } // New returns a middleware which compresses HTTP response using a compression @@ -133,38 +106,40 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { config.Schemes = DefaultConfig.Schemes } + contentTypes := slices.Copy(config.ContentTypes) + gzipEnable := false brotliEnable := false zstdEnable := false for _, s := range config.Schemes { switch s { - case GzipScheme: + case "gzip": gzipEnable = true - case BrotliScheme: + case "br": brotliEnable = true - case ZstdScheme: + case "zstd": zstdEnable = true } } - var gzipPool Compression - var brotliPool Compression - var zstdPool Compression + var gzipCompressor Compression + var brotliCompressor Compression + var zstdCompressor Compression if gzipEnable { - gzipPool = NewGzip(config.Level) + gzipCompressor = NewGzip(config.Level) } if brotliEnable { - brotliPool = NewBrotli(config.Level) + brotliCompressor = NewBrotli(config.Level) } if zstdEnable { - zstdPool = NewZstd(config.Level) + zstdCompressor = NewZstd(config.Level) } - bpool := bufferPool() + bufferPool := mem.NewBufferPool() return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { @@ -173,62 +148,69 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { } res := c.Response() - res.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding) encodings := c.Request().Header.Get(echo.HeaderAcceptEncoding) - var pool Compression - var scheme Scheme - - if zstdEnable && strings.Contains(encodings, ZstdScheme.String()) { - pool = zstdPool - scheme = ZstdScheme - } else if brotliEnable && strings.Contains(encodings, BrotliScheme.String()) { - pool = brotliPool - scheme = BrotliScheme - } else if gzipEnable && strings.Contains(encodings, GzipScheme.String()) { - pool = gzipPool - scheme = GzipScheme + var compress Compression + var scheme string + + if zstdEnable && strings.Contains(encodings, "zstd") { + compress = zstdCompressor + scheme = "zstd" + } else if brotliEnable && strings.Contains(encodings, "br") { + compress = brotliCompressor + scheme = "br" + } else if gzipEnable && strings.Contains(encodings, "gzip") { + compress = gzipCompressor + scheme = "gzip" } - if pool != nil { - w := pool.Acquire() - if w == nil { + if compress != nil { + compressor := compress.Acquire() + if compressor == nil { return echo.NewHTTPError(http.StatusInternalServerError, fmt.Errorf("failed to acquire compressor for %s", scheme)) } rw := res.Writer - w.Reset(rw) + compressor.Reset(rw) - buf := bpool.Get().(*bytes.Buffer) - buf.Reset() + buffer := bufferPool.Get() - grw := &compressResponseWriter{Compressor: w, ResponseWriter: rw, minLength: config.MinLength, buffer: buf, scheme: scheme.String()} + grw := &compressResponseWriter{ + Compressor: compressor, + ResponseWriter: rw, + minLength: config.MinLength, + buffer: buffer, + scheme: scheme, + contentTypes: contentTypes, + } defer func() { - if !grw.wroteBody { - if res.Header().Get(echo.HeaderContentEncoding) == scheme.String() { - res.Header().Del(echo.HeaderContentEncoding) - } - // We have to reset response to it's pristine state when - // nothing is written to body or error is returned. - // See issue #424, #407. - res.Writer = rw - w.Reset(io.Discard) - } else if !grw.minLengthExceeded { - // If the minimum content length hasn't exceeded, write the uncompressed response - res.Writer = rw - if grw.wroteHeader { - // Restore Content-Length header in case it was deleted - if len(grw.headerContentLength) != 0 { - grw.Header().Set(echo.HeaderContentLength, grw.headerContentLength) + if !grw.passThrough { + if !grw.wroteBody { + if res.Header().Get(echo.HeaderContentEncoding) == scheme { + res.Header().Del(echo.HeaderContentEncoding) + } + // We have to reset response to it's pristine state when + // nothing is written to body or error is returned. + // See issue #424, #407. + res.Writer = rw + compressor.Reset(io.Discard) + } else if !grw.minLengthExceeded { + // If the minimum content length hasn't exceeded, write the uncompressed response + res.Writer = rw + if grw.wroteHeader { + // Restore Content-Length header in case it was deleted + if len(grw.headerContentLength) != 0 { + grw.Header().Set(echo.HeaderContentLength, grw.headerContentLength) + } + grw.ResponseWriter.WriteHeader(grw.code) } - grw.ResponseWriter.WriteHeader(grw.code) + grw.buffer.WriteTo(rw) + compressor.Reset(io.Discard) } - grw.buffer.WriteTo(rw) - w.Reset(io.Discard) } - w.Close() - bpool.Put(buf) - pool.Release(w) + compressor.Close() + bufferPool.Put(buffer) + compress.Release(compressor) }() res.Writer = grw @@ -241,17 +223,37 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { func (w *compressResponseWriter) WriteHeader(code int) { if code == http.StatusNoContent { // Issue #489 - w.ResponseWriter.Header().Del(echo.HeaderContentEncoding) + w.Header().Del(echo.HeaderContentEncoding) } w.headerContentLength = w.Header().Get(echo.HeaderContentLength) w.Header().Del(echo.HeaderContentLength) // Issue #444 - w.wroteHeader = true + if !w.canCompress(w.Header().Get(echo.HeaderContentType)) { + w.passThrough = true + } + + w.hasHeader = true // Delay writing of the header until we know if we'll actually compress the response w.code = code } +func (w *compressResponseWriter) canCompress(responseContentType string) bool { + // If no content types are given, compress all + if len(w.contentTypes) == 0 { + return true + } + + // Iterate through the allowed content types and don't skip if the content type matches + for _, contentType := range w.contentTypes { + if strings.Contains(responseContentType, contentType) { + return true + } + } + + return false +} + func (w *compressResponseWriter) Write(b []byte) (int, error) { if w.Header().Get(echo.HeaderContentType) == "" { w.Header().Set(echo.HeaderContentType, http.DetectContentType(b)) @@ -259,6 +261,18 @@ func (w *compressResponseWriter) Write(b []byte) (int, error) { w.wroteBody = true + if !w.hasHeader { + w.WriteHeader(http.StatusOK) + } + + if w.passThrough { + if !w.wroteHeader { + w.ResponseWriter.WriteHeader(w.code) + w.wroteHeader = true + } + return w.ResponseWriter.Write(b) + } + if !w.minLengthExceeded { n, err := w.buffer.Write(b) @@ -267,8 +281,10 @@ func (w *compressResponseWriter) Write(b []byte) (int, error) { // The minimum length is exceeded, add Content-Encoding header and write the header w.Header().Set(echo.HeaderContentEncoding, w.scheme) // Issue #806 - if w.wroteHeader { + w.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding) + if w.hasHeader { w.ResponseWriter.WriteHeader(w.code) + w.wroteHeader = true } return w.Compressor.Write(w.buffer.Bytes()) @@ -281,12 +297,31 @@ func (w *compressResponseWriter) Write(b []byte) (int, error) { } func (w *compressResponseWriter) Flush() { + if !w.hasHeader { + w.WriteHeader(http.StatusOK) + } + + if w.passThrough { + if !w.wroteHeader { + w.ResponseWriter.WriteHeader(w.code) + w.wroteHeader = true + } + + if flusher, ok := w.ResponseWriter.(http.Flusher); ok { + flusher.Flush() + } + + return + } + if !w.minLengthExceeded { // Enforce compression w.minLengthExceeded = true w.Header().Set(echo.HeaderContentEncoding, w.scheme) // Issue #806 - if w.wroteHeader { + w.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding) + if w.hasHeader { w.ResponseWriter.WriteHeader(w.code) + w.wroteHeader = true } w.Compressor.Write(w.buffer.Bytes()) @@ -308,12 +343,3 @@ func (w *compressResponseWriter) Push(target string, opts *http.PushOptions) err } return http.ErrNotSupported } - -func bufferPool() sync.Pool { - return sync.Pool{ - New: func() interface{} { - b := &bytes.Buffer{} - return b - }, - } -} diff --git a/http/middleware/compress/compress_test.go b/http/middleware/compress/compress_test.go index 60888989..b42971d2 100644 --- a/http/middleware/compress/compress_test.go +++ b/http/middleware/compress/compress_test.go @@ -58,15 +58,15 @@ func (rcr *nopReadCloseResetter) Reset(r io.Reader) error { return resetter.Reset(r) } -func getTestcases() map[Scheme]func(r io.Reader) (ReadCloseResetter, error) { - return map[Scheme]func(r io.Reader) (ReadCloseResetter, error){ - GzipScheme: func(r io.Reader) (ReadCloseResetter, error) { +func getTestcases() map[string]func(r io.Reader) (ReadCloseResetter, error) { + return map[string]func(r io.Reader) (ReadCloseResetter, error){ + "gzip": func(r io.Reader) (ReadCloseResetter, error) { return gzip.NewReader(r) }, - BrotliScheme: func(r io.Reader) (ReadCloseResetter, error) { + "br": func(r io.Reader) (ReadCloseResetter, error) { return &nopReadCloseResetter{brotli.NewReader(r)}, nil }, - ZstdScheme: func(r io.Reader) (ReadCloseResetter, error) { + "zstd": func(r io.Reader) (ReadCloseResetter, error) { reader, err := zstd.NewReader(r) return &nopReadCloseResetter{reader}, err }, @@ -77,18 +77,18 @@ func TestCompress(t *testing.T) { schemes := getTestcases() for scheme, reader := range schemes { - t.Run(scheme.String(), func(t *testing.T) { + t.Run(scheme, func(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() - c := e.NewContext(req, rec) + ctx := e.NewContext(req, rec) // Skip if no Accept-Encoding header - h := NewWithConfig(Config{Schemes: []Scheme{scheme}})(func(c echo.Context) error { + handler := NewWithConfig(Config{Schemes: []string{scheme}})(func(c echo.Context) error { c.Response().Write([]byte("test")) // For Content-Type sniffing return nil }) - h(c) + handler(ctx) assert := assert.New(t) @@ -96,15 +96,15 @@ func TestCompress(t *testing.T) { // Compression req = httptest.NewRequest(http.MethodGet, "/", nil) - req.Header.Set(echo.HeaderAcceptEncoding, scheme.String()) + req.Header.Set(echo.HeaderAcceptEncoding, scheme) rec = httptest.NewRecorder() - c = e.NewContext(req, rec) - h(c) - assert.Equal(scheme.String(), rec.Header().Get(echo.HeaderContentEncoding)) + ctx = e.NewContext(req, rec) + handler(ctx) + assert.Equal(scheme, rec.Header().Get(echo.HeaderContentEncoding)) assert.Contains(rec.Header().Get(echo.HeaderContentType), echo.MIMETextPlain) r, err := reader(rec.Body) if assert.NoError(err) { - buf := new(bytes.Buffer) + buf := &bytes.Buffer{} defer r.Close() buf.ReadFrom(r) assert.Equal("test", buf.String()) @@ -112,11 +112,11 @@ func TestCompress(t *testing.T) { // Gzip chunked req = httptest.NewRequest(http.MethodGet, "/", nil) - req.Header.Set(echo.HeaderAcceptEncoding, scheme.String()) + req.Header.Set(echo.HeaderAcceptEncoding, scheme) rec = httptest.NewRecorder() - c = e.NewContext(req, rec) - NewWithConfig(Config{Schemes: []Scheme{scheme}})(func(c echo.Context) error { + ctx = e.NewContext(req, rec) + NewWithConfig(Config{Schemes: []string{scheme}})(func(c echo.Context) error { c.Response().Header().Set("Content-Type", "text/event-stream") c.Response().Header().Set("Transfer-Encoding", "chunked") @@ -126,7 +126,7 @@ func TestCompress(t *testing.T) { // Read the first part of the data assert.True(rec.Flushed) - assert.Equal(scheme.String(), rec.Header().Get(echo.HeaderContentEncoding)) + assert.Equal(scheme, rec.Header().Get(echo.HeaderContentEncoding)) // Write and flush the second part of the data c.Response().Write([]byte("tost\n")) @@ -135,7 +135,7 @@ func TestCompress(t *testing.T) { // Write the final part of the data and return c.Response().Write([]byte("tast")) return nil - })(c) + })(ctx) buf := new(bytes.Buffer) r.Reset(rec.Body) @@ -146,14 +146,53 @@ func TestCompress(t *testing.T) { } } +func TestCompressWithPassthrough(t *testing.T) { + schemes := getTestcases() + + for scheme, reader := range schemes { + t.Run(scheme, func(t *testing.T) { + e := echo.New() + e.Use(NewWithConfig(Config{MinLength: 5, Schemes: []string{scheme}, ContentTypes: []string{"text/compress"}})) + e.GET("/plain", func(c echo.Context) error { + c.Response().Header().Set("Content-Type", "text/plain") + c.Response().Write([]byte("testtest")) + return nil + }) + e.GET("/compress", func(c echo.Context) error { + c.Response().Header().Set("Content-Type", "text/compress") + c.Response().Write([]byte("testtest")) + return nil + }) + req := httptest.NewRequest(http.MethodGet, "/plain", nil) + req.Header.Set(echo.HeaderAcceptEncoding, scheme) + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + assert.Equal(t, "", rec.Header().Get(echo.HeaderContentEncoding)) + assert.Contains(t, rec.Body.String(), "testtest") + + req = httptest.NewRequest(http.MethodGet, "/compress", nil) + req.Header.Set(echo.HeaderAcceptEncoding, scheme) + rec = httptest.NewRecorder() + e.ServeHTTP(rec, req) + assert.Equal(t, scheme, rec.Header().Get(echo.HeaderContentEncoding)) + r, err := reader(rec.Body) + if assert.NoError(t, err) { + buf := new(bytes.Buffer) + defer r.Close() + buf.ReadFrom(r) + assert.Equal(t, "testtest", buf.String()) + } + }) + } +} + func TestCompressWithMinLength(t *testing.T) { schemes := getTestcases() for scheme, reader := range schemes { - t.Run(scheme.String(), func(t *testing.T) { + t.Run(scheme, func(t *testing.T) { e := echo.New() - // Invalid level - e.Use(NewWithConfig(Config{MinLength: 5, Schemes: []Scheme{scheme}})) + e.Use(NewWithConfig(Config{MinLength: 5, Schemes: []string{scheme}})) e.GET("/", func(c echo.Context) error { c.Response().Write([]byte("test")) return nil @@ -163,17 +202,17 @@ func TestCompressWithMinLength(t *testing.T) { return nil }) req := httptest.NewRequest(http.MethodGet, "/", nil) - req.Header.Set(echo.HeaderAcceptEncoding, scheme.String()) + req.Header.Set(echo.HeaderAcceptEncoding, scheme) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) assert.Equal(t, "", rec.Header().Get(echo.HeaderContentEncoding)) assert.Contains(t, rec.Body.String(), "test") req = httptest.NewRequest(http.MethodGet, "/foobar", nil) - req.Header.Set(echo.HeaderAcceptEncoding, scheme.String()) + req.Header.Set(echo.HeaderAcceptEncoding, scheme) rec = httptest.NewRecorder() e.ServeHTTP(rec, req) - assert.Equal(t, scheme.String(), rec.Header().Get(echo.HeaderContentEncoding)) + assert.Equal(t, scheme, rec.Header().Get(echo.HeaderContentEncoding)) r, err := reader(rec.Body) if assert.NoError(t, err) { buf := new(bytes.Buffer) @@ -185,17 +224,60 @@ func TestCompressWithMinLength(t *testing.T) { } } +func TestCompressWithAroundMinLength(t *testing.T) { + schemes := getTestcases() + minLength := 1000 + + for scheme, reader := range schemes { + for i := minLength - 64; i < minLength+64; i++ { + name := fmt.Sprintf("%s-%d", scheme, i) + + t.Run(name, func(t *testing.T) { + data := rand.Bytes(i) + e := echo.New() + e.Use(NewWithConfig(Config{MinLength: minLength, Schemes: []string{scheme}})) + e.GET("/", func(c echo.Context) error { + c.Response().Write(data[:1]) + c.Response().Write(data[1:]) + return nil + }) + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set(echo.HeaderAcceptEncoding, scheme) + rec := httptest.NewRecorder() + e.ServeHTTP(rec, req) + + if i < minLength { + assert.Equal(t, "", rec.Header().Get(echo.HeaderContentEncoding)) + res, err := io.ReadAll(rec.Body) + if assert.NoError(t, err) { + assert.Equal(t, data, res) + } + } else { + assert.Equal(t, scheme, rec.Header().Get(echo.HeaderContentEncoding)) + r, err := reader(rec.Body) + if assert.NoError(t, err) { + buf := new(bytes.Buffer) + defer r.Close() + buf.ReadFrom(r) + assert.Equal(t, data, buf.Bytes()) + } + } + }) + } + } +} + func TestCompressNoContent(t *testing.T) { schemes := getTestcases() for scheme := range schemes { - t.Run(scheme.String(), func(t *testing.T) { + t.Run(scheme, func(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) - req.Header.Set(echo.HeaderAcceptEncoding, scheme.String()) + req.Header.Set(echo.HeaderAcceptEncoding, scheme) rec := httptest.NewRecorder() c := e.NewContext(req, rec) - h := NewWithConfig(Config{Schemes: []Scheme{scheme}})(func(c echo.Context) error { + h := NewWithConfig(Config{Schemes: []string{scheme}})(func(c echo.Context) error { return c.NoContent(http.StatusNoContent) }) if assert.NoError(t, h(c)) { @@ -211,17 +293,17 @@ func TestCompressEmpty(t *testing.T) { schemes := getTestcases() for scheme, reader := range schemes { - t.Run(scheme.String(), func(t *testing.T) { + t.Run(scheme, func(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) - req.Header.Set(echo.HeaderAcceptEncoding, scheme.String()) + req.Header.Set(echo.HeaderAcceptEncoding, scheme) rec := httptest.NewRecorder() c := e.NewContext(req, rec) - h := NewWithConfig(Config{Schemes: []Scheme{scheme}})(func(c echo.Context) error { + h := NewWithConfig(Config{Schemes: []string{scheme}})(func(c echo.Context) error { return c.String(http.StatusOK, "") }) if assert.NoError(t, h(c)) { - assert.Equal(t, scheme.String(), rec.Header().Get(echo.HeaderContentEncoding)) + assert.Equal(t, scheme, rec.Header().Get(echo.HeaderContentEncoding)) assert.Equal(t, "text/plain; charset=UTF-8", rec.Header().Get(echo.HeaderContentType)) r, err := reader(rec.Body) if assert.NoError(t, err) { @@ -238,14 +320,14 @@ func TestCompressErrorReturned(t *testing.T) { schemes := getTestcases() for scheme := range schemes { - t.Run(scheme.String(), func(t *testing.T) { + t.Run(scheme, func(t *testing.T) { e := echo.New() - e.Use(NewWithConfig(Config{Schemes: []Scheme{scheme}})) + e.Use(NewWithConfig(Config{Schemes: []string{scheme}})) e.GET("/", func(c echo.Context) error { return echo.ErrNotFound }) req := httptest.NewRequest(http.MethodGet, "/", nil) - req.Header.Set(echo.HeaderAcceptEncoding, scheme.String()) + req.Header.Set(echo.HeaderAcceptEncoding, scheme) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) assert.Equal(t, http.StatusNotFound, rec.Code) @@ -259,12 +341,12 @@ func TestCompressWithStatic(t *testing.T) { schemes := getTestcases() for scheme, reader := range schemes { - t.Run(scheme.String(), func(t *testing.T) { + t.Run(scheme, func(t *testing.T) { e := echo.New() - e.Use(NewWithConfig(Config{Schemes: []Scheme{scheme}})) + e.Use(NewWithConfig(Config{Schemes: []string{scheme}})) e.Static("/test", "./") req := httptest.NewRequest(http.MethodGet, "/test/compress.go", nil) - req.Header.Set(echo.HeaderAcceptEncoding, scheme.String()) + req.Header.Set(echo.HeaderAcceptEncoding, scheme) rec := httptest.NewRecorder() e.ServeHTTP(rec, req) assert.Equal(t, http.StatusOK, rec.Code) @@ -292,17 +374,17 @@ func BenchmarkCompress(b *testing.B) { for i := 1; i <= 18; i++ { datalen := 2 << i - data := []byte(rand.String(datalen)) + data := rand.Bytes(datalen) for scheme := range schemes { - name := fmt.Sprintf("%s-%d", scheme.String(), datalen) + name := fmt.Sprintf("%s-%d", scheme, datalen) b.Run(name, func(b *testing.B) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) - req.Header.Set(echo.HeaderAcceptEncoding, scheme.String()) + req.Header.Set(echo.HeaderAcceptEncoding, scheme) - h := NewWithConfig(Config{Level: BestSpeed, Schemes: []Scheme{scheme}})(func(c echo.Context) error { + h := NewWithConfig(Config{Level: BestSpeed, Schemes: []string{scheme}})(func(c echo.Context) error { c.Response().Write(data) return nil }) @@ -327,13 +409,13 @@ func BenchmarkCompressJSON(b *testing.B) { schemes := getTestcases() for scheme := range schemes { - b.Run(scheme.String(), func(b *testing.B) { + b.Run(scheme, func(b *testing.B) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) - req.Header.Set(echo.HeaderAcceptEncoding, scheme.String()) + req.Header.Set(echo.HeaderAcceptEncoding, scheme) - h := NewWithConfig(Config{Level: BestSpeed, Schemes: []Scheme{scheme}})(func(c echo.Context) error { + h := NewWithConfig(Config{Level: BestSpeed, Schemes: []string{scheme}})(func(c echo.Context) error { c.Response().Write(data) return nil }) diff --git a/http/middleware/compress/gogzip.go b/http/middleware/compress/gogzip.go new file mode 100644 index 00000000..589fbb99 --- /dev/null +++ b/http/middleware/compress/gogzip.go @@ -0,0 +1,55 @@ +package compress + +import ( + "compress/gzip" + "io" + "sync" +) + +type gogzipImpl struct { + pool sync.Pool +} + +func NewGoGzip(level Level) Compression { + gzipLevel := gzip.DefaultCompression + if level == BestCompression { + gzipLevel = gzip.BestCompression + } else if level == BestSpeed { + gzipLevel = gzip.BestSpeed + } + + g := &gogzipImpl{ + pool: sync.Pool{ + New: func() interface{} { + w, err := gzip.NewWriterLevel(io.Discard, gzipLevel) + if err != nil { + return nil + } + return w + }, + }, + } + + return g +} + +func (g *gogzipImpl) Acquire() Compressor { + c := g.pool.Get() + if c == nil { + return nil + } + + x, ok := c.(Compressor) + if !ok { + return nil + } + + x.Reset(io.Discard) + + return x +} + +func (g *gogzipImpl) Release(c Compressor) { + c.Reset(io.Discard) + g.pool.Put(c) +} diff --git a/http/middleware/compress/gzip.go b/http/middleware/compress/gzip.go index f2441fe1..2482d4d2 100644 --- a/http/middleware/compress/gzip.go +++ b/http/middleware/compress/gzip.go @@ -15,7 +15,7 @@ func NewGzip(level Level) Compression { gzipLevel := gzip.DefaultCompression if level == BestCompression { gzipLevel = gzip.BestCompression - } else { + } else if level == BestSpeed { gzipLevel = gzip.BestSpeed } diff --git a/http/middleware/compress/zstd.go b/http/middleware/compress/zstd.go index 3eaacb56..970732c6 100644 --- a/http/middleware/compress/zstd.go +++ b/http/middleware/compress/zstd.go @@ -15,7 +15,7 @@ func NewZstd(level Level) Compression { zstdLevel := zstd.SpeedDefault if level == BestCompression { zstdLevel = zstd.SpeedBestCompression - } else { + } else if level == BestSpeed { zstdLevel = zstd.SpeedFastest } diff --git a/http/middleware/session/HLS.go b/http/middleware/session/HLS.go index 6e2ad5c4..fbb714b6 100644 --- a/http/middleware/session/HLS.go +++ b/http/middleware/session/HLS.go @@ -29,7 +29,7 @@ func (h *handler) handleHLS(c echo.Context, ctxuser string, data map[string]inte return next(c) } -func (h *handler) handleHLSIngress(c echo.Context, ctxuser string, data map[string]interface{}, next echo.HandlerFunc) error { +func (h *handler) handleHLSIngress(c echo.Context, _ string, data map[string]interface{}, next echo.HandlerFunc) error { req := c.Request() path := req.URL.Path @@ -97,7 +97,7 @@ func (h *handler) handleHLSIngress(c echo.Context, ctxuser string, data map[stri return next(c) } -func (h *handler) handleHLSEgress(c echo.Context, ctxuser string, data map[string]interface{}, next echo.HandlerFunc) error { +func (h *handler) handleHLSEgress(c echo.Context, _ string, data map[string]interface{}, next echo.HandlerFunc) error { req := c.Request() res := c.Response() diff --git a/http/server.go b/http/server.go index 8f5c2ae1..09322e87 100644 --- a/http/server.go +++ b/http/server.go @@ -102,12 +102,19 @@ type Config struct { IAM iam.IAM IAMSkipper func(ip string) bool Resources resources.Resources + Compress CompressConfig } type CorsConfig struct { Origins []string } +type CompressConfig struct { + Encoding []string + MimeTypes []string + MinLength int +} + type server struct { logger log.Logger @@ -143,8 +150,10 @@ type server struct { iam echo.MiddlewareFunc } - gzip struct { + compress struct { + encoding []string mimetypes []string + minLength int } filesystems map[string]*filesystem @@ -375,15 +384,9 @@ func NewServer(config Config) (serverhandler.Server, error) { IAM: config.IAM, }, "/api/graph/query") - s.gzip.mimetypes = []string{ - "text/plain", - "text/html", - "text/javascript", - "application/json", - //"application/x-mpegurl", - //"application/vnd.apple.mpegurl", - "image/svg+xml", - } + s.compress.encoding = config.Compress.Encoding + s.compress.mimetypes = config.Compress.MimeTypes + s.compress.minLength = config.Compress.MinLength s.router = echo.New() s.router.JSONSerializer = &GoJSONSerializer{} @@ -409,6 +412,13 @@ func NewServer(config Config) (serverhandler.Server, error) { s.router.Use(s.middleware.iam) + s.router.Use(mwcompress.NewWithConfig(mwcompress.Config{ + Level: mwcompress.BestSpeed, + MinLength: config.Compress.MinLength, + Schemes: config.Compress.Encoding, + ContentTypes: config.Compress.MimeTypes, + })) + s.router.Use(mwsession.NewWithConfig(mwsession.Config{ HLSIngressCollector: config.Sessions.Collector("hlsingress"), HLSEgressCollector: config.Sessions.Collector("hls"), @@ -487,13 +497,6 @@ func (s *server) HTTPStatus() map[int]uint64 { } func (s *server) setRoutes() { - gzipMiddleware := mwcompress.NewWithConfig(mwcompress.Config{ - Skipper: mwcompress.ContentTypeSkipper(nil), - Level: mwcompress.BestSpeed, - MinLength: 1000, - Schemes: []mwcompress.Scheme{mwcompress.GzipScheme}, - }) - // API router grouo api := s.router.Group("/api") @@ -509,7 +512,6 @@ func (s *server) setRoutes() { // Swagger API documentation router group doc := s.router.Group("/api/swagger/*") - doc.Use(gzipMiddleware) doc.GET("", echoSwagger.WrapHandler) // Mount filesystems @@ -528,15 +530,6 @@ func (s *server) setRoutes() { DefaultContentType: filesystem.DefaultContentType, })) - if filesystem.Gzip { - fs.Use(mwcompress.NewWithConfig(mwcompress.Config{ - Skipper: mwcompress.ContentTypeSkipper(s.gzip.mimetypes), - Level: mwcompress.BestSpeed, - MinLength: 1000, - Schemes: []mwcompress.Scheme{mwcompress.GzipScheme}, - })) - } - if filesystem.Cache != nil { mwcache := mwcache.NewWithConfig(mwcache.Config{ Cache: filesystem.Cache, @@ -590,7 +583,7 @@ func (s *server) setRoutes() { // GraphQL graphql := api.Group("/graph") - graphql.Use(gzipMiddleware) + //graphql.Use(gzipMiddleware) graphql.GET("", s.handler.graph.Playground) graphql.POST("/query", s.handler.graph.Query) @@ -598,7 +591,7 @@ func (s *server) setRoutes() { // APIv3 router group v3 := api.Group("/v3") - v3.Use(gzipMiddleware) + //v3.Use(gzipMiddleware) s.setRoutesV3(v3) } diff --git a/io/fs/mem.go b/io/fs/mem.go index 97b45318..ba102ed1 100644 --- a/io/fs/mem.go +++ b/io/fs/mem.go @@ -15,6 +15,7 @@ import ( "github.com/datarhei/core/v16/glob" "github.com/datarhei/core/v16/log" + "github.com/datarhei/core/v16/mem" ) // MemConfig is the config that is required for creating @@ -126,37 +127,10 @@ func (f *memFile) free() { f.data = nil } -type fileDataPool struct { - pool sync.Pool -} - -var pool *fileDataPool = nil - -func NewFileDataPool() *fileDataPool { - p := &fileDataPool{ - pool: sync.Pool{ - New: func() any { - return &bytes.Buffer{} - }, - }, - } - - return p -} - -func (p *fileDataPool) Get() *bytes.Buffer { - buf := p.pool.Get().(*bytes.Buffer) - buf.Reset() - - return buf -} - -func (p *fileDataPool) Put(buf *bytes.Buffer) { - p.pool.Put(buf) -} +var pool *mem.BufferPool = nil func init() { - pool = NewFileDataPool() + pool = mem.NewBufferPool() } type memFilesystem struct { diff --git a/math/rand/rand.go b/math/rand/rand.go index 5f81ad90..fead355c 100644 --- a/math/rand/rand.go +++ b/math/rand/rand.go @@ -21,6 +21,12 @@ var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano())) var lock sync.Mutex func StringWithCharset(length int, charset string) string { + b := BytesWithCharset(length, charset) + + return string(b) +} + +func BytesWithCharset(length int, charset string) []byte { lock.Lock() defer lock.Unlock() @@ -29,7 +35,7 @@ func StringWithCharset(length int, charset string) string { b[i] = charset[seededRand.Intn(len(charset))] } - return string(b) + return b } func StringLetters(length int) string { @@ -47,3 +53,7 @@ func StringAlphanumeric(length int) string { func String(length int) string { return StringWithCharset(length, CharsetAll) } + +func Bytes(length int) []byte { + return BytesWithCharset(length, CharsetAll) +} diff --git a/mem/buffer.go b/mem/buffer.go new file mode 100644 index 00000000..bcadc871 --- /dev/null +++ b/mem/buffer.go @@ -0,0 +1,33 @@ +package mem + +import ( + "bytes" + "sync" +) + +type BufferPool struct { + pool sync.Pool +} + +func NewBufferPool() *BufferPool { + p := &BufferPool{ + pool: sync.Pool{ + New: func() any { + return &bytes.Buffer{} + }, + }, + } + + return p +} + +func (p *BufferPool) Get() *bytes.Buffer { + buf := p.pool.Get().(*bytes.Buffer) + buf.Reset() + + return buf +} + +func (p *BufferPool) Put(buf *bytes.Buffer) { + p.pool.Put(buf) +} diff --git a/vendor/github.com/99designs/gqlgen/.golangci.yml b/vendor/github.com/99designs/gqlgen/.golangci.yml index 098727cb..79d6627e 100644 --- a/vendor/github.com/99designs/gqlgen/.golangci.yml +++ b/vendor/github.com/99designs/gqlgen/.golangci.yml @@ -106,6 +106,10 @@ issues: - path: codegen/testserver/.*/resolver\.go linters: - gocritic + # The interfaces are autogenerated and don't conform to the paramTypeCombine rule + - path: _examples/federation/products/graph/entity.resolvers.go + linters: + - gocritic # Disable revive.use-any for backwards compatibility - path: graphql/map.go text: "use-any: since GO 1.18 'interface{}' can be replaced by 'any'" @@ -113,3 +117,11 @@ issues: text: "use-any: since GO 1.18 'interface{}' can be replaced by 'any'" - path: codegen/testserver/singlefile/resolver.go text: "use-any: since GO 1.18 'interface{}' can be replaced by 'any'" + - path: codegen/testserver/generated_test.go + linters: + - staticcheck + text: SA1019 + - path: plugin/modelgen/models_test.go + linters: + - staticcheck + text: SA1019 diff --git a/vendor/github.com/99designs/gqlgen/CHANGELOG.md b/vendor/github.com/99designs/gqlgen/CHANGELOG.md index acea9f11..bbcc1722 100644 --- a/vendor/github.com/99designs/gqlgen/CHANGELOG.md +++ b/vendor/github.com/99designs/gqlgen/CHANGELOG.md @@ -5,10 +5,2085 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased](https://github.com/99designs/gqlgen/compare/v0.17.45...HEAD) +## [Unreleased](https://github.com/99designs/gqlgen/compare/v0.17.50...HEAD) + +## [v0.17.50](https://github.com/99designs/gqlgen/compare/v0.17.49...v0.17.50) - 2024-09-13 +- a6d5d843 release v0.17.50 + +- f154d99d Fix Nancy to use Go 1.22 + +- 6b9e40e8 make rewrite default for resolver layout single-file (#3243) + +

      1855758d chore(deps): bump dset in /integration in the npm_and_yarn group (#3268) + +Bumps the npm_and_yarn group in /integration with 1 update: [dset](https://github.com/lukeed/dset). + + +Updates `dset` from 3.1.3 to 3.1.4 +- [Release notes](https://github.com/lukeed/dset/releases) +- [Commits](https://github.com/lukeed/dset/compare/v3.1.3...v3.1.4) + +--- +updated-dependencies: +- dependency-name: dset + dependency-type: indirect + dependency-group: npm_and_yarn +... + +
      + +
      fda0539e Bump some more module versions (#3262) + +* Bump some more module versions + + +* Update aurora + + +* Avoid upgrade to go 1.23 + + +* downgrade goquery to support pre-Go 1.23 for now + + +* Downgrade moq to support pre-Go 1.23 as well + + +--------- + +
      + +- 59f0d04c Bump golang.org/x/net 0.29 (#3261) + +
      cf42b253 chore(deps): bump golang.org/x/text from 0.17.0 to 0.18.0 (#3259) + +Bumps [golang.org/x/text](https://github.com/golang/text) from 0.17.0 to 0.18.0. +- [Release notes](https://github.com/golang/text/releases) +- [Commits](https://github.com/golang/text/compare/v0.17.0...v0.18.0) + +--- +updated-dependencies: +- dependency-name: golang.org/x/text + dependency-type: direct:production + update-type: version-update:semver-minor +... + +
      + +
      b728c12f chore(deps): bump golang.org/x/text from 0.17.0 to 0.18.0 in /_examples (#3256) + +Bumps [golang.org/x/text](https://github.com/golang/text) from 0.17.0 to 0.18.0. +- [Release notes](https://github.com/golang/text/releases) +- [Commits](https://github.com/golang/text/compare/v0.17.0...v0.18.0) + +--- +updated-dependencies: +- dependency-name: golang.org/x/text + dependency-type: direct:production + update-type: version-update:semver-minor +... + +
      + +
      cba40a38 chore(deps-dev): bump vite from 5.4.2 to 5.4.3 in /integration (#3257) + +Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.2 to 5.4.3. +- [Release notes](https://github.com/vitejs/vite/releases) +- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) +- [Commits](https://github.com/vitejs/vite/commits/v5.4.3/packages/vite) + +--- +updated-dependencies: +- dependency-name: vite + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      f7bee06f chore(deps-dev): bump [@apollo](https://github.com/apollo)/client in /integration (#3258) + +- [Release notes](https://github.com/apollographql/apollo-client/releases) +- [Changelog](https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md) +- [Commits](https://github.com/apollographql/apollo-client/compare/v3.11.5...v3.11.8) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      81ac627d chore(deps): bump robherley/go-test-action from 0.4.1 to 0.5.0 (#3255) + +Bumps [robherley/go-test-action](https://github.com/robherley/go-test-action) from 0.4.1 to 0.5.0. +- [Release notes](https://github.com/robherley/go-test-action/releases) +- [Commits](https://github.com/robherley/go-test-action/compare/v0.4.1...v0.5.0) + +--- +updated-dependencies: +- dependency-name: robherley/go-test-action + dependency-type: direct:production + update-type: version-update:semver-minor +... + +
      + +
      86ac6b36 internal/code: `Unalias` element of pointer (#3250) (closes #3247) + +This reverts commit 4c4be0aeaaad758e703724fe4a6575768017ac53. + +* code: `Unalias` element of pointer + +* chore: added comment + +
      + +- 4c4be0ae codegen: Unalias before lookup type (#3247) + +
      ab1781b1 codegen: Go 1.23 alias support (#3246) + +* code: added `Unalias` for Go 1.22 + +* codegen: Go 1.23 alias support + +
      + +
      814f7c71 chore(deps): bump actions/upload-artifact from 4.3.6 to 4.4.0 (#3235) + +Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.6 to 4.4.0. +- [Release notes](https://github.com/actions/upload-artifact/releases) +- [Commits](https://github.com/actions/upload-artifact/compare/v4.3.6...v4.4.0) + +--- +updated-dependencies: +- dependency-name: actions/upload-artifact + dependency-type: direct:production + update-type: version-update:semver-minor +... + +
      + +
      1cbbc120 chore(deps): bump github.com/rs/cors from 1.11.0 to 1.11.1 in /_examples (#3236) + +Bumps [github.com/rs/cors](https://github.com/rs/cors) from 1.11.0 to 1.11.1. +- [Commits](https://github.com/rs/cors/compare/v1.11.0...v1.11.1) + +--- +updated-dependencies: +- dependency-name: github.com/rs/cors + dependency-type: direct:production + update-type: version-update:semver-patch +... + +
      + +
      2da2ac36 chore(deps-dev): bump [@apollo](https://github.com/apollo)/client in /integration (#3237) + +- [Release notes](https://github.com/apollographql/apollo-client/releases) +- [Changelog](https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md) +- [Commits](https://github.com/apollographql/apollo-client/compare/v3.11.4...v3.11.5) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      0b9bd5ee refactor: don't extract [@goField](https://github.com/goField) twice (#3234) + +We already extract the values in config.Init(). Remove the duplicate logic in the modelgen plugin. + +We leave the reference to GoFieldHook even though it's a noop since it's public. This makes this a non-breaking change. We will remove this during the next breaking release. + +
      + +
      18378f90 feat: allow argument directives to be called even if the argument is null (#3233) (closes #3188) + +The existing implementation assumes that if an input argument is null, you don't want to call the directive. This is a very constraining assumption — directives may want to not just mutate an argument but to actually outright set it. + +This is a breaking change as argument directives now need to handle null input values. Added a new config switch: + +call_argument_directives_with_nulls: bool + +to control this new behavior. + +* Run go generate ./... + +
      + +- 3e76e7ee only close websocket once (#3231) + +
      256794aa chore(deps-dev): bump vite from 5.4.0 to 5.4.2 in /integration (#3229) + +Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.0 to 5.4.2. +- [Release notes](https://github.com/vitejs/vite/releases) +- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) +- [Commits](https://github.com/vitejs/vite/commits/v5.4.2/packages/vite) + +--- +updated-dependencies: +- dependency-name: vite + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      6acc182c Go 1.23 support (#3226) + +* Added support for go 1.23 + +* Added handling for *types.Alias + +* Updated golang ci lint to 1.60.2 + +* Fixed lint issues and ignore SA1019 on generated test files + +* Update coverage.yml + +* Update fmt-and-generate.yml + +* Update integration.yml + +* Update lint.yml + +* Update test.yml + +--------- + +
      + +
      f6a82204 chore(deps): bump golang.org/x/tools from 0.23.0 to 0.24.0 (#3219) + +* chore(deps): bump golang.org/x/tools from 0.23.0 to 0.24.0 + +Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.23.0 to 0.24.0. +- [Release notes](https://github.com/golang/tools/releases) +- [Commits](https://github.com/golang/tools/compare/v0.23.0...v0.24.0) + +--- +updated-dependencies: +- dependency-name: golang.org/x/tools + dependency-type: direct:production + update-type: version-update:semver-minor +... + + +* _examples fixup + + +--------- + +
      + +
      1849e124 chore(deps): bump golang.org/x/text from 0.16.0 to 0.17.0 (#3218) + +Bumps [golang.org/x/text](https://github.com/golang/text) from 0.16.0 to 0.17.0. +- [Release notes](https://github.com/golang/text/releases) +- [Commits](https://github.com/golang/text/compare/v0.16.0...v0.17.0) + +--- +updated-dependencies: +- dependency-name: golang.org/x/text + dependency-type: direct:production + update-type: version-update:semver-minor +... + +
      + +
      2f7772c9 [proposal] Add [@concurrent](https://github.com/concurrent) directive for types (#3203) + +* Issue 3202 + +* Issue 3202 + +* Issue 3202 + +* Make optional concurrent for fields of objects + +* Make optional concurrent for fields of objects + +
      + +
      3556475a Fix marshaling interfaces and union types (#3211) + +* Fixed marshaling interfaces and union + +* Fixed marshaling interfaces and union + +
      + +
      23abdc56 chore(deps): bump github.com/urfave/cli/v2 from 2.27.3 to 2.27.4 (#3217) + +Bumps [github.com/urfave/cli/v2](https://github.com/urfave/cli) from 2.27.3 to 2.27.4. +- [Release notes](https://github.com/urfave/cli/releases) +- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md) +- [Commits](https://github.com/urfave/cli/compare/v2.27.3...v2.27.4) + +--- +updated-dependencies: +- dependency-name: github.com/urfave/cli/v2 + dependency-type: direct:production + update-type: version-update:semver-patch +... + +
      + +- bbc354c6 Add local toolchain for matrix + +
      3fe8329d chore(deps-dev): bump [@apollo](https://github.com/apollo)/client in /integration (#3215) + +- [Release notes](https://github.com/apollographql/apollo-client/releases) +- [Changelog](https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md) +- [Commits](https://github.com/apollographql/apollo-client/compare/v3.11.2...v3.11.4) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      edca7992 chore(deps-dev): bump vite from 5.3.5 to 5.4.0 in /integration (#3216) + +Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.3.5 to 5.4.0. +- [Release notes](https://github.com/vitejs/vite/releases) +- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) + +--- +updated-dependencies: +- dependency-name: vite + dependency-type: direct:development + update-type: version-update:semver-minor +... + +
      + +
      f0b7ee3f chore(deps): bump actions/upload-artifact from 4.3.5 to 4.3.6 (#3220) + +Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.5 to 4.3.6. +- [Release notes](https://github.com/actions/upload-artifact/releases) +- [Commits](https://github.com/actions/upload-artifact/compare/v4.3.5...v4.3.6) + +--- +updated-dependencies: +- dependency-name: actions/upload-artifact + dependency-type: direct:production + update-type: version-update:semver-patch +... + +
      + +
      719b7af3 chore(deps): bump golang.org/x/text from 0.16.0 to 0.17.0 in /_examples (#3221) + +Bumps [golang.org/x/text](https://github.com/golang/text) from 0.16.0 to 0.17.0. +- [Release notes](https://github.com/golang/text/releases) +- [Commits](https://github.com/golang/text/compare/v0.16.0...v0.17.0) + +--- +updated-dependencies: +- dependency-name: golang.org/x/text + dependency-type: direct:production + update-type: version-update:semver-minor +... + +
      + +
      d14fd791 chore(deps): bump actions/upload-artifact from 4.3.4 to 4.3.5 (#3208) + +Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.4 to 4.3.5. +- [Release notes](https://github.com/actions/upload-artifact/releases) +- [Commits](https://github.com/actions/upload-artifact/compare/v4.3.4...v4.3.5) + +--- +updated-dependencies: +- dependency-name: actions/upload-artifact + dependency-type: direct:production + update-type: version-update:semver-patch +... + +
      + +
      564e2dc5 chore(deps): bump golangci/golangci-lint-action from 6.0.1 to 6.1.0 (#3207) + +Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 6.0.1 to 6.1.0. +- [Release notes](https://github.com/golangci/golangci-lint-action/releases) +- [Commits](https://github.com/golangci/golangci-lint-action/compare/v6.0.1...v6.1.0) + +--- +updated-dependencies: +- dependency-name: golangci/golangci-lint-action + dependency-type: direct:production + update-type: version-update:semver-minor +... + +
      + +
      d3d147e6 chore(deps): bump golang.org/x/tools from 0.22.0 to 0.23.0 (#3172) + +* chore(deps): bump golang.org/x/tools from 0.22.0 to 0.23.0 + +Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.22.0 to 0.23.0. +- [Release notes](https://github.com/golang/tools/releases) +- [Commits](https://github.com/golang/tools/compare/v0.22.0...v0.23.0) + +--- +updated-dependencies: +- dependency-name: golang.org/x/tools + dependency-type: direct:production + update-type: version-update:semver-minor +... + + + + +--------- + +
      + +
      2d7e00b5 chore(deps-dev): bump typescript from 5.5.3 to 5.5.4 in /integration (#3196) + +Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.5.3 to 5.5.4. +- [Release notes](https://github.com/Microsoft/TypeScript/releases) +- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml) +- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.5.3...v5.5.4) + +--- +updated-dependencies: +- dependency-name: typescript + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      5f86c55a chore(deps-dev): bump [@graphql](https://github.com/graphql)-codegen/client-preset in /integration (#3197) + +- [Release notes](https://github.com/dotansimha/graphql-code-generator/releases) +- [Changelog](https://github.com/dotansimha/graphql-code-generator/blob/master/packages/presets/client/CHANGELOG.md) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      552bb4b9 chore(deps-dev): bump vite from 5.3.4 to 5.3.5 in /integration (#3199) + +Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.3.4 to 5.3.5. +- [Release notes](https://github.com/vitejs/vite/releases) +- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) +- [Commits](https://github.com/vitejs/vite/commits/v5.3.5/packages/vite) + +--- +updated-dependencies: +- dependency-name: vite + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      45a29fe0 chore(deps): bump github.com/urfave/cli/v2 from 2.27.2 to 2.27.3 (#3200) + +Bumps [github.com/urfave/cli/v2](https://github.com/urfave/cli) from 2.27.2 to 2.27.3. +- [Release notes](https://github.com/urfave/cli/releases) +- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md) +- [Commits](https://github.com/urfave/cli/compare/v2.27.2...v2.27.3) + +--- +updated-dependencies: +- dependency-name: github.com/urfave/cli/v2 + dependency-type: direct:production + update-type: version-update:semver-patch +... + +
      + +
      3c2443e4 chore(deps): bump golang.org/x/sync from 0.7.0 to 0.8.0 in /_examples (#3206) + +Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.7.0 to 0.8.0. +- [Commits](https://github.com/golang/sync/compare/v0.7.0...v0.8.0) + +--- +updated-dependencies: +- dependency-name: golang.org/x/sync + dependency-type: direct:production + update-type: version-update:semver-minor +... + +
      + +
      52f65d0f chore(deps-dev): bump vitest from 2.0.4 to 2.0.5 in /integration (#3209) + +Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 2.0.4 to 2.0.5. +- [Release notes](https://github.com/vitest-dev/vitest/releases) +- [Commits](https://github.com/vitest-dev/vitest/commits/v2.0.5/packages/vitest) + +--- +updated-dependencies: +- dependency-name: vitest + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      1beac8b7 chore(deps-dev): bump [@apollo](https://github.com/apollo)/client in /integration (#3210) + +- [Release notes](https://github.com/apollographql/apollo-client/releases) +- [Changelog](https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md) +- [Commits](https://github.com/apollographql/apollo-client/compare/v3.10.8...v3.11.2) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-minor +... + +
      + +- 9b031e4d chore: fix typos in comments, tests and unexported vars (#3193) + +- 892c4842 refactor: decrease indentation in api.ReplacePlugin (#3194) + +
      d1682f7c chore(deps-dev): bump vite from 5.3.3 to 5.3.4 in /integration (#3190) + +Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.3.3 to 5.3.4. +- [Release notes](https://github.com/vitejs/vite/releases) +- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) +- [Commits](https://github.com/vitejs/vite/commits/v5.3.4/packages/vite) + +--- +updated-dependencies: +- dependency-name: vite + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      cfc9863a chore(deps-dev): bump vitest from 2.0.2 to 2.0.4 in /integration (#3189) + +Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 2.0.2 to 2.0.4. +- [Release notes](https://github.com/vitest-dev/vitest/releases) +- [Commits](https://github.com/vitest-dev/vitest/commits/v2.0.4/packages/vitest) + +--- +updated-dependencies: +- dependency-name: vitest + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      1cc0a17b Revert "feat: allow argument directives to be called even if the argument is …" (#3191) + +This reverts commit 0fb31a3ed2a63552eddcf7c2a6c40aa0d59bd4cc. + +
      + +
      0fb31a3e feat: allow argument directives to be called even if the argument is null (#3188) + +The existing implementation assumes that if an input argument is null, you don't want to call the directive. This is a very constraining assumption — directives may want to not just mutate an argument but to actually outright set it. + +This is a breaking change as argument directives now need to handle null input values. Added a new config switch: + +call_argument_directives_with_nulls: bool + +to control this new behavior. + +
      + +
      cd82be01 refactor: significantly clean up the federation.gotpl template (#3187) (closes #2991) + +* fix: fix Federation example + +Some configurations weren't working due to a missing resolver. + +* chore: Introduce mechanism for running all example Federation subgraphs + +This enables engineers to more easily run the debugger on the Federation example. Updated README to show how to use it. + +* refactor: significantly clean up the federation.gotpl template + +There were a number of inline structs and inline functions that made it extremely hard to reason about what the code is doing. Split these out into smaller functions with less closures and mutation. + +
      + +
      a63f94bb chore(deps-dev): bump vitest from 1.6.0 to 2.0.2 in /integration (#3185) + +Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 1.6.0 to 2.0.2. +- [Release notes](https://github.com/vitest-dev/vitest/releases) +- [Commits](https://github.com/vitest-dev/vitest/commits/v2.0.2/packages/vitest) + +--- +updated-dependencies: +- dependency-name: vitest + dependency-type: direct:development + update-type: version-update:semver-major +... + +
      + +
      de315d3d chore: Refactor federation.go to make it easier to read (#3183) (closes #2991) + +* chore: Refactor federation.go + +- Cut functions into smaller functions +- Remove mutation in several locations + + +* Refactor InjectSourcesLate + +Easier to reason about and read this way. + +* Re-run go generate ./... + +* regenerate + + +--------- + +
      + +
      4d8d93cd Make cache generic to avoid casting (#3179) + +* Make cache generic to avoid casting + + +* Update handler/handler.go + +--------- + +
      + +
      f2cf11e5 chore(deps-dev): bump [@graphql](https://github.com/graphql)-codegen/client-preset in /integration (#3174) + +- [Release notes](https://github.com/dotansimha/graphql-code-generator/releases) +- [Changelog](https://github.com/dotansimha/graphql-code-generator/blob/master/packages/presets/client/CHANGELOG.md) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      fc150db0 chore(deps-dev): bump typescript from 5.5.2 to 5.5.3 in /integration (#3175) + +Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.5.2 to 5.5.3. +- [Release notes](https://github.com/Microsoft/TypeScript/releases) +- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml) +- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.5.2...v5.5.3) + +--- +updated-dependencies: +- dependency-name: typescript + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      60c9f671 chore(deps): bump actions/upload-artifact from 4.3.3 to 4.3.4 (#3176) + +Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.3 to 4.3.4. +- [Release notes](https://github.com/actions/upload-artifact/releases) +- [Commits](https://github.com/actions/upload-artifact/compare/v4.3.3...v4.3.4) + +--- +updated-dependencies: +- dependency-name: actions/upload-artifact + dependency-type: direct:production + update-type: version-update:semver-patch +... + +
      + +
      59bdde19 chore(deps-dev): bump vite from 5.3.2 to 5.3.3 in /integration (#3173) + +Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.3.2 to 5.3.3. +- [Release notes](https://github.com/vitejs/vite/releases) +- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) +- [Commits](https://github.com/vitejs/vite/commits/v5.3.3/packages/vite) + +--- +updated-dependencies: +- dependency-name: vite + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      0ca3b19e chore(deps): bump github.com/rs/cors (#3171) + +Bumps the go_modules group with 1 update in the /_examples/websocket-initfunc/server directory: [github.com/rs/cors](https://github.com/rs/cors). + + +Updates `github.com/rs/cors` from 1.9.0 to 1.11.0 +- [Commits](https://github.com/rs/cors/compare/v1.9.0...v1.11.0) + +--- +updated-dependencies: +- dependency-name: github.com/rs/cors + dependency-type: direct:production + dependency-group: go_modules +... + +
      + +
      d0e68928 Nulls are now unmarshalled as zero values for primitive types (#3162) + +* Nulls are now unmarshalled as zero values for primitive types + +* Address uint and run gofumpt + + +--------- + +
      + +
      dce2e353 chore(deps): bump test-summary/action from 2.3 to 2.4 (#3163) + +Bumps [test-summary/action](https://github.com/test-summary/action) from 2.3 to 2.4. +- [Release notes](https://github.com/test-summary/action/releases) +- [Commits](https://github.com/test-summary/action/compare/v2.3...v2.4) + +--- +updated-dependencies: +- dependency-name: test-summary/action + dependency-type: direct:production + update-type: version-update:semver-minor +... + +
      + +
      2afa0c22 chore(deps-dev): bump [@apollo](https://github.com/apollo)/client in /integration (#3164) + +- [Release notes](https://github.com/apollographql/apollo-client/releases) +- [Changelog](https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md) +- [Commits](https://github.com/apollographql/apollo-client/compare/v3.10.6...v3.10.8) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      2aeb1518 chore(deps-dev): bump [@graphql](https://github.com/graphql)-codegen/client-preset in /integration (#3165) + +- [Release notes](https://github.com/dotansimha/graphql-code-generator/releases) +- [Changelog](https://github.com/dotansimha/graphql-code-generator/blob/master/packages/presets/client/CHANGELOG.md) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      28b2f494 chore(deps-dev): bump vite from 5.3.1 to 5.3.2 in /integration (#3166) + +Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.3.1 to 5.3.2. +- [Release notes](https://github.com/vitejs/vite/releases) +- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) +- [Commits](https://github.com/vitejs/vite/commits/v5.3.2/packages/vite) + +--- +updated-dependencies: +- dependency-name: vite + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      f82d604a chore(deps-dev): bump [@graphql](https://github.com/graphql)-codegen/schema-ast in /integration (#3167) + +- [Release notes](https://github.com/dotansimha/graphql-code-generator/releases) +- [Changelog](https://github.com/dotansimha/graphql-code-generator/blob/master/packages/plugins/other/schema-ast/CHANGELOG.md) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-minor +... + +
      + +
      dd37ea00 chore(deps-dev): bump typescript from 5.4.5 to 5.5.2 in /integration (#3157) + +Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.4.5 to 5.5.2. +- [Release notes](https://github.com/Microsoft/TypeScript/releases) +- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml) +- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.4.5...v5.5.2) + +--- +updated-dependencies: +- dependency-name: typescript + dependency-type: direct:development + update-type: version-update:semver-minor +... + +
      + +
      7b9c3223 chore(deps-dev): bump graphql from 16.8.2 to 16.9.0 in /integration (#3158) + +Bumps [graphql](https://github.com/graphql/graphql-js) from 16.8.2 to 16.9.0. +- [Release notes](https://github.com/graphql/graphql-js/releases) +- [Commits](https://github.com/graphql/graphql-js/compare/v16.8.2...v16.9.0) + +--- +updated-dependencies: +- dependency-name: graphql + dependency-type: direct:development + update-type: version-update:semver-minor +... + +
      + +
      b822c2c0 chore(deps): bump mikepenz/action-junit-report from 4.3.0 to 4.3.1 (#3159) + +Bumps [mikepenz/action-junit-report](https://github.com/mikepenz/action-junit-report) from 4.3.0 to 4.3.1. +- [Release notes](https://github.com/mikepenz/action-junit-report/releases) +- [Commits](https://github.com/mikepenz/action-junit-report/compare/v4.3.0...v4.3.1) + +--- +updated-dependencies: +- dependency-name: mikepenz/action-junit-report + dependency-type: direct:production + update-type: version-update:semver-patch +... + +
      + +
      c1525831 chore(deps-dev): bump [@apollo](https://github.com/apollo)/client in /integration (#3156) + +- [Release notes](https://github.com/apollographql/apollo-client/releases) +- [Changelog](https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md) +- [Commits](https://github.com/apollographql/apollo-client/compare/v3.10.5...v3.10.6) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +- feab5f51 fix bug: POST Insufficient rigorous judgment leads to invalid SSE (#3153) + +- 7c8bc50d Add failing test as example (#3151) + +
      d00ace38 Add prettier test results (#3148) + +* Add prettier test results + +
      + +
      641377d7 chore(deps-dev): bump ws in /integration in the npm_and_yarn group (#3147) + +Bumps the npm_and_yarn group in /integration with 1 update: [ws](https://github.com/websockets/ws). + + +Updates `ws` from 8.16.0 to 8.17.1 +- [Release notes](https://github.com/websockets/ws/releases) +- [Commits](https://github.com/websockets/ws/compare/8.16.0...8.17.1) + +--- +updated-dependencies: +- dependency-name: ws + dependency-type: indirect + dependency-group: npm_and_yarn +... + +
      + +- e724bde5 docs: missing 'repeatable' in [@goExtraField](https://github.com/goExtraField) directive (#3150) + +- 85459a32 Fix typo in config field names (#3149) + +- 1422ff25 feat: Change plugin signatures (#2011) + +
      04b13fdb chore(deps-dev): bump vite from 5.2.13 to 5.3.1 in /integration (#3144) + +Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.2.13 to 5.3.1. +- [Release notes](https://github.com/vitejs/vite/releases) +- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) +- [Commits](https://github.com/vitejs/vite/commits/v5.3.1/packages/vite) + +--- +updated-dependencies: +- dependency-name: vite + dependency-type: direct:development + update-type: version-update:semver-minor +... + +
      + +
      a1ccf971 chore(deps-dev): bump [@apollo](https://github.com/apollo)/client in /integration (#3143) + +- [Release notes](https://github.com/apollographql/apollo-client/releases) +- [Changelog](https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md) +- [Commits](https://github.com/apollographql/apollo-client/compare/v3.10.4...v3.10.5) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      8a59a2c4 chore(deps-dev): bump graphql from 16.8.1 to 16.8.2 in /integration (#3142) + +Bumps [graphql](https://github.com/graphql/graphql-js) from 16.8.1 to 16.8.2. +- [Release notes](https://github.com/graphql/graphql-js/releases) +- [Commits](https://github.com/graphql/graphql-js/compare/v16.8.1...v16.8.2) + +--- +updated-dependencies: +- dependency-name: graphql + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      80098c67 chore(deps-dev): bump [@graphql](https://github.com/graphql)-codegen/client-preset in /integration (#3141) + +- [Release notes](https://github.com/dotansimha/graphql-code-generator/releases) +- [Changelog](https://github.com/dotansimha/graphql-code-generator/blob/master/packages/presets/client/CHANGELOG.md) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-minor +... + +
      + +
      fc90169b chore(deps): bump google.golang.org/protobuf from 1.34.1 to 1.34.2 (#3140) + +Bumps google.golang.org/protobuf from 1.34.1 to 1.34.2. + +--- +updated-dependencies: +- dependency-name: google.golang.org/protobuf + dependency-type: direct:production + update-type: version-update:semver-patch +... + +
      + +- fb67b709 v0.17.49 postrelease bump + + + + + + +## [v0.17.49](https://github.com/99designs/gqlgen/compare/v0.17.48...v0.17.49) - 2024-06-13 +- d093c6e5 release v0.17.49 + +- a4f997f8 refactor: add missed file.Close() and use t.TempDir() (#3137) + +
      f813598b #3118 Add token limit option to fix CVE-2023-49559 (#3136) + +* Use ParseQueryWithLmit and add parserTokenLimit to executor + +* add parser token limit test + +* remove failing test + +* move default token limit to const + +--------- + +
      + +
      ee1e18c7 chore(deps-dev): bump braces in /integration in the npm_and_yarn group (#3134) + +Bumps the npm_and_yarn group in /integration with 1 update: [braces](https://github.com/micromatch/braces). + + +Updates `braces` from 3.0.2 to 3.0.3 +- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) +- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) + +--- +updated-dependencies: +- dependency-name: braces + dependency-type: indirect + dependency-group: npm_and_yarn +... + +
      + +
      d6226db6 chore(deps): bump github.com/vektah/gqlparser/v2 from 2.5.12 to 2.5.14 in the go_modules group (#3133) + +* chore(deps): bump github.com/vektah/gqlparser/v2 in the go_modules group + +Bumps the go_modules group with 1 update: [github.com/vektah/gqlparser/v2](https://github.com/vektah/gqlparser). + + +Updates `github.com/vektah/gqlparser/v2` from 2.5.12 to 2.5.14 +- [Release notes](https://github.com/vektah/gqlparser/releases) +- [Commits](https://github.com/vektah/gqlparser/compare/v2.5.12...v2.5.14) + +--- +updated-dependencies: +- dependency-name: github.com/vektah/gqlparser/v2 + dependency-type: direct:production + dependency-group: go_modules +... + + +* Update to v2.5.16 + + +--------- + +
      + +
      6daceaf3 Linter update + add revive rules (#3127) + +* Linter update + add revive rules + + +* More revive lints + + +--------- + +
      + +
      e6860c35 chore(deps): bump golang.org/x/tools from 0.21.0 to 0.22.0 (#3125) + +Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.21.0 to 0.22.0. +- [Release notes](https://github.com/golang/tools/releases) +- [Commits](https://github.com/golang/tools/compare/v0.21.0...v0.22.0) + +--- +updated-dependencies: +- dependency-name: golang.org/x/tools + dependency-type: direct:production + update-type: version-update:semver-minor +... + +
      + +
      3bad9617 chore(deps): bump golang.org/x/text from 0.15.0 to 0.16.0 (#3124) + +* chore(deps): bump golang.org/x/text from 0.15.0 to 0.16.0 + +Bumps [golang.org/x/text](https://github.com/golang/text) from 0.15.0 to 0.16.0. +- [Release notes](https://github.com/golang/text/releases) +- [Commits](https://github.com/golang/text/compare/v0.15.0...v0.16.0) + +--- +updated-dependencies: +- dependency-name: golang.org/x/text + dependency-type: direct:production + update-type: version-update:semver-minor +... + + +* Update examples go mod + + +--------- + +
      + +
      4492b3c0 chore(deps-dev): bump vite from 5.2.12 to 5.2.13 in /integration (#3126) + +Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.2.12 to 5.2.13. +- [Release notes](https://github.com/vitejs/vite/releases) +- [Changelog](https://github.com/vitejs/vite/blob/v5.2.13/packages/vite/CHANGELOG.md) +- [Commits](https://github.com/vitejs/vite/commits/v5.2.13/packages/vite) + +--- +updated-dependencies: +- dependency-name: vite + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      8ec8d795 chore(deps): bump golang.org/x/text from 0.15.0 to 0.16.0 in /_examples (#3123) + +Bumps [golang.org/x/text](https://github.com/golang/text) from 0.15.0 to 0.16.0. +- [Release notes](https://github.com/golang/text/releases) +- [Commits](https://github.com/golang/text/compare/v0.15.0...v0.16.0) + +--- +updated-dependencies: +- dependency-name: golang.org/x/text + dependency-type: direct:production + update-type: version-update:semver-minor +... + +
      + +- d9ba3405 v0.17.48 postrelease bump + + + + + + +## [v0.17.48](https://github.com/99designs/gqlgen/compare/v0.17.47...v0.17.48) - 2024-06-06 +- 621350a1 release v0.17.48 + +
      fbf73ee1 chore(deps-dev): bump vite from 5.2.11 to 5.2.12 in /integration (#3117) + +Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.2.11 to 5.2.12. +- [Release notes](https://github.com/vitejs/vite/releases) +- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) +- [Commits](https://github.com/vitejs/vite/commits/v5.2.12/packages/vite) + +--- +updated-dependencies: +- dependency-name: vite + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      e07134ab add option to omit panic handlers during development (#3114) + +see docs for motivation + +
      + +- 1a7c6090 refactor: fix gocritic lint issues (#3113) + +- 4114515f refactor: use errors.New instead of fmt.Errorf (#3112) + +- 93f6366d Omit gqlgen version in config files used for tests (#3111) + +
      dae915d2 Correct dataloader example (#3110) + +Dataloader requires the value and error slice to be of equal length, in order to correctly return the values. + +Link: https://github.com/vikstrous/dataloadgen/blob/7de6ebe3d882737607ce2ba646e8d6ec652b32e3/dataloadgen_test.go#L19-L20 + +
      + +
      bd9219dd Go template function to split string into array of strings. (#3108) + +* added new template function to split string + +* StrSplit func to upper + +--------- + +
      + +- 6c83b9ea Remove duplicated return_pointers_in_unmarshalinput explanation (#3109) + +- d2a6bd5f refactor: fix testifylint.go-require lint issues (#3107) + +
      b18d0287 testifylint v1.3.0 fixes (#3103) + +* Resolve Merge conflict + + +* Autofixes + + +* Lots more fixes and formatting + + +* Add one more + + +* Apply suggestions from code review + + +--------- + +
      + +- bbb0c959 chore: fix tests, pin golangci-lint version (#3105) + +- 57e88b27 Forgot the examples portion (#3101) + +- ff77f8b2 Some minor test lint (#3102) + +
      90f2271e refactor: use t.Log instead of fmt.Print (#3099) + +* refactor: use t.Log instead of fmt.Printf + +* Add back failure context as to what errors happened and where + + +--------- + +
      + +- d7447c69 refactor: rename local variables to match Go codestyle (#3100) + +- 834d832c refactor: avoid panic in tests (#3098) + +- 71845858 Ignore gorilla/websocket 1.5.1 in dependabot (#3097) + +- 4ecfec90 Fix go install gqlgen binary (#3095) + +- 866075cd refactor: simplify with strconv.FormatBool (#3094) + +- ab19907d refactor: UnmarshalID implementation (#3093) + +- a9965fbd refactor: use 'any' instead of 'interface{}' for consistency (#3090) + +
      d5c9f896 Embed extra fields config (#3088) + +--------- + +
      + +
      0b9e6f9c chore(deps-dev): bump [@apollo](https://github.com/apollo)/client in /integration (#3085) + +- [Release notes](https://github.com/apollographql/apollo-client/releases) +- [Changelog](https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md) +- [Commits](https://github.com/apollographql/apollo-client/compare/v3.10.3...v3.10.4) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      33aad657 chore(deps-dev): bump [@graphql](https://github.com/graphql)-codegen/client-preset in /integration (#3084) + +- [Release notes](https://github.com/dotansimha/graphql-code-generator/releases) +- [Changelog](https://github.com/dotansimha/graphql-code-generator/blob/master/packages/presets/client/CHANGELOG.md) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +- 58d6978e v0.17.47 postrelease bump + + + + + + +## [v0.17.47](https://github.com/99designs/gqlgen/compare/v0.17.46...v0.17.47) - 2024-05-18 +- a9f2b500 release v0.17.47 + +- 611cbcec Update gqlparser (#3080) + +
      3a5827d4 Fix #2856: resolver receive previous implementation on render (#2886) + +* pass previous impl to resolver + +* pass previous only and not default + +
      + +- e0125301 bugfix for [@goField](https://github.com/goField) + [@goExtraField](https://github.com/goExtraField) combination (#3078) + +
      e61a7200 Federation: Update docs to use IntrospectAndCompose (#3077) + +`serviceList` now gets a deprecation warning to use IntrospectAndCompose +instead. We update our docs to avoid referring to deprecated services + +
      + +
      de31828a Ability to inline extraFields configuration. New [@goExtraField](https://github.com/goExtraField) directive. (#3076) + +--------- + +
      + +- 8b4df636 Go mod tidy (#3075) + +
      ae9787cb chore(deps): bump github.com/sosodev/duration from 1.3.0 to 1.3.1 (#3070) + +* chore(deps): bump github.com/sosodev/duration from 1.3.0 to 1.3.1 + +Bumps [github.com/sosodev/duration](https://github.com/sosodev/duration) from 1.3.0 to 1.3.1. +- [Release notes](https://github.com/sosodev/duration/releases) +- [Commits](https://github.com/sosodev/duration/compare/v1.3.0...v1.3.1) + +--- +updated-dependencies: +- dependency-name: github.com/sosodev/duration + dependency-type: direct:production + update-type: version-update:semver-patch +... + + +* go mod tidy examples + + +* Pin gorilla to skip 1.5.1 + + +--------- + +
      + +
      32014fdb chore(deps): bump golang.org/x/tools from 0.20.0 to 0.21.0 (#3072) + +Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.20.0 to 0.21.0. +- [Release notes](https://github.com/golang/tools/releases) +- [Commits](https://github.com/golang/tools/compare/v0.20.0...v0.21.0) + +--- +updated-dependencies: +- dependency-name: golang.org/x/tools + dependency-type: direct:production + update-type: version-update:semver-minor +... + +
      + +
      1b5ed7c0 chore(deps-dev): bump urql from 4.0.7 to 4.1.0 in /integration (#3074) + +Bumps [urql](https://github.com/urql-graphql/urql/tree/HEAD/packages/react-urql) from 4.0.7 to 4.1.0. +- [Release notes](https://github.com/urql-graphql/urql/releases) +- [Changelog](https://github.com/urql-graphql/urql/blob/main/packages/react-urql/CHANGELOG.md) + +--- +updated-dependencies: +- dependency-name: urql + dependency-type: direct:development + update-type: version-update:semver-minor +... + +
      + +
      77ea79a8 chore(deps-dev): bump [@apollo](https://github.com/apollo)/client in /integration (#3073) + +- [Release notes](https://github.com/apollographql/apollo-client/releases) +- [Changelog](https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md) +- [Commits](https://github.com/apollographql/apollo-client/compare/v3.10.2...v3.10.3) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      358c7a2b chore(deps): bump google.golang.org/protobuf from 1.34.0 to 1.34.1 (#3071) + +Bumps google.golang.org/protobuf from 1.34.0 to 1.34.1. + +--- +updated-dependencies: +- dependency-name: google.golang.org/protobuf + dependency-type: direct:production + update-type: version-update:semver-patch +... + +
      + +
      5c951f4e chore(deps): bump golangci/golangci-lint-action from 5.3.0 to 6.0.1 (#3069) + +Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 5.3.0 to 6.0.1. +- [Release notes](https://github.com/golangci/golangci-lint-action/releases) +- [Commits](https://github.com/golangci/golangci-lint-action/compare/v5.3.0...v6.0.1) + +--- +updated-dependencies: +- dependency-name: golangci/golangci-lint-action + dependency-type: direct:production + update-type: version-update:semver-major +... + +
      + +- 42cae907 chore: remove deprecated errcheck.ignore lint option (#3062) + +- 1a59d58b Fix typo in error message (#3065) + +- 39d3d8d0 refactor: simplify test asserts (#3061) + +- 7421bdfb refactor: compile regex only once (#3063) + +- a4bf3a7e chore: simplify generating examples in release script (#3064) + +- 45f6eb56 v0.17.46 postrelease bump + + + + + + +## [v0.17.46](https://github.com/99designs/gqlgen/compare/v0.17.45...v0.17.46) - 2024-05-07 +- 90af8bf5 release v0.17.46 + +- bf49e56a fix: failed to build _examples/websocket-initfunc/server/server.go (#3055) (#3058) + +
      1ee0fa80 chore(deps-dev): bump vite from 5.2.10 to 5.2.11 in /integration (#3047) + +Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.2.10 to 5.2.11. +- [Release notes](https://github.com/vitejs/vite/releases) +- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) +- [Commits](https://github.com/vitejs/vite/commits/v5.2.11/packages/vite) + +--- +updated-dependencies: +- dependency-name: vite + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      ddd9a6ba chore(deps): bump golang.org/x/text from 0.14.0 to 0.15.0 (#3052) + +Bumps [golang.org/x/text](https://github.com/golang/text) from 0.14.0 to 0.15.0. +- [Release notes](https://github.com/golang/text/releases) +- [Commits](https://github.com/golang/text/compare/v0.14.0...v0.15.0) + +--- +updated-dependencies: +- dependency-name: golang.org/x/text + dependency-type: direct:production + update-type: version-update:semver-minor +... + +
      + +
      36b66607 chore(deps): bump github.com/PuerkitoBio/goquery from 1.9.1 to 1.9.2 (#3051) + +* chore(deps): bump github.com/PuerkitoBio/goquery from 1.9.1 to 1.9.2 + +Bumps [github.com/PuerkitoBio/goquery](https://github.com/PuerkitoBio/goquery) from 1.9.1 to 1.9.2. +- [Release notes](https://github.com/PuerkitoBio/goquery/releases) +- [Commits](https://github.com/PuerkitoBio/goquery/compare/v1.9.1...v1.9.2) + +--- +updated-dependencies: +- dependency-name: github.com/PuerkitoBio/goquery + dependency-type: direct:production + update-type: version-update:semver-patch +... + + +* go mod tidy + + +--------- + +
      + +
      ad91bf6c chore(deps-dev): bump vitest from 1.5.2 to 1.6.0 in /integration (#3048) + +Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 1.5.2 to 1.6.0. +- [Release notes](https://github.com/vitest-dev/vitest/releases) +- [Commits](https://github.com/vitest-dev/vitest/commits/v1.6.0/packages/vitest) + +--- +updated-dependencies: +- dependency-name: vitest + dependency-type: direct:development + update-type: version-update:semver-minor +... + +
      + +
      a5cb576c chore(deps-dev): bump [@apollo](https://github.com/apollo)/client in /integration (#3049) + +- [Release notes](https://github.com/apollographql/apollo-client/releases) +- [Changelog](https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md) +- [Commits](https://github.com/apollographql/apollo-client/compare/v3.10.1...v3.10.2) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      6b423e51 chore(deps): bump google.golang.org/protobuf from 1.33.0 to 1.34.0 (#3050) + +Bumps google.golang.org/protobuf from 1.33.0 to 1.34.0. + +--- +updated-dependencies: +- dependency-name: google.golang.org/protobuf + dependency-type: direct:production + update-type: version-update:semver-minor +... + +
      + +
      c34e246b chore(deps): bump golang.org/x/text from 0.14.0 to 0.15.0 in /_examples (#3053) + +Bumps [golang.org/x/text](https://github.com/golang/text) from 0.14.0 to 0.15.0. +- [Release notes](https://github.com/golang/text/releases) +- [Commits](https://github.com/golang/text/compare/v0.14.0...v0.15.0) + +--- +updated-dependencies: +- dependency-name: golang.org/x/text + dependency-type: direct:production + update-type: version-update:semver-minor +... + +
      + +
      a3991df0 chore(deps): bump golangci/golangci-lint-action from 5.0.0 to 5.3.0 (#3054) + +Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 5.0.0 to 5.3.0. +- [Release notes](https://github.com/golangci/golangci-lint-action/releases) +- [Commits](https://github.com/golangci/golangci-lint-action/compare/v5.0.0...v5.3.0) + +--- +updated-dependencies: +- dependency-name: golangci/golangci-lint-action + dependency-type: direct:production + update-type: version-update:semver-minor +... + +
      + +- 769632a1 chore: simplify go generate in examples (#3033) + +- f24ae887 enum values binding v2 (#3014) + +
      b3a10547 Add initial cache tests for MapCache and NoCache (#3040) + +* Add initial cache tests for MapCache and NoCache + +* Add edge case testing to MapCache and NoCache + +* Reformat, regenerate + + +--------- + +
      + +- 16854647 chore: lint _examples directory (#3042) + +- 2bb32fe7 chore: remove deprecated build tag (#3041) + +- 4b559b33 Fix codegen config tests: add file closing (#3037) + +- 293991e9 docs: fix links to the docs latest version (#3038) + +- 79dc5e03 refactor: change test asserts to be more idiomatic (#3036) + +- a1989525 chore: remove unnecessary empty lines (#3035) + +- 6998f19f chore: `run.skip-dirs` is deprecated in golangci-lint v1.57 (#3034) + +
      835c2d11 Improve federation resolver selection (#3029) + +* Improve federation resolver selection + +Just checking for existence of keys in the representations isn't enough. If the values are null, we should skip the resolver. + +* Run go generate ./... + +* Add test cases + +* Fix linter + +
      + +
      9e8e7edd refactor: simplify tests for `api.Generate` (#3031) + +* refactor: simplify tests for Generate + +* Add deleted files to git ignore + + +--------- + +
      + +- 28405ac1 Fix test asserts: reverse expected and actual params (#3027) + +
      75326bc7 Bump github.com/sosodev/duration from 1.2.0 to 1.3.0 (#3024) + +* Bump github.com/sosodev/duration from 1.2.0 to 1.3.0 + +Bumps [github.com/sosodev/duration](https://github.com/sosodev/duration) from 1.2.0 to 1.3.0. +- [Release notes](https://github.com/sosodev/duration/releases) +- [Commits](https://github.com/sosodev/duration/compare/v1.2.0...v1.3.0) + +--- +updated-dependencies: +- dependency-name: github.com/sosodev/duration + dependency-type: direct:production + update-type: version-update:semver-minor +... + + +* go mod tidy + + +--------- + +
      + +
      bf4406a1 Bump vitest from 1.5.0 to 1.5.2 in /integration (#3021) + +Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 1.5.0 to 1.5.2. +- [Release notes](https://github.com/vitest-dev/vitest/releases) +- [Commits](https://github.com/vitest-dev/vitest/commits/v1.5.2/packages/vitest) + +--- +updated-dependencies: +- dependency-name: vitest + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      1a8ebe9b Bump [@apollo](https://github.com/apollo)/client from 3.9.11 to 3.10.1 in /integration (#3022) + +- [Release notes](https://github.com/apollographql/apollo-client/releases) +- [Changelog](https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md) +- [Commits](https://github.com/apollographql/apollo-client/compare/v3.9.11...v3.10.1) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-minor +... + +
      + +
      bacaab8e Bump github.com/urfave/cli/v2 from 2.27.1 to 2.27.2 (#3023) + +Bumps [github.com/urfave/cli/v2](https://github.com/urfave/cli) from 2.27.1 to 2.27.2. +- [Release notes](https://github.com/urfave/cli/releases) +- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md) +- [Commits](https://github.com/urfave/cli/compare/v2.27.1...v2.27.2) + +--- +updated-dependencies: +- dependency-name: github.com/urfave/cli/v2 + dependency-type: direct:production + update-type: version-update:semver-patch +... + +
      + +
      3f515543 Bump github.com/rs/cors from 1.10.1 to 1.11.0 in /_examples (#3025) + +Bumps [github.com/rs/cors](https://github.com/rs/cors) from 1.10.1 to 1.11.0. +- [Commits](https://github.com/rs/cors/compare/v1.10.1...v1.11.0) + +--- +updated-dependencies: +- dependency-name: github.com/rs/cors + dependency-type: direct:production + update-type: version-update:semver-minor +... + +
      + +
      ced2189d Bump golangci/golangci-lint-action from 4.0.0 to 5.0.0 (#3026) + +Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 4.0.0 to 5.0.0. +- [Release notes](https://github.com/golangci/golangci-lint-action/releases) +- [Commits](https://github.com/golangci/golangci-lint-action/compare/v4.0.0...v5.0.0) + +--- +updated-dependencies: +- dependency-name: golangci/golangci-lint-action + dependency-type: direct:production + update-type: version-update:semver-major +... + +
      + +- ada00f78 chore: remove unused lint.txt (#3017) + +
      8bd35429 chore: fix some typos in comments (#3020) + +* chore: fix some typos in comments + + +* Apply suggestions from code review + +--------- + +
      + +
      e1ef86e7 Bump vite from 5.2.8 to 5.2.10 in /integration (#3015) + +Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.2.8 to 5.2.10. +- [Release notes](https://github.com/vitejs/vite/releases) +- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) +- [Commits](https://github.com/vitejs/vite/commits/v5.2.10/packages/vite) + +--- +updated-dependencies: +- dependency-name: vite + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      ecc3f647 Bump [@apollo](https://github.com/apollo)/client from 3.9.10 to 3.9.11 in /integration (#3011) + +- [Release notes](https://github.com/apollographql/apollo-client/releases) +- [Changelog](https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md) +- [Commits](https://github.com/apollographql/apollo-client/compare/v3.9.10...v3.9.11) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      c92b511c Bump typescript from 5.4.4 to 5.4.5 in /integration (#3010) + +Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.4.4 to 5.4.5. +- [Release notes](https://github.com/Microsoft/TypeScript/releases) +- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml) +- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.4.4...v5.4.5) + +--- +updated-dependencies: +- dependency-name: typescript + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      cc2d95a2 Bump vitest from 1.4.0 to 1.5.0 in /integration (#3012) + +Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 1.4.0 to 1.5.0. +- [Release notes](https://github.com/vitest-dev/vitest/releases) +- [Commits](https://github.com/vitest-dev/vitest/commits/v1.5.0/packages/vitest) + +--- +updated-dependencies: +- dependency-name: vitest + dependency-type: direct:development + update-type: version-update:semver-minor +... + +
      + +
      c17a4b6f fix: codegen will _ the fieldset parameter if its not needed (#3006) + +* fix: codegen will _ the fieldset parameter if its not needed + +* update generated examples + +
      + +- 0b0f6592 chore: update Automatic Persisted Queries Link (#3005) + +
      79aa0ceb Mark ctx as unused when no arguments for FieldContextFunc (#2999) + +* Mark ctx as unused when no arguments for FieldContextFunc + +* Regenerate + + +--------- + +
      + +
      f3b34683 Bump urql from 4.0.6 to 4.0.7 in /integration (#2995) (closes #2998) + +* Bump urql from 4.0.6 to 4.0.7 in /integration + +Bumps [urql](https://github.com/urql-graphql/urql/tree/HEAD/packages/react-urql) from 4.0.6 to 4.0.7. +- [Release notes](https://github.com/urql-graphql/urql/releases) +- [Changelog](https://github.com/urql-graphql/urql/blob/main/packages/react-urql/CHANGELOG.md) + +--- +updated-dependencies: +- dependency-name: urql + dependency-type: direct:development + update-type: version-update:semver-patch +... + + + +client. + +--------- + +
      + +
      8ab31646 Bump graphql-ws from 5.15.0 to 5.16.0 in /integration (#2986) + +Bumps [graphql-ws](https://github.com/enisdenjo/graphql-ws) from 5.15.0 to 5.16.0. +- [Release notes](https://github.com/enisdenjo/graphql-ws/releases) +- [Changelog](https://github.com/enisdenjo/graphql-ws/blob/master/CHANGELOG.md) +- [Commits](https://github.com/enisdenjo/graphql-ws/compare/v5.15.0...v5.16.0) + +--- +updated-dependencies: +- dependency-name: graphql-ws + dependency-type: direct:development + update-type: version-update:semver-minor +... + +
      + +
      45fafedc Bump golang.org/x/tools from 0.19.0 to 0.20.0 (#2996) + +* Bump golang.org/x/tools from 0.19.0 to 0.20.0 + +Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.19.0 to 0.20.0. +- [Release notes](https://github.com/golang/tools/releases) +- [Commits](https://github.com/golang/tools/compare/v0.19.0...v0.20.0) + +--- +updated-dependencies: +- dependency-name: golang.org/x/tools + dependency-type: direct:production + update-type: version-update:semver-minor +... + + +* Update examples to match root go.mod + + +--------- + +
      + +
      4c45be21 Bump vite from 5.2.7 to 5.2.8 in /integration (#2992) + +Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.2.7 to 5.2.8. +- [Release notes](https://github.com/vitejs/vite/releases) +- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) +- [Commits](https://github.com/vitejs/vite/commits/v5.2.8/packages/vite) + +--- +updated-dependencies: +- dependency-name: vite + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +- 6e5a7758 Update `tools.go` url (#2987) + +
      6771a804 Bump [@apollo](https://github.com/apollo)/client from 3.9.9 to 3.9.10 in /integration (#2994) + +- [Release notes](https://github.com/apollographql/apollo-client/releases) +- [Changelog](https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md) +- [Commits](https://github.com/apollographql/apollo-client/compare/v3.9.9...v3.9.10) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      2ce5fcd1 Bump typescript from 5.4.3 to 5.4.4 in /integration (#2993) + +Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.4.3 to 5.4.4. +- [Release notes](https://github.com/Microsoft/TypeScript/releases) +- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml) +- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.4.3...v5.4.4) + +--- +updated-dependencies: +- dependency-name: typescript + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +- 99d7d887 fix: stop loading package dependencies (#2988) + +- d0a1aec2 enum values binding (#2982) + +
      6352b800 Bump vite from 5.2.6 to 5.2.7 in /integration (#2984) + +Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.2.6 to 5.2.7. +- [Release notes](https://github.com/vitejs/vite/releases) +- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) +- [Commits](https://github.com/vitejs/vite/commits/v5.2.7/packages/vite) + +--- +updated-dependencies: +- dependency-name: vite + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      2286b0e8 Bump graphql-sse from 2.5.2 to 2.5.3 in /integration (#2985) + +Bumps [graphql-sse](https://github.com/enisdenjo/graphql-sse) from 2.5.2 to 2.5.3. +- [Release notes](https://github.com/enisdenjo/graphql-sse/releases) +- [Changelog](https://github.com/enisdenjo/graphql-sse/blob/master/CHANGELOG.md) +- [Commits](https://github.com/enisdenjo/graphql-sse/compare/v2.5.2...v2.5.3) + +--- +updated-dependencies: +- dependency-name: graphql-sse + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      8ab2c27a Bump [@graphql](https://github.com/graphql)-codegen/client-preset from 4.2.4 to 4.2.5 in /integration (#2983) + +- [Release notes](https://github.com/dotansimha/graphql-code-generator/releases) +- [Changelog](https://github.com/dotansimha/graphql-code-generator/blob/master/packages/presets/client/CHANGELOG.md) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +- 780bf27a Add UintID type binding (#2980) + +
      d192a591 Bump [@apollo](https://github.com/apollo)/client from 3.9.7 to 3.9.9 in /integration (#2977) + +- [Release notes](https://github.com/apollographql/apollo-client/releases) +- [Changelog](https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md) +- [Commits](https://github.com/apollographql/apollo-client/compare/v3.9.7...v3.9.9) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      62289425 Bump vite from 5.1.6 to 5.2.6 in /integration (#2978) + +Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.1.6 to 5.2.6. +- [Release notes](https://github.com/vitejs/vite/releases) +- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) +- [Commits](https://github.com/vitejs/vite/commits/v5.2.6/packages/vite) + +--- +updated-dependencies: +- dependency-name: vite + dependency-type: direct:development + update-type: version-update:semver-minor +... + +
      + +
      105ec44b Bump typescript from 5.4.2 to 5.4.3 in /integration (#2979) + +Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.4.2 to 5.4.3. +- [Release notes](https://github.com/Microsoft/TypeScript/releases) +- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml) +- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.4.2...v5.4.3) + +--- +updated-dependencies: +- dependency-name: typescript + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +- 0afd63a5 chore: remove repetitive words (#2976) + +
      ee526b05 Bump vite from 5.1.5 to 5.1.6 in /integration (#2971) + +Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.1.5 to 5.1.6. +- [Release notes](https://github.com/vitejs/vite/releases) +- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) +- [Commits](https://github.com/vitejs/vite/commits/v5.1.6/packages/vite) + +--- +updated-dependencies: +- dependency-name: vite + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +
      00bf8ef3 Bump vitest from 1.3.1 to 1.4.0 in /integration (#2972) + +Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 1.3.1 to 1.4.0. +- [Release notes](https://github.com/vitest-dev/vitest/releases) +- [Commits](https://github.com/vitest-dev/vitest/commits/v1.4.0/packages/vitest) + +--- +updated-dependencies: +- dependency-name: vitest + dependency-type: direct:development + update-type: version-update:semver-minor +... + +
      + +
      bdbdddf5 Bump [@apollo](https://github.com/apollo)/client from 3.9.6 to 3.9.7 in /integration (#2970) + +- [Release notes](https://github.com/apollographql/apollo-client/releases) +- [Changelog](https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md) +- [Commits](https://github.com/apollographql/apollo-client/compare/v3.9.6...v3.9.7) + +--- +updated-dependencies: + dependency-type: direct:development + update-type: version-update:semver-patch +... + +
      + +- fa221f64 Update Changelog + +- f897668b v0.17.45 postrelease bump + + + + + ## [v0.17.45](https://github.com/99designs/gqlgen/compare/v0.17.44...v0.17.45) - 2024-03-11 - b6d1a8b9 release v0.17.45 @@ -3573,7 +5648,7 @@ when generating next the context was captured there. Which means later when the returned function from DispatchOperation is called. The responseContext which accumulates the errors is the -tempResponseContext which we no longer have access to read the errors +tempResponseContext which we no longer have access to to read the errors out of it. Instead add a context to next() so that it can be passed through and diff --git a/vendor/github.com/99designs/gqlgen/api/generate.go b/vendor/github.com/99designs/gqlgen/api/generate.go index 9e7b4188..7b83be50 100644 --- a/vendor/github.com/99designs/gqlgen/api/generate.go +++ b/vendor/github.com/99designs/gqlgen/api/generate.go @@ -45,7 +45,11 @@ func Generate(cfg *config.Config, option ...Option) error { } } } - plugins = append([]plugin.Plugin{federation.New(cfg.Federation.Version)}, plugins...) + federationPlugin, err := federation.New(cfg.Federation.Version, cfg) + if err != nil { + return fmt.Errorf("failed to construct the Federation plugin: %w", err) + } + plugins = append([]plugin.Plugin{federationPlugin}, plugins...) } for _, o := range option { @@ -58,6 +62,13 @@ func Generate(cfg *config.Config, option ...Option) error { cfg.Sources = append(cfg.Sources, s) } } + if inj, ok := p.(plugin.EarlySourcesInjector); ok { + s, err := inj.InjectSourcesEarly() + if err != nil { + return fmt.Errorf("%s: %w", p.Name(), err) + } + cfg.Sources = append(cfg.Sources, s...) + } } if err := cfg.LoadSchema(); err != nil { @@ -70,6 +81,13 @@ func Generate(cfg *config.Config, option ...Option) error { cfg.Sources = append(cfg.Sources, s) } } + if inj, ok := p.(plugin.LateSourcesInjector); ok { + s, err := inj.InjectSourcesLate(cfg.Schema) + if err != nil { + return fmt.Errorf("%s: %w", p.Name(), err) + } + cfg.Sources = append(cfg.Sources, s...) + } } // LoadSchema again now we have everything diff --git a/vendor/github.com/99designs/gqlgen/api/option.go b/vendor/github.com/99designs/gqlgen/api/option.go index d376193d..344aa819 100644 --- a/vendor/github.com/99designs/gqlgen/api/option.go +++ b/vendor/github.com/99designs/gqlgen/api/option.go @@ -29,19 +29,20 @@ func PrependPlugin(p plugin.Plugin) Option { // ReplacePlugin replaces any existing plugin with a matching plugin name func ReplacePlugin(p plugin.Plugin) Option { return func(cfg *config.Config, plugins *[]plugin.Plugin) { - if plugins != nil { - found := false - ps := *plugins - for i, o := range ps { - if p.Name() == o.Name() { - ps[i] = p - found = true - } - } - if !found { - ps = append(ps, p) + if plugins == nil { + return + } + found := false + ps := *plugins + for i, o := range ps { + if p.Name() == o.Name() { + ps[i] = p + found = true } - *plugins = ps } + if !found { + ps = append(ps, p) + } + *plugins = ps } } diff --git a/vendor/github.com/99designs/gqlgen/codegen/args.go b/vendor/github.com/99designs/gqlgen/codegen/args.go index 983a3a02..736680b3 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/args.go +++ b/vendor/github.com/99designs/gqlgen/codegen/args.go @@ -18,19 +18,20 @@ type ArgSet struct { type FieldArgument struct { *ast.ArgumentDefinition - TypeReference *config.TypeReference - VarName string // The name of the var in go - Object *Object // A link back to the parent object - Default any // The default value - Directives []*Directive - Value any // value set in Data + TypeReference *config.TypeReference + VarName string // The name of the var in go + Object *Object // A link back to the parent object + Default any // The default value + Directives []*Directive + Value any // value set in Data + CallArgumentDirectivesWithNull bool } -// ImplDirectives get not Builtin and location ARGUMENT_DEFINITION directive +// ImplDirectives get not SkipRuntime and location ARGUMENT_DEFINITION directive func (f *FieldArgument) ImplDirectives() []*Directive { d := make([]*Directive, 0) for i := range f.Directives { - if !f.Directives[i].Builtin && f.Directives[i].IsLocation(ast.LocationArgumentDefinition) { + if !f.Directives[i].SkipRuntime && f.Directives[i].IsLocation(ast.LocationArgumentDefinition) { d = append(d, f.Directives[i]) } } @@ -57,11 +58,12 @@ func (b *builder) buildArg(obj *Object, arg *ast.ArgumentDefinition) (*FieldArgu return nil, err } newArg := FieldArgument{ - ArgumentDefinition: arg, - TypeReference: tr, - Object: obj, - VarName: templates.ToGoPrivate(arg.Name), - Directives: argDirs, + ArgumentDefinition: arg, + TypeReference: tr, + Object: obj, + VarName: templates.ToGoPrivate(arg.Name), + Directives: argDirs, + CallArgumentDirectivesWithNull: b.Config.CallArgumentDirectivesWithNull, } if arg.DefaultValue != nil { diff --git a/vendor/github.com/99designs/gqlgen/codegen/args.gotpl b/vendor/github.com/99designs/gqlgen/codegen/args.gotpl index 7b541ae1..2f3afdf0 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/args.gotpl +++ b/vendor/github.com/99designs/gqlgen/codegen/args.gotpl @@ -2,35 +2,67 @@ func (ec *executionContext) {{ $name }}(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} + {{- range $i, $arg := . }} - var arg{{$i}} {{ $arg.TypeReference.GO | ref}} - if tmp, ok := rawArgs[{{$arg.Name|quote}}]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField({{$arg.Name|quote}})) + arg{{$i}}, err := ec.{{ $name }}{{$arg.Name | go}}(ctx, rawArgs) + if err != nil { + return nil, err + } + args[{{$arg.Name|quote}}] = arg{{$i}} + {{- end }} + return args, nil +} + + {{- range $i, $arg := . }} + func (ec *executionContext) {{ $name }}{{$arg.Name | go}}( + ctx context.Context, + rawArgs map[string]interface{}, + ) ({{ $arg.TypeReference.GO | ref}}, error) { + {{- if not .CallArgumentDirectivesWithNull}} + // We won't call the directive if the argument is null. + // Set call_argument_directives_with_null to true to call directives + // even if the argument is null. + _, ok := rawArgs[{{$arg.Name|quote}}] + if !ok { + var zeroVal {{ $arg.TypeReference.GO | ref}} + return zeroVal, nil + } + {{end}} + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField({{$arg.Name|quote}})) {{- if $arg.ImplDirectives }} - directive0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp) } + directive0 := func(ctx context.Context) (interface{}, error) { + tmp, ok := rawArgs[{{$arg.Name|quote}}] + if !ok { + var zeroVal {{ $arg.TypeReference.GO | ref}} + return zeroVal, nil + } + return ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp) + } {{ template "implDirectives" $arg }} - tmp, err = directive{{$arg.ImplDirectives|len}}(ctx) + tmp, err := directive{{$arg.ImplDirectives|len}}(ctx) if err != nil { - return nil, graphql.ErrorOnPath(ctx, err) + var zeroVal {{ $arg.TypeReference.GO | ref}} + return zeroVal, graphql.ErrorOnPath(ctx, err) } if data, ok := tmp.({{ $arg.TypeReference.GO | ref }}) ; ok { - arg{{$i}} = data + return data, nil {{- if $arg.TypeReference.IsNilable }} } else if tmp == nil { - arg{{$i}} = nil + var zeroVal {{ $arg.TypeReference.GO | ref}} + return zeroVal, nil {{- end }} } else { - return nil, graphql.ErrorOnPath(ctx, fmt.Errorf(`unexpected type %T from directive, should be {{ $arg.TypeReference.GO }}`, tmp)) + var zeroVal {{ $arg.TypeReference.GO | ref}} + return zeroVal, graphql.ErrorOnPath(ctx, fmt.Errorf(`unexpected type %T from directive, should be {{ $arg.TypeReference.GO }}`, tmp)) } {{- else }} - arg{{$i}}, err = ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp) - if err != nil { - return nil, err + if tmp, ok := rawArgs[{{$arg.Name|quote}}]; ok { + return ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp) } + + var zeroVal {{ $arg.TypeReference.GO | ref}} + return zeroVal, nil {{- end }} } - args[{{$arg.Name|quote}}] = arg{{$i}} - {{- end }} - return args, nil -} + {{end}} {{ end }} diff --git a/vendor/github.com/99designs/gqlgen/codegen/config/binder.go b/vendor/github.com/99designs/gqlgen/codegen/config/binder.go index 91b6e500..fc7b2edc 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/config/binder.go +++ b/vendor/github.com/99designs/gqlgen/codegen/config/binder.go @@ -36,7 +36,7 @@ func (c *Config) NewBinder() *Binder { } func (b *Binder) TypePosition(typ types.Type) token.Position { - named, isNamed := typ.(*types.Named) + named, isNamed := code.Unalias(typ).(*types.Named) if !isNamed { return token.Position{ Filename: "unknown", @@ -77,10 +77,11 @@ func (b *Binder) FindType(pkgName, typeName string) (types.Type, error) { return nil, err } - if fun, isFunc := obj.(*types.Func); isFunc { - return fun.Type().(*types.Signature).Params().At(0).Type(), nil + t := code.Unalias(obj.Type()) + if _, isFunc := obj.(*types.Func); isFunc { + return code.Unalias(t.(*types.Signature).Params().At(0).Type()), nil } - return obj.Type(), nil + return t, nil } func (b *Binder) InstantiateType(orig types.Type, targs []types.Type) (types.Type, error) { @@ -120,7 +121,7 @@ func (b *Binder) DefaultUserObject(name string) (types.Type, error) { return nil, err } - return obj.Type(), nil + return code.Unalias(obj.Type()), nil } func (b *Binder) FindObject(pkgName, typeName string) (types.Object, error) { @@ -193,19 +194,19 @@ func (b *Binder) PointerTo(ref *TypeReference) *TypeReference { // TypeReference is used by args and field types. The Definition can refer to both input and output types. type TypeReference struct { - Definition *ast.Definition - GQL *ast.Type - GO types.Type // Type of the field being bound. Could be a pointer or a value type of Target. - Target types.Type // The actual type that we know how to bind to. May require pointer juggling when traversing to fields. - CastType types.Type // Before calling marshalling functions cast from/to this base type - Marshaler *types.Func // When using external marshalling functions this will point to the Marshal function - Unmarshaler *types.Func // When using external marshalling functions this will point to the Unmarshal function - IsMarshaler bool // Does the type implement graphql.Marshaler and graphql.Unmarshaler - IsOmittable bool // Is the type wrapped with Omittable - IsContext bool // Is the Marshaler/Unmarshaller the context version; applies to either the method or interface variety. - PointersInUmarshalInput bool // Inverse values and pointers in return. - IsRoot bool // Is the type a root level definition such as Query, Mutation or Subscription - EnumValues []EnumValueReference + Definition *ast.Definition + GQL *ast.Type + GO types.Type // Type of the field being bound. Could be a pointer or a value type of Target. + Target types.Type // The actual type that we know how to bind to. May require pointer juggling when traversing to fields. + CastType types.Type // Before calling marshalling functions cast from/to this base type + Marshaler *types.Func // When using external marshalling functions this will point to the Marshal function + Unmarshaler *types.Func // When using external marshalling functions this will point to the Unmarshal function + IsMarshaler bool // Does the type implement graphql.Marshaler and graphql.Unmarshaler + IsOmittable bool // Is the type wrapped with Omittable + IsContext bool // Is the Marshaler/Unmarshaller the context version; applies to either the method or interface variety. + PointersInUnmarshalInput bool // Inverse values and pointers in return. + IsRoot bool // Is the type a root level definition such as Query, Mutation or Subscription + EnumValues []EnumValueReference } func (ref *TypeReference) Elem() *TypeReference { @@ -264,13 +265,13 @@ func (ref *TypeReference) IsPtrToIntf() bool { } func (ref *TypeReference) IsNamed() bool { - _, isSlice := ref.GO.(*types.Named) - return isSlice + _, ok := ref.GO.(*types.Named) + return ok } func (ref *TypeReference) IsStruct() bool { - _, isStruct := ref.GO.Underlying().(*types.Struct) - return isStruct + _, ok := ref.GO.Underlying().(*types.Struct) + return ok } func (ref *TypeReference) IsScalar() bool { @@ -362,6 +363,9 @@ func unwrapOmittable(t types.Type) (types.Type, bool) { } func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret *TypeReference, err error) { + if bindTarget != nil { + bindTarget = code.Unalias(bindTarget) + } if innerType, ok := unwrapOmittable(bindTarget); ok { if schemaType.NonNull { return nil, fmt.Errorf("%s is wrapped with Omittable but non-null", schemaType.Name()) @@ -433,28 +437,28 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret if err != nil { return nil, err } - + t := code.Unalias(obj.Type()) if values := b.enumValues(def); len(values) > 0 { err = b.enumReference(ref, obj, values) if err != nil { return nil, err } } else if fun, isFunc := obj.(*types.Func); isFunc { - ref.GO = fun.Type().(*types.Signature).Params().At(0).Type() - ref.IsContext = fun.Type().(*types.Signature).Results().At(0).Type().String() == "github.com/99designs/gqlgen/graphql.ContextMarshaler" + ref.GO = code.Unalias(t.(*types.Signature).Params().At(0).Type()) + ref.IsContext = code.Unalias(t.(*types.Signature).Results().At(0).Type()).String() == "github.com/99designs/gqlgen/graphql.ContextMarshaler" ref.Marshaler = fun ref.Unmarshaler = types.NewFunc(0, fun.Pkg(), "Unmarshal"+typeName, nil) - } else if hasMethod(obj.Type(), "MarshalGQLContext") && hasMethod(obj.Type(), "UnmarshalGQLContext") { - ref.GO = obj.Type() + } else if hasMethod(t, "MarshalGQLContext") && hasMethod(t, "UnmarshalGQLContext") { + ref.GO = t ref.IsContext = true ref.IsMarshaler = true - } else if hasMethod(obj.Type(), "MarshalGQL") && hasMethod(obj.Type(), "UnmarshalGQL") { - ref.GO = obj.Type() + } else if hasMethod(t, "MarshalGQL") && hasMethod(t, "UnmarshalGQL") { + ref.GO = t ref.IsMarshaler = true - } else if underlying := basicUnderlying(obj.Type()); def.IsLeafType() && underlying != nil && underlying.Kind() == types.String { + } else if underlying := basicUnderlying(t); def.IsLeafType() && underlying != nil && underlying.Kind() == types.String { // TODO delete before v1. Backwards compatibility case for named types wrapping strings (see #595) - ref.GO = obj.Type() + ref.GO = t ref.CastType = underlying underlyingRef, err := b.TypeReference(&ast.Type{NamedType: "String"}, nil) @@ -465,7 +469,7 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret ref.Marshaler = underlyingRef.Marshaler ref.Unmarshaler = underlyingRef.Unmarshaler } else { - ref.GO = obj.Type() + ref.GO = t } ref.Target = ref.GO @@ -478,7 +482,7 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret ref.GO = bindTarget } - ref.PointersInUmarshalInput = b.cfg.ReturnPointersInUmarshalInput + ref.PointersInUnmarshalInput = b.cfg.ReturnPointersInUnmarshalInput return ref, nil } @@ -516,6 +520,10 @@ func (b *Binder) CopyModifiersFromAst(t *ast.Type, base types.Type) types.Type { } func IsNilable(t types.Type) bool { + // Note that we use types.Unalias rather than code.Unalias here + // because we want to always check the underlying type. + // code.Unalias only unwraps aliases in Go 1.23 + t = types.Unalias(t) if namedType, isNamed := t.(*types.Named); isNamed { return IsNilable(namedType.Underlying()) } @@ -587,10 +595,11 @@ func (b *Binder) enumReference(ref *TypeReference, obj types.Object, values map[ return fmt.Errorf("not all enum values are binded for %v", ref.Definition.Name) } - if fn, ok := obj.Type().(*types.Signature); ok { - ref.GO = fn.Params().At(0).Type() + t := code.Unalias(obj.Type()) + if fn, ok := t.(*types.Signature); ok { + ref.GO = code.Unalias(fn.Params().At(0).Type()) } else { - ref.GO = obj.Type() + ref.GO = t } str, err := b.TypeReference(&ast.Type{NamedType: "String"}, nil) @@ -618,9 +627,10 @@ func (b *Binder) enumReference(ref *TypeReference, obj types.Object, values map[ return err } - if !types.AssignableTo(valueObj.Type(), ref.GO) { + valueTyp := code.Unalias(valueObj.Type()) + if !types.AssignableTo(valueTyp, ref.GO) { return fmt.Errorf("wrong type: %v, for enum value: %v, expected type: %v, of enum: %v", - valueObj.Type(), value.Name, ref.GO, ref.Definition.Name) + valueTyp, value.Name, ref.GO, ref.Definition.Name) } switch valueObj.(type) { diff --git a/vendor/github.com/99designs/gqlgen/codegen/config/config.go b/vendor/github.com/99designs/gqlgen/codegen/config/config.go index 3228756c..761e3524 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/config/config.go +++ b/vendor/github.com/99designs/gqlgen/codegen/config/config.go @@ -42,16 +42,22 @@ type Config struct { OmitRootModels bool `yaml:"omit_root_models,omitempty"` OmitResolverFields bool `yaml:"omit_resolver_fields,omitempty"` OmitPanicHandler bool `yaml:"omit_panic_handler,omitempty"` - StructFieldsAlwaysPointers bool `yaml:"struct_fields_always_pointers,omitempty"` - ReturnPointersInUmarshalInput bool `yaml:"return_pointers_in_unmarshalinput,omitempty"` - ResolversAlwaysReturnPointers bool `yaml:"resolvers_always_return_pointers,omitempty"` - NullableInputOmittable bool `yaml:"nullable_input_omittable,omitempty"` - EnableModelJsonOmitemptyTag *bool `yaml:"enable_model_json_omitempty_tag,omitempty"` - SkipValidation bool `yaml:"skip_validation,omitempty"` - SkipModTidy bool `yaml:"skip_mod_tidy,omitempty"` - Sources []*ast.Source `yaml:"-"` - Packages *code.Packages `yaml:"-"` - Schema *ast.Schema `yaml:"-"` + // If this is set to true, argument directives that + // decorate a field with a null value will still be called. + // + // This enables argumment directives to not just mutate + // argument values but to set them even if they're null. + CallArgumentDirectivesWithNull bool `yaml:"call_argument_directives_with_null,omitempty"` + StructFieldsAlwaysPointers bool `yaml:"struct_fields_always_pointers,omitempty"` + ReturnPointersInUnmarshalInput bool `yaml:"return_pointers_in_unmarshalinput,omitempty"` + ResolversAlwaysReturnPointers bool `yaml:"resolvers_always_return_pointers,omitempty"` + NullableInputOmittable bool `yaml:"nullable_input_omittable,omitempty"` + EnableModelJsonOmitemptyTag *bool `yaml:"enable_model_json_omitempty_tag,omitempty"` + SkipValidation bool `yaml:"skip_validation,omitempty"` + SkipModTidy bool `yaml:"skip_mod_tidy,omitempty"` + Sources []*ast.Source `yaml:"-"` + Packages *code.Packages `yaml:"-"` + Schema *ast.Schema `yaml:"-"` // Deprecated: use Federation instead. Will be removed next release Federated bool `yaml:"federated,omitempty"` @@ -62,15 +68,15 @@ var cfgFilenames = []string{".gqlgen.yml", "gqlgen.yml", "gqlgen.yaml"} // DefaultConfig creates a copy of the default config func DefaultConfig() *Config { return &Config{ - SchemaFilename: StringList{"schema.graphql"}, - Model: PackageConfig{Filename: "models_gen.go"}, - Exec: ExecConfig{Filename: "generated.go"}, - Directives: map[string]DirectiveConfig{}, - Models: TypeMap{}, - StructFieldsAlwaysPointers: true, - ReturnPointersInUmarshalInput: false, - ResolversAlwaysReturnPointers: true, - NullableInputOmittable: false, + SchemaFilename: StringList{"schema.graphql"}, + Model: PackageConfig{Filename: "models_gen.go"}, + Exec: ExecConfig{Filename: "generated.go"}, + Directives: map[string]DirectiveConfig{}, + Models: TypeMap{}, + StructFieldsAlwaysPointers: true, + ReturnPointersInUnmarshalInput: false, + ResolversAlwaysReturnPointers: true, + NullableInputOmittable: false, } } @@ -320,24 +326,33 @@ func (c *Config) injectTypesFromSchema() error { } } - if schemaType.Kind == ast.Object || schemaType.Kind == ast.InputObject { + if schemaType.Kind == ast.Object || + schemaType.Kind == ast.InputObject || + schemaType.Kind == ast.Interface { for _, field := range schemaType.Fields { if fd := field.Directives.ForName("goField"); fd != nil { forceResolver := c.Models[schemaType.Name].Fields[field.Name].Resolver - fieldName := c.Models[schemaType.Name].Fields[field.Name].FieldName - if ra := fd.Arguments.ForName("forceResolver"); ra != nil { if fr, err := ra.Value.Value(nil); err == nil { forceResolver = fr.(bool) } } + fieldName := c.Models[schemaType.Name].Fields[field.Name].FieldName if na := fd.Arguments.ForName("name"); na != nil { if fr, err := na.Value.Value(nil); err == nil { fieldName = fr.(string) } } + omittable := c.Models[schemaType.Name].Fields[field.Name].Omittable + if arg := fd.Arguments.ForName("omittable"); arg != nil { + if k, err := arg.Value.Value(nil); err == nil { + val := k.(bool) + omittable = &val + } + } + if c.Models[schemaType.Name].Fields == nil { c.Models[schemaType.Name] = TypeMapEntry{ Model: c.Models[schemaType.Name].Model, @@ -349,6 +364,7 @@ func (c *Config) injectTypesFromSchema() error { c.Models[schemaType.Name].Fields[field.Name] = TypeMapField{ FieldName: fieldName, Resolver: forceResolver, + Omittable: omittable, } } } @@ -449,6 +465,7 @@ type TypeMapEntry struct { type TypeMapField struct { Resolver bool `yaml:"resolver"` FieldName string `yaml:"fieldName"` + Omittable *bool `yaml:"omittable"` GeneratedMethod string `yaml:"-"` } @@ -659,6 +676,16 @@ func (tm TypeMap) ForceGenerate(name string, forceGenerate bool) { type DirectiveConfig struct { SkipRuntime bool `yaml:"skip_runtime"` + + // If the directive implementation is statically defined, don't provide a hook for it + // in the generated server. This is useful for directives that are implemented + // by plugins or the runtime itself. + // + // The function implemmentation should be provided here as a string. + // + // The function should have the following signature: + // func(ctx context.Context, obj any, next graphql.Resolver[, directive arguments if any]) (res any, err error) + Implementation *string } func inStrSlice(haystack []string, needle string) bool { diff --git a/vendor/github.com/99designs/gqlgen/codegen/config/initialisms.go b/vendor/github.com/99designs/gqlgen/codegen/config/initialisms.go index 25e7331f..432c56e0 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/config/initialisms.go +++ b/vendor/github.com/99designs/gqlgen/codegen/config/initialisms.go @@ -14,7 +14,7 @@ type GoInitialismsConfig struct { Initialisms []string `yaml:"initialisms"` } -// setInitialisms adjustes GetInitialisms based on its settings. +// setInitialisms adjusts GetInitialisms based on its settings. func (i GoInitialismsConfig) setInitialisms() { toUse := i.determineGoInitialisms() templates.GetInitialisms = func() map[string]bool { @@ -22,7 +22,7 @@ func (i GoInitialismsConfig) setInitialisms() { } } -// determineGoInitialisms returns the Go initialims to be used, based on its settings. +// determineGoInitialisms returns the Go initialisms to be used, based on its settings. func (i GoInitialismsConfig) determineGoInitialisms() (initialismsToUse map[string]bool) { if i.ReplaceDefaults { initialismsToUse = make(map[string]bool, len(i.Initialisms)) diff --git a/vendor/github.com/99designs/gqlgen/codegen/data.go b/vendor/github.com/99designs/gqlgen/codegen/data.go index 7110de2f..c5c3fcc6 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/data.go +++ b/vendor/github.com/99designs/gqlgen/codegen/data.go @@ -64,6 +64,30 @@ type builder struct { Directives map[string]*Directive } +// Get only the directives which should have a user provided definition on server instantiation +func (d *Data) UserDirectives() DirectiveList { + res := DirectiveList{} + directives := d.Directives() + for k, directive := range directives { + if directive.Implementation == nil { + res[k] = directive + } + } + return res +} + +// Get only the directives which should have a statically provided definition +func (d *Data) BuiltInDirectives() DirectiveList { + res := DirectiveList{} + directives := d.Directives() + for k, directive := range directives { + if directive.Implementation != nil { + res[k] = directive + } + } + return res +} + // Get only the directives which are defined in the config's sources. func (d *Data) Directives() DirectiveList { res := DirectiveList{} @@ -97,7 +121,7 @@ func BuildData(cfg *config.Config, plugins ...any) (*Data, error) { dataDirectives := make(map[string]*Directive) for name, d := range b.Directives { - if !d.Builtin { + if !d.SkipRuntime { dataDirectives[name] = d } } diff --git a/vendor/github.com/99designs/gqlgen/codegen/directive.go b/vendor/github.com/99designs/gqlgen/codegen/directive.go index 30a79c35..077bd9f7 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/directive.go +++ b/vendor/github.com/99designs/gqlgen/codegen/directive.go @@ -7,6 +7,7 @@ import ( "github.com/vektah/gqlparser/v2/ast" + "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/codegen/templates" ) @@ -19,9 +20,10 @@ func (dl DirectiveList) LocationDirectives(location string) DirectiveList { type Directive struct { *ast.DirectiveDefinition - Name string - Args []*FieldArgument - Builtin bool + Name string + Args []*FieldArgument + + config.DirectiveConfig } // IsLocation check location directive @@ -82,7 +84,7 @@ func (b *builder) buildDirectives() (map[string]*Directive, error) { DirectiveDefinition: dir, Name: name, Args: args, - Builtin: b.Config.Directives[name].SkipRuntime, + DirectiveConfig: b.Config.Directives[name], } } @@ -122,7 +124,7 @@ func (b *builder) getDirectives(list ast.DirectiveList) ([]*Directive, error) { Name: d.Name, Args: args, DirectiveDefinition: list[i].Definition, - Builtin: b.Config.Directives[d.Name].SkipRuntime, + DirectiveConfig: b.Config.Directives[d.Name], } } @@ -162,8 +164,12 @@ func (d *Directive) ResolveArgs(obj string, next int) string { return strings.Join(args, ", ") } +func (d *Directive) CallName() string { + return ucFirst(d.Name) +} + func (d *Directive) Declaration() string { - res := ucFirst(d.Name) + " func(ctx context.Context, obj interface{}, next graphql.Resolver" + res := d.CallName() + " func(ctx context.Context, obj interface{}, next graphql.Resolver" for _, arg := range d.Args { res += fmt.Sprintf(", %s %s", templates.ToGoPrivate(arg.Name), templates.CurrentImports.LookupType(arg.TypeReference.GO)) @@ -172,3 +178,23 @@ func (d *Directive) Declaration() string { res += ") (res interface{}, err error)" return res } + +func (d *Directive) IsBuiltIn() bool { + return d.Implementation != nil +} + +func (d *Directive) CallPath() string { + if d.IsBuiltIn() { + return "builtInDirective" + d.CallName() + } + + return "ec.directives." + d.CallName() +} + +func (d *Directive) FunctionImpl() string { + if d.Implementation == nil { + return "" + } + + return d.CallPath() + " = " + *d.Implementation +} diff --git a/vendor/github.com/99designs/gqlgen/codegen/directives.gotpl b/vendor/github.com/99designs/gqlgen/codegen/directives.gotpl index 23bcf0f8..c3fa8abe 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/directives.gotpl +++ b/vendor/github.com/99designs/gqlgen/codegen/directives.gotpl @@ -1,23 +1,29 @@ {{ define "implDirectives" }}{{ $in := .DirectiveObjName }} + {{ $zeroVal := .TypeReference.GO | ref}} {{- range $i, $directive := .ImplDirectives -}} directive{{add $i 1}} := func(ctx context.Context) (interface{}, error) { {{- range $arg := $directive.Args }} {{- if notNil "Value" $arg }} {{ $arg.VarName }}, err := ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, {{ $arg.Value | dump }}) if err != nil{ - return nil, err + var zeroVal {{$zeroVal}} + return zeroVal, err } {{- else if notNil "Default" $arg }} {{ $arg.VarName }}, err := ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, {{ $arg.Default | dump }}) if err != nil{ - return nil, err + var zeroVal {{$zeroVal}} + return zeroVal, err } {{- end }} {{- end }} - if ec.directives.{{$directive.Name|ucFirst}} == nil { - return nil, errors.New("directive {{$directive.Name}} is not implemented") - } - return ec.directives.{{$directive.Name|ucFirst}}({{$directive.ResolveArgs $in $i }}) + {{- if not $directive.IsBuiltIn}} + if {{$directive.CallPath}} == nil { + var zeroVal {{$zeroVal}} + return zeroVal, errors.New("directive {{$directive.Name}} is not implemented") + } + {{- end}} + return {{$directive.CallPath}}({{$directive.ResolveArgs $in $i }}) } {{ end -}} {{ end }} @@ -37,10 +43,7 @@ {{- end }} n := next next = func(ctx context.Context) (interface{}, error) { - if ec.directives.{{$directive.Name|ucFirst}} == nil { - return nil, errors.New("directive {{$directive.Name}} is not implemented") - } - return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}}) + {{- template "callDirective" $directive -}} } {{- end }} } @@ -57,6 +60,15 @@ return graphql.Null {{end}} +{{define "callDirective"}} + {{- if not .IsBuiltIn}} + if {{.CallPath}} == nil { + return nil, errors.New("directive {{.Name}} is not implemented") + } + {{- end}} + return {{.CallPath}}({{.CallArgs}}) +{{end}} + {{ if .Directives.LocationDirectives "QUERY" }} func (ec *executionContext) _queryMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) graphql.Marshaler { {{ template "queryDirectives" .Directives.LocationDirectives "QUERY" }} @@ -87,10 +99,7 @@ func (ec *executionContext) _subscriptionMiddleware(ctx context.Context, obj *as {{- end }} n := next next = func(ctx context.Context) (interface{}, error) { - if ec.directives.{{$directive.Name|ucFirst}} == nil { - return nil, errors.New("directive {{$directive.Name}} is not implemented") - } - return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}}) + {{- template "callDirective" $directive -}} } {{- end }} } @@ -130,10 +139,7 @@ func (ec *executionContext) _subscriptionMiddleware(ctx context.Context, obj *as {{- end }} n := next next = func(ctx context.Context) (interface{}, error) { - if ec.directives.{{$directive.Name|ucFirst}} == nil { - return nil, errors.New("directive {{$directive.Name}} is not implemented") - } - return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}}) + {{- template "callDirective" $directive -}} } {{- end }} } diff --git a/vendor/github.com/99designs/gqlgen/codegen/field.go b/vendor/github.com/99designs/gqlgen/codegen/field.go index 509f48cd..7f4a5ad1 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/field.go +++ b/vendor/github.com/99designs/gqlgen/codegen/field.go @@ -16,6 +16,7 @@ import ( "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/codegen/templates" + "github.com/99designs/gqlgen/internal/code" ) type Field struct { @@ -144,7 +145,7 @@ func (b *builder) bindField(obj *Object, f *Field) (errret error) { f.GoFieldName = b.Config.Models[obj.Name].Fields[f.Name].FieldName } - target, err := b.findBindTarget(obj.Type.(*types.Named), f.GoFieldName) + target, err := b.findBindTarget(obj.Type, f.GoFieldName) if err != nil { return err } @@ -229,7 +230,7 @@ func (b *builder) bindField(obj *Object, f *Field) (errret error) { } // findBindTarget attempts to match the name to a field or method on a Type -// with the following priorites: +// with the following priorities: // 1. Any Fields with a struct tag (see config.StructTag). Errors if more than one match is found // 2. Any method or field with a matching name. Errors if more than one match is found // 3. Same logic again for embedded fields @@ -380,7 +381,7 @@ func (b *builder) findBindStructEmbedsTarget(strukt *types.Struct, name string) continue } - fieldType := field.Type() + fieldType := code.Unalias(field.Type()) if ptr, ok := fieldType.(*types.Pointer); ok { fieldType = ptr.Elem() } @@ -442,7 +443,7 @@ func (f *Field) ImplDirectives() []*Directive { loc = ast.LocationInputFieldDefinition } for i := range f.Directives { - if !f.Directives[i].Builtin && + if !f.Directives[i].SkipRuntime && (f.Directives[i].IsLocation(loc, ast.LocationObject) || f.Directives[i].IsLocation(loc, ast.LocationInputObject)) { d = append(d, f.Directives[i]) } diff --git a/vendor/github.com/99designs/gqlgen/codegen/generated!.gotpl b/vendor/github.com/99designs/gqlgen/codegen/generated!.gotpl index b343e626..1e5bfbfc 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/generated!.gotpl +++ b/vendor/github.com/99designs/gqlgen/codegen/generated!.gotpl @@ -1,3 +1,4 @@ +{{/* Context object: codegen.Data */}} {{ reserveImport "context" }} {{ reserveImport "fmt" }} {{ reserveImport "io" }} @@ -46,7 +47,7 @@ } type DirectiveRoot struct { - {{ range $directive := .Directives }} + {{ range $directive := .UserDirectives }} {{- $directive.Declaration }} {{ end }} } @@ -93,6 +94,12 @@ {{- end }} {{- end }} +{{ range $directive := .BuiltInDirectives }} + var ( + {{- $directive.FunctionImpl }} + ) +{{ end }} + {{ if eq .Config.Exec.Layout "single-file" }} type executableSchema struct { schema *ast.Schema diff --git a/vendor/github.com/99designs/gqlgen/codegen/input.gotpl b/vendor/github.com/99designs/gqlgen/codegen/input.gotpl index 9240b56c..1b91d8db 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/input.gotpl +++ b/vendor/github.com/99designs/gqlgen/codegen/input.gotpl @@ -1,10 +1,10 @@ {{- range $input := .Inputs }} {{- if not .HasUnmarshal }} {{- $it := "it" }} - {{- if .PointersInUmarshalInput }} + {{- if .PointersInUnmarshalInput }} {{- $it = "&it" }} {{- end }} - func (ec *executionContext) unmarshalInput{{ .Name }}(ctx context.Context, obj interface{}) ({{ if .PointersInUmarshalInput }}*{{ end }}{{.Type | ref}}, error) { + func (ec *executionContext) unmarshalInput{{ .Name }}(ctx context.Context, obj interface{}) ({{ if .PointersInUnmarshalInput }}*{{ end }}{{.Type | ref}}, error) { {{- if $input.IsMap }} it := make(map[string]interface{}, len(obj.(map[string]interface{}))) {{- else }} diff --git a/vendor/github.com/99designs/gqlgen/codegen/object.go b/vendor/github.com/99designs/gqlgen/codegen/object.go index eee43849..869d1b36 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/object.go +++ b/vendor/github.com/99designs/gqlgen/codegen/object.go @@ -26,15 +26,15 @@ const ( type Object struct { *ast.Definition - Type types.Type - ResolverInterface types.Type - Root bool - Fields []*Field - Implements []*ast.Definition - DisableConcurrency bool - Stream bool - Directives []*Directive - PointersInUmarshalInput bool + Type types.Type + ResolverInterface types.Type + Root bool + Fields []*Field + Implements []*ast.Definition + DisableConcurrency bool + Stream bool + Directives []*Directive + PointersInUnmarshalInput bool } func (b *builder) buildObject(typ *ast.Definition) (*Object, error) { @@ -44,12 +44,12 @@ func (b *builder) buildObject(typ *ast.Definition) (*Object, error) { } caser := cases.Title(language.English, cases.NoLower) obj := &Object{ - Definition: typ, - Root: b.Config.IsRoot(typ), - DisableConcurrency: typ == b.Schema.Mutation, - Stream: typ == b.Schema.Subscription, - Directives: dirs, - PointersInUmarshalInput: b.Config.ReturnPointersInUmarshalInput, + Definition: typ, + Root: b.Config.IsRoot(typ), + DisableConcurrency: typ == b.Schema.Mutation, + Stream: typ == b.Schema.Subscription, + Directives: dirs, + PointersInUnmarshalInput: b.Config.ReturnPointersInUnmarshalInput, ResolverInterface: types.NewNamed( types.NewTypeName(0, b.Config.Exec.Pkg(), caser.String(typ.Name)+"Resolver", nil), nil, diff --git a/vendor/github.com/99designs/gqlgen/codegen/root_.gotpl b/vendor/github.com/99designs/gqlgen/codegen/root_.gotpl index 0b90ad53..d392ef53 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/root_.gotpl +++ b/vendor/github.com/99designs/gqlgen/codegen/root_.gotpl @@ -1,3 +1,4 @@ +{{/* Context object: codegen.Data */}} {{ reserveImport "context" }} {{ reserveImport "fmt" }} {{ reserveImport "io" }} @@ -45,7 +46,7 @@ type ResolverRoot interface { } type DirectiveRoot struct { -{{ range $directive := .Directives }} +{{ range $directive := .UserDirectives }} {{- $directive.Declaration }} {{ end }} } @@ -67,6 +68,12 @@ type ComplexityRoot struct { {{- end }} } +{{ range $directive := .BuiltInDirectives }} + var ( + {{- $directive.FunctionImpl }} + ) +{{ end }} + type executableSchema struct { schema *ast.Schema resolvers ResolverRoot diff --git a/vendor/github.com/99designs/gqlgen/codegen/templates/templates.go b/vendor/github.com/99designs/gqlgen/codegen/templates/templates.go index 4de30761..9b6e4cfb 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/templates/templates.go +++ b/vendor/github.com/99designs/gqlgen/codegen/templates/templates.go @@ -495,18 +495,18 @@ func wordWalker(str string, f func(*wordInfo)) { if initialisms[upperWord] { // If the uppercase word (string(runes[w:i]) is "ID" or "IP" // AND - // the word is the first two characters of the str + // the word is the first two characters of the current word // AND // that is not the end of the word // AND - // the length of the string is greater than 3 + // the length of the remaining string is greater than 3 // AND // the third rune is an uppercase one // THEN // do NOT count this as an initialism. switch upperWord { case "ID", "IP": - if word == str[:2] && !eow && len(str) > 3 && unicode.IsUpper(runes[3]) { + if remainingRunes := runes[w:]; word == string(remainingRunes[:2]) && !eow && len(remainingRunes) > 3 && unicode.IsUpper(remainingRunes[3]) { continue } } @@ -694,7 +694,7 @@ var pkgReplacer = strings.NewReplacer( func TypeIdentifier(t types.Type) string { res := "" for { - switch it := t.(type) { + switch it := code.Unalias(t).(type) { case *types.Pointer: t.Underlying() res += "ᚖ" @@ -771,6 +771,8 @@ var CommonInitialisms = map[string]bool{ "XMPP": true, "XSRF": true, "XSS": true, + "AWS": true, + "GCP": true, } // GetInitialisms returns the initialisms to capitalize in Go names. If unchanged, default initialisms will be returned diff --git a/vendor/github.com/99designs/gqlgen/codegen/type.gotpl b/vendor/github.com/99designs/gqlgen/codegen/type.gotpl index ebebdf14..1898d444 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/type.gotpl +++ b/vendor/github.com/99designs/gqlgen/codegen/type.gotpl @@ -76,9 +76,9 @@ return res, graphql.ErrorOnPath(ctx, err) {{- else }} res, err := ec.unmarshalInput{{ $type.GQL.Name }}(ctx, v) - {{- if and $type.IsNilable (not $type.IsMap) (not $type.PointersInUmarshalInput) }} + {{- if and $type.IsNilable (not $type.IsMap) (not $type.PointersInUnmarshalInput) }} return &res, graphql.ErrorOnPath(ctx, err) - {{- else if and (not $type.IsNilable) $type.PointersInUmarshalInput }} + {{- else if and (not $type.IsNilable) $type.PointersInUnmarshalInput }} return *res, graphql.ErrorOnPath(ctx, err) {{- else }} return res, graphql.ErrorOnPath(ctx, err) diff --git a/vendor/github.com/99designs/gqlgen/graphql/bool.go b/vendor/github.com/99designs/gqlgen/graphql/bool.go index b01f6eb1..d9797a38 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/bool.go +++ b/vendor/github.com/99designs/gqlgen/graphql/bool.go @@ -20,6 +20,8 @@ func UnmarshalBoolean(v any) (bool, error) { return v != 0, nil case bool: return v, nil + case nil: + return false, nil default: return false, fmt.Errorf("%T is not a bool", v) } diff --git a/vendor/github.com/99designs/gqlgen/graphql/cache.go b/vendor/github.com/99designs/gqlgen/graphql/cache.go index ef2dd5a5..8804cfe0 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/cache.go +++ b/vendor/github.com/99designs/gqlgen/graphql/cache.go @@ -3,27 +3,29 @@ package graphql import "context" // Cache is a shared store for APQ and query AST caching -type Cache interface { +type Cache[T any] interface { // Get looks up a key's value from the cache. - Get(ctx context.Context, key string) (value any, ok bool) + Get(ctx context.Context, key string) (value T, ok bool) // Add adds a value to the cache. - Add(ctx context.Context, key string, value any) + Add(ctx context.Context, key string, value T) } // MapCache is the simplest implementation of a cache, because it can not evict it should only be used in tests -type MapCache map[string]any +type MapCache[T any] map[string]T // Get looks up a key's value from the cache. -func (m MapCache) Get(_ context.Context, key string) (value any, ok bool) { +func (m MapCache[T]) Get(_ context.Context, key string) (value T, ok bool) { v, ok := m[key] return v, ok } // Add adds a value to the cache. -func (m MapCache) Add(_ context.Context, key string, value any) { m[key] = value } +func (m MapCache[T]) Add(_ context.Context, key string, value T) { m[key] = value } -type NoCache struct{} +type NoCache[T any, T2 *T] struct{} -func (n NoCache) Get(_ context.Context, _ string) (value any, ok bool) { return nil, false } -func (n NoCache) Add(_ context.Context, _ string, _ any) {} +var _ Cache[*string] = (*NoCache[string, *string])(nil) + +func (n NoCache[T, T2]) Get(_ context.Context, _ string) (value T2, ok bool) { return nil, false } +func (n NoCache[T, T2]) Add(_ context.Context, _ string, _ T2) {} diff --git a/vendor/github.com/99designs/gqlgen/graphql/executor/executor.go b/vendor/github.com/99designs/gqlgen/graphql/executor/executor.go index 426ad09b..566b0476 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/executor/executor.go +++ b/vendor/github.com/99designs/gqlgen/graphql/executor/executor.go @@ -22,7 +22,7 @@ type Executor struct { errorPresenter graphql.ErrorPresenterFunc recoverFunc graphql.RecoverFunc - queryCache graphql.Cache + queryCache graphql.Cache[*ast.QueryDocument] parserTokenLimit int } @@ -36,7 +36,7 @@ func New(es graphql.ExecutableSchema) *Executor { es: es, errorPresenter: graphql.DefaultErrorPresenter, recoverFunc: graphql.DefaultRecover, - queryCache: graphql.NoCache{}, + queryCache: graphql.NoCache[ast.QueryDocument, *ast.QueryDocument]{}, ext: processExtensions(nil), parserTokenLimit: parserTokenNoLimit, } @@ -84,7 +84,6 @@ func (e *Executor) CreateOperationContext( var err error rc.Variables, err = validator.VariableValues(e.es.Schema(), rc.Operation, params.Variables) - if err != nil { gqlErr, ok := err.(*gqlerror.Error) if ok { @@ -162,7 +161,7 @@ func (e *Executor) PresentRecoveredError(ctx context.Context, err any) error { return e.errorPresenter(ctx, e.recoverFunc(ctx, err)) } -func (e *Executor) SetQueryCache(cache graphql.Cache) { +func (e *Executor) SetQueryCache(cache graphql.Cache[*ast.QueryDocument]) { e.queryCache = cache } @@ -195,7 +194,7 @@ func (e *Executor) parseQuery( stats.Parsing.End = now stats.Validation.Start = now - return doc.(*ast.QueryDocument), nil + return doc, nil } doc, err := parser.ParseQueryWithTokenLimit(&ast.Source{Input: query}, e.parserTokenLimit) diff --git a/vendor/github.com/99designs/gqlgen/graphql/float.go b/vendor/github.com/99designs/gqlgen/graphql/float.go index 465f46af..b140d5bc 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/float.go +++ b/vendor/github.com/99designs/gqlgen/graphql/float.go @@ -28,6 +28,8 @@ func UnmarshalFloat(v any) (float64, error) { return v, nil case json.Number: return strconv.ParseFloat(string(v), 64) + case nil: + return 0, nil default: return 0, fmt.Errorf("%T is not an float", v) } diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler/extension/apq.go b/vendor/github.com/99designs/gqlgen/graphql/handler/extension/apq.go index 115aaa8a..a4cb32c9 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/handler/extension/apq.go +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/extension/apq.go @@ -23,7 +23,7 @@ const ( // hash in the next request. // see https://github.com/apollographql/apollo-link-persisted-queries type AutomaticPersistedQuery struct { - Cache graphql.Cache + Cache graphql.Cache[string] } type ApqStats struct { @@ -72,14 +72,14 @@ func (a AutomaticPersistedQuery) MutateOperationParameters(ctx context.Context, fullQuery := false if rawParams.Query == "" { + var ok bool // client sent optimistic query hash without query string, get it from the cache - query, ok := a.Cache.Get(ctx, extension.Sha256) + rawParams.Query, ok = a.Cache.Get(ctx, extension.Sha256) if !ok { err := gqlerror.Errorf(errPersistedQueryNotFound) errcode.Set(err, errPersistedQueryNotFoundCode) return err } - rawParams.Query = query.(string) } else { // client sent optimistic query hash with query string, verify and store it if computeQueryHash(rawParams.Query) != extension.Sha256 { diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler/lru/lru.go b/vendor/github.com/99designs/gqlgen/graphql/handler/lru/lru.go index 9dc480e9..946022bf 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/handler/lru/lru.go +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/lru/lru.go @@ -8,26 +8,26 @@ import ( "github.com/99designs/gqlgen/graphql" ) -type LRU struct { - lru *lru.Cache[string, any] +type LRU[T any] struct { + lru *lru.Cache[string, T] } -var _ graphql.Cache = &LRU{} +var _ graphql.Cache[any] = &LRU[any]{} -func New(size int) *LRU { - cache, err := lru.New[string, any](size) +func New[T any](size int) *LRU[T] { + cache, err := lru.New[string, T](size) if err != nil { // An error is only returned for non-positive cache size // and we already checked for that. panic("unexpected error creating cache: " + err.Error()) } - return &LRU{cache} + return &LRU[T]{cache} } -func (l LRU) Get(ctx context.Context, key string) (value any, ok bool) { +func (l LRU[T]) Get(ctx context.Context, key string) (value T, ok bool) { return l.lru.Get(key) } -func (l LRU) Add(ctx context.Context, key string, value any) { +func (l LRU[T]) Add(ctx context.Context, key string, value T) { l.lru.Add(key, value) } diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler/server.go b/vendor/github.com/99designs/gqlgen/graphql/handler/server.go index 54376b13..644bad8d 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/handler/server.go +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/server.go @@ -8,6 +8,7 @@ import ( "net/http" "time" + "github.com/vektah/gqlparser/v2/ast" "github.com/vektah/gqlparser/v2/gqlerror" "github.com/99designs/gqlgen/graphql" @@ -41,11 +42,11 @@ func NewDefaultServer(es graphql.ExecutableSchema) *Server { srv.AddTransport(transport.POST{}) srv.AddTransport(transport.MultipartForm{}) - srv.SetQueryCache(lru.New(1000)) + srv.SetQueryCache(lru.New[*ast.QueryDocument](1000)) srv.Use(extension.Introspection{}) srv.Use(extension.AutomaticPersistedQuery{ - Cache: lru.New(100), + Cache: lru.New[string](100), }) return srv @@ -63,7 +64,7 @@ func (s *Server) SetRecoverFunc(f graphql.RecoverFunc) { s.exec.SetRecoverFunc(f) } -func (s *Server) SetQueryCache(cache graphql.Cache) { +func (s *Server) SetQueryCache(cache graphql.Cache[*ast.QueryDocument]) { s.exec.SetQueryCache(cache) } diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler/transport/http_graphql.go b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/http_graphql.go index bd511525..0bad1110 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/handler/transport/http_graphql.go +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/http_graphql.go @@ -88,7 +88,6 @@ func cleanupBody(body string) (out string, err error) { // is where query starts. If it is, query is url encoded. if strings.HasPrefix(body, "%7B") { body, err = url.QueryUnescape(body) - if err != nil { return body, err } diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler/transport/websocket.go b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/websocket.go index 651ccee4..32e31c7c 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/handler/transport/websocket.go +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/websocket.go @@ -198,7 +198,7 @@ func (c *wsConnection) init() bool { var ctx context.Context ctx, initAckPayload, err = c.InitFunc(c.ctx, c.initPayload) if err != nil { - c.sendConnectionError(err.Error()) + c.sendConnectionError("%s", err.Error()) c.close(websocket.CloseNormalClosure, "terminated") return false } @@ -239,7 +239,6 @@ func (c *wsConnection) run() { ctx, cancel := context.WithCancel(c.ctx) defer func() { cancel() - c.close(websocket.CloseAbnormalClosure, "unexpected closure") }() // If we're running in graphql-ws mode, create a timer that will trigger a @@ -369,7 +368,7 @@ func (c *wsConnection) closeOnCancel(ctx context.Context) { <-ctx.Done() if r := closeReasonForContext(ctx); r != "" { - c.sendConnectionError(r) + c.sendConnectionError("%s", r) } c.close(websocket.CloseNormalClosure, "terminated") } diff --git a/vendor/github.com/99designs/gqlgen/graphql/int.go b/vendor/github.com/99designs/gqlgen/graphql/int.go index 2a5604e9..41cad3f1 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/int.go +++ b/vendor/github.com/99designs/gqlgen/graphql/int.go @@ -23,6 +23,8 @@ func UnmarshalInt(v any) (int, error) { return int(v), nil case json.Number: return strconv.Atoi(string(v)) + case nil: + return 0, nil default: return 0, fmt.Errorf("%T is not an int", v) } @@ -44,6 +46,8 @@ func UnmarshalInt64(v any) (int64, error) { return v, nil case json.Number: return strconv.ParseInt(string(v), 10, 64) + case nil: + return 0, nil default: return 0, fmt.Errorf("%T is not an int", v) } @@ -73,6 +77,8 @@ func UnmarshalInt32(v any) (int32, error) { return 0, err } return int32(iv), nil + case nil: + return 0, nil default: return 0, fmt.Errorf("%T is not an int", v) } diff --git a/vendor/github.com/99designs/gqlgen/graphql/string.go b/vendor/github.com/99designs/gqlgen/graphql/string.go index 61da5810..6622734e 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/string.go +++ b/vendor/github.com/99designs/gqlgen/graphql/string.go @@ -62,7 +62,7 @@ func UnmarshalString(v any) (string, error) { case bool: return strconv.FormatBool(v), nil case nil: - return "null", nil + return "", nil default: return "", fmt.Errorf("%T is not a string", v) } diff --git a/vendor/github.com/99designs/gqlgen/graphql/uint.go b/vendor/github.com/99designs/gqlgen/graphql/uint.go index ffccaf64..cd5d2355 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/uint.go +++ b/vendor/github.com/99designs/gqlgen/graphql/uint.go @@ -34,6 +34,8 @@ func UnmarshalUint(v any) (uint, error) { case json.Number: u64, err := strconv.ParseUint(string(v), 10, 64) return uint(u64), err + case nil: + return 0, nil default: return 0, fmt.Errorf("%T is not an uint", v) } @@ -63,6 +65,8 @@ func UnmarshalUint64(v any) (uint64, error) { return uint64(v), nil case json.Number: return strconv.ParseUint(string(v), 10, 64) + case nil: + return 0, nil default: return 0, fmt.Errorf("%T is not an uint", v) } @@ -100,6 +104,8 @@ func UnmarshalUint32(v any) (uint32, error) { return 0, err } return uint32(iv), nil + case nil: + return 0, nil default: return 0, fmt.Errorf("%T is not an uint", v) } diff --git a/vendor/github.com/99designs/gqlgen/graphql/version.go b/vendor/github.com/99designs/gqlgen/graphql/version.go index 82266736..2031242f 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/version.go +++ b/vendor/github.com/99designs/gqlgen/graphql/version.go @@ -1,3 +1,3 @@ package graphql -const Version = "v0.17.49" +const Version = "v0.17.55" diff --git a/vendor/github.com/99designs/gqlgen/init-templates/gqlgen.yml.gotmpl b/vendor/github.com/99designs/gqlgen/init-templates/gqlgen.yml.gotmpl index 6e97f8bf..648ec2b4 100644 --- a/vendor/github.com/99designs/gqlgen/init-templates/gqlgen.yml.gotmpl +++ b/vendor/github.com/99designs/gqlgen/init-templates/gqlgen.yml.gotmpl @@ -11,6 +11,9 @@ exec: # federation: # filename: graph/federation.go # package: graph +# version: 2 +# options +# computed_requires: true # Where should any generated models go? model: @@ -63,6 +66,13 @@ resolver: # Optional: set to skip running `go mod tidy` when generating server code # skip_mod_tidy: true +# Optional: if this is set to true, argument directives that +# decorate a field with a null value will still be called. +# +# This enables argumment directives to not just mutate +# argument values but to set them even if they're null. +call_argument_directives_with_null: true + # gqlgen will search for any type names in the schema in these go packages # if they match it will use them, otherwise it will generate them. autobind: diff --git a/vendor/github.com/99designs/gqlgen/internal/code/alias.go b/vendor/github.com/99designs/gqlgen/internal/code/alias.go new file mode 100644 index 00000000..f7685801 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/internal/code/alias.go @@ -0,0 +1,13 @@ +//go:build !go1.23 + +package code + +import ( + "go/types" +) + +// Unalias unwraps an alias type +// TODO: Drop this function when we drop support for go1.22 +func Unalias(t types.Type) types.Type { + return t // No-op +} diff --git a/vendor/github.com/99designs/gqlgen/internal/code/alias_1.23.go b/vendor/github.com/99designs/gqlgen/internal/code/alias_1.23.go new file mode 100644 index 00000000..fa0b216c --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/internal/code/alias_1.23.go @@ -0,0 +1,19 @@ +//go:build go1.23 + +package code + +import ( + "go/types" +) + +// Unalias unwraps an alias type +func Unalias(t types.Type) types.Type { + if p, ok := t.(*types.Pointer); ok { + // If the type come from auto-binding, + // it will be a pointer to an alias type. + // (e.g: `type Cursor = entgql.Cursor[int]`) + // *ent.Cursor is the type we got from auto-binding. + return types.NewPointer(Unalias(p.Elem())) + } + return types.Unalias(t) +} diff --git a/vendor/github.com/99designs/gqlgen/internal/code/compare.go b/vendor/github.com/99designs/gqlgen/internal/code/compare.go index a3f15f18..05fad22d 100644 --- a/vendor/github.com/99designs/gqlgen/internal/code/compare.go +++ b/vendor/github.com/99designs/gqlgen/internal/code/compare.go @@ -8,6 +8,8 @@ import ( // CompatibleTypes isnt a strict comparison, it allows for pointer differences func CompatibleTypes(expected, actual types.Type) error { + // Unwrap any aliases + expected, actual = Unalias(expected), Unalias(actual) // Special case to deal with pointer mismatches { expectedPtr, expectedIsPtr := expected.(*types.Pointer) diff --git a/vendor/github.com/99designs/gqlgen/plugin/federation/constants.go b/vendor/github.com/99designs/gqlgen/plugin/federation/constants.go new file mode 100644 index 00000000..8571db1f --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/plugin/federation/constants.go @@ -0,0 +1,185 @@ +package federation + +import ( + "github.com/99designs/gqlgen/codegen/config" + "github.com/vektah/gqlparser/v2/ast" +) + +// The name of the field argument that is injected into the resolver to support @requires. +const fieldArgRequires = "_federationRequires" + +// The name of the scalar type used in the injected field argument to support @requires. +const mapTypeName = "_RequiresMap" + +// The @key directive that defines the key fields for an entity. +const dirNameKey = "key" + +// The @requires directive that defines the required fields for an entity to be resolved. +const dirNameRequires = "requires" + +// The @entityResolver directive allows users to specify entity resolvers as batch lookups +const dirNameEntityResolver = "entityResolver" + +const dirNamePopulateFromRepresentations = "populateFromRepresentations" + +var populateFromRepresentationsImplementation = `func(ctx context.Context, obj any, next graphql.Resolver) (res any, err error) { + fc := graphql.GetFieldContext(ctx) + + // We get the Federation representations argument from the _entities resolver + representations, ok := fc.Parent.Parent.Args["representations"].([]map[string]any) + if !ok { + return nil, errors.New("must be called from within _entities") + } + + // Get the index of the current entity in the representations list. This is + // set by the execution context after the _entities resolver is called. + index := fc.Parent.Index + if index == nil { + return nil, errors.New("couldn't find input index for entity") + } + + if len(representations) < *index { + return nil, errors.New("representation not found") + } + + return representations[*index], nil +}` + +const DirNameEntityReference = "entityReference" + +// The fields arguments must be provided to both key and requires directives. +const DirArgFields = "fields" + +// Tells the code generator what type the directive is referencing +const DirArgType = "type" + +// The file name for Federation directives +const dirGraphQLQFile = "federation/directives.graphql" + +// The file name for Federation entities +const entityGraphQLQFile = "federation/entity.graphql" + +const federationVersion1Schema = ` + directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE + directive @requires(fields: _FieldSet!) on FIELD_DEFINITION + directive @provides(fields: _FieldSet!) on FIELD_DEFINITION + directive @extends on OBJECT | INTERFACE + directive @external on FIELD_DEFINITION + scalar _Any + scalar _FieldSet +` + +const federationVersion2Schema = ` + directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + directive @composeDirective(name: String!) repeatable on SCHEMA + directive @extends on OBJECT | INTERFACE + directive @external on OBJECT | FIELD_DEFINITION + directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE + directive @inaccessible on + | ARGUMENT_DEFINITION + | ENUM + | ENUM_VALUE + | FIELD_DEFINITION + | INPUT_FIELD_DEFINITION + | INPUT_OBJECT + | INTERFACE + | OBJECT + | SCALAR + | UNION + directive @interfaceObject on OBJECT + directive @link(import: [String!], url: String!) repeatable on SCHEMA + directive @override(from: String!, label: String) on FIELD_DEFINITION + directive @policy(policies: [[federation__Policy!]!]!) on + | FIELD_DEFINITION + | OBJECT + | INTERFACE + | SCALAR + | ENUM + directive @provides(fields: FieldSet!) on FIELD_DEFINITION + directive @requires(fields: FieldSet!) on FIELD_DEFINITION + directive @requiresScopes(scopes: [[federation__Scope!]!]!) on + | FIELD_DEFINITION + | OBJECT + | INTERFACE + | SCALAR + | ENUM + directive @shareable repeatable on FIELD_DEFINITION | OBJECT + directive @tag(name: String!) repeatable on + | ARGUMENT_DEFINITION + | ENUM + | ENUM_VALUE + | FIELD_DEFINITION + | INPUT_FIELD_DEFINITION + | INPUT_OBJECT + | INTERFACE + | OBJECT + | SCALAR + | UNION + scalar _Any + scalar FieldSet + scalar federation__Policy + scalar federation__Scope +` + +var builtins = config.TypeMap{ + "_Service": { + Model: config.StringList{ + "github.com/99designs/gqlgen/plugin/federation/fedruntime.Service", + }, + }, + "_Entity": { + Model: config.StringList{ + "github.com/99designs/gqlgen/plugin/federation/fedruntime.Entity", + }, + }, + "Entity": { + Model: config.StringList{ + "github.com/99designs/gqlgen/plugin/federation/fedruntime.Entity", + }, + }, + "_Any": { + Model: config.StringList{"github.com/99designs/gqlgen/graphql.Map"}, + }, + "federation__Scope": { + Model: config.StringList{"github.com/99designs/gqlgen/graphql.String"}, + }, + "federation__Policy": { + Model: config.StringList{"github.com/99designs/gqlgen/graphql.String"}, + }, +} + +var dirPopulateFromRepresentations = &ast.DirectiveDefinition{ + Name: dirNamePopulateFromRepresentations, + IsRepeatable: false, + Description: `This is a runtime directive used to implement @requires. It's automatically placed +on the generated _federationRequires argument, and the implementation of it extracts the +correct value from the input representations list.`, + Locations: []ast.DirectiveLocation{ast.LocationArgumentDefinition}, + Position: &ast.Position{Src: &ast.Source{ + Name: dirGraphQLQFile, + }}, +} + +var dirEntityReference = &ast.DirectiveDefinition{ + Name: DirNameEntityReference, + IsRepeatable: false, + Description: `This is a compile-time directive used to implement @requires. +It tells the code generator how to generate the model for the scalar.`, + Locations: []ast.DirectiveLocation{ast.LocationScalar}, + Arguments: ast.ArgumentDefinitionList{ + { + Name: DirArgType, + Type: ast.NonNullNamedType("String", nil), + Description: `The name of the entity that the fields selection +set should be validated against.`, + }, + { + Name: DirArgFields, + Type: ast.NonNullNamedType("FieldSet", nil), + Description: "The selection that the scalar should generate into.", + }, + }, + Position: &ast.Position{Src: &ast.Source{ + Name: dirGraphQLQFile, + }}, +} diff --git a/vendor/github.com/99designs/gqlgen/plugin/federation/entity.go b/vendor/github.com/99designs/gqlgen/plugin/federation/entity.go index 4e9e1afc..562d86c5 100644 --- a/vendor/github.com/99designs/gqlgen/plugin/federation/entity.go +++ b/vendor/github.com/99designs/gqlgen/plugin/federation/entity.go @@ -22,10 +22,12 @@ type Entity struct { } type EntityResolver struct { - ResolverName string // The resolver name, such as FindUserByID - KeyFields []*KeyField // The fields declared in @key. - InputType types.Type // The Go generated input type for multi entity resolvers - InputTypeName string + ResolverName string // The resolver name, such as FindUserByID + KeyFields []*KeyField // The fields declared in @key. + InputType types.Type // The Go generated input type for multi entity resolvers + InputTypeName string + ReturnType types.Type // The Go generated return type for the entity + ReturnTypeName string } func (e *EntityResolver) LookupInputType() string { @@ -60,7 +62,7 @@ func (e *Entity) isFieldImplicitlyExternal(field *ast.FieldDefinition, federatio if federationVersion != 2 { return false } - // TODO: From the spec, it seems like if an entity is not resolvable then it should not only not have a resolver, but should not appear in the _Entitiy union. + // TODO: From the spec, it seems like if an entity is not resolvable then it should not only not have a resolver, but should not appear in the _Entity union. // The current implementation is a less drastic departure from the previous behavior, but should probably be reviewed. // See https://www.apollographql.com/docs/federation/subgraph-spec/ if e.isResolvable() { @@ -76,7 +78,7 @@ func (e *Entity) isFieldImplicitlyExternal(field *ast.FieldDefinition, federatio // Determine if the entity is resolvable. func (e *Entity) isResolvable() bool { - key := e.Def.Directives.ForName("key") + key := e.Def.Directives.ForName(dirNameKey) if key == nil { // If there is no key directive, the entity is resolvable. return true @@ -102,11 +104,11 @@ func (e *Entity) isKeyField(field *ast.FieldDefinition) bool { // Get the key fields for this entity. func (e *Entity) keyFields() []string { - key := e.Def.Directives.ForName("key") + key := e.Def.Directives.ForName(dirNameKey) if key == nil { return []string{} } - fields := key.Arguments.ForName("fields") + fields := key.Arguments.ForName(DirArgFields) if fields == nil { return []string{} } diff --git a/vendor/github.com/99designs/gqlgen/plugin/federation/federation.go b/vendor/github.com/99designs/gqlgen/plugin/federation/federation.go index fd33255b..f7a4d6be 100644 --- a/vendor/github.com/99designs/gqlgen/plugin/federation/federation.go +++ b/vendor/github.com/99designs/gqlgen/plugin/federation/federation.go @@ -2,6 +2,7 @@ package federation import ( _ "embed" + "errors" "fmt" "sort" "strings" @@ -12,7 +13,6 @@ import ( "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/codegen/templates" "github.com/99designs/gqlgen/internal/rewrite" - "github.com/99designs/gqlgen/plugin" "github.com/99designs/gqlgen/plugin/federation/fieldset" ) @@ -22,56 +22,81 @@ var federationTemplate string //go:embed requires.gotpl var explicitRequiresTemplate string -type federation struct { +type Federation struct { Entities []*Entity - Version int - PackageOptions map[string]bool + PackageOptions PackageOptions + + version int + + // true if @requires is used in the schema + usesRequires bool +} + +type PackageOptions struct { + // ExplicitRequires will generate a function in the execution context + // to populate fields using the @required directive into the entity. + // + // You can only set one of ExplicitRequires or ComputedRequires to true. + ExplicitRequires bool + // ComputedRequires generates resolver functions to compute values for + // fields using the @required directive. + ComputedRequires bool } // New returns a federation plugin that injects // federated directives and types into the schema -func New(version int) plugin.Plugin { +func New(version int, cfg *config.Config) (*Federation, error) { if version == 0 { version = 1 } - return &federation{Version: version} + options, err := buildPackageOptions(cfg) + if err != nil { + return nil, fmt.Errorf("invalid federation package options: %w", err) + } + return &Federation{ + version: version, + PackageOptions: options, + }, nil +} + +func buildPackageOptions(cfg *config.Config) (PackageOptions, error) { + packageOptions := cfg.Federation.Options + + explicitRequires := packageOptions["explicit_requires"] + computedRequires := packageOptions["computed_requires"] + if explicitRequires && computedRequires { + return PackageOptions{}, errors.New("only one of explicit_requires or computed_requires can be set to true") + } + + if computedRequires { + if cfg.Federation.Version != 2 { + return PackageOptions{}, errors.New("when using federation.options.computed_requires you must be using Federation 2") + } + + // We rely on injecting a null argument with a directives for fields with @requires, so we need to ensure + // our directive is always called. + if !cfg.CallArgumentDirectivesWithNull { + return PackageOptions{}, errors.New("when using federation.options.computed_requires, call_argument_directives_with_null must be set to true") + } + } + + // We rely on injecting a null argument with a directives for fields with @requires, so we need to ensure + // our directive is always called. + + return PackageOptions{ + ExplicitRequires: explicitRequires, + ComputedRequires: computedRequires, + }, nil } // Name returns the plugin name -func (f *federation) Name() string { +func (f *Federation) Name() string { return "federation" } // MutateConfig mutates the configuration -func (f *federation) MutateConfig(cfg *config.Config) error { - builtins := config.TypeMap{ - "_Service": { - Model: config.StringList{ - "github.com/99designs/gqlgen/plugin/federation/fedruntime.Service", - }, - }, - "_Entity": { - Model: config.StringList{ - "github.com/99designs/gqlgen/plugin/federation/fedruntime.Entity", - }, - }, - "Entity": { - Model: config.StringList{ - "github.com/99designs/gqlgen/plugin/federation/fedruntime.Entity", - }, - }, - "_Any": { - Model: config.StringList{"github.com/99designs/gqlgen/graphql.Map"}, - }, - "federation__Scope": { - Model: config.StringList{"github.com/99designs/gqlgen/graphql.String"}, - }, - "federation__Policy": { - Model: config.StringList{"github.com/99designs/gqlgen/graphql.String"}, - }, - } - +func (f *Federation) MutateConfig(cfg *config.Config) error { for typeName, entry := range builtins { if cfg.Models.Exists(typeName) { return fmt.Errorf("%v already exists which must be reserved when Federation is enabled", typeName) @@ -79,13 +104,14 @@ func (f *federation) MutateConfig(cfg *config.Config) error { cfg.Models[typeName] = entry } cfg.Directives["external"] = config.DirectiveConfig{SkipRuntime: true} - cfg.Directives["requires"] = config.DirectiveConfig{SkipRuntime: true} + cfg.Directives[dirNameRequires] = config.DirectiveConfig{SkipRuntime: true} cfg.Directives["provides"] = config.DirectiveConfig{SkipRuntime: true} - cfg.Directives["key"] = config.DirectiveConfig{SkipRuntime: true} + cfg.Directives[dirNameKey] = config.DirectiveConfig{SkipRuntime: true} cfg.Directives["extends"] = config.DirectiveConfig{SkipRuntime: true} + cfg.Directives[dirNameEntityResolver] = config.DirectiveConfig{SkipRuntime: true} // Federation 2 specific directives - if f.Version == 2 { + if f.version == 2 { cfg.Directives["shareable"] = config.DirectiveConfig{SkipRuntime: true} cfg.Directives["link"] = config.DirectiveConfig{SkipRuntime: true} cfg.Directives["tag"] = config.DirectiveConfig{SkipRuntime: true} @@ -98,95 +124,48 @@ func (f *federation) MutateConfig(cfg *config.Config) error { cfg.Directives["composeDirective"] = config.DirectiveConfig{SkipRuntime: true} } + if f.usesRequires && f.PackageOptions.ComputedRequires { + cfg.Schema.Directives[dirPopulateFromRepresentations.Name] = dirPopulateFromRepresentations + cfg.Directives[dirPopulateFromRepresentations.Name] = config.DirectiveConfig{Implementation: &populateFromRepresentationsImplementation} + + cfg.Schema.Directives[dirEntityReference.Name] = dirEntityReference + cfg.Directives[dirEntityReference.Name] = config.DirectiveConfig{SkipRuntime: true} + + f.addMapType(cfg) + f.mutateSchemaForRequires(cfg.Schema, cfg) + } + return nil } -func (f *federation) InjectSourceEarly() *ast.Source { +func (f *Federation) InjectSourcesEarly() ([]*ast.Source, error) { input := `` // add version-specific changes on key directive, as well as adding the new directives for federation 2 - if f.Version == 1 { - input += ` - directive @key(fields: _FieldSet!) repeatable on OBJECT | INTERFACE - directive @requires(fields: _FieldSet!) on FIELD_DEFINITION - directive @provides(fields: _FieldSet!) on FIELD_DEFINITION - directive @extends on OBJECT | INTERFACE - directive @external on FIELD_DEFINITION - scalar _Any - scalar _FieldSet -` - } else if f.Version == 2 { - input += ` - directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM - directive @composeDirective(name: String!) repeatable on SCHEMA - directive @extends on OBJECT | INTERFACE - directive @external on OBJECT | FIELD_DEFINITION - directive @key(fields: FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE - directive @inaccessible on - | ARGUMENT_DEFINITION - | ENUM - | ENUM_VALUE - | FIELD_DEFINITION - | INPUT_FIELD_DEFINITION - | INPUT_OBJECT - | INTERFACE - | OBJECT - | SCALAR - | UNION - directive @interfaceObject on OBJECT - directive @link(import: [String!], url: String!) repeatable on SCHEMA - directive @override(from: String!, label: String) on FIELD_DEFINITION - directive @policy(policies: [[federation__Policy!]!]!) on - | FIELD_DEFINITION - | OBJECT - | INTERFACE - | SCALAR - | ENUM - directive @provides(fields: FieldSet!) on FIELD_DEFINITION - directive @requires(fields: FieldSet!) on FIELD_DEFINITION - directive @requiresScopes(scopes: [[federation__Scope!]!]!) on - | FIELD_DEFINITION - | OBJECT - | INTERFACE - | SCALAR - | ENUM - directive @shareable repeatable on FIELD_DEFINITION | OBJECT - directive @tag(name: String!) repeatable on - | ARGUMENT_DEFINITION - | ENUM - | ENUM_VALUE - | FIELD_DEFINITION - | INPUT_FIELD_DEFINITION - | INPUT_OBJECT - | INTERFACE - | OBJECT - | SCALAR - | UNION - scalar _Any - scalar FieldSet - scalar federation__Policy - scalar federation__Scope -` + if f.version == 1 { + input += federationVersion1Schema + } else if f.version == 2 { + input += federationVersion2Schema } - return &ast.Source{ - Name: "federation/directives.graphql", + + return []*ast.Source{{ + Name: dirGraphQLQFile, Input: input, BuiltIn: true, - } + }}, nil } // InjectSourceLate creates a GraphQL Entity type with all // the fields that had the @key directive -func (f *federation) InjectSourceLate(schema *ast.Schema) *ast.Source { - f.setEntities(schema) +func (f *Federation) InjectSourcesLate(schema *ast.Schema) ([]*ast.Source, error) { + f.Entities = f.buildEntities(schema, f.version) - var entities, resolvers, entityResolverInputDefinitions string + entities := make([]string, 0) + resolvers := make([]string, 0) + entityResolverInputDefinitions := make([]string, 0) for _, e := range f.Entities { if e.Def.Kind != ast.Interface { - if entities != "" { - entities += " | " - } - entities += e.Name + entities = append(entities, e.Name) } else if len(schema.GetPossibleTypes(e.Def)) == 0 { fmt.Println( "skipping @key field on interface " + e.Def.Name + " as no types implement it", @@ -194,48 +173,33 @@ func (f *federation) InjectSourceLate(schema *ast.Schema) *ast.Source { } for _, r := range e.Resolvers { - if e.Multi { - if entityResolverInputDefinitions != "" { - entityResolverInputDefinitions += "\n\n" - } - entityResolverInputDefinitions += "input " + r.InputTypeName + " {\n" - for _, keyField := range r.KeyFields { - entityResolverInputDefinitions += fmt.Sprintf( - "\t%s: %s\n", - keyField.Field.ToGo(), - keyField.Definition.Type.String(), - ) - } - entityResolverInputDefinitions += "}" - resolvers += fmt.Sprintf("\t%s(reps: [%s]!): [%s]\n", r.ResolverName, r.InputTypeName, e.Name) - } else { - resolverArgs := "" - for _, keyField := range r.KeyFields { - resolverArgs += fmt.Sprintf("%s: %s,", keyField.Field.ToGoPrivate(), keyField.Definition.Type.String()) - } - resolvers += fmt.Sprintf("\t%s(%s): %s!\n", r.ResolverName, resolverArgs, e.Name) + resolverSDL, entityResolverInputSDL := buildResolverSDL(r, e.Multi) + resolvers = append(resolvers, resolverSDL) + if entityResolverInputSDL != "" { + entityResolverInputDefinitions = append(entityResolverInputDefinitions, entityResolverInputSDL) } } } var blocks []string - if entities != "" { - entities = `# a union of all types that use the @key directive -union _Entity = ` + entities - blocks = append(blocks, entities) + if len(entities) > 0 { + entitiesSDL := `# a union of all types that use the @key directive +union _Entity = ` + strings.Join(entities, " | ") + blocks = append(blocks, entitiesSDL) } // resolvers can be empty if a service defines only "empty // extend" types. This should be rare. - if resolvers != "" { - if entityResolverInputDefinitions != "" { - blocks = append(blocks, entityResolverInputDefinitions) + if len(resolvers) > 0 { + if len(entityResolverInputDefinitions) > 0 { + inputSDL := strings.Join(entityResolverInputDefinitions, "\n\n") + blocks = append(blocks, inputSDL) } - resolvers = `# fake type to build resolver interfaces for users to implement + resolversSDL := `# fake type to build resolver interfaces for users to implement type Entity { - ` + resolvers + ` +` + strings.Join(resolvers, "\n") + ` }` - blocks = append(blocks, resolvers) + blocks = append(blocks, resolversSDL) } _serviceTypeDef := `type _Service { @@ -259,14 +223,14 @@ type Entity { }` blocks = append(blocks, extendTypeQueryDef) - return &ast.Source{ - Name: "federation/entity.graphql", + return []*ast.Source{{ + Name: entityGraphQLQFile, BuiltIn: true, Input: "\n" + strings.Join(blocks, "\n\n") + "\n", - } + }}, nil } -func (f *federation) GenerateCode(data *codegen.Data) error { +func (f *Federation) GenerateCode(data *codegen.Data) error { // requires imports requiresImports := make(map[string]bool, 0) requiresImports["context"] = true @@ -275,7 +239,11 @@ func (f *federation) GenerateCode(data *codegen.Data) error { requiresEntities := make(map[string]*Entity, 0) // Save package options on f for template use - f.PackageOptions = data.Config.Federation.Options + packageOptions, err := buildPackageOptions(data.Config) + if err != nil { + return fmt.Errorf("invalid federation package options: %w", err) + } + f.PackageOptions = packageOptions if len(f.Entities) > 0 { if data.Objects.ByName("Entity") != nil { @@ -295,18 +263,7 @@ func (f *federation) GenerateCode(data *codegen.Data) error { } for _, r := range e.Resolvers { - // fill in types for key fields - // - for _, keyField := range r.KeyFields { - if len(keyField.Field) == 0 { - fmt.Println( - "skipping @key field " + keyField.Definition.Name + " in " + r.ResolverName + " in " + e.Def.Name, - ) - continue - } - cgField := keyField.Field.TypeReference(obj, data.Objects) - keyField.Type = cgField.TypeReference - } + populateKeyFieldTypes(r, obj, data.Objects, e.Def.Name) } // fill in types for requires fields @@ -348,69 +305,12 @@ func (f *federation) GenerateCode(data *codegen.Data) error { } } - if data.Config.Federation.Options["explicit_requires"] && len(requiresEntities) > 0 { - // check for existing requires functions - type Populator struct { - FuncName string - Exists bool - Comment string - Implementation string - Entity *Entity - } - populators := make([]Populator, 0) - - rewriter, err := rewrite.New(data.Config.Federation.Dir()) - if err != nil { - return err - } - - for name, entity := range requiresEntities { - populator := Populator{ - FuncName: fmt.Sprintf("Populate%sRequires", name), - Entity: entity, - } - - populator.Comment = strings.TrimSpace(strings.TrimLeft(rewriter.GetMethodComment("executionContext", populator.FuncName), `\`)) - populator.Implementation = strings.TrimSpace(rewriter.GetMethodBody("executionContext", populator.FuncName)) - - if populator.Implementation == "" { - populator.Exists = false - populator.Implementation = fmt.Sprintf("panic(fmt.Errorf(\"not implemented: %v\"))", populator.FuncName) - } - populators = append(populators, populator) - } - - sort.Slice(populators, func(i, j int) bool { - return populators[i].FuncName < populators[j].FuncName - }) - - requiresFile := data.Config.Federation.Dir() + "/federation.requires.go" - existingImports := rewriter.ExistingImports(requiresFile) - for _, imp := range existingImports { - if imp.Alias == "" { - // import exists in both places, remove - delete(requiresImports, imp.ImportPath) - } - } - - for k := range requiresImports { - existingImports = append(existingImports, rewrite.Import{ImportPath: k}) - } - - // render requires populators - err = templates.Render(templates.Options{ - PackageName: data.Config.Federation.Package, - Filename: requiresFile, - Data: struct { - federation - ExistingImports []rewrite.Import - Populators []Populator - OriginalSource string - }{*f, existingImports, populators, ""}, - GeneratedHeader: false, - Packages: data.Config.Packages, - Template: explicitRequiresTemplate, - }) + if f.PackageOptions.ExplicitRequires && len(requiresEntities) > 0 { + err := f.generateExplicitRequires( + data, + requiresEntities, + requiresImports, + ) if err != nil { return err } @@ -420,7 +320,7 @@ func (f *federation) GenerateCode(data *codegen.Data) error { PackageName: data.Config.Federation.Package, Filename: data.Config.Federation.Filename, Data: struct { - federation + Federation UsePointers bool }{*f, data.Config.ResolversAlwaysReturnPointers}, GeneratedHeader: true, @@ -429,137 +329,227 @@ func (f *federation) GenerateCode(data *codegen.Data) error { }) } -func (f *federation) setEntities(schema *ast.Schema) { - for _, schemaType := range schema.Types { - keys, ok := isFederatedEntity(schemaType) - if !ok { +// Fill in types for key fields +func populateKeyFieldTypes( + resolver *EntityResolver, + obj *codegen.Object, + allObjects codegen.Objects, + name string, +) { + for _, keyField := range resolver.KeyFields { + if len(keyField.Field) == 0 { + fmt.Println( + "skipping @key field " + keyField.Definition.Name + " in " + resolver.ResolverName + " in " + name, + ) continue } + cgField := keyField.Field.TypeReference(obj, allObjects) + keyField.Type = cgField.TypeReference + } +} - if (schemaType.Kind == ast.Interface) && (len(schema.GetPossibleTypes(schemaType)) == 0) { - fmt.Printf("@key directive found on unused \"interface %s\". Will be ignored.\n", schemaType.Name) - continue +func (f *Federation) buildEntities(schema *ast.Schema, version int) []*Entity { + entities := make([]*Entity, 0) + for _, schemaType := range schema.Types { + entity := f.buildEntity(schemaType, schema, version) + if entity != nil { + entities = append(entities, entity) + } + } + + // make sure order remains stable across multiple builds + sort.Slice(entities, func(i, j int) bool { + return entities[i].Name < entities[j].Name + }) + + return entities +} + +func (f *Federation) buildEntity( + schemaType *ast.Definition, + schema *ast.Schema, + version int, +) *Entity { + keys, ok := isFederatedEntity(schemaType) + if !ok { + return nil + } + + if (schemaType.Kind == ast.Interface) && (len(schema.GetPossibleTypes(schemaType)) == 0) { + fmt.Printf("@key directive found on unused \"interface %s\". Will be ignored.\n", schemaType.Name) + return nil + } + + entity := &Entity{ + Name: schemaType.Name, + Def: schemaType, + Resolvers: nil, + Requires: nil, + Multi: isMultiEntity(schemaType), + } + + // If our schema has a field with a type defined in + // another service, then we need to define an "empty + // extend" of that type in this service, so this service + // knows what the type is like. But the graphql-server + // will never ask us to actually resolve this "empty + // extend", so we don't require a resolver function for + // it. (Well, it will never ask in practice; it's + // unclear whether the spec guarantees this. See + // https://github.com/apollographql/apollo-server/issues/3852 + // ). Example: + // type MyType { + // myvar: TypeDefinedInOtherService + // } + // // Federation needs this type, but + // // it doesn't need a resolver for it! + // extend TypeDefinedInOtherService @key(fields: "id") { + // id: ID @external + // } + if entity.allFieldsAreExternal(version) { + return entity + } + + entity.Resolvers = buildResolvers(schemaType, schema, keys, entity.Multi) + entity.Requires = buildRequires(schemaType) + if len(entity.Requires) > 0 { + f.usesRequires = true + } + + return entity +} + +func isMultiEntity(schemaType *ast.Definition) bool { + dir := schemaType.Directives.ForName(dirNameEntityResolver) + if dir == nil { + return false + } + + if dirArg := dir.Arguments.ForName("multi"); dirArg != nil { + if dirVal, err := dirArg.Value.Value(nil); err == nil { + return dirVal.(bool) } + } + + return false +} - e := &Entity{ - Name: schemaType.Name, - Def: schemaType, - Resolvers: nil, - Requires: nil, +func buildResolvers( + schemaType *ast.Definition, + schema *ast.Schema, + keys []*ast.Directive, + multi bool, +) []*EntityResolver { + resolvers := make([]*EntityResolver, 0) + for _, dir := range keys { + if len(dir.Arguments) > 2 { + panic("More than two arguments provided for @key declaration.") + } + keyFields, resolverFields := buildKeyFields( + schemaType, + schema, + dir, + ) + + resolverFieldsToGo := schemaType.Name + "By" + strings.Join(resolverFields, "And") + var resolverName string + if multi { + resolverFieldsToGo += "s" // Pluralize for better API readability + resolverName = fmt.Sprintf("findMany%s", resolverFieldsToGo) + } else { + resolverName = fmt.Sprintf("find%s", resolverFieldsToGo) } - // Let's process custom entity resolver settings. - dir := schemaType.Directives.ForName("entityResolver") - if dir != nil { - if dirArg := dir.Arguments.ForName("multi"); dirArg != nil { - if dirVal, err := dirArg.Value.Value(nil); err == nil { - e.Multi = dirVal.(bool) - } + resolvers = append(resolvers, &EntityResolver{ + ResolverName: resolverName, + KeyFields: keyFields, + InputTypeName: resolverFieldsToGo + "Input", + ReturnTypeName: schemaType.Name, + }) + } + + return resolvers +} + +func extractFields( + dir *ast.Directive, +) (string, error) { + var arg *ast.Argument + + // since directives are able to now have multiple arguments, we need to check both possible for a possible @key(fields="" fields="") + for _, a := range dir.Arguments { + if a.Name == DirArgFields { + if arg != nil { + return "", errors.New("more than one \"fields\" argument provided for declaration") } + arg = a } + } - // If our schema has a field with a type defined in - // another service, then we need to define an "empty - // extend" of that type in this service, so this service - // knows what the type is like. But the graphql-server - // will never ask us to actually resolve this "empty - // extend", so we don't require a resolver function for - // it. (Well, it will never ask in practice; it's - // unclear whether the spec guarantees this. See - // https://github.com/apollographql/apollo-server/issues/3852 - // ). Example: - // type MyType { - // myvar: TypeDefinedInOtherService - // } - // // Federation needs this type, but - // // it doesn't need a resolver for it! - // extend TypeDefinedInOtherService @key(fields: "id") { - // id: ID @external - // } - if !e.allFieldsAreExternal(f.Version) { - for _, dir := range keys { - if len(dir.Arguments) > 2 { - panic("More than two arguments provided for @key declaration.") - } - var arg *ast.Argument - - // since keys are able to now have multiple arguments, we need to check both possible for a possible @key(fields="" fields="") - for _, a := range dir.Arguments { - if a.Name == "fields" { - if arg != nil { - panic("More than one `fields` provided for @key declaration.") - } - arg = a - } - } + return arg.Value.Raw, nil +} - keyFieldSet := fieldset.New(arg.Value.Raw, nil) +func buildKeyFields( + schemaType *ast.Definition, + schema *ast.Schema, + dir *ast.Directive, +) ([]*KeyField, []string) { + fieldsRaw, err := extractFields(dir) + if err != nil { + panic("More than one `fields` argument provided for declaration.") + } - keyFields := make([]*KeyField, len(keyFieldSet)) - resolverFields := []string{} - for i, field := range keyFieldSet { - def := field.FieldDefinition(schemaType, schema) + keyFieldSet := fieldset.New(fieldsRaw, nil) - if def == nil { - panic(fmt.Sprintf("no field for %v", field)) - } + keyFields := make([]*KeyField, len(keyFieldSet)) + resolverFields := []string{} + for i, field := range keyFieldSet { + def := field.FieldDefinition(schemaType, schema) - keyFields[i] = &KeyField{Definition: def, Field: field} - resolverFields = append(resolverFields, keyFields[i].Field.ToGo()) - } + if def == nil { + panic(fmt.Sprintf("no field for %v", field)) + } - resolverFieldsToGo := schemaType.Name + "By" + strings.Join(resolverFields, "And") - var resolverName string - if e.Multi { - resolverFieldsToGo += "s" // Pluralize for better API readability - resolverName = fmt.Sprintf("findMany%s", resolverFieldsToGo) - } else { - resolverName = fmt.Sprintf("find%s", resolverFieldsToGo) - } + keyFields[i] = &KeyField{Definition: def, Field: field} + resolverFields = append(resolverFields, keyFields[i].Field.ToGo()) + } - e.Resolvers = append(e.Resolvers, &EntityResolver{ - ResolverName: resolverName, - KeyFields: keyFields, - InputTypeName: resolverFieldsToGo + "Input", - }) - } + return keyFields, resolverFields +} - e.Requires = []*Requires{} - for _, f := range schemaType.Fields { - dir := f.Directives.ForName("requires") - if dir == nil { - continue - } - if len(dir.Arguments) != 1 || dir.Arguments[0].Name != "fields" { - panic("Exactly one `fields` argument needed for @requires declaration.") - } - requiresFieldSet := fieldset.New(dir.Arguments[0].Value.Raw, nil) - for _, field := range requiresFieldSet { - e.Requires = append(e.Requires, &Requires{ - Name: field.ToGoPrivate(), - Field: field, - }) - } - } +func buildRequires(schemaType *ast.Definition) []*Requires { + requires := make([]*Requires, 0) + for _, f := range schemaType.Fields { + dir := f.Directives.ForName(dirNameRequires) + if dir == nil { + continue + } + + fieldsRaw, err := extractFields(dir) + if err != nil { + panic("Exactly one `fields` argument needed for @requires declaration.") + } + requiresFieldSet := fieldset.New(fieldsRaw, nil) + for _, field := range requiresFieldSet { + requires = append(requires, &Requires{ + Name: field.ToGoPrivate(), + Field: field, + }) } - f.Entities = append(f.Entities, e) } - // make sure order remains stable across multiple builds - sort.Slice(f.Entities, func(i, j int) bool { - return f.Entities[i].Name < f.Entities[j].Name - }) + return requires } func isFederatedEntity(schemaType *ast.Definition) ([]*ast.Directive, bool) { switch schemaType.Kind { case ast.Object: - keys := schemaType.Directives.ForNames("key") + keys := schemaType.Directives.ForNames(dirNameKey) if len(keys) > 0 { return keys, true } case ast.Interface: - keys := schemaType.Directives.ForNames("key") + keys := schemaType.Directives.ForNames(dirNameKey) if len(keys) > 0 { return keys, true } @@ -577,3 +567,146 @@ func isFederatedEntity(schemaType *ast.Definition) ([]*ast.Directive, bool) { } return nil, false } + +func (f *Federation) generateExplicitRequires( + data *codegen.Data, + requiresEntities map[string]*Entity, + requiresImports map[string]bool, +) error { + // check for existing requires functions + type Populator struct { + FuncName string + Exists bool + Comment string + Implementation string + Entity *Entity + } + populators := make([]Populator, 0) + + rewriter, err := rewrite.New(data.Config.Federation.Dir()) + if err != nil { + return err + } + + for name, entity := range requiresEntities { + populator := Populator{ + FuncName: fmt.Sprintf("Populate%sRequires", name), + Entity: entity, + } + + populator.Comment = strings.TrimSpace(strings.TrimLeft(rewriter.GetMethodComment("executionContext", populator.FuncName), `\`)) + populator.Implementation = strings.TrimSpace(rewriter.GetMethodBody("executionContext", populator.FuncName)) + + if populator.Implementation == "" { + populator.Exists = false + populator.Implementation = fmt.Sprintf("panic(fmt.Errorf(\"not implemented: %v\"))", populator.FuncName) + } + populators = append(populators, populator) + } + + sort.Slice(populators, func(i, j int) bool { + return populators[i].FuncName < populators[j].FuncName + }) + + requiresFile := data.Config.Federation.Dir() + "/federation.requires.go" + existingImports := rewriter.ExistingImports(requiresFile) + for _, imp := range existingImports { + if imp.Alias == "" { + // import exists in both places, remove + delete(requiresImports, imp.ImportPath) + } + } + + for k := range requiresImports { + existingImports = append(existingImports, rewrite.Import{ImportPath: k}) + } + + // render requires populators + return templates.Render(templates.Options{ + PackageName: data.Config.Federation.Package, + Filename: requiresFile, + Data: struct { + Federation + ExistingImports []rewrite.Import + Populators []Populator + OriginalSource string + }{*f, existingImports, populators, ""}, + GeneratedHeader: false, + Packages: data.Config.Packages, + Template: explicitRequiresTemplate, + }) +} + +func buildResolverSDL( + resolver *EntityResolver, + multi bool, +) (resolverSDL, entityResolverInputSDL string) { + if multi { + entityResolverInputSDL = buildEntityResolverInputDefinitionSDL(resolver) + resolverSDL := fmt.Sprintf("\t%s(reps: [%s]!): [%s]", resolver.ResolverName, resolver.InputTypeName, resolver.ReturnTypeName) + return resolverSDL, entityResolverInputSDL + } + + resolverArgs := "" + for _, keyField := range resolver.KeyFields { + resolverArgs += fmt.Sprintf("%s: %s,", keyField.Field.ToGoPrivate(), keyField.Definition.Type.String()) + } + resolverSDL = fmt.Sprintf("\t%s(%s): %s!", resolver.ResolverName, resolverArgs, resolver.ReturnTypeName) + return resolverSDL, "" +} + +func buildEntityResolverInputDefinitionSDL(resolver *EntityResolver) string { + entityResolverInputDefinition := "input " + resolver.InputTypeName + " {\n" + for _, keyField := range resolver.KeyFields { + entityResolverInputDefinition += fmt.Sprintf( + "\t%s: %s\n", + keyField.Field.ToGo(), + keyField.Definition.Type.String(), + ) + } + return entityResolverInputDefinition + "}" +} + +func (f *Federation) addMapType(cfg *config.Config) { + cfg.Models[mapTypeName] = config.TypeMapEntry{ + Model: config.StringList{"github.com/99designs/gqlgen/graphql.Map"}, + } + cfg.Schema.Types[mapTypeName] = &ast.Definition{ + Kind: ast.Scalar, + Name: mapTypeName, + Description: "Maps an arbitrary GraphQL value to a map[string]any Go type.", + } +} + +func (f *Federation) mutateSchemaForRequires( + schema *ast.Schema, + cfg *config.Config, +) { + for _, schemaType := range schema.Types { + for _, field := range schemaType.Fields { + if dir := field.Directives.ForName(dirNameRequires); dir != nil { + // ensure we always generate a resolver for any @requires field + model := cfg.Models[schemaType.Name] + fieldConfig := model.Fields[field.Name] + fieldConfig.Resolver = true + if model.Fields == nil { + model.Fields = make(map[string]config.TypeMapField) + } + model.Fields[field.Name] = fieldConfig + cfg.Models[schemaType.Name] = model + + requiresArgument := &ast.ArgumentDefinition{ + Name: fieldArgRequires, + Type: ast.NamedType(mapTypeName, nil), + Directives: ast.DirectiveList{ + { + Name: dirNamePopulateFromRepresentations, + Definition: dirPopulateFromRepresentations, + }, + }, + } + field.Arguments = append(field.Arguments, requiresArgument) + } + } + } +} diff --git a/vendor/github.com/99designs/gqlgen/plugin/federation/federation.gotpl b/vendor/github.com/99designs/gqlgen/plugin/federation/federation.gotpl index 119bab5b..fdb40a6a 100644 --- a/vendor/github.com/99designs/gqlgen/plugin/federation/federation.gotpl +++ b/vendor/github.com/99designs/gqlgen/plugin/federation/federation.gotpl @@ -36,15 +36,50 @@ func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime. func (ec *executionContext) __resolve_entities(ctx context.Context, representations []map[string]interface{}) []fedruntime.Entity { list := make([]fedruntime.Entity, len(representations)) - repsMap := map[string]struct { - i []int - r []map[string]interface{} - }{} + repsMap := ec.buildRepresentationGroups(ctx, representations) + + switch len(repsMap) { + case 0: + return list + case 1: + for typeName, reps := range repsMap { + ec.resolveEntityGroup(ctx, typeName, reps, list) + } + return list + default: + var g sync.WaitGroup + g.Add(len(repsMap)) + for typeName, reps := range repsMap { + go func(typeName string, reps []EntityWithIndex) { + ec.resolveEntityGroup(ctx, typeName, reps, list) + g.Done() + }(typeName, reps) + } + g.Wait() + return list + } +} + +type EntityWithIndex struct { + // The index in the original representation array + index int + entity EntityRepresentation +} + +// EntityRepresentation is the JSON representation of an entity sent by the Router +// used as the inputs for us to resolve. +// +// We make it a map because we know the top level JSON is always an object. +type EntityRepresentation map[string]any // We group entities by typename so that we can parallelize their resolution. // This is particularly helpful when there are entity groups in multi mode. - buildRepresentationGroups := func(reps []map[string]interface{}) { - for i, rep := range reps { +func (ec *executionContext) buildRepresentationGroups( + ctx context.Context, + representations []map[string]any, +) map[string][]EntityWithIndex { + repsMap := make(map[string][]EntityWithIndex) + for i, rep := range representations { typeName, ok := rep["__typename"].(string) if !ok { // If there is no __typename, we just skip the representation; @@ -53,14 +88,48 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati continue } - _r := repsMap[typeName] - _r.i = append(_r.i, i) - _r.r = append(_r.r, rep) - repsMap[typeName] = _r + repsMap[typeName] = append(repsMap[typeName], EntityWithIndex{ + index: i, + entity: rep, + }) + } + + return repsMap +} + +func (ec *executionContext) resolveEntityGroup( + ctx context.Context, + typeName string, + reps []EntityWithIndex, + list []fedruntime.Entity, +) { + if isMulti(typeName) { + err := ec.resolveManyEntities(ctx, typeName, reps, list) + if err != nil { + ec.Error(ctx, err) + } + } else { + // if there are multiple entities to resolve, parallelize (similar to + // graphql.FieldSet.Dispatch) + var e sync.WaitGroup + e.Add(len(reps)) + for i, rep := range reps { + i, rep := i, rep + go func(i int, rep EntityWithIndex) { + entity, err := ec.resolveEntity(ctx, typeName, rep.entity) + if err != nil { + ec.Error(ctx, err) + } else { + list[rep.index] = entity + } + e.Done() + }(i, rep) } + e.Wait() } +} - isMulti := func(typeName string) bool { +func isMulti(typeName string) bool { switch typeName { {{- range .Entities -}} {{- if .Resolvers -}} @@ -75,7 +144,11 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati } } - resolveEntity := func(ctx context.Context, typeName string, rep map[string]interface{}, idx []int, i int) (err error) { +func (ec *executionContext) resolveEntity( + ctx context.Context, + typeName string, + rep EntityRepresentation, +) (e fedruntime.Entity, err error) { // we need to do our own panic handling, because we may be called in a // goroutine, where the usual panic handling can't catch us defer func () { @@ -90,45 +163,51 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati case "{{.Def.Name}}": resolverName, err := entityResolverNameFor{{.Def.Name}}(ctx, rep) if err != nil { - return fmt.Errorf(`finding resolver for Entity "{{.Def.Name}}": %w`, err) + return nil, fmt.Errorf(`finding resolver for Entity "{{.Def.Name}}": %w`, err) } switch resolverName { {{ range $i, $resolver := .Resolvers }} case "{{.ResolverName}}": {{- range $j, $keyField := .KeyFields }} - id{{$j}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"]) + id{{$j}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"]) if err != nil { - return fmt.Errorf(`unmarshalling param {{$j}} for {{$resolver.ResolverName}}(): %w`, err) + return nil, fmt.Errorf(`unmarshalling param {{$j}} for {{$resolver.ResolverName}}(): %w`, err) } {{- end}} entity, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx, {{- range $j, $_ := .KeyFields -}} id{{$j}}, {{end}}) if err != nil { - return fmt.Errorf(`resolving Entity "{{$entity.Def.Name}}": %w`, err) + return nil, fmt.Errorf(`resolving Entity "{{$entity.Def.Name}}": %w`, err) } - {{ if and (index $options "explicit_requires") $entity.Requires }} + {{- if $options.ComputedRequires }} + {{/* We don't do anything in this case, computed requires are handled by standard resolvers */}} + {{- else if and $options.ExplicitRequires $entity.Requires }} err = ec.Populate{{$entity.Def.Name}}Requires(ctx, {{- if (not $usePointers) -}}&{{- end -}}entity, rep) if err != nil { - return fmt.Errorf(`populating requires for Entity "{{$entity.Def.Name}}": %w`, err) + return nil, fmt.Errorf(`populating requires for Entity "{{$entity.Def.Name}}": %w`, err) } {{- else }} {{ range $entity.Requires }} entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"]) if err != nil { - return err + return nil, err } {{- end }} {{- end }} - list[idx[i]] = entity - return nil + return entity, nil {{- end }} } {{ end }} {{- end }} } - return fmt.Errorf("%w: %s", ErrUnknownType, typeName) + return nil, fmt.Errorf("%w: %s", ErrUnknownType, typeName) } - resolveManyEntities := func(ctx context.Context, typeName string, reps []map[string]interface{}, idx []int) (err error) { +func (ec *executionContext) resolveManyEntities( + ctx context.Context, + typeName string, + reps []EntityWithIndex, + list []fedruntime.Entity, +) (err error) { // we need to do our own panic handling, because we may be called in a // goroutine, where the usual panic handling can't catch us defer func () { @@ -141,43 +220,43 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati {{ range $_, $entity := .Entities }} {{ if and .Resolvers .Multi -}} case "{{.Def.Name}}": - resolverName, err := entityResolverNameFor{{.Def.Name}}(ctx, reps[0]) + resolverName, err := entityResolverNameFor{{.Def.Name}}(ctx, reps[0].entity) if err != nil { return fmt.Errorf(`finding resolver for Entity "{{.Def.Name}}": %w`, err) } switch resolverName { {{ range $i, $resolver := .Resolvers }} case "{{.ResolverName}}": - _reps := make([]*{{.LookupInputType}}, len(reps)) + typedReps := make([]*{{.LookupInputType}}, len(reps)) for i, rep := range reps { {{ range $i, $keyField := .KeyFields -}} - id{{$i}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep["{{.Field.Join `"].(map[string]interface{})["`}}"]) + id{{$i}}, err := ec.{{.Type.UnmarshalFunc}}(ctx, rep.entity["{{.Field.Join `"].(map[string]interface{})["`}}"]) if err != nil { return errors.New(fmt.Sprintf("Field %s undefined in schema.", "{{.Definition.Name}}")) } {{end}} - _reps[i] = &{{.LookupInputType}} { + typedReps[i] = &{{.LookupInputType}} { {{ range $i, $keyField := .KeyFields -}} {{$keyField.Field.ToGo}}: id{{$i}}, {{end}} } } - entities, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx, _reps) + entities, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx, typedReps) if err != nil { return err } for i, entity := range entities { {{- range $entity.Requires }} - entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, reps[i]["{{.Field.Join `"].(map[string]interface{})["`}}"]) + entity.{{.Field.JoinGo `.`}}, err = ec.{{.Type.UnmarshalFunc}}(ctx, reps[i].entity["{{.Field.Join `"].(map[string]interface{})["`}}"]) if err != nil { return err } {{- end}} - list[idx[i]] = entity + list[reps[i].index] = entity } return nil {{ end }} @@ -188,54 +267,6 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati {{- end }} default: return errors.New("unknown type: "+typeName) - } - } - - resolveEntityGroup := func(typeName string, reps []map[string]interface{}, idx []int) { - if isMulti(typeName) { - err := resolveManyEntities(ctx, typeName, reps, idx) - if err != nil { - ec.Error(ctx, err) - } - } else { - // if there are multiple entities to resolve, parallelize (similar to - // graphql.FieldSet.Dispatch) - var e sync.WaitGroup - e.Add(len(reps)) - for i, rep := range reps { - i, rep := i, rep - go func(i int, rep map[string]interface{}) { - err := resolveEntity(ctx, typeName, rep, idx, i) - if err != nil { - ec.Error(ctx, err) - } - e.Done() - }(i, rep) - } - e.Wait() - } - } - buildRepresentationGroups(representations) - - switch len(repsMap) { - case 0: - return list - case 1: - for typeName, reps := range repsMap { - resolveEntityGroup(typeName, reps.r, reps.i) - } - return list - default: - var g sync.WaitGroup - g.Add(len(repsMap)) - for typeName, reps := range repsMap { - go func(typeName string, reps []map[string]interface{}, idx []int) { - resolveEntityGroup(typeName, reps, idx) - g.Done() - }(typeName, reps.r, reps.i) - } - g.Wait() - return list } } @@ -244,13 +275,13 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati {{ range $_, $entity := .Entities }} {{- if .Resolvers }} - func entityResolverNameFor{{$entity.Name}}(ctx context.Context, rep map[string]interface{}) (string, error) { + func entityResolverNameFor{{$entity.Name}}(ctx context.Context, rep EntityRepresentation) (string, error) { {{- range .Resolvers }} for { var ( - m map[string]interface{} + m EntityRepresentation val interface{} - ok bool + ok bool ) _ = val // if all of the KeyFields values for this resolver are null, diff --git a/vendor/github.com/99designs/gqlgen/plugin/federation/readme.md b/vendor/github.com/99designs/gqlgen/plugin/federation/readme.md index d5dd0628..e8888c1c 100644 --- a/vendor/github.com/99designs/gqlgen/plugin/federation/readme.md +++ b/vendor/github.com/99designs/gqlgen/plugin/federation/readme.md @@ -18,7 +18,7 @@ TODO(miguel): add details. # Entity resolvers - GetMany entities -The federation plugin implements `GetMany` semantics in which entity resolvers get the entire list of representations that need to be resolved. This functionality is currently optin tho, and to enable it you need to specify the directive `@entityResolver` in the federated entity you want this feature for. E.g. +The federation plugin implements `GetMany` semantics in which entity resolvers get the entire list of representations that need to be resolved. This functionality is currently option tho, and to enable it you need to specify the directive `@entityResolver` in the federated entity you want this feature for. E.g. ``` directive @entityResolver(multi: Boolean) on OBJECT @@ -39,4 +39,4 @@ func (r *entityResolver) FindManyMultiHellosByName(ctx context.Context, reps []* ``` **Note:** -If you are using `omit_slice_element_pointers: true` option in your config yaml, your `GetMany` resolver will still generate in the example above the same signature `FindManyMultiHellosByName(ctx context.Context, reps []*generated.ManyMultiHellosByNameInput) ([]*generated.MultiHello, error)`. But all other instances will continue to honor `omit_slice_element_pointers: true` \ No newline at end of file +If you are using `omit_slice_element_pointers: true` option in your config yaml, your `GetMany` resolver will still generate in the example above the same signature `FindManyMultiHellosByName(ctx context.Context, reps []*generated.ManyMultiHellosByNameInput) ([]*generated.MultiHello, error)`. But all other instances will continue to honor `omit_slice_element_pointers: true` diff --git a/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.go b/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.go index 5f6ce94e..660e3537 100644 --- a/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.go +++ b/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.go @@ -26,11 +26,6 @@ type ( // DefaultFieldMutateHook is the default hook for the Plugin which applies the GoFieldHook and GoTagFieldHook. func DefaultFieldMutateHook(td *ast.Definition, fd *ast.FieldDefinition, f *Field) (*Field, error) { - var err error - f, err = GoFieldHook(td, fd, f) - if err != nil { - return f, err - } return GoTagFieldHook(td, fd, f) } @@ -337,117 +332,139 @@ func (m *Plugin) generateFields(cfg *config.Config, schemaType *ast.Definition) binder := cfg.NewBinder() fields := make([]*Field, 0) - var omittableType types.Type - for _, field := range schemaType.Fields { - var typ types.Type - fieldDef := cfg.Schema.Types[field.Type.Name()] - - if cfg.Models.UserDefined(field.Type.Name()) { - var err error - typ, err = binder.FindTypeFromName(cfg.Models[field.Type.Name()].Model[0]) - if err != nil { - return nil, err - } - } else { - switch fieldDef.Kind { - case ast.Scalar: - // no user defined model, referencing a default scalar - typ = types.NewNamed( - types.NewTypeName(0, cfg.Model.Pkg(), "string", nil), - nil, - nil, - ) - - case ast.Interface, ast.Union: - // no user defined model, referencing a generated interface type - typ = types.NewNamed( - types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil), - types.NewInterfaceType([]*types.Func{}, []types.Type{}), - nil, - ) - - case ast.Enum: - // no user defined model, must reference a generated enum - typ = types.NewNamed( - types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil), - nil, - nil, - ) - - case ast.Object, ast.InputObject: - // no user defined model, must reference a generated struct - typ = types.NewNamed( - types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil), - types.NewStruct(nil, nil), - nil, - ) - - default: - panic(fmt.Errorf("unknown ast type %s", fieldDef.Kind)) - } + f, err := m.generateField(cfg, binder, schemaType, field) + if err != nil { + return nil, err } - name := templates.ToGo(field.Name) - if nameOveride := cfg.Models[schemaType.Name].Fields[field.Name].FieldName; nameOveride != "" { - name = nameOveride + if f == nil { + continue } - typ = binder.CopyModifiersFromAst(field.Type, typ) + fields = append(fields, f) + } - if cfg.StructFieldsAlwaysPointers { - if isStruct(typ) && (fieldDef.Kind == ast.Object || fieldDef.Kind == ast.InputObject) { - typ = types.NewPointer(typ) - } + fields = append(fields, getExtraFields(cfg, schemaType.Name)...) + + return fields, nil +} + +func (m *Plugin) generateField( + cfg *config.Config, + binder *config.Binder, + schemaType *ast.Definition, + field *ast.FieldDefinition, +) (*Field, error) { + var omittableType types.Type + var typ types.Type + fieldDef := cfg.Schema.Types[field.Type.Name()] + + if cfg.Models.UserDefined(field.Type.Name()) { + var err error + typ, err = binder.FindTypeFromName(cfg.Models[field.Type.Name()].Model[0]) + if err != nil { + return nil, err } + } else { + switch fieldDef.Kind { + case ast.Scalar: + // no user defined model, referencing a default scalar + typ = types.NewNamed( + types.NewTypeName(0, cfg.Model.Pkg(), "string", nil), + nil, + nil, + ) + + case ast.Interface, ast.Union: + // no user defined model, referencing a generated interface type + typ = types.NewNamed( + types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil), + types.NewInterfaceType([]*types.Func{}, []types.Type{}), + nil, + ) + + case ast.Enum: + // no user defined model, must reference a generated enum + typ = types.NewNamed( + types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil), + nil, + nil, + ) - f := &Field{ - Name: field.Name, - GoName: name, - Type: typ, - Description: field.Description, - Tag: getStructTagFromField(cfg, field), - Omittable: cfg.NullableInputOmittable && schemaType.Kind == ast.InputObject && !field.Type.NonNull, + case ast.Object, ast.InputObject: + // no user defined model, must reference a generated struct + typ = types.NewNamed( + types.NewTypeName(0, cfg.Model.Pkg(), templates.ToGo(field.Type.Name()), nil), + types.NewStruct(nil, nil), + nil, + ) + + default: + panic(fmt.Errorf("unknown ast type %s", fieldDef.Kind)) } + } - if m.FieldHook != nil { - mf, err := m.FieldHook(schemaType, field, f) - if err != nil { - return nil, fmt.Errorf("generror: field %v.%v: %w", schemaType.Name, field.Name, err) - } - f = mf + name := templates.ToGo(field.Name) + if nameOverride := cfg.Models[schemaType.Name].Fields[field.Name].FieldName; nameOverride != "" { + name = nameOverride + } + + typ = binder.CopyModifiersFromAst(field.Type, typ) + + if cfg.StructFieldsAlwaysPointers { + if isStruct(typ) && (fieldDef.Kind == ast.Object || fieldDef.Kind == ast.InputObject) { + typ = types.NewPointer(typ) } + } - if f.IsResolver && cfg.OmitResolverFields { - continue + f := &Field{ + Name: field.Name, + GoName: name, + Type: typ, + Description: field.Description, + Tag: getStructTagFromField(cfg, field), + Omittable: cfg.NullableInputOmittable && schemaType.Kind == ast.InputObject && !field.Type.NonNull, + IsResolver: cfg.Models[schemaType.Name].Fields[field.Name].Resolver, + } + + if omittable := cfg.Models[schemaType.Name].Fields[field.Name].Omittable; omittable != nil { + f.Omittable = *omittable + } + + if m.FieldHook != nil { + mf, err := m.FieldHook(schemaType, field, f) + if err != nil { + return nil, fmt.Errorf("generror: field %v.%v: %w", schemaType.Name, field.Name, err) } + f = mf + } - if f.Omittable { - if schemaType.Kind != ast.InputObject || field.Type.NonNull { - return nil, fmt.Errorf("generror: field %v.%v: omittable is only applicable to nullable input fields", schemaType.Name, field.Name) - } + if f.IsResolver && cfg.OmitResolverFields { + return nil, nil + } - var err error + if f.Omittable { + if schemaType.Kind != ast.InputObject || field.Type.NonNull { + return nil, fmt.Errorf("generror: field %v.%v: omittable is only applicable to nullable input fields", schemaType.Name, field.Name) + } - if omittableType == nil { - omittableType, err = binder.FindTypeFromName("github.com/99designs/gqlgen/graphql.Omittable") - if err != nil { - return nil, err - } - } + var err error - f.Type, err = binder.InstantiateType(omittableType, []types.Type{f.Type}) + if omittableType == nil { + omittableType, err = binder.FindTypeFromName("github.com/99designs/gqlgen/graphql.Omittable") if err != nil { - return nil, fmt.Errorf("generror: field %v.%v: %w", schemaType.Name, field.Name, err) + return nil, err } } - fields = append(fields, f) + f.Type, err = binder.InstantiateType(omittableType, []types.Type{f.Type}) + if err != nil { + return nil, fmt.Errorf("generror: field %v.%v: %w", schemaType.Name, field.Name, err) + } } - fields = append(fields, getExtraFields(cfg, schemaType.Name)...) - - return fields, nil + return f, nil } func getExtraFields(cfg *config.Config, modelName string) []*Field { @@ -636,29 +653,9 @@ func removeDuplicateTags(t string) string { return returnTags } -// GoFieldHook applies the goField directive to the generated Field f. +// GoFieldHook is a noop +// TODO: This will be removed in the next breaking release func GoFieldHook(td *ast.Definition, fd *ast.FieldDefinition, f *Field) (*Field, error) { - args := make([]string, 0) - _ = args - for _, goField := range fd.Directives.ForNames("goField") { - if arg := goField.Arguments.ForName("name"); arg != nil { - if k, err := arg.Value.Value(nil); err == nil { - f.GoName = k.(string) - } - } - - if arg := goField.Arguments.ForName("forceResolver"); arg != nil { - if k, err := arg.Value.Value(nil); err == nil { - f.IsResolver = k.(bool) - } - } - - if arg := goField.Arguments.ForName("omittable"); arg != nil { - if k, err := arg.Value.Value(nil); err == nil { - f.Omittable = k.(bool) - } - } - } return f, nil } diff --git a/vendor/github.com/99designs/gqlgen/plugin/plugin.go b/vendor/github.com/99designs/gqlgen/plugin/plugin.go index a5e1ba84..6cc4f6da 100644 --- a/vendor/github.com/99designs/gqlgen/plugin/plugin.go +++ b/vendor/github.com/99designs/gqlgen/plugin/plugin.go @@ -22,11 +22,18 @@ type CodeGenerator interface { } // EarlySourceInjector is used to inject things that are required for user schema files to compile. +// Deprecated: Use EarlySourcesInjector instead type EarlySourceInjector interface { InjectSourceEarly() *ast.Source } +// EarlySourcesInjector is used to inject things that are required for user schema files to compile. +type EarlySourcesInjector interface { + InjectSourcesEarly() ([]*ast.Source, error) +} + // LateSourceInjector is used to inject more sources, after we have loaded the users schema. +// Deprecated: Use LateSourcesInjector instead type LateSourceInjector interface { InjectSourceLate(schema *ast.Schema) *ast.Source } @@ -35,3 +42,8 @@ type LateSourceInjector interface { type ResolverImplementer interface { Implement(prevImplementation string, field *codegen.Field) string } + +// LateSourcesInjector is used to inject more sources, after we have loaded the users schema. +type LateSourcesInjector interface { + InjectSourcesLate(schema *ast.Schema) ([]*ast.Source, error) +} diff --git a/vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.go b/vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.go index 38138d52..c0e04089 100644 --- a/vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.go +++ b/vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.go @@ -53,26 +53,44 @@ func (m *Plugin) GenerateCode(data *codegen.Data) error { func (m *Plugin) generateSingleFile(data *codegen.Data) error { file := File{} - - if _, err := os.Stat(data.Config.Resolver.Filename); err == nil { - // file already exists and we do not support updating resolvers with layout = single so just return - return nil + rewriter, err := rewrite.New(data.Config.Resolver.Dir()) + if err != nil { + return err } for _, o := range data.Objects { if o.HasResolvers() { + caser := cases.Title(language.English, cases.NoLower) + rewriter.MarkStructCopied(templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type)) + rewriter.GetMethodBody(data.Config.Resolver.Type, caser.String(o.Name)) + file.Objects = append(file.Objects, o) } + for _, f := range o.Fields { if !f.IsResolver { continue } - resolver := Resolver{o, f, nil, "", `panic("not implemented")`, nil} - file.Resolvers = append(file.Resolvers, &resolver) + structName := templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type) + comment := strings.TrimSpace(strings.TrimLeft(rewriter.GetMethodComment(structName, f.GoFieldName), `\`)) + implementation := strings.TrimSpace(rewriter.GetMethodBody(structName, f.GoFieldName)) + if implementation != "" { + resolver := Resolver{o, f, rewriter.GetPrevDecl(structName, f.GoFieldName), comment, implementation, nil} + file.Resolvers = append(file.Resolvers, &resolver) + } else { + resolver := Resolver{o, f, nil, "", `panic("not implemented")`, nil} + file.Resolvers = append(file.Resolvers, &resolver) + } } } + if _, err := os.Stat(data.Config.Resolver.Filename); err == nil { + file.name = data.Config.Resolver.Filename + file.imports = rewriter.ExistingImports(file.name) + file.RemainingSource = rewriter.RemainingSource(file.name) + } + resolverBuild := &ResolverBuild{ File: &file, PackageName: data.Config.Resolver.Package, @@ -88,7 +106,7 @@ func (m *Plugin) generateSingleFile(data *codegen.Data) error { return templates.Render(templates.Options{ PackageName: data.Config.Resolver.Package, - FileNotice: `// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.`, + FileNotice: `// THIS CODE WILL BE UPDATED WITH SCHEMA CHANGES. PREVIOUS IMPLEMENTATION FOR SCHEMA CHANGES WILL BE KEPT IN THE COMMENT SECTION. IMPLEMENTATION FOR UNCHANGED SCHEMA WILL BE KEPT.`, Filename: data.Config.Resolver.Filename, Data: resolverBuild, Packages: data.Config.Packages, diff --git a/vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.gotpl b/vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.gotpl index c25bd1d5..ad6c1085 100644 --- a/vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.gotpl +++ b/vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.gotpl @@ -48,5 +48,7 @@ // - When renaming or deleting a resolver the old code will be put in here. You can safely delete // it when you're done. // - You have helper methods in this file. Move them out to keep these resolver files clean. + /* {{ .RemainingSource }} + */ {{ end }} diff --git a/vendor/github.com/Masterminds/semver/v3/CHANGELOG.md b/vendor/github.com/Masterminds/semver/v3/CHANGELOG.md index f1262642..f95a504f 100644 --- a/vendor/github.com/Masterminds/semver/v3/CHANGELOG.md +++ b/vendor/github.com/Masterminds/semver/v3/CHANGELOG.md @@ -1,5 +1,33 @@ # Changelog +## 3.3.0 (2024-08-27) + +### Added + +- #238: Add LessThanEqual and GreaterThanEqual functions (thanks @grosser) +- #213: nil version equality checking (thanks @KnutZuidema) + +### Changed + +- #241: Simplify StrictNewVersion parsing (thanks @grosser) +- Testing support up through Go 1.23 +- Minimum version set to 1.21 as this is what's tested now +- Fuzz testing now supports caching + +## 3.2.1 (2023-04-10) + +### Changed + +- #198: Improved testing around pre-release names +- #200: Improved code scanning with addition of CodeQL +- #201: Testing now includes Go 1.20. Go 1.17 has been dropped +- #202: Migrated Fuzz testing to Go built-in Fuzzing. CI runs daily +- #203: Docs updated for security details + +### Fixed + +- #199: Fixed issue with range transformations + ## 3.2.0 (2022-11-28) ### Added diff --git a/vendor/github.com/Masterminds/semver/v3/Makefile b/vendor/github.com/Masterminds/semver/v3/Makefile index 0e7b5c71..9ca87a2c 100644 --- a/vendor/github.com/Masterminds/semver/v3/Makefile +++ b/vendor/github.com/Masterminds/semver/v3/Makefile @@ -19,6 +19,7 @@ test-cover: .PHONY: fuzz fuzz: @echo "==> Running Fuzz Tests" + go env GOCACHE go test -fuzz=FuzzNewVersion -fuzztime=15s . go test -fuzz=FuzzStrictNewVersion -fuzztime=15s . go test -fuzz=FuzzNewConstraint -fuzztime=15s . @@ -27,4 +28,4 @@ $(GOLANGCI_LINT): # Install golangci-lint. The configuration for it is in the .golangci.yml # file in the root of the repository echo ${GOPATH} - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.17.1 + curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.56.2 diff --git a/vendor/github.com/Masterminds/semver/v3/README.md b/vendor/github.com/Masterminds/semver/v3/README.md index eab8cac3..ed569360 100644 --- a/vendor/github.com/Masterminds/semver/v3/README.md +++ b/vendor/github.com/Masterminds/semver/v3/README.md @@ -13,12 +13,9 @@ Active](https://masterminds.github.io/stability/active.svg)](https://masterminds [![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/github.com/Masterminds/semver/v3) [![Go Report Card](https://goreportcard.com/badge/github.com/Masterminds/semver)](https://goreportcard.com/report/github.com/Masterminds/semver) -If you are looking for a command line tool for version comparisons please see -[vert](https://github.com/Masterminds/vert) which uses this library. - ## Package Versions -Note, import `github.com/github.com/Masterminds/semver/v3` to use the latest version. +Note, import `github.com/Masterminds/semver/v3` to use the latest version. There are three major versions fo the `semver` package. @@ -80,12 +77,12 @@ There are two methods for comparing versions. One uses comparison methods on differences to notes between these two methods of comparison. 1. When two versions are compared using functions such as `Compare`, `LessThan`, - and others it will follow the specification and always include prereleases + and others it will follow the specification and always include pre-releases within the comparison. It will provide an answer that is valid with the comparison section of the spec at https://semver.org/#spec-item-11 2. When constraint checking is used for checks or validation it will follow a different set of rules that are common for ranges with tools like npm/js - and Rust/Cargo. This includes considering prereleases to be invalid if the + and Rust/Cargo. This includes considering pre-releases to be invalid if the ranges does not include one. If you want to have it include pre-releases a simple solution is to include `-0` in your range. 3. Constraint ranges can have some complex rules including the shorthand use of @@ -113,7 +110,7 @@ v, err := semver.NewVersion("1.3") if err != nil { // Handle version not being parsable. } -// Check if the version meets the constraints. The a variable will be true. +// Check if the version meets the constraints. The variable a will be true. a := c.Check(v) ``` @@ -137,20 +134,20 @@ The basic comparisons are: ### Working With Prerelease Versions Pre-releases, for those not familiar with them, are used for software releases -prior to stable or generally available releases. Examples of prereleases include -development, alpha, beta, and release candidate releases. A prerelease may be +prior to stable or generally available releases. Examples of pre-releases include +development, alpha, beta, and release candidate releases. A pre-release may be a version such as `1.2.3-beta.1` while the stable release would be `1.2.3`. In the -order of precedence, prereleases come before their associated releases. In this +order of precedence, pre-releases come before their associated releases. In this example `1.2.3-beta.1 < 1.2.3`. -According to the Semantic Version specification prereleases may not be +According to the Semantic Version specification, pre-releases may not be API compliant with their release counterpart. It says, > A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version. -SemVer comparisons using constraints without a prerelease comparator will skip -prerelease versions. For example, `>=1.2.3` will skip prereleases when looking -at a list of releases while `>=1.2.3-0` will evaluate and find prereleases. +SemVer's comparisons using constraints without a pre-release comparator will skip +pre-release versions. For example, `>=1.2.3` will skip pre-releases when looking +at a list of releases while `>=1.2.3-0` will evaluate and find pre-releases. The reason for the `0` as a pre-release version in the example comparison is because pre-releases can only contain ASCII alphanumerics and hyphens (along with @@ -171,6 +168,9 @@ These look like: * `1.2 - 1.4.5` which is equivalent to `>= 1.2 <= 1.4.5` * `2.3.4 - 4.5` which is equivalent to `>= 2.3.4 <= 4.5` +Note that `1.2-1.4.5` without whitespace is parsed completely differently; it's +parsed as a single constraint `1.2.0` with _prerelease_ `1.4.5`. + ### Wildcards In Comparisons The `x`, `X`, and `*` characters can be used as a wildcard character. This works diff --git a/vendor/github.com/Masterminds/semver/v3/version.go b/vendor/github.com/Masterminds/semver/v3/version.go index 7c4bed33..ff499fb6 100644 --- a/vendor/github.com/Masterminds/semver/v3/version.go +++ b/vendor/github.com/Masterminds/semver/v3/version.go @@ -83,22 +83,23 @@ func StrictNewVersion(v string) (*Version, error) { original: v, } - // check for prerelease or build metadata - var extra []string - if strings.ContainsAny(parts[2], "-+") { - // Start with the build metadata first as it needs to be on the right - extra = strings.SplitN(parts[2], "+", 2) - if len(extra) > 1 { - // build metadata found - sv.metadata = extra[1] - parts[2] = extra[0] + // Extract build metadata + if strings.Contains(parts[2], "+") { + extra := strings.SplitN(parts[2], "+", 2) + sv.metadata = extra[1] + parts[2] = extra[0] + if err := validateMetadata(sv.metadata); err != nil { + return nil, err } + } - extra = strings.SplitN(parts[2], "-", 2) - if len(extra) > 1 { - // prerelease found - sv.pre = extra[1] - parts[2] = extra[0] + // Extract build prerelease + if strings.Contains(parts[2], "-") { + extra := strings.SplitN(parts[2], "-", 2) + sv.pre = extra[1] + parts[2] = extra[0] + if err := validatePrerelease(sv.pre); err != nil { + return nil, err } } @@ -114,7 +115,7 @@ func StrictNewVersion(v string) (*Version, error) { } } - // Extract the major, minor, and patch elements onto the returned Version + // Extract major, minor, and patch var err error sv.major, err = strconv.ParseUint(parts[0], 10, 64) if err != nil { @@ -131,23 +132,6 @@ func StrictNewVersion(v string) (*Version, error) { return nil, err } - // No prerelease or build metadata found so returning now as a fastpath. - if sv.pre == "" && sv.metadata == "" { - return sv, nil - } - - if sv.pre != "" { - if err = validatePrerelease(sv.pre); err != nil { - return nil, err - } - } - - if sv.metadata != "" { - if err = validateMetadata(sv.metadata); err != nil { - return nil, err - } - } - return sv, nil } @@ -381,15 +365,31 @@ func (v *Version) LessThan(o *Version) bool { return v.Compare(o) < 0 } +// LessThanEqual tests if one version is less or equal than another one. +func (v *Version) LessThanEqual(o *Version) bool { + return v.Compare(o) <= 0 +} + // GreaterThan tests if one version is greater than another one. func (v *Version) GreaterThan(o *Version) bool { return v.Compare(o) > 0 } +// GreaterThanEqual tests if one version is greater or equal than another one. +func (v *Version) GreaterThanEqual(o *Version) bool { + return v.Compare(o) >= 0 +} + // Equal tests if two versions are equal to each other. // Note, versions can be equal with different metadata since metadata // is not considered part of the comparable version. func (v *Version) Equal(o *Version) bool { + if v == o { + return true + } + if v == nil || o == nil { + return false + } return v.Compare(o) == 0 } diff --git a/vendor/github.com/adhocore/gronx/README.md b/vendor/github.com/adhocore/gronx/README.md index 0b3b78f5..00682ee7 100644 --- a/vendor/github.com/adhocore/gronx/README.md +++ b/vendor/github.com/adhocore/gronx/README.md @@ -47,6 +47,14 @@ gron.IsDue(expr) // true|false, nil gron.IsDue(expr, time.Date(2021, time.April, 1, 1, 1, 0, 0, time.UTC)) // true|false, nil ``` +> Validity can be checked without instantiation: + +```go +import "github.com/adhocore/gronx" + +gronx.IsValid("* * * * *") // true +``` + ### Batch Due Check If you have multiple cron expressions to check due on same reference time use `BatchDue()`: diff --git a/vendor/github.com/adhocore/gronx/gronx.go b/vendor/github.com/adhocore/gronx/gronx.go index 958d8fb1..82c0baa4 100644 --- a/vendor/github.com/adhocore/gronx/gronx.go +++ b/vendor/github.com/adhocore/gronx/gronx.go @@ -75,7 +75,9 @@ func New() *Gronx { // IsDue checks if cron expression is due for given reference time (or now). // It returns bool or error if any. func (g *Gronx) IsDue(expr string, ref ...time.Time) (bool, error) { - ref = append(ref, time.Now()) + if len(ref) == 0 { + ref = append(ref, time.Now()) + } g.C.SetRef(ref[0]) segs, err := Segments(expr) @@ -157,12 +159,16 @@ func (g *Gronx) SegmentsDue(segs []string) (bool, error) { return true, nil } +// IsValid checks if cron expression is valid. +// It returns bool. +func (g *Gronx) IsValid(expr string) bool { return IsValid(expr) } + // checker for validity var checker = &SegmentChecker{ref: time.Now()} // IsValid checks if cron expression is valid. // It returns bool. -func (g *Gronx) IsValid(expr string) bool { +func IsValid(expr string) bool { segs, err := Segments(expr) if err != nil { return false diff --git a/vendor/github.com/adhocore/gronx/validator.go b/vendor/github.com/adhocore/gronx/validator.go index 62c3a363..d7441d4a 100644 --- a/vendor/github.com/adhocore/gronx/validator.go +++ b/vendor/github.com/adhocore/gronx/validator.go @@ -14,7 +14,7 @@ func inStep(val int, s string, bounds []int) (bool, error) { if err != nil { return false, err } - if step == 0 { + if step <= 0 { return false, errors.New("step can't be 0") } diff --git a/vendor/github.com/agnivade/levenshtein/.travis.yml b/vendor/github.com/agnivade/levenshtein/.travis.yml deleted file mode 100644 index 0873fa98..00000000 --- a/vendor/github.com/agnivade/levenshtein/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: go - -# See https://travis-ci.community/t/goos-js-goarch-wasm-go-run-fails-panic-newosproc-not-implemented/1651 -#addons: -# chrome: stable - -before_install: -- export GO111MODULE=on - -#install: -#- go get github.com/agnivade/wasmbrowsertest -#- mv $GOPATH/bin/wasmbrowsertest $GOPATH/bin/go_js_wasm_exec -#- export PATH=$GOPATH/bin:$PATH - -go: -- 1.13.x -- 1.14.x -- 1.15.x -- tip - -script: -#- GOOS=js GOARCH=wasm go test -v -- go test -v diff --git a/vendor/github.com/agnivade/levenshtein/Makefile b/vendor/github.com/agnivade/levenshtein/Makefile index 5f6890d6..3bbda319 100644 --- a/vendor/github.com/agnivade/levenshtein/Makefile +++ b/vendor/github.com/agnivade/levenshtein/Makefile @@ -4,12 +4,10 @@ install: go install lint: - gofmt -l -s -w . && go vet . && golint -set_exit_status=1 . + gofmt -l -s -w . && go vet . -test: # The first 2 go gets are to support older Go versions - go get github.com/arbovm/levenshtein - go get github.com/dgryski/trifles/leven - GO111MODULE=on go test -race -v -coverprofile=coverage.txt -covermode=atomic +test: + go test -race -v -coverprofile=coverage.txt -covermode=atomic bench: go test -run=XXX -bench=. -benchmem -count=5 diff --git a/vendor/github.com/agnivade/levenshtein/README.md b/vendor/github.com/agnivade/levenshtein/README.md index 13c52a21..34378aab 100644 --- a/vendor/github.com/agnivade/levenshtein/README.md +++ b/vendor/github.com/agnivade/levenshtein/README.md @@ -1,4 +1,4 @@ -levenshtein [![Build Status](https://travis-ci.org/agnivade/levenshtein.svg?branch=master)](https://travis-ci.org/agnivade/levenshtein) [![Go Report Card](https://goreportcard.com/badge/github.com/agnivade/levenshtein)](https://goreportcard.com/report/github.com/agnivade/levenshtein) [![PkgGoDev](https://pkg.go.dev/badge/github.com/agnivade/levenshtein)](https://pkg.go.dev/github.com/agnivade/levenshtein) +levenshtein ![Build Status](https://github.com/agnivade/levenshtein/actions/workflows/ci.yml/badge.svg) [![Go Report Card](https://goreportcard.com/badge/github.com/agnivade/levenshtein)](https://goreportcard.com/report/github.com/agnivade/levenshtein) [![PkgGoDev](https://pkg.go.dev/badge/github.com/agnivade/levenshtein)](https://pkg.go.dev/github.com/agnivade/levenshtein) =========== [Go](http://golang.org) package to calculate the [Levenshtein Distance](http://en.wikipedia.org/wiki/Levenshtein_distance) diff --git a/vendor/github.com/agnivade/levenshtein/levenshtein.go b/vendor/github.com/agnivade/levenshtein/levenshtein.go index f727a66f..861f409d 100644 --- a/vendor/github.com/agnivade/levenshtein/levenshtein.go +++ b/vendor/github.com/agnivade/levenshtein/levenshtein.go @@ -41,6 +41,25 @@ func ComputeDistance(a, b string) int { if len(s1) > len(s2) { s1, s2 = s2, s1 } + + // remove trailing identical runes. + for i := 0; i < len(s1); i++ { + if s1[len(s1)-1-i] != s2[len(s2)-1-i] { + s1 = s1[:len(s1)-i] + s2 = s2[:len(s2)-i] + break + } + } + + // Remove leading identical runes. + for i := 0; i < len(s1); i++ { + if s1[i] != s2[i] { + s1 = s1[i:] + s2 = s2[i:] + break + } + } + lenS1 := len(s1) lenS2 := len(s2) @@ -71,7 +90,7 @@ func ComputeDistance(a, b string) int { for j := 1; j <= lenS1; j++ { current := x[j-1] // match if s2[i-1] != s1[j-1] { - current = min(min(x[j-1]+1, prev+1), x[j]+1) + current = min(x[j-1]+1, prev+1, x[j]+1) } x[j-1] = prev prev = current @@ -80,10 +99,3 @@ func ComputeDistance(a, b string) int { } return int(x[lenS1]) } - -func min(a, b uint16) uint16 { - if a < b { - return a - } - return b -} diff --git a/vendor/github.com/caddyserver/certmagic/account.go b/vendor/github.com/caddyserver/certmagic/account.go index f3b8d44d..0c43ad63 100644 --- a/vendor/github.com/caddyserver/certmagic/account.go +++ b/vendor/github.com/caddyserver/certmagic/account.go @@ -33,6 +33,7 @@ import ( "sync" "github.com/mholt/acmez/v2/acme" + "go.uber.org/zap" ) // getAccount either loads or creates a new account, depending on if @@ -40,8 +41,15 @@ import ( func (am *ACMEIssuer) getAccount(ctx context.Context, ca, email string) (acme.Account, error) { acct, err := am.loadAccount(ctx, ca, email) if errors.Is(err, fs.ErrNotExist) { + am.Logger.Info("creating new account because no account for configured email is known to us", + zap.String("email", email), + zap.String("ca", ca), + zap.Error(err)) return am.newAccount(email) } + am.Logger.Debug("using existing ACME account because key found in storage associated with email", + zap.String("email", email), + zap.String("ca", ca)) return acct, err } @@ -407,6 +415,15 @@ func (am *ACMEIssuer) mostRecentAccountEmail(ctx context.Context, caURL string) return getPrimaryContact(account), true } +func accountRegLockKey(acc acme.Account) string { + key := "register_acme_account" + if len(acc.Contact) == 0 { + return key + } + key += "_" + getPrimaryContact(acc) + return key +} + // getPrimaryContact returns the first contact on the account (if any) // without the scheme. (I guess we assume an email address.) func getPrimaryContact(account acme.Account) string { diff --git a/vendor/github.com/caddyserver/certmagic/acmeclient.go b/vendor/github.com/caddyserver/certmagic/acmeclient.go index 031aaa11..c6e1f6ed 100644 --- a/vendor/github.com/caddyserver/certmagic/acmeclient.go +++ b/vendor/github.com/caddyserver/certmagic/acmeclient.go @@ -50,77 +50,123 @@ func (iss *ACMEIssuer) newACMEClientWithAccount(ctx context.Context, useTestCA, return nil, err } - // look up or create the ACME account - var account acme.Account - if iss.AccountKeyPEM != "" { - account, err = iss.GetAccount(ctx, []byte(iss.AccountKeyPEM)) - } else { - account, err = iss.getAccount(ctx, client.Directory, iss.getEmail()) + // we try loading the account from storage before a potential + // lock, and after obtaining the lock as well, to ensure we don't + // repeat work done by another instance or goroutine + getAccount := func() (acme.Account, error) { + // look up or create the ACME account + var account acme.Account + if iss.AccountKeyPEM != "" { + iss.Logger.Info("using configured ACME account") + account, err = iss.GetAccount(ctx, []byte(iss.AccountKeyPEM)) + } else { + account, err = iss.getAccount(ctx, client.Directory, iss.getEmail()) + } + if err != nil { + return acme.Account{}, fmt.Errorf("getting ACME account: %v", err) + } + return account, nil } + + // first try getting the account + account, err := getAccount() if err != nil { - return nil, fmt.Errorf("getting ACME account: %v", err) + return nil, err } // register account if it is new if account.Status == "" { - if iss.NewAccountFunc != nil { - // obtain lock here, since NewAccountFunc calls happen concurrently and they typically read and change the issuer - iss.mu.Lock() - account, err = iss.NewAccountFunc(ctx, iss, account) - iss.mu.Unlock() - if err != nil { - return nil, fmt.Errorf("account pre-registration callback: %v", err) + iss.Logger.Info("ACME account has empty status; registering account with ACME server", + zap.Strings("contact", account.Contact), + zap.String("location", account.Location)) + + // synchronize this so the account is only created once + acctLockKey := accountRegLockKey(account) + err = acquireLock(ctx, iss.config.Storage, acctLockKey) + if err != nil { + return nil, fmt.Errorf("locking account registration: %v", err) + } + defer func() { + if err := releaseLock(ctx, iss.config.Storage, acctLockKey); err != nil { + iss.Logger.Error("failed to unlock account registration lock", zap.Error(err)) } + }() + + // if we're not the only one waiting for this account, then by this point it should already be registered and in storage; reload it + account, err = getAccount() + if err != nil { + return nil, err } - // agree to terms - if interactive { - if !iss.isAgreed() { - var termsURL string - dir, err := client.GetDirectory(ctx) + // if we are the only or first one waiting for this account, then proceed to register it while we have the lock + if account.Status == "" { + if iss.NewAccountFunc != nil { + // obtain lock here, since NewAccountFunc calls happen concurrently and they typically read and change the issuer + iss.mu.Lock() + account, err = iss.NewAccountFunc(ctx, iss, account) + iss.mu.Unlock() if err != nil { - return nil, fmt.Errorf("getting directory: %w", err) - } - if dir.Meta != nil { - termsURL = dir.Meta.TermsOfService + return nil, fmt.Errorf("account pre-registration callback: %v", err) } - if termsURL != "" { - agreed := iss.askUserAgreement(termsURL) - if !agreed { - return nil, fmt.Errorf("user must agree to CA terms") + } + + // agree to terms + if interactive { + if !iss.isAgreed() { + var termsURL string + dir, err := client.GetDirectory(ctx) + if err != nil { + return nil, fmt.Errorf("getting directory: %w", err) + } + if dir.Meta != nil { + termsURL = dir.Meta.TermsOfService + } + if termsURL != "" { + agreed := iss.askUserAgreement(termsURL) + if !agreed { + return nil, fmt.Errorf("user must agree to CA terms") + } + iss.mu.Lock() + iss.agreed = agreed + iss.mu.Unlock() } - iss.mu.Lock() - iss.agreed = agreed - iss.mu.Unlock() } + } else { + // can't prompt a user who isn't there; they should + // have reviewed the terms beforehand + iss.mu.Lock() + iss.agreed = true + iss.mu.Unlock() } - } else { - // can't prompt a user who isn't there; they should - // have reviewed the terms beforehand - iss.mu.Lock() - iss.agreed = true - iss.mu.Unlock() - } - account.TermsOfServiceAgreed = iss.isAgreed() + account.TermsOfServiceAgreed = iss.isAgreed() - // associate account with external binding, if configured - if iss.ExternalAccount != nil { - err := account.SetExternalAccountBinding(ctx, client.Client, *iss.ExternalAccount) - if err != nil { - return nil, err + // associate account with external binding, if configured + if iss.ExternalAccount != nil { + err := account.SetExternalAccountBinding(ctx, client.Client, *iss.ExternalAccount) + if err != nil { + return nil, err + } } - } - // create account - account, err = client.NewAccount(ctx, account) - if err != nil { - return nil, fmt.Errorf("registering account %v with server: %w", account.Contact, err) - } + // create account + account, err = client.NewAccount(ctx, account) + if err != nil { + return nil, fmt.Errorf("registering account %v with server: %w", account.Contact, err) + } + iss.Logger.Info("new ACME account registered", + zap.Strings("contact", account.Contact), + zap.String("status", account.Status)) - // persist the account to storage - err = iss.saveAccount(ctx, client.Directory, account) - if err != nil { - return nil, fmt.Errorf("could not save account %v: %v", account.Contact, err) + // persist the account to storage + err = iss.saveAccount(ctx, client.Directory, account) + if err != nil { + return nil, fmt.Errorf("could not save account %v: %v", account.Contact, err) + } + } else { + iss.Logger.Info("account has already been registered; reloaded", + zap.Strings("contact", account.Contact), + zap.String("status", account.Status), + zap.String("location", account.Location)) } } diff --git a/vendor/github.com/caddyserver/certmagic/acmeissuer.go b/vendor/github.com/caddyserver/certmagic/acmeissuer.go index 87fa5ff5..e010f087 100644 --- a/vendor/github.com/caddyserver/certmagic/acmeissuer.go +++ b/vendor/github.com/caddyserver/certmagic/acmeissuer.go @@ -461,7 +461,7 @@ func (am *ACMEIssuer) doIssue(ctx context.Context, csr *x509.CertificateRequest, // between client and server or some sort of bookkeeping error with regards to the certID // and the server is rejecting the ARI certID. In any case, an invalid certID may cause // orders to fail. So try once without setting it. - if !usingTestCA && attempts != 2 { + if !am.config.DisableARI && !usingTestCA && attempts != 2 { if replacing, ok := ctx.Value(ctxKeyARIReplaces).(*x509.Certificate); ok { params.Replaces = replacing } diff --git a/vendor/github.com/caddyserver/certmagic/certificates.go b/vendor/github.com/caddyserver/certmagic/certificates.go index a5147a2d..2965712a 100644 --- a/vendor/github.com/caddyserver/certmagic/certificates.go +++ b/vendor/github.com/caddyserver/certmagic/certificates.go @@ -103,53 +103,54 @@ func (cfg *Config) certNeedsRenewal(leaf *x509.Certificate, ari acme.RenewalInfo logger = zap.NewNop() } - // first check ARI: if it says it's time to renew, it's time to renew - // (notice that we don't strictly require an ARI window to also exist; we presume - // that if a time has been selected, a window does or did exist, even if it didn't - // get stored/encoded for some reason - but also: this allows administrators to - // manually or explicitly schedule a renewal time indepedently of ARI which could - // be useful) - selectedTime := ari.SelectedTime - - // if, for some reason a random time in the window hasn't been selected yet, but an ARI - // window does exist, we can always improvise one... even if this is called repeatedly, - // a random time is a random time, whether you generate it once or more :D - // (code borrowed from our acme package) - if selectedTime.IsZero() && - (!ari.SuggestedWindow.Start.IsZero() && !ari.SuggestedWindow.End.IsZero()) { - start, end := ari.SuggestedWindow.Start.Unix()+1, ari.SuggestedWindow.End.Unix() - selectedTime = time.Unix(rand.Int63n(end-start)+start, 0).UTC() - logger.Warn("no renewal time had been selected with ARI; chose an ephemeral one for now", - zap.Time("ephemeral_selected_time", selectedTime)) - } - - // if a renewal time has been selected, start with that - if !selectedTime.IsZero() { - // ARI spec recommends an algorithm that renews after the randomly-selected - // time OR just before it if the next waking time would be after it; this - // cutoff can actually be before the start of the renewal window, but the spec - // author says that's OK: https://github.com/aarongable/draft-acme-ari/issues/71 - cutoff := ari.SelectedTime.Add(-cfg.certCache.options.RenewCheckInterval) - if time.Now().After(cutoff) { - logger.Info("certificate needs renewal based on ARI window", - zap.Time("selected_time", selectedTime), - zap.Time("renewal_cutoff", cutoff)) - return true + if !cfg.DisableARI { + // first check ARI: if it says it's time to renew, it's time to renew + // (notice that we don't strictly require an ARI window to also exist; we presume + // that if a time has been selected, a window does or did exist, even if it didn't + // get stored/encoded for some reason - but also: this allows administrators to + // manually or explicitly schedule a renewal time indepedently of ARI which could + // be useful) + selectedTime := ari.SelectedTime + + // if, for some reason a random time in the window hasn't been selected yet, but an ARI + // window does exist, we can always improvise one... even if this is called repeatedly, + // a random time is a random time, whether you generate it once or more :D + // (code borrowed from our acme package) + if selectedTime.IsZero() && + (!ari.SuggestedWindow.Start.IsZero() && !ari.SuggestedWindow.End.IsZero()) { + start, end := ari.SuggestedWindow.Start.Unix()+1, ari.SuggestedWindow.End.Unix() + selectedTime = time.Unix(rand.Int63n(end-start)+start, 0).UTC() + logger.Warn("no renewal time had been selected with ARI; chose an ephemeral one for now", + zap.Time("ephemeral_selected_time", selectedTime)) } - // according to ARI, we are not ready to renew; however, we do not rely solely on - // ARI calculations... what if there is a bug in our implementation, or in the - // server's, or the stored metadata? for redundancy, give credence to the expiration - // date; ignore ARI if we are past a "dangerously close" limit, to avoid any - // possibility of a bug in ARI compromising a site's uptime: we should always always - // always give heed to actual validity period - if currentlyInRenewalWindow(leaf.NotBefore, expiration, 1.0/20.0) { - logger.Warn("certificate is in emergency renewal window; superceding ARI", - zap.Duration("remaining", time.Until(expiration)), - zap.Time("renewal_cutoff", cutoff)) - return true + // if a renewal time has been selected, start with that + if !selectedTime.IsZero() { + // ARI spec recommends an algorithm that renews after the randomly-selected + // time OR just before it if the next waking time would be after it; this + // cutoff can actually be before the start of the renewal window, but the spec + // author says that's OK: https://github.com/aarongable/draft-acme-ari/issues/71 + cutoff := ari.SelectedTime.Add(-cfg.certCache.options.RenewCheckInterval) + if time.Now().After(cutoff) { + logger.Info("certificate needs renewal based on ARI window", + zap.Time("selected_time", selectedTime), + zap.Time("renewal_cutoff", cutoff)) + return true + } + + // according to ARI, we are not ready to renew; however, we do not rely solely on + // ARI calculations... what if there is a bug in our implementation, or in the + // server's, or the stored metadata? for redundancy, give credence to the expiration + // date; ignore ARI if we are past a "dangerously close" limit, to avoid any + // possibility of a bug in ARI compromising a site's uptime: we should always always + // always give heed to actual validity period + if currentlyInRenewalWindow(leaf.NotBefore, expiration, 1.0/20.0) { + logger.Warn("certificate is in emergency renewal window; superceding ARI", + zap.Duration("remaining", time.Until(expiration)), + zap.Time("renewal_cutoff", cutoff)) + return true + } } - } // the normal check, in the absence of ARI, is to determine if we're near enough (or past) @@ -552,6 +553,7 @@ func SubjectIsInternal(subj string) bool { return subj == "localhost" || strings.HasSuffix(subj, ".localhost") || strings.HasSuffix(subj, ".local") || + strings.HasSuffix(subj, ".internal") || strings.HasSuffix(subj, ".home.arpa") || isInternalIP(subj) } diff --git a/vendor/github.com/caddyserver/certmagic/config.go b/vendor/github.com/caddyserver/certmagic/config.go index 5a9cf498..a7771848 100644 --- a/vendor/github.com/caddyserver/certmagic/config.go +++ b/vendor/github.com/caddyserver/certmagic/config.go @@ -149,6 +149,10 @@ type Config struct { // EXPERIMENTAL: Subject to change or removal. SubjectTransformer func(ctx context.Context, domain string) string + // Disables both ARI fetching and the use of ARI for renewal decisions. + // TEMPORARY: Will likely be removed in the future. + DisableARI bool + // Set a logger to enable logging. If not set, // a default logger will be created. Logger *zap.Logger @@ -370,9 +374,11 @@ func (cfg *Config) manageAll(ctx context.Context, domainNames []string, async bo } for _, domainName := range domainNames { + domainName = normalizedName(domainName) + // if on-demand is configured, defer obtain and renew operations if cfg.OnDemand != nil { - cfg.OnDemand.hostAllowlist[normalizedName(domainName)] = struct{}{} + cfg.OnDemand.hostAllowlist[domainName] = struct{}{} continue } @@ -449,7 +455,7 @@ func (cfg *Config) manageOne(ctx context.Context, domainName string, async bool) // ensure ARI is updated before we check whether the cert needs renewing // (we ignore the second return value because we already check if needs renewing anyway) - if cert.ari.NeedsRefresh() { + if !cfg.DisableARI && cert.ari.NeedsRefresh() { cert, _, err = cfg.updateARI(ctx, cert, cfg.Logger) if err != nil { cfg.Logger.Error("updating ARI upon managing", zap.Error(err)) @@ -886,11 +892,13 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv // if we're renewing with the same ACME CA as before, have the ACME // client tell the server we are replacing a certificate (but doing // this on the wrong CA, or when the CA doesn't recognize the certID, - // can fail the order) - if acmeData, err := certRes.getACMEData(); err == nil && acmeData.CA != "" { - if acmeIss, ok := issuer.(*ACMEIssuer); ok { - if acmeIss.CA == acmeData.CA { - ctx = context.WithValue(ctx, ctxKeyARIReplaces, leaf) + // can fail the order) -- TODO: change this check to whether we're using the same ACME account, not CA + if !cfg.DisableARI { + if acmeData, err := certRes.getACMEData(); err == nil && acmeData.CA != "" { + if acmeIss, ok := issuer.(*ACMEIssuer); ok { + if acmeIss.CA == acmeData.CA { + ctx = context.WithValue(ctx, ctxKeyARIReplaces, leaf) + } } } } @@ -982,23 +990,26 @@ func (cfg *Config) generateCSR(privateKey crypto.PrivateKey, sans []string, useC csrTemplate := new(x509.CertificateRequest) for _, name := range sans { + // identifiers should be converted to punycode before going into the CSR + // (convert IDNs to ASCII according to RFC 5280 section 7) + normalizedName, err := idna.ToASCII(name) + if err != nil { + return nil, fmt.Errorf("converting identifier '%s' to ASCII: %v", name, err) + } + // TODO: This is a temporary hack to support ZeroSSL API... - if useCN && csrTemplate.Subject.CommonName == "" && len(name) <= 64 { - csrTemplate.Subject.CommonName = name + if useCN && csrTemplate.Subject.CommonName == "" && len(normalizedName) <= 64 { + csrTemplate.Subject.CommonName = normalizedName continue } - if ip := net.ParseIP(name); ip != nil { + + if ip := net.ParseIP(normalizedName); ip != nil { csrTemplate.IPAddresses = append(csrTemplate.IPAddresses, ip) - } else if strings.Contains(name, "@") { - csrTemplate.EmailAddresses = append(csrTemplate.EmailAddresses, name) - } else if u, err := url.Parse(name); err == nil && strings.Contains(name, "/") { + } else if strings.Contains(normalizedName, "@") { + csrTemplate.EmailAddresses = append(csrTemplate.EmailAddresses, normalizedName) + } else if u, err := url.Parse(normalizedName); err == nil && strings.Contains(normalizedName, "/") { csrTemplate.URIs = append(csrTemplate.URIs, u) } else { - // convert IDNs to ASCII according to RFC 5280 section 7 - normalizedName, err := idna.ToASCII(name) - if err != nil { - return nil, fmt.Errorf("converting identifier '%s' to ASCII: %v", name, err) - } csrTemplate.DNSNames = append(csrTemplate.DNSNames, normalizedName) } } @@ -1007,6 +1018,16 @@ func (cfg *Config) generateCSR(privateKey crypto.PrivateKey, sans []string, useC csrTemplate.ExtraExtensions = append(csrTemplate.ExtraExtensions, mustStapleExtension) } + // IP addresses aren't printed here because I'm too lazy to marshal them as strings, but + // we at least print the incoming SANs so it should be obvious what became IPs + cfg.Logger.Debug("created CSR", + zap.Strings("identifiers", sans), + zap.Strings("san_dns_names", csrTemplate.DNSNames), + zap.Strings("san_emails", csrTemplate.EmailAddresses), + zap.String("common_name", csrTemplate.Subject.CommonName), + zap.Int("extra_extensions", len(csrTemplate.ExtraExtensions)), + ) + csrDER, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, privateKey) if err != nil { return nil, err @@ -1244,8 +1265,10 @@ func (cfg *Config) managedCertNeedsRenewal(certRes CertificateResource, emitLogs return 0, nil, true } var ari acme.RenewalInfo - if ariPtr, err := certRes.getARI(); err == nil && ariPtr != nil { - ari = *ariPtr + if !cfg.DisableARI { + if ariPtr, err := certRes.getARI(); err == nil && ariPtr != nil { + ari = *ariPtr + } } remaining := time.Until(expiresAt(certChain[0])) return remaining, certChain[0], cfg.certNeedsRenewal(certChain[0], ari, emitLogs) diff --git a/vendor/github.com/caddyserver/certmagic/filestorage.go b/vendor/github.com/caddyserver/certmagic/filestorage.go index f6f13603..d3df9cf7 100644 --- a/vendor/github.com/caddyserver/certmagic/filestorage.go +++ b/vendor/github.com/caddyserver/certmagic/filestorage.go @@ -27,6 +27,8 @@ import ( "path/filepath" "runtime" "time" + + "github.com/caddyserver/certmagic/internal/atomicfile" ) // FileStorage facilitates forming file paths derived from a root @@ -82,12 +84,30 @@ func (s *FileStorage) Store(_ context.Context, key string, value []byte) error { if err != nil { return err } - return os.WriteFile(filename, value, 0600) + fp, err := atomicfile.New(filename, 0o600) + if err != nil { + return err + } + _, err = fp.Write(value) + if err != nil { + // cancel the write + fp.Cancel() + return err + } + // close, thereby flushing the write + return fp.Close() } // Load retrieves the value at key. func (s *FileStorage) Load(_ context.Context, key string) ([]byte, error) { - return os.ReadFile(s.Filename(key)) + // i believe it's possible for the read call to error but still return bytes, in event of something like a shortread? + // therefore, i think it's appropriate to not return any bytes to avoid downstream users of the package erroniously believing that + // bytes read + error is a valid response (it should not be) + xs, err := os.ReadFile(s.Filename(key)) + if err != nil { + return nil, err + } + return xs, nil } // Delete deletes the value at key. diff --git a/vendor/github.com/caddyserver/certmagic/handshake.go b/vendor/github.com/caddyserver/certmagic/handshake.go index fd576995..1ff0ce27 100644 --- a/vendor/github.com/caddyserver/certmagic/handshake.go +++ b/vendor/github.com/caddyserver/certmagic/handshake.go @@ -582,7 +582,7 @@ func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHe } // Check ARI status - if cert.ari.NeedsRefresh() { + if !cfg.DisableARI && cert.ari.NeedsRefresh() { // we ignore the second return value here because we go on to check renewal status below regardless var err error cert, _, err = cfg.updateARI(ctx, cert, logger) diff --git a/vendor/github.com/caddyserver/certmagic/internal/atomicfile/README b/vendor/github.com/caddyserver/certmagic/internal/atomicfile/README new file mode 100644 index 00000000..17d04ddd --- /dev/null +++ b/vendor/github.com/caddyserver/certmagic/internal/atomicfile/README @@ -0,0 +1,11 @@ +# atomic file + + +this is copied from + +https://github.com/containerd/containerd/blob/main/pkg%2Fatomicfile%2Ffile.go + + +see + +https://github.com/caddyserver/certmagic/issues/296 diff --git a/vendor/github.com/caddyserver/certmagic/internal/atomicfile/file.go b/vendor/github.com/caddyserver/certmagic/internal/atomicfile/file.go new file mode 100644 index 00000000..7b870f7a --- /dev/null +++ b/vendor/github.com/caddyserver/certmagic/internal/atomicfile/file.go @@ -0,0 +1,148 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/* +Package atomicfile provides a mechanism (on Unix-like platforms) to present a consistent view of a file to separate +processes even while the file is being written. This is accomplished by writing a temporary file, syncing to disk, and +renaming over the destination file name. + +Partial/inconsistent reads can occur due to: + 1. A process attempting to read the file while it is being written to (both in the case of a new file with a + short/incomplete write or in the case of an existing, updated file where new bytes may be written at the beginning + but old bytes may still be present after). + 2. Concurrent goroutines leading to multiple active writers of the same file. + +The above mechanism explicitly protects against (1) as all writes are to a file with a temporary name. + +There is no explicit protection against multiple, concurrent goroutines attempting to write the same file. However, +atomically writing the file should mean only one writer will "win" and a consistent file will be visible. + +Note: atomicfile is partially implemented for Windows. The Windows codepath performs the same operations, however +Windows does not guarantee that a rename operation is atomic; a crash in the middle may leave the destination file +truncated rather than with the expected content. +*/ +package atomicfile + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "sync" +) + +// File is an io.ReadWriteCloser that can also be Canceled if a change needs to be abandoned. +type File interface { + io.ReadWriteCloser + // Cancel abandons a change to a file. This can be called if a write fails or another error occurs. + Cancel() error +} + +// ErrClosed is returned if Read or Write are called on a closed File. +var ErrClosed = errors.New("file is closed") + +// New returns a new atomic file. On Unix-like platforms, the writer (an io.ReadWriteCloser) is backed by a temporary +// file placed into the same directory as the destination file (using filepath.Dir to split the directory from the +// name). On a call to Close the temporary file is synced to disk and renamed to its final name, hiding any previous +// file by the same name. +// +// Note: Take care to call Close and handle any errors that are returned. Errors returned from Close may indicate that +// the file was not written with its final name. +func New(name string, mode os.FileMode) (File, error) { + return newFile(name, mode) +} + +type atomicFile struct { + name string + f *os.File + closed bool + closedMu sync.RWMutex +} + +func newFile(name string, mode os.FileMode) (File, error) { + dir := filepath.Dir(name) + f, err := os.CreateTemp(dir, "") + if err != nil { + return nil, fmt.Errorf("failed to create temp file: %w", err) + } + if err := f.Chmod(mode); err != nil { + return nil, fmt.Errorf("failed to change temp file permissions: %w", err) + } + return &atomicFile{name: name, f: f}, nil +} + +func (a *atomicFile) Close() (err error) { + a.closedMu.Lock() + defer a.closedMu.Unlock() + + if a.closed { + return nil + } + a.closed = true + + defer func() { + if err != nil { + _ = os.Remove(a.f.Name()) // ignore errors + } + }() + // The order of operations here is: + // 1. sync + // 2. close + // 3. rename + // While the ordering of 2 and 3 is not important on Unix-like operating systems, Windows cannot rename an open + // file. By closing first, we allow the rename operation to succeed. + if err = a.f.Sync(); err != nil { + return fmt.Errorf("failed to sync temp file %q: %w", a.f.Name(), err) + } + if err = a.f.Close(); err != nil { + return fmt.Errorf("failed to close temp file %q: %w", a.f.Name(), err) + } + if err = os.Rename(a.f.Name(), a.name); err != nil { + return fmt.Errorf("failed to rename %q to %q: %w", a.f.Name(), a.name, err) + } + return nil +} + +func (a *atomicFile) Cancel() error { + a.closedMu.Lock() + defer a.closedMu.Unlock() + + if a.closed { + return nil + } + a.closed = true + _ = a.f.Close() // ignore error + return os.Remove(a.f.Name()) +} + +func (a *atomicFile) Read(p []byte) (n int, err error) { + a.closedMu.RLock() + defer a.closedMu.RUnlock() + if a.closed { + return 0, ErrClosed + } + return a.f.Read(p) +} + +func (a *atomicFile) Write(p []byte) (n int, err error) { + a.closedMu.RLock() + defer a.closedMu.RUnlock() + if a.closed { + return 0, ErrClosed + } + return a.f.Write(p) +} diff --git a/vendor/github.com/caddyserver/certmagic/maintain.go b/vendor/github.com/caddyserver/certmagic/maintain.go index 88d36531..dea2cfdf 100644 --- a/vendor/github.com/caddyserver/certmagic/maintain.go +++ b/vendor/github.com/caddyserver/certmagic/maintain.go @@ -136,7 +136,7 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error { } // ACME-specific: see if if ACME Renewal Info (ARI) window needs refreshing - if cert.ari.NeedsRefresh() { + if !cfg.DisableARI && cert.ari.NeedsRefresh() { configs[cert.hash] = cfg ariQueue = append(ariQueue, cert) } @@ -427,7 +427,7 @@ func (cfg *Config) storageHasNewerARI(ctx context.Context, cert Certificate) (bo // or if the one in storage has a later RetryAfter (though I suppose // it's not guaranteed, typically those will move forward in time) if (!cert.ari.HasWindow() && storedCertData.RenewalInfo.HasWindow()) || - storedCertData.RenewalInfo.RetryAfter.After(*cert.ari.RetryAfter) { + (cert.ari.RetryAfter == nil || storedCertData.RenewalInfo.RetryAfter.After(*cert.ari.RetryAfter)) { return true, *storedCertData.RenewalInfo, nil } return false, acme.RenewalInfo{}, nil @@ -459,6 +459,9 @@ func (cfg *Config) loadStoredACMECertificateMetadata(ctx context.Context, cert C // updated in the cache. The certificate with the updated ARI is returned. If true is // returned, the ARI window or selected time has changed, and the caller should check if // the cert needs to be renewed now, even if there is an error. +// +// This will always try to ARI without checking if it needs to be refreshed. Call +// NeedsRefresh() on the RenewalInfo first, and only call this if that returns true. func (cfg *Config) updateARI(ctx context.Context, cert Certificate, logger *zap.Logger) (updatedCert Certificate, changed bool, err error) { logger = logger.With( zap.Strings("identifiers", cert.Names), @@ -469,6 +472,17 @@ func (cfg *Config) updateARI(ctx context.Context, cert Certificate, logger *zap. updatedCert = cert oldARI := cert.ari + // synchronize ARI fetching; see #297 + lockName := "ari_" + cert.ari.UniqueIdentifier + if err := acquireLock(ctx, cfg.Storage, lockName); err != nil { + return cert, false, fmt.Errorf("unable to obtain ARI lock: %v", err) + } + defer func() { + if err := releaseLock(ctx, cfg.Storage, lockName); err != nil { + logger.Error("unable to release ARI lock", zap.Error(err)) + } + }() + // see if the stored value has been refreshed already by another instance gotNewARI, newARI, err := cfg.storageHasNewerARI(ctx, cert) @@ -615,11 +629,11 @@ func CleanStorage(ctx context.Context, storage Storage, opts CleanStorageOptions opts.Logger = opts.Logger.With(zap.Any("storage", storage)) // storage cleaning should be globally exclusive - if err := storage.Lock(ctx, lockName); err != nil { + if err := acquireLock(ctx, storage, lockName); err != nil { return fmt.Errorf("unable to acquire %s lock: %v", lockName, err) } defer func() { - if err := storage.Unlock(ctx, lockName); err != nil { + if err := releaseLock(ctx, storage, lockName); err != nil { opts.Logger.Error("unable to release lock", zap.Error(err)) return } diff --git a/vendor/github.com/caddyserver/certmagic/zerosslissuer.go b/vendor/github.com/caddyserver/certmagic/zerosslissuer.go index 8ee044b4..4a33bfa2 100644 --- a/vendor/github.com/caddyserver/certmagic/zerosslissuer.go +++ b/vendor/github.com/caddyserver/certmagic/zerosslissuer.go @@ -146,7 +146,7 @@ func (iss *ZeroSSLIssuer) Issue(ctx context.Context, csr *x509.CertificateReques // create the CNAME record(s) records := make(map[string]zoneRecord, len(cert.Validation.OtherMethods)) for name, verifyInfo := range cert.Validation.OtherMethods { - zr, err := iss.CNAMEValidation.createRecord(ctx, verifyInfo.CnameValidationP1, "CNAME", verifyInfo.CnameValidationP2) + zr, err := iss.CNAMEValidation.createRecord(ctx, verifyInfo.CnameValidationP1, "CNAME", verifyInfo.CnameValidationP2+".") // see issue #304 if err != nil { return nil, fmt.Errorf("creating CNAME record: %v", err) } diff --git a/vendor/github.com/go-playground/validator/v10/README.md b/vendor/github.com/go-playground/validator/v10/README.md index 9ab0705a..ddd65b07 100644 --- a/vendor/github.com/go-playground/validator/v10/README.md +++ b/vendor/github.com/go-playground/validator/v10/README.md @@ -1,7 +1,7 @@ Package validator ================= [![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -![Project status](https://img.shields.io/badge/version-10.22.0-green.svg) +![Project status](https://img.shields.io/badge/version-10.22.1-green.svg) [![Build Status](https://travis-ci.org/go-playground/validator.svg?branch=master)](https://travis-ci.org/go-playground/validator) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-playground/validator?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) diff --git a/vendor/github.com/go-playground/validator/v10/baked_in.go b/vendor/github.com/go-playground/validator/v10/baked_in.go index b6fbaafa..d1a3656a 100644 --- a/vendor/github.com/go-playground/validator/v10/baked_in.go +++ b/vendor/github.com/go-playground/validator/v10/baked_in.go @@ -1828,7 +1828,14 @@ func requireCheckFieldValue( return int64(field.Len()) == asInt(value) case reflect.Bool: - return field.Bool() == asBool(value) + return field.Bool() == (value == "true") + + case reflect.Ptr: + if field.IsNil() { + return value == "nil" + } + // Handle non-nil pointers + return requireCheckFieldValue(fl, param, value, defaultNotFoundValue) } // default reflect.String: diff --git a/vendor/github.com/hashicorp/raft/raft.go b/vendor/github.com/hashicorp/raft/raft.go index 183f041a..cbc9a59a 100644 --- a/vendor/github.com/hashicorp/raft/raft.go +++ b/vendor/github.com/hashicorp/raft/raft.go @@ -1749,7 +1749,7 @@ func (r *Raft) requestPreVote(rpc RPC, req *RequestPreVoteRequest) { }() // Check if we have an existing leader [who's not the candidate] and also - var candidate ServerAddress + candidate := r.trans.DecodePeer(req.GetRPCHeader().Addr) candidateID := ServerID(req.ID) // if the Servers list is empty that mean the cluster is very likely trying to bootstrap, @@ -1805,7 +1805,6 @@ func (r *Raft) requestPreVote(rpc RPC, req *RequestPreVoteRequest) { } resp.Granted = true - r.setLastContact() } // installSnapshot is invoked when we get a InstallSnapshot RPC call. diff --git a/vendor/github.com/klauspost/compress/README.md b/vendor/github.com/klauspost/compress/README.md index 05c7359e..684a3085 100644 --- a/vendor/github.com/klauspost/compress/README.md +++ b/vendor/github.com/klauspost/compress/README.md @@ -16,6 +16,20 @@ This package provides various compression algorithms. # changelog +* Jun 12th, 2024 - [1.17.9](https://github.com/klauspost/compress/releases/tag/v1.17.9) + * s2: Reduce ReadFrom temporary allocations https://github.com/klauspost/compress/pull/949 + * flate, zstd: Shave some bytes off amd64 matchLen by @greatroar in https://github.com/klauspost/compress/pull/963 + * Upgrade zip/zlib to 1.22.4 upstream https://github.com/klauspost/compress/pull/970 https://github.com/klauspost/compress/pull/971 + * zstd: BuildDict fails with RLE table https://github.com/klauspost/compress/pull/951 + +* Apr 9th, 2024 - [1.17.8](https://github.com/klauspost/compress/releases/tag/v1.17.8) + * zstd: Reject blocks where reserved values are not 0 https://github.com/klauspost/compress/pull/885 + * zstd: Add RLE detection+encoding https://github.com/klauspost/compress/pull/938 + +* Feb 21st, 2024 - [1.17.7](https://github.com/klauspost/compress/releases/tag/v1.17.7) + * s2: Add AsyncFlush method: Complete the block without flushing by @Jille in https://github.com/klauspost/compress/pull/927 + * s2: Fix literal+repeat exceeds dst crash https://github.com/klauspost/compress/pull/930 + * Feb 5th, 2024 - [1.17.6](https://github.com/klauspost/compress/releases/tag/v1.17.6) * zstd: Fix incorrect repeat coding in best mode https://github.com/klauspost/compress/pull/923 * s2: Fix DecodeConcurrent deadlock on errors https://github.com/klauspost/compress/pull/925 @@ -81,7 +95,7 @@ https://github.com/klauspost/compress/pull/919 https://github.com/klauspost/comp * zstd: Various minor improvements by @greatroar in https://github.com/klauspost/compress/pull/788 https://github.com/klauspost/compress/pull/794 https://github.com/klauspost/compress/pull/795 * s2: Fix huge block overflow https://github.com/klauspost/compress/pull/779 * s2: Allow CustomEncoder fallback https://github.com/klauspost/compress/pull/780 - * gzhttp: Suppport ResponseWriter Unwrap() in gzhttp handler by @jgimenez in https://github.com/klauspost/compress/pull/799 + * gzhttp: Support ResponseWriter Unwrap() in gzhttp handler by @jgimenez in https://github.com/klauspost/compress/pull/799 * Mar 13, 2023 - [v1.16.1](https://github.com/klauspost/compress/releases/tag/v1.16.1) * zstd: Speed up + improve best encoder by @greatroar in https://github.com/klauspost/compress/pull/776 @@ -136,7 +150,7 @@ https://github.com/klauspost/compress/pull/919 https://github.com/klauspost/comp * zstd: Add [WithDecodeAllCapLimit](https://pkg.go.dev/github.com/klauspost/compress@v1.15.10/zstd#WithDecodeAllCapLimit) https://github.com/klauspost/compress/pull/649 * Add Go 1.19 - deprecate Go 1.16 https://github.com/klauspost/compress/pull/651 * flate: Improve level 5+6 compression https://github.com/klauspost/compress/pull/656 - * zstd: Improve "better" compresssion https://github.com/klauspost/compress/pull/657 + * zstd: Improve "better" compression https://github.com/klauspost/compress/pull/657 * s2: Improve "best" compression https://github.com/klauspost/compress/pull/658 * s2: Improve "better" compression. https://github.com/klauspost/compress/pull/635 * s2: Slightly faster non-assembly decompression https://github.com/klauspost/compress/pull/646 @@ -339,7 +353,7 @@ While the release has been extensively tested, it is recommended to testing when * s2: Fix binaries. * Feb 25, 2021 (v1.11.8) - * s2: Fixed occational out-of-bounds write on amd64. Upgrade recommended. + * s2: Fixed occasional out-of-bounds write on amd64. Upgrade recommended. * s2: Add AMD64 assembly for better mode. 25-50% faster. [#315](https://github.com/klauspost/compress/pull/315) * s2: Less upfront decoder allocation. [#322](https://github.com/klauspost/compress/pull/322) * zstd: Faster "compression" of incompressible data. [#314](https://github.com/klauspost/compress/pull/314) @@ -518,7 +532,7 @@ While the release has been extensively tested, it is recommended to testing when * Feb 19, 2016: Faster bit writer, level -2 is 15% faster, level 1 is 4% faster. * Feb 19, 2016: Handle small payloads faster in level 1-3. * Feb 19, 2016: Added faster level 2 + 3 compression modes. -* Feb 19, 2016: [Rebalanced compression levels](https://blog.klauspost.com/rebalancing-deflate-compression-levels/), so there is a more even progresssion in terms of compression. New default level is 5. +* Feb 19, 2016: [Rebalanced compression levels](https://blog.klauspost.com/rebalancing-deflate-compression-levels/), so there is a more even progression in terms of compression. New default level is 5. * Feb 14, 2016: Snappy: Merge upstream changes. * Feb 14, 2016: Snappy: Fix aggressive skipping. * Feb 14, 2016: Snappy: Update benchmark. diff --git a/vendor/github.com/klauspost/compress/flate/deflate.go b/vendor/github.com/klauspost/compress/flate/deflate.go index 66d1657d..af53fb86 100644 --- a/vendor/github.com/klauspost/compress/flate/deflate.go +++ b/vendor/github.com/klauspost/compress/flate/deflate.go @@ -861,7 +861,7 @@ func (d *compressor) reset(w io.Writer) { } switch d.compressionLevel.chain { case 0: - // level was NoCompression or ConstantCompresssion. + // level was NoCompression or ConstantCompression. d.windowEnd = 0 default: s := d.state diff --git a/vendor/github.com/klauspost/compress/flate/inflate.go b/vendor/github.com/klauspost/compress/flate/inflate.go index 2f410d64..0d7b437f 100644 --- a/vendor/github.com/klauspost/compress/flate/inflate.go +++ b/vendor/github.com/klauspost/compress/flate/inflate.go @@ -298,6 +298,14 @@ const ( huffmanGenericReader ) +// flushMode tells decompressor when to return data +type flushMode uint8 + +const ( + syncFlush flushMode = iota // return data after sync flush block + partialFlush // return data after each block +) + // Decompress state. type decompressor struct { // Input source. @@ -332,6 +340,8 @@ type decompressor struct { nb uint final bool + + flushMode flushMode } func (f *decompressor) nextBlock() { @@ -618,7 +628,10 @@ func (f *decompressor) dataBlock() { } if n == 0 { - f.toRead = f.dict.readFlush() + if f.flushMode == syncFlush { + f.toRead = f.dict.readFlush() + } + f.finishBlock() return } @@ -657,8 +670,12 @@ func (f *decompressor) finishBlock() { if f.dict.availRead() > 0 { f.toRead = f.dict.readFlush() } + f.err = io.EOF + } else if f.flushMode == partialFlush && f.dict.availRead() > 0 { + f.toRead = f.dict.readFlush() } + f.step = nextBlock } @@ -789,15 +806,25 @@ func (f *decompressor) Reset(r io.Reader, dict []byte) error { return nil } -// NewReader returns a new ReadCloser that can be used -// to read the uncompressed version of r. -// If r does not also implement io.ByteReader, -// the decompressor may read more data than necessary from r. -// It is the caller's responsibility to call Close on the ReadCloser -// when finished reading. -// -// The ReadCloser returned by NewReader also implements Resetter. -func NewReader(r io.Reader) io.ReadCloser { +type ReaderOpt func(*decompressor) + +// WithPartialBlock tells decompressor to return after each block, +// so it can read data written with partial flush +func WithPartialBlock() ReaderOpt { + return func(f *decompressor) { + f.flushMode = partialFlush + } +} + +// WithDict initializes the reader with a preset dictionary +func WithDict(dict []byte) ReaderOpt { + return func(f *decompressor) { + f.dict.init(maxMatchOffset, dict) + } +} + +// NewReaderOpts returns new reader with provided options +func NewReaderOpts(r io.Reader, opts ...ReaderOpt) io.ReadCloser { fixedHuffmanDecoderInit() var f decompressor @@ -806,9 +833,26 @@ func NewReader(r io.Reader) io.ReadCloser { f.codebits = new([numCodes]int) f.step = nextBlock f.dict.init(maxMatchOffset, nil) + + for _, opt := range opts { + opt(&f) + } + return &f } +// NewReader returns a new ReadCloser that can be used +// to read the uncompressed version of r. +// If r does not also implement io.ByteReader, +// the decompressor may read more data than necessary from r. +// It is the caller's responsibility to call Close on the ReadCloser +// when finished reading. +// +// The ReadCloser returned by NewReader also implements Resetter. +func NewReader(r io.Reader) io.ReadCloser { + return NewReaderOpts(r) +} + // NewReaderDict is like NewReader but initializes the reader // with a preset dictionary. The returned Reader behaves as if // the uncompressed data stream started with the given dictionary, @@ -817,13 +861,5 @@ func NewReader(r io.Reader) io.ReadCloser { // // The ReadCloser returned by NewReader also implements Resetter. func NewReaderDict(r io.Reader, dict []byte) io.ReadCloser { - fixedHuffmanDecoderInit() - - var f decompressor - f.r = makeReader(r) - f.bits = new([maxNumLit + maxNumDist]int) - f.codebits = new([numCodes]int) - f.step = nextBlock - f.dict.init(maxMatchOffset, dict) - return &f + return NewReaderOpts(r, WithDict(dict)) } diff --git a/vendor/github.com/klauspost/compress/fse/decompress.go b/vendor/github.com/klauspost/compress/fse/decompress.go index cc05d0f7..0c7dd4ff 100644 --- a/vendor/github.com/klauspost/compress/fse/decompress.go +++ b/vendor/github.com/klauspost/compress/fse/decompress.go @@ -15,7 +15,7 @@ const ( // It is possible, but by no way guaranteed that corrupt data will // return an error. // It is up to the caller to verify integrity of the returned data. -// Use a predefined Scrach to set maximum acceptable output size. +// Use a predefined Scratch to set maximum acceptable output size. func Decompress(b []byte, s *Scratch) ([]byte, error) { s, err := s.prepare(b) if err != nil { diff --git a/vendor/github.com/klauspost/compress/huff0/decompress.go b/vendor/github.com/klauspost/compress/huff0/decompress.go index 54bd08b2..0f56b02d 100644 --- a/vendor/github.com/klauspost/compress/huff0/decompress.go +++ b/vendor/github.com/klauspost/compress/huff0/decompress.go @@ -1136,7 +1136,7 @@ func (s *Scratch) matches(ct cTable, w io.Writer) { errs++ } if errs > 0 { - fmt.Fprintf(w, "%d errros in base, stopping\n", errs) + fmt.Fprintf(w, "%d errors in base, stopping\n", errs) continue } // Ensure that all combinations are covered. @@ -1152,7 +1152,7 @@ func (s *Scratch) matches(ct cTable, w io.Writer) { errs++ } if errs > 20 { - fmt.Fprintf(w, "%d errros, stopping\n", errs) + fmt.Fprintf(w, "%d errors, stopping\n", errs) break } } diff --git a/vendor/github.com/klauspost/compress/s2/writer.go b/vendor/github.com/klauspost/compress/s2/writer.go index 0a46f2b9..fd15078f 100644 --- a/vendor/github.com/klauspost/compress/s2/writer.go +++ b/vendor/github.com/klauspost/compress/s2/writer.go @@ -83,11 +83,14 @@ type Writer struct { snappy bool flushOnWrite bool appendIndex bool + bufferCB func([]byte) level uint8 } type result struct { b []byte + // return when writing + ret []byte // Uncompressed start offset startOffset int64 } @@ -146,6 +149,10 @@ func (w *Writer) Reset(writer io.Writer) { for write := range toWrite { // Wait for the data to be available. input := <-write + if input.ret != nil && w.bufferCB != nil { + w.bufferCB(input.ret) + input.ret = nil + } in := input.b if len(in) > 0 { if w.err(nil) == nil { @@ -341,7 +348,8 @@ func (w *Writer) AddSkippableBlock(id uint8, data []byte) (err error) { // but the input buffer cannot be written to by the caller // until Flush or Close has been called when concurrency != 1. // -// If you cannot control that, use the regular Write function. +// Use the WriterBufferDone to receive a callback when the buffer is done +// Processing. // // Note that input is not buffered. // This means that each write will result in discrete blocks being created. @@ -364,6 +372,9 @@ func (w *Writer) EncodeBuffer(buf []byte) (err error) { } if w.concurrency == 1 { _, err := w.writeSync(buf) + if w.bufferCB != nil { + w.bufferCB(buf) + } return err } @@ -378,7 +389,7 @@ func (w *Writer) EncodeBuffer(buf []byte) (err error) { hWriter <- result{startOffset: w.uncompWritten, b: magicChunkBytes} } } - + orgBuf := buf for len(buf) > 0 { // Cut input. uncompressed := buf @@ -397,6 +408,9 @@ func (w *Writer) EncodeBuffer(buf []byte) (err error) { startOffset: w.uncompWritten, } w.uncompWritten += int64(len(uncompressed)) + if len(buf) == 0 && w.bufferCB != nil { + res.ret = orgBuf + } go func() { race.ReadSlice(uncompressed) @@ -922,7 +936,7 @@ func WriterBetterCompression() WriterOption { } // WriterBestCompression will enable better compression. -// EncodeBetter compresses better than Encode but typically with a +// EncodeBest compresses better than Encode but typically with a // big speed decrease on compression. func WriterBestCompression() WriterOption { return func(w *Writer) error { @@ -941,6 +955,17 @@ func WriterUncompressed() WriterOption { } } +// WriterBufferDone will perform a callback when EncodeBuffer has finished +// writing a buffer to the output and the buffer can safely be reused. +// If the buffer was split into several blocks, it will be sent after the last block. +// Callbacks will not be done concurrently. +func WriterBufferDone(fn func(b []byte)) WriterOption { + return func(w *Writer) error { + w.bufferCB = fn + return nil + } +} + // WriterBlockSize allows to override the default block size. // Blocks will be this size or smaller. // Minimum size is 4KB and maximum size is 4MB. diff --git a/vendor/github.com/klauspost/compress/zstd/blockdec.go b/vendor/github.com/klauspost/compress/zstd/blockdec.go index 03744fbc..9c28840c 100644 --- a/vendor/github.com/klauspost/compress/zstd/blockdec.go +++ b/vendor/github.com/klauspost/compress/zstd/blockdec.go @@ -598,7 +598,9 @@ func (b *blockDec) prepareSequences(in []byte, hist *history) (err error) { printf("RLE set to 0x%x, code: %v", symb, v) } case compModeFSE: - println("Reading table for", tableIndex(i)) + if debugDecoder { + println("Reading table for", tableIndex(i)) + } if seq.fse == nil || seq.fse.preDefined { seq.fse = fseDecoderPool.Get().(*fseDecoder) } diff --git a/vendor/github.com/klauspost/compress/zstd/enc_better.go b/vendor/github.com/klauspost/compress/zstd/enc_better.go index a4f5bf91..84a79fde 100644 --- a/vendor/github.com/klauspost/compress/zstd/enc_better.go +++ b/vendor/github.com/klauspost/compress/zstd/enc_better.go @@ -179,9 +179,9 @@ encodeLoop: if repIndex >= 0 && load3232(src, repIndex) == uint32(cv>>(repOff*8)) { // Consider history as well. var seq seq - lenght := 4 + e.matchlen(s+4+repOff, repIndex+4, src) + length := 4 + e.matchlen(s+4+repOff, repIndex+4, src) - seq.matchLen = uint32(lenght - zstdMinMatch) + seq.matchLen = uint32(length - zstdMinMatch) // We might be able to match backwards. // Extend as long as we can. @@ -210,12 +210,12 @@ encodeLoop: // Index match start+1 (long) -> s - 1 index0 := s + repOff - s += lenght + repOff + s += length + repOff nextEmit = s if s >= sLimit { if debugEncoder { - println("repeat ended", s, lenght) + println("repeat ended", s, length) } break encodeLoop @@ -241,9 +241,9 @@ encodeLoop: if false && repIndex >= 0 && load6432(src, repIndex) == load6432(src, s+repOff) { // Consider history as well. var seq seq - lenght := 8 + e.matchlen(s+8+repOff2, repIndex+8, src) + length := 8 + e.matchlen(s+8+repOff2, repIndex+8, src) - seq.matchLen = uint32(lenght - zstdMinMatch) + seq.matchLen = uint32(length - zstdMinMatch) // We might be able to match backwards. // Extend as long as we can. @@ -270,11 +270,11 @@ encodeLoop: } blk.sequences = append(blk.sequences, seq) - s += lenght + repOff2 + s += length + repOff2 nextEmit = s if s >= sLimit { if debugEncoder { - println("repeat ended", s, lenght) + println("repeat ended", s, length) } break encodeLoop @@ -708,9 +708,9 @@ encodeLoop: if repIndex >= 0 && load3232(src, repIndex) == uint32(cv>>(repOff*8)) { // Consider history as well. var seq seq - lenght := 4 + e.matchlen(s+4+repOff, repIndex+4, src) + length := 4 + e.matchlen(s+4+repOff, repIndex+4, src) - seq.matchLen = uint32(lenght - zstdMinMatch) + seq.matchLen = uint32(length - zstdMinMatch) // We might be able to match backwards. // Extend as long as we can. @@ -738,12 +738,12 @@ encodeLoop: blk.sequences = append(blk.sequences, seq) // Index match start+1 (long) -> s - 1 - s += lenght + repOff + s += length + repOff nextEmit = s if s >= sLimit { if debugEncoder { - println("repeat ended", s, lenght) + println("repeat ended", s, length) } break encodeLoop @@ -772,9 +772,9 @@ encodeLoop: if false && repIndex >= 0 && load6432(src, repIndex) == load6432(src, s+repOff) { // Consider history as well. var seq seq - lenght := 8 + e.matchlen(s+8+repOff2, repIndex+8, src) + length := 8 + e.matchlen(s+8+repOff2, repIndex+8, src) - seq.matchLen = uint32(lenght - zstdMinMatch) + seq.matchLen = uint32(length - zstdMinMatch) // We might be able to match backwards. // Extend as long as we can. @@ -801,11 +801,11 @@ encodeLoop: } blk.sequences = append(blk.sequences, seq) - s += lenght + repOff2 + s += length + repOff2 nextEmit = s if s >= sLimit { if debugEncoder { - println("repeat ended", s, lenght) + println("repeat ended", s, length) } break encodeLoop diff --git a/vendor/github.com/klauspost/compress/zstd/enc_dfast.go b/vendor/github.com/klauspost/compress/zstd/enc_dfast.go index a154c18f..d36be7bd 100644 --- a/vendor/github.com/klauspost/compress/zstd/enc_dfast.go +++ b/vendor/github.com/klauspost/compress/zstd/enc_dfast.go @@ -138,9 +138,9 @@ encodeLoop: if repIndex >= 0 && load3232(src, repIndex) == uint32(cv>>(repOff*8)) { // Consider history as well. var seq seq - lenght := 4 + e.matchlen(s+4+repOff, repIndex+4, src) + length := 4 + e.matchlen(s+4+repOff, repIndex+4, src) - seq.matchLen = uint32(lenght - zstdMinMatch) + seq.matchLen = uint32(length - zstdMinMatch) // We might be able to match backwards. // Extend as long as we can. @@ -166,11 +166,11 @@ encodeLoop: println("repeat sequence", seq, "next s:", s) } blk.sequences = append(blk.sequences, seq) - s += lenght + repOff + s += length + repOff nextEmit = s if s >= sLimit { if debugEncoder { - println("repeat ended", s, lenght) + println("repeat ended", s, length) } break encodeLoop @@ -798,9 +798,9 @@ encodeLoop: if repIndex >= 0 && load3232(src, repIndex) == uint32(cv>>(repOff*8)) { // Consider history as well. var seq seq - lenght := 4 + e.matchlen(s+4+repOff, repIndex+4, src) + length := 4 + e.matchlen(s+4+repOff, repIndex+4, src) - seq.matchLen = uint32(lenght - zstdMinMatch) + seq.matchLen = uint32(length - zstdMinMatch) // We might be able to match backwards. // Extend as long as we can. @@ -826,11 +826,11 @@ encodeLoop: println("repeat sequence", seq, "next s:", s) } blk.sequences = append(blk.sequences, seq) - s += lenght + repOff + s += length + repOff nextEmit = s if s >= sLimit { if debugEncoder { - println("repeat ended", s, lenght) + println("repeat ended", s, length) } break encodeLoop diff --git a/vendor/github.com/klauspost/compress/zstd/encoder.go b/vendor/github.com/klauspost/compress/zstd/encoder.go index 72af7ef0..a79c4a52 100644 --- a/vendor/github.com/klauspost/compress/zstd/encoder.go +++ b/vendor/github.com/klauspost/compress/zstd/encoder.go @@ -202,7 +202,7 @@ func (e *Encoder) nextBlock(final bool) error { return nil } if final && len(s.filling) > 0 { - s.current = e.EncodeAll(s.filling, s.current[:0]) + s.current = e.encodeAll(s.encoder, s.filling, s.current[:0]) var n2 int n2, s.err = s.w.Write(s.current) if s.err != nil { @@ -469,6 +469,15 @@ func (e *Encoder) Close() error { // Data compressed with EncodeAll can be decoded with the Decoder, // using either a stream or DecodeAll. func (e *Encoder) EncodeAll(src, dst []byte) []byte { + e.init.Do(e.initialize) + enc := <-e.encoders + defer func() { + e.encoders <- enc + }() + return e.encodeAll(enc, src, dst) +} + +func (e *Encoder) encodeAll(enc encoder, src, dst []byte) []byte { if len(src) == 0 { if e.o.fullZero { // Add frame header. @@ -491,13 +500,7 @@ func (e *Encoder) EncodeAll(src, dst []byte) []byte { } return dst } - e.init.Do(e.initialize) - enc := <-e.encoders - defer func() { - // Release encoder reference to last block. - // If a non-single block is needed the encoder will reset again. - e.encoders <- enc - }() + // Use single segments when above minimum window and below window size. single := len(src) <= e.o.windowSize && len(src) > MinWindowSize if e.o.single != nil { diff --git a/vendor/github.com/klauspost/compress/zstd/framedec.go b/vendor/github.com/klauspost/compress/zstd/framedec.go index 53e160f7..e47af66e 100644 --- a/vendor/github.com/klauspost/compress/zstd/framedec.go +++ b/vendor/github.com/klauspost/compress/zstd/framedec.go @@ -146,7 +146,9 @@ func (d *frameDec) reset(br byteBuffer) error { } return err } - printf("raw: %x, mantissa: %d, exponent: %d\n", wd, wd&7, wd>>3) + if debugDecoder { + printf("raw: %x, mantissa: %d, exponent: %d\n", wd, wd&7, wd>>3) + } windowLog := 10 + (wd >> 3) windowBase := uint64(1) << windowLog windowAdd := (windowBase / 8) * uint64(wd&0x7) diff --git a/vendor/github.com/klauspost/compress/zstd/seqdec_amd64.go b/vendor/github.com/klauspost/compress/zstd/seqdec_amd64.go index 8adabd82..c59f17e0 100644 --- a/vendor/github.com/klauspost/compress/zstd/seqdec_amd64.go +++ b/vendor/github.com/klauspost/compress/zstd/seqdec_amd64.go @@ -146,7 +146,7 @@ func (s *sequenceDecs) decodeSyncSimple(hist []byte) (bool, error) { return true, fmt.Errorf("output bigger than max block size (%d)", maxBlockSize) default: - return true, fmt.Errorf("sequenceDecs_decode returned erronous code %d", errCode) + return true, fmt.Errorf("sequenceDecs_decode returned erroneous code %d", errCode) } s.seqSize += ctx.litRemain @@ -292,7 +292,7 @@ func (s *sequenceDecs) decode(seqs []seqVals) error { return io.ErrUnexpectedEOF } - return fmt.Errorf("sequenceDecs_decode_amd64 returned erronous code %d", errCode) + return fmt.Errorf("sequenceDecs_decode_amd64 returned erroneous code %d", errCode) } if ctx.litRemain < 0 { diff --git a/vendor/github.com/klauspost/compress/zstd/seqdec_amd64.s b/vendor/github.com/klauspost/compress/zstd/seqdec_amd64.s index 5b06174b..f5591fa1 100644 --- a/vendor/github.com/klauspost/compress/zstd/seqdec_amd64.s +++ b/vendor/github.com/klauspost/compress/zstd/seqdec_amd64.s @@ -1814,7 +1814,7 @@ TEXT ·sequenceDecs_decodeSync_amd64(SB), $64-32 MOVQ 40(SP), AX ADDQ AX, 48(SP) - // Calculate poiter to s.out[cap(s.out)] (a past-end pointer) + // Calculate pointer to s.out[cap(s.out)] (a past-end pointer) ADDQ R10, 32(SP) // outBase += outPosition @@ -2376,7 +2376,7 @@ TEXT ·sequenceDecs_decodeSync_bmi2(SB), $64-32 MOVQ 40(SP), CX ADDQ CX, 48(SP) - // Calculate poiter to s.out[cap(s.out)] (a past-end pointer) + // Calculate pointer to s.out[cap(s.out)] (a past-end pointer) ADDQ R9, 32(SP) // outBase += outPosition @@ -2896,7 +2896,7 @@ TEXT ·sequenceDecs_decodeSync_safe_amd64(SB), $64-32 MOVQ 40(SP), AX ADDQ AX, 48(SP) - // Calculate poiter to s.out[cap(s.out)] (a past-end pointer) + // Calculate pointer to s.out[cap(s.out)] (a past-end pointer) ADDQ R10, 32(SP) // outBase += outPosition @@ -3560,7 +3560,7 @@ TEXT ·sequenceDecs_decodeSync_safe_bmi2(SB), $64-32 MOVQ 40(SP), CX ADDQ CX, 48(SP) - // Calculate poiter to s.out[cap(s.out)] (a past-end pointer) + // Calculate pointer to s.out[cap(s.out)] (a past-end pointer) ADDQ R9, 32(SP) // outBase += outPosition diff --git a/vendor/github.com/lestrrat-go/strftime/Changes b/vendor/github.com/lestrrat-go/strftime/Changes index b86a84c4..35cef2bf 100644 --- a/vendor/github.com/lestrrat-go/strftime/Changes +++ b/vendor/github.com/lestrrat-go/strftime/Changes @@ -1,6 +1,13 @@ Changes ======= +v1.1.0 - 28 Aug 2024 +[Miscellaneous] + * github.com/pkg/errors has been removed (it has been two years :) + * Updated build/test actions + * Updated minimum required go version to go 1.21 + * Fix week number handling + v1.0.6 - 20 Apr 2022 [Miscellaneous] * Minimum go version is now go 1.13 diff --git a/vendor/github.com/lestrrat-go/strftime/appenders.go b/vendor/github.com/lestrrat-go/strftime/appenders.go index 2941a247..e09a3e5d 100644 --- a/vendor/github.com/lestrrat-go/strftime/appenders.go +++ b/vendor/github.com/lestrrat-go/strftime/appenders.go @@ -36,13 +36,13 @@ var ( secondsNumberZeroPad = StdlibFormat("05") hms = StdlibFormat("15:04:05") tab = Verbatim("\t") - weekNumberSundayOrigin = weeknumberOffset(0) // week number of the year, Sunday first + weekNumberSundayOrigin = weeknumberOffset(true) // week number of the year, Sunday first weekdayMondayOrigin = weekday(1) // monday as the first day, and 01 as the first value weekNumberMondayOriginOneOrigin = AppendFunc(appendWeekNumber) eby = StdlibFormat("_2-Jan-2006") // monday as the first day, and 00 as the first value - weekNumberMondayOrigin = weeknumberOffset(1) // week number of the year, Monday first + weekNumberMondayOrigin = weeknumberOffset(false) // week number of the year, Monday first weekdaySundayOrigin = weekday(0) natReprTime = StdlibFormat("15:04:05") // national representation of the time XXX is this correct? natReprDate = StdlibFormat("01/02/06") // national representation of the date XXX is this correct? @@ -243,20 +243,16 @@ func (v weekday) Append(b []byte, t time.Time) []byte { return append(b, byte(n+48)) } -type weeknumberOffset int +type weeknumberOffset bool func (v weeknumberOffset) Append(b []byte, t time.Time) []byte { - yd := t.YearDay() - offset := int(t.Weekday()) - int(v) - if offset < 0 { - offset += 7 + offset := int(t.Weekday()) + if v { + offset = 6 - offset + } else if offset != 0 { + offset = 7 - offset } - - if yd < offset { - return append(b, '0', '0') - } - - n := ((yd - offset) / 7) + 1 + n := (t.YearDay() + offset) / 7 if n < 10 { b = append(b, '0') } diff --git a/vendor/github.com/lestrrat-go/strftime/internal/errors/errors_fmt.go b/vendor/github.com/lestrrat-go/strftime/internal/errors/errors_fmt.go deleted file mode 100644 index 18871c14..00000000 --- a/vendor/github.com/lestrrat-go/strftime/internal/errors/errors_fmt.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build strftime_native_errors -// +build strftime_native_errors - -package errors - -import "fmt" - -func New(s string) error { - return fmt.Errorf(s) -} - -func Errorf(s string, args ...interface{}) error { - return fmt.Errorf(s, args...) -} - -func Wrap(err error, s string) error { - return fmt.Errorf(s+`: %w`, err) -} diff --git a/vendor/github.com/lestrrat-go/strftime/internal/errors/errors_pkg.go b/vendor/github.com/lestrrat-go/strftime/internal/errors/errors_pkg.go deleted file mode 100644 index 35797878..00000000 --- a/vendor/github.com/lestrrat-go/strftime/internal/errors/errors_pkg.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build !strftime_native_errors -// +build !strftime_native_errors - -package errors - -import "github.com/pkg/errors" - -func New(s string) error { - return errors.New(s) -} - -func Errorf(s string, args ...interface{}) error { - return errors.Errorf(s, args...) -} - -func Wrap(err error, s string) error { - return errors.Wrap(err, s) -} diff --git a/vendor/github.com/lestrrat-go/strftime/specifications.go b/vendor/github.com/lestrrat-go/strftime/specifications.go index 2b6e11fe..3bd796a9 100644 --- a/vendor/github.com/lestrrat-go/strftime/specifications.go +++ b/vendor/github.com/lestrrat-go/strftime/specifications.go @@ -1,10 +1,9 @@ package strftime import ( + "errors" "fmt" "sync" - - "github.com/lestrrat-go/strftime/internal/errors" ) // because there is no such thing was a sync.RWLocker @@ -124,7 +123,7 @@ func (ds *specificationSet) Lookup(b byte) (Appender, error) { } v, ok := ds.store[b] if !ok { - return nil, errors.Errorf(`lookup failed: '%%%c' was not found in specification set`, b) + return nil, fmt.Errorf(`lookup failed: '%%%c' was not found in specification set`, b) } return v, nil } diff --git a/vendor/github.com/lestrrat-go/strftime/strftime.go b/vendor/github.com/lestrrat-go/strftime/strftime.go index c869491f..3d51ac6d 100644 --- a/vendor/github.com/lestrrat-go/strftime/strftime.go +++ b/vendor/github.com/lestrrat-go/strftime/strftime.go @@ -1,12 +1,12 @@ package strftime import ( + "errors" + "fmt" "io" "strings" "sync" "time" - - "github.com/lestrrat-go/strftime/internal/errors" ) type compileHandler interface { @@ -62,7 +62,7 @@ func compile(handler compileHandler, p string, ds SpecificationSet) error { specification, err := ds.Lookup(p[1]) if err != nil { - return errors.Wrap(err, `pattern compilation failed`) + return fmt.Errorf("pattern compilation failed: %w", err) } handler.handle(specification) @@ -127,14 +127,14 @@ func Format(p string, t time.Time, options ...Option) (string, error) { // TODO: this may be premature optimization ds, err := getSpecificationSetFor(options...) if err != nil { - return "", errors.Wrap(err, `failed to get specification set`) + return "", fmt.Errorf("failed to get specification set: %w", err) } h := getFmtAppendExecutor() defer releasdeFmtAppendExecutor(h) h.t = t if err := compile(h, p, ds); err != nil { - return "", errors.Wrap(err, `failed to compile format`) + return "", fmt.Errorf("failed to compile format: %w", err) } return string(h.dst), nil @@ -152,14 +152,14 @@ func New(p string, options ...Option) (*Strftime, error) { // TODO: this may be premature optimization ds, err := getSpecificationSetFor(options...) if err != nil { - return nil, errors.Wrap(err, `failed to get specification set`) + return nil, fmt.Errorf("failed to get specification set: %w", err) } var h appenderListBuilder h.list = &combiningAppend{} if err := compile(&h, p, ds); err != nil { - return nil, errors.Wrap(err, `failed to compile format`) + return nil, fmt.Errorf("failed to compile format: %w", err) } return &Strftime{ diff --git a/vendor/github.com/mholt/acmez/v2/acme/ari.go b/vendor/github.com/mholt/acmez/v2/acme/ari.go index d096aa92..93401f37 100644 --- a/vendor/github.com/mholt/acmez/v2/acme/ari.go +++ b/vendor/github.com/mholt/acmez/v2/acme/ari.go @@ -140,11 +140,54 @@ func (c *Client) GetRenewalInfo(ctx context.Context, leafCert *x509.Certificate) } var ari RenewalInfo - resp, err := c.httpReq(ctx, http.MethodGet, c.ariEndpoint(certID), nil, &ari) - if err != nil { - return RenewalInfo{}, err + var resp *http.Response + for i := 0; i < 3; i++ { + // backoff between retries; the if is probably not needed, but just for "properness"... + if i > 0 { + select { + case <-ctx.Done(): + return RenewalInfo{}, ctx.Err() + case <-time.After(time.Duration(i*i+1) * time.Second): + } + } + + resp, err = c.httpReq(ctx, http.MethodGet, c.ariEndpoint(certID), nil, &ari) + if err != nil { + if c.Logger != nil { + c.Logger.Warn("error getting ARI response", + zap.Error(err), + zap.Int("attempt", i), + zap.Strings("names", leafCert.DNSNames)) + } + continue + } + + // "If the client receives no response or a malformed response + // (e.g. an end timestamp which is equal to or precedes the start + // timestamp), it SHOULD make its own determination of when to + // renew the certificate, and MAY retry the renewalInfo request + // with appropriate exponential backoff behavior." + // draft-ietf-acme-ari-04 §4.2 + if ari.SuggestedWindow.Start.IsZero() || + ari.SuggestedWindow.End.IsZero() || + ari.SuggestedWindow.Start.Equal(ari.SuggestedWindow.End) || + (ari.SuggestedWindow.End.Unix()-ari.SuggestedWindow.Start.Unix()-1 <= 0) { + if c.Logger != nil { + c.Logger.Debug("invalid ARI window", + zap.Time("start", ari.SuggestedWindow.Start), + zap.Time("end", ari.SuggestedWindow.End), + zap.Strings("names", leafCert.DNSNames)) + } + continue + } + + // valid ARI window + ari.UniqueIdentifier = certID + break + } + if err != nil || resp == nil { + return RenewalInfo{}, fmt.Errorf("could not get a valid ARI response; last error: %v", err) } - ari.UniqueIdentifier = certID // "The server SHOULD include a Retry-After header indicating the polling // interval that the ACME server recommends." draft-ietf-acme-ari-03 §4.2 @@ -161,7 +204,7 @@ func (c *Client) GetRenewalInfo(ctx context.Context, leafCert *x509.Certificate) // time within the suggested window." §4.2 // TODO: It's unclear whether this time should be selected once // or every time the client wakes to check ARI (see step 5 of the - // recommended algorithm); I've inquired here: + // recommended algorithm); I've enquired here: // https://github.com/aarongable/draft-acme-ari/issues/70 // We add 1 to the start time since we are dealing in seconds for // simplicity, but the server may provide sub-second timestamps. diff --git a/vendor/github.com/minio/minio-go/v7/api-put-object-multipart.go b/vendor/github.com/minio/minio-go/v7/api-put-object-multipart.go index 5f117afa..a70cbea9 100644 --- a/vendor/github.com/minio/minio-go/v7/api-put-object-multipart.go +++ b/vendor/github.com/minio/minio-go/v7/api-put-object-multipart.go @@ -24,7 +24,6 @@ import ( "encoding/hex" "encoding/xml" "fmt" - "hash/crc32" "io" "net/http" "net/url" @@ -87,7 +86,7 @@ func (c *Client) putObjectMultipartNoStream(ctx context.Context, bucketName, obj if opts.UserMetadata == nil { opts.UserMetadata = make(map[string]string, 1) } - opts.UserMetadata["X-Amz-Checksum-Algorithm"] = "CRC32C" + opts.UserMetadata["X-Amz-Checksum-Algorithm"] = opts.AutoChecksum.String() } // Initiate a new multipart upload. @@ -116,7 +115,7 @@ func (c *Client) putObjectMultipartNoStream(ctx context.Context, bucketName, obj // CRC32C is ~50% faster on AMD64 @ 30GB/s var crcBytes []byte customHeader := make(http.Header) - crc := crc32.New(crc32.MakeTable(crc32.Castagnoli)) + crc := opts.AutoChecksum.Hasher() for partNumber <= totalPartsCount { length, rErr := readFull(reader, buf) if rErr == io.EOF && partNumber > 1 { @@ -154,7 +153,7 @@ func (c *Client) putObjectMultipartNoStream(ctx context.Context, bucketName, obj crc.Reset() crc.Write(buf[:length]) cSum := crc.Sum(nil) - customHeader.Set("x-amz-checksum-crc32c", base64.StdEncoding.EncodeToString(cSum)) + customHeader.Set(opts.AutoChecksum.Key(), base64.StdEncoding.EncodeToString(cSum)) crcBytes = append(crcBytes, cSum...) } @@ -202,12 +201,13 @@ func (c *Client) putObjectMultipartNoStream(ctx context.Context, bucketName, obj sort.Sort(completedParts(complMultipartUpload.Parts)) opts = PutObjectOptions{ ServerSideEncryption: opts.ServerSideEncryption, + AutoChecksum: opts.AutoChecksum, } if len(crcBytes) > 0 { // Add hash of hashes. crc.Reset() crc.Write(crcBytes) - opts.UserMetadata = map[string]string{"X-Amz-Checksum-Crc32c": base64.StdEncoding.EncodeToString(crc.Sum(nil))} + opts.UserMetadata = map[string]string{opts.AutoChecksum.Key(): base64.StdEncoding.EncodeToString(crc.Sum(nil))} } uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload, opts) if err != nil { diff --git a/vendor/github.com/minio/minio-go/v7/api-put-object-streaming.go b/vendor/github.com/minio/minio-go/v7/api-put-object-streaming.go index 51226630..eef976c8 100644 --- a/vendor/github.com/minio/minio-go/v7/api-put-object-streaming.go +++ b/vendor/github.com/minio/minio-go/v7/api-put-object-streaming.go @@ -22,7 +22,6 @@ import ( "context" "encoding/base64" "fmt" - "hash/crc32" "io" "net/http" "net/url" @@ -109,13 +108,15 @@ func (c *Client) putObjectMultipartStreamFromReadAt(ctx context.Context, bucketN if err != nil { return UploadInfo{}, err } - + if opts.Checksum.IsSet() { + opts.AutoChecksum = opts.Checksum + } withChecksum := c.trailingHeaderSupport if withChecksum { if opts.UserMetadata == nil { opts.UserMetadata = make(map[string]string, 1) } - opts.UserMetadata["X-Amz-Checksum-Algorithm"] = "CRC32C" + opts.UserMetadata["X-Amz-Checksum-Algorithm"] = opts.AutoChecksum.String() } // Initiate a new multipart upload. uploadID, err := c.newUploadID(ctx, bucketName, objectName, opts) @@ -195,10 +196,10 @@ func (c *Client) putObjectMultipartStreamFromReadAt(ctx context.Context, bucketN sectionReader := newHook(io.NewSectionReader(reader, readOffset, partSize), opts.Progress) trailer := make(http.Header, 1) if withChecksum { - crc := crc32.New(crc32.MakeTable(crc32.Castagnoli)) - trailer.Set("x-amz-checksum-crc32c", base64.StdEncoding.EncodeToString(crc.Sum(nil))) + crc := opts.AutoChecksum.Hasher() + trailer.Set(opts.AutoChecksum.Key(), base64.StdEncoding.EncodeToString(crc.Sum(nil))) sectionReader = newHashReaderWrapper(sectionReader, crc, func(hash []byte) { - trailer.Set("x-amz-checksum-crc32c", base64.StdEncoding.EncodeToString(hash)) + trailer.Set(opts.AutoChecksum.Key(), base64.StdEncoding.EncodeToString(hash)) }) } @@ -271,17 +272,18 @@ func (c *Client) putObjectMultipartStreamFromReadAt(ctx context.Context, bucketN opts = PutObjectOptions{ ServerSideEncryption: opts.ServerSideEncryption, + AutoChecksum: opts.AutoChecksum, } if withChecksum { // Add hash of hashes. - crc := crc32.New(crc32.MakeTable(crc32.Castagnoli)) + crc := opts.AutoChecksum.Hasher() for _, part := range complMultipartUpload.Parts { - cs, err := base64.StdEncoding.DecodeString(part.ChecksumCRC32C) + cs, err := base64.StdEncoding.DecodeString(part.Checksum(opts.AutoChecksum)) if err == nil { crc.Write(cs) } } - opts.UserMetadata = map[string]string{"X-Amz-Checksum-Crc32c": base64.StdEncoding.EncodeToString(crc.Sum(nil))} + opts.UserMetadata = map[string]string{opts.AutoChecksum.KeyCapitalized(): base64.StdEncoding.EncodeToString(crc.Sum(nil))} } uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload, opts) @@ -304,11 +306,16 @@ func (c *Client) putObjectMultipartStreamOptionalChecksum(ctx context.Context, b return UploadInfo{}, err } + if opts.Checksum.IsSet() { + opts.AutoChecksum = opts.Checksum + opts.SendContentMd5 = false + } + if !opts.SendContentMd5 { if opts.UserMetadata == nil { opts.UserMetadata = make(map[string]string, 1) } - opts.UserMetadata["X-Amz-Checksum-Algorithm"] = "CRC32C" + opts.UserMetadata["X-Amz-Checksum-Algorithm"] = opts.AutoChecksum.String() } // Calculate the optimal parts info for a given size. @@ -337,7 +344,7 @@ func (c *Client) putObjectMultipartStreamOptionalChecksum(ctx context.Context, b // CRC32C is ~50% faster on AMD64 @ 30GB/s var crcBytes []byte customHeader := make(http.Header) - crc := crc32.New(crc32.MakeTable(crc32.Castagnoli)) + crc := opts.AutoChecksum.Hasher() md5Hash := c.md5Hasher() defer md5Hash.Close() @@ -381,7 +388,7 @@ func (c *Client) putObjectMultipartStreamOptionalChecksum(ctx context.Context, b crc.Reset() crc.Write(buf[:length]) cSum := crc.Sum(nil) - customHeader.Set("x-amz-checksum-crc32c", base64.StdEncoding.EncodeToString(cSum)) + customHeader.Set(opts.AutoChecksum.KeyCapitalized(), base64.StdEncoding.EncodeToString(cSum)) crcBytes = append(crcBytes, cSum...) } @@ -433,12 +440,13 @@ func (c *Client) putObjectMultipartStreamOptionalChecksum(ctx context.Context, b opts = PutObjectOptions{ ServerSideEncryption: opts.ServerSideEncryption, + AutoChecksum: opts.AutoChecksum, } if len(crcBytes) > 0 { // Add hash of hashes. crc.Reset() crc.Write(crcBytes) - opts.UserMetadata = map[string]string{"X-Amz-Checksum-Crc32c": base64.StdEncoding.EncodeToString(crc.Sum(nil))} + opts.UserMetadata = map[string]string{opts.AutoChecksum.KeyCapitalized(): base64.StdEncoding.EncodeToString(crc.Sum(nil))} } uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload, opts) if err != nil { @@ -462,12 +470,15 @@ func (c *Client) putObjectMultipartStreamParallel(ctx context.Context, bucketNam if err = s3utils.CheckValidObjectName(objectName); err != nil { return UploadInfo{}, err } - + if opts.Checksum.IsSet() { + opts.SendContentMd5 = false + opts.AutoChecksum = opts.Checksum + } if !opts.SendContentMd5 { if opts.UserMetadata == nil { opts.UserMetadata = make(map[string]string, 1) } - opts.UserMetadata["X-Amz-Checksum-Algorithm"] = "CRC32C" + opts.UserMetadata["X-Amz-Checksum-Algorithm"] = opts.AutoChecksum.String() } // Cancel all when an error occurs. @@ -500,7 +511,7 @@ func (c *Client) putObjectMultipartStreamParallel(ctx context.Context, bucketNam // Create checksums // CRC32C is ~50% faster on AMD64 @ 30GB/s var crcBytes []byte - crc := crc32.New(crc32.MakeTable(crc32.Castagnoli)) + crc := opts.AutoChecksum.Hasher() // Total data read and written to server. should be equal to 'size' at the end of the call. var totalUploadedSize int64 @@ -554,11 +565,11 @@ func (c *Client) putObjectMultipartStreamParallel(ctx context.Context, bucketNam // Calculate md5sum. customHeader := make(http.Header) if !opts.SendContentMd5 { - // Add CRC32C instead. + // Add Checksum instead. crc.Reset() crc.Write(buf[:length]) cSum := crc.Sum(nil) - customHeader.Set("x-amz-checksum-crc32c", base64.StdEncoding.EncodeToString(cSum)) + customHeader.Set(opts.AutoChecksum.Key(), base64.StdEncoding.EncodeToString(cSum)) crcBytes = append(crcBytes, cSum...) } @@ -639,12 +650,13 @@ func (c *Client) putObjectMultipartStreamParallel(ctx context.Context, bucketNam opts = PutObjectOptions{ ServerSideEncryption: opts.ServerSideEncryption, + AutoChecksum: opts.AutoChecksum, } if len(crcBytes) > 0 { // Add hash of hashes. crc.Reset() crc.Write(crcBytes) - opts.UserMetadata = map[string]string{"X-Amz-Checksum-Crc32c": base64.StdEncoding.EncodeToString(crc.Sum(nil))} + opts.UserMetadata = map[string]string{opts.AutoChecksum.KeyCapitalized(): base64.StdEncoding.EncodeToString(crc.Sum(nil))} } uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload, opts) if err != nil { @@ -675,6 +687,9 @@ func (c *Client) putObject(ctx context.Context, bucketName, objectName string, r if opts.SendContentMd5 && s3utils.IsGoogleEndpoint(*c.endpointURL) && size < 0 { return UploadInfo{}, errInvalidArgument("MD5Sum cannot be calculated with size '-1'") } + if opts.Checksum.IsSet() { + opts.SendContentMd5 = false + } var readSeeker io.Seeker if size > 0 { @@ -744,17 +759,6 @@ func (c *Client) putObjectDo(ctx context.Context, bucketName, objectName string, // Set headers. customHeader := opts.Header() - // Add CRC when client supports it, MD5 is not set, not Google and we don't add SHA256 to chunks. - addCrc := c.trailingHeaderSupport && md5Base64 == "" && !s3utils.IsGoogleEndpoint(*c.endpointURL) && (opts.DisableContentSha256 || c.secure) - - if addCrc { - // If user has added checksums, don't add them ourselves. - for k := range opts.UserMetadata { - if strings.HasPrefix(strings.ToLower(k), "x-amz-checksum-") { - addCrc = false - } - } - } // Populate request metadata. reqMetadata := requestMetadata{ bucketName: bucketName, @@ -765,8 +769,24 @@ func (c *Client) putObjectDo(ctx context.Context, bucketName, objectName string, contentMD5Base64: md5Base64, contentSHA256Hex: sha256Hex, streamSha256: !opts.DisableContentSha256, - addCrc: addCrc, } + // Add CRC when client supports it, MD5 is not set, not Google and we don't add SHA256 to chunks. + addCrc := c.trailingHeaderSupport && md5Base64 == "" && !s3utils.IsGoogleEndpoint(*c.endpointURL) && (opts.DisableContentSha256 || c.secure) + if opts.Checksum.IsSet() { + reqMetadata.addCrc = &opts.Checksum + } else if addCrc { + // If user has added checksums, don't add them ourselves. + for k := range opts.UserMetadata { + if strings.HasPrefix(strings.ToLower(k), "x-amz-checksum-") { + addCrc = false + } + } + if addCrc { + opts.AutoChecksum.SetDefault(ChecksumCRC32C) + reqMetadata.addCrc = &opts.AutoChecksum + } + } + if opts.Internal.SourceVersionID != "" { if opts.Internal.SourceVersionID != nullVersionID { if _, err := uuid.Parse(opts.Internal.SourceVersionID); err != nil { diff --git a/vendor/github.com/minio/minio-go/v7/api-put-object.go b/vendor/github.com/minio/minio-go/v7/api-put-object.go index 6ccb5815..d769648a 100644 --- a/vendor/github.com/minio/minio-go/v7/api-put-object.go +++ b/vendor/github.com/minio/minio-go/v7/api-put-object.go @@ -23,7 +23,6 @@ import ( "encoding/base64" "errors" "fmt" - "hash/crc32" "io" "net/http" "sort" @@ -90,6 +89,18 @@ type PutObjectOptions struct { DisableContentSha256 bool DisableMultipart bool + // AutoChecksum is the type of checksum that will be added if no other checksum is added, + // like MD5 or SHA256 streaming checksum, and it is feasible for the upload type. + // If none is specified CRC32C is used, since it is generally the fastest. + AutoChecksum ChecksumType + + // Checksum will force a checksum of the specific type. + // This requires that the client was created with "TrailingHeaders:true" option, + // and that the destination server supports it. + // Unavailable with V2 signatures & Google endpoints. + // This will disable content MD5 checksums if set. + Checksum ChecksumType + // ConcurrentStreamParts will create NumThreads buffers of PartSize bytes, // fill them serially and upload them in parallel. // This can be used for faster uploads on non-seekable or slow-to-seek input. @@ -236,7 +247,7 @@ func (opts PutObjectOptions) Header() (header http.Header) { } // validate() checks if the UserMetadata map has standard headers or and raises an error if so. -func (opts PutObjectOptions) validate() (err error) { +func (opts PutObjectOptions) validate(c *Client) (err error) { for k, v := range opts.UserMetadata { if !httpguts.ValidHeaderFieldName(k) || isStandardHeader(k) || isSSEHeader(k) || isStorageClassHeader(k) || isMinioHeader(k) { return errInvalidArgument(k + " unsupported user defined metadata name") @@ -251,6 +262,17 @@ func (opts PutObjectOptions) validate() (err error) { if opts.LegalHold != "" && !opts.LegalHold.IsValid() { return errInvalidArgument(opts.LegalHold.String() + " unsupported legal-hold status") } + if opts.Checksum.IsSet() { + switch { + case !c.trailingHeaderSupport: + return errInvalidArgument("Checksum requires Client with TrailingHeaders enabled") + case c.overrideSignerType.IsV2(): + return errInvalidArgument("Checksum cannot be used with v2 signatures") + case s3utils.IsGoogleEndpoint(*c.endpointURL): + return errInvalidArgument("Checksum cannot be used with GCS endpoints") + } + } + return nil } @@ -287,7 +309,7 @@ func (c *Client) PutObject(ctx context.Context, bucketName, objectName string, r return UploadInfo{}, errors.New("object size must be provided with disable multipart upload") } - err = opts.validate() + err = opts.validate(c) if err != nil { return UploadInfo{}, err } @@ -300,6 +322,7 @@ func (c *Client) putObjectCommon(ctx context.Context, bucketName, objectName str if size > int64(maxMultipartPutObjectSize) { return UploadInfo{}, errEntityTooLarge(size, maxMultipartPutObjectSize, bucketName, objectName) } + opts.AutoChecksum.SetDefault(ChecksumCRC32C) // NOTE: Streaming signature is not supported by GCS. if s3utils.IsGoogleEndpoint(*c.endpointURL) { @@ -328,7 +351,7 @@ func (c *Client) putObjectCommon(ctx context.Context, bucketName, objectName str return c.putObjectMultipartStreamNoLength(ctx, bucketName, objectName, reader, opts) } - if size < int64(partSize) || opts.DisableMultipart { + if size <= int64(partSize) || opts.DisableMultipart { return c.putObject(ctx, bucketName, objectName, reader, size, opts) } @@ -357,11 +380,15 @@ func (c *Client) putObjectMultipartStreamNoLength(ctx context.Context, bucketNam return UploadInfo{}, err } + if opts.Checksum.IsSet() { + opts.SendContentMd5 = false + opts.AutoChecksum = opts.Checksum + } if !opts.SendContentMd5 { if opts.UserMetadata == nil { opts.UserMetadata = make(map[string]string, 1) } - opts.UserMetadata["X-Amz-Checksum-Algorithm"] = "CRC32C" + opts.UserMetadata["X-Amz-Checksum-Algorithm"] = opts.AutoChecksum.String() } // Initiate a new multipart upload. @@ -390,7 +417,7 @@ func (c *Client) putObjectMultipartStreamNoLength(ctx context.Context, bucketNam // CRC32C is ~50% faster on AMD64 @ 30GB/s var crcBytes []byte customHeader := make(http.Header) - crc := crc32.New(crc32.MakeTable(crc32.Castagnoli)) + crc := opts.AutoChecksum.Hasher() for partNumber <= totalPartsCount { length, rerr := readFull(reader, buf) @@ -413,7 +440,7 @@ func (c *Client) putObjectMultipartStreamNoLength(ctx context.Context, bucketNam crc.Reset() crc.Write(buf[:length]) cSum := crc.Sum(nil) - customHeader.Set("x-amz-checksum-crc32c", base64.StdEncoding.EncodeToString(cSum)) + customHeader.Set(opts.AutoChecksum.Key(), base64.StdEncoding.EncodeToString(cSum)) crcBytes = append(crcBytes, cSum...) } @@ -466,12 +493,13 @@ func (c *Client) putObjectMultipartStreamNoLength(ctx context.Context, bucketNam opts = PutObjectOptions{ ServerSideEncryption: opts.ServerSideEncryption, + AutoChecksum: opts.AutoChecksum, } if len(crcBytes) > 0 { // Add hash of hashes. crc.Reset() crc.Write(crcBytes) - opts.UserMetadata = map[string]string{"X-Amz-Checksum-Crc32c": base64.StdEncoding.EncodeToString(crc.Sum(nil))} + opts.UserMetadata = map[string]string{opts.AutoChecksum.KeyCapitalized(): base64.StdEncoding.EncodeToString(crc.Sum(nil))} } uploadInfo, err := c.completeMultipartUpload(ctx, bucketName, objectName, uploadID, complMultipartUpload, opts) if err != nil { diff --git a/vendor/github.com/minio/minio-go/v7/api-putobject-snowball.go b/vendor/github.com/minio/minio-go/v7/api-putobject-snowball.go index eb4da414..6b6559bf 100644 --- a/vendor/github.com/minio/minio-go/v7/api-putobject-snowball.go +++ b/vendor/github.com/minio/minio-go/v7/api-putobject-snowball.go @@ -107,7 +107,7 @@ type readSeekCloser interface { // Total size should be < 5TB. // This function blocks until 'objs' is closed and the content has been uploaded. func (c Client) PutObjectsSnowball(ctx context.Context, bucketName string, opts SnowballOptions, objs <-chan SnowballObject) (err error) { - err = opts.Opts.validate() + err = opts.Opts.validate(&c) if err != nil { return err } diff --git a/vendor/github.com/minio/minio-go/v7/api-s3-datatypes.go b/vendor/github.com/minio/minio-go/v7/api-s3-datatypes.go index 1527b746..790606c5 100644 --- a/vendor/github.com/minio/minio-go/v7/api-s3-datatypes.go +++ b/vendor/github.com/minio/minio-go/v7/api-s3-datatypes.go @@ -340,6 +340,22 @@ type CompletePart struct { ChecksumSHA256 string `xml:"ChecksumSHA256,omitempty"` } +// Checksum will return the checksum for the given type. +// Will return the empty string if not set. +func (c CompletePart) Checksum(t ChecksumType) string { + switch { + case t.Is(ChecksumCRC32C): + return c.ChecksumCRC32C + case t.Is(ChecksumCRC32): + return c.ChecksumCRC32 + case t.Is(ChecksumSHA1): + return c.ChecksumSHA1 + case t.Is(ChecksumSHA256): + return c.ChecksumSHA256 + } + return "" +} + // completeMultipartUpload container for completing multipart upload. type completeMultipartUpload struct { XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CompleteMultipartUpload" json:"-"` diff --git a/vendor/github.com/minio/minio-go/v7/api.go b/vendor/github.com/minio/minio-go/v7/api.go index 13c493d0..1d6b6650 100644 --- a/vendor/github.com/minio/minio-go/v7/api.go +++ b/vendor/github.com/minio/minio-go/v7/api.go @@ -23,7 +23,6 @@ import ( "encoding/base64" "errors" "fmt" - "hash/crc32" "io" "math/rand" "net" @@ -129,7 +128,7 @@ type Options struct { // Global constants. const ( libraryName = "minio-go" - libraryVersion = "v7.0.75" + libraryVersion = "v7.0.77" ) // User Agent should always following the below style. @@ -471,7 +470,7 @@ type requestMetadata struct { contentMD5Base64 string // carries base64 encoded md5sum contentSHA256Hex string // carries hex encoded sha256sum streamSha256 bool - addCrc bool + addCrc *ChecksumType trailer http.Header // (http.Request).Trailer. Requires v4 signature. } @@ -616,16 +615,16 @@ func (c *Client) executeMethod(ctx context.Context, method string, metadata requ } } - if metadata.addCrc && metadata.contentLength > 0 { + if metadata.addCrc != nil && metadata.contentLength > 0 { if metadata.trailer == nil { metadata.trailer = make(http.Header, 1) } - crc := crc32.New(crc32.MakeTable(crc32.Castagnoli)) + crc := metadata.addCrc.Hasher() metadata.contentBody = newHashReaderWrapper(metadata.contentBody, crc, func(hash []byte) { // Update trailer when done. - metadata.trailer.Set("x-amz-checksum-crc32c", base64.StdEncoding.EncodeToString(hash)) + metadata.trailer.Set(metadata.addCrc.Key(), base64.StdEncoding.EncodeToString(hash)) }) - metadata.trailer.Set("x-amz-checksum-crc32c", base64.StdEncoding.EncodeToString(crc.Sum(nil))) + metadata.trailer.Set(metadata.addCrc.Key(), base64.StdEncoding.EncodeToString(crc.Sum(nil))) } // Create cancel context to control 'newRetryTimer' go routine. @@ -662,7 +661,7 @@ func (c *Client) executeMethod(ctx context.Context, method string, metadata requ // Initiate the request. res, err = c.do(req) if err != nil { - if isRequestErrorRetryable(err) { + if isRequestErrorRetryable(ctx, err) { // Retry the request continue } diff --git a/vendor/github.com/minio/minio-go/v7/checksum.go b/vendor/github.com/minio/minio-go/v7/checksum.go index a1f6f434..7eb1bf25 100644 --- a/vendor/github.com/minio/minio-go/v7/checksum.go +++ b/vendor/github.com/minio/minio-go/v7/checksum.go @@ -25,6 +25,7 @@ import ( "hash/crc32" "io" "math/bits" + "net/http" ) // ChecksumType contains information about the checksum type. @@ -78,6 +79,11 @@ func (c ChecksumType) Key() string { return "" } +// KeyCapitalized returns the capitalized key as used in HTTP headers. +func (c ChecksumType) KeyCapitalized() string { + return http.CanonicalHeaderKey(c.Key()) +} + // RawByteLen returns the size of the un-encoded checksum. func (c ChecksumType) RawByteLen() int { switch c & checksumMask { @@ -112,6 +118,13 @@ func (c ChecksumType) IsSet() bool { return bits.OnesCount32(uint32(c)) == 1 } +// SetDefault will set the checksum if not already set. +func (c *ChecksumType) SetDefault(t ChecksumType) { + if !c.IsSet() { + *c = t + } +} + // String returns the type as a string. // CRC32, CRC32C, SHA1, and SHA256 for valid values. // Empty string for unset and "" if not valid. diff --git a/vendor/github.com/minio/minio-go/v7/functional_tests.go b/vendor/github.com/minio/minio-go/v7/functional_tests.go index 871034bc..780dc899 100644 --- a/vendor/github.com/minio/minio-go/v7/functional_tests.go +++ b/vendor/github.com/minio/minio-go/v7/functional_tests.go @@ -24,7 +24,6 @@ import ( "archive/zip" "bytes" "context" - "crypto/sha1" "crypto/sha256" "encoding/base64" "errors" @@ -84,7 +83,7 @@ func createHTTPTransport() (transport *http.Transport) { return nil } - if mustParseBool(os.Getenv(skipCERTValidation)) { + if mustParseBool(os.Getenv(enableHTTPS)) && mustParseBool(os.Getenv(skipCERTValidation)) { transport.TLSClientConfig.InsecureSkipVerify = true } @@ -166,7 +165,7 @@ func logError(testName, function string, args map[string]interface{}, startTime } } -// log failed test runs +// Log failed test runs, do not call this directly, use logError instead, as that correctly stops the test run func logFailure(testName, function string, args map[string]interface{}, startTime time.Time, alert, message string, err error) { l := baseLogger(testName, function, args, startTime).With( "status", "FAIL", @@ -2199,22 +2198,15 @@ func testPutObjectWithChecksums() { defer cleanupBucket(bucketName, c) tests := []struct { - header string - hasher hash.Hash - - // Checksum values - ChecksumCRC32 string - ChecksumCRC32C string - ChecksumSHA1 string - ChecksumSHA256 string + cs minio.ChecksumType }{ - {header: "x-amz-checksum-crc32", hasher: crc32.NewIEEE()}, - {header: "x-amz-checksum-crc32c", hasher: crc32.New(crc32.MakeTable(crc32.Castagnoli))}, - {header: "x-amz-checksum-sha1", hasher: sha1.New()}, - {header: "x-amz-checksum-sha256", hasher: sha256.New()}, + {cs: minio.ChecksumCRC32C}, + {cs: minio.ChecksumCRC32}, + {cs: minio.ChecksumSHA1}, + {cs: minio.ChecksumSHA256}, } - for i, test := range tests { + for _, test := range tests { bufSize := dataFileMap["datafile-10-kB"] // Save the data @@ -2235,29 +2227,27 @@ func testPutObjectWithChecksums() { logError(testName, function, args, startTime, "", "Read failed", err) return } - h := test.hasher + h := test.cs.Hasher() h.Reset() - // Wrong CRC. - meta[test.header] = base64.StdEncoding.EncodeToString(h.Sum(nil)) + + // Test with Wrong CRC. + meta[test.cs.Key()] = base64.StdEncoding.EncodeToString(h.Sum(nil)) args["metadata"] = meta args["range"] = "false" + args["checksum"] = test.cs.String() resp, err := c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(b), int64(bufSize), minio.PutObjectOptions{ DisableMultipart: true, UserMetadata: meta, }) if err == nil { - if i == 0 && resp.ChecksumCRC32 == "" { - logIgnored(testName, function, args, startTime, "Checksums does not appear to be supported by backend") - return - } - logError(testName, function, args, startTime, "", "PutObject failed", err) + logError(testName, function, args, startTime, "", "PutObject did not fail on wrong CRC", err) return } // Set correct CRC. h.Write(b) - meta[test.header] = base64.StdEncoding.EncodeToString(h.Sum(nil)) + meta[test.cs.Key()] = base64.StdEncoding.EncodeToString(h.Sum(nil)) reader.Close() resp, err = c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(b), int64(bufSize), minio.PutObjectOptions{ @@ -2344,7 +2334,7 @@ func testPutObjectWithChecksums() { } // Test PutObject with custom checksums. -func testPutMultipartObjectWithChecksums() { +func testPutObjectWithTrailingChecksums() { // initialize logging params startTime := time.Now() testName := getFuncName() @@ -2352,7 +2342,7 @@ func testPutMultipartObjectWithChecksums() { args := map[string]interface{}{ "bucketName": "", "objectName": "", - "opts": "minio.PutObjectOptions{UserMetadata: metadata, Progress: progress}", + "opts": "minio.PutObjectOptions{UserMetadata: metadata, Progress: progress, TrailChecksum: xxx}", } if !isFullMode() { @@ -2366,9 +2356,201 @@ func testPutMultipartObjectWithChecksums() { // Instantiate new minio client object. c, err := minio.New(os.Getenv(serverEndpoint), &minio.Options{ - Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), - Transport: createHTTPTransport(), - Secure: mustParseBool(os.Getenv(enableHTTPS)), + Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), + Transport: createHTTPTransport(), + Secure: mustParseBool(os.Getenv(enableHTTPS)), + TrailingHeaders: true, + }) + if err != nil { + logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) + return + } + + // Enable tracing, write to stderr. + // c.TraceOn(os.Stderr) + + // Set user agent. + c.SetAppInfo("MinIO-go-FunctionalTest", appVersion) + + // Generate a new random bucket name. + bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") + args["bucketName"] = bucketName + + // Make a new bucket. + err = c.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{Region: "us-east-1"}) + if err != nil { + logError(testName, function, args, startTime, "", "Make bucket failed", err) + return + } + + defer cleanupBucket(bucketName, c) + tests := []struct { + cs minio.ChecksumType + }{ + {cs: minio.ChecksumCRC32C}, + {cs: minio.ChecksumCRC32}, + {cs: minio.ChecksumSHA1}, + {cs: minio.ChecksumSHA256}, + } + + for _, test := range tests { + function := "PutObject(bucketName, objectName, reader,size, opts)" + bufSize := dataFileMap["datafile-10-kB"] + + // Save the data + objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "") + args["objectName"] = objectName + + cmpChecksum := func(got, want string) { + if want != got { + logError(testName, function, args, startTime, "", "checksum mismatch", fmt.Errorf("want %s, got %s", want, got)) + return + } + } + + meta := map[string]string{} + reader := getDataReader("datafile-10-kB") + b, err := io.ReadAll(reader) + if err != nil { + logError(testName, function, args, startTime, "", "Read failed", err) + return + } + h := test.cs.Hasher() + h.Reset() + + // Test with Wrong CRC. + args["metadata"] = meta + args["range"] = "false" + args["checksum"] = test.cs.String() + + resp, err := c.PutObject(context.Background(), bucketName, objectName, bytes.NewReader(b), int64(bufSize), minio.PutObjectOptions{ + DisableMultipart: true, + DisableContentSha256: true, + UserMetadata: meta, + Checksum: test.cs, + }) + if err != nil { + logError(testName, function, args, startTime, "", "PutObject failed", err) + return + } + + h.Write(b) + meta[test.cs.Key()] = base64.StdEncoding.EncodeToString(h.Sum(nil)) + + cmpChecksum(resp.ChecksumSHA256, meta["x-amz-checksum-sha256"]) + cmpChecksum(resp.ChecksumSHA1, meta["x-amz-checksum-sha1"]) + cmpChecksum(resp.ChecksumCRC32, meta["x-amz-checksum-crc32"]) + cmpChecksum(resp.ChecksumCRC32C, meta["x-amz-checksum-crc32c"]) + + // Read the data back + gopts := minio.GetObjectOptions{Checksum: true} + + function = "GetObject(...)" + r, err := c.GetObject(context.Background(), bucketName, objectName, gopts) + if err != nil { + logError(testName, function, args, startTime, "", "GetObject failed", err) + return + } + + st, err := r.Stat() + if err != nil { + logError(testName, function, args, startTime, "", "Stat failed", err) + return + } + cmpChecksum(st.ChecksumSHA256, meta["x-amz-checksum-sha256"]) + cmpChecksum(st.ChecksumSHA1, meta["x-amz-checksum-sha1"]) + cmpChecksum(st.ChecksumCRC32, meta["x-amz-checksum-crc32"]) + cmpChecksum(st.ChecksumCRC32C, meta["x-amz-checksum-crc32c"]) + + if st.Size != int64(bufSize) { + logError(testName, function, args, startTime, "", "Number of bytes returned by PutObject does not match GetObject, expected "+string(bufSize)+" got "+string(st.Size), err) + return + } + + if err := r.Close(); err != nil { + logError(testName, function, args, startTime, "", "Object Close failed", err) + return + } + if err := r.Close(); err == nil { + logError(testName, function, args, startTime, "", "Object already closed, should respond with error", err) + return + } + + function = "GetObject( Range...)" + args["range"] = "true" + err = gopts.SetRange(100, 1000) + if err != nil { + logError(testName, function, args, startTime, "", "SetRange failed", err) + return + } + r, err = c.GetObject(context.Background(), bucketName, objectName, gopts) + if err != nil { + logError(testName, function, args, startTime, "", "GetObject failed", err) + return + } + + b, err = io.ReadAll(r) + if err != nil { + logError(testName, function, args, startTime, "", "Read failed", err) + return + } + st, err = r.Stat() + if err != nil { + logError(testName, function, args, startTime, "", "Stat failed", err) + return + } + + // Range requests should return empty checksums... + cmpChecksum(st.ChecksumSHA256, "") + cmpChecksum(st.ChecksumSHA1, "") + cmpChecksum(st.ChecksumCRC32, "") + cmpChecksum(st.ChecksumCRC32C, "") + + function = "GetObjectAttributes(...)" + s, err := c.GetObjectAttributes(context.Background(), bucketName, objectName, minio.ObjectAttributesOptions{}) + if err != nil { + logError(testName, function, args, startTime, "", "GetObjectAttributes failed", err) + return + } + cmpChecksum(s.Checksum.ChecksumSHA256, meta["x-amz-checksum-sha256"]) + cmpChecksum(s.Checksum.ChecksumSHA1, meta["x-amz-checksum-sha1"]) + cmpChecksum(s.Checksum.ChecksumCRC32, meta["x-amz-checksum-crc32"]) + cmpChecksum(s.Checksum.ChecksumCRC32C, meta["x-amz-checksum-crc32c"]) + + delete(args, "range") + delete(args, "metadata") + } + + logSuccess(testName, function, args, startTime) +} + +// Test PutObject with custom checksums. +func testPutMultipartObjectWithChecksums(trailing bool) { + // initialize logging params + startTime := time.Now() + testName := getFuncName() + function := "PutObject(bucketName, objectName, reader,size, opts)" + args := map[string]interface{}{ + "bucketName": "", + "objectName": "", + "opts": fmt.Sprintf("minio.PutObjectOptions{UserMetadata: metadata, Progress: progress Checksum: %v}", trailing), + } + + if !isFullMode() { + logIgnored(testName, function, args, startTime, "Skipping functional tests for short/quick runs") + return + } + + // Seed random based on current time. + rand.Seed(time.Now().Unix()) + + // Instantiate new minio client object. + c, err := minio.New(os.Getenv(serverEndpoint), + &minio.Options{ + Creds: credentials.NewStaticV4(os.Getenv(accessKey), os.Getenv(secretKey), ""), + Transport: createHTTPTransport(), + Secure: mustParseBool(os.Getenv(enableHTTPS)), + TrailingHeaders: trailing, }) if err != nil { logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) @@ -2419,17 +2601,12 @@ func testPutMultipartObjectWithChecksums() { } defer cleanupBucket(bucketName, c) tests := []struct { - header string - hasher hash.Hash - - // Checksum values - ChecksumCRC32 string - ChecksumCRC32C string - ChecksumSHA1 string - ChecksumSHA256 string + cs minio.ChecksumType }{ - // Currently there is no way to override the checksum type. - {header: "x-amz-checksum-crc32c", hasher: crc32.New(crc32.MakeTable(crc32.Castagnoli)), ChecksumCRC32C: "OpEx0Q==-13"}, + {cs: minio.ChecksumCRC32C}, + {cs: minio.ChecksumCRC32}, + {cs: minio.ChecksumSHA1}, + {cs: minio.ChecksumSHA256}, } for _, test := range tests { @@ -2438,11 +2615,12 @@ func testPutMultipartObjectWithChecksums() { // Save the data objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "") args["objectName"] = objectName + args["checksum"] = test.cs.String() cmpChecksum := func(got, want string) { if want != got { - // logError(testName, function, args, startTime, "", "checksum mismatch", fmt.Errorf("want %s, got %s", want, got)) - fmt.Printf("want %s, got %s\n", want, got) + logError(testName, function, args, startTime, "", "checksum mismatch", fmt.Errorf("want %s, got %s", want, got)) + //fmt.Printf("want %s, got %s\n", want, got) return } } @@ -2455,26 +2633,57 @@ func testPutMultipartObjectWithChecksums() { return } reader.Close() - h := test.hasher + h := test.cs.Hasher() h.Reset() - test.ChecksumCRC32C = hashMultiPart(b, partSize, test.hasher) + want := hashMultiPart(b, partSize, test.cs.Hasher()) + var cs minio.ChecksumType + rd := io.Reader(io.NopCloser(bytes.NewReader(b))) + if trailing { + cs = test.cs + rd = bytes.NewReader(b) + } // Set correct CRC. - - resp, err := c.PutObject(context.Background(), bucketName, objectName, io.NopCloser(bytes.NewReader(b)), int64(bufSize), minio.PutObjectOptions{ + resp, err := c.PutObject(context.Background(), bucketName, objectName, rd, int64(bufSize), minio.PutObjectOptions{ DisableContentSha256: true, DisableMultipart: false, UserMetadata: nil, PartSize: partSize, + AutoChecksum: test.cs, + Checksum: cs, }) if err != nil { logError(testName, function, args, startTime, "", "PutObject failed", err) return } - cmpChecksum(resp.ChecksumSHA256, test.ChecksumSHA256) - cmpChecksum(resp.ChecksumSHA1, test.ChecksumSHA1) - cmpChecksum(resp.ChecksumCRC32, test.ChecksumCRC32) - cmpChecksum(resp.ChecksumCRC32C, test.ChecksumCRC32C) + + switch test.cs { + case minio.ChecksumCRC32C: + cmpChecksum(resp.ChecksumCRC32C, want) + case minio.ChecksumCRC32: + cmpChecksum(resp.ChecksumCRC32, want) + case minio.ChecksumSHA1: + cmpChecksum(resp.ChecksumSHA1, want) + case minio.ChecksumSHA256: + cmpChecksum(resp.ChecksumSHA256, want) + } + + s, err := c.GetObjectAttributes(context.Background(), bucketName, objectName, minio.ObjectAttributesOptions{}) + if err != nil { + logError(testName, function, args, startTime, "", "GetObjectAttributes failed", err) + return + } + want = want[:strings.IndexByte(want, '-')] + switch test.cs { + case minio.ChecksumCRC32C: + cmpChecksum(s.Checksum.ChecksumCRC32C, want) + case minio.ChecksumCRC32: + cmpChecksum(s.Checksum.ChecksumCRC32, want) + case minio.ChecksumSHA1: + cmpChecksum(s.Checksum.ChecksumSHA1, want) + case minio.ChecksumSHA256: + cmpChecksum(s.Checksum.ChecksumSHA256, want) + } // Read the data back gopts := minio.GetObjectOptions{Checksum: true} @@ -2496,18 +2705,17 @@ func testPutMultipartObjectWithChecksums() { // Test part 2 checksum... h.Reset() h.Write(b[partSize : 2*partSize]) - got := base64.StdEncoding.EncodeToString(h.Sum(nil)) - if test.ChecksumSHA256 != "" { - cmpChecksum(st.ChecksumSHA256, got) - } - if test.ChecksumSHA1 != "" { - cmpChecksum(st.ChecksumSHA1, got) - } - if test.ChecksumCRC32 != "" { - cmpChecksum(st.ChecksumCRC32, got) - } - if test.ChecksumCRC32C != "" { - cmpChecksum(st.ChecksumCRC32C, got) + want = base64.StdEncoding.EncodeToString(h.Sum(nil)) + + switch test.cs { + case minio.ChecksumCRC32C: + cmpChecksum(st.ChecksumCRC32C, want) + case minio.ChecksumCRC32: + cmpChecksum(st.ChecksumCRC32, want) + case minio.ChecksumSHA1: + cmpChecksum(st.ChecksumSHA1, want) + case minio.ChecksumSHA256: + cmpChecksum(st.ChecksumSHA256, want) } delete(args, "metadata") @@ -2972,6 +3180,7 @@ func testGetObjectAttributes() { testFiles[i].UploadInfo, err = c.PutObject(context.Background(), v.Bucket, v.Object, reader, int64(bufSize), minio.PutObjectOptions{ ContentType: v.ContentType, SendContentMd5: v.SendContentMd5, + Checksum: minio.ChecksumCRC32C, }) if err != nil { logError(testName, function, args, startTime, "", "PutObject failed", err) @@ -3053,7 +3262,7 @@ func testGetObjectAttributes() { test: objectAttributesTestOptions{ TestFileName: "file1", StorageClass: "STANDARD", - HasFullChecksum: false, + HasFullChecksum: true, }, } @@ -3142,9 +3351,10 @@ func testGetObjectAttributesSSECEncryption() { info, err := c.PutObject(context.Background(), bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ ContentType: "content/custom", - SendContentMd5: true, + SendContentMd5: false, ServerSideEncryption: sse, PartSize: uint64(bufSize) / 2, + Checksum: minio.ChecksumCRC32C, }) if err != nil { logError(testName, function, args, startTime, "", "PutObject failed", err) @@ -3164,9 +3374,9 @@ func testGetObjectAttributesSSECEncryption() { ETag: info.ETag, NumberOfParts: 2, ObjectSize: int(info.Size), - HasFullChecksum: false, + HasFullChecksum: true, HasParts: true, - HasPartChecksums: false, + HasPartChecksums: true, }) if err != nil { logError(testName, function, args, startTime, "", "Validating GetObjectsAttributes response failed", err) @@ -5584,18 +5794,12 @@ func testPresignedPostPolicy() { } writer.Close() - transport, err := minio.DefaultTransport(mustParseBool(os.Getenv(enableHTTPS))) - if err != nil { - logError(testName, function, args, startTime, "", "DefaultTransport failed", err) - return - } - httpClient := &http.Client{ // Setting a sensible time out of 30secs to wait for response // headers. Request is pro-actively canceled after 30secs // with no response. Timeout: 30 * time.Second, - Transport: transport, + Transport: createHTTPTransport(), } args["url"] = presignedPostPolicyURL.String() @@ -7509,7 +7713,7 @@ func testFunctional() { return } - transport, err := minio.DefaultTransport(mustParseBool(os.Getenv(enableHTTPS))) + transport := createHTTPTransport() if err != nil { logError(testName, function, args, startTime, "", "DefaultTransport failed", err) return @@ -12440,18 +12644,12 @@ func testFunctionalV2() { return } - transport, err := minio.DefaultTransport(mustParseBool(os.Getenv(enableHTTPS))) - if err != nil { - logError(testName, function, args, startTime, "", "DefaultTransport failed", err) - return - } - httpClient := &http.Client{ // Setting a sensible time out of 30secs to wait for response // headers. Request is pro-actively canceled after 30secs // with no response. Timeout: 30 * time.Second, - Transport: transport, + Transport: createHTTPTransport(), } req, err := http.NewRequest(http.MethodHead, presignedHeadURL.String(), nil) @@ -13500,7 +13698,7 @@ func testCors() { Secure: mustParseBool(os.Getenv(enableHTTPS)), }) if err != nil { - logFailure(testName, function, args, startTime, "", "MinIO client object creation failed", err) + logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } @@ -13516,7 +13714,7 @@ func testCors() { bucketName = randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") err = c.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: "us-east-1"}) if err != nil { - logFailure(testName, function, args, startTime, "", "MakeBucket failed", err) + logError(testName, function, args, startTime, "", "MakeBucket failed", err) return } } @@ -13526,7 +13724,7 @@ func testCors() { publicPolicy := `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:*"],"Resource":["arn:aws:s3:::` + bucketName + `", "arn:aws:s3:::` + bucketName + `/*"]}]}` err = c.SetBucketPolicy(ctx, bucketName, publicPolicy) if err != nil { - logFailure(testName, function, args, startTime, "", "SetBucketPolicy failed", err) + logError(testName, function, args, startTime, "", "SetBucketPolicy failed", err) return } @@ -13540,20 +13738,15 @@ func testCors() { _, err = c.PutObject(ctx, bucketName, objectName, reader, int64(bufSize), minio.PutObjectOptions{ContentType: "binary/octet-stream"}) if err != nil { - logFailure(testName, function, args, startTime, "", "PutObject call failed", err) + logError(testName, function, args, startTime, "", "PutObject call failed", err) return } bucketURL := c.EndpointURL().String() + "/" + bucketName + "/" objectURL := bucketURL + objectName - transport, err := minio.DefaultTransport(mustParseBool(os.Getenv(enableHTTPS))) - if err != nil { - logFailure(testName, function, args, startTime, "", "DefaultTransport failed", err) - return - } httpClient := &http.Client{ Timeout: 30 * time.Second, - Transport: transport, + Transport: createHTTPTransport(), } errStrAccessForbidden := `AccessForbiddenCORSResponse: This CORS request is not allowed. This is usually because the evalution of Origin, request method / Access-Control-Request-Method or Access-Control-Request-Headers are not whitelisted` @@ -14156,7 +14349,7 @@ func testCors() { } err = c.SetBucketCors(ctx, bucketName, corsConfig) if err != nil { - logFailure(testName, function, args, startTime, "", "SetBucketCors failed to apply", err) + logError(testName, function, args, startTime, "", "SetBucketCors failed to apply", err) return } } @@ -14165,7 +14358,7 @@ func testCors() { if test.method != "" && test.url != "" { req, err := http.NewRequestWithContext(ctx, test.method, test.url, nil) if err != nil { - logFailure(testName, function, args, startTime, "", "HTTP request creation failed", err) + logError(testName, function, args, startTime, "", "HTTP request creation failed", err) return } req.Header.Set("User-Agent", "MinIO-go-FunctionalTest/"+appVersion) @@ -14175,7 +14368,7 @@ func testCors() { } resp, err := httpClient.Do(req) if err != nil { - logFailure(testName, function, args, startTime, "", "HTTP request failed", err) + logError(testName, function, args, startTime, "", "HTTP request failed", err) return } defer resp.Body.Close() @@ -14183,7 +14376,7 @@ func testCors() { // Check returned status code if resp.StatusCode != test.wantStatus { errStr := fmt.Sprintf(" incorrect status code in response, want: %d, got: %d", test.wantStatus, resp.StatusCode) - logFailure(testName, function, args, startTime, "", errStr, nil) + logError(testName, function, args, startTime, "", errStr, nil) return } @@ -14191,12 +14384,12 @@ func testCors() { if test.wantBodyContains != "" { body, err := io.ReadAll(resp.Body) if err != nil { - logFailure(testName, function, args, startTime, "", "Failed to read response body", err) + logError(testName, function, args, startTime, "", "Failed to read response body", err) return } if !strings.Contains(string(body), test.wantBodyContains) { errStr := fmt.Sprintf(" incorrect body in response, want: %s, in got: %s", test.wantBodyContains, string(body)) - logFailure(testName, function, args, startTime, "", errStr, nil) + logError(testName, function, args, startTime, "", errStr, nil) return } } @@ -14213,7 +14406,7 @@ func testCors() { gotVal = strings.ReplaceAll(gotVal, " ", "") if gotVal != v { errStr := fmt.Sprintf(" incorrect header in response, want: %s: '%s', got: '%s'", k, v, gotVal) - logFailure(testName, function, args, startTime, "", errStr, nil) + logError(testName, function, args, startTime, "", errStr, nil) return } } @@ -14241,7 +14434,7 @@ func testCorsSetGetDelete() { Secure: mustParseBool(os.Getenv(enableHTTPS)), }) if err != nil { - logFailure(testName, function, args, startTime, "", "MinIO client object creation failed", err) + logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) return } @@ -14258,7 +14451,7 @@ func testCorsSetGetDelete() { // Make a new bucket. err = c.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: "us-east-1"}) if err != nil { - logFailure(testName, function, args, startTime, "", "MakeBucket failed", err) + logError(testName, function, args, startTime, "", "MakeBucket failed", err) return } defer cleanupBucket(bucketName, c) @@ -14284,37 +14477,37 @@ func testCorsSetGetDelete() { corsConfig := cors.NewConfig(corsRules) err = c.SetBucketCors(ctx, bucketName, corsConfig) if err != nil { - logFailure(testName, function, args, startTime, "", "SetBucketCors failed to apply", err) + logError(testName, function, args, startTime, "", "SetBucketCors failed to apply", err) return } // Get the rules and check they match what we set gotCorsConfig, err := c.GetBucketCors(ctx, bucketName) if err != nil { - logFailure(testName, function, args, startTime, "", "GetBucketCors failed", err) + logError(testName, function, args, startTime, "", "GetBucketCors failed", err) return } if !reflect.DeepEqual(corsConfig, gotCorsConfig) { msg := fmt.Sprintf("GetBucketCors returned unexpected rules, expected: %+v, got: %+v", corsConfig, gotCorsConfig) - logFailure(testName, function, args, startTime, "", msg, nil) + logError(testName, function, args, startTime, "", msg, nil) return } // Delete the rules err = c.SetBucketCors(ctx, bucketName, nil) if err != nil { - logFailure(testName, function, args, startTime, "", "SetBucketCors failed to delete", err) + logError(testName, function, args, startTime, "", "SetBucketCors failed to delete", err) return } // Get the rules and check they are now empty gotCorsConfig, err = c.GetBucketCors(ctx, bucketName) if err != nil { - logFailure(testName, function, args, startTime, "", "GetBucketCors failed", err) + logError(testName, function, args, startTime, "", "GetBucketCors failed", err) return } if gotCorsConfig != nil { - logFailure(testName, function, args, startTime, "", "GetBucketCors returned unexpected rules", nil) + logError(testName, function, args, startTime, "", "GetBucketCors returned unexpected rules", nil) return } @@ -14747,7 +14940,9 @@ func main() { testCompose10KSourcesV2() testUserMetadataCopyingV2() testPutObjectWithChecksums() - testPutMultipartObjectWithChecksums() + testPutObjectWithTrailingChecksums() + testPutMultipartObjectWithChecksums(false) + testPutMultipartObjectWithChecksums(true) testPutObject0ByteV2() testPutObjectNoLengthV2() testPutObjectsUnknownV2() diff --git a/vendor/github.com/minio/minio-go/v7/post-policy.go b/vendor/github.com/minio/minio-go/v7/post-policy.go index 3f023704..19687e02 100644 --- a/vendor/github.com/minio/minio-go/v7/post-policy.go +++ b/vendor/github.com/minio/minio-go/v7/post-policy.go @@ -301,6 +301,25 @@ func (p *PostPolicy) SetUserMetadata(key, value string) error { return nil } +// SetUserMetadataStartsWith - Set how an user metadata should starts with. +// Can be retrieved through a HEAD request or an event. +func (p *PostPolicy) SetUserMetadataStartsWith(key, value string) error { + if strings.TrimSpace(key) == "" || key == "" { + return errInvalidArgument("Key is empty") + } + headerName := fmt.Sprintf("x-amz-meta-%s", key) + policyCond := policyCondition{ + matchType: "starts-with", + condition: fmt.Sprintf("$%s", headerName), + value: value, + } + if err := p.addNewPolicy(policyCond); err != nil { + return err + } + p.formData[headerName] = value + return nil +} + // SetChecksum sets the checksum of the request. func (p *PostPolicy) SetChecksum(c Checksum) { if c.IsSet() { diff --git a/vendor/github.com/minio/minio-go/v7/retry.go b/vendor/github.com/minio/minio-go/v7/retry.go index 5ddcad89..d15eb590 100644 --- a/vendor/github.com/minio/minio-go/v7/retry.go +++ b/vendor/github.com/minio/minio-go/v7/retry.go @@ -129,9 +129,10 @@ func isHTTPStatusRetryable(httpStatusCode int) (ok bool) { } // For now, all http Do() requests are retriable except some well defined errors -func isRequestErrorRetryable(err error) bool { +func isRequestErrorRetryable(ctx context.Context, err error) bool { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - return false + // Retry if internal timeout in the HTTP call. + return ctx.Err() == nil } if ue, ok := err.(*url.Error); ok { e := ue.Unwrap() diff --git a/vendor/github.com/pkg/errors/.gitignore b/vendor/github.com/pkg/errors/.gitignore deleted file mode 100644 index daf913b1..00000000 --- a/vendor/github.com/pkg/errors/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.prof diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml deleted file mode 100644 index 9159de03..00000000 --- a/vendor/github.com/pkg/errors/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: go -go_import_path: github.com/pkg/errors -go: - - 1.11.x - - 1.12.x - - 1.13.x - - tip - -script: - - make check diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE deleted file mode 100644 index 835ba3e7..00000000 --- a/vendor/github.com/pkg/errors/LICENSE +++ /dev/null @@ -1,23 +0,0 @@ -Copyright (c) 2015, Dave Cheney -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/errors/Makefile b/vendor/github.com/pkg/errors/Makefile deleted file mode 100644 index ce9d7cde..00000000 --- a/vendor/github.com/pkg/errors/Makefile +++ /dev/null @@ -1,44 +0,0 @@ -PKGS := github.com/pkg/errors -SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS)) -GO := go - -check: test vet gofmt misspell unconvert staticcheck ineffassign unparam - -test: - $(GO) test $(PKGS) - -vet: | test - $(GO) vet $(PKGS) - -staticcheck: - $(GO) get honnef.co/go/tools/cmd/staticcheck - staticcheck -checks all $(PKGS) - -misspell: - $(GO) get github.com/client9/misspell/cmd/misspell - misspell \ - -locale GB \ - -error \ - *.md *.go - -unconvert: - $(GO) get github.com/mdempsky/unconvert - unconvert -v $(PKGS) - -ineffassign: - $(GO) get github.com/gordonklaus/ineffassign - find $(SRCDIRS) -name '*.go' | xargs ineffassign - -pedantic: check errcheck - -unparam: - $(GO) get mvdan.cc/unparam - unparam ./... - -errcheck: - $(GO) get github.com/kisielk/errcheck - errcheck $(PKGS) - -gofmt: - @echo Checking code is gofmted - @test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)" diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md deleted file mode 100644 index 54dfdcb1..00000000 --- a/vendor/github.com/pkg/errors/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge) - -Package errors provides simple error handling primitives. - -`go get github.com/pkg/errors` - -The traditional error handling idiom in Go is roughly akin to -```go -if err != nil { - return err -} -``` -which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. - -## Adding context to an error - -The errors.Wrap function returns a new error that adds context to the original error. For example -```go -_, err := ioutil.ReadAll(r) -if err != nil { - return errors.Wrap(err, "read failed") -} -``` -## Retrieving the cause of an error - -Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. -```go -type causer interface { - Cause() error -} -``` -`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: -```go -switch err := errors.Cause(err).(type) { -case *MyError: - // handle specifically -default: - // unknown error -} -``` - -[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). - -## Roadmap - -With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows: - -- 0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible) -- 1.0. Final release. - -## Contributing - -Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports. - -Before sending a PR, please discuss your change by raising an issue. - -## License - -BSD-2-Clause diff --git a/vendor/github.com/pkg/errors/appveyor.yml b/vendor/github.com/pkg/errors/appveyor.yml deleted file mode 100644 index a932eade..00000000 --- a/vendor/github.com/pkg/errors/appveyor.yml +++ /dev/null @@ -1,32 +0,0 @@ -version: build-{build}.{branch} - -clone_folder: C:\gopath\src\github.com\pkg\errors -shallow_clone: true # for startup speed - -environment: - GOPATH: C:\gopath - -platform: - - x64 - -# http://www.appveyor.com/docs/installed-software -install: - # some helpful output for debugging builds - - go version - - go env - # pre-installed MinGW at C:\MinGW is 32bit only - # but MSYS2 at C:\msys64 has mingw64 - - set PATH=C:\msys64\mingw64\bin;%PATH% - - gcc --version - - g++ --version - -build_script: - - go install -v ./... - -test_script: - - set PATH=C:\gopath\bin;%PATH% - - go test -v ./... - -#artifacts: -# - path: '%GOPATH%\bin\*.exe' -deploy: off diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go deleted file mode 100644 index 161aea25..00000000 --- a/vendor/github.com/pkg/errors/errors.go +++ /dev/null @@ -1,288 +0,0 @@ -// Package errors provides simple error handling primitives. -// -// The traditional error handling idiom in Go is roughly akin to -// -// if err != nil { -// return err -// } -// -// which when applied recursively up the call stack results in error reports -// without context or debugging information. The errors package allows -// programmers to add context to the failure path in their code in a way -// that does not destroy the original value of the error. -// -// Adding context to an error -// -// The errors.Wrap function returns a new error that adds context to the -// original error by recording a stack trace at the point Wrap is called, -// together with the supplied message. For example -// -// _, err := ioutil.ReadAll(r) -// if err != nil { -// return errors.Wrap(err, "read failed") -// } -// -// If additional control is required, the errors.WithStack and -// errors.WithMessage functions destructure errors.Wrap into its component -// operations: annotating an error with a stack trace and with a message, -// respectively. -// -// Retrieving the cause of an error -// -// Using errors.Wrap constructs a stack of errors, adding context to the -// preceding error. Depending on the nature of the error it may be necessary -// to reverse the operation of errors.Wrap to retrieve the original error -// for inspection. Any error value which implements this interface -// -// type causer interface { -// Cause() error -// } -// -// can be inspected by errors.Cause. errors.Cause will recursively retrieve -// the topmost error that does not implement causer, which is assumed to be -// the original cause. For example: -// -// switch err := errors.Cause(err).(type) { -// case *MyError: -// // handle specifically -// default: -// // unknown error -// } -// -// Although the causer interface is not exported by this package, it is -// considered a part of its stable public interface. -// -// Formatted printing of errors -// -// All error values returned from this package implement fmt.Formatter and can -// be formatted by the fmt package. The following verbs are supported: -// -// %s print the error. If the error has a Cause it will be -// printed recursively. -// %v see %s -// %+v extended format. Each Frame of the error's StackTrace will -// be printed in detail. -// -// Retrieving the stack trace of an error or wrapper -// -// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are -// invoked. This information can be retrieved with the following interface: -// -// type stackTracer interface { -// StackTrace() errors.StackTrace -// } -// -// The returned errors.StackTrace type is defined as -// -// type StackTrace []Frame -// -// The Frame type represents a call site in the stack trace. Frame supports -// the fmt.Formatter interface that can be used for printing information about -// the stack trace of this error. For example: -// -// if err, ok := err.(stackTracer); ok { -// for _, f := range err.StackTrace() { -// fmt.Printf("%+s:%d\n", f, f) -// } -// } -// -// Although the stackTracer interface is not exported by this package, it is -// considered a part of its stable public interface. -// -// See the documentation for Frame.Format for more details. -package errors - -import ( - "fmt" - "io" -) - -// New returns an error with the supplied message. -// New also records the stack trace at the point it was called. -func New(message string) error { - return &fundamental{ - msg: message, - stack: callers(), - } -} - -// Errorf formats according to a format specifier and returns the string -// as a value that satisfies error. -// Errorf also records the stack trace at the point it was called. -func Errorf(format string, args ...interface{}) error { - return &fundamental{ - msg: fmt.Sprintf(format, args...), - stack: callers(), - } -} - -// fundamental is an error that has a message and a stack, but no caller. -type fundamental struct { - msg string - *stack -} - -func (f *fundamental) Error() string { return f.msg } - -func (f *fundamental) Format(s fmt.State, verb rune) { - switch verb { - case 'v': - if s.Flag('+') { - io.WriteString(s, f.msg) - f.stack.Format(s, verb) - return - } - fallthrough - case 's': - io.WriteString(s, f.msg) - case 'q': - fmt.Fprintf(s, "%q", f.msg) - } -} - -// WithStack annotates err with a stack trace at the point WithStack was called. -// If err is nil, WithStack returns nil. -func WithStack(err error) error { - if err == nil { - return nil - } - return &withStack{ - err, - callers(), - } -} - -type withStack struct { - error - *stack -} - -func (w *withStack) Cause() error { return w.error } - -// Unwrap provides compatibility for Go 1.13 error chains. -func (w *withStack) Unwrap() error { return w.error } - -func (w *withStack) Format(s fmt.State, verb rune) { - switch verb { - case 'v': - if s.Flag('+') { - fmt.Fprintf(s, "%+v", w.Cause()) - w.stack.Format(s, verb) - return - } - fallthrough - case 's': - io.WriteString(s, w.Error()) - case 'q': - fmt.Fprintf(s, "%q", w.Error()) - } -} - -// Wrap returns an error annotating err with a stack trace -// at the point Wrap is called, and the supplied message. -// If err is nil, Wrap returns nil. -func Wrap(err error, message string) error { - if err == nil { - return nil - } - err = &withMessage{ - cause: err, - msg: message, - } - return &withStack{ - err, - callers(), - } -} - -// Wrapf returns an error annotating err with a stack trace -// at the point Wrapf is called, and the format specifier. -// If err is nil, Wrapf returns nil. -func Wrapf(err error, format string, args ...interface{}) error { - if err == nil { - return nil - } - err = &withMessage{ - cause: err, - msg: fmt.Sprintf(format, args...), - } - return &withStack{ - err, - callers(), - } -} - -// WithMessage annotates err with a new message. -// If err is nil, WithMessage returns nil. -func WithMessage(err error, message string) error { - if err == nil { - return nil - } - return &withMessage{ - cause: err, - msg: message, - } -} - -// WithMessagef annotates err with the format specifier. -// If err is nil, WithMessagef returns nil. -func WithMessagef(err error, format string, args ...interface{}) error { - if err == nil { - return nil - } - return &withMessage{ - cause: err, - msg: fmt.Sprintf(format, args...), - } -} - -type withMessage struct { - cause error - msg string -} - -func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } -func (w *withMessage) Cause() error { return w.cause } - -// Unwrap provides compatibility for Go 1.13 error chains. -func (w *withMessage) Unwrap() error { return w.cause } - -func (w *withMessage) Format(s fmt.State, verb rune) { - switch verb { - case 'v': - if s.Flag('+') { - fmt.Fprintf(s, "%+v\n", w.Cause()) - io.WriteString(s, w.msg) - return - } - fallthrough - case 's', 'q': - io.WriteString(s, w.Error()) - } -} - -// Cause returns the underlying cause of the error, if possible. -// An error value has a cause if it implements the following -// interface: -// -// type causer interface { -// Cause() error -// } -// -// If the error does not implement Cause, the original error will -// be returned. If the error is nil, nil will be returned without further -// investigation. -func Cause(err error) error { - type causer interface { - Cause() error - } - - for err != nil { - cause, ok := err.(causer) - if !ok { - break - } - err = cause.Cause() - } - return err -} diff --git a/vendor/github.com/pkg/errors/go113.go b/vendor/github.com/pkg/errors/go113.go deleted file mode 100644 index be0d10d0..00000000 --- a/vendor/github.com/pkg/errors/go113.go +++ /dev/null @@ -1,38 +0,0 @@ -// +build go1.13 - -package errors - -import ( - stderrors "errors" -) - -// Is reports whether any error in err's chain matches target. -// -// The chain consists of err itself followed by the sequence of errors obtained by -// repeatedly calling Unwrap. -// -// An error is considered to match a target if it is equal to that target or if -// it implements a method Is(error) bool such that Is(target) returns true. -func Is(err, target error) bool { return stderrors.Is(err, target) } - -// As finds the first error in err's chain that matches target, and if so, sets -// target to that error value and returns true. -// -// The chain consists of err itself followed by the sequence of errors obtained by -// repeatedly calling Unwrap. -// -// An error matches target if the error's concrete value is assignable to the value -// pointed to by target, or if the error has a method As(interface{}) bool such that -// As(target) returns true. In the latter case, the As method is responsible for -// setting target. -// -// As will panic if target is not a non-nil pointer to either a type that implements -// error, or to any interface type. As returns false if err is nil. -func As(err error, target interface{}) bool { return stderrors.As(err, target) } - -// Unwrap returns the result of calling the Unwrap method on err, if err's -// type contains an Unwrap method returning error. -// Otherwise, Unwrap returns nil. -func Unwrap(err error) error { - return stderrors.Unwrap(err) -} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go deleted file mode 100644 index 779a8348..00000000 --- a/vendor/github.com/pkg/errors/stack.go +++ /dev/null @@ -1,177 +0,0 @@ -package errors - -import ( - "fmt" - "io" - "path" - "runtime" - "strconv" - "strings" -) - -// Frame represents a program counter inside a stack frame. -// For historical reasons if Frame is interpreted as a uintptr -// its value represents the program counter + 1. -type Frame uintptr - -// pc returns the program counter for this frame; -// multiple frames may have the same PC value. -func (f Frame) pc() uintptr { return uintptr(f) - 1 } - -// file returns the full path to the file that contains the -// function for this Frame's pc. -func (f Frame) file() string { - fn := runtime.FuncForPC(f.pc()) - if fn == nil { - return "unknown" - } - file, _ := fn.FileLine(f.pc()) - return file -} - -// line returns the line number of source code of the -// function for this Frame's pc. -func (f Frame) line() int { - fn := runtime.FuncForPC(f.pc()) - if fn == nil { - return 0 - } - _, line := fn.FileLine(f.pc()) - return line -} - -// name returns the name of this function, if known. -func (f Frame) name() string { - fn := runtime.FuncForPC(f.pc()) - if fn == nil { - return "unknown" - } - return fn.Name() -} - -// Format formats the frame according to the fmt.Formatter interface. -// -// %s source file -// %d source line -// %n function name -// %v equivalent to %s:%d -// -// Format accepts flags that alter the printing of some verbs, as follows: -// -// %+s function name and path of source file relative to the compile time -// GOPATH separated by \n\t (\n\t) -// %+v equivalent to %+s:%d -func (f Frame) Format(s fmt.State, verb rune) { - switch verb { - case 's': - switch { - case s.Flag('+'): - io.WriteString(s, f.name()) - io.WriteString(s, "\n\t") - io.WriteString(s, f.file()) - default: - io.WriteString(s, path.Base(f.file())) - } - case 'd': - io.WriteString(s, strconv.Itoa(f.line())) - case 'n': - io.WriteString(s, funcname(f.name())) - case 'v': - f.Format(s, 's') - io.WriteString(s, ":") - f.Format(s, 'd') - } -} - -// MarshalText formats a stacktrace Frame as a text string. The output is the -// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs. -func (f Frame) MarshalText() ([]byte, error) { - name := f.name() - if name == "unknown" { - return []byte(name), nil - } - return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil -} - -// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). -type StackTrace []Frame - -// Format formats the stack of Frames according to the fmt.Formatter interface. -// -// %s lists source files for each Frame in the stack -// %v lists the source file and line number for each Frame in the stack -// -// Format accepts flags that alter the printing of some verbs, as follows: -// -// %+v Prints filename, function, and line number for each Frame in the stack. -func (st StackTrace) Format(s fmt.State, verb rune) { - switch verb { - case 'v': - switch { - case s.Flag('+'): - for _, f := range st { - io.WriteString(s, "\n") - f.Format(s, verb) - } - case s.Flag('#'): - fmt.Fprintf(s, "%#v", []Frame(st)) - default: - st.formatSlice(s, verb) - } - case 's': - st.formatSlice(s, verb) - } -} - -// formatSlice will format this StackTrace into the given buffer as a slice of -// Frame, only valid when called with '%s' or '%v'. -func (st StackTrace) formatSlice(s fmt.State, verb rune) { - io.WriteString(s, "[") - for i, f := range st { - if i > 0 { - io.WriteString(s, " ") - } - f.Format(s, verb) - } - io.WriteString(s, "]") -} - -// stack represents a stack of program counters. -type stack []uintptr - -func (s *stack) Format(st fmt.State, verb rune) { - switch verb { - case 'v': - switch { - case st.Flag('+'): - for _, pc := range *s { - f := Frame(pc) - fmt.Fprintf(st, "\n%+v", f) - } - } - } -} - -func (s *stack) StackTrace() StackTrace { - f := make([]Frame, len(*s)) - for i := 0; i < len(f); i++ { - f[i] = Frame((*s)[i]) - } - return f -} - -func callers() *stack { - const depth = 32 - var pcs [depth]uintptr - n := runtime.Callers(3, pcs[:]) - var st stack = pcs[0:n] - return &st -} - -// funcname removes the path prefix component of a function's name reported by func.Name(). -func funcname(name string) string { - i := strings.LastIndex(name, "/") - name = name[i+1:] - i = strings.Index(name, ".") - return name[i+1:] -} diff --git a/vendor/github.com/prometheus/client_golang/prometheus/histogram.go b/vendor/github.com/prometheus/client_golang/prometheus/histogram.go index 8d35f2d8..519db348 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/histogram.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/histogram.go @@ -844,9 +844,7 @@ func (h *histogram) Write(out *dto.Metric) error { }} } - // If exemplars are not configured, the cap will be 0. - // So append is not needed in this case. - if cap(h.nativeExemplars.exemplars) > 0 { + if h.nativeExemplars.isEnabled() { h.nativeExemplars.Lock() his.Exemplars = append(his.Exemplars, h.nativeExemplars.exemplars...) h.nativeExemplars.Unlock() @@ -1658,10 +1656,17 @@ func addAndResetCounts(hot, cold *histogramCounts) { type nativeExemplars struct { sync.Mutex - ttl time.Duration + // Time-to-live for exemplars, it is set to -1 if exemplars are disabled, that is NativeHistogramMaxExemplars is below 0. + // The ttl is used on insertion to remove an exemplar that is older than ttl, if present. + ttl time.Duration + exemplars []*dto.Exemplar } +func (n *nativeExemplars) isEnabled() bool { + return n.ttl != -1 +} + func makeNativeExemplars(ttl time.Duration, maxCount int) nativeExemplars { if ttl == 0 { ttl = 5 * time.Minute @@ -1673,6 +1678,7 @@ func makeNativeExemplars(ttl time.Duration, maxCount int) nativeExemplars { if maxCount < 0 { maxCount = 0 + ttl = -1 } return nativeExemplars{ @@ -1682,20 +1688,18 @@ func makeNativeExemplars(ttl time.Duration, maxCount int) nativeExemplars { } func (n *nativeExemplars) addExemplar(e *dto.Exemplar) { - if cap(n.exemplars) == 0 { + if !n.isEnabled() { return } n.Lock() defer n.Unlock() - // The index where to insert the new exemplar. - var nIdx int = -1 - // When the number of exemplars has not yet exceeded or // is equal to cap(n.exemplars), then // insert the new exemplar directly. if len(n.exemplars) < cap(n.exemplars) { + var nIdx int for nIdx = 0; nIdx < len(n.exemplars); nIdx++ { if *e.Value < *n.exemplars[nIdx].Value { break @@ -1705,17 +1709,46 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) { return } + if len(n.exemplars) == 1 { + // When the number of exemplars is 1, then + // replace the existing exemplar with the new exemplar. + n.exemplars[0] = e + return + } + // From this point on, the number of exemplars is greater than 1. + // When the number of exemplars exceeds the limit, remove one exemplar. var ( - rIdx int // The index where to remove the old exemplar. - - ot = time.Now() // Oldest timestamp seen. - otIdx = -1 // Index of the exemplar with the oldest timestamp. - - md = -1.0 // Logarithm of the delta of the closest pair of exemplars. - mdIdx = -1 // Index of the older exemplar within the closest pair. - cLog float64 // Logarithm of the current exemplar. - pLog float64 // Logarithm of the previous exemplar. + ot = time.Time{} // Oldest timestamp seen. Initial value doesn't matter as we replace it due to otIdx == -1 in the loop. + otIdx = -1 // Index of the exemplar with the oldest timestamp. + + md = -1.0 // Logarithm of the delta of the closest pair of exemplars. + + // The insertion point of the new exemplar in the exemplars slice after insertion. + // This is calculated purely based on the order of the exemplars by value. + // nIdx == len(n.exemplars) means the new exemplar is to be inserted after the end. + nIdx = -1 + + // rIdx is ultimately the index for the exemplar that we are replacing with the new exemplar. + // The aim is to keep a good spread of exemplars by value and not let them bunch up too much. + // It is calculated in 3 steps: + // 1. First we set rIdx to the index of the older exemplar within the closest pair by value. + // That is the following will be true (on log scale): + // either the exemplar pair on index (rIdx-1, rIdx) or (rIdx, rIdx+1) will have + // the closest values to each other from all pairs. + // For example, suppose the values are distributed like this: + // |-----------x-------------x----------------x----x-----| + // ^--rIdx as this is older. + // Or like this: + // |-----------x-------------x----------------x----x-----| + // ^--rIdx as this is older. + // 2. If there is an exemplar that expired, then we simple reset rIdx to that index. + // 3. We check if by inserting the new exemplar we would create a closer pair at + // (nIdx-1, nIdx) or (nIdx, nIdx+1) and set rIdx to nIdx-1 or nIdx accordingly to + // keep the spread of exemplars by value; otherwise we keep rIdx as it is. + rIdx = -1 + cLog float64 // Logarithm of the current exemplar. + pLog float64 // Logarithm of the previous exemplar. ) for i, exemplar := range n.exemplars { @@ -1726,7 +1759,7 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) { } // Find the index at which to insert new the exemplar. - if *e.Value <= *exemplar.Value && nIdx == -1 { + if nIdx == -1 && *e.Value <= *exemplar.Value { nIdx = i } @@ -1738,11 +1771,13 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) { } diff := math.Abs(cLog - pLog) if md == -1 || diff < md { + // The closest exemplar pair is at index: i-1, i. + // Choose the exemplar with the older timestamp for replacement. md = diff if n.exemplars[i].Timestamp.AsTime().Before(n.exemplars[i-1].Timestamp.AsTime()) { - mdIdx = i + rIdx = i } else { - mdIdx = i - 1 + rIdx = i - 1 } } @@ -1753,8 +1788,12 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) { if nIdx == -1 { nIdx = len(n.exemplars) } + // Here, we have the following relationships: + // n.exemplars[nIdx-1].Value < e.Value (if nIdx > 0) + // e.Value <= n.exemplars[nIdx].Value (if nIdx < len(n.exemplars)) if otIdx != -1 && e.Timestamp.AsTime().Sub(ot) > n.ttl { + // If the oldest exemplar has expired, then replace it with the new exemplar. rIdx = otIdx } else { // In the previous for loop, when calculating the closest pair of exemplars, @@ -1764,23 +1803,26 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) { if nIdx > 0 { diff := math.Abs(elog - math.Log(n.exemplars[nIdx-1].GetValue())) if diff < md { + // The value we are about to insert is closer to the previous exemplar at the insertion point than what we calculated before in rIdx. + // v--rIdx + // |-----------x-n-----------x----------------x----x-----| + // nIdx-1--^ ^--new exemplar value + // Do not make the spread worse, replace nIdx-1 and not rIdx. md = diff - mdIdx = nIdx - if n.exemplars[nIdx-1].Timestamp.AsTime().Before(e.Timestamp.AsTime()) { - mdIdx = nIdx - 1 - } + rIdx = nIdx - 1 } } if nIdx < len(n.exemplars) { diff := math.Abs(math.Log(n.exemplars[nIdx].GetValue()) - elog) if diff < md { - mdIdx = nIdx - if n.exemplars[nIdx].Timestamp.AsTime().Before(e.Timestamp.AsTime()) { - mdIdx = nIdx - } + // The value we are about to insert is closer to the next exemplar at the insertion point than what we calculated before in rIdx. + // v--rIdx + // |-----------x-----------n-x----------------x----x-----| + // new exemplar value--^ ^--nIdx + // Do not make the spread worse, replace nIdx-1 and not rIdx. + rIdx = nIdx } } - rIdx = mdIdx } // Adjust the slice according to rIdx and nIdx. diff --git a/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go b/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go index efbc3ea8..62a4e7ad 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go @@ -140,6 +140,8 @@ func (c *processCollector) Describe(ch chan<- *Desc) { ch <- c.maxVsize ch <- c.rss ch <- c.startTime + ch <- c.inBytes + ch <- c.outBytes } // Collect returns the current state of all metrics of the collector. diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go index 2e0b9a86..e598e66e 100644 --- a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go +++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go @@ -203,8 +203,10 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO defer closeWriter() - rsp.Header().Set(contentEncodingHeader, encodingHeader) - + // Set Content-Encoding only when data is compressed + if encodingHeader != string(Identity) { + rsp.Header().Set(contentEncodingHeader, encodingHeader) + } enc := expfmt.NewEncoder(w, contentType) // handleError handles the error according to opts.ErrorHandling diff --git a/vendor/github.com/prometheus/common/expfmt/decode.go b/vendor/github.com/prometheus/common/expfmt/decode.go index 25cfaa21..1448439b 100644 --- a/vendor/github.com/prometheus/common/expfmt/decode.go +++ b/vendor/github.com/prometheus/common/expfmt/decode.go @@ -45,7 +45,7 @@ func ResponseFormat(h http.Header) Format { mediatype, params, err := mime.ParseMediaType(ct) if err != nil { - return fmtUnknown + return FmtUnknown } const textType = "text/plain" @@ -53,21 +53,21 @@ func ResponseFormat(h http.Header) Format { switch mediatype { case ProtoType: if p, ok := params["proto"]; ok && p != ProtoProtocol { - return fmtUnknown + return FmtUnknown } if e, ok := params["encoding"]; ok && e != "delimited" { - return fmtUnknown + return FmtUnknown } - return fmtProtoDelim + return FmtProtoDelim case textType: if v, ok := params["version"]; ok && v != TextVersion { - return fmtUnknown + return FmtUnknown } - return fmtText + return FmtText } - return fmtUnknown + return FmtUnknown } // NewDecoder returns a new decoder based on the given input format. diff --git a/vendor/github.com/prometheus/common/expfmt/encode.go b/vendor/github.com/prometheus/common/expfmt/encode.go index ff5ef7a9..cf0c150c 100644 --- a/vendor/github.com/prometheus/common/expfmt/encode.go +++ b/vendor/github.com/prometheus/common/expfmt/encode.go @@ -77,18 +77,18 @@ func Negotiate(h http.Header) Format { if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol { switch ac.Params["encoding"] { case "delimited": - return fmtProtoDelim + escapingScheme + return FmtProtoDelim + escapingScheme case "text": - return fmtProtoText + escapingScheme + return FmtProtoText + escapingScheme case "compact-text": - return fmtProtoCompact + escapingScheme + return FmtProtoCompact + escapingScheme } } if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") { - return fmtText + escapingScheme + return FmtText + escapingScheme } } - return fmtText + escapingScheme + return FmtText + escapingScheme } // NegotiateIncludingOpenMetrics works like Negotiate but includes @@ -110,26 +110,26 @@ func NegotiateIncludingOpenMetrics(h http.Header) Format { if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol { switch ac.Params["encoding"] { case "delimited": - return fmtProtoDelim + escapingScheme + return FmtProtoDelim + escapingScheme case "text": - return fmtProtoText + escapingScheme + return FmtProtoText + escapingScheme case "compact-text": - return fmtProtoCompact + escapingScheme + return FmtProtoCompact + escapingScheme } } if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") { - return fmtText + escapingScheme + return FmtText + escapingScheme } if ac.Type+"/"+ac.SubType == OpenMetricsType && (ver == OpenMetricsVersion_0_0_1 || ver == OpenMetricsVersion_1_0_0 || ver == "") { switch ver { case OpenMetricsVersion_1_0_0: - return fmtOpenMetrics_1_0_0 + escapingScheme + return FmtOpenMetrics_1_0_0 + escapingScheme default: - return fmtOpenMetrics_0_0_1 + escapingScheme + return FmtOpenMetrics_0_0_1 + escapingScheme } } } - return fmtText + escapingScheme + return FmtText + escapingScheme } // NewEncoder returns a new encoder based on content type negotiation. All diff --git a/vendor/github.com/prometheus/common/expfmt/expfmt.go b/vendor/github.com/prometheus/common/expfmt/expfmt.go index 051b38cd..d942af8e 100644 --- a/vendor/github.com/prometheus/common/expfmt/expfmt.go +++ b/vendor/github.com/prometheus/common/expfmt/expfmt.go @@ -32,24 +32,31 @@ type Format string // it on the wire, new content-type strings will have to be agreed upon and // added here. const ( - TextVersion = "0.0.4" - ProtoType = `application/vnd.google.protobuf` - ProtoProtocol = `io.prometheus.client.MetricFamily` - protoFmt = ProtoType + "; proto=" + ProtoProtocol + ";" + TextVersion = "0.0.4" + ProtoType = `application/vnd.google.protobuf` + ProtoProtocol = `io.prometheus.client.MetricFamily` + // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoCompact) instead. + ProtoFmt = ProtoType + "; proto=" + ProtoProtocol + ";" OpenMetricsType = `application/openmetrics-text` OpenMetricsVersion_0_0_1 = "0.0.1" OpenMetricsVersion_1_0_0 = "1.0.0" - // The Content-Type values for the different wire protocols. Note that these - // values are now unexported. If code was relying on comparisons to these - // constants, instead use FormatType(). - fmtUnknown Format = `` - fmtText Format = `text/plain; version=` + TextVersion + `; charset=utf-8` - fmtProtoDelim Format = protoFmt + ` encoding=delimited` - fmtProtoText Format = protoFmt + ` encoding=text` - fmtProtoCompact Format = protoFmt + ` encoding=compact-text` - fmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8` - fmtOpenMetrics_0_0_1 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_0_0_1 + `; charset=utf-8` + // The Content-Type values for the different wire protocols. Do not do direct + // comparisons to these constants, instead use the comparison functions. + // Deprecated: Use expfmt.NewFormat(expfmt.TypeUnknown) instead. + FmtUnknown Format = `` + // Deprecated: Use expfmt.NewFormat(expfmt.TypeTextPlain) instead. + FmtText Format = `text/plain; version=` + TextVersion + `; charset=utf-8` + // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoDelim) instead. + FmtProtoDelim Format = ProtoFmt + ` encoding=delimited` + // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoText) instead. + FmtProtoText Format = ProtoFmt + ` encoding=text` + // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoCompact) instead. + FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text` + // Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead. + FmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8` + // Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead. + FmtOpenMetrics_0_0_1 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_0_0_1 + `; charset=utf-8` ) const ( @@ -79,17 +86,17 @@ const ( func NewFormat(t FormatType) Format { switch t { case TypeProtoCompact: - return fmtProtoCompact + return FmtProtoCompact case TypeProtoDelim: - return fmtProtoDelim + return FmtProtoDelim case TypeProtoText: - return fmtProtoText + return FmtProtoText case TypeTextPlain: - return fmtText + return FmtText case TypeOpenMetrics: - return fmtOpenMetrics_1_0_0 + return FmtOpenMetrics_1_0_0 default: - return fmtUnknown + return FmtUnknown } } @@ -97,12 +104,35 @@ func NewFormat(t FormatType) Format { // specified version number. func NewOpenMetricsFormat(version string) (Format, error) { if version == OpenMetricsVersion_0_0_1 { - return fmtOpenMetrics_0_0_1, nil + return FmtOpenMetrics_0_0_1, nil } if version == OpenMetricsVersion_1_0_0 { - return fmtOpenMetrics_1_0_0, nil + return FmtOpenMetrics_1_0_0, nil } - return fmtUnknown, fmt.Errorf("unknown open metrics version string") + return FmtUnknown, fmt.Errorf("unknown open metrics version string") +} + +// WithEscapingScheme returns a copy of Format with the specified escaping +// scheme appended to the end. If an escaping scheme already exists it is +// removed. +func (f Format) WithEscapingScheme(s model.EscapingScheme) Format { + var terms []string + for _, p := range strings.Split(string(f), ";") { + toks := strings.Split(p, "=") + if len(toks) != 2 { + trimmed := strings.TrimSpace(p) + if len(trimmed) > 0 { + terms = append(terms, trimmed) + } + continue + } + key := strings.TrimSpace(toks[0]) + if key != model.EscapingKey { + terms = append(terms, strings.TrimSpace(p)) + } + } + terms = append(terms, model.EscapingKey+"="+s.String()) + return Format(strings.Join(terms, "; ")) } // FormatType deduces an overall FormatType for the given format. diff --git a/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go b/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go index 353c5e93..11c8ff4b 100644 --- a/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go +++ b/vendor/github.com/prometheus/common/expfmt/openmetrics_create.go @@ -477,7 +477,7 @@ func writeOpenMetricsNameAndLabelPairs( if name != "" { // If the name does not pass the legacy validity check, we must put the // metric name inside the braces, quoted. - if !model.IsValidLegacyMetricName(model.LabelValue(name)) { + if !model.IsValidLegacyMetricName(name) { metricInsideBraces = true err := w.WriteByte(separator) written++ diff --git a/vendor/github.com/prometheus/common/expfmt/text_create.go b/vendor/github.com/prometheus/common/expfmt/text_create.go index f9b8265a..4b86434b 100644 --- a/vendor/github.com/prometheus/common/expfmt/text_create.go +++ b/vendor/github.com/prometheus/common/expfmt/text_create.go @@ -354,7 +354,7 @@ func writeNameAndLabelPairs( if name != "" { // If the name does not pass the legacy validity check, we must put the // metric name inside the braces. - if !model.IsValidLegacyMetricName(model.LabelValue(name)) { + if !model.IsValidLegacyMetricName(name) { metricInsideBraces = true err := w.WriteByte(separator) written++ @@ -498,7 +498,7 @@ func writeInt(w enhancedWriter, i int64) (int, error) { // writeName writes a string as-is if it complies with the legacy naming // scheme, or escapes it in double quotes if not. func writeName(w enhancedWriter, name string) (int, error) { - if model.IsValidLegacyMetricName(model.LabelValue(name)) { + if model.IsValidLegacyMetricName(name) { return w.WriteString(name) } var written int diff --git a/vendor/github.com/prometheus/common/expfmt/text_parse.go b/vendor/github.com/prometheus/common/expfmt/text_parse.go index 26490211..f085a923 100644 --- a/vendor/github.com/prometheus/common/expfmt/text_parse.go +++ b/vendor/github.com/prometheus/common/expfmt/text_parse.go @@ -22,9 +22,9 @@ import ( "math" "strconv" "strings" + "unicode/utf8" dto "github.com/prometheus/client_model/go" - "google.golang.org/protobuf/proto" "github.com/prometheus/common/model" @@ -60,6 +60,7 @@ type TextParser struct { currentMF *dto.MetricFamily currentMetric *dto.Metric currentLabelPair *dto.LabelPair + currentLabelPairs []*dto.LabelPair // Temporarily stores label pairs while parsing a metric line. // The remaining member variables are only used for summaries/histograms. currentLabels map[string]string // All labels including '__name__' but excluding 'quantile'/'le' @@ -74,6 +75,9 @@ type TextParser struct { // count and sum of that summary/histogram. currentIsSummaryCount, currentIsSummarySum bool currentIsHistogramCount, currentIsHistogramSum bool + // These indicate if the metric name from the current line being parsed is inside + // braces and if that metric name was found respectively. + currentMetricIsInsideBraces, currentMetricInsideBracesIsPresent bool } // TextToMetricFamilies reads 'in' as the simple and flat text-based exchange @@ -137,12 +141,15 @@ func (p *TextParser) reset(in io.Reader) { } p.currentQuantile = math.NaN() p.currentBucket = math.NaN() + p.currentMF = nil } // startOfLine represents the state where the next byte read from p.buf is the // start of a line (or whitespace leading up to it). func (p *TextParser) startOfLine() stateFn { p.lineCount++ + p.currentMetricIsInsideBraces = false + p.currentMetricInsideBracesIsPresent = false if p.skipBlankTab(); p.err != nil { // This is the only place that we expect to see io.EOF, // which is not an error but the signal that we are done. @@ -158,6 +165,9 @@ func (p *TextParser) startOfLine() stateFn { return p.startComment case '\n': return p.startOfLine // Empty line, start the next one. + case '{': + p.currentMetricIsInsideBraces = true + return p.readingLabels } return p.readingMetricName } @@ -275,6 +285,8 @@ func (p *TextParser) startLabelName() stateFn { return nil // Unexpected end of input. } if p.currentByte == '}' { + p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPairs...) + p.currentLabelPairs = nil if p.skipBlankTab(); p.err != nil { return nil // Unexpected end of input. } @@ -287,6 +299,45 @@ func (p *TextParser) startLabelName() stateFn { p.parseError(fmt.Sprintf("invalid label name for metric %q", p.currentMF.GetName())) return nil } + if p.skipBlankTabIfCurrentBlankTab(); p.err != nil { + return nil // Unexpected end of input. + } + if p.currentByte != '=' { + if p.currentMetricIsInsideBraces { + if p.currentMetricInsideBracesIsPresent { + p.parseError(fmt.Sprintf("multiple metric names for metric %q", p.currentMF.GetName())) + return nil + } + switch p.currentByte { + case ',': + p.setOrCreateCurrentMF() + if p.currentMF.Type == nil { + p.currentMF.Type = dto.MetricType_UNTYPED.Enum() + } + p.currentMetric = &dto.Metric{} + p.currentMetricInsideBracesIsPresent = true + return p.startLabelName + case '}': + p.setOrCreateCurrentMF() + if p.currentMF.Type == nil { + p.currentMF.Type = dto.MetricType_UNTYPED.Enum() + } + p.currentMetric = &dto.Metric{} + p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPairs...) + p.currentLabelPairs = nil + if p.skipBlankTab(); p.err != nil { + return nil // Unexpected end of input. + } + return p.readingValue + default: + p.parseError(fmt.Sprintf("unexpected end of metric name %q", p.currentByte)) + return nil + } + } + p.parseError(fmt.Sprintf("expected '=' after label name, found %q", p.currentByte)) + p.currentLabelPairs = nil + return nil + } p.currentLabelPair = &dto.LabelPair{Name: proto.String(p.currentToken.String())} if p.currentLabelPair.GetName() == string(model.MetricNameLabel) { p.parseError(fmt.Sprintf("label name %q is reserved", model.MetricNameLabel)) @@ -296,23 +347,17 @@ func (p *TextParser) startLabelName() stateFn { // labels to 'real' labels. if !(p.currentMF.GetType() == dto.MetricType_SUMMARY && p.currentLabelPair.GetName() == model.QuantileLabel) && !(p.currentMF.GetType() == dto.MetricType_HISTOGRAM && p.currentLabelPair.GetName() == model.BucketLabel) { - p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPair) - } - if p.skipBlankTabIfCurrentBlankTab(); p.err != nil { - return nil // Unexpected end of input. - } - if p.currentByte != '=' { - p.parseError(fmt.Sprintf("expected '=' after label name, found %q", p.currentByte)) - return nil + p.currentLabelPairs = append(p.currentLabelPairs, p.currentLabelPair) } // Check for duplicate label names. labels := make(map[string]struct{}) - for _, l := range p.currentMetric.Label { + for _, l := range p.currentLabelPairs { lName := l.GetName() if _, exists := labels[lName]; !exists { labels[lName] = struct{}{} } else { p.parseError(fmt.Sprintf("duplicate label names for metric %q", p.currentMF.GetName())) + p.currentLabelPairs = nil return nil } } @@ -345,6 +390,7 @@ func (p *TextParser) startLabelValue() stateFn { if p.currentQuantile, p.err = parseFloat(p.currentLabelPair.GetValue()); p.err != nil { // Create a more helpful error message. p.parseError(fmt.Sprintf("expected float as value for 'quantile' label, got %q", p.currentLabelPair.GetValue())) + p.currentLabelPairs = nil return nil } } else { @@ -371,12 +417,19 @@ func (p *TextParser) startLabelValue() stateFn { return p.startLabelName case '}': + if p.currentMF == nil { + p.parseError("invalid metric name") + return nil + } + p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPairs...) + p.currentLabelPairs = nil if p.skipBlankTab(); p.err != nil { return nil // Unexpected end of input. } return p.readingValue default: p.parseError(fmt.Sprintf("unexpected end of label value %q", p.currentLabelPair.GetValue())) + p.currentLabelPairs = nil return nil } } @@ -585,6 +638,8 @@ func (p *TextParser) readTokenUntilNewline(recognizeEscapeSequence bool) { p.currentToken.WriteByte(p.currentByte) case 'n': p.currentToken.WriteByte('\n') + case '"': + p.currentToken.WriteByte('"') default: p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte)) return @@ -610,13 +665,45 @@ func (p *TextParser) readTokenUntilNewline(recognizeEscapeSequence bool) { // but not into p.currentToken. func (p *TextParser) readTokenAsMetricName() { p.currentToken.Reset() + // A UTF-8 metric name must be quoted and may have escaped characters. + quoted := false + escaped := false if !isValidMetricNameStart(p.currentByte) { return } - for { - p.currentToken.WriteByte(p.currentByte) + for p.err == nil { + if escaped { + switch p.currentByte { + case '\\': + p.currentToken.WriteByte(p.currentByte) + case 'n': + p.currentToken.WriteByte('\n') + case '"': + p.currentToken.WriteByte('"') + default: + p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte)) + return + } + escaped = false + } else { + switch p.currentByte { + case '"': + quoted = !quoted + if !quoted { + p.currentByte, p.err = p.buf.ReadByte() + return + } + case '\n': + p.parseError(fmt.Sprintf("metric name %q contains unescaped new-line", p.currentToken.String())) + return + case '\\': + escaped = true + default: + p.currentToken.WriteByte(p.currentByte) + } + } p.currentByte, p.err = p.buf.ReadByte() - if p.err != nil || !isValidMetricNameContinuation(p.currentByte) { + if !isValidMetricNameContinuation(p.currentByte, quoted) || (!quoted && p.currentByte == ' ') { return } } @@ -628,13 +715,45 @@ func (p *TextParser) readTokenAsMetricName() { // but not into p.currentToken. func (p *TextParser) readTokenAsLabelName() { p.currentToken.Reset() + // A UTF-8 label name must be quoted and may have escaped characters. + quoted := false + escaped := false if !isValidLabelNameStart(p.currentByte) { return } - for { - p.currentToken.WriteByte(p.currentByte) + for p.err == nil { + if escaped { + switch p.currentByte { + case '\\': + p.currentToken.WriteByte(p.currentByte) + case 'n': + p.currentToken.WriteByte('\n') + case '"': + p.currentToken.WriteByte('"') + default: + p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte)) + return + } + escaped = false + } else { + switch p.currentByte { + case '"': + quoted = !quoted + if !quoted { + p.currentByte, p.err = p.buf.ReadByte() + return + } + case '\n': + p.parseError(fmt.Sprintf("label name %q contains unescaped new-line", p.currentToken.String())) + return + case '\\': + escaped = true + default: + p.currentToken.WriteByte(p.currentByte) + } + } p.currentByte, p.err = p.buf.ReadByte() - if p.err != nil || !isValidLabelNameContinuation(p.currentByte) { + if !isValidLabelNameContinuation(p.currentByte, quoted) || (!quoted && p.currentByte == '=') { return } } @@ -660,6 +779,7 @@ func (p *TextParser) readTokenAsLabelValue() { p.currentToken.WriteByte('\n') default: p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte)) + p.currentLabelPairs = nil return } escaped = false @@ -718,19 +838,19 @@ func (p *TextParser) setOrCreateCurrentMF() { } func isValidLabelNameStart(b byte) bool { - return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' + return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || b == '"' } -func isValidLabelNameContinuation(b byte) bool { - return isValidLabelNameStart(b) || (b >= '0' && b <= '9') +func isValidLabelNameContinuation(b byte, quoted bool) bool { + return isValidLabelNameStart(b) || (b >= '0' && b <= '9') || (quoted && utf8.ValidString(string(b))) } func isValidMetricNameStart(b byte) bool { return isValidLabelNameStart(b) || b == ':' } -func isValidMetricNameContinuation(b byte) bool { - return isValidLabelNameContinuation(b) || b == ':' +func isValidMetricNameContinuation(b byte, quoted bool) bool { + return isValidLabelNameContinuation(b, quoted) || b == ':' } func isBlankOrTab(b byte) bool { diff --git a/vendor/github.com/prometheus/common/model/labels.go b/vendor/github.com/prometheus/common/model/labels.go index 3317ce22..73b7aa3e 100644 --- a/vendor/github.com/prometheus/common/model/labels.go +++ b/vendor/github.com/prometheus/common/model/labels.go @@ -97,26 +97,35 @@ var LabelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$") // therewith. type LabelName string -// IsValid returns true iff name matches the pattern of LabelNameRE for legacy -// names, and iff it's valid UTF-8 if NameValidationScheme is set to -// UTF8Validation. For the legacy matching, it does not use LabelNameRE for the -// check but a much faster hardcoded implementation. +// IsValid returns true iff the name matches the pattern of LabelNameRE when +// NameValidationScheme is set to LegacyValidation, or valid UTF-8 if +// NameValidationScheme is set to UTF8Validation. func (ln LabelName) IsValid() bool { if len(ln) == 0 { return false } switch NameValidationScheme { case LegacyValidation: - for i, b := range ln { - if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { - return false - } - } + return ln.IsValidLegacy() case UTF8Validation: return utf8.ValidString(string(ln)) default: panic(fmt.Sprintf("Invalid name validation scheme requested: %d", NameValidationScheme)) } +} + +// IsValidLegacy returns true iff name matches the pattern of LabelNameRE for +// legacy names. It does not use LabelNameRE for the check but a much faster +// hardcoded implementation. +func (ln LabelName) IsValidLegacy() bool { + if len(ln) == 0 { + return false + } + for i, b := range ln { + if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { + return false + } + } return true } diff --git a/vendor/github.com/prometheus/common/model/labelset_string.go b/vendor/github.com/prometheus/common/model/labelset_string.go index 481c47b4..abb2c900 100644 --- a/vendor/github.com/prometheus/common/model/labelset_string.go +++ b/vendor/github.com/prometheus/common/model/labelset_string.go @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build go1.21 - package model import ( diff --git a/vendor/github.com/prometheus/common/model/labelset_string_go120.go b/vendor/github.com/prometheus/common/model/labelset_string_go120.go deleted file mode 100644 index c4212685..00000000 --- a/vendor/github.com/prometheus/common/model/labelset_string_go120.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2024 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build !go1.21 - -package model - -import ( - "fmt" - "sort" - "strings" -) - -// String was optimized using functions not available for go 1.20 -// or lower. We keep the old implementation for compatibility with client_golang. -// Once client golang drops support for go 1.20 (scheduled for August 2024), this -// file can be removed. -func (l LabelSet) String() string { - labelNames := make([]string, 0, len(l)) - for name := range l { - labelNames = append(labelNames, string(name)) - } - sort.Strings(labelNames) - lstrs := make([]string, 0, len(l)) - for _, name := range labelNames { - lstrs = append(lstrs, fmt.Sprintf("%s=%q", name, l[LabelName(name)])) - } - return fmt.Sprintf("{%s}", strings.Join(lstrs, ", ")) -} diff --git a/vendor/github.com/prometheus/common/model/metric.go b/vendor/github.com/prometheus/common/model/metric.go index eb865e5a..f50966bc 100644 --- a/vendor/github.com/prometheus/common/model/metric.go +++ b/vendor/github.com/prometheus/common/model/metric.go @@ -34,10 +34,13 @@ var ( // goroutines are started. NameValidationScheme = LegacyValidation - // NameEscapingScheme defines the default way that names will be - // escaped when presented to systems that do not support UTF-8 names. If the - // Content-Type "escaping" term is specified, that will override this value. - NameEscapingScheme = ValueEncodingEscaping + // NameEscapingScheme defines the default way that names will be escaped when + // presented to systems that do not support UTF-8 names. If the Content-Type + // "escaping" term is specified, that will override this value. + // NameEscapingScheme should not be set to the NoEscaping value. That string + // is used in content negotiation to indicate that a system supports UTF-8 and + // has that feature enabled. + NameEscapingScheme = UnderscoreEscaping ) // ValidationScheme is a Go enum for determining how metric and label names will @@ -161,7 +164,7 @@ func (m Metric) FastFingerprint() Fingerprint { func IsValidMetricName(n LabelValue) bool { switch NameValidationScheme { case LegacyValidation: - return IsValidLegacyMetricName(n) + return IsValidLegacyMetricName(string(n)) case UTF8Validation: if len(n) == 0 { return false @@ -176,7 +179,7 @@ func IsValidMetricName(n LabelValue) bool { // legacy validation scheme regardless of the value of NameValidationScheme. // This function, however, does not use MetricNameRE for the check but a much // faster hardcoded implementation. -func IsValidLegacyMetricName(n LabelValue) bool { +func IsValidLegacyMetricName(n string) bool { if len(n) == 0 { return false } @@ -208,7 +211,7 @@ func EscapeMetricFamily(v *dto.MetricFamily, scheme EscapingScheme) *dto.MetricF } // If the name is nil, copy as-is, don't try to escape. - if v.Name == nil || IsValidLegacyMetricName(LabelValue(v.GetName())) { + if v.Name == nil || IsValidLegacyMetricName(v.GetName()) { out.Name = v.Name } else { out.Name = proto.String(EscapeName(v.GetName(), scheme)) @@ -230,7 +233,7 @@ func EscapeMetricFamily(v *dto.MetricFamily, scheme EscapingScheme) *dto.MetricF for _, l := range m.Label { if l.GetName() == MetricNameLabel { - if l.Value == nil || IsValidLegacyMetricName(LabelValue(l.GetValue())) { + if l.Value == nil || IsValidLegacyMetricName(l.GetValue()) { escaped.Label = append(escaped.Label, l) continue } @@ -240,7 +243,7 @@ func EscapeMetricFamily(v *dto.MetricFamily, scheme EscapingScheme) *dto.MetricF }) continue } - if l.Name == nil || IsValidLegacyMetricName(LabelValue(l.GetName())) { + if l.Name == nil || IsValidLegacyMetricName(l.GetName()) { escaped.Label = append(escaped.Label, l) continue } @@ -256,10 +259,10 @@ func EscapeMetricFamily(v *dto.MetricFamily, scheme EscapingScheme) *dto.MetricF func metricNeedsEscaping(m *dto.Metric) bool { for _, l := range m.Label { - if l.GetName() == MetricNameLabel && !IsValidLegacyMetricName(LabelValue(l.GetValue())) { + if l.GetName() == MetricNameLabel && !IsValidLegacyMetricName(l.GetValue()) { return true } - if !IsValidLegacyMetricName(LabelValue(l.GetName())) { + if !IsValidLegacyMetricName(l.GetName()) { return true } } @@ -283,7 +286,7 @@ func EscapeName(name string, scheme EscapingScheme) string { case NoEscaping: return name case UnderscoreEscaping: - if IsValidLegacyMetricName(LabelValue(name)) { + if IsValidLegacyMetricName(name) { return name } for i, b := range name { @@ -309,7 +312,7 @@ func EscapeName(name string, scheme EscapingScheme) string { } return escaped.String() case ValueEncodingEscaping: - if IsValidLegacyMetricName(LabelValue(name)) { + if IsValidLegacyMetricName(name) { return name } escaped.WriteString("U__") @@ -452,6 +455,6 @@ func ToEscapingScheme(s string) (EscapingScheme, error) { case EscapeValues: return ValueEncodingEscaping, nil default: - return NoEscaping, fmt.Errorf("unknown format scheme " + s) + return NoEscaping, fmt.Errorf("unknown format scheme %s", s) } } diff --git a/vendor/github.com/rs/xid/.gitignore b/vendor/github.com/rs/xid/.gitignore new file mode 100644 index 00000000..81be9277 --- /dev/null +++ b/vendor/github.com/rs/xid/.gitignore @@ -0,0 +1,3 @@ +/.idea +/.vscode +.DS_Store \ No newline at end of file diff --git a/vendor/github.com/rs/xid/README.md b/vendor/github.com/rs/xid/README.md index 974e67d2..1bf45bd1 100644 --- a/vendor/github.com/rs/xid/README.md +++ b/vendor/github.com/rs/xid/README.md @@ -4,7 +4,7 @@ Package xid is a globally unique id generator library, ready to safely be used directly in your server code. -Xid uses the Mongo Object ID algorithm to generate globally unique ids with a different serialization (base64) to make it shorter when transported as a string: +Xid uses the Mongo Object ID algorithm to generate globally unique ids with a different serialization ([base32hex](https://datatracker.ietf.org/doc/html/rfc4648#page-10)) to make it shorter when transported as a string: https://docs.mongodb.org/manual/reference/object-id/ - 4-byte value representing the seconds since the Unix epoch, @@ -13,7 +13,7 @@ https://docs.mongodb.org/manual/reference/object-id/ - 3-byte counter, starting with a random value. The binary representation of the id is compatible with Mongo 12 bytes Object IDs. -The string representation is using base32 hex (w/o padding) for better space efficiency +The string representation is using [base32hex](https://datatracker.ietf.org/doc/html/rfc4648#page-10) (w/o padding) for better space efficiency when stored in that form (20 bytes). The hex variant of base32 is used to retain the sortable property of the id. @@ -71,8 +71,10 @@ References: - Java port by [0xShamil](https://github.com/0xShamil/): https://github.com/0xShamil/java-xid - Dart port by [Peter Bwire](https://github.com/pitabwire): https://pub.dev/packages/xid - PostgreSQL port by [Rasmus Holm](https://github.com/crholm): https://github.com/modfin/pg-xid -- Swift port by [Uditha Atukorala](https://github.com/uditha-atukorala): https://github.com/uditha-atukorala/swift-xid -- C++ port by [Uditha Atukorala](https://github.com/uditha-atukorala): https://github.com/uditha-atukorala/libxid +- Swift port by [Uditha Atukorala](https://github.com/uatuko): https://github.com/uatuko/swift-xid +- C++ port by [Uditha Atukorala](https://github.com/uatuko): https://github.com/uatuko/libxid +- Typescript & Javascript port by [Yiwen AI](https://github.com/yiwen-ai): https://github.com/yiwen-ai/xid-ts +- Gleam port by [Alexandre Del Vecchio](https://github.com/defgenx): https://github.com/defgenx/gxid ## Install diff --git a/vendor/github.com/rs/xid/hostid_darwin.go b/vendor/github.com/rs/xid/hostid_darwin.go index 08351ff7..17351563 100644 --- a/vendor/github.com/rs/xid/hostid_darwin.go +++ b/vendor/github.com/rs/xid/hostid_darwin.go @@ -2,8 +2,33 @@ package xid -import "syscall" +import ( + "errors" + "os/exec" + "strings" +) func readPlatformMachineID() (string, error) { - return syscall.Sysctl("kern.uuid") + ioreg, err := exec.LookPath("ioreg") + if err != nil { + return "", err + } + + cmd := exec.Command(ioreg, "-rd1", "-c", "IOPlatformExpertDevice") + out, err := cmd.CombinedOutput() + if err != nil { + return "", err + } + + for _, line := range strings.Split(string(out), "\n") { + if strings.Contains(line, "IOPlatformUUID") { + parts := strings.SplitAfter(line, `" = "`) + if len(parts) == 2 { + uuid := strings.TrimRight(parts[1], `"`) + return strings.ToLower(uuid), nil + } + } + } + + return "", errors.New("cannot find host id") } diff --git a/vendor/github.com/rs/xid/hostid_windows.go b/vendor/github.com/rs/xid/hostid_windows.go index ec2593ee..a4d98ab0 100644 --- a/vendor/github.com/rs/xid/hostid_windows.go +++ b/vendor/github.com/rs/xid/hostid_windows.go @@ -11,11 +11,17 @@ import ( func readPlatformMachineID() (string, error) { // source: https://github.com/shirou/gopsutil/blob/master/host/host_syscall.go var h syscall.Handle - err := syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE, syscall.StringToUTF16Ptr(`SOFTWARE\Microsoft\Cryptography`), 0, syscall.KEY_READ|syscall.KEY_WOW64_64KEY, &h) + + regKeyCryptoPtr, err := syscall.UTF16PtrFromString(`SOFTWARE\Microsoft\Cryptography`) + if err != nil { + return "", fmt.Errorf(`error reading registry key "SOFTWARE\Microsoft\Cryptography": %w`, err) + } + + err = syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE, regKeyCryptoPtr, 0, syscall.KEY_READ|syscall.KEY_WOW64_64KEY, &h) if err != nil { return "", err } - defer syscall.RegCloseKey(h) + defer func() { _ = syscall.RegCloseKey(h) }() const syscallRegBufLen = 74 // len(`{`) + len(`abcdefgh-1234-456789012-123345456671` * 2) + len(`}`) // 2 == bytes/UTF16 const uuidLen = 36 @@ -23,9 +29,15 @@ func readPlatformMachineID() (string, error) { var regBuf [syscallRegBufLen]uint16 bufLen := uint32(syscallRegBufLen) var valType uint32 - err = syscall.RegQueryValueEx(h, syscall.StringToUTF16Ptr(`MachineGuid`), nil, &valType, (*byte)(unsafe.Pointer(®Buf[0])), &bufLen) + + mGuidPtr, err := syscall.UTF16PtrFromString(`MachineGuid`) if err != nil { - return "", err + return "", fmt.Errorf("error reading machine GUID: %w", err) + } + + err = syscall.RegQueryValueEx(h, mGuidPtr, nil, &valType, (*byte)(unsafe.Pointer(®Buf[0])), &bufLen) + if err != nil { + return "", fmt.Errorf("error parsing ") } hostID := syscall.UTF16ToString(regBuf[:]) diff --git a/vendor/github.com/rs/xid/id.go b/vendor/github.com/rs/xid/id.go index fcd7a041..e88984d9 100644 --- a/vendor/github.com/rs/xid/id.go +++ b/vendor/github.com/rs/xid/id.go @@ -54,7 +54,6 @@ import ( "sort" "sync/atomic" "time" - "unsafe" ) // Code inspired from mgo/bson ObjectId @@ -172,7 +171,7 @@ func FromString(id string) (ID, error) { func (id ID) String() string { text := make([]byte, encodedLen) encode(text, id[:]) - return *(*string)(unsafe.Pointer(&text)) + return string(text) } // Encode encodes the id using base32 encoding, writing 20 bytes to dst and return it. @@ -206,23 +205,23 @@ func encode(dst, id []byte) { dst[19] = encoding[(id[11]<<4)&0x1F] dst[18] = encoding[(id[11]>>1)&0x1F] - dst[17] = encoding[(id[11]>>6)&0x1F|(id[10]<<2)&0x1F] + dst[17] = encoding[(id[11]>>6)|(id[10]<<2)&0x1F] dst[16] = encoding[id[10]>>3] dst[15] = encoding[id[9]&0x1F] dst[14] = encoding[(id[9]>>5)|(id[8]<<3)&0x1F] dst[13] = encoding[(id[8]>>2)&0x1F] dst[12] = encoding[id[8]>>7|(id[7]<<1)&0x1F] - dst[11] = encoding[(id[7]>>4)&0x1F|(id[6]<<4)&0x1F] + dst[11] = encoding[(id[7]>>4)|(id[6]<<4)&0x1F] dst[10] = encoding[(id[6]>>1)&0x1F] - dst[9] = encoding[(id[6]>>6)&0x1F|(id[5]<<2)&0x1F] + dst[9] = encoding[(id[6]>>6)|(id[5]<<2)&0x1F] dst[8] = encoding[id[5]>>3] dst[7] = encoding[id[4]&0x1F] dst[6] = encoding[id[4]>>5|(id[3]<<3)&0x1F] dst[5] = encoding[(id[3]>>2)&0x1F] dst[4] = encoding[id[3]>>7|(id[2]<<1)&0x1F] - dst[3] = encoding[(id[2]>>4)&0x1F|(id[1]<<4)&0x1F] + dst[3] = encoding[(id[2]>>4)|(id[1]<<4)&0x1F] dst[2] = encoding[(id[1]>>1)&0x1F] - dst[1] = encoding[(id[1]>>6)&0x1F|(id[0]<<2)&0x1F] + dst[1] = encoding[(id[1]>>6)|(id[0]<<2)&0x1F] dst[0] = encoding[id[0]>>3] } diff --git a/vendor/github.com/tklauser/numcpus/.cirrus.yml b/vendor/github.com/tklauser/numcpus/.cirrus.yml index 33e6595c..b3091efd 100644 --- a/vendor/github.com/tklauser/numcpus/.cirrus.yml +++ b/vendor/github.com/tklauser/numcpus/.cirrus.yml @@ -1,10 +1,10 @@ env: CIRRUS_CLONE_DEPTH: 1 - GO_VERSION: go1.22.2 + GO_VERSION: go1.23.0 freebsd_13_task: freebsd_instance: - image_family: freebsd-13-2 + image_family: freebsd-13-3 install_script: | pkg install -y go GOBIN=$PWD/bin go install golang.org/dl/${GO_VERSION}@latest diff --git a/vendor/github.com/tklauser/numcpus/numcpus.go b/vendor/github.com/tklauser/numcpus/numcpus.go index af59983e..de206f06 100644 --- a/vendor/github.com/tklauser/numcpus/numcpus.go +++ b/vendor/github.com/tklauser/numcpus/numcpus.go @@ -73,3 +73,26 @@ func GetPossible() (int, error) { func GetPresent() (int, error) { return getPresent() } + +// ListOffline returns the list of offline CPUs. See [GetOffline] for details on +// when a CPU is considered offline. +func ListOffline() ([]int, error) { + return listOffline() +} + +// ListOnline returns the list of CPUs that are online and being scheduled. +func ListOnline() ([]int, error) { + return listOnline() +} + +// ListPossible returns the list of possible CPUs. See [GetPossible] for +// details on when a CPU is considered possible. +func ListPossible() ([]int, error) { + return listPossible() +} + +// ListPresent returns the list of present CPUs. See [GetPresent] for +// details on when a CPU is considered present. +func ListPresent() ([]int, error) { + return listPresent() +} diff --git a/vendor/github.com/tklauser/numcpus/numcpus_linux.go b/vendor/github.com/tklauser/numcpus/numcpus_linux.go index 7e75cb06..7b991da4 100644 --- a/vendor/github.com/tklauser/numcpus/numcpus_linux.go +++ b/vendor/github.com/tklauser/numcpus/numcpus_linux.go @@ -15,6 +15,7 @@ package numcpus import ( + "fmt" "os" "path/filepath" "strconv" @@ -23,7 +24,14 @@ import ( "golang.org/x/sys/unix" ) -const sysfsCPUBasePath = "/sys/devices/system/cpu" +const ( + sysfsCPUBasePath = "/sys/devices/system/cpu" + + offline = "offline" + online = "online" + possible = "possible" + present = "present" +) func getFromCPUAffinity() (int, error) { var cpuSet unix.CPUSet @@ -33,19 +41,26 @@ func getFromCPUAffinity() (int, error) { return cpuSet.Count(), nil } -func readCPURange(file string) (int, error) { +func readCPURangeWith[T any](file string, f func(cpus string) (T, error)) (T, error) { + var zero T buf, err := os.ReadFile(filepath.Join(sysfsCPUBasePath, file)) if err != nil { - return 0, err + return zero, err } - return parseCPURange(strings.Trim(string(buf), "\n ")) + return f(strings.Trim(string(buf), "\n ")) } -func parseCPURange(cpus string) (int, error) { +func countCPURange(cpus string) (int, error) { + // Treat empty file as valid. This might be the case if there are no offline CPUs in which + // case /sys/devices/system/cpu/offline is empty. + if cpus == "" { + return 0, nil + } + n := int(0) for _, cpuRange := range strings.Split(cpus, ",") { - if len(cpuRange) == 0 { - continue + if cpuRange == "" { + return 0, fmt.Errorf("empty CPU range in CPU string %q", cpus) } from, to, found := strings.Cut(cpuRange, "-") first, err := strconv.ParseUint(from, 10, 32) @@ -60,11 +75,49 @@ func parseCPURange(cpus string) (int, error) { if err != nil { return 0, err } + if last < first { + return 0, fmt.Errorf("last CPU in range (%d) less than first (%d)", last, first) + } n += int(last - first + 1) } return n, nil } +func listCPURange(cpus string) ([]int, error) { + // See comment in countCPURange. + if cpus == "" { + return []int{}, nil + } + + list := []int{} + for _, cpuRange := range strings.Split(cpus, ",") { + if cpuRange == "" { + return nil, fmt.Errorf("empty CPU range in CPU string %q", cpus) + } + from, to, found := strings.Cut(cpuRange, "-") + first, err := strconv.ParseUint(from, 10, 32) + if err != nil { + return nil, err + } + if !found { + // range containing a single element + list = append(list, int(first)) + continue + } + last, err := strconv.ParseUint(to, 10, 32) + if err != nil { + return nil, err + } + if last < first { + return nil, fmt.Errorf("last CPU in range (%d) less than first (%d)", last, first) + } + for cpu := int(first); cpu <= int(last); cpu++ { + list = append(list, cpu) + } + } + return list, nil +} + func getConfigured() (int, error) { d, err := os.Open(sysfsCPUBasePath) if err != nil { @@ -100,20 +153,36 @@ func getKernelMax() (int, error) { } func getOffline() (int, error) { - return readCPURange("offline") + return readCPURangeWith(offline, countCPURange) } func getOnline() (int, error) { if n, err := getFromCPUAffinity(); err == nil { return n, nil } - return readCPURange("online") + return readCPURangeWith(online, countCPURange) } func getPossible() (int, error) { - return readCPURange("possible") + return readCPURangeWith(possible, countCPURange) } func getPresent() (int, error) { - return readCPURange("present") + return readCPURangeWith(present, countCPURange) +} + +func listOffline() ([]int, error) { + return readCPURangeWith(offline, listCPURange) +} + +func listOnline() ([]int, error) { + return readCPURangeWith(online, listCPURange) +} + +func listPossible() ([]int, error) { + return readCPURangeWith(possible, listCPURange) +} + +func listPresent() ([]int, error) { + return readCPURangeWith(present, listCPURange) } diff --git a/vendor/github.com/tklauser/numcpus/numcpus_list_unsupported.go b/vendor/github.com/tklauser/numcpus/numcpus_list_unsupported.go new file mode 100644 index 00000000..af4efeac --- /dev/null +++ b/vendor/github.com/tklauser/numcpus/numcpus_list_unsupported.go @@ -0,0 +1,33 @@ +// Copyright 2024 Tobias Klauser +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !linux + +package numcpus + +func listOffline() ([]int, error) { + return nil, ErrNotSupported +} + +func listOnline() ([]int, error) { + return nil, ErrNotSupported +} + +func listPossible() ([]int, error) { + return nil, ErrNotSupported +} + +func listPresent() ([]int, error) { + return nil, ErrNotSupported +} diff --git a/vendor/github.com/urfave/cli/v2/godoc-current.txt b/vendor/github.com/urfave/cli/v2/godoc-current.txt index 4b620fee..3e29faab 100644 --- a/vendor/github.com/urfave/cli/v2/godoc-current.txt +++ b/vendor/github.com/urfave/cli/v2/godoc-current.txt @@ -35,7 +35,7 @@ var AppHelpTemplate = `NAME: {{template "helpNameTemplate" .}} USAGE: - {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}{{if .Args}}[arguments...]{{end}}{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Args}} [arguments...]{{end}}{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} VERSION: {{.Version}}{{end}}{{end}}{{if .Description}} @@ -136,7 +136,7 @@ var SubcommandHelpTemplate = `NAME: {{template "helpNameTemplate" .}} USAGE: - {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}{{if .Args}}[arguments...]{{end}}{{end}}{{end}}{{if .Description}} + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}command [command options]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Args}} [arguments...]{{end}}{{end}}{{end}}{{if .Description}} DESCRIPTION: {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}} diff --git a/vendor/github.com/urfave/cli/v2/help.go b/vendor/github.com/urfave/cli/v2/help.go index 640e2904..874be941 100644 --- a/vendor/github.com/urfave/cli/v2/help.go +++ b/vendor/github.com/urfave/cli/v2/help.go @@ -248,7 +248,6 @@ func ShowCommandHelpAndExit(c *Context, command string, code int) { // ShowCommandHelp prints help for the given command func ShowCommandHelp(ctx *Context, command string) error { - commands := ctx.App.Commands if ctx.Command.Subcommands != nil { commands = ctx.Command.Subcommands @@ -337,7 +336,6 @@ func ShowCommandCompletions(ctx *Context, command string) { DefaultCompleteWithFlags(c)(ctx) } } - } // printHelpCustom is the default implementation of HelpPrinterCustom. @@ -345,7 +343,6 @@ func ShowCommandCompletions(ctx *Context, command string) { // The customFuncs map will be combined with a default template.FuncMap to // allow using arbitrary functions in template rendering. func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs map[string]interface{}) { - const maxLineLength = 10000 funcMap := template.FuncMap{ @@ -450,6 +447,15 @@ func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { return false, arguments } + for _, arg := range arguments { + // If arguments include "--", shell completion is disabled + // because after "--" only positional arguments are accepted. + // https://unix.stackexchange.com/a/11382 + if arg == "--" { + return false, arguments + } + } + return true, arguments[:pos] } @@ -499,7 +505,6 @@ func wrap(input string, offset int, wrapAt int) string { ss = append(ss, wrapped) } else { ss = append(ss, padding+wrapped) - } } diff --git a/vendor/github.com/urfave/cli/v2/template.go b/vendor/github.com/urfave/cli/v2/template.go index 5748f4c2..8abc5ba4 100644 --- a/vendor/github.com/urfave/cli/v2/template.go +++ b/vendor/github.com/urfave/cli/v2/template.go @@ -1,7 +1,7 @@ package cli var helpNameTemplate = `{{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}}` -var usageTemplate = `{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}}{{if .ArgsUsage}}{{.ArgsUsage}}{{else}}{{if .Args}} [arguments...]{{end}}{{end}}{{end}}` +var usageTemplate = `{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Args}} [arguments...]{{end}}{{end}}{{end}}` var descriptionTemplate = `{{wrap .Description 3}}` var authorsTemplate = `{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: {{range $index, $author := .Authors}}{{if $index}} @@ -35,7 +35,7 @@ var AppHelpTemplate = `NAME: {{template "helpNameTemplate" .}} USAGE: - {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}{{if .Args}}[arguments...]{{end}}{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Args}} [arguments...]{{end}}{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} VERSION: {{.Version}}{{end}}{{end}}{{if .Description}} @@ -83,7 +83,7 @@ var SubcommandHelpTemplate = `NAME: {{template "helpNameTemplate" .}} USAGE: - {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}{{if .Args}}[arguments...]{{end}}{{end}}{{end}}{{if .Description}} + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}command [command options]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Args}} [arguments...]{{end}}{{end}}{{end}}{{if .Description}} DESCRIPTION: {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}} diff --git a/vendor/github.com/vektah/gqlparser/v2/ast/document.go b/vendor/github.com/vektah/gqlparser/v2/ast/document.go index a3a9e98d..7881f349 100644 --- a/vendor/github.com/vektah/gqlparser/v2/ast/document.go +++ b/vendor/github.com/vektah/gqlparser/v2/ast/document.go @@ -26,9 +26,10 @@ func (d *SchemaDocument) Merge(other *SchemaDocument) { } type Schema struct { - Query *Definition - Mutation *Definition - Subscription *Definition + Query *Definition + Mutation *Definition + Subscription *Definition + SchemaDirectives DirectiveList Types map[string]*Definition Directives map[string]*DirectiveDefinition diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/fields_on_correct_type.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/fields_on_correct_type.go index 24d4f3db..daa86448 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/fields_on_correct_type.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/fields_on_correct_type.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "fmt" @@ -11,8 +11,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("FieldsOnCorrectType", func(observers *Events, addError AddErrFunc) { +var FieldsOnCorrectTypeRule = Rule{ + Name: "FieldsOnCorrectType", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnField(func(walker *Walker, field *ast.Field) { if field.ObjectDefinition == nil || field.Definition != nil { return @@ -27,14 +28,18 @@ func init() { } addError( - Message(message), + Message("%s", message), At(field.Position), ) }) - }) + }, +} + +func init() { + AddRule(FieldsOnCorrectTypeRule.Name, FieldsOnCorrectTypeRule.RuleFunc) } -// Go through all of the implementations of type, as well as the interfaces +// Go through all the implementations of type, as well as the interfaces // that they implement. If any of those types include the provided field, // suggest them, sorted by how often the type is referenced, starting // with Interfaces. diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/fragments_on_composite_types.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/fragments_on_composite_types.go index 81ef861b..ccbffbf6 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/fragments_on_composite_types.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/fragments_on_composite_types.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "fmt" @@ -9,8 +9,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("FragmentsOnCompositeTypes", func(observers *Events, addError AddErrFunc) { +var FragmentsOnCompositeTypesRule = Rule{ + Name: "FragmentsOnCompositeTypes", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnInlineFragment(func(walker *Walker, inlineFragment *ast.InlineFragment) { fragmentType := walker.Schema.Types[inlineFragment.TypeCondition] if fragmentType == nil || fragmentType.IsCompositeType() { @@ -20,7 +21,7 @@ func init() { message := fmt.Sprintf(`Fragment cannot condition on non composite type "%s".`, inlineFragment.TypeCondition) addError( - Message(message), + Message("%s", message), At(inlineFragment.Position), ) }) @@ -33,9 +34,13 @@ func init() { message := fmt.Sprintf(`Fragment "%s" cannot condition on non composite type "%s".`, fragment.Name, fragment.TypeCondition) addError( - Message(message), + Message("%s", message), At(fragment.Position), ) }) - }) + }, +} + +func init() { + AddRule(FragmentsOnCompositeTypesRule.Name, FragmentsOnCompositeTypesRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_argument_names.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_argument_names.go index c187dabf..2659aeb5 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_argument_names.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_argument_names.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("KnownArgumentNames", func(observers *Events, addError AddErrFunc) { +var KnownArgumentNamesRule = Rule{ + Name: "KnownArgumentNames", + RuleFunc: func(observers *Events, addError AddErrFunc) { // A GraphQL field is only valid if all supplied arguments are defined by that field. observers.OnField(func(walker *Walker, field *ast.Field) { if field.Definition == nil || field.ObjectDefinition == nil { @@ -55,5 +56,9 @@ func init() { ) } }) - }) + }, +} + +func init() { + AddRule(KnownArgumentNamesRule.Name, KnownArgumentNamesRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_directives.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_directives.go index f7bae811..b68fa5e8 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_directives.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_directives.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("KnownDirectives", func(observers *Events, addError AddErrFunc) { +var KnownDirectivesRule = Rule{ + Name: "KnownDirectives", + RuleFunc: func(observers *Events, addError AddErrFunc) { type mayNotBeUsedDirective struct { Name string Line int @@ -45,5 +46,9 @@ func init() { seen[tmp] = true } }) - }) + }, +} + +func init() { + AddRule(KnownDirectivesRule.Name, KnownDirectivesRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_fragment_names.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_fragment_names.go index 3afd9c1c..77a82f67 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_fragment_names.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_fragment_names.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("KnownFragmentNames", func(observers *Events, addError AddErrFunc) { +var KnownFragmentNamesRule = Rule{ + Name: "KnownFragmentNames", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnFragmentSpread(func(walker *Walker, fragmentSpread *ast.FragmentSpread) { if fragmentSpread.Definition == nil { addError( @@ -17,5 +18,9 @@ func init() { ) } }) - }) + }, +} + +func init() { + AddRule(KnownFragmentNamesRule.Name, KnownFragmentNamesRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_root_type.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_root_type.go index 60bc0d52..76962c3d 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_root_type.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_root_type.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "fmt" @@ -9,8 +9,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("KnownRootType", func(observers *Events, addError AddErrFunc) { +var KnownRootTypeRule = Rule{ + Name: "KnownRootType", + RuleFunc: func(observers *Events, addError AddErrFunc) { // A query's root must be a valid type. Surprisingly, this isn't // checked anywhere else! observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) { @@ -33,5 +34,9 @@ func init() { At(operation.Position)) } }) - }) + }, +} + +func init() { + AddRule(KnownRootTypeRule.Name, KnownRootTypeRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_type_names.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_type_names.go index 902939d3..717019fb 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_type_names.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_type_names.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("KnownTypeNames", func(observers *Events, addError AddErrFunc) { +var KnownTypeNamesRule = Rule{ + Name: "KnownTypeNames", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnVariable(func(walker *Walker, variable *ast.VariableDefinition) { typeName := variable.Type.Name() typdef := walker.Schema.Types[typeName] @@ -57,5 +58,9 @@ func init() { At(fragment.Position), ) }) - }) + }, +} + +func init() { + AddRule(KnownTypeNamesRule.Name, KnownTypeNamesRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/lone_anonymous_operation.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/lone_anonymous_operation.go index fe8bb203..ba71f141 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/lone_anonymous_operation.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/lone_anonymous_operation.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("LoneAnonymousOperation", func(observers *Events, addError AddErrFunc) { +var LoneAnonymousOperationRule = Rule{ + Name: "LoneAnonymousOperation", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) { if operation.Name == "" && len(walker.Document.Operations) > 1 { addError( @@ -17,5 +18,9 @@ func init() { ) } }) - }) + }, +} + +func init() { + AddRule(LoneAnonymousOperationRule.Name, LoneAnonymousOperationRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_fragment_cycles.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_fragment_cycles.go index a953174f..c80def7c 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_fragment_cycles.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_fragment_cycles.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "fmt" @@ -10,8 +10,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("NoFragmentCycles", func(observers *Events, addError AddErrFunc) { +var NoFragmentCyclesRule = Rule{ + Name: "NoFragmentCycles", + RuleFunc: func(observers *Events, addError AddErrFunc) { visitedFrags := make(map[string]bool) observers.OnFragment(func(walker *Walker, fragment *ast.FragmentDefinition) { @@ -67,7 +68,11 @@ func init() { recursive(fragment) }) - }) + }, +} + +func init() { + AddRule(NoFragmentCyclesRule.Name, NoFragmentCyclesRule.RuleFunc) } func getFragmentSpreads(node ast.SelectionSet) []*ast.FragmentSpread { diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_undefined_variables.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_undefined_variables.go index 46c18d12..84ed5fa7 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_undefined_variables.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_undefined_variables.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("NoUndefinedVariables", func(observers *Events, addError AddErrFunc) { +var NoUndefinedVariablesRule = Rule{ + Name: "NoUndefinedVariables", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnValue(func(walker *Walker, value *ast.Value) { if walker.CurrentOperation == nil || value.Kind != ast.Variable || value.VariableDefinition != nil { return @@ -26,5 +27,9 @@ func init() { ) } }) - }) + }, +} + +func init() { + AddRule(NoUndefinedVariablesRule.Name, NoUndefinedVariablesRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_unused_fragments.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_unused_fragments.go index 59d9c15c..e95bbe69 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_unused_fragments.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_unused_fragments.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("NoUnusedFragments", func(observers *Events, addError AddErrFunc) { +var NoUnusedFragmentsRule = Rule{ + Name: "NoUnusedFragments", + RuleFunc: func(observers *Events, addError AddErrFunc) { inFragmentDefinition := false fragmentNameUsed := make(map[string]bool) @@ -27,5 +28,9 @@ func init() { ) } }) - }) + }, +} + +func init() { + AddRule(NoUnusedFragmentsRule.Name, NoUnusedFragmentsRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_unused_variables.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_unused_variables.go index d3088109..425df205 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_unused_variables.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_unused_variables.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("NoUnusedVariables", func(observers *Events, addError AddErrFunc) { +var NoUnusedVariablesRule = Rule{ + Name: "NoUnusedVariables", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) { for _, varDef := range operation.VariableDefinitions { if varDef.Used { @@ -28,5 +29,9 @@ func init() { } } }) - }) + }, +} + +func init() { + AddRule(NoUnusedVariablesRule.Name, NoUnusedVariablesRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/overlapping_fields_can_be_merged.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/overlapping_fields_can_be_merged.go index eaa2035e..d2c66aac 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/overlapping_fields_can_be_merged.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/overlapping_fields_can_be_merged.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "bytes" @@ -11,8 +11,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("OverlappingFieldsCanBeMerged", func(observers *Events, addError AddErrFunc) { +var OverlappingFieldsCanBeMergedRule = Rule{ + Name: "OverlappingFieldsCanBeMerged", + RuleFunc: func(observers *Events, addError AddErrFunc) { /** * Algorithm: * @@ -41,7 +42,7 @@ func init() { * * D) When comparing "between" a set of fields and a referenced fragment, first * a comparison is made between each field in the original set of fields and - * each field in the the referenced set of fields. + * each field in the referenced set of fields. * * E) Also, if any fragment is referenced in the referenced selection set, * then a comparison is made "between" the original set of fields and the @@ -104,7 +105,11 @@ func init() { conflict.addFieldsConflictMessage(addError) } }) - }) + }, +} + +func init() { + AddRule(OverlappingFieldsCanBeMergedRule.Name, OverlappingFieldsCanBeMergedRule.RuleFunc) } type pairSet struct { diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/possible_fragment_spreads.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/possible_fragment_spreads.go index 244e5f20..01255e9b 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/possible_fragment_spreads.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/possible_fragment_spreads.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("PossibleFragmentSpreads", func(observers *Events, addError AddErrFunc) { +var PossibleFragmentSpreadsRule = Rule{ + Name: "PossibleFragmentSpreads", + RuleFunc: func(observers *Events, addError AddErrFunc) { validate := func(walker *Walker, parentDef *ast.Definition, fragmentName string, emitError func()) { if parentDef == nil { return @@ -65,5 +66,9 @@ func init() { ) }) }) - }) + }, +} + +func init() { + AddRule(PossibleFragmentSpreadsRule.Name, PossibleFragmentSpreadsRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/provided_required_arguments.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/provided_required_arguments.go index ab79163b..37428d44 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/provided_required_arguments.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/provided_required_arguments.go @@ -1,14 +1,14 @@ -package validator +package rules import ( - "github.com/vektah/gqlparser/v2/ast" - //nolint:revive // Validator rules each use dot imports for convenience. + "github.com/vektah/gqlparser/v2/ast" . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("ProvidedRequiredArguments", func(observers *Events, addError AddErrFunc) { +var ProvidedRequiredArgumentsRule = Rule{ + Name: "ProvidedRequiredArguments", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnField(func(walker *Walker, field *ast.Field) { if field.Definition == nil { return @@ -60,5 +60,9 @@ func init() { ) } }) - }) + }, +} + +func init() { + AddRule(ProvidedRequiredArgumentsRule.Name, ProvidedRequiredArgumentsRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/scalar_leafs.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/scalar_leafs.go index 605ab9e8..5628ce2c 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/scalar_leafs.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/scalar_leafs.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("ScalarLeafs", func(observers *Events, addError AddErrFunc) { +var ScalarLeafsRule = Rule{ + Name: "ScalarLeafs", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnField(func(walker *Walker, field *ast.Field) { if field.Definition == nil { return @@ -34,5 +35,9 @@ func init() { ) } }) - }) + }, +} + +func init() { + AddRule(ScalarLeafsRule.Name, ScalarLeafsRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/single_field_subscriptions.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/single_field_subscriptions.go index 7d4c6843..94a4b304 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/single_field_subscriptions.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/single_field_subscriptions.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "strconv" @@ -10,8 +10,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("SingleFieldSubscriptions", func(observers *Events, addError AddErrFunc) { +var SingleFieldSubscriptionsRule = Rule{ + Name: "SingleFieldSubscriptions", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) { if walker.Schema.Subscription == nil || operation.Operation != ast.Subscription { return @@ -40,7 +41,11 @@ func init() { } } }) - }) + }, +} + +func init() { + AddRule(SingleFieldSubscriptionsRule.Name, SingleFieldSubscriptionsRule.RuleFunc) } type topField struct { diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_argument_names.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_argument_names.go index e977d638..d0c52909 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_argument_names.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_argument_names.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("UniqueArgumentNames", func(observers *Events, addError AddErrFunc) { +var UniqueArgumentNamesRule = Rule{ + Name: "UniqueArgumentNames", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnField(func(walker *Walker, field *ast.Field) { checkUniqueArgs(field.Arguments, addError) }) @@ -16,7 +17,11 @@ func init() { observers.OnDirective(func(walker *Walker, directive *ast.Directive) { checkUniqueArgs(directive.Arguments, addError) }) - }) + }, +} + +func init() { + AddRule(UniqueArgumentNamesRule.Name, UniqueArgumentNamesRule.RuleFunc) } func checkUniqueArgs(args ast.ArgumentList, addError AddErrFunc) { diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_directives_per_location.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_directives_per_location.go index 47971ee1..4cab38bd 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_directives_per_location.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_directives_per_location.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("UniqueDirectivesPerLocation", func(observers *Events, addError AddErrFunc) { +var UniqueDirectivesPerLocationRule = Rule{ + Name: "UniqueDirectivesPerLocation", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnDirectiveList(func(walker *Walker, directives []*ast.Directive) { seen := map[string]bool{} @@ -22,5 +23,9 @@ func init() { seen[dir.Name] = true } }) - }) + }, +} + +func init() { + AddRule(UniqueDirectivesPerLocationRule.Name, UniqueDirectivesPerLocationRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_fragment_names.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_fragment_names.go index 2c44a437..3d2c7289 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_fragment_names.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_fragment_names.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("UniqueFragmentNames", func(observers *Events, addError AddErrFunc) { +var UniqueFragmentNamesRule = Rule{ + Name: "UniqueFragmentNames", + RuleFunc: func(observers *Events, addError AddErrFunc) { seenFragments := map[string]bool{} observers.OnFragment(func(walker *Walker, fragment *ast.FragmentDefinition) { @@ -20,5 +21,9 @@ func init() { } seenFragments[fragment.Name] = true }) - }) + }, +} + +func init() { + AddRule(UniqueFragmentNamesRule.Name, UniqueFragmentNamesRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_input_field_names.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_input_field_names.go index c5fce8ff..3ccbbfe0 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_input_field_names.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_input_field_names.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("UniqueInputFieldNames", func(observers *Events, addError AddErrFunc) { +var UniqueInputFieldNamesRule = Rule{ + Name: "UniqueInputFieldNames", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnValue(func(walker *Walker, value *ast.Value) { if value.Kind != ast.ObjectValue { return @@ -25,5 +26,9 @@ func init() { seen[field.Name] = true } }) - }) + }, +} + +func init() { + AddRule(UniqueInputFieldNamesRule.Name, UniqueInputFieldNamesRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_operation_names.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_operation_names.go index 49ffbe47..401b0ad3 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_operation_names.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_operation_names.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("UniqueOperationNames", func(observers *Events, addError AddErrFunc) { +var UniqueOperationNamesRule = Rule{ + Name: "UniqueOperationNames", + RuleFunc: func(observers *Events, addError AddErrFunc) { seen := map[string]bool{} observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) { @@ -20,5 +21,9 @@ func init() { } seen[operation.Name] = true }) - }) + }, +} + +func init() { + AddRule(UniqueOperationNamesRule.Name, UniqueOperationNamesRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_variable_names.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_variable_names.go index c93948c1..f0e4a200 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_variable_names.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_variable_names.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("UniqueVariableNames", func(observers *Events, addError AddErrFunc) { +var UniqueVariableNamesRule = Rule{ + Name: "UniqueVariableNames", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) { seen := map[string]int{} for _, def := range operation.VariableDefinitions { @@ -22,5 +23,9 @@ func init() { seen[def.Variable]++ } }) - }) + }, +} + +func init() { + AddRule(UniqueVariableNamesRule.Name, UniqueVariableNamesRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/values_of_correct_type.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/values_of_correct_type.go index 914e428e..7784fe0c 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/values_of_correct_type.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/values_of_correct_type.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "errors" @@ -11,8 +11,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("ValuesOfCorrectType", func(observers *Events, addError AddErrFunc) { +var ValuesOfCorrectTypeRule = Rule{ + Name: "ValuesOfCorrectType", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnValue(func(walker *Walker, value *ast.Value) { if value.Definition == nil || value.ExpectedType == nil { return @@ -134,7 +135,11 @@ func init() { panic(fmt.Errorf("unhandled %T", value)) } }) - }) + }, +} + +func init() { + AddRule(ValuesOfCorrectTypeRule.Name, ValuesOfCorrectTypeRule.RuleFunc) } func unexpectedTypeMessage(addError AddErrFunc, v *ast.Value) { diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/variables_are_input_types.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/variables_are_input_types.go index d16ee021..59b2e6f7 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/variables_are_input_types.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/variables_are_input_types.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("VariablesAreInputTypes", func(observers *Events, addError AddErrFunc) { +var VariablesAreInputTypesRule = Rule{ + Name: "VariablesAreInputTypes", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnOperation(func(walker *Walker, operation *ast.OperationDefinition) { for _, def := range operation.VariableDefinitions { if def.Definition == nil { @@ -26,5 +27,9 @@ func init() { } } }) - }) + }, +} + +func init() { + AddRule(VariablesAreInputTypesRule.Name, VariablesAreInputTypesRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/rules/variables_in_allowed_position.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/variables_in_allowed_position.go index e3fd6fbb..75b0f7f7 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/rules/variables_in_allowed_position.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/variables_in_allowed_position.go @@ -1,4 +1,4 @@ -package validator +package rules import ( "github.com/vektah/gqlparser/v2/ast" @@ -7,8 +7,9 @@ import ( . "github.com/vektah/gqlparser/v2/validator" ) -func init() { - AddRule("VariablesInAllowedPosition", func(observers *Events, addError AddErrFunc) { +var VariablesInAllowedPositionRule = Rule{ + Name: "VariablesInAllowedPosition", + RuleFunc: func(observers *Events, addError AddErrFunc) { observers.OnValue(func(walker *Walker, value *ast.Value) { if value.Kind != ast.Variable || value.ExpectedType == nil || value.VariableDefinition == nil || walker.CurrentOperation == nil { return @@ -36,5 +37,9 @@ func init() { ) } }) - }) + }, +} + +func init() { + AddRule(VariablesInAllowedPositionRule.Name, VariablesInAllowedPositionRule.RuleFunc) } diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/schema.go b/vendor/github.com/vektah/gqlparser/v2/validator/schema.go index d8590284..9f9ddde4 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/schema.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/schema.go @@ -122,6 +122,10 @@ func ValidateSchemaDocument(sd *SchemaDocument) (*Schema, error) { schema.Subscription = def } } + if err := validateDirectives(&schema, sd.Schema[0].Directives, LocationSchema, nil); err != nil { + return nil, err + } + schema.SchemaDirectives = append(schema.SchemaDirectives, sd.Schema[0].Directives...) } for _, ext := range sd.SchemaExtension { @@ -139,6 +143,10 @@ func ValidateSchemaDocument(sd *SchemaDocument) (*Schema, error) { schema.Subscription = def } } + if err := validateDirectives(&schema, ext.Directives, LocationSchema, nil); err != nil { + return nil, err + } + schema.SchemaDirectives = append(schema.SchemaDirectives, ext.Directives...) } if err := validateTypeDefinitions(&schema); err != nil { diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/validator.go b/vendor/github.com/vektah/gqlparser/v2/validator/validator.go index b4f37ce2..36564b23 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/validator.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/validator.go @@ -8,22 +8,26 @@ import ( type AddErrFunc func(options ...ErrorOption) -type ruleFunc func(observers *Events, addError AddErrFunc) +type RuleFunc func(observers *Events, addError AddErrFunc) -type rule struct { - name string - rule ruleFunc +type Rule struct { + Name string + RuleFunc RuleFunc } -var rules []rule +var specifiedRules []Rule -// addRule to rule set. +// AddRule adds rule to the rule set. // f is called once each time `Validate` is executed. -func AddRule(name string, f ruleFunc) { - rules = append(rules, rule{name: name, rule: f}) +func AddRule(name string, ruleFunc RuleFunc) { + specifiedRules = append(specifiedRules, Rule{Name: name, RuleFunc: ruleFunc}) } -func Validate(schema *Schema, doc *QueryDocument) gqlerror.List { +func Validate(schema *Schema, doc *QueryDocument, rules ...Rule) gqlerror.List { + if rules == nil { + rules = specifiedRules + } + var errs gqlerror.List if schema == nil { errs = append(errs, gqlerror.Errorf("cannot validate as Schema is nil")) @@ -37,9 +41,9 @@ func Validate(schema *Schema, doc *QueryDocument) gqlerror.List { observers := &Events{} for i := range rules { rule := rules[i] - rule.rule(observers, func(options ...ErrorOption) { + rule.RuleFunc(observers, func(options ...ErrorOption) { err := &gqlerror.Error{ - Rule: rule.name, + Rule: rule.Name, } for _, o := range options { o(err) diff --git a/vendor/github.com/vektah/gqlparser/v2/validator/vars.go b/vendor/github.com/vektah/gqlparser/v2/validator/vars.go index c386b6b9..f2934caf 100644 --- a/vendor/github.com/vektah/gqlparser/v2/validator/vars.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/vars.go @@ -180,7 +180,7 @@ func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) (reflec return val, gqlerror.ErrorPathf(v.path, "cannot use %s as %s", kind.String(), typ.NamedType) case ast.InputObject: if val.Kind() != reflect.Map { - return val, gqlerror.ErrorPathf(v.path, "must be a %s", def.Name) + return val, gqlerror.ErrorPathf(v.path, "must be a %s, not a %s", def.Name, val.Kind()) } // check for unknown fields diff --git a/vendor/go.etcd.io/bbolt/.go-version b/vendor/go.etcd.io/bbolt/.go-version index f124bfa1..013173af 100644 --- a/vendor/go.etcd.io/bbolt/.go-version +++ b/vendor/go.etcd.io/bbolt/.go-version @@ -1 +1 @@ -1.21.9 +1.22.6 diff --git a/vendor/go.etcd.io/bbolt/Makefile b/vendor/go.etcd.io/bbolt/Makefile index 18154c63..21407797 100644 --- a/vendor/go.etcd.io/bbolt/Makefile +++ b/vendor/go.etcd.io/bbolt/Makefile @@ -41,6 +41,15 @@ coverage: TEST_FREELIST_TYPE=array go test -v -timeout 30m \ -coverprofile cover-freelist-array.out -covermode atomic +BOLT_CMD=bbolt + +build: + go build -o bin/${BOLT_CMD} ./cmd/${BOLT_CMD} + +.PHONY: clean +clean: # Clean binaries + rm -f ./bin/${BOLT_CMD} + .PHONY: gofail-enable gofail-enable: install-gofail gofail enable . @@ -61,3 +70,7 @@ test-failpoint: @echo "[failpoint] array freelist test" TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint +.PHONY: test-robustness # Running robustness tests requires root permission +test-robustness: + go test -v ${TESTFLAGS} ./tests/dmflakey -test.root + go test -v ${TESTFLAGS} ./tests/robustness -test.root diff --git a/vendor/go.etcd.io/bbolt/db.go b/vendor/go.etcd.io/bbolt/db.go index 4175bdf3..822798e4 100644 --- a/vendor/go.etcd.io/bbolt/db.go +++ b/vendor/go.etcd.io/bbolt/db.go @@ -524,7 +524,7 @@ func (db *DB) munmap() error { // gofail: var unmapError string // return errors.New(unmapError) if err := munmap(db); err != nil { - return fmt.Errorf("unmap error: " + err.Error()) + return fmt.Errorf("unmap error: %v", err.Error()) } return nil @@ -571,7 +571,7 @@ func (db *DB) munlock(fileSize int) error { // gofail: var munlockError string // return errors.New(munlockError) if err := munlock(db, fileSize); err != nil { - return fmt.Errorf("munlock error: " + err.Error()) + return fmt.Errorf("munlock error: %v", err.Error()) } return nil } @@ -580,7 +580,7 @@ func (db *DB) mlock(fileSize int) error { // gofail: var mlockError string // return errors.New(mlockError) if err := mlock(db, fileSize); err != nil { - return fmt.Errorf("mlock error: " + err.Error()) + return fmt.Errorf("mlock error: %v", err.Error()) } return nil } @@ -1159,6 +1159,8 @@ func (db *DB) grow(sz int) error { // https://github.com/boltdb/bolt/issues/284 if !db.NoGrowSync && !db.readOnly { if runtime.GOOS != "windows" { + // gofail: var resizeFileError string + // return errors.New(resizeFileError) if err := db.file.Truncate(int64(sz)); err != nil { return fmt.Errorf("file resize error: %s", err) } diff --git a/vendor/go.etcd.io/bbolt/freelist.go b/vendor/go.etcd.io/bbolt/freelist.go index 61d43f81..dffc7bc7 100644 --- a/vendor/go.etcd.io/bbolt/freelist.go +++ b/vendor/go.etcd.io/bbolt/freelist.go @@ -252,6 +252,14 @@ func (f *freelist) rollback(txid txid) { } // Remove pages from pending list and mark as free if allocated by txid. delete(f.pending, txid) + + // Remove pgids which are allocated by this txid + for pgid, tid := range f.allocs { + if tid == txid { + delete(f.allocs, pgid) + } + } + f.mergeSpans(m) } diff --git a/vendor/go.etcd.io/bbolt/tx.go b/vendor/go.etcd.io/bbolt/tx.go index 2fac8c0a..766395de 100644 --- a/vendor/go.etcd.io/bbolt/tx.go +++ b/vendor/go.etcd.io/bbolt/tx.go @@ -1,6 +1,7 @@ package bbolt import ( + "errors" "fmt" "io" "os" @@ -185,6 +186,10 @@ func (tx *Tx) Commit() error { // If the high water mark has moved up then attempt to grow the database. if tx.meta.pgid > opgid { + _ = errors.New("") + // gofail: var lackOfDiskSpace string + // tx.rollback() + // return errors.New(lackOfDiskSpace) if err := tx.db.grow(int(tx.meta.pgid+1) * tx.db.pageSize); err != nil { tx.rollback() return err @@ -470,6 +475,7 @@ func (tx *Tx) write() error { // Ignore file sync if flag is set on DB. if !tx.db.NoSync || IgnoreNoSync { + // gofail: var beforeSyncDataPages struct{} if err := fdatasync(tx.db); err != nil { return err } @@ -507,6 +513,7 @@ func (tx *Tx) writeMeta() error { return err } if !tx.db.NoSync || IgnoreNoSync { + // gofail: var beforeSyncMetaPage struct{} if err := fdatasync(tx.db); err != nil { return err } diff --git a/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_linux.go b/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_linux.go index 3b974754..f9057fd2 100644 --- a/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_linux.go +++ b/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_linux.go @@ -25,15 +25,18 @@ package runtime import ( "errors" - "math" cg "go.uber.org/automaxprocs/internal/cgroups" ) // CPUQuotaToGOMAXPROCS converts the CPU quota applied to the calling process -// to a valid GOMAXPROCS value. -func CPUQuotaToGOMAXPROCS(minValue int) (int, CPUQuotaStatus, error) { - cgroups, err := newQueryer() +// to a valid GOMAXPROCS value. The quota is converted from float to int using round. +// If round == nil, DefaultRoundFunc is used. +func CPUQuotaToGOMAXPROCS(minValue int, round func(v float64) int) (int, CPUQuotaStatus, error) { + if round == nil { + round = DefaultRoundFunc + } + cgroups, err := _newQueryer() if err != nil { return -1, CPUQuotaUndefined, err } @@ -43,7 +46,7 @@ func CPUQuotaToGOMAXPROCS(minValue int) (int, CPUQuotaStatus, error) { return -1, CPUQuotaUndefined, err } - maxProcs := int(math.Floor(quota)) + maxProcs := round(quota) if minValue > 0 && maxProcs < minValue { return minValue, CPUQuotaMinUsed, nil } @@ -57,6 +60,7 @@ type queryer interface { var ( _newCgroups2 = cg.NewCGroups2ForCurrentProcess _newCgroups = cg.NewCGroupsForCurrentProcess + _newQueryer = newQueryer ) func newQueryer() (queryer, error) { diff --git a/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_unsupported.go b/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_unsupported.go index 69225544..e7470150 100644 --- a/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_unsupported.go +++ b/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_unsupported.go @@ -26,6 +26,6 @@ package runtime // CPUQuotaToGOMAXPROCS converts the CPU quota applied to the calling process // to a valid GOMAXPROCS value. This is Linux-specific and not supported in the // current OS. -func CPUQuotaToGOMAXPROCS(_ int) (int, CPUQuotaStatus, error) { +func CPUQuotaToGOMAXPROCS(_ int, _ func(v float64) int) (int, CPUQuotaStatus, error) { return -1, CPUQuotaUndefined, nil } diff --git a/vendor/go.uber.org/automaxprocs/internal/runtime/runtime.go b/vendor/go.uber.org/automaxprocs/internal/runtime/runtime.go index df6eacf0..f8a2834a 100644 --- a/vendor/go.uber.org/automaxprocs/internal/runtime/runtime.go +++ b/vendor/go.uber.org/automaxprocs/internal/runtime/runtime.go @@ -20,6 +20,8 @@ package runtime +import "math" + // CPUQuotaStatus presents the status of how CPU quota is used type CPUQuotaStatus int @@ -31,3 +33,8 @@ const ( // CPUQuotaMinUsed is returned when CPU quota is smaller than the min value CPUQuotaMinUsed ) + +// DefaultRoundFunc is the default function to convert CPU quota from float to int. It rounds the value down (floor). +func DefaultRoundFunc(v float64) int { + return int(math.Floor(v)) +} diff --git a/vendor/go.uber.org/automaxprocs/maxprocs/maxprocs.go b/vendor/go.uber.org/automaxprocs/maxprocs/maxprocs.go index 98176d64..e561fe60 100644 --- a/vendor/go.uber.org/automaxprocs/maxprocs/maxprocs.go +++ b/vendor/go.uber.org/automaxprocs/maxprocs/maxprocs.go @@ -37,9 +37,10 @@ func currentMaxProcs() int { } type config struct { - printf func(string, ...interface{}) - procs func(int) (int, iruntime.CPUQuotaStatus, error) - minGOMAXPROCS int + printf func(string, ...interface{}) + procs func(int, func(v float64) int) (int, iruntime.CPUQuotaStatus, error) + minGOMAXPROCS int + roundQuotaFunc func(v float64) int } func (c *config) log(fmt string, args ...interface{}) { @@ -71,6 +72,13 @@ func Min(n int) Option { }) } +// RoundQuotaFunc sets the function that will be used to covert the CPU quota from float to int. +func RoundQuotaFunc(rf func(v float64) int) Option { + return optionFunc(func(cfg *config) { + cfg.roundQuotaFunc = rf + }) +} + type optionFunc func(*config) func (of optionFunc) apply(cfg *config) { of(cfg) } @@ -82,8 +90,9 @@ func (of optionFunc) apply(cfg *config) { of(cfg) } // configured CPU quota. func Set(opts ...Option) (func(), error) { cfg := &config{ - procs: iruntime.CPUQuotaToGOMAXPROCS, - minGOMAXPROCS: 1, + procs: iruntime.CPUQuotaToGOMAXPROCS, + roundQuotaFunc: iruntime.DefaultRoundFunc, + minGOMAXPROCS: 1, } for _, o := range opts { o.apply(cfg) @@ -102,7 +111,7 @@ func Set(opts ...Option) (func(), error) { return undoNoop, nil } - maxProcs, status, err := cfg.procs(cfg.minGOMAXPROCS) + maxProcs, status, err := cfg.procs(cfg.minGOMAXPROCS, cfg.roundQuotaFunc) if err != nil { return undoNoop, err } diff --git a/vendor/go.uber.org/automaxprocs/maxprocs/version.go b/vendor/go.uber.org/automaxprocs/maxprocs/version.go index 108a9553..cc7fc5ae 100644 --- a/vendor/go.uber.org/automaxprocs/maxprocs/version.go +++ b/vendor/go.uber.org/automaxprocs/maxprocs/version.go @@ -21,4 +21,4 @@ package maxprocs // Version is the current package version. -const Version = "1.5.2" +const Version = "1.6.0" diff --git a/vendor/golang.org/x/crypto/argon2/blamka_amd64.s b/vendor/golang.org/x/crypto/argon2/blamka_amd64.s index 6713acca..c3895478 100644 --- a/vendor/golang.org/x/crypto/argon2/blamka_amd64.s +++ b/vendor/golang.org/x/crypto/argon2/blamka_amd64.s @@ -1,243 +1,2791 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Code generated by command: go run blamka_amd64.go -out ../blamka_amd64.s -pkg argon2. DO NOT EDIT. //go:build amd64 && gc && !purego #include "textflag.h" -DATA ·c40<>+0x00(SB)/8, $0x0201000706050403 -DATA ·c40<>+0x08(SB)/8, $0x0a09080f0e0d0c0b -GLOBL ·c40<>(SB), (NOPTR+RODATA), $16 - -DATA ·c48<>+0x00(SB)/8, $0x0100070605040302 -DATA ·c48<>+0x08(SB)/8, $0x09080f0e0d0c0b0a -GLOBL ·c48<>(SB), (NOPTR+RODATA), $16 - -#define SHUFFLE(v2, v3, v4, v5, v6, v7, t1, t2) \ - MOVO v4, t1; \ - MOVO v5, v4; \ - MOVO t1, v5; \ - MOVO v6, t1; \ - PUNPCKLQDQ v6, t2; \ - PUNPCKHQDQ v7, v6; \ - PUNPCKHQDQ t2, v6; \ - PUNPCKLQDQ v7, t2; \ - MOVO t1, v7; \ - MOVO v2, t1; \ - PUNPCKHQDQ t2, v7; \ - PUNPCKLQDQ v3, t2; \ - PUNPCKHQDQ t2, v2; \ - PUNPCKLQDQ t1, t2; \ - PUNPCKHQDQ t2, v3 - -#define SHUFFLE_INV(v2, v3, v4, v5, v6, v7, t1, t2) \ - MOVO v4, t1; \ - MOVO v5, v4; \ - MOVO t1, v5; \ - MOVO v2, t1; \ - PUNPCKLQDQ v2, t2; \ - PUNPCKHQDQ v3, v2; \ - PUNPCKHQDQ t2, v2; \ - PUNPCKLQDQ v3, t2; \ - MOVO t1, v3; \ - MOVO v6, t1; \ - PUNPCKHQDQ t2, v3; \ - PUNPCKLQDQ v7, t2; \ - PUNPCKHQDQ t2, v6; \ - PUNPCKLQDQ t1, t2; \ - PUNPCKHQDQ t2, v7 - -#define HALF_ROUND(v0, v1, v2, v3, v4, v5, v6, v7, t0, c40, c48) \ - MOVO v0, t0; \ - PMULULQ v2, t0; \ - PADDQ v2, v0; \ - PADDQ t0, v0; \ - PADDQ t0, v0; \ - PXOR v0, v6; \ - PSHUFD $0xB1, v6, v6; \ - MOVO v4, t0; \ - PMULULQ v6, t0; \ - PADDQ v6, v4; \ - PADDQ t0, v4; \ - PADDQ t0, v4; \ - PXOR v4, v2; \ - PSHUFB c40, v2; \ - MOVO v0, t0; \ - PMULULQ v2, t0; \ - PADDQ v2, v0; \ - PADDQ t0, v0; \ - PADDQ t0, v0; \ - PXOR v0, v6; \ - PSHUFB c48, v6; \ - MOVO v4, t0; \ - PMULULQ v6, t0; \ - PADDQ v6, v4; \ - PADDQ t0, v4; \ - PADDQ t0, v4; \ - PXOR v4, v2; \ - MOVO v2, t0; \ - PADDQ v2, t0; \ - PSRLQ $63, v2; \ - PXOR t0, v2; \ - MOVO v1, t0; \ - PMULULQ v3, t0; \ - PADDQ v3, v1; \ - PADDQ t0, v1; \ - PADDQ t0, v1; \ - PXOR v1, v7; \ - PSHUFD $0xB1, v7, v7; \ - MOVO v5, t0; \ - PMULULQ v7, t0; \ - PADDQ v7, v5; \ - PADDQ t0, v5; \ - PADDQ t0, v5; \ - PXOR v5, v3; \ - PSHUFB c40, v3; \ - MOVO v1, t0; \ - PMULULQ v3, t0; \ - PADDQ v3, v1; \ - PADDQ t0, v1; \ - PADDQ t0, v1; \ - PXOR v1, v7; \ - PSHUFB c48, v7; \ - MOVO v5, t0; \ - PMULULQ v7, t0; \ - PADDQ v7, v5; \ - PADDQ t0, v5; \ - PADDQ t0, v5; \ - PXOR v5, v3; \ - MOVO v3, t0; \ - PADDQ v3, t0; \ - PSRLQ $63, v3; \ - PXOR t0, v3 - -#define LOAD_MSG_0(block, off) \ - MOVOU 8*(off+0)(block), X0; \ - MOVOU 8*(off+2)(block), X1; \ - MOVOU 8*(off+4)(block), X2; \ - MOVOU 8*(off+6)(block), X3; \ - MOVOU 8*(off+8)(block), X4; \ - MOVOU 8*(off+10)(block), X5; \ - MOVOU 8*(off+12)(block), X6; \ - MOVOU 8*(off+14)(block), X7 - -#define STORE_MSG_0(block, off) \ - MOVOU X0, 8*(off+0)(block); \ - MOVOU X1, 8*(off+2)(block); \ - MOVOU X2, 8*(off+4)(block); \ - MOVOU X3, 8*(off+6)(block); \ - MOVOU X4, 8*(off+8)(block); \ - MOVOU X5, 8*(off+10)(block); \ - MOVOU X6, 8*(off+12)(block); \ - MOVOU X7, 8*(off+14)(block) - -#define LOAD_MSG_1(block, off) \ - MOVOU 8*off+0*8(block), X0; \ - MOVOU 8*off+16*8(block), X1; \ - MOVOU 8*off+32*8(block), X2; \ - MOVOU 8*off+48*8(block), X3; \ - MOVOU 8*off+64*8(block), X4; \ - MOVOU 8*off+80*8(block), X5; \ - MOVOU 8*off+96*8(block), X6; \ - MOVOU 8*off+112*8(block), X7 - -#define STORE_MSG_1(block, off) \ - MOVOU X0, 8*off+0*8(block); \ - MOVOU X1, 8*off+16*8(block); \ - MOVOU X2, 8*off+32*8(block); \ - MOVOU X3, 8*off+48*8(block); \ - MOVOU X4, 8*off+64*8(block); \ - MOVOU X5, 8*off+80*8(block); \ - MOVOU X6, 8*off+96*8(block); \ - MOVOU X7, 8*off+112*8(block) - -#define BLAMKA_ROUND_0(block, off, t0, t1, c40, c48) \ - LOAD_MSG_0(block, off); \ - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48); \ - SHUFFLE(X2, X3, X4, X5, X6, X7, t0, t1); \ - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48); \ - SHUFFLE_INV(X2, X3, X4, X5, X6, X7, t0, t1); \ - STORE_MSG_0(block, off) - -#define BLAMKA_ROUND_1(block, off, t0, t1, c40, c48) \ - LOAD_MSG_1(block, off); \ - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48); \ - SHUFFLE(X2, X3, X4, X5, X6, X7, t0, t1); \ - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, t0, c40, c48); \ - SHUFFLE_INV(X2, X3, X4, X5, X6, X7, t0, t1); \ - STORE_MSG_1(block, off) - // func blamkaSSE4(b *block) -TEXT ·blamkaSSE4(SB), 4, $0-8 - MOVQ b+0(FP), AX - - MOVOU ·c40<>(SB), X10 - MOVOU ·c48<>(SB), X11 +// Requires: SSE2, SSSE3 +TEXT ·blamkaSSE4(SB), NOSPLIT, $0-8 + MOVQ b+0(FP), AX + MOVOU ·c40<>+0(SB), X10 + MOVOU ·c48<>+0(SB), X11 + MOVOU (AX), X0 + MOVOU 16(AX), X1 + MOVOU 32(AX), X2 + MOVOU 48(AX), X3 + MOVOU 64(AX), X4 + MOVOU 80(AX), X5 + MOVOU 96(AX), X6 + MOVOU 112(AX), X7 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU X0, (AX) + MOVOU X1, 16(AX) + MOVOU X2, 32(AX) + MOVOU X3, 48(AX) + MOVOU X4, 64(AX) + MOVOU X5, 80(AX) + MOVOU X6, 96(AX) + MOVOU X7, 112(AX) + MOVOU 128(AX), X0 + MOVOU 144(AX), X1 + MOVOU 160(AX), X2 + MOVOU 176(AX), X3 + MOVOU 192(AX), X4 + MOVOU 208(AX), X5 + MOVOU 224(AX), X6 + MOVOU 240(AX), X7 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU X0, 128(AX) + MOVOU X1, 144(AX) + MOVOU X2, 160(AX) + MOVOU X3, 176(AX) + MOVOU X4, 192(AX) + MOVOU X5, 208(AX) + MOVOU X6, 224(AX) + MOVOU X7, 240(AX) + MOVOU 256(AX), X0 + MOVOU 272(AX), X1 + MOVOU 288(AX), X2 + MOVOU 304(AX), X3 + MOVOU 320(AX), X4 + MOVOU 336(AX), X5 + MOVOU 352(AX), X6 + MOVOU 368(AX), X7 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU X0, 256(AX) + MOVOU X1, 272(AX) + MOVOU X2, 288(AX) + MOVOU X3, 304(AX) + MOVOU X4, 320(AX) + MOVOU X5, 336(AX) + MOVOU X6, 352(AX) + MOVOU X7, 368(AX) + MOVOU 384(AX), X0 + MOVOU 400(AX), X1 + MOVOU 416(AX), X2 + MOVOU 432(AX), X3 + MOVOU 448(AX), X4 + MOVOU 464(AX), X5 + MOVOU 480(AX), X6 + MOVOU 496(AX), X7 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU X0, 384(AX) + MOVOU X1, 400(AX) + MOVOU X2, 416(AX) + MOVOU X3, 432(AX) + MOVOU X4, 448(AX) + MOVOU X5, 464(AX) + MOVOU X6, 480(AX) + MOVOU X7, 496(AX) + MOVOU 512(AX), X0 + MOVOU 528(AX), X1 + MOVOU 544(AX), X2 + MOVOU 560(AX), X3 + MOVOU 576(AX), X4 + MOVOU 592(AX), X5 + MOVOU 608(AX), X6 + MOVOU 624(AX), X7 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU X0, 512(AX) + MOVOU X1, 528(AX) + MOVOU X2, 544(AX) + MOVOU X3, 560(AX) + MOVOU X4, 576(AX) + MOVOU X5, 592(AX) + MOVOU X6, 608(AX) + MOVOU X7, 624(AX) + MOVOU 640(AX), X0 + MOVOU 656(AX), X1 + MOVOU 672(AX), X2 + MOVOU 688(AX), X3 + MOVOU 704(AX), X4 + MOVOU 720(AX), X5 + MOVOU 736(AX), X6 + MOVOU 752(AX), X7 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU X0, 640(AX) + MOVOU X1, 656(AX) + MOVOU X2, 672(AX) + MOVOU X3, 688(AX) + MOVOU X4, 704(AX) + MOVOU X5, 720(AX) + MOVOU X6, 736(AX) + MOVOU X7, 752(AX) + MOVOU 768(AX), X0 + MOVOU 784(AX), X1 + MOVOU 800(AX), X2 + MOVOU 816(AX), X3 + MOVOU 832(AX), X4 + MOVOU 848(AX), X5 + MOVOU 864(AX), X6 + MOVOU 880(AX), X7 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU X0, 768(AX) + MOVOU X1, 784(AX) + MOVOU X2, 800(AX) + MOVOU X3, 816(AX) + MOVOU X4, 832(AX) + MOVOU X5, 848(AX) + MOVOU X6, 864(AX) + MOVOU X7, 880(AX) + MOVOU 896(AX), X0 + MOVOU 912(AX), X1 + MOVOU 928(AX), X2 + MOVOU 944(AX), X3 + MOVOU 960(AX), X4 + MOVOU 976(AX), X5 + MOVOU 992(AX), X6 + MOVOU 1008(AX), X7 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU X0, 896(AX) + MOVOU X1, 912(AX) + MOVOU X2, 928(AX) + MOVOU X3, 944(AX) + MOVOU X4, 960(AX) + MOVOU X5, 976(AX) + MOVOU X6, 992(AX) + MOVOU X7, 1008(AX) + MOVOU (AX), X0 + MOVOU 128(AX), X1 + MOVOU 256(AX), X2 + MOVOU 384(AX), X3 + MOVOU 512(AX), X4 + MOVOU 640(AX), X5 + MOVOU 768(AX), X6 + MOVOU 896(AX), X7 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU X0, (AX) + MOVOU X1, 128(AX) + MOVOU X2, 256(AX) + MOVOU X3, 384(AX) + MOVOU X4, 512(AX) + MOVOU X5, 640(AX) + MOVOU X6, 768(AX) + MOVOU X7, 896(AX) + MOVOU 16(AX), X0 + MOVOU 144(AX), X1 + MOVOU 272(AX), X2 + MOVOU 400(AX), X3 + MOVOU 528(AX), X4 + MOVOU 656(AX), X5 + MOVOU 784(AX), X6 + MOVOU 912(AX), X7 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU X0, 16(AX) + MOVOU X1, 144(AX) + MOVOU X2, 272(AX) + MOVOU X3, 400(AX) + MOVOU X4, 528(AX) + MOVOU X5, 656(AX) + MOVOU X6, 784(AX) + MOVOU X7, 912(AX) + MOVOU 32(AX), X0 + MOVOU 160(AX), X1 + MOVOU 288(AX), X2 + MOVOU 416(AX), X3 + MOVOU 544(AX), X4 + MOVOU 672(AX), X5 + MOVOU 800(AX), X6 + MOVOU 928(AX), X7 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU X0, 32(AX) + MOVOU X1, 160(AX) + MOVOU X2, 288(AX) + MOVOU X3, 416(AX) + MOVOU X4, 544(AX) + MOVOU X5, 672(AX) + MOVOU X6, 800(AX) + MOVOU X7, 928(AX) + MOVOU 48(AX), X0 + MOVOU 176(AX), X1 + MOVOU 304(AX), X2 + MOVOU 432(AX), X3 + MOVOU 560(AX), X4 + MOVOU 688(AX), X5 + MOVOU 816(AX), X6 + MOVOU 944(AX), X7 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU X0, 48(AX) + MOVOU X1, 176(AX) + MOVOU X2, 304(AX) + MOVOU X3, 432(AX) + MOVOU X4, 560(AX) + MOVOU X5, 688(AX) + MOVOU X6, 816(AX) + MOVOU X7, 944(AX) + MOVOU 64(AX), X0 + MOVOU 192(AX), X1 + MOVOU 320(AX), X2 + MOVOU 448(AX), X3 + MOVOU 576(AX), X4 + MOVOU 704(AX), X5 + MOVOU 832(AX), X6 + MOVOU 960(AX), X7 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU X0, 64(AX) + MOVOU X1, 192(AX) + MOVOU X2, 320(AX) + MOVOU X3, 448(AX) + MOVOU X4, 576(AX) + MOVOU X5, 704(AX) + MOVOU X6, 832(AX) + MOVOU X7, 960(AX) + MOVOU 80(AX), X0 + MOVOU 208(AX), X1 + MOVOU 336(AX), X2 + MOVOU 464(AX), X3 + MOVOU 592(AX), X4 + MOVOU 720(AX), X5 + MOVOU 848(AX), X6 + MOVOU 976(AX), X7 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU X0, 80(AX) + MOVOU X1, 208(AX) + MOVOU X2, 336(AX) + MOVOU X3, 464(AX) + MOVOU X4, 592(AX) + MOVOU X5, 720(AX) + MOVOU X6, 848(AX) + MOVOU X7, 976(AX) + MOVOU 96(AX), X0 + MOVOU 224(AX), X1 + MOVOU 352(AX), X2 + MOVOU 480(AX), X3 + MOVOU 608(AX), X4 + MOVOU 736(AX), X5 + MOVOU 864(AX), X6 + MOVOU 992(AX), X7 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU X0, 96(AX) + MOVOU X1, 224(AX) + MOVOU X2, 352(AX) + MOVOU X3, 480(AX) + MOVOU X4, 608(AX) + MOVOU X5, 736(AX) + MOVOU X6, 864(AX) + MOVOU X7, 992(AX) + MOVOU 112(AX), X0 + MOVOU 240(AX), X1 + MOVOU 368(AX), X2 + MOVOU 496(AX), X3 + MOVOU 624(AX), X4 + MOVOU 752(AX), X5 + MOVOU 880(AX), X6 + MOVOU 1008(AX), X7 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFD $0xb1, X6, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + PSHUFB X10, X2 + MOVO X0, X8 + PMULULQ X2, X8 + PADDQ X2, X0 + PADDQ X8, X0 + PADDQ X8, X0 + PXOR X0, X6 + PSHUFB X11, X6 + MOVO X4, X8 + PMULULQ X6, X8 + PADDQ X6, X4 + PADDQ X8, X4 + PADDQ X8, X4 + PXOR X4, X2 + MOVO X2, X8 + PADDQ X2, X8 + PSRLQ $0x3f, X2 + PXOR X8, X2 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFD $0xb1, X7, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + PSHUFB X10, X3 + MOVO X1, X8 + PMULULQ X3, X8 + PADDQ X3, X1 + PADDQ X8, X1 + PADDQ X8, X1 + PXOR X1, X7 + PSHUFB X11, X7 + MOVO X5, X8 + PMULULQ X7, X8 + PADDQ X7, X5 + PADDQ X8, X5 + PADDQ X8, X5 + PXOR X5, X3 + MOVO X3, X8 + PADDQ X3, X8 + PSRLQ $0x3f, X3 + PXOR X8, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU X0, 112(AX) + MOVOU X1, 240(AX) + MOVOU X2, 368(AX) + MOVOU X3, 496(AX) + MOVOU X4, 624(AX) + MOVOU X5, 752(AX) + MOVOU X6, 880(AX) + MOVOU X7, 1008(AX) + RET - BLAMKA_ROUND_0(AX, 0, X8, X9, X10, X11) - BLAMKA_ROUND_0(AX, 16, X8, X9, X10, X11) - BLAMKA_ROUND_0(AX, 32, X8, X9, X10, X11) - BLAMKA_ROUND_0(AX, 48, X8, X9, X10, X11) - BLAMKA_ROUND_0(AX, 64, X8, X9, X10, X11) - BLAMKA_ROUND_0(AX, 80, X8, X9, X10, X11) - BLAMKA_ROUND_0(AX, 96, X8, X9, X10, X11) - BLAMKA_ROUND_0(AX, 112, X8, X9, X10, X11) +DATA ·c40<>+0(SB)/8, $0x0201000706050403 +DATA ·c40<>+8(SB)/8, $0x0a09080f0e0d0c0b +GLOBL ·c40<>(SB), RODATA|NOPTR, $16 - BLAMKA_ROUND_1(AX, 0, X8, X9, X10, X11) - BLAMKA_ROUND_1(AX, 2, X8, X9, X10, X11) - BLAMKA_ROUND_1(AX, 4, X8, X9, X10, X11) - BLAMKA_ROUND_1(AX, 6, X8, X9, X10, X11) - BLAMKA_ROUND_1(AX, 8, X8, X9, X10, X11) - BLAMKA_ROUND_1(AX, 10, X8, X9, X10, X11) - BLAMKA_ROUND_1(AX, 12, X8, X9, X10, X11) - BLAMKA_ROUND_1(AX, 14, X8, X9, X10, X11) - RET +DATA ·c48<>+0(SB)/8, $0x0100070605040302 +DATA ·c48<>+8(SB)/8, $0x09080f0e0d0c0b0a +GLOBL ·c48<>(SB), RODATA|NOPTR, $16 -// func mixBlocksSSE2(out, a, b, c *block) -TEXT ·mixBlocksSSE2(SB), 4, $0-32 +// func mixBlocksSSE2(out *block, a *block, b *block, c *block) +// Requires: SSE2 +TEXT ·mixBlocksSSE2(SB), NOSPLIT, $0-32 MOVQ out+0(FP), DX MOVQ a+8(FP), AX MOVQ b+16(FP), BX MOVQ c+24(FP), CX - MOVQ $128, DI + MOVQ $0x00000080, DI loop: - MOVOU 0(AX), X0 - MOVOU 0(BX), X1 - MOVOU 0(CX), X2 + MOVOU (AX), X0 + MOVOU (BX), X1 + MOVOU (CX), X2 PXOR X1, X0 PXOR X2, X0 - MOVOU X0, 0(DX) - ADDQ $16, AX - ADDQ $16, BX - ADDQ $16, CX - ADDQ $16, DX - SUBQ $2, DI + MOVOU X0, (DX) + ADDQ $0x10, AX + ADDQ $0x10, BX + ADDQ $0x10, CX + ADDQ $0x10, DX + SUBQ $0x02, DI JA loop RET -// func xorBlocksSSE2(out, a, b, c *block) -TEXT ·xorBlocksSSE2(SB), 4, $0-32 +// func xorBlocksSSE2(out *block, a *block, b *block, c *block) +// Requires: SSE2 +TEXT ·xorBlocksSSE2(SB), NOSPLIT, $0-32 MOVQ out+0(FP), DX MOVQ a+8(FP), AX MOVQ b+16(FP), BX MOVQ c+24(FP), CX - MOVQ $128, DI + MOVQ $0x00000080, DI loop: - MOVOU 0(AX), X0 - MOVOU 0(BX), X1 - MOVOU 0(CX), X2 - MOVOU 0(DX), X3 + MOVOU (AX), X0 + MOVOU (BX), X1 + MOVOU (CX), X2 + MOVOU (DX), X3 PXOR X1, X0 PXOR X2, X0 PXOR X3, X0 - MOVOU X0, 0(DX) - ADDQ $16, AX - ADDQ $16, BX - ADDQ $16, CX - ADDQ $16, DX - SUBQ $2, DI + MOVOU X0, (DX) + ADDQ $0x10, AX + ADDQ $0x10, BX + ADDQ $0x10, CX + ADDQ $0x10, DX + SUBQ $0x02, DI JA loop RET diff --git a/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s b/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s index 9ae8206c..f75162e0 100644 --- a/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s +++ b/vendor/golang.org/x/crypto/blake2b/blake2bAVX2_amd64.s @@ -1,722 +1,4517 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Code generated by command: go run blake2bAVX2_amd64_asm.go -out ../../blake2bAVX2_amd64.s -pkg blake2b. DO NOT EDIT. //go:build amd64 && gc && !purego #include "textflag.h" -DATA ·AVX2_iv0<>+0x00(SB)/8, $0x6a09e667f3bcc908 -DATA ·AVX2_iv0<>+0x08(SB)/8, $0xbb67ae8584caa73b -DATA ·AVX2_iv0<>+0x10(SB)/8, $0x3c6ef372fe94f82b -DATA ·AVX2_iv0<>+0x18(SB)/8, $0xa54ff53a5f1d36f1 -GLOBL ·AVX2_iv0<>(SB), (NOPTR+RODATA), $32 - -DATA ·AVX2_iv1<>+0x00(SB)/8, $0x510e527fade682d1 -DATA ·AVX2_iv1<>+0x08(SB)/8, $0x9b05688c2b3e6c1f -DATA ·AVX2_iv1<>+0x10(SB)/8, $0x1f83d9abfb41bd6b -DATA ·AVX2_iv1<>+0x18(SB)/8, $0x5be0cd19137e2179 -GLOBL ·AVX2_iv1<>(SB), (NOPTR+RODATA), $32 - -DATA ·AVX2_c40<>+0x00(SB)/8, $0x0201000706050403 -DATA ·AVX2_c40<>+0x08(SB)/8, $0x0a09080f0e0d0c0b -DATA ·AVX2_c40<>+0x10(SB)/8, $0x0201000706050403 -DATA ·AVX2_c40<>+0x18(SB)/8, $0x0a09080f0e0d0c0b -GLOBL ·AVX2_c40<>(SB), (NOPTR+RODATA), $32 - -DATA ·AVX2_c48<>+0x00(SB)/8, $0x0100070605040302 -DATA ·AVX2_c48<>+0x08(SB)/8, $0x09080f0e0d0c0b0a -DATA ·AVX2_c48<>+0x10(SB)/8, $0x0100070605040302 -DATA ·AVX2_c48<>+0x18(SB)/8, $0x09080f0e0d0c0b0a -GLOBL ·AVX2_c48<>(SB), (NOPTR+RODATA), $32 - -DATA ·AVX_iv0<>+0x00(SB)/8, $0x6a09e667f3bcc908 -DATA ·AVX_iv0<>+0x08(SB)/8, $0xbb67ae8584caa73b -GLOBL ·AVX_iv0<>(SB), (NOPTR+RODATA), $16 - -DATA ·AVX_iv1<>+0x00(SB)/8, $0x3c6ef372fe94f82b -DATA ·AVX_iv1<>+0x08(SB)/8, $0xa54ff53a5f1d36f1 -GLOBL ·AVX_iv1<>(SB), (NOPTR+RODATA), $16 - -DATA ·AVX_iv2<>+0x00(SB)/8, $0x510e527fade682d1 -DATA ·AVX_iv2<>+0x08(SB)/8, $0x9b05688c2b3e6c1f -GLOBL ·AVX_iv2<>(SB), (NOPTR+RODATA), $16 - -DATA ·AVX_iv3<>+0x00(SB)/8, $0x1f83d9abfb41bd6b -DATA ·AVX_iv3<>+0x08(SB)/8, $0x5be0cd19137e2179 -GLOBL ·AVX_iv3<>(SB), (NOPTR+RODATA), $16 - -DATA ·AVX_c40<>+0x00(SB)/8, $0x0201000706050403 -DATA ·AVX_c40<>+0x08(SB)/8, $0x0a09080f0e0d0c0b -GLOBL ·AVX_c40<>(SB), (NOPTR+RODATA), $16 - -DATA ·AVX_c48<>+0x00(SB)/8, $0x0100070605040302 -DATA ·AVX_c48<>+0x08(SB)/8, $0x09080f0e0d0c0b0a -GLOBL ·AVX_c48<>(SB), (NOPTR+RODATA), $16 - -#define VPERMQ_0x39_Y1_Y1 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xc9; BYTE $0x39 -#define VPERMQ_0x93_Y1_Y1 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xc9; BYTE $0x93 -#define VPERMQ_0x4E_Y2_Y2 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xd2; BYTE $0x4e -#define VPERMQ_0x93_Y3_Y3 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xdb; BYTE $0x93 -#define VPERMQ_0x39_Y3_Y3 BYTE $0xc4; BYTE $0xe3; BYTE $0xfd; BYTE $0x00; BYTE $0xdb; BYTE $0x39 - -#define ROUND_AVX2(m0, m1, m2, m3, t, c40, c48) \ - VPADDQ m0, Y0, Y0; \ - VPADDQ Y1, Y0, Y0; \ - VPXOR Y0, Y3, Y3; \ - VPSHUFD $-79, Y3, Y3; \ - VPADDQ Y3, Y2, Y2; \ - VPXOR Y2, Y1, Y1; \ - VPSHUFB c40, Y1, Y1; \ - VPADDQ m1, Y0, Y0; \ - VPADDQ Y1, Y0, Y0; \ - VPXOR Y0, Y3, Y3; \ - VPSHUFB c48, Y3, Y3; \ - VPADDQ Y3, Y2, Y2; \ - VPXOR Y2, Y1, Y1; \ - VPADDQ Y1, Y1, t; \ - VPSRLQ $63, Y1, Y1; \ - VPXOR t, Y1, Y1; \ - VPERMQ_0x39_Y1_Y1; \ - VPERMQ_0x4E_Y2_Y2; \ - VPERMQ_0x93_Y3_Y3; \ - VPADDQ m2, Y0, Y0; \ - VPADDQ Y1, Y0, Y0; \ - VPXOR Y0, Y3, Y3; \ - VPSHUFD $-79, Y3, Y3; \ - VPADDQ Y3, Y2, Y2; \ - VPXOR Y2, Y1, Y1; \ - VPSHUFB c40, Y1, Y1; \ - VPADDQ m3, Y0, Y0; \ - VPADDQ Y1, Y0, Y0; \ - VPXOR Y0, Y3, Y3; \ - VPSHUFB c48, Y3, Y3; \ - VPADDQ Y3, Y2, Y2; \ - VPXOR Y2, Y1, Y1; \ - VPADDQ Y1, Y1, t; \ - VPSRLQ $63, Y1, Y1; \ - VPXOR t, Y1, Y1; \ - VPERMQ_0x39_Y3_Y3; \ - VPERMQ_0x4E_Y2_Y2; \ - VPERMQ_0x93_Y1_Y1 - -#define VMOVQ_SI_X11_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x1E -#define VMOVQ_SI_X12_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x26 -#define VMOVQ_SI_X13_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x2E -#define VMOVQ_SI_X14_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x36 -#define VMOVQ_SI_X15_0 BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x3E - -#define VMOVQ_SI_X11(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x5E; BYTE $n -#define VMOVQ_SI_X12(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x66; BYTE $n -#define VMOVQ_SI_X13(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x6E; BYTE $n -#define VMOVQ_SI_X14(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x76; BYTE $n -#define VMOVQ_SI_X15(n) BYTE $0xC5; BYTE $0x7A; BYTE $0x7E; BYTE $0x7E; BYTE $n - -#define VPINSRQ_1_SI_X11_0 BYTE $0xC4; BYTE $0x63; BYTE $0xA1; BYTE $0x22; BYTE $0x1E; BYTE $0x01 -#define VPINSRQ_1_SI_X12_0 BYTE $0xC4; BYTE $0x63; BYTE $0x99; BYTE $0x22; BYTE $0x26; BYTE $0x01 -#define VPINSRQ_1_SI_X13_0 BYTE $0xC4; BYTE $0x63; BYTE $0x91; BYTE $0x22; BYTE $0x2E; BYTE $0x01 -#define VPINSRQ_1_SI_X14_0 BYTE $0xC4; BYTE $0x63; BYTE $0x89; BYTE $0x22; BYTE $0x36; BYTE $0x01 -#define VPINSRQ_1_SI_X15_0 BYTE $0xC4; BYTE $0x63; BYTE $0x81; BYTE $0x22; BYTE $0x3E; BYTE $0x01 - -#define VPINSRQ_1_SI_X11(n) BYTE $0xC4; BYTE $0x63; BYTE $0xA1; BYTE $0x22; BYTE $0x5E; BYTE $n; BYTE $0x01 -#define VPINSRQ_1_SI_X12(n) BYTE $0xC4; BYTE $0x63; BYTE $0x99; BYTE $0x22; BYTE $0x66; BYTE $n; BYTE $0x01 -#define VPINSRQ_1_SI_X13(n) BYTE $0xC4; BYTE $0x63; BYTE $0x91; BYTE $0x22; BYTE $0x6E; BYTE $n; BYTE $0x01 -#define VPINSRQ_1_SI_X14(n) BYTE $0xC4; BYTE $0x63; BYTE $0x89; BYTE $0x22; BYTE $0x76; BYTE $n; BYTE $0x01 -#define VPINSRQ_1_SI_X15(n) BYTE $0xC4; BYTE $0x63; BYTE $0x81; BYTE $0x22; BYTE $0x7E; BYTE $n; BYTE $0x01 - -#define VMOVQ_R8_X15 BYTE $0xC4; BYTE $0x41; BYTE $0xF9; BYTE $0x6E; BYTE $0xF8 -#define VPINSRQ_1_R9_X15 BYTE $0xC4; BYTE $0x43; BYTE $0x81; BYTE $0x22; BYTE $0xF9; BYTE $0x01 - -// load msg: Y12 = (i0, i1, i2, i3) -// i0, i1, i2, i3 must not be 0 -#define LOAD_MSG_AVX2_Y12(i0, i1, i2, i3) \ - VMOVQ_SI_X12(i0*8); \ - VMOVQ_SI_X11(i2*8); \ - VPINSRQ_1_SI_X12(i1*8); \ - VPINSRQ_1_SI_X11(i3*8); \ - VINSERTI128 $1, X11, Y12, Y12 - -// load msg: Y13 = (i0, i1, i2, i3) -// i0, i1, i2, i3 must not be 0 -#define LOAD_MSG_AVX2_Y13(i0, i1, i2, i3) \ - VMOVQ_SI_X13(i0*8); \ - VMOVQ_SI_X11(i2*8); \ - VPINSRQ_1_SI_X13(i1*8); \ - VPINSRQ_1_SI_X11(i3*8); \ - VINSERTI128 $1, X11, Y13, Y13 - -// load msg: Y14 = (i0, i1, i2, i3) -// i0, i1, i2, i3 must not be 0 -#define LOAD_MSG_AVX2_Y14(i0, i1, i2, i3) \ - VMOVQ_SI_X14(i0*8); \ - VMOVQ_SI_X11(i2*8); \ - VPINSRQ_1_SI_X14(i1*8); \ - VPINSRQ_1_SI_X11(i3*8); \ - VINSERTI128 $1, X11, Y14, Y14 - -// load msg: Y15 = (i0, i1, i2, i3) -// i0, i1, i2, i3 must not be 0 -#define LOAD_MSG_AVX2_Y15(i0, i1, i2, i3) \ - VMOVQ_SI_X15(i0*8); \ - VMOVQ_SI_X11(i2*8); \ - VPINSRQ_1_SI_X15(i1*8); \ - VPINSRQ_1_SI_X11(i3*8); \ - VINSERTI128 $1, X11, Y15, Y15 - -#define LOAD_MSG_AVX2_0_2_4_6_1_3_5_7_8_10_12_14_9_11_13_15() \ - VMOVQ_SI_X12_0; \ - VMOVQ_SI_X11(4*8); \ - VPINSRQ_1_SI_X12(2*8); \ - VPINSRQ_1_SI_X11(6*8); \ - VINSERTI128 $1, X11, Y12, Y12; \ - LOAD_MSG_AVX2_Y13(1, 3, 5, 7); \ - LOAD_MSG_AVX2_Y14(8, 10, 12, 14); \ - LOAD_MSG_AVX2_Y15(9, 11, 13, 15) - -#define LOAD_MSG_AVX2_14_4_9_13_10_8_15_6_1_0_11_5_12_2_7_3() \ - LOAD_MSG_AVX2_Y12(14, 4, 9, 13); \ - LOAD_MSG_AVX2_Y13(10, 8, 15, 6); \ - VMOVQ_SI_X11(11*8); \ - VPSHUFD $0x4E, 0*8(SI), X14; \ - VPINSRQ_1_SI_X11(5*8); \ - VINSERTI128 $1, X11, Y14, Y14; \ - LOAD_MSG_AVX2_Y15(12, 2, 7, 3) - -#define LOAD_MSG_AVX2_11_12_5_15_8_0_2_13_10_3_7_9_14_6_1_4() \ - VMOVQ_SI_X11(5*8); \ - VMOVDQU 11*8(SI), X12; \ - VPINSRQ_1_SI_X11(15*8); \ - VINSERTI128 $1, X11, Y12, Y12; \ - VMOVQ_SI_X13(8*8); \ - VMOVQ_SI_X11(2*8); \ - VPINSRQ_1_SI_X13_0; \ - VPINSRQ_1_SI_X11(13*8); \ - VINSERTI128 $1, X11, Y13, Y13; \ - LOAD_MSG_AVX2_Y14(10, 3, 7, 9); \ - LOAD_MSG_AVX2_Y15(14, 6, 1, 4) - -#define LOAD_MSG_AVX2_7_3_13_11_9_1_12_14_2_5_4_15_6_10_0_8() \ - LOAD_MSG_AVX2_Y12(7, 3, 13, 11); \ - LOAD_MSG_AVX2_Y13(9, 1, 12, 14); \ - LOAD_MSG_AVX2_Y14(2, 5, 4, 15); \ - VMOVQ_SI_X15(6*8); \ - VMOVQ_SI_X11_0; \ - VPINSRQ_1_SI_X15(10*8); \ - VPINSRQ_1_SI_X11(8*8); \ - VINSERTI128 $1, X11, Y15, Y15 - -#define LOAD_MSG_AVX2_9_5_2_10_0_7_4_15_14_11_6_3_1_12_8_13() \ - LOAD_MSG_AVX2_Y12(9, 5, 2, 10); \ - VMOVQ_SI_X13_0; \ - VMOVQ_SI_X11(4*8); \ - VPINSRQ_1_SI_X13(7*8); \ - VPINSRQ_1_SI_X11(15*8); \ - VINSERTI128 $1, X11, Y13, Y13; \ - LOAD_MSG_AVX2_Y14(14, 11, 6, 3); \ - LOAD_MSG_AVX2_Y15(1, 12, 8, 13) - -#define LOAD_MSG_AVX2_2_6_0_8_12_10_11_3_4_7_15_1_13_5_14_9() \ - VMOVQ_SI_X12(2*8); \ - VMOVQ_SI_X11_0; \ - VPINSRQ_1_SI_X12(6*8); \ - VPINSRQ_1_SI_X11(8*8); \ - VINSERTI128 $1, X11, Y12, Y12; \ - LOAD_MSG_AVX2_Y13(12, 10, 11, 3); \ - LOAD_MSG_AVX2_Y14(4, 7, 15, 1); \ - LOAD_MSG_AVX2_Y15(13, 5, 14, 9) - -#define LOAD_MSG_AVX2_12_1_14_4_5_15_13_10_0_6_9_8_7_3_2_11() \ - LOAD_MSG_AVX2_Y12(12, 1, 14, 4); \ - LOAD_MSG_AVX2_Y13(5, 15, 13, 10); \ - VMOVQ_SI_X14_0; \ - VPSHUFD $0x4E, 8*8(SI), X11; \ - VPINSRQ_1_SI_X14(6*8); \ - VINSERTI128 $1, X11, Y14, Y14; \ - LOAD_MSG_AVX2_Y15(7, 3, 2, 11) - -#define LOAD_MSG_AVX2_13_7_12_3_11_14_1_9_5_15_8_2_0_4_6_10() \ - LOAD_MSG_AVX2_Y12(13, 7, 12, 3); \ - LOAD_MSG_AVX2_Y13(11, 14, 1, 9); \ - LOAD_MSG_AVX2_Y14(5, 15, 8, 2); \ - VMOVQ_SI_X15_0; \ - VMOVQ_SI_X11(6*8); \ - VPINSRQ_1_SI_X15(4*8); \ - VPINSRQ_1_SI_X11(10*8); \ - VINSERTI128 $1, X11, Y15, Y15 - -#define LOAD_MSG_AVX2_6_14_11_0_15_9_3_8_12_13_1_10_2_7_4_5() \ - VMOVQ_SI_X12(6*8); \ - VMOVQ_SI_X11(11*8); \ - VPINSRQ_1_SI_X12(14*8); \ - VPINSRQ_1_SI_X11_0; \ - VINSERTI128 $1, X11, Y12, Y12; \ - LOAD_MSG_AVX2_Y13(15, 9, 3, 8); \ - VMOVQ_SI_X11(1*8); \ - VMOVDQU 12*8(SI), X14; \ - VPINSRQ_1_SI_X11(10*8); \ - VINSERTI128 $1, X11, Y14, Y14; \ - VMOVQ_SI_X15(2*8); \ - VMOVDQU 4*8(SI), X11; \ - VPINSRQ_1_SI_X15(7*8); \ - VINSERTI128 $1, X11, Y15, Y15 - -#define LOAD_MSG_AVX2_10_8_7_1_2_4_6_5_15_9_3_13_11_14_12_0() \ - LOAD_MSG_AVX2_Y12(10, 8, 7, 1); \ - VMOVQ_SI_X13(2*8); \ - VPSHUFD $0x4E, 5*8(SI), X11; \ - VPINSRQ_1_SI_X13(4*8); \ - VINSERTI128 $1, X11, Y13, Y13; \ - LOAD_MSG_AVX2_Y14(15, 9, 3, 13); \ - VMOVQ_SI_X15(11*8); \ - VMOVQ_SI_X11(12*8); \ - VPINSRQ_1_SI_X15(14*8); \ - VPINSRQ_1_SI_X11_0; \ - VINSERTI128 $1, X11, Y15, Y15 - // func hashBlocksAVX2(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) -TEXT ·hashBlocksAVX2(SB), 4, $320-48 // frame size = 288 + 32 byte alignment - MOVQ h+0(FP), AX - MOVQ c+8(FP), BX - MOVQ flag+16(FP), CX - MOVQ blocks_base+24(FP), SI - MOVQ blocks_len+32(FP), DI - - MOVQ SP, DX - ADDQ $31, DX - ANDQ $~31, DX - - MOVQ CX, 16(DX) - XORQ CX, CX - MOVQ CX, 24(DX) - - VMOVDQU ·AVX2_c40<>(SB), Y4 - VMOVDQU ·AVX2_c48<>(SB), Y5 - - VMOVDQU 0(AX), Y8 +// Requires: AVX, AVX2 +TEXT ·hashBlocksAVX2(SB), NOSPLIT, $320-48 + MOVQ h+0(FP), AX + MOVQ c+8(FP), BX + MOVQ flag+16(FP), CX + MOVQ blocks_base+24(FP), SI + MOVQ blocks_len+32(FP), DI + MOVQ SP, DX + ADDQ $+31, DX + ANDQ $-32, DX + MOVQ CX, 16(DX) + XORQ CX, CX + MOVQ CX, 24(DX) + VMOVDQU ·AVX2_c40<>+0(SB), Y4 + VMOVDQU ·AVX2_c48<>+0(SB), Y5 + VMOVDQU (AX), Y8 VMOVDQU 32(AX), Y9 - VMOVDQU ·AVX2_iv0<>(SB), Y6 - VMOVDQU ·AVX2_iv1<>(SB), Y7 - - MOVQ 0(BX), R8 - MOVQ 8(BX), R9 - MOVQ R9, 8(DX) + VMOVDQU ·AVX2_iv0<>+0(SB), Y6 + VMOVDQU ·AVX2_iv1<>+0(SB), Y7 + MOVQ (BX), R8 + MOVQ 8(BX), R9 + MOVQ R9, 8(DX) loop: - ADDQ $128, R8 - MOVQ R8, 0(DX) - CMPQ R8, $128 + ADDQ $0x80, R8 + MOVQ R8, (DX) + CMPQ R8, $0x80 JGE noinc INCQ R9 MOVQ R9, 8(DX) noinc: - VMOVDQA Y8, Y0 - VMOVDQA Y9, Y1 - VMOVDQA Y6, Y2 - VPXOR 0(DX), Y7, Y3 - - LOAD_MSG_AVX2_0_2_4_6_1_3_5_7_8_10_12_14_9_11_13_15() - VMOVDQA Y12, 32(DX) - VMOVDQA Y13, 64(DX) - VMOVDQA Y14, 96(DX) - VMOVDQA Y15, 128(DX) - ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) - LOAD_MSG_AVX2_14_4_9_13_10_8_15_6_1_0_11_5_12_2_7_3() - VMOVDQA Y12, 160(DX) - VMOVDQA Y13, 192(DX) - VMOVDQA Y14, 224(DX) - VMOVDQA Y15, 256(DX) - - ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) - LOAD_MSG_AVX2_11_12_5_15_8_0_2_13_10_3_7_9_14_6_1_4() - ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) - LOAD_MSG_AVX2_7_3_13_11_9_1_12_14_2_5_4_15_6_10_0_8() - ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) - LOAD_MSG_AVX2_9_5_2_10_0_7_4_15_14_11_6_3_1_12_8_13() - ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) - LOAD_MSG_AVX2_2_6_0_8_12_10_11_3_4_7_15_1_13_5_14_9() - ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) - LOAD_MSG_AVX2_12_1_14_4_5_15_13_10_0_6_9_8_7_3_2_11() - ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) - LOAD_MSG_AVX2_13_7_12_3_11_14_1_9_5_15_8_2_0_4_6_10() - ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) - LOAD_MSG_AVX2_6_14_11_0_15_9_3_8_12_13_1_10_2_7_4_5() - ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) - LOAD_MSG_AVX2_10_8_7_1_2_4_6_5_15_9_3_13_11_14_12_0() - ROUND_AVX2(Y12, Y13, Y14, Y15, Y10, Y4, Y5) - - ROUND_AVX2(32(DX), 64(DX), 96(DX), 128(DX), Y10, Y4, Y5) - ROUND_AVX2(160(DX), 192(DX), 224(DX), 256(DX), Y10, Y4, Y5) - - VPXOR Y0, Y8, Y8 - VPXOR Y1, Y9, Y9 - VPXOR Y2, Y8, Y8 - VPXOR Y3, Y9, Y9 - - LEAQ 128(SI), SI - SUBQ $128, DI - JNE loop - - MOVQ R8, 0(BX) - MOVQ R9, 8(BX) - - VMOVDQU Y8, 0(AX) - VMOVDQU Y9, 32(AX) + VMOVDQA Y8, Y0 + VMOVDQA Y9, Y1 + VMOVDQA Y6, Y2 + VPXOR (DX), Y7, Y3 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x26 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x20 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x10 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x30 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x08 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x28 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x38 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x40 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x60 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x50 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x70 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x48 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x68 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x58 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x78 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VMOVDQA Y12, 32(DX) + VMOVDQA Y13, 64(DX) + VMOVDQA Y14, 96(DX) + VMOVDQA Y15, 128(DX) + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x70 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x48 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x20 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x68 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x50 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x78 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x40 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x30 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x58 + VPSHUFD $0x4e, (SI), X14 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x28 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x60 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x38 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x10 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x18 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VMOVDQA Y12, 160(DX) + VMOVDQA Y13, 192(DX) + VMOVDQA Y14, 224(DX) + VMOVDQA Y15, 256(DX) + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x28 + VMOVDQU 88(SI), X12 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x78 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x40 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x10 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x2e + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x68 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x50 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x38 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x48 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x70 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x08 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x30 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x20 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x38 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x68 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x58 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x48 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x60 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x08 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x70 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x10 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x20 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x28 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x78 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x30 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x1e + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x50 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x40 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x48 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x10 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x28 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x50 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x2e + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x20 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x38 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x78 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x70 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x30 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x58 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x18 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x08 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x40 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x60 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x68 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x10 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x1e + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x30 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x40 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x60 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x58 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x50 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x18 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x20 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x78 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x38 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x08 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x68 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x70 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x28 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x48 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x60 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x70 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x08 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x20 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x28 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x68 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x78 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x50 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x36 + VPSHUFD $0x4e, 64(SI), X11 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x30 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x38 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x10 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x58 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x68 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x60 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x38 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x18 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x58 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x08 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x70 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x48 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x28 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x40 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x78 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x10 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x3e + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x30 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x20 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x50 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x30 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x58 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x70 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x1e + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x78 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x18 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x48 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x40 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x08 + VMOVDQU 96(SI), X14 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x50 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x10 + VMOVDQU 32(SI), X11 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x38 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x50 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x38 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x40 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x08 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y12, Y12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x10 + VPSHUFD $0x4e, 40(SI), X11 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x20 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y13, Y13 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x78 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x18 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x48 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x5e + BYTE $0x68 + BYTE $0x01 + VINSERTI128 $0x01, X11, Y14, Y14 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x58 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x5e + BYTE $0x60 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x70 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0xa1 + BYTE $0x22 + BYTE $0x1e + BYTE $0x01 + VINSERTI128 $0x01, X11, Y15, Y15 + VPADDQ Y12, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y13, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ Y14, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ Y15, Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + VPADDQ 32(DX), Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ 64(DX), Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ 96(DX), Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ 128(DX), Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + VPADDQ 160(DX), Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ 192(DX), Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x93 + VPADDQ 224(DX), Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFD $-79, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPSHUFB Y4, Y1, Y1 + VPADDQ 256(DX), Y0, Y0 + VPADDQ Y1, Y0, Y0 + VPXOR Y0, Y3, Y3 + VPSHUFB Y5, Y3, Y3 + VPADDQ Y3, Y2, Y2 + VPXOR Y2, Y1, Y1 + VPADDQ Y1, Y1, Y10 + VPSRLQ $0x3f, Y1, Y1 + VPXOR Y10, Y1, Y1 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xdb + BYTE $0x39 + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xd2 + BYTE $0x4e + BYTE $0xc4 + BYTE $0xe3 + BYTE $0xfd + BYTE $0x00 + BYTE $0xc9 + BYTE $0x93 + VPXOR Y0, Y8, Y8 + VPXOR Y1, Y9, Y9 + VPXOR Y2, Y8, Y8 + VPXOR Y3, Y9, Y9 + LEAQ 128(SI), SI + SUBQ $0x80, DI + JNE loop + MOVQ R8, (BX) + MOVQ R9, 8(BX) + VMOVDQU Y8, (AX) + VMOVDQU Y9, 32(AX) VZEROUPPER - RET -#define VPUNPCKLQDQ_X2_X2_X15 BYTE $0xC5; BYTE $0x69; BYTE $0x6C; BYTE $0xFA -#define VPUNPCKLQDQ_X3_X3_X15 BYTE $0xC5; BYTE $0x61; BYTE $0x6C; BYTE $0xFB -#define VPUNPCKLQDQ_X7_X7_X15 BYTE $0xC5; BYTE $0x41; BYTE $0x6C; BYTE $0xFF -#define VPUNPCKLQDQ_X13_X13_X15 BYTE $0xC4; BYTE $0x41; BYTE $0x11; BYTE $0x6C; BYTE $0xFD -#define VPUNPCKLQDQ_X14_X14_X15 BYTE $0xC4; BYTE $0x41; BYTE $0x09; BYTE $0x6C; BYTE $0xFE - -#define VPUNPCKHQDQ_X15_X2_X2 BYTE $0xC4; BYTE $0xC1; BYTE $0x69; BYTE $0x6D; BYTE $0xD7 -#define VPUNPCKHQDQ_X15_X3_X3 BYTE $0xC4; BYTE $0xC1; BYTE $0x61; BYTE $0x6D; BYTE $0xDF -#define VPUNPCKHQDQ_X15_X6_X6 BYTE $0xC4; BYTE $0xC1; BYTE $0x49; BYTE $0x6D; BYTE $0xF7 -#define VPUNPCKHQDQ_X15_X7_X7 BYTE $0xC4; BYTE $0xC1; BYTE $0x41; BYTE $0x6D; BYTE $0xFF -#define VPUNPCKHQDQ_X15_X3_X2 BYTE $0xC4; BYTE $0xC1; BYTE $0x61; BYTE $0x6D; BYTE $0xD7 -#define VPUNPCKHQDQ_X15_X7_X6 BYTE $0xC4; BYTE $0xC1; BYTE $0x41; BYTE $0x6D; BYTE $0xF7 -#define VPUNPCKHQDQ_X15_X13_X3 BYTE $0xC4; BYTE $0xC1; BYTE $0x11; BYTE $0x6D; BYTE $0xDF -#define VPUNPCKHQDQ_X15_X13_X7 BYTE $0xC4; BYTE $0xC1; BYTE $0x11; BYTE $0x6D; BYTE $0xFF - -#define SHUFFLE_AVX() \ - VMOVDQA X6, X13; \ - VMOVDQA X2, X14; \ - VMOVDQA X4, X6; \ - VPUNPCKLQDQ_X13_X13_X15; \ - VMOVDQA X5, X4; \ - VMOVDQA X6, X5; \ - VPUNPCKHQDQ_X15_X7_X6; \ - VPUNPCKLQDQ_X7_X7_X15; \ - VPUNPCKHQDQ_X15_X13_X7; \ - VPUNPCKLQDQ_X3_X3_X15; \ - VPUNPCKHQDQ_X15_X2_X2; \ - VPUNPCKLQDQ_X14_X14_X15; \ - VPUNPCKHQDQ_X15_X3_X3; \ - -#define SHUFFLE_AVX_INV() \ - VMOVDQA X2, X13; \ - VMOVDQA X4, X14; \ - VPUNPCKLQDQ_X2_X2_X15; \ - VMOVDQA X5, X4; \ - VPUNPCKHQDQ_X15_X3_X2; \ - VMOVDQA X14, X5; \ - VPUNPCKLQDQ_X3_X3_X15; \ - VMOVDQA X6, X14; \ - VPUNPCKHQDQ_X15_X13_X3; \ - VPUNPCKLQDQ_X7_X7_X15; \ - VPUNPCKHQDQ_X15_X6_X6; \ - VPUNPCKLQDQ_X14_X14_X15; \ - VPUNPCKHQDQ_X15_X7_X7; \ - -#define HALF_ROUND_AVX(v0, v1, v2, v3, v4, v5, v6, v7, m0, m1, m2, m3, t0, c40, c48) \ - VPADDQ m0, v0, v0; \ - VPADDQ v2, v0, v0; \ - VPADDQ m1, v1, v1; \ - VPADDQ v3, v1, v1; \ - VPXOR v0, v6, v6; \ - VPXOR v1, v7, v7; \ - VPSHUFD $-79, v6, v6; \ - VPSHUFD $-79, v7, v7; \ - VPADDQ v6, v4, v4; \ - VPADDQ v7, v5, v5; \ - VPXOR v4, v2, v2; \ - VPXOR v5, v3, v3; \ - VPSHUFB c40, v2, v2; \ - VPSHUFB c40, v3, v3; \ - VPADDQ m2, v0, v0; \ - VPADDQ v2, v0, v0; \ - VPADDQ m3, v1, v1; \ - VPADDQ v3, v1, v1; \ - VPXOR v0, v6, v6; \ - VPXOR v1, v7, v7; \ - VPSHUFB c48, v6, v6; \ - VPSHUFB c48, v7, v7; \ - VPADDQ v6, v4, v4; \ - VPADDQ v7, v5, v5; \ - VPXOR v4, v2, v2; \ - VPXOR v5, v3, v3; \ - VPADDQ v2, v2, t0; \ - VPSRLQ $63, v2, v2; \ - VPXOR t0, v2, v2; \ - VPADDQ v3, v3, t0; \ - VPSRLQ $63, v3, v3; \ - VPXOR t0, v3, v3 - -// load msg: X12 = (i0, i1), X13 = (i2, i3), X14 = (i4, i5), X15 = (i6, i7) -// i0, i1, i2, i3, i4, i5, i6, i7 must not be 0 -#define LOAD_MSG_AVX(i0, i1, i2, i3, i4, i5, i6, i7) \ - VMOVQ_SI_X12(i0*8); \ - VMOVQ_SI_X13(i2*8); \ - VMOVQ_SI_X14(i4*8); \ - VMOVQ_SI_X15(i6*8); \ - VPINSRQ_1_SI_X12(i1*8); \ - VPINSRQ_1_SI_X13(i3*8); \ - VPINSRQ_1_SI_X14(i5*8); \ - VPINSRQ_1_SI_X15(i7*8) - -// load msg: X12 = (0, 2), X13 = (4, 6), X14 = (1, 3), X15 = (5, 7) -#define LOAD_MSG_AVX_0_2_4_6_1_3_5_7() \ - VMOVQ_SI_X12_0; \ - VMOVQ_SI_X13(4*8); \ - VMOVQ_SI_X14(1*8); \ - VMOVQ_SI_X15(5*8); \ - VPINSRQ_1_SI_X12(2*8); \ - VPINSRQ_1_SI_X13(6*8); \ - VPINSRQ_1_SI_X14(3*8); \ - VPINSRQ_1_SI_X15(7*8) - -// load msg: X12 = (1, 0), X13 = (11, 5), X14 = (12, 2), X15 = (7, 3) -#define LOAD_MSG_AVX_1_0_11_5_12_2_7_3() \ - VPSHUFD $0x4E, 0*8(SI), X12; \ - VMOVQ_SI_X13(11*8); \ - VMOVQ_SI_X14(12*8); \ - VMOVQ_SI_X15(7*8); \ - VPINSRQ_1_SI_X13(5*8); \ - VPINSRQ_1_SI_X14(2*8); \ - VPINSRQ_1_SI_X15(3*8) - -// load msg: X12 = (11, 12), X13 = (5, 15), X14 = (8, 0), X15 = (2, 13) -#define LOAD_MSG_AVX_11_12_5_15_8_0_2_13() \ - VMOVDQU 11*8(SI), X12; \ - VMOVQ_SI_X13(5*8); \ - VMOVQ_SI_X14(8*8); \ - VMOVQ_SI_X15(2*8); \ - VPINSRQ_1_SI_X13(15*8); \ - VPINSRQ_1_SI_X14_0; \ - VPINSRQ_1_SI_X15(13*8) - -// load msg: X12 = (2, 5), X13 = (4, 15), X14 = (6, 10), X15 = (0, 8) -#define LOAD_MSG_AVX_2_5_4_15_6_10_0_8() \ - VMOVQ_SI_X12(2*8); \ - VMOVQ_SI_X13(4*8); \ - VMOVQ_SI_X14(6*8); \ - VMOVQ_SI_X15_0; \ - VPINSRQ_1_SI_X12(5*8); \ - VPINSRQ_1_SI_X13(15*8); \ - VPINSRQ_1_SI_X14(10*8); \ - VPINSRQ_1_SI_X15(8*8) +DATA ·AVX2_c40<>+0(SB)/8, $0x0201000706050403 +DATA ·AVX2_c40<>+8(SB)/8, $0x0a09080f0e0d0c0b +DATA ·AVX2_c40<>+16(SB)/8, $0x0201000706050403 +DATA ·AVX2_c40<>+24(SB)/8, $0x0a09080f0e0d0c0b +GLOBL ·AVX2_c40<>(SB), RODATA|NOPTR, $32 -// load msg: X12 = (9, 5), X13 = (2, 10), X14 = (0, 7), X15 = (4, 15) -#define LOAD_MSG_AVX_9_5_2_10_0_7_4_15() \ - VMOVQ_SI_X12(9*8); \ - VMOVQ_SI_X13(2*8); \ - VMOVQ_SI_X14_0; \ - VMOVQ_SI_X15(4*8); \ - VPINSRQ_1_SI_X12(5*8); \ - VPINSRQ_1_SI_X13(10*8); \ - VPINSRQ_1_SI_X14(7*8); \ - VPINSRQ_1_SI_X15(15*8) +DATA ·AVX2_c48<>+0(SB)/8, $0x0100070605040302 +DATA ·AVX2_c48<>+8(SB)/8, $0x09080f0e0d0c0b0a +DATA ·AVX2_c48<>+16(SB)/8, $0x0100070605040302 +DATA ·AVX2_c48<>+24(SB)/8, $0x09080f0e0d0c0b0a +GLOBL ·AVX2_c48<>(SB), RODATA|NOPTR, $32 -// load msg: X12 = (2, 6), X13 = (0, 8), X14 = (12, 10), X15 = (11, 3) -#define LOAD_MSG_AVX_2_6_0_8_12_10_11_3() \ - VMOVQ_SI_X12(2*8); \ - VMOVQ_SI_X13_0; \ - VMOVQ_SI_X14(12*8); \ - VMOVQ_SI_X15(11*8); \ - VPINSRQ_1_SI_X12(6*8); \ - VPINSRQ_1_SI_X13(8*8); \ - VPINSRQ_1_SI_X14(10*8); \ - VPINSRQ_1_SI_X15(3*8) +DATA ·AVX2_iv0<>+0(SB)/8, $0x6a09e667f3bcc908 +DATA ·AVX2_iv0<>+8(SB)/8, $0xbb67ae8584caa73b +DATA ·AVX2_iv0<>+16(SB)/8, $0x3c6ef372fe94f82b +DATA ·AVX2_iv0<>+24(SB)/8, $0xa54ff53a5f1d36f1 +GLOBL ·AVX2_iv0<>(SB), RODATA|NOPTR, $32 -// load msg: X12 = (0, 6), X13 = (9, 8), X14 = (7, 3), X15 = (2, 11) -#define LOAD_MSG_AVX_0_6_9_8_7_3_2_11() \ - MOVQ 0*8(SI), X12; \ - VPSHUFD $0x4E, 8*8(SI), X13; \ - MOVQ 7*8(SI), X14; \ - MOVQ 2*8(SI), X15; \ - VPINSRQ_1_SI_X12(6*8); \ - VPINSRQ_1_SI_X14(3*8); \ - VPINSRQ_1_SI_X15(11*8) - -// load msg: X12 = (6, 14), X13 = (11, 0), X14 = (15, 9), X15 = (3, 8) -#define LOAD_MSG_AVX_6_14_11_0_15_9_3_8() \ - MOVQ 6*8(SI), X12; \ - MOVQ 11*8(SI), X13; \ - MOVQ 15*8(SI), X14; \ - MOVQ 3*8(SI), X15; \ - VPINSRQ_1_SI_X12(14*8); \ - VPINSRQ_1_SI_X13_0; \ - VPINSRQ_1_SI_X14(9*8); \ - VPINSRQ_1_SI_X15(8*8) - -// load msg: X12 = (5, 15), X13 = (8, 2), X14 = (0, 4), X15 = (6, 10) -#define LOAD_MSG_AVX_5_15_8_2_0_4_6_10() \ - MOVQ 5*8(SI), X12; \ - MOVQ 8*8(SI), X13; \ - MOVQ 0*8(SI), X14; \ - MOVQ 6*8(SI), X15; \ - VPINSRQ_1_SI_X12(15*8); \ - VPINSRQ_1_SI_X13(2*8); \ - VPINSRQ_1_SI_X14(4*8); \ - VPINSRQ_1_SI_X15(10*8) - -// load msg: X12 = (12, 13), X13 = (1, 10), X14 = (2, 7), X15 = (4, 5) -#define LOAD_MSG_AVX_12_13_1_10_2_7_4_5() \ - VMOVDQU 12*8(SI), X12; \ - MOVQ 1*8(SI), X13; \ - MOVQ 2*8(SI), X14; \ - VPINSRQ_1_SI_X13(10*8); \ - VPINSRQ_1_SI_X14(7*8); \ - VMOVDQU 4*8(SI), X15 - -// load msg: X12 = (15, 9), X13 = (3, 13), X14 = (11, 14), X15 = (12, 0) -#define LOAD_MSG_AVX_15_9_3_13_11_14_12_0() \ - MOVQ 15*8(SI), X12; \ - MOVQ 3*8(SI), X13; \ - MOVQ 11*8(SI), X14; \ - MOVQ 12*8(SI), X15; \ - VPINSRQ_1_SI_X12(9*8); \ - VPINSRQ_1_SI_X13(13*8); \ - VPINSRQ_1_SI_X14(14*8); \ - VPINSRQ_1_SI_X15_0 +DATA ·AVX2_iv1<>+0(SB)/8, $0x510e527fade682d1 +DATA ·AVX2_iv1<>+8(SB)/8, $0x9b05688c2b3e6c1f +DATA ·AVX2_iv1<>+16(SB)/8, $0x1f83d9abfb41bd6b +DATA ·AVX2_iv1<>+24(SB)/8, $0x5be0cd19137e2179 +GLOBL ·AVX2_iv1<>(SB), RODATA|NOPTR, $32 // func hashBlocksAVX(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) -TEXT ·hashBlocksAVX(SB), 4, $288-48 // frame size = 272 + 16 byte alignment - MOVQ h+0(FP), AX - MOVQ c+8(FP), BX - MOVQ flag+16(FP), CX - MOVQ blocks_base+24(FP), SI - MOVQ blocks_len+32(FP), DI - - MOVQ SP, R10 - ADDQ $15, R10 - ANDQ $~15, R10 - - VMOVDQU ·AVX_c40<>(SB), X0 - VMOVDQU ·AVX_c48<>(SB), X1 +// Requires: AVX, SSE2 +TEXT ·hashBlocksAVX(SB), NOSPLIT, $288-48 + MOVQ h+0(FP), AX + MOVQ c+8(FP), BX + MOVQ flag+16(FP), CX + MOVQ blocks_base+24(FP), SI + MOVQ blocks_len+32(FP), DI + MOVQ SP, R10 + ADDQ $0x0f, R10 + ANDQ $-16, R10 + VMOVDQU ·AVX_c40<>+0(SB), X0 + VMOVDQU ·AVX_c48<>+0(SB), X1 VMOVDQA X0, X8 VMOVDQA X1, X9 - - VMOVDQU ·AVX_iv3<>(SB), X0 - VMOVDQA X0, 0(R10) - XORQ CX, 0(R10) // 0(R10) = ·AVX_iv3 ^ (CX || 0) - - VMOVDQU 0(AX), X10 + VMOVDQU ·AVX_iv3<>+0(SB), X0 + VMOVDQA X0, (R10) + XORQ CX, (R10) + VMOVDQU (AX), X10 VMOVDQU 16(AX), X11 VMOVDQU 32(AX), X2 VMOVDQU 48(AX), X3 - - MOVQ 0(BX), R8 - MOVQ 8(BX), R9 + MOVQ (BX), R8 + MOVQ 8(BX), R9 loop: - ADDQ $128, R8 - CMPQ R8, $128 + ADDQ $0x80, R8 + CMPQ R8, $0x80 JGE noinc INCQ R9 noinc: - VMOVQ_R8_X15 - VPINSRQ_1_R9_X15 - + BYTE $0xc4 + BYTE $0x41 + BYTE $0xf9 + BYTE $0x6e + BYTE $0xf8 + BYTE $0xc4 + BYTE $0x43 + BYTE $0x81 + BYTE $0x22 + BYTE $0xf9 + BYTE $0x01 VMOVDQA X10, X0 VMOVDQA X11, X1 - VMOVDQU ·AVX_iv0<>(SB), X4 - VMOVDQU ·AVX_iv1<>(SB), X5 - VMOVDQU ·AVX_iv2<>(SB), X6 - + VMOVDQU ·AVX_iv0<>+0(SB), X4 + VMOVDQU ·AVX_iv1<>+0(SB), X5 + VMOVDQU ·AVX_iv2<>+0(SB), X6 VPXOR X15, X6, X6 - VMOVDQA 0(R10), X7 - - LOAD_MSG_AVX_0_2_4_6_1_3_5_7() + VMOVDQA (R10), X7 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x26 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x20 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x08 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x28 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x10 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x30 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x38 + BYTE $0x01 VMOVDQA X12, 16(R10) VMOVDQA X13, 32(R10) VMOVDQA X14, 48(R10) VMOVDQA X15, 64(R10) - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX() - LOAD_MSG_AVX(8, 10, 12, 14, 9, 11, 13, 15) + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x40 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x60 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x48 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x68 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x50 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x70 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x58 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x78 + BYTE $0x01 VMOVDQA X12, 80(R10) VMOVDQA X13, 96(R10) VMOVDQA X14, 112(R10) VMOVDQA X15, 128(R10) - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX_INV() - - LOAD_MSG_AVX(14, 4, 9, 13, 10, 8, 15, 6) + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x70 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x48 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x50 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x78 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x20 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x68 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x40 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x30 + BYTE $0x01 VMOVDQA X12, 144(R10) VMOVDQA X13, 160(R10) VMOVDQA X14, 176(R10) VMOVDQA X15, 192(R10) - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX() - LOAD_MSG_AVX_1_0_11_5_12_2_7_3() + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + VPSHUFD $0x4e, (SI), X12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x58 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x60 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x38 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x28 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x10 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x18 + BYTE $0x01 VMOVDQA X12, 208(R10) VMOVDQA X13, 224(R10) VMOVDQA X14, 240(R10) VMOVDQA X15, 256(R10) - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX_INV() - - LOAD_MSG_AVX_11_12_5_15_8_0_2_13() - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX() - LOAD_MSG_AVX(10, 3, 7, 9, 14, 6, 1, 4) - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX_INV() - - LOAD_MSG_AVX(7, 3, 13, 11, 9, 1, 12, 14) - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX() - LOAD_MSG_AVX_2_5_4_15_6_10_0_8() - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX_INV() - - LOAD_MSG_AVX_9_5_2_10_0_7_4_15() - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX() - LOAD_MSG_AVX(14, 11, 6, 3, 1, 12, 8, 13) - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX_INV() - - LOAD_MSG_AVX_2_6_0_8_12_10_11_3() - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX() - LOAD_MSG_AVX(4, 7, 15, 1, 13, 5, 14, 9) - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX_INV() - - LOAD_MSG_AVX(12, 1, 14, 4, 5, 15, 13, 10) - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX() - LOAD_MSG_AVX_0_6_9_8_7_3_2_11() - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX_INV() - - LOAD_MSG_AVX(13, 7, 12, 3, 11, 14, 1, 9) - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX() - LOAD_MSG_AVX_5_15_8_2_0_4_6_10() - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX_INV() - - LOAD_MSG_AVX_6_14_11_0_15_9_3_8() - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX() - LOAD_MSG_AVX_12_13_1_10_2_7_4_5() - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX_INV() - - LOAD_MSG_AVX(10, 8, 7, 1, 2, 4, 6, 5) - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX() - LOAD_MSG_AVX_15_9_3_13_11_14_12_0() - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, X12, X13, X14, X15, X15, X8, X9) - SHUFFLE_AVX_INV() - - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 16(R10), 32(R10), 48(R10), 64(R10), X15, X8, X9) - SHUFFLE_AVX() - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 80(R10), 96(R10), 112(R10), 128(R10), X15, X8, X9) - SHUFFLE_AVX_INV() - - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 144(R10), 160(R10), 176(R10), 192(R10), X15, X8, X9) - SHUFFLE_AVX() - HALF_ROUND_AVX(X0, X1, X2, X3, X4, X5, X6, X7, 208(R10), 224(R10), 240(R10), 256(R10), X15, X8, X9) - SHUFFLE_AVX_INV() - + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + VMOVDQU 88(SI), X12 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x28 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x40 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x10 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x78 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x36 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x68 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x50 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x38 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x70 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x08 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x48 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x30 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x20 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x38 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x68 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x48 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x60 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x58 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x08 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x70 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x10 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x20 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x30 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x3e + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x28 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x78 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x50 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x40 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x48 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x10 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x36 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x20 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x28 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x50 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x38 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x78 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x70 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x30 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x08 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x40 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x58 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x60 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x68 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x10 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x2e + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x60 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x58 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x30 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x40 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x50 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x18 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x20 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x78 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x68 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x70 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x38 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x08 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x28 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x48 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x60 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x70 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x28 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x68 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x08 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x20 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x78 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x50 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + MOVQ (SI), X12 + VPSHUFD $0x4e, 64(SI), X13 + MOVQ 56(SI), X14 + MOVQ 16(SI), X15 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x30 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x58 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x68 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x60 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x58 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x08 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x38 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x18 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x70 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x48 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + MOVQ 40(SI), X12 + MOVQ 64(SI), X13 + MOVQ (SI), X14 + MOVQ 48(SI), X15 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x78 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x10 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x20 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x50 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + MOVQ 48(SI), X12 + MOVQ 88(SI), X13 + MOVQ 120(SI), X14 + MOVQ 24(SI), X15 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x70 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x2e + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x48 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x40 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + VMOVDQU 96(SI), X12 + MOVQ 8(SI), X13 + MOVQ 16(SI), X14 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x50 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x38 + BYTE $0x01 + VMOVDQU 32(SI), X15 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x66 + BYTE $0x50 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x6e + BYTE $0x38 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x76 + BYTE $0x10 + BYTE $0xc5 + BYTE $0x7a + BYTE $0x7e + BYTE $0x7e + BYTE $0x30 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x40 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x08 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x20 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x7e + BYTE $0x28 + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + MOVQ 120(SI), X12 + MOVQ 24(SI), X13 + MOVQ 88(SI), X14 + MOVQ 96(SI), X15 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x99 + BYTE $0x22 + BYTE $0x66 + BYTE $0x48 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x91 + BYTE $0x22 + BYTE $0x6e + BYTE $0x68 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x89 + BYTE $0x22 + BYTE $0x76 + BYTE $0x70 + BYTE $0x01 + BYTE $0xc4 + BYTE $0x63 + BYTE $0x81 + BYTE $0x22 + BYTE $0x3e + BYTE $0x01 + VPADDQ X12, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X13, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ X14, X0, X0 + VPADDQ X2, X0, X0 + VPADDQ X15, X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + VPADDQ 16(R10), X0, X0 + VPADDQ X2, X0, X0 + VPADDQ 32(R10), X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ 48(R10), X0, X0 + VPADDQ X2, X0, X0 + VPADDQ 64(R10), X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + VPADDQ 80(R10), X0, X0 + VPADDQ X2, X0, X0 + VPADDQ 96(R10), X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ 112(R10), X0, X0 + VPADDQ X2, X0, X0 + VPADDQ 128(R10), X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff + VPADDQ 144(R10), X0, X0 + VPADDQ X2, X0, X0 + VPADDQ 160(R10), X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ 176(R10), X0, X0 + VPADDQ X2, X0, X0 + VPADDQ 192(R10), X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X6, X13 + VMOVDQA X2, X14 + VMOVDQA X4, X6 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x11 + BYTE $0x6c + BYTE $0xfd + VMOVDQA X5, X4 + VMOVDQA X6, X5 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xff + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x69 + BYTE $0x6d + BYTE $0xd7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xdf + VPADDQ 208(R10), X0, X0 + VPADDQ X2, X0, X0 + VPADDQ 224(R10), X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFD $-79, X6, X6 + VPSHUFD $-79, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPSHUFB X8, X2, X2 + VPSHUFB X8, X3, X3 + VPADDQ 240(R10), X0, X0 + VPADDQ X2, X0, X0 + VPADDQ 256(R10), X1, X1 + VPADDQ X3, X1, X1 + VPXOR X0, X6, X6 + VPXOR X1, X7, X7 + VPSHUFB X9, X6, X6 + VPSHUFB X9, X7, X7 + VPADDQ X6, X4, X4 + VPADDQ X7, X5, X5 + VPXOR X4, X2, X2 + VPXOR X5, X3, X3 + VPADDQ X2, X2, X15 + VPSRLQ $0x3f, X2, X2 + VPXOR X15, X2, X2 + VPADDQ X3, X3, X15 + VPSRLQ $0x3f, X3, X3 + VPXOR X15, X3, X3 + VMOVDQA X2, X13 + VMOVDQA X4, X14 + BYTE $0xc5 + BYTE $0x69 + BYTE $0x6c + BYTE $0xfa + VMOVDQA X5, X4 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x61 + BYTE $0x6d + BYTE $0xd7 + VMOVDQA X14, X5 + BYTE $0xc5 + BYTE $0x61 + BYTE $0x6c + BYTE $0xfb + VMOVDQA X6, X14 + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x11 + BYTE $0x6d + BYTE $0xdf + BYTE $0xc5 + BYTE $0x41 + BYTE $0x6c + BYTE $0xff + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x49 + BYTE $0x6d + BYTE $0xf7 + BYTE $0xc4 + BYTE $0x41 + BYTE $0x09 + BYTE $0x6c + BYTE $0xfe + BYTE $0xc4 + BYTE $0xc1 + BYTE $0x41 + BYTE $0x6d + BYTE $0xff VMOVDQU 32(AX), X14 VMOVDQU 48(AX), X15 VPXOR X0, X10, X10 @@ -729,16 +4524,36 @@ noinc: VPXOR X7, X15, X3 VMOVDQU X2, 32(AX) VMOVDQU X3, 48(AX) + LEAQ 128(SI), SI + SUBQ $0x80, DI + JNE loop + VMOVDQU X10, (AX) + VMOVDQU X11, 16(AX) + MOVQ R8, (BX) + MOVQ R9, 8(BX) + VZEROUPPER + RET - LEAQ 128(SI), SI - SUBQ $128, DI - JNE loop +DATA ·AVX_c40<>+0(SB)/8, $0x0201000706050403 +DATA ·AVX_c40<>+8(SB)/8, $0x0a09080f0e0d0c0b +GLOBL ·AVX_c40<>(SB), RODATA|NOPTR, $16 - VMOVDQU X10, 0(AX) - VMOVDQU X11, 16(AX) +DATA ·AVX_c48<>+0(SB)/8, $0x0100070605040302 +DATA ·AVX_c48<>+8(SB)/8, $0x09080f0e0d0c0b0a +GLOBL ·AVX_c48<>(SB), RODATA|NOPTR, $16 - MOVQ R8, 0(BX) - MOVQ R9, 8(BX) - VZEROUPPER +DATA ·AVX_iv3<>+0(SB)/8, $0x1f83d9abfb41bd6b +DATA ·AVX_iv3<>+8(SB)/8, $0x5be0cd19137e2179 +GLOBL ·AVX_iv3<>(SB), RODATA|NOPTR, $16 - RET +DATA ·AVX_iv0<>+0(SB)/8, $0x6a09e667f3bcc908 +DATA ·AVX_iv0<>+8(SB)/8, $0xbb67ae8584caa73b +GLOBL ·AVX_iv0<>(SB), RODATA|NOPTR, $16 + +DATA ·AVX_iv1<>+0(SB)/8, $0x3c6ef372fe94f82b +DATA ·AVX_iv1<>+8(SB)/8, $0xa54ff53a5f1d36f1 +GLOBL ·AVX_iv1<>(SB), RODATA|NOPTR, $16 + +DATA ·AVX_iv2<>+0(SB)/8, $0x510e527fade682d1 +DATA ·AVX_iv2<>+8(SB)/8, $0x9b05688c2b3e6c1f +GLOBL ·AVX_iv2<>(SB), RODATA|NOPTR, $16 diff --git a/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s b/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s index adfac00c..9a0ce212 100644 --- a/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s +++ b/vendor/golang.org/x/crypto/blake2b/blake2b_amd64.s @@ -1,278 +1,1441 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Code generated by command: go run blake2b_amd64_asm.go -out ../../blake2b_amd64.s -pkg blake2b. DO NOT EDIT. //go:build amd64 && gc && !purego #include "textflag.h" -DATA ·iv0<>+0x00(SB)/8, $0x6a09e667f3bcc908 -DATA ·iv0<>+0x08(SB)/8, $0xbb67ae8584caa73b -GLOBL ·iv0<>(SB), (NOPTR+RODATA), $16 - -DATA ·iv1<>+0x00(SB)/8, $0x3c6ef372fe94f82b -DATA ·iv1<>+0x08(SB)/8, $0xa54ff53a5f1d36f1 -GLOBL ·iv1<>(SB), (NOPTR+RODATA), $16 - -DATA ·iv2<>+0x00(SB)/8, $0x510e527fade682d1 -DATA ·iv2<>+0x08(SB)/8, $0x9b05688c2b3e6c1f -GLOBL ·iv2<>(SB), (NOPTR+RODATA), $16 - -DATA ·iv3<>+0x00(SB)/8, $0x1f83d9abfb41bd6b -DATA ·iv3<>+0x08(SB)/8, $0x5be0cd19137e2179 -GLOBL ·iv3<>(SB), (NOPTR+RODATA), $16 - -DATA ·c40<>+0x00(SB)/8, $0x0201000706050403 -DATA ·c40<>+0x08(SB)/8, $0x0a09080f0e0d0c0b -GLOBL ·c40<>(SB), (NOPTR+RODATA), $16 - -DATA ·c48<>+0x00(SB)/8, $0x0100070605040302 -DATA ·c48<>+0x08(SB)/8, $0x09080f0e0d0c0b0a -GLOBL ·c48<>(SB), (NOPTR+RODATA), $16 - -#define SHUFFLE(v2, v3, v4, v5, v6, v7, t1, t2) \ - MOVO v4, t1; \ - MOVO v5, v4; \ - MOVO t1, v5; \ - MOVO v6, t1; \ - PUNPCKLQDQ v6, t2; \ - PUNPCKHQDQ v7, v6; \ - PUNPCKHQDQ t2, v6; \ - PUNPCKLQDQ v7, t2; \ - MOVO t1, v7; \ - MOVO v2, t1; \ - PUNPCKHQDQ t2, v7; \ - PUNPCKLQDQ v3, t2; \ - PUNPCKHQDQ t2, v2; \ - PUNPCKLQDQ t1, t2; \ - PUNPCKHQDQ t2, v3 - -#define SHUFFLE_INV(v2, v3, v4, v5, v6, v7, t1, t2) \ - MOVO v4, t1; \ - MOVO v5, v4; \ - MOVO t1, v5; \ - MOVO v2, t1; \ - PUNPCKLQDQ v2, t2; \ - PUNPCKHQDQ v3, v2; \ - PUNPCKHQDQ t2, v2; \ - PUNPCKLQDQ v3, t2; \ - MOVO t1, v3; \ - MOVO v6, t1; \ - PUNPCKHQDQ t2, v3; \ - PUNPCKLQDQ v7, t2; \ - PUNPCKHQDQ t2, v6; \ - PUNPCKLQDQ t1, t2; \ - PUNPCKHQDQ t2, v7 - -#define HALF_ROUND(v0, v1, v2, v3, v4, v5, v6, v7, m0, m1, m2, m3, t0, c40, c48) \ - PADDQ m0, v0; \ - PADDQ m1, v1; \ - PADDQ v2, v0; \ - PADDQ v3, v1; \ - PXOR v0, v6; \ - PXOR v1, v7; \ - PSHUFD $0xB1, v6, v6; \ - PSHUFD $0xB1, v7, v7; \ - PADDQ v6, v4; \ - PADDQ v7, v5; \ - PXOR v4, v2; \ - PXOR v5, v3; \ - PSHUFB c40, v2; \ - PSHUFB c40, v3; \ - PADDQ m2, v0; \ - PADDQ m3, v1; \ - PADDQ v2, v0; \ - PADDQ v3, v1; \ - PXOR v0, v6; \ - PXOR v1, v7; \ - PSHUFB c48, v6; \ - PSHUFB c48, v7; \ - PADDQ v6, v4; \ - PADDQ v7, v5; \ - PXOR v4, v2; \ - PXOR v5, v3; \ - MOVOU v2, t0; \ - PADDQ v2, t0; \ - PSRLQ $63, v2; \ - PXOR t0, v2; \ - MOVOU v3, t0; \ - PADDQ v3, t0; \ - PSRLQ $63, v3; \ - PXOR t0, v3 - -#define LOAD_MSG(m0, m1, m2, m3, src, i0, i1, i2, i3, i4, i5, i6, i7) \ - MOVQ i0*8(src), m0; \ - PINSRQ $1, i1*8(src), m0; \ - MOVQ i2*8(src), m1; \ - PINSRQ $1, i3*8(src), m1; \ - MOVQ i4*8(src), m2; \ - PINSRQ $1, i5*8(src), m2; \ - MOVQ i6*8(src), m3; \ - PINSRQ $1, i7*8(src), m3 - // func hashBlocksSSE4(h *[8]uint64, c *[2]uint64, flag uint64, blocks []byte) -TEXT ·hashBlocksSSE4(SB), 4, $288-48 // frame size = 272 + 16 byte alignment - MOVQ h+0(FP), AX - MOVQ c+8(FP), BX - MOVQ flag+16(FP), CX - MOVQ blocks_base+24(FP), SI - MOVQ blocks_len+32(FP), DI - - MOVQ SP, R10 - ADDQ $15, R10 - ANDQ $~15, R10 - - MOVOU ·iv3<>(SB), X0 - MOVO X0, 0(R10) - XORQ CX, 0(R10) // 0(R10) = ·iv3 ^ (CX || 0) - - MOVOU ·c40<>(SB), X13 - MOVOU ·c48<>(SB), X14 - - MOVOU 0(AX), X12 +// Requires: SSE2, SSE4.1, SSSE3 +TEXT ·hashBlocksSSE4(SB), NOSPLIT, $288-48 + MOVQ h+0(FP), AX + MOVQ c+8(FP), BX + MOVQ flag+16(FP), CX + MOVQ blocks_base+24(FP), SI + MOVQ blocks_len+32(FP), DI + MOVQ SP, R10 + ADDQ $0x0f, R10 + ANDQ $-16, R10 + MOVOU ·iv3<>+0(SB), X0 + MOVO X0, (R10) + XORQ CX, (R10) + MOVOU ·c40<>+0(SB), X13 + MOVOU ·c48<>+0(SB), X14 + MOVOU (AX), X12 MOVOU 16(AX), X15 - - MOVQ 0(BX), R8 - MOVQ 8(BX), R9 + MOVQ (BX), R8 + MOVQ 8(BX), R9 loop: - ADDQ $128, R8 - CMPQ R8, $128 + ADDQ $0x80, R8 + CMPQ R8, $0x80 JGE noinc INCQ R9 noinc: - MOVQ R8, X8 - PINSRQ $1, R9, X8 - - MOVO X12, X0 - MOVO X15, X1 - MOVOU 32(AX), X2 - MOVOU 48(AX), X3 - MOVOU ·iv0<>(SB), X4 - MOVOU ·iv1<>(SB), X5 - MOVOU ·iv2<>(SB), X6 - - PXOR X8, X6 - MOVO 0(R10), X7 - - LOAD_MSG(X8, X9, X10, X11, SI, 0, 2, 4, 6, 1, 3, 5, 7) - MOVO X8, 16(R10) - MOVO X9, 32(R10) - MOVO X10, 48(R10) - MOVO X11, 64(R10) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) - LOAD_MSG(X8, X9, X10, X11, SI, 8, 10, 12, 14, 9, 11, 13, 15) - MOVO X8, 80(R10) - MOVO X9, 96(R10) - MOVO X10, 112(R10) - MOVO X11, 128(R10) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) - - LOAD_MSG(X8, X9, X10, X11, SI, 14, 4, 9, 13, 10, 8, 15, 6) - MOVO X8, 144(R10) - MOVO X9, 160(R10) - MOVO X10, 176(R10) - MOVO X11, 192(R10) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) - LOAD_MSG(X8, X9, X10, X11, SI, 1, 0, 11, 5, 12, 2, 7, 3) - MOVO X8, 208(R10) - MOVO X9, 224(R10) - MOVO X10, 240(R10) - MOVO X11, 256(R10) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) - - LOAD_MSG(X8, X9, X10, X11, SI, 11, 12, 5, 15, 8, 0, 2, 13) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) - LOAD_MSG(X8, X9, X10, X11, SI, 10, 3, 7, 9, 14, 6, 1, 4) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) - - LOAD_MSG(X8, X9, X10, X11, SI, 7, 3, 13, 11, 9, 1, 12, 14) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) - LOAD_MSG(X8, X9, X10, X11, SI, 2, 5, 4, 15, 6, 10, 0, 8) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) - - LOAD_MSG(X8, X9, X10, X11, SI, 9, 5, 2, 10, 0, 7, 4, 15) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) - LOAD_MSG(X8, X9, X10, X11, SI, 14, 11, 6, 3, 1, 12, 8, 13) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) - - LOAD_MSG(X8, X9, X10, X11, SI, 2, 6, 0, 8, 12, 10, 11, 3) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) - LOAD_MSG(X8, X9, X10, X11, SI, 4, 7, 15, 1, 13, 5, 14, 9) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) - - LOAD_MSG(X8, X9, X10, X11, SI, 12, 1, 14, 4, 5, 15, 13, 10) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) - LOAD_MSG(X8, X9, X10, X11, SI, 0, 6, 9, 8, 7, 3, 2, 11) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) - - LOAD_MSG(X8, X9, X10, X11, SI, 13, 7, 12, 3, 11, 14, 1, 9) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) - LOAD_MSG(X8, X9, X10, X11, SI, 5, 15, 8, 2, 0, 4, 6, 10) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) - - LOAD_MSG(X8, X9, X10, X11, SI, 6, 14, 11, 0, 15, 9, 3, 8) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) - LOAD_MSG(X8, X9, X10, X11, SI, 12, 13, 1, 10, 2, 7, 4, 5) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) - - LOAD_MSG(X8, X9, X10, X11, SI, 10, 8, 7, 1, 2, 4, 6, 5) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) - LOAD_MSG(X8, X9, X10, X11, SI, 15, 9, 3, 13, 11, 14, 12, 0) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X11, X13, X14) - SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) - - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 16(R10), 32(R10), 48(R10), 64(R10), X11, X13, X14) - SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 80(R10), 96(R10), 112(R10), 128(R10), X11, X13, X14) - SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) + MOVQ R8, X8 + PINSRQ $0x01, R9, X8 + MOVO X12, X0 + MOVO X15, X1 + MOVOU 32(AX), X2 + MOVOU 48(AX), X3 + MOVOU ·iv0<>+0(SB), X4 + MOVOU ·iv1<>+0(SB), X5 + MOVOU ·iv2<>+0(SB), X6 + PXOR X8, X6 + MOVO (R10), X7 + MOVQ (SI), X8 + PINSRQ $0x01, 16(SI), X8 + MOVQ 32(SI), X9 + PINSRQ $0x01, 48(SI), X9 + MOVQ 8(SI), X10 + PINSRQ $0x01, 24(SI), X10 + MOVQ 40(SI), X11 + PINSRQ $0x01, 56(SI), X11 + MOVO X8, 16(R10) + MOVO X9, 32(R10) + MOVO X10, 48(R10) + MOVO X11, 64(R10) + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 64(SI), X8 + PINSRQ $0x01, 80(SI), X8 + MOVQ 96(SI), X9 + PINSRQ $0x01, 112(SI), X9 + MOVQ 72(SI), X10 + PINSRQ $0x01, 88(SI), X10 + MOVQ 104(SI), X11 + PINSRQ $0x01, 120(SI), X11 + MOVO X8, 80(R10) + MOVO X9, 96(R10) + MOVO X10, 112(R10) + MOVO X11, 128(R10) + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 112(SI), X8 + PINSRQ $0x01, 32(SI), X8 + MOVQ 72(SI), X9 + PINSRQ $0x01, 104(SI), X9 + MOVQ 80(SI), X10 + PINSRQ $0x01, 64(SI), X10 + MOVQ 120(SI), X11 + PINSRQ $0x01, 48(SI), X11 + MOVO X8, 144(R10) + MOVO X9, 160(R10) + MOVO X10, 176(R10) + MOVO X11, 192(R10) + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 8(SI), X8 + PINSRQ $0x01, (SI), X8 + MOVQ 88(SI), X9 + PINSRQ $0x01, 40(SI), X9 + MOVQ 96(SI), X10 + PINSRQ $0x01, 16(SI), X10 + MOVQ 56(SI), X11 + PINSRQ $0x01, 24(SI), X11 + MOVO X8, 208(R10) + MOVO X9, 224(R10) + MOVO X10, 240(R10) + MOVO X11, 256(R10) + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 88(SI), X8 + PINSRQ $0x01, 96(SI), X8 + MOVQ 40(SI), X9 + PINSRQ $0x01, 120(SI), X9 + MOVQ 64(SI), X10 + PINSRQ $0x01, (SI), X10 + MOVQ 16(SI), X11 + PINSRQ $0x01, 104(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 80(SI), X8 + PINSRQ $0x01, 24(SI), X8 + MOVQ 56(SI), X9 + PINSRQ $0x01, 72(SI), X9 + MOVQ 112(SI), X10 + PINSRQ $0x01, 48(SI), X10 + MOVQ 8(SI), X11 + PINSRQ $0x01, 32(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 56(SI), X8 + PINSRQ $0x01, 24(SI), X8 + MOVQ 104(SI), X9 + PINSRQ $0x01, 88(SI), X9 + MOVQ 72(SI), X10 + PINSRQ $0x01, 8(SI), X10 + MOVQ 96(SI), X11 + PINSRQ $0x01, 112(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 16(SI), X8 + PINSRQ $0x01, 40(SI), X8 + MOVQ 32(SI), X9 + PINSRQ $0x01, 120(SI), X9 + MOVQ 48(SI), X10 + PINSRQ $0x01, 80(SI), X10 + MOVQ (SI), X11 + PINSRQ $0x01, 64(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 72(SI), X8 + PINSRQ $0x01, 40(SI), X8 + MOVQ 16(SI), X9 + PINSRQ $0x01, 80(SI), X9 + MOVQ (SI), X10 + PINSRQ $0x01, 56(SI), X10 + MOVQ 32(SI), X11 + PINSRQ $0x01, 120(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 112(SI), X8 + PINSRQ $0x01, 88(SI), X8 + MOVQ 48(SI), X9 + PINSRQ $0x01, 24(SI), X9 + MOVQ 8(SI), X10 + PINSRQ $0x01, 96(SI), X10 + MOVQ 64(SI), X11 + PINSRQ $0x01, 104(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 16(SI), X8 + PINSRQ $0x01, 48(SI), X8 + MOVQ (SI), X9 + PINSRQ $0x01, 64(SI), X9 + MOVQ 96(SI), X10 + PINSRQ $0x01, 80(SI), X10 + MOVQ 88(SI), X11 + PINSRQ $0x01, 24(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 32(SI), X8 + PINSRQ $0x01, 56(SI), X8 + MOVQ 120(SI), X9 + PINSRQ $0x01, 8(SI), X9 + MOVQ 104(SI), X10 + PINSRQ $0x01, 40(SI), X10 + MOVQ 112(SI), X11 + PINSRQ $0x01, 72(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 96(SI), X8 + PINSRQ $0x01, 8(SI), X8 + MOVQ 112(SI), X9 + PINSRQ $0x01, 32(SI), X9 + MOVQ 40(SI), X10 + PINSRQ $0x01, 120(SI), X10 + MOVQ 104(SI), X11 + PINSRQ $0x01, 80(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ (SI), X8 + PINSRQ $0x01, 48(SI), X8 + MOVQ 72(SI), X9 + PINSRQ $0x01, 64(SI), X9 + MOVQ 56(SI), X10 + PINSRQ $0x01, 24(SI), X10 + MOVQ 16(SI), X11 + PINSRQ $0x01, 88(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 104(SI), X8 + PINSRQ $0x01, 56(SI), X8 + MOVQ 96(SI), X9 + PINSRQ $0x01, 24(SI), X9 + MOVQ 88(SI), X10 + PINSRQ $0x01, 112(SI), X10 + MOVQ 8(SI), X11 + PINSRQ $0x01, 72(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 40(SI), X8 + PINSRQ $0x01, 120(SI), X8 + MOVQ 64(SI), X9 + PINSRQ $0x01, 16(SI), X9 + MOVQ (SI), X10 + PINSRQ $0x01, 32(SI), X10 + MOVQ 48(SI), X11 + PINSRQ $0x01, 80(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 48(SI), X8 + PINSRQ $0x01, 112(SI), X8 + MOVQ 88(SI), X9 + PINSRQ $0x01, (SI), X9 + MOVQ 120(SI), X10 + PINSRQ $0x01, 72(SI), X10 + MOVQ 24(SI), X11 + PINSRQ $0x01, 64(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 96(SI), X8 + PINSRQ $0x01, 104(SI), X8 + MOVQ 8(SI), X9 + PINSRQ $0x01, 80(SI), X9 + MOVQ 16(SI), X10 + PINSRQ $0x01, 56(SI), X10 + MOVQ 32(SI), X11 + PINSRQ $0x01, 40(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVQ 80(SI), X8 + PINSRQ $0x01, 64(SI), X8 + MOVQ 56(SI), X9 + PINSRQ $0x01, 8(SI), X9 + MOVQ 16(SI), X10 + PINSRQ $0x01, 32(SI), X10 + MOVQ 48(SI), X11 + PINSRQ $0x01, 40(SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + MOVQ 120(SI), X8 + PINSRQ $0x01, 72(SI), X8 + MOVQ 24(SI), X9 + PINSRQ $0x01, 104(SI), X9 + MOVQ 88(SI), X10 + PINSRQ $0x01, 112(SI), X10 + MOVQ 96(SI), X11 + PINSRQ $0x01, (SI), X11 + PADDQ X8, X0 + PADDQ X9, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ X10, X0 + PADDQ X11, X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + PADDQ 16(R10), X0 + PADDQ 32(R10), X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ 48(R10), X0 + PADDQ 64(R10), X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + PADDQ 80(R10), X0 + PADDQ 96(R10), X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ 112(R10), X0 + PADDQ 128(R10), X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + PADDQ 144(R10), X0 + PADDQ 160(R10), X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ 176(R10), X0 + PADDQ 192(R10), X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X6, X8 + PUNPCKLQDQ X6, X9 + PUNPCKHQDQ X7, X6 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X7, X9 + MOVO X8, X7 + MOVO X2, X8 + PUNPCKHQDQ X9, X7 + PUNPCKLQDQ X3, X9 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X3 + PADDQ 208(R10), X0 + PADDQ 224(R10), X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFD $0xb1, X6, X6 + PSHUFD $0xb1, X7, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + PSHUFB X13, X2 + PSHUFB X13, X3 + PADDQ 240(R10), X0 + PADDQ 256(R10), X1 + PADDQ X2, X0 + PADDQ X3, X1 + PXOR X0, X6 + PXOR X1, X7 + PSHUFB X14, X6 + PSHUFB X14, X7 + PADDQ X6, X4 + PADDQ X7, X5 + PXOR X4, X2 + PXOR X5, X3 + MOVOU X2, X11 + PADDQ X2, X11 + PSRLQ $0x3f, X2 + PXOR X11, X2 + MOVOU X3, X11 + PADDQ X3, X11 + PSRLQ $0x3f, X3 + PXOR X11, X3 + MOVO X4, X8 + MOVO X5, X4 + MOVO X8, X5 + MOVO X2, X8 + PUNPCKLQDQ X2, X9 + PUNPCKHQDQ X3, X2 + PUNPCKHQDQ X9, X2 + PUNPCKLQDQ X3, X9 + MOVO X8, X3 + MOVO X6, X8 + PUNPCKHQDQ X9, X3 + PUNPCKLQDQ X7, X9 + PUNPCKHQDQ X9, X6 + PUNPCKLQDQ X8, X9 + PUNPCKHQDQ X9, X7 + MOVOU 32(AX), X10 + MOVOU 48(AX), X11 + PXOR X0, X12 + PXOR X1, X15 + PXOR X2, X10 + PXOR X3, X11 + PXOR X4, X12 + PXOR X5, X15 + PXOR X6, X10 + PXOR X7, X11 + MOVOU X10, 32(AX) + MOVOU X11, 48(AX) + LEAQ 128(SI), SI + SUBQ $0x80, DI + JNE loop + MOVOU X12, (AX) + MOVOU X15, 16(AX) + MOVQ R8, (BX) + MOVQ R9, 8(BX) + RET - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 144(R10), 160(R10), 176(R10), 192(R10), X11, X13, X14) - SHUFFLE(X2, X3, X4, X5, X6, X7, X8, X9) - HALF_ROUND(X0, X1, X2, X3, X4, X5, X6, X7, 208(R10), 224(R10), 240(R10), 256(R10), X11, X13, X14) - SHUFFLE_INV(X2, X3, X4, X5, X6, X7, X8, X9) +DATA ·iv3<>+0(SB)/8, $0x1f83d9abfb41bd6b +DATA ·iv3<>+8(SB)/8, $0x5be0cd19137e2179 +GLOBL ·iv3<>(SB), RODATA|NOPTR, $16 - MOVOU 32(AX), X10 - MOVOU 48(AX), X11 - PXOR X0, X12 - PXOR X1, X15 - PXOR X2, X10 - PXOR X3, X11 - PXOR X4, X12 - PXOR X5, X15 - PXOR X6, X10 - PXOR X7, X11 - MOVOU X10, 32(AX) - MOVOU X11, 48(AX) +DATA ·c40<>+0(SB)/8, $0x0201000706050403 +DATA ·c40<>+8(SB)/8, $0x0a09080f0e0d0c0b +GLOBL ·c40<>(SB), RODATA|NOPTR, $16 - LEAQ 128(SI), SI - SUBQ $128, DI - JNE loop +DATA ·c48<>+0(SB)/8, $0x0100070605040302 +DATA ·c48<>+8(SB)/8, $0x09080f0e0d0c0b0a +GLOBL ·c48<>(SB), RODATA|NOPTR, $16 - MOVOU X12, 0(AX) - MOVOU X15, 16(AX) +DATA ·iv0<>+0(SB)/8, $0x6a09e667f3bcc908 +DATA ·iv0<>+8(SB)/8, $0xbb67ae8584caa73b +GLOBL ·iv0<>(SB), RODATA|NOPTR, $16 - MOVQ R8, 0(BX) - MOVQ R9, 8(BX) +DATA ·iv1<>+0(SB)/8, $0x3c6ef372fe94f82b +DATA ·iv1<>+8(SB)/8, $0xa54ff53a5f1d36f1 +GLOBL ·iv1<>(SB), RODATA|NOPTR, $16 - RET +DATA ·iv2<>+0(SB)/8, $0x510e527fade682d1 +DATA ·iv2<>+8(SB)/8, $0x9b05688c2b3e6c1f +GLOBL ·iv2<>(SB), RODATA|NOPTR, $16 diff --git a/vendor/golang.org/x/crypto/sha3/shake.go b/vendor/golang.org/x/crypto/sha3/shake.go index 1ea9275b..a01ef435 100644 --- a/vendor/golang.org/x/crypto/sha3/shake.go +++ b/vendor/golang.org/x/crypto/sha3/shake.go @@ -85,9 +85,9 @@ func newCShake(N, S []byte, rate, outputLen int, dsbyte byte) ShakeHash { // leftEncode returns max 9 bytes c.initBlock = make([]byte, 0, 9*2+len(N)+len(S)) - c.initBlock = append(c.initBlock, leftEncode(uint64(len(N)*8))...) + c.initBlock = append(c.initBlock, leftEncode(uint64(len(N))*8)...) c.initBlock = append(c.initBlock, N...) - c.initBlock = append(c.initBlock, leftEncode(uint64(len(S)*8))...) + c.initBlock = append(c.initBlock, leftEncode(uint64(len(S))*8)...) c.initBlock = append(c.initBlock, S...) c.Write(bytepad(c.initBlock, c.rate)) return &c diff --git a/vendor/golang.org/x/net/http2/config.go b/vendor/golang.org/x/net/http2/config.go new file mode 100644 index 00000000..de58dfb8 --- /dev/null +++ b/vendor/golang.org/x/net/http2/config.go @@ -0,0 +1,122 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http2 + +import ( + "math" + "net/http" + "time" +) + +// http2Config is a package-internal version of net/http.HTTP2Config. +// +// http.HTTP2Config was added in Go 1.24. +// When running with a version of net/http that includes HTTP2Config, +// we merge the configuration with the fields in Transport or Server +// to produce an http2Config. +// +// Zero valued fields in http2Config are interpreted as in the +// net/http.HTTPConfig documentation. +// +// Precedence order for reconciling configurations is: +// +// - Use the net/http.{Server,Transport}.HTTP2Config value, when non-zero. +// - Otherwise use the http2.{Server.Transport} value. +// - If the resulting value is zero or out of range, use a default. +type http2Config struct { + MaxConcurrentStreams uint32 + MaxDecoderHeaderTableSize uint32 + MaxEncoderHeaderTableSize uint32 + MaxReadFrameSize uint32 + MaxUploadBufferPerConnection int32 + MaxUploadBufferPerStream int32 + SendPingTimeout time.Duration + PingTimeout time.Duration + WriteByteTimeout time.Duration + PermitProhibitedCipherSuites bool + CountError func(errType string) +} + +// configFromServer merges configuration settings from +// net/http.Server.HTTP2Config and http2.Server. +func configFromServer(h1 *http.Server, h2 *Server) http2Config { + conf := http2Config{ + MaxConcurrentStreams: h2.MaxConcurrentStreams, + MaxEncoderHeaderTableSize: h2.MaxEncoderHeaderTableSize, + MaxDecoderHeaderTableSize: h2.MaxDecoderHeaderTableSize, + MaxReadFrameSize: h2.MaxReadFrameSize, + MaxUploadBufferPerConnection: h2.MaxUploadBufferPerConnection, + MaxUploadBufferPerStream: h2.MaxUploadBufferPerStream, + SendPingTimeout: h2.ReadIdleTimeout, + PingTimeout: h2.PingTimeout, + WriteByteTimeout: h2.WriteByteTimeout, + PermitProhibitedCipherSuites: h2.PermitProhibitedCipherSuites, + CountError: h2.CountError, + } + fillNetHTTPServerConfig(&conf, h1) + setConfigDefaults(&conf, true) + return conf +} + +// configFromServer merges configuration settings from h2 and h2.t1.HTTP2 +// (the net/http Transport). +func configFromTransport(h2 *Transport) http2Config { + conf := http2Config{ + MaxEncoderHeaderTableSize: h2.MaxEncoderHeaderTableSize, + MaxDecoderHeaderTableSize: h2.MaxDecoderHeaderTableSize, + MaxReadFrameSize: h2.MaxReadFrameSize, + SendPingTimeout: h2.ReadIdleTimeout, + PingTimeout: h2.PingTimeout, + WriteByteTimeout: h2.WriteByteTimeout, + } + + // Unlike most config fields, where out-of-range values revert to the default, + // Transport.MaxReadFrameSize clips. + if conf.MaxReadFrameSize < minMaxFrameSize { + conf.MaxReadFrameSize = minMaxFrameSize + } else if conf.MaxReadFrameSize > maxFrameSize { + conf.MaxReadFrameSize = maxFrameSize + } + + if h2.t1 != nil { + fillNetHTTPTransportConfig(&conf, h2.t1) + } + setConfigDefaults(&conf, false) + return conf +} + +func setDefault[T ~int | ~int32 | ~uint32 | ~int64](v *T, minval, maxval, defval T) { + if *v < minval || *v > maxval { + *v = defval + } +} + +func setConfigDefaults(conf *http2Config, server bool) { + setDefault(&conf.MaxConcurrentStreams, 1, math.MaxUint32, defaultMaxStreams) + setDefault(&conf.MaxEncoderHeaderTableSize, 1, math.MaxUint32, initialHeaderTableSize) + setDefault(&conf.MaxDecoderHeaderTableSize, 1, math.MaxUint32, initialHeaderTableSize) + if server { + setDefault(&conf.MaxUploadBufferPerConnection, initialWindowSize, math.MaxInt32, 1<<20) + } else { + setDefault(&conf.MaxUploadBufferPerConnection, initialWindowSize, math.MaxInt32, transportDefaultConnFlow) + } + if server { + setDefault(&conf.MaxUploadBufferPerStream, 1, math.MaxInt32, 1<<20) + } else { + setDefault(&conf.MaxUploadBufferPerStream, 1, math.MaxInt32, transportDefaultStreamFlow) + } + setDefault(&conf.MaxReadFrameSize, minMaxFrameSize, maxFrameSize, defaultMaxReadFrameSize) + setDefault(&conf.PingTimeout, 1, math.MaxInt64, 15*time.Second) +} + +// adjustHTTP1MaxHeaderSize converts a limit in bytes on the size of an HTTP/1 header +// to an HTTP/2 MAX_HEADER_LIST_SIZE value. +func adjustHTTP1MaxHeaderSize(n int64) int64 { + // http2's count is in a slightly different unit and includes 32 bytes per pair. + // So, take the net/http.Server value and pad it up a bit, assuming 10 headers. + const perFieldOverhead = 32 // per http2 spec + const typicalHeaders = 10 // conservative + return n + typicalHeaders*perFieldOverhead +} diff --git a/vendor/golang.org/x/net/http2/config_go124.go b/vendor/golang.org/x/net/http2/config_go124.go new file mode 100644 index 00000000..e3784123 --- /dev/null +++ b/vendor/golang.org/x/net/http2/config_go124.go @@ -0,0 +1,61 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.24 + +package http2 + +import "net/http" + +// fillNetHTTPServerConfig sets fields in conf from srv.HTTP2. +func fillNetHTTPServerConfig(conf *http2Config, srv *http.Server) { + fillNetHTTPConfig(conf, srv.HTTP2) +} + +// fillNetHTTPServerConfig sets fields in conf from tr.HTTP2. +func fillNetHTTPTransportConfig(conf *http2Config, tr *http.Transport) { + fillNetHTTPConfig(conf, tr.HTTP2) +} + +func fillNetHTTPConfig(conf *http2Config, h2 *http.HTTP2Config) { + if h2 == nil { + return + } + if h2.MaxConcurrentStreams != 0 { + conf.MaxConcurrentStreams = uint32(h2.MaxConcurrentStreams) + } + if h2.MaxEncoderHeaderTableSize != 0 { + conf.MaxEncoderHeaderTableSize = uint32(h2.MaxEncoderHeaderTableSize) + } + if h2.MaxDecoderHeaderTableSize != 0 { + conf.MaxDecoderHeaderTableSize = uint32(h2.MaxDecoderHeaderTableSize) + } + if h2.MaxConcurrentStreams != 0 { + conf.MaxConcurrentStreams = uint32(h2.MaxConcurrentStreams) + } + if h2.MaxReadFrameSize != 0 { + conf.MaxReadFrameSize = uint32(h2.MaxReadFrameSize) + } + if h2.MaxReceiveBufferPerConnection != 0 { + conf.MaxUploadBufferPerConnection = int32(h2.MaxReceiveBufferPerConnection) + } + if h2.MaxReceiveBufferPerStream != 0 { + conf.MaxUploadBufferPerStream = int32(h2.MaxReceiveBufferPerStream) + } + if h2.SendPingTimeout != 0 { + conf.SendPingTimeout = h2.SendPingTimeout + } + if h2.PingTimeout != 0 { + conf.PingTimeout = h2.PingTimeout + } + if h2.WriteByteTimeout != 0 { + conf.WriteByteTimeout = h2.WriteByteTimeout + } + if h2.PermitProhibitedCipherSuites { + conf.PermitProhibitedCipherSuites = true + } + if h2.CountError != nil { + conf.CountError = h2.CountError + } +} diff --git a/vendor/golang.org/x/net/http2/config_pre_go124.go b/vendor/golang.org/x/net/http2/config_pre_go124.go new file mode 100644 index 00000000..060fd6c6 --- /dev/null +++ b/vendor/golang.org/x/net/http2/config_pre_go124.go @@ -0,0 +1,16 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.24 + +package http2 + +import "net/http" + +// Pre-Go 1.24 fallback. +// The Server.HTTP2 and Transport.HTTP2 config fields were added in Go 1.24. + +func fillNetHTTPServerConfig(conf *http2Config, srv *http.Server) {} + +func fillNetHTTPTransportConfig(conf *http2Config, tr *http.Transport) {} diff --git a/vendor/golang.org/x/net/http2/http2.go b/vendor/golang.org/x/net/http2/http2.go index 003e649f..7688c356 100644 --- a/vendor/golang.org/x/net/http2/http2.go +++ b/vendor/golang.org/x/net/http2/http2.go @@ -19,8 +19,9 @@ import ( "bufio" "context" "crypto/tls" + "errors" "fmt" - "io" + "net" "net/http" "os" "sort" @@ -237,13 +238,19 @@ func (cw closeWaiter) Wait() { // Its buffered writer is lazily allocated as needed, to minimize // idle memory usage with many connections. type bufferedWriter struct { - _ incomparable - w io.Writer // immutable - bw *bufio.Writer // non-nil when data is buffered + _ incomparable + group synctestGroupInterface // immutable + conn net.Conn // immutable + bw *bufio.Writer // non-nil when data is buffered + byteTimeout time.Duration // immutable, WriteByteTimeout } -func newBufferedWriter(w io.Writer) *bufferedWriter { - return &bufferedWriter{w: w} +func newBufferedWriter(group synctestGroupInterface, conn net.Conn, timeout time.Duration) *bufferedWriter { + return &bufferedWriter{ + group: group, + conn: conn, + byteTimeout: timeout, + } } // bufWriterPoolBufferSize is the size of bufio.Writer's @@ -270,7 +277,7 @@ func (w *bufferedWriter) Available() int { func (w *bufferedWriter) Write(p []byte) (n int, err error) { if w.bw == nil { bw := bufWriterPool.Get().(*bufio.Writer) - bw.Reset(w.w) + bw.Reset((*bufferedWriterTimeoutWriter)(w)) w.bw = bw } return w.bw.Write(p) @@ -288,6 +295,38 @@ func (w *bufferedWriter) Flush() error { return err } +type bufferedWriterTimeoutWriter bufferedWriter + +func (w *bufferedWriterTimeoutWriter) Write(p []byte) (n int, err error) { + return writeWithByteTimeout(w.group, w.conn, w.byteTimeout, p) +} + +// writeWithByteTimeout writes to conn. +// If more than timeout passes without any bytes being written to the connection, +// the write fails. +func writeWithByteTimeout(group synctestGroupInterface, conn net.Conn, timeout time.Duration, p []byte) (n int, err error) { + if timeout <= 0 { + return conn.Write(p) + } + for { + var now time.Time + if group == nil { + now = time.Now() + } else { + now = group.Now() + } + conn.SetWriteDeadline(now.Add(timeout)) + nn, err := conn.Write(p[n:]) + n += nn + if n == len(p) || nn == 0 || !errors.Is(err, os.ErrDeadlineExceeded) { + // Either we finished the write, made no progress, or hit the deadline. + // Whichever it is, we're done now. + conn.SetWriteDeadline(time.Time{}) + return n, err + } + } +} + func mustUint31(v int32) uint32 { if v < 0 || v > 2147483647 { panic("out of range") diff --git a/vendor/golang.org/x/net/http2/server.go b/vendor/golang.org/x/net/http2/server.go index 6c349f3e..617b4a47 100644 --- a/vendor/golang.org/x/net/http2/server.go +++ b/vendor/golang.org/x/net/http2/server.go @@ -29,6 +29,7 @@ import ( "bufio" "bytes" "context" + "crypto/rand" "crypto/tls" "errors" "fmt" @@ -52,10 +53,14 @@ import ( ) const ( - prefaceTimeout = 10 * time.Second - firstSettingsTimeout = 2 * time.Second // should be in-flight with preface anyway - handlerChunkWriteSize = 4 << 10 - defaultMaxStreams = 250 // TODO: make this 100 as the GFE seems to? + prefaceTimeout = 10 * time.Second + firstSettingsTimeout = 2 * time.Second // should be in-flight with preface anyway + handlerChunkWriteSize = 4 << 10 + defaultMaxStreams = 250 // TODO: make this 100 as the GFE seems to? + + // maxQueuedControlFrames is the maximum number of control frames like + // SETTINGS, PING and RST_STREAM that will be queued for writing before + // the connection is closed to prevent memory exhaustion attacks. maxQueuedControlFrames = 10000 ) @@ -127,6 +132,22 @@ type Server struct { // If zero or negative, there is no timeout. IdleTimeout time.Duration + // ReadIdleTimeout is the timeout after which a health check using a ping + // frame will be carried out if no frame is received on the connection. + // If zero, no health check is performed. + ReadIdleTimeout time.Duration + + // PingTimeout is the timeout after which the connection will be closed + // if a response to a ping is not received. + // If zero, a default of 15 seconds is used. + PingTimeout time.Duration + + // WriteByteTimeout is the timeout after which a connection will be + // closed if no data can be written to it. The timeout begins when data is + // available to write, and is extended whenever any bytes are written. + // If zero or negative, there is no timeout. + WriteByteTimeout time.Duration + // MaxUploadBufferPerConnection is the size of the initial flow // control window for each connections. The HTTP/2 spec does not // allow this to be smaller than 65535 or larger than 2^32-1. @@ -189,57 +210,6 @@ func (s *Server) afterFunc(d time.Duration, f func()) timer { return timeTimer{time.AfterFunc(d, f)} } -func (s *Server) initialConnRecvWindowSize() int32 { - if s.MaxUploadBufferPerConnection >= initialWindowSize { - return s.MaxUploadBufferPerConnection - } - return 1 << 20 -} - -func (s *Server) initialStreamRecvWindowSize() int32 { - if s.MaxUploadBufferPerStream > 0 { - return s.MaxUploadBufferPerStream - } - return 1 << 20 -} - -func (s *Server) maxReadFrameSize() uint32 { - if v := s.MaxReadFrameSize; v >= minMaxFrameSize && v <= maxFrameSize { - return v - } - return defaultMaxReadFrameSize -} - -func (s *Server) maxConcurrentStreams() uint32 { - if v := s.MaxConcurrentStreams; v > 0 { - return v - } - return defaultMaxStreams -} - -func (s *Server) maxDecoderHeaderTableSize() uint32 { - if v := s.MaxDecoderHeaderTableSize; v > 0 { - return v - } - return initialHeaderTableSize -} - -func (s *Server) maxEncoderHeaderTableSize() uint32 { - if v := s.MaxEncoderHeaderTableSize; v > 0 { - return v - } - return initialHeaderTableSize -} - -// maxQueuedControlFrames is the maximum number of control frames like -// SETTINGS, PING and RST_STREAM that will be queued for writing before -// the connection is closed to prevent memory exhaustion attacks. -func (s *Server) maxQueuedControlFrames() int { - // TODO: if anybody asks, add a Server field, and remember to define the - // behavior of negative values. - return maxQueuedControlFrames -} - type serverInternalState struct { mu sync.Mutex activeConns map[*serverConn]struct{} @@ -440,13 +410,15 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon baseCtx, cancel := serverConnBaseContext(c, opts) defer cancel() + http1srv := opts.baseConfig() + conf := configFromServer(http1srv, s) sc := &serverConn{ srv: s, - hs: opts.baseConfig(), + hs: http1srv, conn: c, baseCtx: baseCtx, remoteAddrStr: c.RemoteAddr().String(), - bw: newBufferedWriter(c), + bw: newBufferedWriter(s.group, c, conf.WriteByteTimeout), handler: opts.handler(), streams: make(map[uint32]*stream), readFrameCh: make(chan readFrameResult), @@ -456,9 +428,12 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon bodyReadCh: make(chan bodyReadMsg), // buffering doesn't matter either way doneServing: make(chan struct{}), clientMaxStreams: math.MaxUint32, // Section 6.5.2: "Initially, there is no limit to this value" - advMaxStreams: s.maxConcurrentStreams(), + advMaxStreams: conf.MaxConcurrentStreams, initialStreamSendWindowSize: initialWindowSize, + initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream, maxFrameSize: initialMaxFrameSize, + pingTimeout: conf.PingTimeout, + countErrorFunc: conf.CountError, serveG: newGoroutineLock(), pushEnabled: true, sawClientPreface: opts.SawClientPreface, @@ -491,15 +466,15 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon sc.flow.add(initialWindowSize) sc.inflow.init(initialWindowSize) sc.hpackEncoder = hpack.NewEncoder(&sc.headerWriteBuf) - sc.hpackEncoder.SetMaxDynamicTableSizeLimit(s.maxEncoderHeaderTableSize()) + sc.hpackEncoder.SetMaxDynamicTableSizeLimit(conf.MaxEncoderHeaderTableSize) fr := NewFramer(sc.bw, c) - if s.CountError != nil { - fr.countError = s.CountError + if conf.CountError != nil { + fr.countError = conf.CountError } - fr.ReadMetaHeaders = hpack.NewDecoder(s.maxDecoderHeaderTableSize(), nil) + fr.ReadMetaHeaders = hpack.NewDecoder(conf.MaxDecoderHeaderTableSize, nil) fr.MaxHeaderListSize = sc.maxHeaderListSize() - fr.SetMaxReadFrameSize(s.maxReadFrameSize()) + fr.SetMaxReadFrameSize(conf.MaxReadFrameSize) sc.framer = fr if tc, ok := c.(connectionStater); ok { @@ -532,7 +507,7 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon // So for now, do nothing here again. } - if !s.PermitProhibitedCipherSuites && isBadCipher(sc.tlsState.CipherSuite) { + if !conf.PermitProhibitedCipherSuites && isBadCipher(sc.tlsState.CipherSuite) { // "Endpoints MAY choose to generate a connection error // (Section 5.4.1) of type INADEQUATE_SECURITY if one of // the prohibited cipher suites are negotiated." @@ -569,7 +544,7 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon opts.UpgradeRequest = nil } - sc.serve() + sc.serve(conf) } func serverConnBaseContext(c net.Conn, opts *ServeConnOpts) (ctx context.Context, cancel func()) { @@ -609,6 +584,7 @@ type serverConn struct { tlsState *tls.ConnectionState // shared by all handlers, like net/http remoteAddrStr string writeSched WriteScheduler + countErrorFunc func(errType string) // Everything following is owned by the serve loop; use serveG.check(): serveG goroutineLock // used to verify funcs are on serve() @@ -628,6 +604,7 @@ type serverConn struct { streams map[uint32]*stream unstartedHandlers []unstartedHandler initialStreamSendWindowSize int32 + initialStreamRecvWindowSize int32 maxFrameSize int32 peerMaxHeaderListSize uint32 // zero means unknown (default) canonHeader map[string]string // http2-lower-case -> Go-Canonical-Case @@ -638,9 +615,14 @@ type serverConn struct { inGoAway bool // we've started to or sent GOAWAY inFrameScheduleLoop bool // whether we're in the scheduleFrameWrite loop needToSendGoAway bool // we need to schedule a GOAWAY frame write + pingSent bool + sentPingData [8]byte goAwayCode ErrCode shutdownTimer timer // nil until used idleTimer timer // nil if unused + readIdleTimeout time.Duration + pingTimeout time.Duration + readIdleTimer timer // nil if unused // Owned by the writeFrameAsync goroutine: headerWriteBuf bytes.Buffer @@ -655,11 +637,7 @@ func (sc *serverConn) maxHeaderListSize() uint32 { if n <= 0 { n = http.DefaultMaxHeaderBytes } - // http2's count is in a slightly different unit and includes 32 bytes per pair. - // So, take the net/http.Server value and pad it up a bit, assuming 10 headers. - const perFieldOverhead = 32 // per http2 spec - const typicalHeaders = 10 // conservative - return uint32(n + typicalHeaders*perFieldOverhead) + return uint32(adjustHTTP1MaxHeaderSize(int64(n))) } func (sc *serverConn) curOpenStreams() uint32 { @@ -923,7 +901,7 @@ func (sc *serverConn) notePanic() { } } -func (sc *serverConn) serve() { +func (sc *serverConn) serve(conf http2Config) { sc.serveG.check() defer sc.notePanic() defer sc.conn.Close() @@ -937,18 +915,18 @@ func (sc *serverConn) serve() { sc.writeFrame(FrameWriteRequest{ write: writeSettings{ - {SettingMaxFrameSize, sc.srv.maxReadFrameSize()}, + {SettingMaxFrameSize, conf.MaxReadFrameSize}, {SettingMaxConcurrentStreams, sc.advMaxStreams}, {SettingMaxHeaderListSize, sc.maxHeaderListSize()}, - {SettingHeaderTableSize, sc.srv.maxDecoderHeaderTableSize()}, - {SettingInitialWindowSize, uint32(sc.srv.initialStreamRecvWindowSize())}, + {SettingHeaderTableSize, conf.MaxDecoderHeaderTableSize}, + {SettingInitialWindowSize, uint32(sc.initialStreamRecvWindowSize)}, }, }) sc.unackedSettings++ // Each connection starts with initialWindowSize inflow tokens. // If a higher value is configured, we add more tokens. - if diff := sc.srv.initialConnRecvWindowSize() - initialWindowSize; diff > 0 { + if diff := conf.MaxUploadBufferPerConnection - initialWindowSize; diff > 0 { sc.sendWindowUpdate(nil, int(diff)) } @@ -968,11 +946,18 @@ func (sc *serverConn) serve() { defer sc.idleTimer.Stop() } + if conf.SendPingTimeout > 0 { + sc.readIdleTimeout = conf.SendPingTimeout + sc.readIdleTimer = sc.srv.afterFunc(conf.SendPingTimeout, sc.onReadIdleTimer) + defer sc.readIdleTimer.Stop() + } + go sc.readFrames() // closed by defer sc.conn.Close above settingsTimer := sc.srv.afterFunc(firstSettingsTimeout, sc.onSettingsTimer) defer settingsTimer.Stop() + lastFrameTime := sc.srv.now() loopNum := 0 for { loopNum++ @@ -986,6 +971,7 @@ func (sc *serverConn) serve() { case res := <-sc.wroteFrameCh: sc.wroteFrame(res) case res := <-sc.readFrameCh: + lastFrameTime = sc.srv.now() // Process any written frames before reading new frames from the client since a // written frame could have triggered a new stream to be started. if sc.writingFrameAsync { @@ -1017,6 +1003,8 @@ func (sc *serverConn) serve() { case idleTimerMsg: sc.vlogf("connection is idle") sc.goAway(ErrCodeNo) + case readIdleTimerMsg: + sc.handlePingTimer(lastFrameTime) case shutdownTimerMsg: sc.vlogf("GOAWAY close timer fired; closing conn from %v", sc.conn.RemoteAddr()) return @@ -1039,7 +1027,7 @@ func (sc *serverConn) serve() { // If the peer is causing us to generate a lot of control frames, // but not reading them from us, assume they are trying to make us // run out of memory. - if sc.queuedControlFrames > sc.srv.maxQueuedControlFrames() { + if sc.queuedControlFrames > maxQueuedControlFrames { sc.vlogf("http2: too many control frames in send queue, closing connection") return } @@ -1055,12 +1043,39 @@ func (sc *serverConn) serve() { } } +func (sc *serverConn) handlePingTimer(lastFrameReadTime time.Time) { + if sc.pingSent { + sc.vlogf("timeout waiting for PING response") + sc.conn.Close() + return + } + + pingAt := lastFrameReadTime.Add(sc.readIdleTimeout) + now := sc.srv.now() + if pingAt.After(now) { + // We received frames since arming the ping timer. + // Reset it for the next possible timeout. + sc.readIdleTimer.Reset(pingAt.Sub(now)) + return + } + + sc.pingSent = true + // Ignore crypto/rand.Read errors: It generally can't fail, and worse case if it does + // is we send a PING frame containing 0s. + _, _ = rand.Read(sc.sentPingData[:]) + sc.writeFrame(FrameWriteRequest{ + write: &writePing{data: sc.sentPingData}, + }) + sc.readIdleTimer.Reset(sc.pingTimeout) +} + type serverMessage int // Message values sent to serveMsgCh. var ( settingsTimerMsg = new(serverMessage) idleTimerMsg = new(serverMessage) + readIdleTimerMsg = new(serverMessage) shutdownTimerMsg = new(serverMessage) gracefulShutdownMsg = new(serverMessage) handlerDoneMsg = new(serverMessage) @@ -1068,6 +1083,7 @@ var ( func (sc *serverConn) onSettingsTimer() { sc.sendServeMsg(settingsTimerMsg) } func (sc *serverConn) onIdleTimer() { sc.sendServeMsg(idleTimerMsg) } +func (sc *serverConn) onReadIdleTimer() { sc.sendServeMsg(readIdleTimerMsg) } func (sc *serverConn) onShutdownTimer() { sc.sendServeMsg(shutdownTimerMsg) } func (sc *serverConn) sendServeMsg(msg interface{}) { @@ -1320,6 +1336,10 @@ func (sc *serverConn) wroteFrame(res frameWriteResult) { sc.writingFrame = false sc.writingFrameAsync = false + if res.err != nil { + sc.conn.Close() + } + wr := res.wr if writeEndsStream(wr.write) { @@ -1594,6 +1614,11 @@ func (sc *serverConn) processFrame(f Frame) error { func (sc *serverConn) processPing(f *PingFrame) error { sc.serveG.check() if f.IsAck() { + if sc.pingSent && sc.sentPingData == f.Data { + // This is a response to a PING we sent. + sc.pingSent = false + sc.readIdleTimer.Reset(sc.readIdleTimeout) + } // 6.7 PING: " An endpoint MUST NOT respond to PING frames // containing this flag." return nil @@ -2160,7 +2185,7 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream st.cw.Init() st.flow.conn = &sc.flow // link to conn-level counter st.flow.add(sc.initialStreamSendWindowSize) - st.inflow.init(sc.srv.initialStreamRecvWindowSize()) + st.inflow.init(sc.initialStreamRecvWindowSize) if sc.hs.WriteTimeout > 0 { st.writeDeadline = sc.srv.afterFunc(sc.hs.WriteTimeout, st.onWriteTimeout) } @@ -3301,7 +3326,7 @@ func (sc *serverConn) countError(name string, err error) error { if sc == nil || sc.srv == nil { return err } - f := sc.srv.CountError + f := sc.countErrorFunc if f == nil { return err } diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go index 61f511f9..0c5f64aa 100644 --- a/vendor/golang.org/x/net/http2/transport.go +++ b/vendor/golang.org/x/net/http2/transport.go @@ -25,7 +25,6 @@ import ( "net/http" "net/http/httptrace" "net/textproto" - "os" "sort" "strconv" "strings" @@ -227,40 +226,26 @@ func (t *Transport) contextWithTimeout(ctx context.Context, d time.Duration) (co } func (t *Transport) maxHeaderListSize() uint32 { - if t.MaxHeaderListSize == 0 { + n := int64(t.MaxHeaderListSize) + if t.t1 != nil && t.t1.MaxResponseHeaderBytes != 0 { + n = t.t1.MaxResponseHeaderBytes + if n > 0 { + n = adjustHTTP1MaxHeaderSize(n) + } + } + if n <= 0 { return 10 << 20 } - if t.MaxHeaderListSize == 0xffffffff { + if n >= 0xffffffff { return 0 } - return t.MaxHeaderListSize -} - -func (t *Transport) maxFrameReadSize() uint32 { - if t.MaxReadFrameSize == 0 { - return 0 // use the default provided by the peer - } - if t.MaxReadFrameSize < minMaxFrameSize { - return minMaxFrameSize - } - if t.MaxReadFrameSize > maxFrameSize { - return maxFrameSize - } - return t.MaxReadFrameSize + return uint32(n) } func (t *Transport) disableCompression() bool { return t.DisableCompression || (t.t1 != nil && t.t1.DisableCompression) } -func (t *Transport) pingTimeout() time.Duration { - if t.PingTimeout == 0 { - return 15 * time.Second - } - return t.PingTimeout - -} - // ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2. // It returns an error if t1 has already been HTTP/2-enabled. // @@ -370,11 +355,14 @@ type ClientConn struct { lastActive time.Time lastIdle time.Time // time last idle // Settings from peer: (also guarded by wmu) - maxFrameSize uint32 - maxConcurrentStreams uint32 - peerMaxHeaderListSize uint64 - peerMaxHeaderTableSize uint32 - initialWindowSize uint32 + maxFrameSize uint32 + maxConcurrentStreams uint32 + peerMaxHeaderListSize uint64 + peerMaxHeaderTableSize uint32 + initialWindowSize uint32 + initialStreamRecvWindowSize int32 + readIdleTimeout time.Duration + pingTimeout time.Duration // reqHeaderMu is a 1-element semaphore channel controlling access to sending new requests. // Write to reqHeaderMu to lock it, read from it to unlock. @@ -499,6 +487,7 @@ func (cs *clientStream) closeReqBodyLocked() { } type stickyErrWriter struct { + group synctestGroupInterface conn net.Conn timeout time.Duration err *error @@ -508,22 +497,9 @@ func (sew stickyErrWriter) Write(p []byte) (n int, err error) { if *sew.err != nil { return 0, *sew.err } - for { - if sew.timeout != 0 { - sew.conn.SetWriteDeadline(time.Now().Add(sew.timeout)) - } - nn, err := sew.conn.Write(p[n:]) - n += nn - if n < len(p) && nn > 0 && errors.Is(err, os.ErrDeadlineExceeded) { - // Keep extending the deadline so long as we're making progress. - continue - } - if sew.timeout != 0 { - sew.conn.SetWriteDeadline(time.Time{}) - } - *sew.err = err - return n, err - } + n, err = writeWithByteTimeout(sew.group, sew.conn, sew.timeout, p) + *sew.err = err + return n, err } // noCachedConnError is the concrete type of ErrNoCachedConn, which @@ -758,44 +734,36 @@ func (t *Transport) expectContinueTimeout() time.Duration { return t.t1.ExpectContinueTimeout } -func (t *Transport) maxDecoderHeaderTableSize() uint32 { - if v := t.MaxDecoderHeaderTableSize; v > 0 { - return v - } - return initialHeaderTableSize -} - -func (t *Transport) maxEncoderHeaderTableSize() uint32 { - if v := t.MaxEncoderHeaderTableSize; v > 0 { - return v - } - return initialHeaderTableSize -} - func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) { return t.newClientConn(c, t.disableKeepAlives()) } func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, error) { + conf := configFromTransport(t) cc := &ClientConn{ - t: t, - tconn: c, - readerDone: make(chan struct{}), - nextStreamID: 1, - maxFrameSize: 16 << 10, // spec default - initialWindowSize: 65535, // spec default - maxConcurrentStreams: initialMaxConcurrentStreams, // "infinite", per spec. Use a smaller value until we have received server settings. - peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead. - streams: make(map[uint32]*clientStream), - singleUse: singleUse, - wantSettingsAck: true, - pings: make(map[[8]byte]chan struct{}), - reqHeaderMu: make(chan struct{}, 1), - } + t: t, + tconn: c, + readerDone: make(chan struct{}), + nextStreamID: 1, + maxFrameSize: 16 << 10, // spec default + initialWindowSize: 65535, // spec default + initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream, + maxConcurrentStreams: initialMaxConcurrentStreams, // "infinite", per spec. Use a smaller value until we have received server settings. + peerMaxHeaderListSize: 0xffffffffffffffff, // "infinite", per spec. Use 2^64-1 instead. + streams: make(map[uint32]*clientStream), + singleUse: singleUse, + wantSettingsAck: true, + readIdleTimeout: conf.SendPingTimeout, + pingTimeout: conf.PingTimeout, + pings: make(map[[8]byte]chan struct{}), + reqHeaderMu: make(chan struct{}, 1), + } + var group synctestGroupInterface if t.transportTestHooks != nil { t.markNewGoroutine() t.transportTestHooks.newclientconn(cc) c = cc.tconn + group = t.group } if VerboseLogs { t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr()) @@ -807,24 +775,23 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro // TODO: adjust this writer size to account for frame size + // MTU + crypto/tls record padding. cc.bw = bufio.NewWriter(stickyErrWriter{ + group: group, conn: c, - timeout: t.WriteByteTimeout, + timeout: conf.WriteByteTimeout, err: &cc.werr, }) cc.br = bufio.NewReader(c) cc.fr = NewFramer(cc.bw, cc.br) - if t.maxFrameReadSize() != 0 { - cc.fr.SetMaxReadFrameSize(t.maxFrameReadSize()) - } + cc.fr.SetMaxReadFrameSize(conf.MaxReadFrameSize) if t.CountError != nil { cc.fr.countError = t.CountError } - maxHeaderTableSize := t.maxDecoderHeaderTableSize() + maxHeaderTableSize := conf.MaxDecoderHeaderTableSize cc.fr.ReadMetaHeaders = hpack.NewDecoder(maxHeaderTableSize, nil) cc.fr.MaxHeaderListSize = t.maxHeaderListSize() cc.henc = hpack.NewEncoder(&cc.hbuf) - cc.henc.SetMaxDynamicTableSizeLimit(t.maxEncoderHeaderTableSize()) + cc.henc.SetMaxDynamicTableSizeLimit(conf.MaxEncoderHeaderTableSize) cc.peerMaxHeaderTableSize = initialHeaderTableSize if cs, ok := c.(connectionStater); ok { @@ -834,11 +801,9 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro initialSettings := []Setting{ {ID: SettingEnablePush, Val: 0}, - {ID: SettingInitialWindowSize, Val: transportDefaultStreamFlow}, - } - if max := t.maxFrameReadSize(); max != 0 { - initialSettings = append(initialSettings, Setting{ID: SettingMaxFrameSize, Val: max}) + {ID: SettingInitialWindowSize, Val: uint32(cc.initialStreamRecvWindowSize)}, } + initialSettings = append(initialSettings, Setting{ID: SettingMaxFrameSize, Val: conf.MaxReadFrameSize}) if max := t.maxHeaderListSize(); max != 0 { initialSettings = append(initialSettings, Setting{ID: SettingMaxHeaderListSize, Val: max}) } @@ -848,8 +813,8 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro cc.bw.Write(clientPreface) cc.fr.WriteSettings(initialSettings...) - cc.fr.WriteWindowUpdate(0, transportDefaultConnFlow) - cc.inflow.init(transportDefaultConnFlow + initialWindowSize) + cc.fr.WriteWindowUpdate(0, uint32(conf.MaxUploadBufferPerConnection)) + cc.inflow.init(conf.MaxUploadBufferPerConnection + initialWindowSize) cc.bw.Flush() if cc.werr != nil { cc.Close() @@ -867,7 +832,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro } func (cc *ClientConn) healthCheck() { - pingTimeout := cc.t.pingTimeout() + pingTimeout := cc.pingTimeout // We don't need to periodically ping in the health check, because the readLoop of ClientConn will // trigger the healthCheck again if there is no frame received. ctx, cancel := cc.t.contextWithTimeout(context.Background(), pingTimeout) @@ -2199,7 +2164,7 @@ type resAndError struct { func (cc *ClientConn) addStreamLocked(cs *clientStream) { cs.flow.add(int32(cc.initialWindowSize)) cs.flow.setConnFlow(&cc.flow) - cs.inflow.init(transportDefaultStreamFlow) + cs.inflow.init(cc.initialStreamRecvWindowSize) cs.ID = cc.nextStreamID cc.nextStreamID += 2 cc.streams[cs.ID] = cs @@ -2345,7 +2310,7 @@ func (cc *ClientConn) countReadFrameError(err error) { func (rl *clientConnReadLoop) run() error { cc := rl.cc gotSettings := false - readIdleTimeout := cc.t.ReadIdleTimeout + readIdleTimeout := cc.readIdleTimeout var t timer if readIdleTimeout != 0 { t = cc.t.afterFunc(readIdleTimeout, cc.healthCheck) diff --git a/vendor/golang.org/x/net/http2/write.go b/vendor/golang.org/x/net/http2/write.go index 33f61398..6ff6bee7 100644 --- a/vendor/golang.org/x/net/http2/write.go +++ b/vendor/golang.org/x/net/http2/write.go @@ -131,6 +131,16 @@ func (se StreamError) writeFrame(ctx writeContext) error { func (se StreamError) staysWithinBuffer(max int) bool { return frameHeaderLen+4 <= max } +type writePing struct { + data [8]byte +} + +func (w writePing) writeFrame(ctx writeContext) error { + return ctx.Framer().WritePing(false, w.data) +} + +func (w writePing) staysWithinBuffer(max int) bool { return frameHeaderLen+len(w.data) <= max } + type writePingAck struct{ pf *PingFrame } func (w writePingAck) writeFrame(ctx writeContext) error { diff --git a/vendor/golang.org/x/sys/cpu/cpu.go b/vendor/golang.org/x/sys/cpu/cpu.go index ec07aab0..02609d5b 100644 --- a/vendor/golang.org/x/sys/cpu/cpu.go +++ b/vendor/golang.org/x/sys/cpu/cpu.go @@ -201,6 +201,25 @@ var S390X struct { _ CacheLinePad } +// RISCV64 contains the supported CPU features and performance characteristics for riscv64 +// platforms. The booleans in RISCV64, with the exception of HasFastMisaligned, indicate +// the presence of RISC-V extensions. +// +// It is safe to assume that all the RV64G extensions are supported and so they are omitted from +// this structure. As riscv64 Go programs require at least RV64G, the code that populates +// this structure cannot run successfully if some of the RV64G extensions are missing. +// The struct is padded to avoid false sharing. +var RISCV64 struct { + _ CacheLinePad + HasFastMisaligned bool // Fast misaligned accesses + HasC bool // Compressed instruction-set extension + HasV bool // Vector extension compatible with RVV 1.0 + HasZba bool // Address generation instructions extension + HasZbb bool // Basic bit-manipulation extension + HasZbs bool // Single-bit instructions extension + _ CacheLinePad +} + func init() { archInit() initOptions() diff --git a/vendor/golang.org/x/sys/cpu/cpu_linux_noinit.go b/vendor/golang.org/x/sys/cpu/cpu_linux_noinit.go index cd63e733..7d902b68 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_linux_noinit.go +++ b/vendor/golang.org/x/sys/cpu/cpu_linux_noinit.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && !arm && !arm64 && !mips64 && !mips64le && !ppc64 && !ppc64le && !s390x +//go:build linux && !arm && !arm64 && !mips64 && !mips64le && !ppc64 && !ppc64le && !s390x && !riscv64 package cpu diff --git a/vendor/golang.org/x/sys/cpu/cpu_linux_riscv64.go b/vendor/golang.org/x/sys/cpu/cpu_linux_riscv64.go new file mode 100644 index 00000000..cb4a0c57 --- /dev/null +++ b/vendor/golang.org/x/sys/cpu/cpu_linux_riscv64.go @@ -0,0 +1,137 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +import ( + "syscall" + "unsafe" +) + +// RISC-V extension discovery code for Linux. The approach here is to first try the riscv_hwprobe +// syscall falling back to HWCAP to check for the C extension if riscv_hwprobe is not available. +// +// A note on detection of the Vector extension using HWCAP. +// +// Support for the Vector extension version 1.0 was added to the Linux kernel in release 6.5. +// Support for the riscv_hwprobe syscall was added in 6.4. It follows that if the riscv_hwprobe +// syscall is not available then neither is the Vector extension (which needs kernel support). +// The riscv_hwprobe syscall should then be all we need to detect the Vector extension. +// However, some RISC-V board manufacturers ship boards with an older kernel on top of which +// they have back-ported various versions of the Vector extension patches but not the riscv_hwprobe +// patches. These kernels advertise support for the Vector extension using HWCAP. Falling +// back to HWCAP to detect the Vector extension, if riscv_hwprobe is not available, or simply not +// bothering with riscv_hwprobe at all and just using HWCAP may then seem like an attractive option. +// +// Unfortunately, simply checking the 'V' bit in AT_HWCAP will not work as this bit is used by +// RISC-V board and cloud instance providers to mean different things. The Lichee Pi 4A board +// and the Scaleway RV1 cloud instances use the 'V' bit to advertise their support for the unratified +// 0.7.1 version of the Vector Specification. The Banana Pi BPI-F3 and the CanMV-K230 board use +// it to advertise support for 1.0 of the Vector extension. Versions 0.7.1 and 1.0 of the Vector +// extension are binary incompatible. HWCAP can then not be used in isolation to populate the +// HasV field as this field indicates that the underlying CPU is compatible with RVV 1.0. +// +// There is a way at runtime to distinguish between versions 0.7.1 and 1.0 of the Vector +// specification by issuing a RVV 1.0 vsetvli instruction and checking the vill bit of the vtype +// register. This check would allow us to safely detect version 1.0 of the Vector extension +// with HWCAP, if riscv_hwprobe were not available. However, the check cannot +// be added until the assembler supports the Vector instructions. +// +// Note the riscv_hwprobe syscall does not suffer from these ambiguities by design as all of the +// extensions it advertises support for are explicitly versioned. It's also worth noting that +// the riscv_hwprobe syscall is the only way to detect multi-letter RISC-V extensions, e.g., Zba. +// These cannot be detected using HWCAP and so riscv_hwprobe must be used to detect the majority +// of RISC-V extensions. +// +// Please see https://docs.kernel.org/arch/riscv/hwprobe.html for more information. + +// golang.org/x/sys/cpu is not allowed to depend on golang.org/x/sys/unix so we must +// reproduce the constants, types and functions needed to make the riscv_hwprobe syscall +// here. + +const ( + // Copied from golang.org/x/sys/unix/ztypes_linux_riscv64.go. + riscv_HWPROBE_KEY_IMA_EXT_0 = 0x4 + riscv_HWPROBE_IMA_C = 0x2 + riscv_HWPROBE_IMA_V = 0x4 + riscv_HWPROBE_EXT_ZBA = 0x8 + riscv_HWPROBE_EXT_ZBB = 0x10 + riscv_HWPROBE_EXT_ZBS = 0x20 + riscv_HWPROBE_KEY_CPUPERF_0 = 0x5 + riscv_HWPROBE_MISALIGNED_FAST = 0x3 + riscv_HWPROBE_MISALIGNED_MASK = 0x7 +) + +const ( + // sys_RISCV_HWPROBE is copied from golang.org/x/sys/unix/zsysnum_linux_riscv64.go. + sys_RISCV_HWPROBE = 258 +) + +// riscvHWProbePairs is copied from golang.org/x/sys/unix/ztypes_linux_riscv64.go. +type riscvHWProbePairs struct { + key int64 + value uint64 +} + +const ( + // CPU features + hwcap_RISCV_ISA_C = 1 << ('C' - 'A') +) + +func doinit() { + // A slice of key/value pair structures is passed to the RISCVHWProbe syscall. The key + // field should be initialised with one of the key constants defined above, e.g., + // RISCV_HWPROBE_KEY_IMA_EXT_0. The syscall will set the value field to the appropriate value. + // If the kernel does not recognise a key it will set the key field to -1 and the value field to 0. + + pairs := []riscvHWProbePairs{ + {riscv_HWPROBE_KEY_IMA_EXT_0, 0}, + {riscv_HWPROBE_KEY_CPUPERF_0, 0}, + } + + // This call only indicates that extensions are supported if they are implemented on all cores. + if riscvHWProbe(pairs, 0) { + if pairs[0].key != -1 { + v := uint(pairs[0].value) + RISCV64.HasC = isSet(v, riscv_HWPROBE_IMA_C) + RISCV64.HasV = isSet(v, riscv_HWPROBE_IMA_V) + RISCV64.HasZba = isSet(v, riscv_HWPROBE_EXT_ZBA) + RISCV64.HasZbb = isSet(v, riscv_HWPROBE_EXT_ZBB) + RISCV64.HasZbs = isSet(v, riscv_HWPROBE_EXT_ZBS) + } + if pairs[1].key != -1 { + v := pairs[1].value & riscv_HWPROBE_MISALIGNED_MASK + RISCV64.HasFastMisaligned = v == riscv_HWPROBE_MISALIGNED_FAST + } + } + + // Let's double check with HWCAP if the C extension does not appear to be supported. + // This may happen if we're running on a kernel older than 6.4. + + if !RISCV64.HasC { + RISCV64.HasC = isSet(hwCap, hwcap_RISCV_ISA_C) + } +} + +func isSet(hwc uint, value uint) bool { + return hwc&value != 0 +} + +// riscvHWProbe is a simplified version of the generated wrapper function found in +// golang.org/x/sys/unix/zsyscall_linux_riscv64.go. We simplify it by removing the +// cpuCount and cpus parameters which we do not need. We always want to pass 0 for +// these parameters here so the kernel only reports the extensions that are present +// on all cores. +func riscvHWProbe(pairs []riscvHWProbePairs, flags uint) bool { + var _zero uintptr + var p0 unsafe.Pointer + if len(pairs) > 0 { + p0 = unsafe.Pointer(&pairs[0]) + } else { + p0 = unsafe.Pointer(&_zero) + } + + _, _, e1 := syscall.Syscall6(sys_RISCV_HWPROBE, uintptr(p0), uintptr(len(pairs)), uintptr(0), uintptr(0), uintptr(flags), 0) + return e1 == 0 +} diff --git a/vendor/golang.org/x/sys/cpu/cpu_riscv64.go b/vendor/golang.org/x/sys/cpu/cpu_riscv64.go index 7f0c79c0..aca3199c 100644 --- a/vendor/golang.org/x/sys/cpu/cpu_riscv64.go +++ b/vendor/golang.org/x/sys/cpu/cpu_riscv64.go @@ -8,4 +8,13 @@ package cpu const cacheLineSize = 64 -func initOptions() {} +func initOptions() { + options = []option{ + {Name: "fastmisaligned", Feature: &RISCV64.HasFastMisaligned}, + {Name: "c", Feature: &RISCV64.HasC}, + {Name: "v", Feature: &RISCV64.HasV}, + {Name: "zba", Feature: &RISCV64.HasZba}, + {Name: "zbb", Feature: &RISCV64.HasZbb}, + {Name: "zbs", Feature: &RISCV64.HasZbs}, + } +} diff --git a/vendor/golang.org/x/sys/unix/README.md b/vendor/golang.org/x/sys/unix/README.md index 7d3c060e..6e08a76a 100644 --- a/vendor/golang.org/x/sys/unix/README.md +++ b/vendor/golang.org/x/sys/unix/README.md @@ -156,7 +156,7 @@ from the generated architecture-specific files listed below, and merge these into a common file for each OS. The merge is performed in the following steps: -1. Construct the set of common code that is idential in all architecture-specific files. +1. Construct the set of common code that is identical in all architecture-specific files. 2. Write this common code to the merged file. 3. Remove the common code from all architecture-specific files. diff --git a/vendor/golang.org/x/sys/unix/mkerrors.sh b/vendor/golang.org/x/sys/unix/mkerrors.sh index d07dd09e..ac54ecab 100644 --- a/vendor/golang.org/x/sys/unix/mkerrors.sh +++ b/vendor/golang.org/x/sys/unix/mkerrors.sh @@ -552,6 +552,7 @@ ccflags="$@" $2 !~ /^RTC_VL_(ACCURACY|BACKUP|DATA)/ && $2 ~ /^(NETLINK|NLM|NLMSG|NLA|IFA|IFAN|RT|RTC|RTCF|RTN|RTPROT|RTNH|ARPHRD|ETH_P|NETNSA)_/ || $2 ~ /^SOCK_|SK_DIAG_|SKNLGRP_$/ || + $2 ~ /^(CONNECT|SAE)_/ || $2 ~ /^FIORDCHK$/ || $2 ~ /^SIOC/ || $2 ~ /^TIOC/ || @@ -655,7 +656,7 @@ errors=$( signals=$( echo '#include ' | $CC -x c - -E -dM $ccflags | awk '$1=="#define" && $2 ~ /^SIG[A-Z0-9]+$/ { print $2 }' | - grep -v 'SIGSTKSIZE\|SIGSTKSZ\|SIGRT\|SIGMAX64' | + grep -E -v '(SIGSTKSIZE|SIGSTKSZ|SIGRT|SIGMAX64)' | sort ) @@ -665,7 +666,7 @@ echo '#include ' | $CC -x c - -E -dM $ccflags | sort >_error.grep echo '#include ' | $CC -x c - -E -dM $ccflags | awk '$1=="#define" && $2 ~ /^SIG[A-Z0-9]+$/ { print "^\t" $2 "[ \t]*=" }' | - grep -v 'SIGSTKSIZE\|SIGSTKSZ\|SIGRT\|SIGMAX64' | + grep -E -v '(SIGSTKSIZE|SIGSTKSZ|SIGRT|SIGMAX64)' | sort >_signal.grep echo '// mkerrors.sh' "$@" diff --git a/vendor/golang.org/x/sys/unix/syscall_aix.go b/vendor/golang.org/x/sys/unix/syscall_aix.go index 67ce6cef..6f15ba1e 100644 --- a/vendor/golang.org/x/sys/unix/syscall_aix.go +++ b/vendor/golang.org/x/sys/unix/syscall_aix.go @@ -360,7 +360,7 @@ func Wait4(pid int, wstatus *WaitStatus, options int, rusage *Rusage) (wpid int, var status _C_int var r Pid_t err = ERESTART - // AIX wait4 may return with ERESTART errno, while the processus is still + // AIX wait4 may return with ERESTART errno, while the process is still // active. for err == ERESTART { r, err = wait4(Pid_t(pid), &status, options, rusage) diff --git a/vendor/golang.org/x/sys/unix/syscall_darwin.go b/vendor/golang.org/x/sys/unix/syscall_darwin.go index 2d15200a..099867de 100644 --- a/vendor/golang.org/x/sys/unix/syscall_darwin.go +++ b/vendor/golang.org/x/sys/unix/syscall_darwin.go @@ -566,6 +566,43 @@ func PthreadFchdir(fd int) (err error) { return pthread_fchdir_np(fd) } +// Connectx calls connectx(2) to initiate a connection on a socket. +// +// srcIf, srcAddr, and dstAddr are filled into a [SaEndpoints] struct and passed as the endpoints argument. +// +// - srcIf is the optional source interface index. 0 means unspecified. +// - srcAddr is the optional source address. nil means unspecified. +// - dstAddr is the destination address. +// +// On success, Connectx returns the number of bytes enqueued for transmission. +func Connectx(fd int, srcIf uint32, srcAddr, dstAddr Sockaddr, associd SaeAssocID, flags uint32, iov []Iovec, connid *SaeConnID) (n uintptr, err error) { + endpoints := SaEndpoints{ + Srcif: srcIf, + } + + if srcAddr != nil { + addrp, addrlen, err := srcAddr.sockaddr() + if err != nil { + return 0, err + } + endpoints.Srcaddr = (*RawSockaddr)(addrp) + endpoints.Srcaddrlen = uint32(addrlen) + } + + if dstAddr != nil { + addrp, addrlen, err := dstAddr.sockaddr() + if err != nil { + return 0, err + } + endpoints.Dstaddr = (*RawSockaddr)(addrp) + endpoints.Dstaddrlen = uint32(addrlen) + } + + err = connectx(fd, &endpoints, associd, flags, iov, &n, connid) + return +} + +//sys connectx(fd int, endpoints *SaEndpoints, associd SaeAssocID, flags uint32, iov []Iovec, n *uintptr, connid *SaeConnID) (err error) //sys sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) //sys shmat(id int, addr uintptr, flag int) (ret uintptr, err error) diff --git a/vendor/golang.org/x/sys/unix/syscall_hurd.go b/vendor/golang.org/x/sys/unix/syscall_hurd.go index ba46651f..a6a2d2fc 100644 --- a/vendor/golang.org/x/sys/unix/syscall_hurd.go +++ b/vendor/golang.org/x/sys/unix/syscall_hurd.go @@ -11,6 +11,7 @@ package unix int ioctl(int, unsigned long int, uintptr_t); */ import "C" +import "unsafe" func ioctl(fd int, req uint, arg uintptr) (err error) { r0, er := C.ioctl(C.int(fd), C.ulong(req), C.uintptr_t(arg)) diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go index 3f1d3d4c..f08abd43 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux.go @@ -1295,6 +1295,48 @@ func GetsockoptTCPInfo(fd, level, opt int) (*TCPInfo, error) { return &value, err } +// GetsockoptTCPCCVegasInfo returns algorithm specific congestion control information for a socket using the "vegas" +// algorithm. +// +// The socket's congestion control algorighm can be retrieved via [GetsockoptString] with the [TCP_CONGESTION] option: +// +// algo, err := unix.GetsockoptString(fd, unix.IPPROTO_TCP, unix.TCP_CONGESTION) +func GetsockoptTCPCCVegasInfo(fd, level, opt int) (*TCPVegasInfo, error) { + var value [SizeofTCPCCInfo / 4]uint32 // ensure proper alignment + vallen := _Socklen(SizeofTCPCCInfo) + err := getsockopt(fd, level, opt, unsafe.Pointer(&value[0]), &vallen) + out := (*TCPVegasInfo)(unsafe.Pointer(&value[0])) + return out, err +} + +// GetsockoptTCPCCDCTCPInfo returns algorithm specific congestion control information for a socket using the "dctp" +// algorithm. +// +// The socket's congestion control algorighm can be retrieved via [GetsockoptString] with the [TCP_CONGESTION] option: +// +// algo, err := unix.GetsockoptString(fd, unix.IPPROTO_TCP, unix.TCP_CONGESTION) +func GetsockoptTCPCCDCTCPInfo(fd, level, opt int) (*TCPDCTCPInfo, error) { + var value [SizeofTCPCCInfo / 4]uint32 // ensure proper alignment + vallen := _Socklen(SizeofTCPCCInfo) + err := getsockopt(fd, level, opt, unsafe.Pointer(&value[0]), &vallen) + out := (*TCPDCTCPInfo)(unsafe.Pointer(&value[0])) + return out, err +} + +// GetsockoptTCPCCBBRInfo returns algorithm specific congestion control information for a socket using the "bbr" +// algorithm. +// +// The socket's congestion control algorighm can be retrieved via [GetsockoptString] with the [TCP_CONGESTION] option: +// +// algo, err := unix.GetsockoptString(fd, unix.IPPROTO_TCP, unix.TCP_CONGESTION) +func GetsockoptTCPCCBBRInfo(fd, level, opt int) (*TCPBBRInfo, error) { + var value [SizeofTCPCCInfo / 4]uint32 // ensure proper alignment + vallen := _Socklen(SizeofTCPCCInfo) + err := getsockopt(fd, level, opt, unsafe.Pointer(&value[0]), &vallen) + out := (*TCPBBRInfo)(unsafe.Pointer(&value[0])) + return out, err +} + // GetsockoptString returns the string value of the socket option opt for the // socket associated with fd at the given socket level. func GetsockoptString(fd, level, opt int) (string, error) { @@ -1959,7 +2001,26 @@ func Getpgrp() (pid int) { //sysnb Getpid() (pid int) //sysnb Getppid() (ppid int) //sys Getpriority(which int, who int) (prio int, err error) -//sys Getrandom(buf []byte, flags int) (n int, err error) + +func Getrandom(buf []byte, flags int) (n int, err error) { + vdsoRet, supported := vgetrandom(buf, uint32(flags)) + if supported { + if vdsoRet < 0 { + return 0, errnoErr(syscall.Errno(-vdsoRet)) + } + return vdsoRet, nil + } + var p *byte + if len(buf) > 0 { + p = &buf[0] + } + r, _, e := Syscall(SYS_GETRANDOM, uintptr(unsafe.Pointer(p)), uintptr(len(buf)), uintptr(flags)) + if e != 0 { + return 0, errnoErr(e) + } + return int(r), nil +} + //sysnb Getrusage(who int, rusage *Rusage) (err error) //sysnb Getsid(pid int) (sid int, err error) //sysnb Gettid() (tid int) diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go b/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go index cf2ee6c7..745e5c7e 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go @@ -182,3 +182,5 @@ func KexecFileLoad(kernelFd int, initrdFd int, cmdline string, flags int) error } return kexecFileLoad(kernelFd, initrdFd, cmdlineLen, cmdline, flags) } + +const SYS_FSTATAT = SYS_NEWFSTATAT diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_loong64.go b/vendor/golang.org/x/sys/unix/syscall_linux_loong64.go index 3d0e9845..dd2262a4 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_loong64.go @@ -214,3 +214,5 @@ func KexecFileLoad(kernelFd int, initrdFd int, cmdline string, flags int) error } return kexecFileLoad(kernelFd, initrdFd, cmdlineLen, cmdline, flags) } + +const SYS_FSTATAT = SYS_NEWFSTATAT diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go b/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go index 6f5a2889..8cf3670b 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go @@ -187,3 +187,5 @@ func RISCVHWProbe(pairs []RISCVHWProbePairs, set *CPUSet, flags uint) (err error } return riscvHWProbe(pairs, setSize, set, flags) } + +const SYS_FSTATAT = SYS_NEWFSTATAT diff --git a/vendor/golang.org/x/sys/unix/vgetrandom_linux.go b/vendor/golang.org/x/sys/unix/vgetrandom_linux.go new file mode 100644 index 00000000..07ac8e09 --- /dev/null +++ b/vendor/golang.org/x/sys/unix/vgetrandom_linux.go @@ -0,0 +1,13 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build linux && go1.24 + +package unix + +import _ "unsafe" + +//go:linkname vgetrandom runtime.vgetrandom +//go:noescape +func vgetrandom(p []byte, flags uint32) (ret int, supported bool) diff --git a/vendor/golang.org/x/tools/internal/versions/toolchain_go119.go b/vendor/golang.org/x/sys/unix/vgetrandom_unsupported.go similarity index 56% rename from vendor/golang.org/x/tools/internal/versions/toolchain_go119.go rename to vendor/golang.org/x/sys/unix/vgetrandom_unsupported.go index f65beed9..297e97bc 100644 --- a/vendor/golang.org/x/tools/internal/versions/toolchain_go119.go +++ b/vendor/golang.org/x/sys/unix/vgetrandom_unsupported.go @@ -2,13 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build go1.19 -// +build go1.19 +//go:build !linux || !go1.24 -package versions +package unix -func init() { - if Compare(toolchain, Go1_19) < 0 { - toolchain = Go1_19 - } +func vgetrandom(p []byte, flags uint32) (ret int, supported bool) { + return -1, false } diff --git a/vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go b/vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go index 4308ac17..d73c4652 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go @@ -237,6 +237,9 @@ const ( CLOCK_UPTIME_RAW_APPROX = 0x9 CLONE_NOFOLLOW = 0x1 CLONE_NOOWNERCOPY = 0x2 + CONNECT_DATA_AUTHENTICATED = 0x4 + CONNECT_DATA_IDEMPOTENT = 0x2 + CONNECT_RESUME_ON_READ_WRITE = 0x1 CR0 = 0x0 CR1 = 0x1000 CR2 = 0x2000 @@ -1265,6 +1268,10 @@ const ( RTV_SSTHRESH = 0x20 RUSAGE_CHILDREN = -0x1 RUSAGE_SELF = 0x0 + SAE_ASSOCID_ALL = 0xffffffff + SAE_ASSOCID_ANY = 0x0 + SAE_CONNID_ALL = 0xffffffff + SAE_CONNID_ANY = 0x0 SCM_CREDS = 0x3 SCM_RIGHTS = 0x1 SCM_TIMESTAMP = 0x2 diff --git a/vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go b/vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go index c8068a7a..4a55a400 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go @@ -237,6 +237,9 @@ const ( CLOCK_UPTIME_RAW_APPROX = 0x9 CLONE_NOFOLLOW = 0x1 CLONE_NOOWNERCOPY = 0x2 + CONNECT_DATA_AUTHENTICATED = 0x4 + CONNECT_DATA_IDEMPOTENT = 0x2 + CONNECT_RESUME_ON_READ_WRITE = 0x1 CR0 = 0x0 CR1 = 0x1000 CR2 = 0x2000 @@ -1265,6 +1268,10 @@ const ( RTV_SSTHRESH = 0x20 RUSAGE_CHILDREN = -0x1 RUSAGE_SELF = 0x0 + SAE_ASSOCID_ALL = 0xffffffff + SAE_ASSOCID_ANY = 0x0 + SAE_CONNID_ALL = 0xffffffff + SAE_CONNID_ANY = 0x0 SCM_CREDS = 0x3 SCM_RIGHTS = 0x1 SCM_TIMESTAMP = 0x2 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux.go b/vendor/golang.org/x/sys/unix/zerrors_linux.go index 01a70b24..de3b4624 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux.go @@ -495,6 +495,7 @@ const ( BPF_F_TEST_REG_INVARIANTS = 0x80 BPF_F_TEST_RND_HI32 = 0x4 BPF_F_TEST_RUN_ON_CPU = 0x1 + BPF_F_TEST_SKB_CHECKSUM_COMPLETE = 0x4 BPF_F_TEST_STATE_FREQ = 0x8 BPF_F_TEST_XDP_LIVE_FRAMES = 0x2 BPF_F_XDP_DEV_BOUND_ONLY = 0x40 @@ -1922,6 +1923,7 @@ const ( MNT_EXPIRE = 0x4 MNT_FORCE = 0x1 MNT_ID_REQ_SIZE_VER0 = 0x18 + MNT_ID_REQ_SIZE_VER1 = 0x20 MODULE_INIT_COMPRESSED_FILE = 0x4 MODULE_INIT_IGNORE_MODVERSIONS = 0x1 MODULE_INIT_IGNORE_VERMAGIC = 0x2 @@ -2187,7 +2189,7 @@ const ( NFT_REG_SIZE = 0x10 NFT_REJECT_ICMPX_MAX = 0x3 NFT_RT_MAX = 0x4 - NFT_SECMARK_CTX_MAXLEN = 0x100 + NFT_SECMARK_CTX_MAXLEN = 0x1000 NFT_SET_MAXNAMELEN = 0x100 NFT_SOCKET_MAX = 0x3 NFT_TABLE_F_MASK = 0x7 @@ -2356,9 +2358,11 @@ const ( PERF_MEM_LVLNUM_IO = 0xa PERF_MEM_LVLNUM_L1 = 0x1 PERF_MEM_LVLNUM_L2 = 0x2 + PERF_MEM_LVLNUM_L2_MHB = 0x5 PERF_MEM_LVLNUM_L3 = 0x3 PERF_MEM_LVLNUM_L4 = 0x4 PERF_MEM_LVLNUM_LFB = 0xc + PERF_MEM_LVLNUM_MSC = 0x6 PERF_MEM_LVLNUM_NA = 0xf PERF_MEM_LVLNUM_PMEM = 0xe PERF_MEM_LVLNUM_RAM = 0xd @@ -2431,6 +2435,7 @@ const ( PRIO_PGRP = 0x1 PRIO_PROCESS = 0x0 PRIO_USER = 0x2 + PROCFS_IOCTL_MAGIC = 'f' PROC_SUPER_MAGIC = 0x9fa0 PROT_EXEC = 0x4 PROT_GROWSDOWN = 0x1000000 @@ -2933,11 +2938,12 @@ const ( RUSAGE_SELF = 0x0 RUSAGE_THREAD = 0x1 RWF_APPEND = 0x10 + RWF_ATOMIC = 0x40 RWF_DSYNC = 0x2 RWF_HIPRI = 0x1 RWF_NOAPPEND = 0x20 RWF_NOWAIT = 0x8 - RWF_SUPPORTED = 0x3f + RWF_SUPPORTED = 0x7f RWF_SYNC = 0x4 RWF_WRITE_LIFE_NOT_SET = 0x0 SCHED_BATCH = 0x3 @@ -3210,6 +3216,7 @@ const ( STATX_ATTR_MOUNT_ROOT = 0x2000 STATX_ATTR_NODUMP = 0x40 STATX_ATTR_VERITY = 0x100000 + STATX_ATTR_WRITE_ATOMIC = 0x400000 STATX_BASIC_STATS = 0x7ff STATX_BLOCKS = 0x400 STATX_BTIME = 0x800 @@ -3226,6 +3233,7 @@ const ( STATX_SUBVOL = 0x8000 STATX_TYPE = 0x1 STATX_UID = 0x8 + STATX_WRITE_ATOMIC = 0x10000 STATX__RESERVED = 0x80000000 SYNC_FILE_RANGE_WAIT_AFTER = 0x4 SYNC_FILE_RANGE_WAIT_BEFORE = 0x1 @@ -3624,6 +3632,7 @@ const ( XDP_UMEM_PGOFF_COMPLETION_RING = 0x180000000 XDP_UMEM_PGOFF_FILL_RING = 0x100000000 XDP_UMEM_REG = 0x4 + XDP_UMEM_TX_METADATA_LEN = 0x4 XDP_UMEM_TX_SW_CSUM = 0x2 XDP_UMEM_UNALIGNED_CHUNK_FLAG = 0x1 XDP_USE_NEED_WAKEUP = 0x8 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go index 684a5168..8aa6d77c 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go @@ -153,9 +153,14 @@ const ( NFDBITS = 0x20 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 + NS_GET_PID_FROM_PIDNS = 0x8004b706 + NS_GET_PID_IN_PIDNS = 0x8004b708 + NS_GET_TGID_FROM_PIDNS = 0x8004b707 + NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go index 61d74b59..da428f42 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go @@ -153,9 +153,14 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 + NS_GET_PID_FROM_PIDNS = 0x8004b706 + NS_GET_PID_IN_PIDNS = 0x8004b708 + NS_GET_TGID_FROM_PIDNS = 0x8004b707 + NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go index a28c9e3e..bf45bfec 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go @@ -150,9 +150,14 @@ const ( NFDBITS = 0x20 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 + NS_GET_PID_FROM_PIDNS = 0x8004b706 + NS_GET_PID_IN_PIDNS = 0x8004b708 + NS_GET_TGID_FROM_PIDNS = 0x8004b707 + NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go index ab5d1fe8..71c67162 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go @@ -154,9 +154,14 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 + NS_GET_PID_FROM_PIDNS = 0x8004b706 + NS_GET_PID_IN_PIDNS = 0x8004b708 + NS_GET_TGID_FROM_PIDNS = 0x8004b707 + NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go index c523090e..9476628f 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go @@ -154,9 +154,14 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 + NS_GET_PID_FROM_PIDNS = 0x8004b706 + NS_GET_PID_IN_PIDNS = 0x8004b708 + NS_GET_TGID_FROM_PIDNS = 0x8004b707 + NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go index 01e6ea78..b9e85f3c 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go @@ -150,9 +150,14 @@ const ( NFDBITS = 0x20 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 + NS_GET_PID_FROM_PIDNS = 0x4004b706 + NS_GET_PID_IN_PIDNS = 0x4004b708 + NS_GET_TGID_FROM_PIDNS = 0x4004b707 + NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go index 7aa610b1..a48b68a7 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go @@ -150,9 +150,14 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 + NS_GET_PID_FROM_PIDNS = 0x4004b706 + NS_GET_PID_IN_PIDNS = 0x4004b708 + NS_GET_TGID_FROM_PIDNS = 0x4004b707 + NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go index 92af771b..ea00e852 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go @@ -150,9 +150,14 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 + NS_GET_PID_FROM_PIDNS = 0x4004b706 + NS_GET_PID_IN_PIDNS = 0x4004b708 + NS_GET_TGID_FROM_PIDNS = 0x4004b707 + NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go index b27ef5e6..91c64687 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go @@ -150,9 +150,14 @@ const ( NFDBITS = 0x20 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 + NS_GET_PID_FROM_PIDNS = 0x4004b706 + NS_GET_PID_IN_PIDNS = 0x4004b708 + NS_GET_TGID_FROM_PIDNS = 0x4004b707 + NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go index 237a2cef..8cbf38d6 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go @@ -152,9 +152,14 @@ const ( NL3 = 0x300 NLDLY = 0x300 NOFLSH = 0x80000000 + NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 + NS_GET_PID_FROM_PIDNS = 0x4004b706 + NS_GET_PID_IN_PIDNS = 0x4004b708 + NS_GET_TGID_FROM_PIDNS = 0x4004b707 + NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x4 ONLCR = 0x2 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go index 4a5c555a..a2df7341 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go @@ -152,9 +152,14 @@ const ( NL3 = 0x300 NLDLY = 0x300 NOFLSH = 0x80000000 + NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 + NS_GET_PID_FROM_PIDNS = 0x4004b706 + NS_GET_PID_IN_PIDNS = 0x4004b708 + NS_GET_TGID_FROM_PIDNS = 0x4004b707 + NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x4 ONLCR = 0x2 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go index a02fb49a..24791379 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go @@ -152,9 +152,14 @@ const ( NL3 = 0x300 NLDLY = 0x300 NOFLSH = 0x80000000 + NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 + NS_GET_PID_FROM_PIDNS = 0x4004b706 + NS_GET_PID_IN_PIDNS = 0x4004b708 + NS_GET_TGID_FROM_PIDNS = 0x4004b707 + NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x4 ONLCR = 0x2 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go index e26a7c61..d265f146 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go @@ -150,9 +150,14 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 + NS_GET_PID_FROM_PIDNS = 0x8004b706 + NS_GET_PID_IN_PIDNS = 0x8004b708 + NS_GET_TGID_FROM_PIDNS = 0x8004b707 + NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go index c48f7c21..3f2d6443 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go @@ -150,9 +150,14 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x8008b705 NS_GET_NSTYPE = 0xb703 NS_GET_OWNER_UID = 0xb704 NS_GET_PARENT = 0xb702 + NS_GET_PID_FROM_PIDNS = 0x8004b706 + NS_GET_PID_IN_PIDNS = 0x8004b708 + NS_GET_TGID_FROM_PIDNS = 0x8004b707 + NS_GET_TGID_IN_PIDNS = 0x8004b709 NS_GET_USERNS = 0xb701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go index ad4b9aac..5d8b727a 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go @@ -155,9 +155,14 @@ const ( NFDBITS = 0x40 NLDLY = 0x100 NOFLSH = 0x80 + NS_GET_MNTNS_ID = 0x4008b705 NS_GET_NSTYPE = 0x2000b703 NS_GET_OWNER_UID = 0x2000b704 NS_GET_PARENT = 0x2000b702 + NS_GET_PID_FROM_PIDNS = 0x4004b706 + NS_GET_PID_IN_PIDNS = 0x4004b708 + NS_GET_TGID_FROM_PIDNS = 0x4004b707 + NS_GET_TGID_IN_PIDNS = 0x4004b709 NS_GET_USERNS = 0x2000b701 OLCUC = 0x2 ONLCR = 0x4 diff --git a/vendor/golang.org/x/sys/unix/zerrors_zos_s390x.go b/vendor/golang.org/x/sys/unix/zerrors_zos_s390x.go index da08b2ab..1ec2b140 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_zos_s390x.go +++ b/vendor/golang.org/x/sys/unix/zerrors_zos_s390x.go @@ -581,6 +581,8 @@ const ( AT_EMPTY_PATH = 0x1000 AT_REMOVEDIR = 0x200 RENAME_NOREPLACE = 1 << 0 + ST_RDONLY = 1 + ST_NOSUID = 2 ) const ( diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go index b622533e..24b346e1 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go @@ -841,6 +841,26 @@ var libc_pthread_fchdir_np_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func connectx(fd int, endpoints *SaEndpoints, associd SaeAssocID, flags uint32, iov []Iovec, n *uintptr, connid *SaeConnID) (err error) { + var _p0 unsafe.Pointer + if len(iov) > 0 { + _p0 = unsafe.Pointer(&iov[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + _, _, e1 := syscall_syscall9(libc_connectx_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(endpoints)), uintptr(associd), uintptr(flags), uintptr(_p0), uintptr(len(iov)), uintptr(unsafe.Pointer(n)), uintptr(unsafe.Pointer(connid)), 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_connectx_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_connectx connectx "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) { _, _, e1 := syscall_syscall6(libc_sendfile_trampoline_addr, uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags)) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s index cfe6646b..ebd21310 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s @@ -248,6 +248,11 @@ TEXT libc_pthread_fchdir_np_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_pthread_fchdir_np_trampoline_addr(SB), RODATA, $8 DATA ·libc_pthread_fchdir_np_trampoline_addr(SB)/8, $libc_pthread_fchdir_np_trampoline<>(SB) +TEXT libc_connectx_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_connectx(SB) +GLOBL ·libc_connectx_trampoline_addr(SB), RODATA, $8 +DATA ·libc_connectx_trampoline_addr(SB)/8, $libc_connectx_trampoline<>(SB) + TEXT libc_sendfile_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_sendfile(SB) GLOBL ·libc_sendfile_trampoline_addr(SB), RODATA, $8 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go index 13f624f6..824b9c2d 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go @@ -841,6 +841,26 @@ var libc_pthread_fchdir_np_trampoline_addr uintptr // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func connectx(fd int, endpoints *SaEndpoints, associd SaeAssocID, flags uint32, iov []Iovec, n *uintptr, connid *SaeConnID) (err error) { + var _p0 unsafe.Pointer + if len(iov) > 0 { + _p0 = unsafe.Pointer(&iov[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + _, _, e1 := syscall_syscall9(libc_connectx_trampoline_addr, uintptr(fd), uintptr(unsafe.Pointer(endpoints)), uintptr(associd), uintptr(flags), uintptr(_p0), uintptr(len(iov)), uintptr(unsafe.Pointer(n)), uintptr(unsafe.Pointer(connid)), 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +var libc_connectx_trampoline_addr uintptr + +//go:cgo_import_dynamic libc_connectx connectx "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) { _, _, e1 := syscall_syscall6(libc_sendfile_trampoline_addr, uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags)) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s index fe222b75..4f178a22 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s @@ -248,6 +248,11 @@ TEXT libc_pthread_fchdir_np_trampoline<>(SB),NOSPLIT,$0-0 GLOBL ·libc_pthread_fchdir_np_trampoline_addr(SB), RODATA, $8 DATA ·libc_pthread_fchdir_np_trampoline_addr(SB)/8, $libc_pthread_fchdir_np_trampoline<>(SB) +TEXT libc_connectx_trampoline<>(SB),NOSPLIT,$0-0 + JMP libc_connectx(SB) +GLOBL ·libc_connectx_trampoline_addr(SB), RODATA, $8 +DATA ·libc_connectx_trampoline_addr(SB)/8, $libc_connectx_trampoline<>(SB) + TEXT libc_sendfile_trampoline<>(SB),NOSPLIT,$0-0 JMP libc_sendfile(SB) GLOBL ·libc_sendfile_trampoline_addr(SB), RODATA, $8 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux.go b/vendor/golang.org/x/sys/unix/zsyscall_linux.go index 1bc1a5ad..af30da55 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux.go @@ -971,23 +971,6 @@ func Getpriority(which int, who int) (prio int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func Getrandom(buf []byte, flags int) (n int, err error) { - var _p0 unsafe.Pointer - if len(buf) > 0 { - _p0 = unsafe.Pointer(&buf[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - r0, _, e1 := Syscall(SYS_GETRANDOM, uintptr(_p0), uintptr(len(buf)), uintptr(flags)) - n = int(r0) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func Getrusage(who int, rusage *Rusage) (err error) { _, _, e1 := RawSyscall(SYS_GETRUSAGE, uintptr(who), uintptr(unsafe.Pointer(rusage)), 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go index d3e38f68..f485dbf4 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go @@ -341,6 +341,7 @@ const ( SYS_STATX = 332 SYS_IO_PGETEVENTS = 333 SYS_RSEQ = 334 + SYS_URETPROBE = 335 SYS_PIDFD_SEND_SIGNAL = 424 SYS_IO_URING_SETUP = 425 SYS_IO_URING_ENTER = 426 diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go index 6c778c23..1893e2fe 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go @@ -85,7 +85,7 @@ const ( SYS_SPLICE = 76 SYS_TEE = 77 SYS_READLINKAT = 78 - SYS_FSTATAT = 79 + SYS_NEWFSTATAT = 79 SYS_FSTAT = 80 SYS_SYNC = 81 SYS_FSYNC = 82 diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go index 37281cf5..16a4017d 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go @@ -84,6 +84,8 @@ const ( SYS_SPLICE = 76 SYS_TEE = 77 SYS_READLINKAT = 78 + SYS_NEWFSTATAT = 79 + SYS_FSTAT = 80 SYS_SYNC = 81 SYS_FSYNC = 82 SYS_FDATASYNC = 83 diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go index 9889f6a5..a5459e76 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go @@ -84,7 +84,7 @@ const ( SYS_SPLICE = 76 SYS_TEE = 77 SYS_READLINKAT = 78 - SYS_FSTATAT = 79 + SYS_NEWFSTATAT = 79 SYS_FSTAT = 80 SYS_SYNC = 81 SYS_FSYNC = 82 diff --git a/vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go b/vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go index 091d107f..d003c3d4 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go @@ -306,6 +306,19 @@ type XVSockPgen struct { type _Socklen uint32 +type SaeAssocID uint32 + +type SaeConnID uint32 + +type SaEndpoints struct { + Srcif uint32 + Srcaddr *RawSockaddr + Srcaddrlen uint32 + Dstaddr *RawSockaddr + Dstaddrlen uint32 + _ [4]byte +} + type Xucred struct { Version uint32 Uid uint32 diff --git a/vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go b/vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go index 28ff4ef7..0d45a941 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go @@ -306,6 +306,19 @@ type XVSockPgen struct { type _Socklen uint32 +type SaeAssocID uint32 + +type SaeConnID uint32 + +type SaEndpoints struct { + Srcif uint32 + Srcaddr *RawSockaddr + Srcaddrlen uint32 + Dstaddr *RawSockaddr + Dstaddrlen uint32 + _ [4]byte +} + type Xucred struct { Version uint32 Uid uint32 diff --git a/vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go b/vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go index 6cbd094a..51e13eb0 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go +++ b/vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go @@ -625,6 +625,7 @@ const ( POLLRDNORM = 0x40 POLLWRBAND = 0x100 POLLWRNORM = 0x4 + POLLRDHUP = 0x4000 ) type CapRights struct { diff --git a/vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go b/vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go index 7c03b6ee..d002d8ef 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go @@ -630,6 +630,7 @@ const ( POLLRDNORM = 0x40 POLLWRBAND = 0x100 POLLWRNORM = 0x4 + POLLRDHUP = 0x4000 ) type CapRights struct { diff --git a/vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go b/vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go index 422107ee..3f863d89 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go +++ b/vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go @@ -616,6 +616,7 @@ const ( POLLRDNORM = 0x40 POLLWRBAND = 0x100 POLLWRNORM = 0x4 + POLLRDHUP = 0x4000 ) type CapRights struct { diff --git a/vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go b/vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go index 505a12ac..61c72931 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go @@ -610,6 +610,7 @@ const ( POLLRDNORM = 0x40 POLLWRBAND = 0x100 POLLWRNORM = 0x4 + POLLRDHUP = 0x4000 ) type CapRights struct { diff --git a/vendor/golang.org/x/sys/unix/ztypes_freebsd_riscv64.go b/vendor/golang.org/x/sys/unix/ztypes_freebsd_riscv64.go index cc986c79..b5d17414 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_freebsd_riscv64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_freebsd_riscv64.go @@ -612,6 +612,7 @@ const ( POLLRDNORM = 0x40 POLLWRBAND = 0x100 POLLWRNORM = 0x4 + POLLRDHUP = 0x4000 ) type CapRights struct { diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go index 7f1961b9..3a69e454 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go @@ -87,31 +87,35 @@ type StatxTimestamp struct { } type Statx_t struct { - Mask uint32 - Blksize uint32 - Attributes uint64 - Nlink uint32 - Uid uint32 - Gid uint32 - Mode uint16 - _ [1]uint16 - Ino uint64 - Size uint64 - Blocks uint64 - Attributes_mask uint64 - Atime StatxTimestamp - Btime StatxTimestamp - Ctime StatxTimestamp - Mtime StatxTimestamp - Rdev_major uint32 - Rdev_minor uint32 - Dev_major uint32 - Dev_minor uint32 - Mnt_id uint64 - Dio_mem_align uint32 - Dio_offset_align uint32 - Subvol uint64 - _ [11]uint64 + Mask uint32 + Blksize uint32 + Attributes uint64 + Nlink uint32 + Uid uint32 + Gid uint32 + Mode uint16 + _ [1]uint16 + Ino uint64 + Size uint64 + Blocks uint64 + Attributes_mask uint64 + Atime StatxTimestamp + Btime StatxTimestamp + Ctime StatxTimestamp + Mtime StatxTimestamp + Rdev_major uint32 + Rdev_minor uint32 + Dev_major uint32 + Dev_minor uint32 + Mnt_id uint64 + Dio_mem_align uint32 + Dio_offset_align uint32 + Subvol uint64 + Atomic_write_unit_min uint32 + Atomic_write_unit_max uint32 + Atomic_write_segments_max uint32 + _ [1]uint32 + _ [9]uint64 } type Fsid struct { @@ -516,6 +520,29 @@ type TCPInfo struct { Total_rto_time uint32 } +type TCPVegasInfo struct { + Enabled uint32 + Rttcnt uint32 + Rtt uint32 + Minrtt uint32 +} + +type TCPDCTCPInfo struct { + Enabled uint16 + Ce_state uint16 + Alpha uint32 + Ab_ecn uint32 + Ab_tot uint32 +} + +type TCPBBRInfo struct { + Bw_lo uint32 + Bw_hi uint32 + Min_rtt uint32 + Pacing_gain uint32 + Cwnd_gain uint32 +} + type CanFilter struct { Id uint32 Mask uint32 @@ -557,6 +584,7 @@ const ( SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc SizeofTCPInfo = 0xf8 + SizeofTCPCCInfo = 0x14 SizeofCanFilter = 0x8 SizeofTCPRepairOpt = 0x8 ) @@ -2486,7 +2514,7 @@ type XDPMmapOffsets struct { type XDPUmemReg struct { Addr uint64 Len uint64 - Chunk_size uint32 + Size uint32 Headroom uint32 Flags uint32 Tx_metadata_len uint32 @@ -3766,7 +3794,7 @@ const ( ETHTOOL_MSG_PSE_GET = 0x24 ETHTOOL_MSG_PSE_SET = 0x25 ETHTOOL_MSG_RSS_GET = 0x26 - ETHTOOL_MSG_USER_MAX = 0x2b + ETHTOOL_MSG_USER_MAX = 0x2c ETHTOOL_MSG_KERNEL_NONE = 0x0 ETHTOOL_MSG_STRSET_GET_REPLY = 0x1 ETHTOOL_MSG_LINKINFO_GET_REPLY = 0x2 @@ -3806,7 +3834,7 @@ const ( ETHTOOL_MSG_MODULE_NTF = 0x24 ETHTOOL_MSG_PSE_GET_REPLY = 0x25 ETHTOOL_MSG_RSS_GET_REPLY = 0x26 - ETHTOOL_MSG_KERNEL_MAX = 0x2b + ETHTOOL_MSG_KERNEL_MAX = 0x2c ETHTOOL_FLAG_COMPACT_BITSETS = 0x1 ETHTOOL_FLAG_OMIT_REPLY = 0x2 ETHTOOL_FLAG_STATS = 0x4 @@ -3951,7 +3979,7 @@ const ( ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL = 0x17 ETHTOOL_A_COALESCE_USE_CQE_MODE_TX = 0x18 ETHTOOL_A_COALESCE_USE_CQE_MODE_RX = 0x19 - ETHTOOL_A_COALESCE_MAX = 0x1c + ETHTOOL_A_COALESCE_MAX = 0x1e ETHTOOL_A_PAUSE_UNSPEC = 0x0 ETHTOOL_A_PAUSE_HEADER = 0x1 ETHTOOL_A_PAUSE_AUTONEG = 0x2 @@ -4609,7 +4637,7 @@ const ( NL80211_ATTR_MAC_HINT = 0xc8 NL80211_ATTR_MAC_MASK = 0xd7 NL80211_ATTR_MAX_AP_ASSOC_STA = 0xca - NL80211_ATTR_MAX = 0x14a + NL80211_ATTR_MAX = 0x14c NL80211_ATTR_MAX_CRIT_PROT_DURATION = 0xb4 NL80211_ATTR_MAX_CSA_COUNTERS = 0xce NL80211_ATTR_MAX_MATCH_SETS = 0x85 @@ -5213,7 +5241,7 @@ const ( NL80211_FREQUENCY_ATTR_GO_CONCURRENT = 0xf NL80211_FREQUENCY_ATTR_INDOOR_ONLY = 0xe NL80211_FREQUENCY_ATTR_IR_CONCURRENT = 0xf - NL80211_FREQUENCY_ATTR_MAX = 0x20 + NL80211_FREQUENCY_ATTR_MAX = 0x21 NL80211_FREQUENCY_ATTR_MAX_TX_POWER = 0x6 NL80211_FREQUENCY_ATTR_NO_10MHZ = 0x11 NL80211_FREQUENCY_ATTR_NO_160MHZ = 0xc diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go index 15adc041..ad05b51a 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go @@ -727,6 +727,37 @@ const ( RISCV_HWPROBE_EXT_ZBA = 0x8 RISCV_HWPROBE_EXT_ZBB = 0x10 RISCV_HWPROBE_EXT_ZBS = 0x20 + RISCV_HWPROBE_EXT_ZICBOZ = 0x40 + RISCV_HWPROBE_EXT_ZBC = 0x80 + RISCV_HWPROBE_EXT_ZBKB = 0x100 + RISCV_HWPROBE_EXT_ZBKC = 0x200 + RISCV_HWPROBE_EXT_ZBKX = 0x400 + RISCV_HWPROBE_EXT_ZKND = 0x800 + RISCV_HWPROBE_EXT_ZKNE = 0x1000 + RISCV_HWPROBE_EXT_ZKNH = 0x2000 + RISCV_HWPROBE_EXT_ZKSED = 0x4000 + RISCV_HWPROBE_EXT_ZKSH = 0x8000 + RISCV_HWPROBE_EXT_ZKT = 0x10000 + RISCV_HWPROBE_EXT_ZVBB = 0x20000 + RISCV_HWPROBE_EXT_ZVBC = 0x40000 + RISCV_HWPROBE_EXT_ZVKB = 0x80000 + RISCV_HWPROBE_EXT_ZVKG = 0x100000 + RISCV_HWPROBE_EXT_ZVKNED = 0x200000 + RISCV_HWPROBE_EXT_ZVKNHA = 0x400000 + RISCV_HWPROBE_EXT_ZVKNHB = 0x800000 + RISCV_HWPROBE_EXT_ZVKSED = 0x1000000 + RISCV_HWPROBE_EXT_ZVKSH = 0x2000000 + RISCV_HWPROBE_EXT_ZVKT = 0x4000000 + RISCV_HWPROBE_EXT_ZFH = 0x8000000 + RISCV_HWPROBE_EXT_ZFHMIN = 0x10000000 + RISCV_HWPROBE_EXT_ZIHINTNTL = 0x20000000 + RISCV_HWPROBE_EXT_ZVFH = 0x40000000 + RISCV_HWPROBE_EXT_ZVFHMIN = 0x80000000 + RISCV_HWPROBE_EXT_ZFA = 0x100000000 + RISCV_HWPROBE_EXT_ZTSO = 0x200000000 + RISCV_HWPROBE_EXT_ZACAS = 0x400000000 + RISCV_HWPROBE_EXT_ZICOND = 0x800000000 + RISCV_HWPROBE_EXT_ZIHINTPAUSE = 0x1000000000 RISCV_HWPROBE_KEY_CPUPERF_0 = 0x5 RISCV_HWPROBE_MISALIGNED_UNKNOWN = 0x0 RISCV_HWPROBE_MISALIGNED_EMULATED = 0x1 @@ -734,4 +765,6 @@ const ( RISCV_HWPROBE_MISALIGNED_FAST = 0x3 RISCV_HWPROBE_MISALIGNED_UNSUPPORTED = 0x4 RISCV_HWPROBE_MISALIGNED_MASK = 0x7 + RISCV_HWPROBE_KEY_ZICBOZ_BLOCK_SIZE = 0x6 + RISCV_HWPROBE_WHICH_CPUS = 0x1 ) diff --git a/vendor/golang.org/x/sys/windows/dll_windows.go b/vendor/golang.org/x/sys/windows/dll_windows.go index 115341fb..4e613cf6 100644 --- a/vendor/golang.org/x/sys/windows/dll_windows.go +++ b/vendor/golang.org/x/sys/windows/dll_windows.go @@ -65,7 +65,7 @@ func LoadDLL(name string) (dll *DLL, err error) { return d, nil } -// MustLoadDLL is like LoadDLL but panics if load operation failes. +// MustLoadDLL is like LoadDLL but panics if load operation fails. func MustLoadDLL(name string) *DLL { d, e := LoadDLL(name) if e != nil { diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go index 1fa34fd1..5cee9a31 100644 --- a/vendor/golang.org/x/sys/windows/syscall_windows.go +++ b/vendor/golang.org/x/sys/windows/syscall_windows.go @@ -313,6 +313,10 @@ func NewCallbackCDecl(fn interface{}) uintptr { //sys SetConsoleMode(console Handle, mode uint32) (err error) = kernel32.SetConsoleMode //sys GetConsoleScreenBufferInfo(console Handle, info *ConsoleScreenBufferInfo) (err error) = kernel32.GetConsoleScreenBufferInfo //sys setConsoleCursorPosition(console Handle, position uint32) (err error) = kernel32.SetConsoleCursorPosition +//sys GetConsoleCP() (cp uint32, err error) = kernel32.GetConsoleCP +//sys GetConsoleOutputCP() (cp uint32, err error) = kernel32.GetConsoleOutputCP +//sys SetConsoleCP(cp uint32) (err error) = kernel32.SetConsoleCP +//sys SetConsoleOutputCP(cp uint32) (err error) = kernel32.SetConsoleOutputCP //sys WriteConsole(console Handle, buf *uint16, towrite uint32, written *uint32, reserved *byte) (err error) = kernel32.WriteConsoleW //sys ReadConsole(console Handle, buf *uint16, toread uint32, read *uint32, inputControl *byte) (err error) = kernel32.ReadConsoleW //sys resizePseudoConsole(pconsole Handle, size uint32) (hr error) = kernel32.ResizePseudoConsole diff --git a/vendor/golang.org/x/sys/windows/types_windows.go b/vendor/golang.org/x/sys/windows/types_windows.go index 3f03b3d5..7b97a154 100644 --- a/vendor/golang.org/x/sys/windows/types_windows.go +++ b/vendor/golang.org/x/sys/windows/types_windows.go @@ -1060,6 +1060,7 @@ const ( SIO_GET_EXTENSION_FUNCTION_POINTER = IOC_INOUT | IOC_WS2 | 6 SIO_KEEPALIVE_VALS = IOC_IN | IOC_VENDOR | 4 SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12 + SIO_UDP_NETRESET = IOC_IN | IOC_VENDOR | 15 // cf. http://support.microsoft.com/default.aspx?scid=kb;en-us;257460 diff --git a/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/zsyscall_windows.go index 9bb979a3..4c2e1bdc 100644 --- a/vendor/golang.org/x/sys/windows/zsyscall_windows.go +++ b/vendor/golang.org/x/sys/windows/zsyscall_windows.go @@ -247,7 +247,9 @@ var ( procGetCommandLineW = modkernel32.NewProc("GetCommandLineW") procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW") procGetComputerNameW = modkernel32.NewProc("GetComputerNameW") + procGetConsoleCP = modkernel32.NewProc("GetConsoleCP") procGetConsoleMode = modkernel32.NewProc("GetConsoleMode") + procGetConsoleOutputCP = modkernel32.NewProc("GetConsoleOutputCP") procGetConsoleScreenBufferInfo = modkernel32.NewProc("GetConsoleScreenBufferInfo") procGetCurrentDirectoryW = modkernel32.NewProc("GetCurrentDirectoryW") procGetCurrentProcessId = modkernel32.NewProc("GetCurrentProcessId") @@ -347,8 +349,10 @@ var ( procSetCommMask = modkernel32.NewProc("SetCommMask") procSetCommState = modkernel32.NewProc("SetCommState") procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts") + procSetConsoleCP = modkernel32.NewProc("SetConsoleCP") procSetConsoleCursorPosition = modkernel32.NewProc("SetConsoleCursorPosition") procSetConsoleMode = modkernel32.NewProc("SetConsoleMode") + procSetConsoleOutputCP = modkernel32.NewProc("SetConsoleOutputCP") procSetCurrentDirectoryW = modkernel32.NewProc("SetCurrentDirectoryW") procSetDefaultDllDirectories = modkernel32.NewProc("SetDefaultDllDirectories") procSetDllDirectoryW = modkernel32.NewProc("SetDllDirectoryW") @@ -2162,6 +2166,15 @@ func GetComputerName(buf *uint16, n *uint32) (err error) { return } +func GetConsoleCP() (cp uint32, err error) { + r0, _, e1 := syscall.Syscall(procGetConsoleCP.Addr(), 0, 0, 0, 0) + cp = uint32(r0) + if cp == 0 { + err = errnoErr(e1) + } + return +} + func GetConsoleMode(console Handle, mode *uint32) (err error) { r1, _, e1 := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(console), uintptr(unsafe.Pointer(mode)), 0) if r1 == 0 { @@ -2170,6 +2183,15 @@ func GetConsoleMode(console Handle, mode *uint32) (err error) { return } +func GetConsoleOutputCP() (cp uint32, err error) { + r0, _, e1 := syscall.Syscall(procGetConsoleOutputCP.Addr(), 0, 0, 0, 0) + cp = uint32(r0) + if cp == 0 { + err = errnoErr(e1) + } + return +} + func GetConsoleScreenBufferInfo(console Handle, info *ConsoleScreenBufferInfo) (err error) { r1, _, e1 := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(console), uintptr(unsafe.Pointer(info)), 0) if r1 == 0 { @@ -3038,6 +3060,14 @@ func SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) { return } +func SetConsoleCP(cp uint32) (err error) { + r1, _, e1 := syscall.Syscall(procSetConsoleCP.Addr(), 1, uintptr(cp), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func setConsoleCursorPosition(console Handle, position uint32) (err error) { r1, _, e1 := syscall.Syscall(procSetConsoleCursorPosition.Addr(), 2, uintptr(console), uintptr(position), 0) if r1 == 0 { @@ -3054,6 +3084,14 @@ func SetConsoleMode(console Handle, mode uint32) (err error) { return } +func SetConsoleOutputCP(cp uint32) (err error) { + r1, _, e1 := syscall.Syscall(procSetConsoleOutputCP.Addr(), 1, uintptr(cp), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func SetCurrentDirectory(path *uint16) (err error) { r1, _, e1 := syscall.Syscall(procSetCurrentDirectoryW.Addr(), 1, uintptr(unsafe.Pointer(path)), 0, 0) if r1 == 0 { diff --git a/vendor/golang.org/x/time/rate/rate.go b/vendor/golang.org/x/time/rate/rate.go index 8f6c7f49..93a798ab 100644 --- a/vendor/golang.org/x/time/rate/rate.go +++ b/vendor/golang.org/x/time/rate/rate.go @@ -99,8 +99,9 @@ func (lim *Limiter) Tokens() float64 { // bursts of at most b tokens. func NewLimiter(r Limit, b int) *Limiter { return &Limiter{ - limit: r, - burst: b, + limit: r, + burst: b, + tokens: float64(b), } } @@ -344,18 +345,6 @@ func (lim *Limiter) reserveN(t time.Time, n int, maxFutureReserve time.Duration) tokens: n, timeToAct: t, } - } else if lim.limit == 0 { - var ok bool - if lim.burst >= n { - ok = true - lim.burst -= n - } - return Reservation{ - ok: ok, - lim: lim, - tokens: lim.burst, - timeToAct: t, - } } t, tokens := lim.advance(t) diff --git a/vendor/golang.org/x/tools/go/ast/astutil/util.go b/vendor/golang.org/x/tools/go/ast/astutil/util.go index 6bdcf70a..ca71e3e1 100644 --- a/vendor/golang.org/x/tools/go/ast/astutil/util.go +++ b/vendor/golang.org/x/tools/go/ast/astutil/util.go @@ -7,13 +7,5 @@ package astutil import "go/ast" // Unparen returns e with any enclosing parentheses stripped. -// TODO(adonovan): use go1.22's ast.Unparen. -func Unparen(e ast.Expr) ast.Expr { - for { - p, ok := e.(*ast.ParenExpr) - if !ok { - return e - } - e = p.X - } -} +// Deprecated: use [ast.Unparen]. +func Unparen(e ast.Expr) ast.Expr { return ast.Unparen(e) } diff --git a/vendor/golang.org/x/tools/go/packages/doc.go b/vendor/golang.org/x/tools/go/packages/doc.go index 3531ac8f..f1931d10 100644 --- a/vendor/golang.org/x/tools/go/packages/doc.go +++ b/vendor/golang.org/x/tools/go/packages/doc.go @@ -64,7 +64,7 @@ graph using the Imports fields. The Load function can be configured by passing a pointer to a Config as the first argument. A nil Config is equivalent to the zero Config, which -causes Load to run in LoadFiles mode, collecting minimal information. +causes Load to run in [LoadFiles] mode, collecting minimal information. See the documentation for type Config for details. As noted earlier, the Config.Mode controls the amount of detail @@ -72,14 +72,14 @@ reported about the loaded packages. See the documentation for type LoadMode for details. Most tools should pass their command-line arguments (after any flags) -uninterpreted to [Load], so that it can interpret them +uninterpreted to Load, so that it can interpret them according to the conventions of the underlying build system. See the Example function for typical usage. # The driver protocol -[Load] may be used to load Go packages even in Go projects that use +Load may be used to load Go packages even in Go projects that use alternative build systems, by installing an appropriate "driver" program for the build system and specifying its location in the GOPACKAGESDRIVER environment variable. @@ -97,6 +97,15 @@ JSON-encoded [DriverRequest] message providing additional information is written to the driver's standard input. The driver must write a JSON-encoded [DriverResponse] message to its standard output. (This message differs from the JSON schema produced by 'go list'.) + +The value of the PWD environment variable seen by the driver process +is the preferred name of its working directory. (The working directory +may have other aliases due to symbolic links; see the comment on the +Dir field of [exec.Cmd] for related information.) +When the driver process emits in its response the name of a file +that is a descendant of this directory, it must use an absolute path +that has the value of PWD as a prefix, to ensure that the returned +filenames satisfy the original query. */ package packages // import "golang.org/x/tools/go/packages" diff --git a/vendor/golang.org/x/tools/go/packages/external.go b/vendor/golang.org/x/tools/go/packages/external.go index c2b4b711..8f7afcb5 100644 --- a/vendor/golang.org/x/tools/go/packages/external.go +++ b/vendor/golang.org/x/tools/go/packages/external.go @@ -82,7 +82,7 @@ type DriverResponse struct { type driver func(cfg *Config, patterns ...string) (*DriverResponse, error) // findExternalDriver returns the file path of a tool that supplies -// the build system package structure, or "" if not found." +// the build system package structure, or "" if not found. // If GOPACKAGESDRIVER is set in the environment findExternalTool returns its // value, otherwise it searches for a binary named gopackagesdriver on the PATH. func findExternalDriver(cfg *Config) driver { diff --git a/vendor/golang.org/x/tools/go/packages/loadmode_string.go b/vendor/golang.org/x/tools/go/packages/loadmode_string.go index 5c080d21..5fcad6ea 100644 --- a/vendor/golang.org/x/tools/go/packages/loadmode_string.go +++ b/vendor/golang.org/x/tools/go/packages/loadmode_string.go @@ -9,49 +9,46 @@ import ( "strings" ) -var allModes = []LoadMode{ - NeedName, - NeedFiles, - NeedCompiledGoFiles, - NeedImports, - NeedDeps, - NeedExportFile, - NeedTypes, - NeedSyntax, - NeedTypesInfo, - NeedTypesSizes, +var modes = [...]struct { + mode LoadMode + name string +}{ + {NeedName, "NeedName"}, + {NeedFiles, "NeedFiles"}, + {NeedCompiledGoFiles, "NeedCompiledGoFiles"}, + {NeedImports, "NeedImports"}, + {NeedDeps, "NeedDeps"}, + {NeedExportFile, "NeedExportFile"}, + {NeedTypes, "NeedTypes"}, + {NeedSyntax, "NeedSyntax"}, + {NeedTypesInfo, "NeedTypesInfo"}, + {NeedTypesSizes, "NeedTypesSizes"}, + {NeedModule, "NeedModule"}, + {NeedEmbedFiles, "NeedEmbedFiles"}, + {NeedEmbedPatterns, "NeedEmbedPatterns"}, } -var modeStrings = []string{ - "NeedName", - "NeedFiles", - "NeedCompiledGoFiles", - "NeedImports", - "NeedDeps", - "NeedExportFile", - "NeedTypes", - "NeedSyntax", - "NeedTypesInfo", - "NeedTypesSizes", -} - -func (mod LoadMode) String() string { - m := mod - if m == 0 { +func (mode LoadMode) String() string { + if mode == 0 { return "LoadMode(0)" } var out []string - for i, x := range allModes { - if x > m { - break + // named bits + for _, item := range modes { + if (mode & item.mode) != 0 { + mode ^= item.mode + out = append(out, item.name) } - if (m & x) != 0 { - out = append(out, modeStrings[i]) - m = m ^ x + } + // unnamed residue + if mode != 0 { + if out == nil { + return fmt.Sprintf("LoadMode(%#x)", int(mode)) } + out = append(out, fmt.Sprintf("%#x", int(mode))) } - if m != 0 { - out = append(out, "Unknown") + if len(out) == 1 { + return out[0] } - return fmt.Sprintf("LoadMode(%s)", strings.Join(out, "|")) + return "(" + strings.Join(out, "|") + ")" } diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go index 0b6bfaff..f227f1ba 100644 --- a/vendor/golang.org/x/tools/go/packages/packages.go +++ b/vendor/golang.org/x/tools/go/packages/packages.go @@ -46,10 +46,10 @@ import ( // // Unfortunately there are a number of open bugs related to // interactions among the LoadMode bits: -// - https://github.com/golang/go/issues/56633 -// - https://github.com/golang/go/issues/56677 -// - https://github.com/golang/go/issues/58726 -// - https://github.com/golang/go/issues/63517 +// - https://github.com/golang/go/issues/56633 +// - https://github.com/golang/go/issues/56677 +// - https://github.com/golang/go/issues/58726 +// - https://github.com/golang/go/issues/63517 type LoadMode int const ( @@ -103,25 +103,37 @@ const ( // NeedEmbedPatterns adds EmbedPatterns. NeedEmbedPatterns + + // Be sure to update loadmode_string.go when adding new items! ) const ( + // LoadFiles loads the name and file names for the initial packages. + // // Deprecated: LoadFiles exists for historical compatibility // and should not be used. Please directly specify the needed fields using the Need values. LoadFiles = NeedName | NeedFiles | NeedCompiledGoFiles + // LoadImports loads the name, file names, and import mapping for the initial packages. + // // Deprecated: LoadImports exists for historical compatibility // and should not be used. Please directly specify the needed fields using the Need values. LoadImports = LoadFiles | NeedImports + // LoadTypes loads exported type information for the initial packages. + // // Deprecated: LoadTypes exists for historical compatibility // and should not be used. Please directly specify the needed fields using the Need values. LoadTypes = LoadImports | NeedTypes | NeedTypesSizes + // LoadSyntax loads typed syntax for the initial packages. + // // Deprecated: LoadSyntax exists for historical compatibility // and should not be used. Please directly specify the needed fields using the Need values. LoadSyntax = LoadTypes | NeedSyntax | NeedTypesInfo + // LoadAllSyntax loads typed syntax for the initial packages and all dependencies. + // // Deprecated: LoadAllSyntax exists for historical compatibility // and should not be used. Please directly specify the needed fields using the Need values. LoadAllSyntax = LoadSyntax | NeedDeps @@ -236,14 +248,13 @@ type Config struct { // Load loads and returns the Go packages named by the given patterns. // -// Config specifies loading options; -// nil behaves the same as an empty Config. +// The cfg parameter specifies loading options; nil behaves the same as an empty [Config]. // // The [Config.Mode] field is a set of bits that determine what kinds // of information should be computed and returned. Modes that require // more information tend to be slower. See [LoadMode] for details // and important caveats. Its zero value is equivalent to -// NeedName | NeedFiles | NeedCompiledGoFiles. +// [NeedName] | [NeedFiles] | [NeedCompiledGoFiles]. // // Each call to Load returns a new set of [Package] instances. // The Packages and their Imports form a directed acyclic graph. @@ -260,7 +271,7 @@ type Config struct { // Errors associated with a particular package are recorded in the // corresponding Package's Errors list, and do not cause Load to // return an error. Clients may need to handle such errors before -// proceeding with further analysis. The PrintErrors function is +// proceeding with further analysis. The [PrintErrors] function is // provided for convenient display of all errors. func Load(cfg *Config, patterns ...string) ([]*Package, error) { ld := newLoader(cfg) @@ -763,6 +774,7 @@ func newLoader(cfg *Config) *loader { // because we load source if export data is missing. if ld.ParseFile == nil { ld.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) { + // We implicitly promise to keep doing ast.Object resolution. :( const mode = parser.AllErrors | parser.ParseComments return parser.ParseFile(fset, filename, src, mode) } diff --git a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go index 9ada1777..a70b727f 100644 --- a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go +++ b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go @@ -228,7 +228,7 @@ func (enc *Encoder) For(obj types.Object) (Path, error) { // Reject obviously non-viable cases. switch obj := obj.(type) { case *types.TypeName: - if _, ok := aliases.Unalias(obj.Type()).(*types.TypeParam); !ok { + if _, ok := types.Unalias(obj.Type()).(*types.TypeParam); !ok { // With the exception of type parameters, only package-level type names // have a path. return "", fmt.Errorf("no path for %v", obj) @@ -280,7 +280,7 @@ func (enc *Encoder) For(obj types.Object) (Path, error) { path = append(path, opType) T := o.Type() - if alias, ok := T.(*aliases.Alias); ok { + if alias, ok := T.(*types.Alias); ok { if r := findTypeParam(obj, aliases.TypeParams(alias), path, opTypeParam, nil); r != nil { return Path(r), nil } @@ -320,7 +320,7 @@ func (enc *Encoder) For(obj types.Object) (Path, error) { } // Inspect declared methods of defined types. - if T, ok := aliases.Unalias(o.Type()).(*types.Named); ok { + if T, ok := types.Unalias(o.Type()).(*types.Named); ok { path = append(path, opType) // The method index here is always with respect // to the underlying go/types data structures, @@ -449,8 +449,8 @@ func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) { // nil, it will be allocated as necessary. func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName]bool) []byte { switch T := T.(type) { - case *aliases.Alias: - return find(obj, aliases.Unalias(T), path, seen) + case *types.Alias: + return find(obj, types.Unalias(T), path, seen) case *types.Basic, *types.Named: // Named types belonging to pkg were handled already, // so T must belong to another package. No path. @@ -626,7 +626,7 @@ func Object(pkg *types.Package, p Path) (types.Object, error) { // Inv: t != nil, obj == nil - t = aliases.Unalias(t) + t = types.Unalias(t) switch code { case opElem: hasElem, ok := t.(hasElem) // Pointer, Slice, Array, Chan, Map @@ -664,7 +664,7 @@ func Object(pkg *types.Package, p Path) (types.Object, error) { t = named.Underlying() case opRhs: - if alias, ok := t.(*aliases.Alias); ok { + if alias, ok := t.(*types.Alias); ok { t = aliases.Rhs(alias) } else if false && aliases.Enabled() { // The Enabled check is too expensive, so for now we diff --git a/vendor/golang.org/x/tools/go/types/typeutil/callee.go b/vendor/golang.org/x/tools/go/types/typeutil/callee.go new file mode 100644 index 00000000..75438035 --- /dev/null +++ b/vendor/golang.org/x/tools/go/types/typeutil/callee.go @@ -0,0 +1,68 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typeutil + +import ( + "go/ast" + "go/types" + + "golang.org/x/tools/internal/typeparams" +) + +// Callee returns the named target of a function call, if any: +// a function, method, builtin, or variable. +// +// Functions and methods may potentially have type parameters. +func Callee(info *types.Info, call *ast.CallExpr) types.Object { + fun := ast.Unparen(call.Fun) + + // Look through type instantiation if necessary. + isInstance := false + switch fun.(type) { + case *ast.IndexExpr, *ast.IndexListExpr: + // When extracting the callee from an *IndexExpr, we need to check that + // it is a *types.Func and not a *types.Var. + // Example: Don't match a slice m within the expression `m[0]()`. + isInstance = true + fun, _, _, _ = typeparams.UnpackIndexExpr(fun) + } + + var obj types.Object + switch fun := fun.(type) { + case *ast.Ident: + obj = info.Uses[fun] // type, var, builtin, or declared func + case *ast.SelectorExpr: + if sel, ok := info.Selections[fun]; ok { + obj = sel.Obj() // method or field + } else { + obj = info.Uses[fun.Sel] // qualified identifier? + } + } + if _, ok := obj.(*types.TypeName); ok { + return nil // T(x) is a conversion, not a call + } + // A Func is required to match instantiations. + if _, ok := obj.(*types.Func); isInstance && !ok { + return nil // Was not a Func. + } + return obj +} + +// StaticCallee returns the target (function or method) of a static function +// call, if any. It returns nil for calls to builtins. +// +// Note: for calls of instantiated functions and methods, StaticCallee returns +// the corresponding generic function or method on the generic type. +func StaticCallee(info *types.Info, call *ast.CallExpr) *types.Func { + if f, ok := Callee(info, call).(*types.Func); ok && !interfaceMethod(f) { + return f + } + return nil +} + +func interfaceMethod(f *types.Func) bool { + recv := f.Type().(*types.Signature).Recv() + return recv != nil && types.IsInterface(recv.Type()) +} diff --git a/vendor/golang.org/x/tools/go/types/typeutil/imports.go b/vendor/golang.org/x/tools/go/types/typeutil/imports.go new file mode 100644 index 00000000..b81ce0c3 --- /dev/null +++ b/vendor/golang.org/x/tools/go/types/typeutil/imports.go @@ -0,0 +1,30 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typeutil + +import "go/types" + +// Dependencies returns all dependencies of the specified packages. +// +// Dependent packages appear in topological order: if package P imports +// package Q, Q appears earlier than P in the result. +// The algorithm follows import statements in the order they +// appear in the source code, so the result is a total order. +func Dependencies(pkgs ...*types.Package) []*types.Package { + var result []*types.Package + seen := make(map[*types.Package]bool) + var visit func(pkgs []*types.Package) + visit = func(pkgs []*types.Package) { + for _, p := range pkgs { + if !seen[p] { + seen[p] = true + visit(p.Imports()) + result = append(result, p) + } + } + } + visit(pkgs) + return result +} diff --git a/vendor/golang.org/x/tools/go/types/typeutil/map.go b/vendor/golang.org/x/tools/go/types/typeutil/map.go new file mode 100644 index 00000000..8d824f71 --- /dev/null +++ b/vendor/golang.org/x/tools/go/types/typeutil/map.go @@ -0,0 +1,517 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package typeutil defines various utilities for types, such as Map, +// a mapping from types.Type to any values. +package typeutil // import "golang.org/x/tools/go/types/typeutil" + +import ( + "bytes" + "fmt" + "go/types" + "reflect" + + "golang.org/x/tools/internal/typeparams" +) + +// Map is a hash-table-based mapping from types (types.Type) to +// arbitrary any values. The concrete types that implement +// the Type interface are pointers. Since they are not canonicalized, +// == cannot be used to check for equivalence, and thus we cannot +// simply use a Go map. +// +// Just as with map[K]V, a nil *Map is a valid empty map. +// +// Not thread-safe. +type Map struct { + hasher Hasher // shared by many Maps + table map[uint32][]entry // maps hash to bucket; entry.key==nil means unused + length int // number of map entries +} + +// entry is an entry (key/value association) in a hash bucket. +type entry struct { + key types.Type + value any +} + +// SetHasher sets the hasher used by Map. +// +// All Hashers are functionally equivalent but contain internal state +// used to cache the results of hashing previously seen types. +// +// A single Hasher created by MakeHasher() may be shared among many +// Maps. This is recommended if the instances have many keys in +// common, as it will amortize the cost of hash computation. +// +// A Hasher may grow without bound as new types are seen. Even when a +// type is deleted from the map, the Hasher never shrinks, since other +// types in the map may reference the deleted type indirectly. +// +// Hashers are not thread-safe, and read-only operations such as +// Map.Lookup require updates to the hasher, so a full Mutex lock (not a +// read-lock) is require around all Map operations if a shared +// hasher is accessed from multiple threads. +// +// If SetHasher is not called, the Map will create a private hasher at +// the first call to Insert. +func (m *Map) SetHasher(hasher Hasher) { + m.hasher = hasher +} + +// Delete removes the entry with the given key, if any. +// It returns true if the entry was found. +func (m *Map) Delete(key types.Type) bool { + if m != nil && m.table != nil { + hash := m.hasher.Hash(key) + bucket := m.table[hash] + for i, e := range bucket { + if e.key != nil && types.Identical(key, e.key) { + // We can't compact the bucket as it + // would disturb iterators. + bucket[i] = entry{} + m.length-- + return true + } + } + } + return false +} + +// At returns the map entry for the given key. +// The result is nil if the entry is not present. +func (m *Map) At(key types.Type) any { + if m != nil && m.table != nil { + for _, e := range m.table[m.hasher.Hash(key)] { + if e.key != nil && types.Identical(key, e.key) { + return e.value + } + } + } + return nil +} + +// Set sets the map entry for key to val, +// and returns the previous entry, if any. +func (m *Map) Set(key types.Type, value any) (prev any) { + if m.table != nil { + hash := m.hasher.Hash(key) + bucket := m.table[hash] + var hole *entry + for i, e := range bucket { + if e.key == nil { + hole = &bucket[i] + } else if types.Identical(key, e.key) { + prev = e.value + bucket[i].value = value + return + } + } + + if hole != nil { + *hole = entry{key, value} // overwrite deleted entry + } else { + m.table[hash] = append(bucket, entry{key, value}) + } + } else { + if m.hasher.memo == nil { + m.hasher = MakeHasher() + } + hash := m.hasher.Hash(key) + m.table = map[uint32][]entry{hash: {entry{key, value}}} + } + + m.length++ + return +} + +// Len returns the number of map entries. +func (m *Map) Len() int { + if m != nil { + return m.length + } + return 0 +} + +// Iterate calls function f on each entry in the map in unspecified order. +// +// If f should mutate the map, Iterate provides the same guarantees as +// Go maps: if f deletes a map entry that Iterate has not yet reached, +// f will not be invoked for it, but if f inserts a map entry that +// Iterate has not yet reached, whether or not f will be invoked for +// it is unspecified. +func (m *Map) Iterate(f func(key types.Type, value any)) { + if m != nil { + for _, bucket := range m.table { + for _, e := range bucket { + if e.key != nil { + f(e.key, e.value) + } + } + } + } +} + +// Keys returns a new slice containing the set of map keys. +// The order is unspecified. +func (m *Map) Keys() []types.Type { + keys := make([]types.Type, 0, m.Len()) + m.Iterate(func(key types.Type, _ any) { + keys = append(keys, key) + }) + return keys +} + +func (m *Map) toString(values bool) string { + if m == nil { + return "{}" + } + var buf bytes.Buffer + fmt.Fprint(&buf, "{") + sep := "" + m.Iterate(func(key types.Type, value any) { + fmt.Fprint(&buf, sep) + sep = ", " + fmt.Fprint(&buf, key) + if values { + fmt.Fprintf(&buf, ": %q", value) + } + }) + fmt.Fprint(&buf, "}") + return buf.String() +} + +// String returns a string representation of the map's entries. +// Values are printed using fmt.Sprintf("%v", v). +// Order is unspecified. +func (m *Map) String() string { + return m.toString(true) +} + +// KeysString returns a string representation of the map's key set. +// Order is unspecified. +func (m *Map) KeysString() string { + return m.toString(false) +} + +//////////////////////////////////////////////////////////////////////// +// Hasher + +// A Hasher maps each type to its hash value. +// For efficiency, a hasher uses memoization; thus its memory +// footprint grows monotonically over time. +// Hashers are not thread-safe. +// Hashers have reference semantics. +// Call MakeHasher to create a Hasher. +type Hasher struct { + memo map[types.Type]uint32 + + // ptrMap records pointer identity. + ptrMap map[any]uint32 + + // sigTParams holds type parameters from the signature being hashed. + // Signatures are considered identical modulo renaming of type parameters, so + // within the scope of a signature type the identity of the signature's type + // parameters is just their index. + // + // Since the language does not currently support referring to uninstantiated + // generic types or functions, and instantiated signatures do not have type + // parameter lists, we should never encounter a second non-empty type + // parameter list when hashing a generic signature. + sigTParams *types.TypeParamList +} + +// MakeHasher returns a new Hasher instance. +func MakeHasher() Hasher { + return Hasher{ + memo: make(map[types.Type]uint32), + ptrMap: make(map[any]uint32), + sigTParams: nil, + } +} + +// Hash computes a hash value for the given type t such that +// Identical(t, t') => Hash(t) == Hash(t'). +func (h Hasher) Hash(t types.Type) uint32 { + hash, ok := h.memo[t] + if !ok { + hash = h.hashFor(t) + h.memo[t] = hash + } + return hash +} + +// hashString computes the Fowler–Noll–Vo hash of s. +func hashString(s string) uint32 { + var h uint32 + for i := 0; i < len(s); i++ { + h ^= uint32(s[i]) + h *= 16777619 + } + return h +} + +// hashFor computes the hash of t. +func (h Hasher) hashFor(t types.Type) uint32 { + // See Identical for rationale. + switch t := t.(type) { + case *types.Basic: + return uint32(t.Kind()) + + case *types.Alias: + return h.Hash(types.Unalias(t)) + + case *types.Array: + return 9043 + 2*uint32(t.Len()) + 3*h.Hash(t.Elem()) + + case *types.Slice: + return 9049 + 2*h.Hash(t.Elem()) + + case *types.Struct: + var hash uint32 = 9059 + for i, n := 0, t.NumFields(); i < n; i++ { + f := t.Field(i) + if f.Anonymous() { + hash += 8861 + } + hash += hashString(t.Tag(i)) + hash += hashString(f.Name()) // (ignore f.Pkg) + hash += h.Hash(f.Type()) + } + return hash + + case *types.Pointer: + return 9067 + 2*h.Hash(t.Elem()) + + case *types.Signature: + var hash uint32 = 9091 + if t.Variadic() { + hash *= 8863 + } + + // Use a separate hasher for types inside of the signature, where type + // parameter identity is modified to be (index, constraint). We must use a + // new memo for this hasher as type identity may be affected by this + // masking. For example, in func[T any](*T), the identity of *T depends on + // whether we are mapping the argument in isolation, or recursively as part + // of hashing the signature. + // + // We should never encounter a generic signature while hashing another + // generic signature, but defensively set sigTParams only if h.mask is + // unset. + tparams := t.TypeParams() + if h.sigTParams == nil && tparams.Len() != 0 { + h = Hasher{ + // There may be something more efficient than discarding the existing + // memo, but it would require detecting whether types are 'tainted' by + // references to type parameters. + memo: make(map[types.Type]uint32), + // Re-using ptrMap ensures that pointer identity is preserved in this + // hasher. + ptrMap: h.ptrMap, + sigTParams: tparams, + } + } + + for i := 0; i < tparams.Len(); i++ { + tparam := tparams.At(i) + hash += 7 * h.Hash(tparam.Constraint()) + } + + return hash + 3*h.hashTuple(t.Params()) + 5*h.hashTuple(t.Results()) + + case *types.Union: + return h.hashUnion(t) + + case *types.Interface: + // Interfaces are identical if they have the same set of methods, with + // identical names and types, and they have the same set of type + // restrictions. See go/types.identical for more details. + var hash uint32 = 9103 + + // Hash methods. + for i, n := 0, t.NumMethods(); i < n; i++ { + // Method order is not significant. + // Ignore m.Pkg(). + m := t.Method(i) + // Use shallow hash on method signature to + // avoid anonymous interface cycles. + hash += 3*hashString(m.Name()) + 5*h.shallowHash(m.Type()) + } + + // Hash type restrictions. + terms, err := typeparams.InterfaceTermSet(t) + // if err != nil t has invalid type restrictions. + if err == nil { + hash += h.hashTermSet(terms) + } + + return hash + + case *types.Map: + return 9109 + 2*h.Hash(t.Key()) + 3*h.Hash(t.Elem()) + + case *types.Chan: + return 9127 + 2*uint32(t.Dir()) + 3*h.Hash(t.Elem()) + + case *types.Named: + hash := h.hashPtr(t.Obj()) + targs := t.TypeArgs() + for i := 0; i < targs.Len(); i++ { + targ := targs.At(i) + hash += 2 * h.Hash(targ) + } + return hash + + case *types.TypeParam: + return h.hashTypeParam(t) + + case *types.Tuple: + return h.hashTuple(t) + } + + panic(fmt.Sprintf("%T: %v", t, t)) +} + +func (h Hasher) hashTuple(tuple *types.Tuple) uint32 { + // See go/types.identicalTypes for rationale. + n := tuple.Len() + hash := 9137 + 2*uint32(n) + for i := 0; i < n; i++ { + hash += 3 * h.Hash(tuple.At(i).Type()) + } + return hash +} + +func (h Hasher) hashUnion(t *types.Union) uint32 { + // Hash type restrictions. + terms, err := typeparams.UnionTermSet(t) + // if err != nil t has invalid type restrictions. Fall back on a non-zero + // hash. + if err != nil { + return 9151 + } + return h.hashTermSet(terms) +} + +func (h Hasher) hashTermSet(terms []*types.Term) uint32 { + hash := 9157 + 2*uint32(len(terms)) + for _, term := range terms { + // term order is not significant. + termHash := h.Hash(term.Type()) + if term.Tilde() { + termHash *= 9161 + } + hash += 3 * termHash + } + return hash +} + +// hashTypeParam returns a hash of the type parameter t, with a hash value +// depending on whether t is contained in h.sigTParams. +// +// If h.sigTParams is set and contains t, then we are in the process of hashing +// a signature, and the hash value of t must depend only on t's index and +// constraint: signatures are considered identical modulo type parameter +// renaming. To avoid infinite recursion, we only hash the type parameter +// index, and rely on types.Identical to handle signatures where constraints +// are not identical. +// +// Otherwise the hash of t depends only on t's pointer identity. +func (h Hasher) hashTypeParam(t *types.TypeParam) uint32 { + if h.sigTParams != nil { + i := t.Index() + if i >= 0 && i < h.sigTParams.Len() && t == h.sigTParams.At(i) { + return 9173 + 3*uint32(i) + } + } + return h.hashPtr(t.Obj()) +} + +// hashPtr hashes the pointer identity of ptr. It uses h.ptrMap to ensure that +// pointers values are not dependent on the GC. +func (h Hasher) hashPtr(ptr any) uint32 { + if hash, ok := h.ptrMap[ptr]; ok { + return hash + } + hash := uint32(reflect.ValueOf(ptr).Pointer()) + h.ptrMap[ptr] = hash + return hash +} + +// shallowHash computes a hash of t without looking at any of its +// element Types, to avoid potential anonymous cycles in the types of +// interface methods. +// +// When an unnamed non-empty interface type appears anywhere among the +// arguments or results of an interface method, there is a potential +// for endless recursion. Consider: +// +// type X interface { m() []*interface { X } } +// +// The problem is that the Methods of the interface in m's result type +// include m itself; there is no mention of the named type X that +// might help us break the cycle. +// (See comment in go/types.identical, case *Interface, for more.) +func (h Hasher) shallowHash(t types.Type) uint32 { + // t is the type of an interface method (Signature), + // its params or results (Tuples), or their immediate + // elements (mostly Slice, Pointer, Basic, Named), + // so there's no need to optimize anything else. + switch t := t.(type) { + case *types.Alias: + return h.shallowHash(types.Unalias(t)) + + case *types.Signature: + var hash uint32 = 604171 + if t.Variadic() { + hash *= 971767 + } + // The Signature/Tuple recursion is always finite + // and invariably shallow. + return hash + 1062599*h.shallowHash(t.Params()) + 1282529*h.shallowHash(t.Results()) + + case *types.Tuple: + n := t.Len() + hash := 9137 + 2*uint32(n) + for i := 0; i < n; i++ { + hash += 53471161 * h.shallowHash(t.At(i).Type()) + } + return hash + + case *types.Basic: + return 45212177 * uint32(t.Kind()) + + case *types.Array: + return 1524181 + 2*uint32(t.Len()) + + case *types.Slice: + return 2690201 + + case *types.Struct: + return 3326489 + + case *types.Pointer: + return 4393139 + + case *types.Union: + return 562448657 + + case *types.Interface: + return 2124679 // no recursion here + + case *types.Map: + return 9109 + + case *types.Chan: + return 9127 + + case *types.Named: + return h.hashPtr(t.Obj()) + + case *types.TypeParam: + return h.hashPtr(t.Obj()) + } + panic(fmt.Sprintf("shallowHash: %T: %v", t, t)) +} diff --git a/vendor/golang.org/x/tools/go/types/typeutil/methodsetcache.go b/vendor/golang.org/x/tools/go/types/typeutil/methodsetcache.go new file mode 100644 index 00000000..f7666028 --- /dev/null +++ b/vendor/golang.org/x/tools/go/types/typeutil/methodsetcache.go @@ -0,0 +1,71 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements a cache of method sets. + +package typeutil + +import ( + "go/types" + "sync" +) + +// A MethodSetCache records the method set of each type T for which +// MethodSet(T) is called so that repeat queries are fast. +// The zero value is a ready-to-use cache instance. +type MethodSetCache struct { + mu sync.Mutex + named map[*types.Named]struct{ value, pointer *types.MethodSet } // method sets for named N and *N + others map[types.Type]*types.MethodSet // all other types +} + +// MethodSet returns the method set of type T. It is thread-safe. +// +// If cache is nil, this function is equivalent to types.NewMethodSet(T). +// Utility functions can thus expose an optional *MethodSetCache +// parameter to clients that care about performance. +func (cache *MethodSetCache) MethodSet(T types.Type) *types.MethodSet { + if cache == nil { + return types.NewMethodSet(T) + } + cache.mu.Lock() + defer cache.mu.Unlock() + + switch T := types.Unalias(T).(type) { + case *types.Named: + return cache.lookupNamed(T).value + + case *types.Pointer: + if N, ok := types.Unalias(T.Elem()).(*types.Named); ok { + return cache.lookupNamed(N).pointer + } + } + + // all other types + // (The map uses pointer equivalence, not type identity.) + mset := cache.others[T] + if mset == nil { + mset = types.NewMethodSet(T) + if cache.others == nil { + cache.others = make(map[types.Type]*types.MethodSet) + } + cache.others[T] = mset + } + return mset +} + +func (cache *MethodSetCache) lookupNamed(named *types.Named) struct{ value, pointer *types.MethodSet } { + if cache.named == nil { + cache.named = make(map[*types.Named]struct{ value, pointer *types.MethodSet }) + } + // Avoid recomputing mset(*T) for each distinct Pointer + // instance whose underlying type is a named type. + msets, ok := cache.named[named] + if !ok { + msets.value = types.NewMethodSet(named) + msets.pointer = types.NewMethodSet(types.NewPointer(named)) + cache.named[named] = msets + } + return msets +} diff --git a/vendor/golang.org/x/tools/go/types/typeutil/ui.go b/vendor/golang.org/x/tools/go/types/typeutil/ui.go new file mode 100644 index 00000000..9dda6a25 --- /dev/null +++ b/vendor/golang.org/x/tools/go/types/typeutil/ui.go @@ -0,0 +1,53 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typeutil + +// This file defines utilities for user interfaces that display types. + +import ( + "go/types" +) + +// IntuitiveMethodSet returns the intuitive method set of a type T, +// which is the set of methods you can call on an addressable value of +// that type. +// +// The result always contains MethodSet(T), and is exactly MethodSet(T) +// for interface types and for pointer-to-concrete types. +// For all other concrete types T, the result additionally +// contains each method belonging to *T if there is no identically +// named method on T itself. +// +// This corresponds to user intuition about method sets; +// this function is intended only for user interfaces. +// +// The order of the result is as for types.MethodSet(T). +func IntuitiveMethodSet(T types.Type, msets *MethodSetCache) []*types.Selection { + isPointerToConcrete := func(T types.Type) bool { + ptr, ok := types.Unalias(T).(*types.Pointer) + return ok && !types.IsInterface(ptr.Elem()) + } + + var result []*types.Selection + mset := msets.MethodSet(T) + if types.IsInterface(T) || isPointerToConcrete(T) { + for i, n := 0, mset.Len(); i < n; i++ { + result = append(result, mset.At(i)) + } + } else { + // T is some other concrete type. + // Report methods of T and *T, preferring those of T. + pmset := msets.MethodSet(types.NewPointer(T)) + for i, n := 0, pmset.Len(); i < n; i++ { + meth := pmset.At(i) + if m := mset.Lookup(meth.Obj().Pkg(), meth.Obj().Name()); m != nil { + meth = m + } + result = append(result, meth) + } + + } + return result +} diff --git a/vendor/golang.org/x/tools/internal/aliases/aliases.go b/vendor/golang.org/x/tools/internal/aliases/aliases.go index c24c2eee..b9425f5a 100644 --- a/vendor/golang.org/x/tools/internal/aliases/aliases.go +++ b/vendor/golang.org/x/tools/internal/aliases/aliases.go @@ -22,11 +22,17 @@ import ( // GODEBUG=gotypesalias=... by invoking the type checker. The Enabled // function is expensive and should be called once per task (e.g. // package import), not once per call to NewAlias. -func NewAlias(enabled bool, pos token.Pos, pkg *types.Package, name string, rhs types.Type) *types.TypeName { +// +// Precondition: enabled || len(tparams)==0. +// If materialized aliases are disabled, there must not be any type parameters. +func NewAlias(enabled bool, pos token.Pos, pkg *types.Package, name string, rhs types.Type, tparams []*types.TypeParam) *types.TypeName { if enabled { tname := types.NewTypeName(pos, pkg, name, nil) - newAlias(tname, rhs) + SetTypeParams(types.NewAlias(tname, rhs), tparams) return tname } + if len(tparams) > 0 { + panic("cannot create an alias with type parameters when gotypesalias is not enabled") + } return types.NewTypeName(pos, pkg, name, rhs) } diff --git a/vendor/golang.org/x/tools/internal/aliases/aliases_go121.go b/vendor/golang.org/x/tools/internal/aliases/aliases_go121.go deleted file mode 100644 index 6652f7db..00000000 --- a/vendor/golang.org/x/tools/internal/aliases/aliases_go121.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2024 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !go1.22 -// +build !go1.22 - -package aliases - -import ( - "go/types" -) - -// Alias is a placeholder for a go/types.Alias for <=1.21. -// It will never be created by go/types. -type Alias struct{} - -func (*Alias) String() string { panic("unreachable") } -func (*Alias) Underlying() types.Type { panic("unreachable") } -func (*Alias) Obj() *types.TypeName { panic("unreachable") } -func Rhs(alias *Alias) types.Type { panic("unreachable") } -func TypeParams(alias *Alias) *types.TypeParamList { panic("unreachable") } -func SetTypeParams(alias *Alias, tparams []*types.TypeParam) { panic("unreachable") } -func TypeArgs(alias *Alias) *types.TypeList { panic("unreachable") } -func Origin(alias *Alias) *Alias { panic("unreachable") } - -// Unalias returns the type t for go <=1.21. -func Unalias(t types.Type) types.Type { return t } - -func newAlias(name *types.TypeName, rhs types.Type) *Alias { panic("unreachable") } - -// Enabled reports whether [NewAlias] should create [types.Alias] types. -// -// Before go1.22, this function always returns false. -func Enabled() bool { return false } diff --git a/vendor/golang.org/x/tools/internal/aliases/aliases_go122.go b/vendor/golang.org/x/tools/internal/aliases/aliases_go122.go index 3ef1afeb..7716a333 100644 --- a/vendor/golang.org/x/tools/internal/aliases/aliases_go122.go +++ b/vendor/golang.org/x/tools/internal/aliases/aliases_go122.go @@ -2,9 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build go1.22 -// +build go1.22 - package aliases import ( @@ -14,22 +11,19 @@ import ( "go/types" ) -// Alias is an alias of types.Alias. -type Alias = types.Alias - // Rhs returns the type on the right-hand side of the alias declaration. -func Rhs(alias *Alias) types.Type { +func Rhs(alias *types.Alias) types.Type { if alias, ok := any(alias).(interface{ Rhs() types.Type }); ok { return alias.Rhs() // go1.23+ } // go1.22's Alias didn't have the Rhs method, // so Unalias is the best we can do. - return Unalias(alias) + return types.Unalias(alias) } // TypeParams returns the type parameter list of the alias. -func TypeParams(alias *Alias) *types.TypeParamList { +func TypeParams(alias *types.Alias) *types.TypeParamList { if alias, ok := any(alias).(interface{ TypeParams() *types.TypeParamList }); ok { return alias.TypeParams() // go1.23+ } @@ -37,7 +31,7 @@ func TypeParams(alias *Alias) *types.TypeParamList { } // SetTypeParams sets the type parameters of the alias type. -func SetTypeParams(alias *Alias, tparams []*types.TypeParam) { +func SetTypeParams(alias *types.Alias, tparams []*types.TypeParam) { if alias, ok := any(alias).(interface { SetTypeParams(tparams []*types.TypeParam) }); ok { @@ -48,7 +42,7 @@ func SetTypeParams(alias *Alias, tparams []*types.TypeParam) { } // TypeArgs returns the type arguments used to instantiate the Alias type. -func TypeArgs(alias *Alias) *types.TypeList { +func TypeArgs(alias *types.Alias) *types.TypeList { if alias, ok := any(alias).(interface{ TypeArgs() *types.TypeList }); ok { return alias.TypeArgs() // go1.23+ } @@ -57,26 +51,13 @@ func TypeArgs(alias *Alias) *types.TypeList { // Origin returns the generic Alias type of which alias is an instance. // If alias is not an instance of a generic alias, Origin returns alias. -func Origin(alias *Alias) *Alias { +func Origin(alias *types.Alias) *types.Alias { if alias, ok := any(alias).(interface{ Origin() *types.Alias }); ok { return alias.Origin() // go1.23+ } return alias // not an instance of a generic alias (go1.22) } -// Unalias is a wrapper of types.Unalias. -func Unalias(t types.Type) types.Type { return types.Unalias(t) } - -// newAlias is an internal alias around types.NewAlias. -// Direct usage is discouraged as the moment. -// Try to use NewAlias instead. -func newAlias(tname *types.TypeName, rhs types.Type) *Alias { - a := types.NewAlias(tname, rhs) - // TODO(go.dev/issue/65455): Remove kludgy workaround to set a.actual as a side-effect. - Unalias(a) - return a -} - // Enabled reports whether [NewAlias] should create [types.Alias] types. // // This function is expensive! Call it sparingly. @@ -92,7 +73,7 @@ func Enabled() bool { // many tests. Therefore any attempt to cache the result // is just incorrect. fset := token.NewFileSet() - f, _ := parser.ParseFile(fset, "a.go", "package p; type A = int", 0) + f, _ := parser.ParseFile(fset, "a.go", "package p; type A = int", parser.SkipObjectResolution) pkg, _ := new(types.Config).Check("p", fset, []*ast.File{f}, nil) _, enabled := pkg.Scope().Lookup("A").Type().(*types.Alias) return enabled diff --git a/vendor/golang.org/x/tools/internal/gcimporter/bimport.go b/vendor/golang.org/x/tools/internal/gcimporter/bimport.go index d98b0db2..d79a605e 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/bimport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/bimport.go @@ -87,64 +87,3 @@ func chanDir(d int) types.ChanDir { return 0 } } - -var predeclOnce sync.Once -var predecl []types.Type // initialized lazily - -func predeclared() []types.Type { - predeclOnce.Do(func() { - // initialize lazily to be sure that all - // elements have been initialized before - predecl = []types.Type{ // basic types - types.Typ[types.Bool], - types.Typ[types.Int], - types.Typ[types.Int8], - types.Typ[types.Int16], - types.Typ[types.Int32], - types.Typ[types.Int64], - types.Typ[types.Uint], - types.Typ[types.Uint8], - types.Typ[types.Uint16], - types.Typ[types.Uint32], - types.Typ[types.Uint64], - types.Typ[types.Uintptr], - types.Typ[types.Float32], - types.Typ[types.Float64], - types.Typ[types.Complex64], - types.Typ[types.Complex128], - types.Typ[types.String], - - // basic type aliases - types.Universe.Lookup("byte").Type(), - types.Universe.Lookup("rune").Type(), - - // error - types.Universe.Lookup("error").Type(), - - // untyped types - types.Typ[types.UntypedBool], - types.Typ[types.UntypedInt], - types.Typ[types.UntypedRune], - types.Typ[types.UntypedFloat], - types.Typ[types.UntypedComplex], - types.Typ[types.UntypedString], - types.Typ[types.UntypedNil], - - // package unsafe - types.Typ[types.UnsafePointer], - - // invalid type - types.Typ[types.Invalid], // only appears in packages with errors - - // used internally by gc; never used by this package or in .a files - anyType{}, - } - predecl = append(predecl, additionalPredeclared()...) - }) - return predecl -} - -type anyType struct{} - -func (t anyType) Underlying() types.Type { return t } -func (t anyType) String() string { return "any" } diff --git a/vendor/golang.org/x/tools/internal/gcimporter/gcimporter.go b/vendor/golang.org/x/tools/internal/gcimporter/gcimporter.go index 39df9112..e6c5d51f 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/gcimporter.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/gcimporter.go @@ -232,14 +232,19 @@ func Import(packages map[string]*types.Package, path, srcDir string, lookup func // Select appropriate importer. if len(data) > 0 { switch data[0] { - case 'v', 'c', 'd': // binary, till go1.10 + case 'v', 'c', 'd': + // binary: emitted by cmd/compile till go1.10; obsolete. return nil, fmt.Errorf("binary (%c) import format is no longer supported", data[0]) - case 'i': // indexed, till go1.19 + case 'i': + // indexed: emitted by cmd/compile till go1.19; + // now used only for serializing go/types. + // See https://github.com/golang/go/issues/69491. _, pkg, err := IImportData(fset, packages, data[1:], id) return pkg, err - case 'u': // unified, from go1.20 + case 'u': + // unified: emitted by cmd/compile since go1.20. _, pkg, err := UImportData(fset, packages, data[1:size], id) return pkg, err diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go index deeb67f3..1e19fbed 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go @@ -2,9 +2,227 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Indexed binary package export. -// This file was derived from $GOROOT/src/cmd/compile/internal/gc/iexport.go; -// see that file for specification of the format. +// Indexed package export. +// +// The indexed export data format is an evolution of the previous +// binary export data format. Its chief contribution is introducing an +// index table, which allows efficient random access of individual +// declarations and inline function bodies. In turn, this allows +// avoiding unnecessary work for compilation units that import large +// packages. +// +// +// The top-level data format is structured as: +// +// Header struct { +// Tag byte // 'i' +// Version uvarint +// StringSize uvarint +// DataSize uvarint +// } +// +// Strings [StringSize]byte +// Data [DataSize]byte +// +// MainIndex []struct{ +// PkgPath stringOff +// PkgName stringOff +// PkgHeight uvarint +// +// Decls []struct{ +// Name stringOff +// Offset declOff +// } +// } +// +// Fingerprint [8]byte +// +// uvarint means a uint64 written out using uvarint encoding. +// +// []T means a uvarint followed by that many T objects. In other +// words: +// +// Len uvarint +// Elems [Len]T +// +// stringOff means a uvarint that indicates an offset within the +// Strings section. At that offset is another uvarint, followed by +// that many bytes, which form the string value. +// +// declOff means a uvarint that indicates an offset within the Data +// section where the associated declaration can be found. +// +// +// There are five kinds of declarations, distinguished by their first +// byte: +// +// type Var struct { +// Tag byte // 'V' +// Pos Pos +// Type typeOff +// } +// +// type Func struct { +// Tag byte // 'F' or 'G' +// Pos Pos +// TypeParams []typeOff // only present if Tag == 'G' +// Signature Signature +// } +// +// type Const struct { +// Tag byte // 'C' +// Pos Pos +// Value Value +// } +// +// type Type struct { +// Tag byte // 'T' or 'U' +// Pos Pos +// TypeParams []typeOff // only present if Tag == 'U' +// Underlying typeOff +// +// Methods []struct{ // omitted if Underlying is an interface type +// Pos Pos +// Name stringOff +// Recv Param +// Signature Signature +// } +// } +// +// type Alias struct { +// Tag byte // 'A' or 'B' +// Pos Pos +// TypeParams []typeOff // only present if Tag == 'B' +// Type typeOff +// } +// +// // "Automatic" declaration of each typeparam +// type TypeParam struct { +// Tag byte // 'P' +// Pos Pos +// Implicit bool +// Constraint typeOff +// } +// +// typeOff means a uvarint that either indicates a predeclared type, +// or an offset into the Data section. If the uvarint is less than +// predeclReserved, then it indicates the index into the predeclared +// types list (see predeclared in bexport.go for order). Otherwise, +// subtracting predeclReserved yields the offset of a type descriptor. +// +// Value means a type, kind, and type-specific value. See +// (*exportWriter).value for details. +// +// +// There are twelve kinds of type descriptors, distinguished by an itag: +// +// type DefinedType struct { +// Tag itag // definedType +// Name stringOff +// PkgPath stringOff +// } +// +// type PointerType struct { +// Tag itag // pointerType +// Elem typeOff +// } +// +// type SliceType struct { +// Tag itag // sliceType +// Elem typeOff +// } +// +// type ArrayType struct { +// Tag itag // arrayType +// Len uint64 +// Elem typeOff +// } +// +// type ChanType struct { +// Tag itag // chanType +// Dir uint64 // 1 RecvOnly; 2 SendOnly; 3 SendRecv +// Elem typeOff +// } +// +// type MapType struct { +// Tag itag // mapType +// Key typeOff +// Elem typeOff +// } +// +// type FuncType struct { +// Tag itag // signatureType +// PkgPath stringOff +// Signature Signature +// } +// +// type StructType struct { +// Tag itag // structType +// PkgPath stringOff +// Fields []struct { +// Pos Pos +// Name stringOff +// Type typeOff +// Embedded bool +// Note stringOff +// } +// } +// +// type InterfaceType struct { +// Tag itag // interfaceType +// PkgPath stringOff +// Embeddeds []struct { +// Pos Pos +// Type typeOff +// } +// Methods []struct { +// Pos Pos +// Name stringOff +// Signature Signature +// } +// } +// +// // Reference to a type param declaration +// type TypeParamType struct { +// Tag itag // typeParamType +// Name stringOff +// PkgPath stringOff +// } +// +// // Instantiation of a generic type (like List[T2] or List[int]) +// type InstanceType struct { +// Tag itag // instanceType +// Pos pos +// TypeArgs []typeOff +// BaseType typeOff +// } +// +// type UnionType struct { +// Tag itag // interfaceType +// Terms []struct { +// tilde bool +// Type typeOff +// } +// } +// +// +// +// type Signature struct { +// Params []Param +// Results []Param +// Variadic bool // omitted if Results is empty +// } +// +// type Param struct { +// Pos Pos +// Name stringOff +// Type typOff +// } +// +// +// Pos encodes a file:line:column triple, incorporating a simple delta +// encoding scheme within a data object. See exportWriter.pos for +// details. package gcimporter @@ -24,7 +242,6 @@ import ( "golang.org/x/tools/go/types/objectpath" "golang.org/x/tools/internal/aliases" - "golang.org/x/tools/internal/tokeninternal" ) // IExportShallow encodes "shallow" export data for the specified package. @@ -223,7 +440,7 @@ func (p *iexporter) encodeFile(w *intWriter, file *token.File, needed []uint64) // Sort the set of needed offsets. Duplicates are harmless. sort.Slice(needed, func(i, j int) bool { return needed[i] < needed[j] }) - lines := tokeninternal.GetLines(file) // byte offset of each line start + lines := file.Lines() // byte offset of each line start w.uint64(uint64(len(lines))) // Rather than record the entire array of line start offsets, @@ -507,13 +724,13 @@ func (p *iexporter) doDecl(obj types.Object) { case *types.TypeName: t := obj.Type() - if tparam, ok := aliases.Unalias(t).(*types.TypeParam); ok { + if tparam, ok := types.Unalias(t).(*types.TypeParam); ok { w.tag(typeParamTag) w.pos(obj.Pos()) constraint := tparam.Constraint() if p.version >= iexportVersionGo1_18 { implicit := false - if iface, _ := aliases.Unalias(constraint).(*types.Interface); iface != nil { + if iface, _ := types.Unalias(constraint).(*types.Interface); iface != nil { implicit = iface.IsImplicit() } w.bool(implicit) @@ -523,9 +740,22 @@ func (p *iexporter) doDecl(obj types.Object) { } if obj.IsAlias() { - w.tag(aliasTag) + alias, materialized := t.(*types.Alias) // may fail when aliases are not enabled + + var tparams *types.TypeParamList + if materialized { + tparams = aliases.TypeParams(alias) + } + if tparams.Len() == 0 { + w.tag(aliasTag) + } else { + w.tag(genericAliasTag) + } w.pos(obj.Pos()) - if alias, ok := t.(*aliases.Alias); ok { + if tparams.Len() > 0 { + w.tparamList(obj.Name(), tparams, obj.Pkg()) + } + if materialized { // Preserve materialized aliases, // even of non-exported types. t = aliases.Rhs(alias) @@ -744,8 +974,14 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { }() } switch t := t.(type) { - case *aliases.Alias: - // TODO(adonovan): support parameterized aliases, following *types.Named. + case *types.Alias: + if targs := aliases.TypeArgs(t); targs.Len() > 0 { + w.startType(instanceType) + w.pos(t.Obj().Pos()) + w.typeList(targs, pkg) + w.typ(aliases.Origin(t), pkg) + return + } w.startType(aliasType) w.qualifiedType(t.Obj()) @@ -854,7 +1090,7 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { for i := 0; i < n; i++ { ft := t.EmbeddedType(i) tPkg := pkg - if named, _ := aliases.Unalias(ft).(*types.Named); named != nil { + if named, _ := types.Unalias(ft).(*types.Named); named != nil { w.pos(named.Obj().Pos()) } else { w.pos(token.NoPos) diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go index 136aa036..21908a15 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // Indexed package import. -// See cmd/compile/internal/gc/iexport.go for the export data format. +// See iexport.go for the export data format. // This file is a copy of $GOROOT/src/go/internal/gcimporter/iimport.go. @@ -53,6 +53,7 @@ const ( iexportVersionPosCol = 1 iexportVersionGo1_18 = 2 iexportVersionGenerics = 2 + iexportVersion = iexportVersionGenerics iexportVersionCurrent = 2 ) @@ -540,7 +541,7 @@ func canReuse(def *types.Named, rhs types.Type) bool { if def == nil { return true } - iface, _ := aliases.Unalias(rhs).(*types.Interface) + iface, _ := types.Unalias(rhs).(*types.Interface) if iface == nil { return true } @@ -562,14 +563,14 @@ func (r *importReader) obj(name string) { pos := r.pos() switch tag { - case aliasTag: + case aliasTag, genericAliasTag: + var tparams []*types.TypeParam + if tag == genericAliasTag { + tparams = r.tparamList() + } typ := r.typ() - // TODO(adonovan): support generic aliases: - // if tag == genericAliasTag { - // tparams := r.tparamList() - // alias.SetTypeParams(tparams) - // } - r.declare(aliases.NewAlias(r.p.aliases, pos, r.currPkg, name, typ)) + obj := aliases.NewAlias(r.p.aliases, pos, r.currPkg, name, typ, tparams) + r.declare(obj) case constTag: typ, val := r.value() @@ -615,7 +616,7 @@ func (r *importReader) obj(name string) { if targs.Len() > 0 { rparams = make([]*types.TypeParam, targs.Len()) for i := range rparams { - rparams[i] = aliases.Unalias(targs.At(i)).(*types.TypeParam) + rparams[i] = types.Unalias(targs.At(i)).(*types.TypeParam) } } msig := r.signature(recv, rparams, nil) @@ -645,7 +646,7 @@ func (r *importReader) obj(name string) { } constraint := r.typ() if implicit { - iface, _ := aliases.Unalias(constraint).(*types.Interface) + iface, _ := types.Unalias(constraint).(*types.Interface) if iface == nil { errorf("non-interface constraint marked implicit") } @@ -852,7 +853,7 @@ func (r *importReader) typ() types.Type { } func isInterface(t types.Type) bool { - _, ok := aliases.Unalias(t).(*types.Interface) + _, ok := types.Unalias(t).(*types.Interface) return ok } @@ -862,7 +863,7 @@ func (r *importReader) string() string { return r.p.stringAt(r.uint64()) } func (r *importReader) doType(base *types.Named) (res types.Type) { k := r.kind() if debug { - r.p.trace("importing type %d (base: %s)", k, base) + r.p.trace("importing type %d (base: %v)", k, base) r.p.indent++ defer func() { r.p.indent-- @@ -959,7 +960,7 @@ func (r *importReader) doType(base *types.Named) (res types.Type) { methods[i] = method } - typ := newInterface(methods, embeddeds) + typ := types.NewInterfaceType(methods, embeddeds) r.p.interfaceList = append(r.p.interfaceList, typ) return typ @@ -1051,7 +1052,7 @@ func (r *importReader) tparamList() []*types.TypeParam { for i := range xs { // Note: the standard library importer is tolerant of nil types here, // though would panic in SetTypeParams. - xs[i] = aliases.Unalias(r.typ()).(*types.TypeParam) + xs[i] = types.Unalias(r.typ()).(*types.TypeParam) } return xs } diff --git a/vendor/golang.org/x/tools/internal/gcimporter/newInterface10.go b/vendor/golang.org/x/tools/internal/gcimporter/newInterface10.go deleted file mode 100644 index 8b163e3d..00000000 --- a/vendor/golang.org/x/tools/internal/gcimporter/newInterface10.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !go1.11 -// +build !go1.11 - -package gcimporter - -import "go/types" - -func newInterface(methods []*types.Func, embeddeds []types.Type) *types.Interface { - named := make([]*types.Named, len(embeddeds)) - for i, e := range embeddeds { - var ok bool - named[i], ok = e.(*types.Named) - if !ok { - panic("embedding of non-defined interfaces in interfaces is not supported before Go 1.11") - } - } - return types.NewInterface(methods, named) -} diff --git a/vendor/golang.org/x/tools/internal/gcimporter/newInterface11.go b/vendor/golang.org/x/tools/internal/gcimporter/newInterface11.go deleted file mode 100644 index 49984f40..00000000 --- a/vendor/golang.org/x/tools/internal/gcimporter/newInterface11.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build go1.11 -// +build go1.11 - -package gcimporter - -import "go/types" - -func newInterface(methods []*types.Func, embeddeds []types.Type) *types.Interface { - return types.NewInterfaceType(methods, embeddeds) -} diff --git a/vendor/golang.org/x/tools/internal/gcimporter/predeclared.go b/vendor/golang.org/x/tools/internal/gcimporter/predeclared.go new file mode 100644 index 00000000..907c8557 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/gcimporter/predeclared.go @@ -0,0 +1,91 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gcimporter + +import ( + "go/types" + "sync" +) + +// predecl is a cache for the predeclared types in types.Universe. +// +// Cache a distinct result based on the runtime value of any. +// The pointer value of the any type varies based on GODEBUG settings. +var predeclMu sync.Mutex +var predecl map[types.Type][]types.Type + +func predeclared() []types.Type { + anyt := types.Universe.Lookup("any").Type() + + predeclMu.Lock() + defer predeclMu.Unlock() + + if pre, ok := predecl[anyt]; ok { + return pre + } + + if predecl == nil { + predecl = make(map[types.Type][]types.Type) + } + + decls := []types.Type{ // basic types + types.Typ[types.Bool], + types.Typ[types.Int], + types.Typ[types.Int8], + types.Typ[types.Int16], + types.Typ[types.Int32], + types.Typ[types.Int64], + types.Typ[types.Uint], + types.Typ[types.Uint8], + types.Typ[types.Uint16], + types.Typ[types.Uint32], + types.Typ[types.Uint64], + types.Typ[types.Uintptr], + types.Typ[types.Float32], + types.Typ[types.Float64], + types.Typ[types.Complex64], + types.Typ[types.Complex128], + types.Typ[types.String], + + // basic type aliases + types.Universe.Lookup("byte").Type(), + types.Universe.Lookup("rune").Type(), + + // error + types.Universe.Lookup("error").Type(), + + // untyped types + types.Typ[types.UntypedBool], + types.Typ[types.UntypedInt], + types.Typ[types.UntypedRune], + types.Typ[types.UntypedFloat], + types.Typ[types.UntypedComplex], + types.Typ[types.UntypedString], + types.Typ[types.UntypedNil], + + // package unsafe + types.Typ[types.UnsafePointer], + + // invalid type + types.Typ[types.Invalid], // only appears in packages with errors + + // used internally by gc; never used by this package or in .a files + anyType{}, + + // comparable + types.Universe.Lookup("comparable").Type(), + + // any + anyt, + } + + predecl[anyt] = decls + return decls +} + +type anyType struct{} + +func (t anyType) Underlying() types.Type { return t } +func (t anyType) String() string { return "any" } diff --git a/vendor/golang.org/x/tools/internal/gcimporter/support_go118.go b/vendor/golang.org/x/tools/internal/gcimporter/support_go118.go deleted file mode 100644 index 0cd3b91b..00000000 --- a/vendor/golang.org/x/tools/internal/gcimporter/support_go118.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2021 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gcimporter - -import "go/types" - -const iexportVersion = iexportVersionGenerics - -// additionalPredeclared returns additional predeclared types in go.1.18. -func additionalPredeclared() []types.Type { - return []types.Type{ - // comparable - types.Universe.Lookup("comparable").Type(), - - // any - types.Universe.Lookup("any").Type(), - } -} - -// See cmd/compile/internal/types.SplitVargenSuffix. -func splitVargenSuffix(name string) (base, suffix string) { - i := len(name) - for i > 0 && name[i-1] >= '0' && name[i-1] <= '9' { - i-- - } - const dot = "·" - if i >= len(dot) && name[i-len(dot):i] == dot { - i -= len(dot) - return name[:i], name[i:] - } - return name, "" -} diff --git a/vendor/golang.org/x/tools/internal/gcimporter/unified_no.go b/vendor/golang.org/x/tools/internal/gcimporter/unified_no.go deleted file mode 100644 index 38b624ca..00000000 --- a/vendor/golang.org/x/tools/internal/gcimporter/unified_no.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !goexperiment.unified -// +build !goexperiment.unified - -package gcimporter - -const unifiedIR = false diff --git a/vendor/golang.org/x/tools/internal/gcimporter/unified_yes.go b/vendor/golang.org/x/tools/internal/gcimporter/unified_yes.go deleted file mode 100644 index b5118d0b..00000000 --- a/vendor/golang.org/x/tools/internal/gcimporter/unified_yes.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build goexperiment.unified -// +build goexperiment.unified - -package gcimporter - -const unifiedIR = true diff --git a/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go b/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go index 2c077068..1db40861 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go @@ -52,8 +52,7 @@ func (pr *pkgReader) later(fn func()) { // See cmd/compile/internal/noder.derivedInfo. type derivedInfo struct { - idx pkgbits.Index - needed bool + idx pkgbits.Index } // See cmd/compile/internal/noder.typeInfo. @@ -110,13 +109,17 @@ func readUnifiedPackage(fset *token.FileSet, ctxt *types.Context, imports map[st r := pr.newReader(pkgbits.RelocMeta, pkgbits.PublicRootIdx, pkgbits.SyncPublic) pkg := r.pkg() - r.Bool() // has init + if r.Version().Has(pkgbits.HasInit) { + r.Bool() + } for i, n := 0, r.Len(); i < n; i++ { // As if r.obj(), but avoiding the Scope.Lookup call, // to avoid eager loading of imports. r.Sync(pkgbits.SyncObject) - assert(!r.Bool()) + if r.Version().Has(pkgbits.DerivedFuncInstance) { + assert(!r.Bool()) + } r.p.objIdx(r.Reloc(pkgbits.RelocObj)) assert(r.Len() == 0) } @@ -165,7 +168,7 @@ type readerDict struct { // tparams is a slice of the constructed TypeParams for the element. tparams []*types.TypeParam - // devived is a slice of types derived from tparams, which may be + // derived is a slice of types derived from tparams, which may be // instantiated while reading the current element. derived []derivedInfo derivedTypes []types.Type // lazily instantiated from derived @@ -471,7 +474,9 @@ func (r *reader) param() *types.Var { func (r *reader) obj() (types.Object, []types.Type) { r.Sync(pkgbits.SyncObject) - assert(!r.Bool()) + if r.Version().Has(pkgbits.DerivedFuncInstance) { + assert(!r.Bool()) + } pkg, name := r.p.objIdx(r.Reloc(pkgbits.RelocObj)) obj := pkgScope(pkg).Lookup(name) @@ -525,8 +530,12 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) { case pkgbits.ObjAlias: pos := r.pos() + var tparams []*types.TypeParam + if r.Version().Has(pkgbits.AliasTypeParamNames) { + tparams = r.typeParamNames() + } typ := r.typ() - declare(aliases.NewAlias(r.p.aliases, pos, objPkg, objName, typ)) + declare(aliases.NewAlias(r.p.aliases, pos, objPkg, objName, typ, tparams)) case pkgbits.ObjConst: pos := r.pos() @@ -553,7 +562,7 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) { // If the underlying type is an interface, we need to // duplicate its methods so we can replace the receiver // parameter's type (#49906). - if iface, ok := aliases.Unalias(underlying).(*types.Interface); ok && iface.NumExplicitMethods() != 0 { + if iface, ok := types.Unalias(underlying).(*types.Interface); ok && iface.NumExplicitMethods() != 0 { methods := make([]*types.Func, iface.NumExplicitMethods()) for i := range methods { fn := iface.ExplicitMethod(i) @@ -632,7 +641,10 @@ func (pr *pkgReader) objDictIdx(idx pkgbits.Index) *readerDict { dict.derived = make([]derivedInfo, r.Len()) dict.derivedTypes = make([]types.Type, len(dict.derived)) for i := range dict.derived { - dict.derived[i] = derivedInfo{r.Reloc(pkgbits.RelocType), r.Bool()} + dict.derived[i] = derivedInfo{idx: r.Reloc(pkgbits.RelocType)} + if r.Version().Has(pkgbits.DerivedInfoNeeded) { + assert(!r.Bool()) + } } pr.retireReader(r) @@ -726,3 +738,17 @@ func pkgScope(pkg *types.Package) *types.Scope { } return types.Universe } + +// See cmd/compile/internal/types.SplitVargenSuffix. +func splitVargenSuffix(name string) (base, suffix string) { + i := len(name) + for i > 0 && name[i-1] >= '0' && name[i-1] <= '9' { + i-- + } + const dot = "·" + if i >= len(dot) && name[i-len(dot):i] == dot { + i -= len(dot) + return name[:i], name[i:] + } + return name, "" +} diff --git a/vendor/golang.org/x/tools/internal/gocommand/invoke.go b/vendor/golang.org/x/tools/internal/gocommand/invoke.go index 2e59ff85..e333efc8 100644 --- a/vendor/golang.org/x/tools/internal/gocommand/invoke.go +++ b/vendor/golang.org/x/tools/internal/gocommand/invoke.go @@ -16,7 +16,6 @@ import ( "os" "os/exec" "path/filepath" - "reflect" "regexp" "runtime" "strconv" @@ -250,16 +249,13 @@ func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error { cmd.Stdout = stdout cmd.Stderr = stderr - // cmd.WaitDelay was added only in go1.20 (see #50436). - if waitDelay := reflect.ValueOf(cmd).Elem().FieldByName("WaitDelay"); waitDelay.IsValid() { - // https://go.dev/issue/59541: don't wait forever copying stderr - // after the command has exited. - // After CL 484741 we copy stdout manually, so we we'll stop reading that as - // soon as ctx is done. However, we also don't want to wait around forever - // for stderr. Give a much-longer-than-reasonable delay and then assume that - // something has wedged in the kernel or runtime. - waitDelay.Set(reflect.ValueOf(30 * time.Second)) - } + // https://go.dev/issue/59541: don't wait forever copying stderr + // after the command has exited. + // After CL 484741 we copy stdout manually, so we we'll stop reading that as + // soon as ctx is done. However, we also don't want to wait around forever + // for stderr. Give a much-longer-than-reasonable delay and then assume that + // something has wedged in the kernel or runtime. + cmd.WaitDelay = 30 * time.Second // The cwd gets resolved to the real path. On Darwin, where // /tmp is a symlink, this breaks anything that expects the diff --git a/vendor/golang.org/x/tools/internal/imports/fix.go b/vendor/golang.org/x/tools/internal/imports/fix.go index dc7d50a7..c1510817 100644 --- a/vendor/golang.org/x/tools/internal/imports/fix.go +++ b/vendor/golang.org/x/tools/internal/imports/fix.go @@ -131,7 +131,7 @@ func parseOtherFiles(ctx context.Context, fset *token.FileSet, srcDir, filename continue } - f, err := parser.ParseFile(fset, filepath.Join(srcDir, fi.Name()), nil, 0) + f, err := parser.ParseFile(fset, filepath.Join(srcDir, fi.Name()), nil, parser.SkipObjectResolution) if err != nil { continue } @@ -1620,6 +1620,7 @@ func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string, incl } fullFile := filepath.Join(dir, fi.Name()) + // Legacy ast.Object resolution is needed here. f, err := parser.ParseFile(fset, fullFile, nil, 0) if err != nil { env.logf("error parsing %v: %v", fullFile, err) diff --git a/vendor/golang.org/x/tools/internal/imports/imports.go b/vendor/golang.org/x/tools/internal/imports/imports.go index f8346552..ff6b59a5 100644 --- a/vendor/golang.org/x/tools/internal/imports/imports.go +++ b/vendor/golang.org/x/tools/internal/imports/imports.go @@ -86,7 +86,7 @@ func ApplyFixes(fixes []*ImportFix, filename string, src []byte, opt *Options, e // Don't use parse() -- we don't care about fragments or statement lists // here, and we need to work with unparseable files. fileSet := token.NewFileSet() - parserMode := parser.Mode(0) + parserMode := parser.SkipObjectResolution if opt.Comments { parserMode |= parser.ParseComments } @@ -165,7 +165,7 @@ func formatFile(fset *token.FileSet, file *ast.File, src []byte, adjust func(ori // parse parses src, which was read from filename, // as a Go source file or statement list. func parse(fset *token.FileSet, filename string, src []byte, opt *Options) (*ast.File, func(orig, src []byte) []byte, error) { - parserMode := parser.Mode(0) + var parserMode parser.Mode // legacy ast.Object resolution is required here if opt.Comments { parserMode |= parser.ParseComments } diff --git a/vendor/golang.org/x/tools/internal/imports/mod.go b/vendor/golang.org/x/tools/internal/imports/mod.go index 91221fda..8555e3f8 100644 --- a/vendor/golang.org/x/tools/internal/imports/mod.go +++ b/vendor/golang.org/x/tools/internal/imports/mod.go @@ -245,7 +245,10 @@ func newModuleResolver(e *ProcessEnv, moduleCacheCache *DirInfoCache) (*ModuleRe // 2. Use this to separate module cache scanning from other scanning. func gomodcacheForEnv(goenv map[string]string) string { if gmc := goenv["GOMODCACHE"]; gmc != "" { - return gmc + // golang/go#67156: ensure that the module cache is clean, since it is + // assumed as a prefix to directories scanned by gopathwalk, which are + // themselves clean. + return filepath.Clean(gmc) } gopaths := filepath.SplitList(goenv["GOPATH"]) if len(gopaths) == 0 { @@ -740,8 +743,8 @@ func (r *ModuleResolver) loadExports(ctx context.Context, pkg *pkg, includeTest func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) directoryPackageInfo { subdir := "" - if dir != root.Path { - subdir = dir[len(root.Path)+len("/"):] + if prefix := root.Path + string(filepath.Separator); strings.HasPrefix(dir, prefix) { + subdir = dir[len(prefix):] } importPath := filepath.ToSlash(subdir) if strings.HasPrefix(importPath, "vendor/") { diff --git a/vendor/golang.org/x/tools/internal/pkgbits/decoder.go b/vendor/golang.org/x/tools/internal/pkgbits/decoder.go index b92e8e6e..f6cb37c5 100644 --- a/vendor/golang.org/x/tools/internal/pkgbits/decoder.go +++ b/vendor/golang.org/x/tools/internal/pkgbits/decoder.go @@ -21,7 +21,7 @@ import ( // export data. type PkgDecoder struct { // version is the file format version. - version uint32 + version Version // sync indicates whether the file uses sync markers. sync bool @@ -68,8 +68,6 @@ func (pr *PkgDecoder) SyncMarkers() bool { return pr.sync } // NewPkgDecoder returns a PkgDecoder initialized to read the Unified // IR export data from input. pkgPath is the package path for the // compilation unit that produced the export data. -// -// TODO(mdempsky): Remove pkgPath parameter; unneeded since CL 391014. func NewPkgDecoder(pkgPath, input string) PkgDecoder { pr := PkgDecoder{ pkgPath: pkgPath, @@ -80,14 +78,15 @@ func NewPkgDecoder(pkgPath, input string) PkgDecoder { r := strings.NewReader(input) - assert(binary.Read(r, binary.LittleEndian, &pr.version) == nil) + var ver uint32 + assert(binary.Read(r, binary.LittleEndian, &ver) == nil) + pr.version = Version(ver) - switch pr.version { - default: - panic(fmt.Errorf("unsupported version: %v", pr.version)) - case 0: - // no flags - case 1: + if pr.version >= numVersions { + panic(fmt.Errorf("cannot decode %q, export data version %d is greater than maximum supported version %d", pkgPath, pr.version, numVersions-1)) + } + + if pr.version.Has(Flags) { var flags uint32 assert(binary.Read(r, binary.LittleEndian, &flags) == nil) pr.sync = flags&flagSyncMarkers != 0 @@ -102,7 +101,9 @@ func NewPkgDecoder(pkgPath, input string) PkgDecoder { assert(err == nil) pr.elemData = input[pos:] - assert(len(pr.elemData)-8 == int(pr.elemEnds[len(pr.elemEnds)-1])) + + const fingerprintSize = 8 + assert(len(pr.elemData)-fingerprintSize == int(pr.elemEnds[len(pr.elemEnds)-1])) return pr } @@ -136,7 +137,7 @@ func (pr *PkgDecoder) AbsIdx(k RelocKind, idx Index) int { absIdx += int(pr.elemEndsEnds[k-1]) } if absIdx >= int(pr.elemEndsEnds[k]) { - errorf("%v:%v is out of bounds; %v", k, idx, pr.elemEndsEnds) + panicf("%v:%v is out of bounds; %v", k, idx, pr.elemEndsEnds) } return absIdx } @@ -193,9 +194,7 @@ func (pr *PkgDecoder) NewDecoderRaw(k RelocKind, idx Index) Decoder { Idx: idx, } - // TODO(mdempsky) r.data.Reset(...) after #44505 is resolved. - r.Data = *strings.NewReader(pr.DataIdx(k, idx)) - + r.Data.Reset(pr.DataIdx(k, idx)) r.Sync(SyncRelocs) r.Relocs = make([]RelocEnt, r.Len()) for i := range r.Relocs { @@ -244,7 +243,7 @@ type Decoder struct { func (r *Decoder) checkErr(err error) { if err != nil { - errorf("unexpected decoding error: %w", err) + panicf("unexpected decoding error: %w", err) } } @@ -515,3 +514,6 @@ func (pr *PkgDecoder) PeekObj(idx Index) (string, string, CodeObj) { return path, name, tag } + +// Version reports the version of the bitstream. +func (w *Decoder) Version() Version { return w.common.version } diff --git a/vendor/golang.org/x/tools/internal/pkgbits/encoder.go b/vendor/golang.org/x/tools/internal/pkgbits/encoder.go index 6482617a..c17a1239 100644 --- a/vendor/golang.org/x/tools/internal/pkgbits/encoder.go +++ b/vendor/golang.org/x/tools/internal/pkgbits/encoder.go @@ -12,18 +12,15 @@ import ( "io" "math/big" "runtime" + "strings" ) -// currentVersion is the current version number. -// -// - v0: initial prototype -// -// - v1: adds the flags uint32 word -const currentVersion uint32 = 1 - // A PkgEncoder provides methods for encoding a package's Unified IR // export data. type PkgEncoder struct { + // version of the bitstream. + version Version + // elems holds the bitstream for previously encoded elements. elems [numRelocs][]string @@ -47,8 +44,9 @@ func (pw *PkgEncoder) SyncMarkers() bool { return pw.syncFrames >= 0 } // export data files, but can help diagnosing desync errors in // higher-level Unified IR reader/writer code. If syncFrames is // negative, then sync markers are omitted entirely. -func NewPkgEncoder(syncFrames int) PkgEncoder { +func NewPkgEncoder(version Version, syncFrames int) PkgEncoder { return PkgEncoder{ + version: version, stringsIdx: make(map[string]Index), syncFrames: syncFrames, } @@ -64,13 +62,15 @@ func (pw *PkgEncoder) DumpTo(out0 io.Writer) (fingerprint [8]byte) { assert(binary.Write(out, binary.LittleEndian, x) == nil) } - writeUint32(currentVersion) + writeUint32(uint32(pw.version)) - var flags uint32 - if pw.SyncMarkers() { - flags |= flagSyncMarkers + if pw.version.Has(Flags) { + var flags uint32 + if pw.SyncMarkers() { + flags |= flagSyncMarkers + } + writeUint32(flags) } - writeUint32(flags) // Write elemEndsEnds. var sum uint32 @@ -159,7 +159,7 @@ type Encoder struct { // Flush finalizes the element's bitstream and returns its Index. func (w *Encoder) Flush() Index { - var sb bytes.Buffer // TODO(mdempsky): strings.Builder after #44505 is resolved + var sb strings.Builder // Backup the data so we write the relocations at the front. var tmp bytes.Buffer @@ -189,7 +189,7 @@ func (w *Encoder) Flush() Index { func (w *Encoder) checkErr(err error) { if err != nil { - errorf("unexpected encoding error: %v", err) + panicf("unexpected encoding error: %v", err) } } @@ -320,8 +320,14 @@ func (w *Encoder) Code(c Code) { // section (if not already present), and then writing a relocation // into the element bitstream. func (w *Encoder) String(s string) { + w.StringRef(w.p.StringIdx(s)) +} + +// StringRef writes a reference to the given index, which must be a +// previously encoded string value. +func (w *Encoder) StringRef(idx Index) { w.Sync(SyncString) - w.Reloc(RelocString, w.p.StringIdx(s)) + w.Reloc(RelocString, idx) } // Strings encodes and writes a variable-length slice of strings into @@ -348,7 +354,7 @@ func (w *Encoder) Value(val constant.Value) { func (w *Encoder) scalar(val constant.Value) { switch v := constant.Val(val).(type) { default: - errorf("unhandled %v (%v)", val, val.Kind()) + panicf("unhandled %v (%v)", val, val.Kind()) case bool: w.Code(ValBool) w.Bool(v) @@ -381,3 +387,6 @@ func (w *Encoder) bigFloat(v *big.Float) { b := v.Append(nil, 'p', -1) w.String(string(b)) // TODO: More efficient encoding. } + +// Version reports the version of the bitstream. +func (w *Encoder) Version() Version { return w.p.version } diff --git a/vendor/golang.org/x/tools/internal/pkgbits/frames_go1.go b/vendor/golang.org/x/tools/internal/pkgbits/frames_go1.go deleted file mode 100644 index 5294f6a6..00000000 --- a/vendor/golang.org/x/tools/internal/pkgbits/frames_go1.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2021 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !go1.7 -// +build !go1.7 - -// TODO(mdempsky): Remove after #44505 is resolved - -package pkgbits - -import "runtime" - -func walkFrames(pcs []uintptr, visit frameVisitor) { - for _, pc := range pcs { - fn := runtime.FuncForPC(pc) - file, line := fn.FileLine(pc) - - visit(file, line, fn.Name(), pc-fn.Entry()) - } -} diff --git a/vendor/golang.org/x/tools/internal/pkgbits/frames_go17.go b/vendor/golang.org/x/tools/internal/pkgbits/frames_go17.go deleted file mode 100644 index 2324ae7a..00000000 --- a/vendor/golang.org/x/tools/internal/pkgbits/frames_go17.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2021 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build go1.7 -// +build go1.7 - -package pkgbits - -import "runtime" - -// walkFrames calls visit for each call frame represented by pcs. -// -// pcs should be a slice of PCs, as returned by runtime.Callers. -func walkFrames(pcs []uintptr, visit frameVisitor) { - if len(pcs) == 0 { - return - } - - frames := runtime.CallersFrames(pcs) - for { - frame, more := frames.Next() - visit(frame.File, frame.Line, frame.Function, frame.PC-frame.Entry) - if !more { - return - } - } -} diff --git a/vendor/golang.org/x/tools/internal/pkgbits/support.go b/vendor/golang.org/x/tools/internal/pkgbits/support.go index ad26d3b2..50534a29 100644 --- a/vendor/golang.org/x/tools/internal/pkgbits/support.go +++ b/vendor/golang.org/x/tools/internal/pkgbits/support.go @@ -12,6 +12,6 @@ func assert(b bool) { } } -func errorf(format string, args ...interface{}) { +func panicf(format string, args ...any) { panic(fmt.Errorf(format, args...)) } diff --git a/vendor/golang.org/x/tools/internal/pkgbits/sync.go b/vendor/golang.org/x/tools/internal/pkgbits/sync.go index 5bd51ef7..1520b73a 100644 --- a/vendor/golang.org/x/tools/internal/pkgbits/sync.go +++ b/vendor/golang.org/x/tools/internal/pkgbits/sync.go @@ -6,6 +6,7 @@ package pkgbits import ( "fmt" + "runtime" "strings" ) @@ -23,6 +24,24 @@ func fmtFrames(pcs ...uintptr) []string { type frameVisitor func(file string, line int, name string, offset uintptr) +// walkFrames calls visit for each call frame represented by pcs. +// +// pcs should be a slice of PCs, as returned by runtime.Callers. +func walkFrames(pcs []uintptr, visit frameVisitor) { + if len(pcs) == 0 { + return + } + + frames := runtime.CallersFrames(pcs) + for { + frame, more := frames.Next() + visit(frame.File, frame.Line, frame.Function, frame.PC-frame.Entry) + if !more { + return + } + } +} + // SyncMarker is an enum type that represents markers that may be // written to export data to ensure the reader and writer stay // synchronized. @@ -110,4 +129,8 @@ const ( SyncStmtsEnd SyncLabel SyncOptLabel + + SyncMultiExpr + SyncRType + SyncConvRTTI ) diff --git a/vendor/golang.org/x/tools/internal/pkgbits/syncmarker_string.go b/vendor/golang.org/x/tools/internal/pkgbits/syncmarker_string.go index 4a5b0ca5..582ad56d 100644 --- a/vendor/golang.org/x/tools/internal/pkgbits/syncmarker_string.go +++ b/vendor/golang.org/x/tools/internal/pkgbits/syncmarker_string.go @@ -74,11 +74,14 @@ func _() { _ = x[SyncStmtsEnd-64] _ = x[SyncLabel-65] _ = x[SyncOptLabel-66] + _ = x[SyncMultiExpr-67] + _ = x[SyncRType-68] + _ = x[SyncConvRTTI-69] } -const _SyncMarker_name = "EOFBoolInt64Uint64StringValueValRelocsRelocUseRelocPublicPosPosBaseObjectObject1PkgPkgDefMethodTypeTypeIdxTypeParamNamesSignatureParamsParamCodeObjSymLocalIdentSelectorPrivateFuncExtVarExtTypeExtPragmaExprListExprsExprExprTypeAssignOpFuncLitCompLitDeclFuncBodyOpenScopeCloseScopeCloseAnotherScopeDeclNamesDeclNameStmtsBlockStmtIfStmtForStmtSwitchStmtRangeStmtCaseClauseCommClauseSelectStmtDeclsLabeledStmtUseObjLocalAddLocalLinknameStmt1StmtsEndLabelOptLabel" +const _SyncMarker_name = "EOFBoolInt64Uint64StringValueValRelocsRelocUseRelocPublicPosPosBaseObjectObject1PkgPkgDefMethodTypeTypeIdxTypeParamNamesSignatureParamsParamCodeObjSymLocalIdentSelectorPrivateFuncExtVarExtTypeExtPragmaExprListExprsExprExprTypeAssignOpFuncLitCompLitDeclFuncBodyOpenScopeCloseScopeCloseAnotherScopeDeclNamesDeclNameStmtsBlockStmtIfStmtForStmtSwitchStmtRangeStmtCaseClauseCommClauseSelectStmtDeclsLabeledStmtUseObjLocalAddLocalLinknameStmt1StmtsEndLabelOptLabelMultiExprRTypeConvRTTI" -var _SyncMarker_index = [...]uint16{0, 3, 7, 12, 18, 24, 29, 32, 38, 43, 51, 57, 60, 67, 73, 80, 83, 89, 95, 99, 106, 120, 129, 135, 140, 147, 150, 160, 168, 175, 182, 188, 195, 201, 209, 214, 218, 226, 232, 234, 241, 248, 252, 260, 269, 279, 296, 305, 313, 318, 327, 333, 340, 350, 359, 369, 379, 389, 394, 405, 416, 424, 432, 437, 445, 450, 458} +var _SyncMarker_index = [...]uint16{0, 3, 7, 12, 18, 24, 29, 32, 38, 43, 51, 57, 60, 67, 73, 80, 83, 89, 95, 99, 106, 120, 129, 135, 140, 147, 150, 160, 168, 175, 182, 188, 195, 201, 209, 214, 218, 226, 232, 234, 241, 248, 252, 260, 269, 279, 296, 305, 313, 318, 327, 333, 340, 350, 359, 369, 379, 389, 394, 405, 416, 424, 432, 437, 445, 450, 458, 467, 472, 480} func (i SyncMarker) String() string { i -= 1 diff --git a/vendor/golang.org/x/tools/internal/pkgbits/version.go b/vendor/golang.org/x/tools/internal/pkgbits/version.go new file mode 100644 index 00000000..53af9df2 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/pkgbits/version.go @@ -0,0 +1,85 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkgbits + +// Version indicates a version of a unified IR bitstream. +// Each Version indicates the addition, removal, or change of +// new data in the bitstream. +// +// These are serialized to disk and the interpretation remains fixed. +type Version uint32 + +const ( + // V0: initial prototype. + // + // All data that is not assigned a Field is in version V0 + // and has not been deprecated. + V0 Version = iota + + // V1: adds the Flags uint32 word + V1 + + // V2: removes unused legacy fields and supports type parameters for aliases. + // - remove the legacy "has init" bool from the public root + // - remove obj's "derived func instance" bool + // - add a TypeParamNames field to ObjAlias + // - remove derived info "needed" bool + V2 + + numVersions = iota +) + +// Field denotes a unit of data in the serialized unified IR bitstream. +// It is conceptually a like field in a structure. +// +// We only really need Fields when the data may or may not be present +// in a stream based on the Version of the bitstream. +// +// Unlike much of pkgbits, Fields are not serialized and +// can change values as needed. +type Field int + +const ( + // Flags in a uint32 in the header of a bitstream + // that is used to indicate whether optional features are enabled. + Flags Field = iota + + // Deprecated: HasInit was a bool indicating whether a package + // has any init functions. + HasInit + + // Deprecated: DerivedFuncInstance was a bool indicating + // whether an object was a function instance. + DerivedFuncInstance + + // ObjAlias has a list of TypeParamNames. + AliasTypeParamNames + + // Deprecated: DerivedInfoNeeded was a bool indicating + // whether a type was a derived type. + DerivedInfoNeeded + + numFields = iota +) + +// introduced is the version a field was added. +var introduced = [numFields]Version{ + Flags: V1, + AliasTypeParamNames: V2, +} + +// removed is the version a field was removed in or 0 for fields +// that have not yet been deprecated. +// (So removed[f]-1 is the last version it is included in.) +var removed = [numFields]Version{ + HasInit: V2, + DerivedFuncInstance: V2, + DerivedInfoNeeded: V2, +} + +// Has reports whether field f is present in a bitstream at version v. +func (v Version) Has(f Field) bool { + return introduced[f] <= v && (v < removed[f] || removed[f] == V0) +} diff --git a/vendor/golang.org/x/tools/internal/stdlib/manifest.go b/vendor/golang.org/x/tools/internal/stdlib/manifest.go index a928acf2..cdaac9ab 100644 --- a/vendor/golang.org/x/tools/internal/stdlib/manifest.go +++ b/vendor/golang.org/x/tools/internal/stdlib/manifest.go @@ -951,7 +951,7 @@ var PackageSymbols = map[string][]Symbol{ {"ParseSessionState", Func, 21}, {"QUICClient", Func, 21}, {"QUICConfig", Type, 21}, - {"QUICConfig.EnableStoreSessionEvent", Field, 23}, + {"QUICConfig.EnableSessionEvents", Field, 23}, {"QUICConfig.TLSConfig", Field, 21}, {"QUICConn", Type, 21}, {"QUICEncryptionLevel", Type, 21}, diff --git a/vendor/golang.org/x/tools/internal/tokeninternal/tokeninternal.go b/vendor/golang.org/x/tools/internal/tokeninternal/tokeninternal.go deleted file mode 100644 index ff9437a3..00000000 --- a/vendor/golang.org/x/tools/internal/tokeninternal/tokeninternal.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2023 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// package tokeninternal provides access to some internal features of the token -// package. -package tokeninternal - -import ( - "fmt" - "go/token" - "sort" - "sync" - "unsafe" -) - -// GetLines returns the table of line-start offsets from a token.File. -func GetLines(file *token.File) []int { - // token.File has a Lines method on Go 1.21 and later. - if file, ok := (interface{})(file).(interface{ Lines() []int }); ok { - return file.Lines() - } - - // This declaration must match that of token.File. - // This creates a risk of dependency skew. - // For now we check that the size of the two - // declarations is the same, on the (fragile) assumption - // that future changes would add fields. - type tokenFile119 struct { - _ string - _ int - _ int - mu sync.Mutex // we're not complete monsters - lines []int - _ []struct{} - } - - if unsafe.Sizeof(*file) != unsafe.Sizeof(tokenFile119{}) { - panic("unexpected token.File size") - } - var ptr *tokenFile119 - type uP = unsafe.Pointer - *(*uP)(uP(&ptr)) = uP(file) - ptr.mu.Lock() - defer ptr.mu.Unlock() - return ptr.lines -} - -// AddExistingFiles adds the specified files to the FileSet if they -// are not already present. It panics if any pair of files in the -// resulting FileSet would overlap. -func AddExistingFiles(fset *token.FileSet, files []*token.File) { - // Punch through the FileSet encapsulation. - type tokenFileSet struct { - // This type remained essentially consistent from go1.16 to go1.21. - mutex sync.RWMutex - base int - files []*token.File - _ *token.File // changed to atomic.Pointer[token.File] in go1.19 - } - - // If the size of token.FileSet changes, this will fail to compile. - const delta = int64(unsafe.Sizeof(tokenFileSet{})) - int64(unsafe.Sizeof(token.FileSet{})) - var _ [-delta * delta]int - - type uP = unsafe.Pointer - var ptr *tokenFileSet - *(*uP)(uP(&ptr)) = uP(fset) - ptr.mutex.Lock() - defer ptr.mutex.Unlock() - - // Merge and sort. - newFiles := append(ptr.files, files...) - sort.Slice(newFiles, func(i, j int) bool { - return newFiles[i].Base() < newFiles[j].Base() - }) - - // Reject overlapping files. - // Discard adjacent identical files. - out := newFiles[:0] - for i, file := range newFiles { - if i > 0 { - prev := newFiles[i-1] - if file == prev { - continue - } - if prev.Base()+prev.Size()+1 > file.Base() { - panic(fmt.Sprintf("file %s (%d-%d) overlaps with file %s (%d-%d)", - prev.Name(), prev.Base(), prev.Base()+prev.Size(), - file.Name(), file.Base(), file.Base()+file.Size())) - } - } - out = append(out, file) - } - newFiles = out - - ptr.files = newFiles - - // Advance FileSet.Base(). - if len(newFiles) > 0 { - last := newFiles[len(newFiles)-1] - newBase := last.Base() + last.Size() + 1 - if ptr.base < newBase { - ptr.base = newBase - } - } -} - -// FileSetFor returns a new FileSet containing a sequence of new Files with -// the same base, size, and line as the input files, for use in APIs that -// require a FileSet. -// -// Precondition: the input files must be non-overlapping, and sorted in order -// of their Base. -func FileSetFor(files ...*token.File) *token.FileSet { - fset := token.NewFileSet() - for _, f := range files { - f2 := fset.AddFile(f.Name(), f.Base(), f.Size()) - lines := GetLines(f) - f2.SetLines(lines) - } - return fset -} - -// CloneFileSet creates a new FileSet holding all files in fset. It does not -// create copies of the token.Files in fset: they are added to the resulting -// FileSet unmodified. -func CloneFileSet(fset *token.FileSet) *token.FileSet { - var files []*token.File - fset.Iterate(func(f *token.File) bool { - files = append(files, f) - return true - }) - newFileSet := token.NewFileSet() - AddExistingFiles(newFileSet, files) - return newFileSet -} diff --git a/vendor/golang.org/x/tools/internal/typeparams/common.go b/vendor/golang.org/x/tools/internal/typeparams/common.go new file mode 100644 index 00000000..0b84acc5 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/typeparams/common.go @@ -0,0 +1,140 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package typeparams contains common utilities for writing tools that +// interact with generic Go code, as introduced with Go 1.18. It +// supplements the standard library APIs. Notably, the StructuralTerms +// API computes a minimal representation of the structural +// restrictions on a type parameter. +// +// An external version of these APIs is available in the +// golang.org/x/exp/typeparams module. +package typeparams + +import ( + "go/ast" + "go/token" + "go/types" +) + +// UnpackIndexExpr extracts data from AST nodes that represent index +// expressions. +// +// For an ast.IndexExpr, the resulting indices slice will contain exactly one +// index expression. For an ast.IndexListExpr (go1.18+), it may have a variable +// number of index expressions. +// +// For nodes that don't represent index expressions, the first return value of +// UnpackIndexExpr will be nil. +func UnpackIndexExpr(n ast.Node) (x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) { + switch e := n.(type) { + case *ast.IndexExpr: + return e.X, e.Lbrack, []ast.Expr{e.Index}, e.Rbrack + case *ast.IndexListExpr: + return e.X, e.Lbrack, e.Indices, e.Rbrack + } + return nil, token.NoPos, nil, token.NoPos +} + +// PackIndexExpr returns an *ast.IndexExpr or *ast.IndexListExpr, depending on +// the cardinality of indices. Calling PackIndexExpr with len(indices) == 0 +// will panic. +func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) ast.Expr { + switch len(indices) { + case 0: + panic("empty indices") + case 1: + return &ast.IndexExpr{ + X: x, + Lbrack: lbrack, + Index: indices[0], + Rbrack: rbrack, + } + default: + return &ast.IndexListExpr{ + X: x, + Lbrack: lbrack, + Indices: indices, + Rbrack: rbrack, + } + } +} + +// IsTypeParam reports whether t is a type parameter (or an alias of one). +func IsTypeParam(t types.Type) bool { + _, ok := types.Unalias(t).(*types.TypeParam) + return ok +} + +// GenericAssignableTo is a generalization of types.AssignableTo that +// implements the following rule for uninstantiated generic types: +// +// If V and T are generic named types, then V is considered assignable to T if, +// for every possible instantiation of V[A_1, ..., A_N], the instantiation +// T[A_1, ..., A_N] is valid and V[A_1, ..., A_N] implements T[A_1, ..., A_N]. +// +// If T has structural constraints, they must be satisfied by V. +// +// For example, consider the following type declarations: +// +// type Interface[T any] interface { +// Accept(T) +// } +// +// type Container[T any] struct { +// Element T +// } +// +// func (c Container[T]) Accept(t T) { c.Element = t } +// +// In this case, GenericAssignableTo reports that instantiations of Container +// are assignable to the corresponding instantiation of Interface. +func GenericAssignableTo(ctxt *types.Context, V, T types.Type) bool { + V = types.Unalias(V) + T = types.Unalias(T) + + // If V and T are not both named, or do not have matching non-empty type + // parameter lists, fall back on types.AssignableTo. + + VN, Vnamed := V.(*types.Named) + TN, Tnamed := T.(*types.Named) + if !Vnamed || !Tnamed { + return types.AssignableTo(V, T) + } + + vtparams := VN.TypeParams() + ttparams := TN.TypeParams() + if vtparams.Len() == 0 || vtparams.Len() != ttparams.Len() || VN.TypeArgs().Len() != 0 || TN.TypeArgs().Len() != 0 { + return types.AssignableTo(V, T) + } + + // V and T have the same (non-zero) number of type params. Instantiate both + // with the type parameters of V. This must always succeed for V, and will + // succeed for T if and only if the type set of each type parameter of V is a + // subset of the type set of the corresponding type parameter of T, meaning + // that every instantiation of V corresponds to a valid instantiation of T. + + // Minor optimization: ensure we share a context across the two + // instantiations below. + if ctxt == nil { + ctxt = types.NewContext() + } + + var targs []types.Type + for i := 0; i < vtparams.Len(); i++ { + targs = append(targs, vtparams.At(i)) + } + + vinst, err := types.Instantiate(ctxt, V, targs, true) + if err != nil { + panic("type parameters should satisfy their own constraints") + } + + tinst, err := types.Instantiate(ctxt, T, targs, true) + if err != nil { + return false + } + + return types.AssignableTo(vinst, tinst) +} diff --git a/vendor/golang.org/x/tools/internal/typeparams/coretype.go b/vendor/golang.org/x/tools/internal/typeparams/coretype.go new file mode 100644 index 00000000..6e83c6fb --- /dev/null +++ b/vendor/golang.org/x/tools/internal/typeparams/coretype.go @@ -0,0 +1,150 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typeparams + +import ( + "fmt" + "go/types" +) + +// CoreType returns the core type of T or nil if T does not have a core type. +// +// See https://go.dev/ref/spec#Core_types for the definition of a core type. +func CoreType(T types.Type) types.Type { + U := T.Underlying() + if _, ok := U.(*types.Interface); !ok { + return U // for non-interface types, + } + + terms, err := NormalTerms(U) + if len(terms) == 0 || err != nil { + // len(terms) -> empty type set of interface. + // err != nil => U is invalid, exceeds complexity bounds, or has an empty type set. + return nil // no core type. + } + + U = terms[0].Type().Underlying() + var identical int // i in [0,identical) => Identical(U, terms[i].Type().Underlying()) + for identical = 1; identical < len(terms); identical++ { + if !types.Identical(U, terms[identical].Type().Underlying()) { + break + } + } + + if identical == len(terms) { + // https://go.dev/ref/spec#Core_types + // "There is a single type U which is the underlying type of all types in the type set of T" + return U + } + ch, ok := U.(*types.Chan) + if !ok { + return nil // no core type as identical < len(terms) and U is not a channel. + } + // https://go.dev/ref/spec#Core_types + // "the type chan E if T contains only bidirectional channels, or the type chan<- E or + // <-chan E depending on the direction of the directional channels present." + for chans := identical; chans < len(terms); chans++ { + curr, ok := terms[chans].Type().Underlying().(*types.Chan) + if !ok { + return nil + } + if !types.Identical(ch.Elem(), curr.Elem()) { + return nil // channel elements are not identical. + } + if ch.Dir() == types.SendRecv { + // ch is bidirectional. We can safely always use curr's direction. + ch = curr + } else if curr.Dir() != types.SendRecv && ch.Dir() != curr.Dir() { + // ch and curr are not bidirectional and not the same direction. + return nil + } + } + return ch +} + +// NormalTerms returns a slice of terms representing the normalized structural +// type restrictions of a type, if any. +// +// For all types other than *types.TypeParam, *types.Interface, and +// *types.Union, this is just a single term with Tilde() == false and +// Type() == typ. For *types.TypeParam, *types.Interface, and *types.Union, see +// below. +// +// Structural type restrictions of a type parameter are created via +// non-interface types embedded in its constraint interface (directly, or via a +// chain of interface embeddings). For example, in the declaration type +// T[P interface{~int; m()}] int the structural restriction of the type +// parameter P is ~int. +// +// With interface embedding and unions, the specification of structural type +// restrictions may be arbitrarily complex. For example, consider the +// following: +// +// type A interface{ ~string|~[]byte } +// +// type B interface{ int|string } +// +// type C interface { ~string|~int } +// +// type T[P interface{ A|B; C }] int +// +// In this example, the structural type restriction of P is ~string|int: A|B +// expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int, +// which when intersected with C (~string|~int) yields ~string|int. +// +// NormalTerms computes these expansions and reductions, producing a +// "normalized" form of the embeddings. A structural restriction is normalized +// if it is a single union containing no interface terms, and is minimal in the +// sense that removing any term changes the set of types satisfying the +// constraint. It is left as a proof for the reader that, modulo sorting, there +// is exactly one such normalized form. +// +// Because the minimal representation always takes this form, NormalTerms +// returns a slice of tilde terms corresponding to the terms of the union in +// the normalized structural restriction. An error is returned if the type is +// invalid, exceeds complexity bounds, or has an empty type set. In the latter +// case, NormalTerms returns ErrEmptyTypeSet. +// +// NormalTerms makes no guarantees about the order of terms, except that it +// is deterministic. +func NormalTerms(typ types.Type) ([]*types.Term, error) { + switch typ := typ.Underlying().(type) { + case *types.TypeParam: + return StructuralTerms(typ) + case *types.Union: + return UnionTermSet(typ) + case *types.Interface: + return InterfaceTermSet(typ) + default: + return []*types.Term{types.NewTerm(false, typ)}, nil + } +} + +// Deref returns the type of the variable pointed to by t, +// if t's core type is a pointer; otherwise it returns t. +// +// Do not assume that Deref(T)==T implies T is not a pointer: +// consider "type T *T", for example. +// +// TODO(adonovan): ideally this would live in typesinternal, but that +// creates an import cycle. Move there when we melt this package down. +func Deref(t types.Type) types.Type { + if ptr, ok := CoreType(t).(*types.Pointer); ok { + return ptr.Elem() + } + return t +} + +// MustDeref returns the type of the variable pointed to by t. +// It panics if t's core type is not a pointer. +// +// TODO(adonovan): ideally this would live in typesinternal, but that +// creates an import cycle. Move there when we melt this package down. +func MustDeref(t types.Type) types.Type { + if ptr, ok := CoreType(t).(*types.Pointer); ok { + return ptr.Elem() + } + panic(fmt.Sprintf("%v is not a pointer", t)) +} diff --git a/vendor/golang.org/x/tools/internal/typeparams/free.go b/vendor/golang.org/x/tools/internal/typeparams/free.go new file mode 100644 index 00000000..35810826 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/typeparams/free.go @@ -0,0 +1,118 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typeparams + +import ( + "go/types" +) + +// Free is a memoization of the set of free type parameters within a +// type. It makes a sequence of calls to [Free.Has] for overlapping +// types more efficient. The zero value is ready for use. +// +// NOTE: Adapted from go/types/infer.go. If it is later exported, factor. +type Free struct { + seen map[types.Type]bool +} + +// Has reports whether the specified type has a free type parameter. +func (w *Free) Has(typ types.Type) (res bool) { + // detect cycles + if x, ok := w.seen[typ]; ok { + return x + } + if w.seen == nil { + w.seen = make(map[types.Type]bool) + } + w.seen[typ] = false + defer func() { + w.seen[typ] = res + }() + + switch t := typ.(type) { + case nil, *types.Basic: // TODO(gri) should nil be handled here? + break + + case *types.Alias: + return w.Has(types.Unalias(t)) + + case *types.Array: + return w.Has(t.Elem()) + + case *types.Slice: + return w.Has(t.Elem()) + + case *types.Struct: + for i, n := 0, t.NumFields(); i < n; i++ { + if w.Has(t.Field(i).Type()) { + return true + } + } + + case *types.Pointer: + return w.Has(t.Elem()) + + case *types.Tuple: + n := t.Len() + for i := 0; i < n; i++ { + if w.Has(t.At(i).Type()) { + return true + } + } + + case *types.Signature: + // t.tparams may not be nil if we are looking at a signature + // of a generic function type (or an interface method) that is + // part of the type we're testing. We don't care about these type + // parameters. + // Similarly, the receiver of a method may declare (rather than + // use) type parameters, we don't care about those either. + // Thus, we only need to look at the input and result parameters. + return w.Has(t.Params()) || w.Has(t.Results()) + + case *types.Interface: + for i, n := 0, t.NumMethods(); i < n; i++ { + if w.Has(t.Method(i).Type()) { + return true + } + } + terms, err := InterfaceTermSet(t) + if err != nil { + return false // ill typed + } + for _, term := range terms { + if w.Has(term.Type()) { + return true + } + } + + case *types.Map: + return w.Has(t.Key()) || w.Has(t.Elem()) + + case *types.Chan: + return w.Has(t.Elem()) + + case *types.Named: + args := t.TypeArgs() + // TODO(taking): this does not match go/types/infer.go. Check with rfindley. + if params := t.TypeParams(); params.Len() > args.Len() { + return true + } + for i, n := 0, args.Len(); i < n; i++ { + if w.Has(args.At(i)) { + return true + } + } + return w.Has(t.Underlying()) // recurse for types local to parameterized functions + + case *types.TypeParam: + return true + + default: + panic(t) // unreachable + } + + return false +} diff --git a/vendor/golang.org/x/tools/internal/typeparams/normalize.go b/vendor/golang.org/x/tools/internal/typeparams/normalize.go new file mode 100644 index 00000000..93c80fdc --- /dev/null +++ b/vendor/golang.org/x/tools/internal/typeparams/normalize.go @@ -0,0 +1,218 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typeparams + +import ( + "errors" + "fmt" + "go/types" + "os" + "strings" +) + +//go:generate go run copytermlist.go + +const debug = false + +var ErrEmptyTypeSet = errors.New("empty type set") + +// StructuralTerms returns a slice of terms representing the normalized +// structural type restrictions of a type parameter, if any. +// +// Structural type restrictions of a type parameter are created via +// non-interface types embedded in its constraint interface (directly, or via a +// chain of interface embeddings). For example, in the declaration +// +// type T[P interface{~int; m()}] int +// +// the structural restriction of the type parameter P is ~int. +// +// With interface embedding and unions, the specification of structural type +// restrictions may be arbitrarily complex. For example, consider the +// following: +// +// type A interface{ ~string|~[]byte } +// +// type B interface{ int|string } +// +// type C interface { ~string|~int } +// +// type T[P interface{ A|B; C }] int +// +// In this example, the structural type restriction of P is ~string|int: A|B +// expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int, +// which when intersected with C (~string|~int) yields ~string|int. +// +// StructuralTerms computes these expansions and reductions, producing a +// "normalized" form of the embeddings. A structural restriction is normalized +// if it is a single union containing no interface terms, and is minimal in the +// sense that removing any term changes the set of types satisfying the +// constraint. It is left as a proof for the reader that, modulo sorting, there +// is exactly one such normalized form. +// +// Because the minimal representation always takes this form, StructuralTerms +// returns a slice of tilde terms corresponding to the terms of the union in +// the normalized structural restriction. An error is returned if the +// constraint interface is invalid, exceeds complexity bounds, or has an empty +// type set. In the latter case, StructuralTerms returns ErrEmptyTypeSet. +// +// StructuralTerms makes no guarantees about the order of terms, except that it +// is deterministic. +func StructuralTerms(tparam *types.TypeParam) ([]*types.Term, error) { + constraint := tparam.Constraint() + if constraint == nil { + return nil, fmt.Errorf("%s has nil constraint", tparam) + } + iface, _ := constraint.Underlying().(*types.Interface) + if iface == nil { + return nil, fmt.Errorf("constraint is %T, not *types.Interface", constraint.Underlying()) + } + return InterfaceTermSet(iface) +} + +// InterfaceTermSet computes the normalized terms for a constraint interface, +// returning an error if the term set cannot be computed or is empty. In the +// latter case, the error will be ErrEmptyTypeSet. +// +// See the documentation of StructuralTerms for more information on +// normalization. +func InterfaceTermSet(iface *types.Interface) ([]*types.Term, error) { + return computeTermSet(iface) +} + +// UnionTermSet computes the normalized terms for a union, returning an error +// if the term set cannot be computed or is empty. In the latter case, the +// error will be ErrEmptyTypeSet. +// +// See the documentation of StructuralTerms for more information on +// normalization. +func UnionTermSet(union *types.Union) ([]*types.Term, error) { + return computeTermSet(union) +} + +func computeTermSet(typ types.Type) ([]*types.Term, error) { + tset, err := computeTermSetInternal(typ, make(map[types.Type]*termSet), 0) + if err != nil { + return nil, err + } + if tset.terms.isEmpty() { + return nil, ErrEmptyTypeSet + } + if tset.terms.isAll() { + return nil, nil + } + var terms []*types.Term + for _, term := range tset.terms { + terms = append(terms, types.NewTerm(term.tilde, term.typ)) + } + return terms, nil +} + +// A termSet holds the normalized set of terms for a given type. +// +// The name termSet is intentionally distinct from 'type set': a type set is +// all types that implement a type (and includes method restrictions), whereas +// a term set just represents the structural restrictions on a type. +type termSet struct { + complete bool + terms termlist +} + +func indentf(depth int, format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, strings.Repeat(".", depth)+format+"\n", args...) +} + +func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth int) (res *termSet, err error) { + if t == nil { + panic("nil type") + } + + if debug { + indentf(depth, "%s", t.String()) + defer func() { + if err != nil { + indentf(depth, "=> %s", err) + } else { + indentf(depth, "=> %s", res.terms.String()) + } + }() + } + + const maxTermCount = 100 + if tset, ok := seen[t]; ok { + if !tset.complete { + return nil, fmt.Errorf("cycle detected in the declaration of %s", t) + } + return tset, nil + } + + // Mark the current type as seen to avoid infinite recursion. + tset := new(termSet) + defer func() { + tset.complete = true + }() + seen[t] = tset + + switch u := t.Underlying().(type) { + case *types.Interface: + // The term set of an interface is the intersection of the term sets of its + // embedded types. + tset.terms = allTermlist + for i := 0; i < u.NumEmbeddeds(); i++ { + embedded := u.EmbeddedType(i) + if _, ok := embedded.Underlying().(*types.TypeParam); ok { + return nil, fmt.Errorf("invalid embedded type %T", embedded) + } + tset2, err := computeTermSetInternal(embedded, seen, depth+1) + if err != nil { + return nil, err + } + tset.terms = tset.terms.intersect(tset2.terms) + } + case *types.Union: + // The term set of a union is the union of term sets of its terms. + tset.terms = nil + for i := 0; i < u.Len(); i++ { + t := u.Term(i) + var terms termlist + switch t.Type().Underlying().(type) { + case *types.Interface: + tset2, err := computeTermSetInternal(t.Type(), seen, depth+1) + if err != nil { + return nil, err + } + terms = tset2.terms + case *types.TypeParam, *types.Union: + // A stand-alone type parameter or union is not permitted as union + // term. + return nil, fmt.Errorf("invalid union term %T", t) + default: + if t.Type() == types.Typ[types.Invalid] { + continue + } + terms = termlist{{t.Tilde(), t.Type()}} + } + tset.terms = tset.terms.union(terms) + if len(tset.terms) > maxTermCount { + return nil, fmt.Errorf("exceeded max term count %d", maxTermCount) + } + } + case *types.TypeParam: + panic("unreachable") + default: + // For all other types, the term set is just a single non-tilde term + // holding the type itself. + if u != types.Typ[types.Invalid] { + tset.terms = termlist{{false, t}} + } + } + return tset, nil +} + +// under is a facade for the go/types internal function of the same name. It is +// used by typeterm.go. +func under(t types.Type) types.Type { + return t.Underlying() +} diff --git a/vendor/golang.org/x/tools/internal/typeparams/termlist.go b/vendor/golang.org/x/tools/internal/typeparams/termlist.go new file mode 100644 index 00000000..cbd12f80 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/typeparams/termlist.go @@ -0,0 +1,163 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Code generated by copytermlist.go DO NOT EDIT. + +package typeparams + +import ( + "bytes" + "go/types" +) + +// A termlist represents the type set represented by the union +// t1 ∪ y2 ∪ ... tn of the type sets of the terms t1 to tn. +// A termlist is in normal form if all terms are disjoint. +// termlist operations don't require the operands to be in +// normal form. +type termlist []*term + +// allTermlist represents the set of all types. +// It is in normal form. +var allTermlist = termlist{new(term)} + +// String prints the termlist exactly (without normalization). +func (xl termlist) String() string { + if len(xl) == 0 { + return "∅" + } + var buf bytes.Buffer + for i, x := range xl { + if i > 0 { + buf.WriteString(" | ") + } + buf.WriteString(x.String()) + } + return buf.String() +} + +// isEmpty reports whether the termlist xl represents the empty set of types. +func (xl termlist) isEmpty() bool { + // If there's a non-nil term, the entire list is not empty. + // If the termlist is in normal form, this requires at most + // one iteration. + for _, x := range xl { + if x != nil { + return false + } + } + return true +} + +// isAll reports whether the termlist xl represents the set of all types. +func (xl termlist) isAll() bool { + // If there's a 𝓤 term, the entire list is 𝓤. + // If the termlist is in normal form, this requires at most + // one iteration. + for _, x := range xl { + if x != nil && x.typ == nil { + return true + } + } + return false +} + +// norm returns the normal form of xl. +func (xl termlist) norm() termlist { + // Quadratic algorithm, but good enough for now. + // TODO(gri) fix asymptotic performance + used := make([]bool, len(xl)) + var rl termlist + for i, xi := range xl { + if xi == nil || used[i] { + continue + } + for j := i + 1; j < len(xl); j++ { + xj := xl[j] + if xj == nil || used[j] { + continue + } + if u1, u2 := xi.union(xj); u2 == nil { + // If we encounter a 𝓤 term, the entire list is 𝓤. + // Exit early. + // (Note that this is not just an optimization; + // if we continue, we may end up with a 𝓤 term + // and other terms and the result would not be + // in normal form.) + if u1.typ == nil { + return allTermlist + } + xi = u1 + used[j] = true // xj is now unioned into xi - ignore it in future iterations + } + } + rl = append(rl, xi) + } + return rl +} + +// union returns the union xl ∪ yl. +func (xl termlist) union(yl termlist) termlist { + return append(xl, yl...).norm() +} + +// intersect returns the intersection xl ∩ yl. +func (xl termlist) intersect(yl termlist) termlist { + if xl.isEmpty() || yl.isEmpty() { + return nil + } + + // Quadratic algorithm, but good enough for now. + // TODO(gri) fix asymptotic performance + var rl termlist + for _, x := range xl { + for _, y := range yl { + if r := x.intersect(y); r != nil { + rl = append(rl, r) + } + } + } + return rl.norm() +} + +// equal reports whether xl and yl represent the same type set. +func (xl termlist) equal(yl termlist) bool { + // TODO(gri) this should be more efficient + return xl.subsetOf(yl) && yl.subsetOf(xl) +} + +// includes reports whether t ∈ xl. +func (xl termlist) includes(t types.Type) bool { + for _, x := range xl { + if x.includes(t) { + return true + } + } + return false +} + +// supersetOf reports whether y ⊆ xl. +func (xl termlist) supersetOf(y *term) bool { + for _, x := range xl { + if y.subsetOf(x) { + return true + } + } + return false +} + +// subsetOf reports whether xl ⊆ yl. +func (xl termlist) subsetOf(yl termlist) bool { + if yl.isEmpty() { + return xl.isEmpty() + } + + // each term x of xl must be a subset of yl + for _, x := range xl { + if !yl.supersetOf(x) { + return false // x is not a subset yl + } + } + return true +} diff --git a/vendor/golang.org/x/tools/internal/typeparams/typeterm.go b/vendor/golang.org/x/tools/internal/typeparams/typeterm.go new file mode 100644 index 00000000..7350bb70 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/typeparams/typeterm.go @@ -0,0 +1,169 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Code generated by copytermlist.go DO NOT EDIT. + +package typeparams + +import "go/types" + +// A term describes elementary type sets: +// +// ∅: (*term)(nil) == ∅ // set of no types (empty set) +// 𝓤: &term{} == 𝓤 // set of all types (𝓤niverse) +// T: &term{false, T} == {T} // set of type T +// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t +type term struct { + tilde bool // valid if typ != nil + typ types.Type +} + +func (x *term) String() string { + switch { + case x == nil: + return "∅" + case x.typ == nil: + return "𝓤" + case x.tilde: + return "~" + x.typ.String() + default: + return x.typ.String() + } +} + +// equal reports whether x and y represent the same type set. +func (x *term) equal(y *term) bool { + // easy cases + switch { + case x == nil || y == nil: + return x == y + case x.typ == nil || y.typ == nil: + return x.typ == y.typ + } + // ∅ ⊂ x, y ⊂ 𝓤 + + return x.tilde == y.tilde && types.Identical(x.typ, y.typ) +} + +// union returns the union x ∪ y: zero, one, or two non-nil terms. +func (x *term) union(y *term) (_, _ *term) { + // easy cases + switch { + case x == nil && y == nil: + return nil, nil // ∅ ∪ ∅ == ∅ + case x == nil: + return y, nil // ∅ ∪ y == y + case y == nil: + return x, nil // x ∪ ∅ == x + case x.typ == nil: + return x, nil // 𝓤 ∪ y == 𝓤 + case y.typ == nil: + return y, nil // x ∪ 𝓤 == 𝓤 + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return x, y // x ∪ y == (x, y) if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ∪ ~t == ~t + // ~t ∪ T == ~t + // T ∪ ~t == ~t + // T ∪ T == T + if x.tilde || !y.tilde { + return x, nil + } + return y, nil +} + +// intersect returns the intersection x ∩ y. +func (x *term) intersect(y *term) *term { + // easy cases + switch { + case x == nil || y == nil: + return nil // ∅ ∩ y == ∅ and ∩ ∅ == ∅ + case x.typ == nil: + return y // 𝓤 ∩ y == y + case y.typ == nil: + return x // x ∩ 𝓤 == x + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return nil // x ∩ y == ∅ if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ∩ ~t == ~t + // ~t ∩ T == T + // T ∩ ~t == T + // T ∩ T == T + if !x.tilde || y.tilde { + return x + } + return y +} + +// includes reports whether t ∈ x. +func (x *term) includes(t types.Type) bool { + // easy cases + switch { + case x == nil: + return false // t ∈ ∅ == false + case x.typ == nil: + return true // t ∈ 𝓤 == true + } + // ∅ ⊂ x ⊂ 𝓤 + + u := t + if x.tilde { + u = under(u) + } + return types.Identical(x.typ, u) +} + +// subsetOf reports whether x ⊆ y. +func (x *term) subsetOf(y *term) bool { + // easy cases + switch { + case x == nil: + return true // ∅ ⊆ y == true + case y == nil: + return false // x ⊆ ∅ == false since x != ∅ + case y.typ == nil: + return true // x ⊆ 𝓤 == true + case x.typ == nil: + return false // 𝓤 ⊆ y == false since y != 𝓤 + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return false // x ⊆ y == false if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ⊆ ~t == true + // ~t ⊆ T == false + // T ⊆ ~t == true + // T ⊆ T == true + return !x.tilde || y.tilde +} + +// disjoint reports whether x ∩ y == ∅. +// x.typ and y.typ must not be nil. +func (x *term) disjoint(y *term) bool { + if debug && (x.typ == nil || y.typ == nil) { + panic("invalid argument(s)") + } + ux := x.typ + if y.tilde { + ux = under(ux) + } + uy := y.typ + if x.tilde { + uy = under(uy) + } + return !types.Identical(ux, uy) +} diff --git a/vendor/golang.org/x/tools/internal/typesinternal/element.go b/vendor/golang.org/x/tools/internal/typesinternal/element.go new file mode 100644 index 00000000..4957f021 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/typesinternal/element.go @@ -0,0 +1,133 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "fmt" + "go/types" + + "golang.org/x/tools/go/types/typeutil" +) + +// ForEachElement calls f for type T and each type reachable from its +// type through reflection. It does this by recursively stripping off +// type constructors; in addition, for each named type N, the type *N +// is added to the result as it may have additional methods. +// +// The caller must provide an initially empty set used to de-duplicate +// identical types, potentially across multiple calls to ForEachElement. +// (Its final value holds all the elements seen, matching the arguments +// passed to f.) +// +// TODO(adonovan): share/harmonize with go/callgraph/rta. +func ForEachElement(rtypes *typeutil.Map, msets *typeutil.MethodSetCache, T types.Type, f func(types.Type)) { + var visit func(T types.Type, skip bool) + visit = func(T types.Type, skip bool) { + if !skip { + if seen, _ := rtypes.Set(T, true).(bool); seen { + return // de-dup + } + + f(T) // notify caller of new element type + } + + // Recursion over signatures of each method. + tmset := msets.MethodSet(T) + for i := 0; i < tmset.Len(); i++ { + sig := tmset.At(i).Type().(*types.Signature) + // It is tempting to call visit(sig, false) + // but, as noted in golang.org/cl/65450043, + // the Signature.Recv field is ignored by + // types.Identical and typeutil.Map, which + // is confusing at best. + // + // More importantly, the true signature rtype + // reachable from a method using reflection + // has no receiver but an extra ordinary parameter. + // For the Read method of io.Reader we want: + // func(Reader, []byte) (int, error) + // but here sig is: + // func([]byte) (int, error) + // with .Recv = Reader (though it is hard to + // notice because it doesn't affect Signature.String + // or types.Identical). + // + // TODO(adonovan): construct and visit the correct + // non-method signature with an extra parameter + // (though since unnamed func types have no methods + // there is essentially no actual demand for this). + // + // TODO(adonovan): document whether or not it is + // safe to skip non-exported methods (as RTA does). + visit(sig.Params(), true) // skip the Tuple + visit(sig.Results(), true) // skip the Tuple + } + + switch T := T.(type) { + case *types.Alias: + visit(types.Unalias(T), skip) // emulates the pre-Alias behavior + + case *types.Basic: + // nop + + case *types.Interface: + // nop---handled by recursion over method set. + + case *types.Pointer: + visit(T.Elem(), false) + + case *types.Slice: + visit(T.Elem(), false) + + case *types.Chan: + visit(T.Elem(), false) + + case *types.Map: + visit(T.Key(), false) + visit(T.Elem(), false) + + case *types.Signature: + if T.Recv() != nil { + panic(fmt.Sprintf("Signature %s has Recv %s", T, T.Recv())) + } + visit(T.Params(), true) // skip the Tuple + visit(T.Results(), true) // skip the Tuple + + case *types.Named: + // A pointer-to-named type can be derived from a named + // type via reflection. It may have methods too. + visit(types.NewPointer(T), false) + + // Consider 'type T struct{S}' where S has methods. + // Reflection provides no way to get from T to struct{S}, + // only to S, so the method set of struct{S} is unwanted, + // so set 'skip' flag during recursion. + visit(T.Underlying(), true) // skip the unnamed type + + case *types.Array: + visit(T.Elem(), false) + + case *types.Struct: + for i, n := 0, T.NumFields(); i < n; i++ { + // TODO(adonovan): document whether or not + // it is safe to skip non-exported fields. + visit(T.Field(i).Type(), false) + } + + case *types.Tuple: + for i, n := 0, T.Len(); i < n; i++ { + visit(T.At(i).Type(), false) + } + + case *types.TypeParam, *types.Union: + // forEachReachable must not be called on parameterized types. + panic(T) + + default: + panic(T) + } + } + visit(T, false) +} diff --git a/vendor/golang.org/x/tools/internal/typesinternal/errorcode.go b/vendor/golang.org/x/tools/internal/typesinternal/errorcode.go index 834e0538..131caab2 100644 --- a/vendor/golang.org/x/tools/internal/typesinternal/errorcode.go +++ b/vendor/golang.org/x/tools/internal/typesinternal/errorcode.go @@ -838,7 +838,7 @@ const ( // InvalidCap occurs when an argument to the cap built-in function is not of // supported type. // - // See https://golang.org/ref/spec#Lengthand_capacity for information on + // See https://golang.org/ref/spec#Length_and_capacity for information on // which underlying types are supported as arguments to cap and len. // // Example: @@ -859,7 +859,7 @@ const ( // InvalidCopy occurs when the arguments are not of slice type or do not // have compatible type. // - // See https://golang.org/ref/spec#Appendingand_copying_slices for more + // See https://golang.org/ref/spec#Appending_and_copying_slices for more // information on the type requirements for the copy built-in. // // Example: @@ -897,7 +897,7 @@ const ( // InvalidLen occurs when an argument to the len built-in function is not of // supported type. // - // See https://golang.org/ref/spec#Lengthand_capacity for information on + // See https://golang.org/ref/spec#Length_and_capacity for information on // which underlying types are supported as arguments to cap and len. // // Example: @@ -914,7 +914,7 @@ const ( // InvalidMake occurs when make is called with an unsupported type argument. // - // See https://golang.org/ref/spec#Makingslices_maps_and_channels for + // See https://golang.org/ref/spec#Making_slices_maps_and_channels for // information on the types that may be created using make. // // Example: diff --git a/vendor/golang.org/x/tools/internal/typesinternal/recv.go b/vendor/golang.org/x/tools/internal/typesinternal/recv.go index fea7c8b7..ba6f4f4e 100644 --- a/vendor/golang.org/x/tools/internal/typesinternal/recv.go +++ b/vendor/golang.org/x/tools/internal/typesinternal/recv.go @@ -6,8 +6,6 @@ package typesinternal import ( "go/types" - - "golang.org/x/tools/internal/aliases" ) // ReceiverNamed returns the named type (if any) associated with the @@ -15,11 +13,11 @@ import ( // It also reports whether a Pointer was present. func ReceiverNamed(recv *types.Var) (isPtr bool, named *types.Named) { t := recv.Type() - if ptr, ok := aliases.Unalias(t).(*types.Pointer); ok { + if ptr, ok := types.Unalias(t).(*types.Pointer); ok { isPtr = true t = ptr.Elem() } - named, _ = aliases.Unalias(t).(*types.Named) + named, _ = types.Unalias(t).(*types.Named) return } @@ -36,7 +34,7 @@ func ReceiverNamed(recv *types.Var) (isPtr bool, named *types.Named) { // indirection from the type, regardless of named types (analogous to // a LOAD instruction). func Unpointer(t types.Type) types.Type { - if ptr, ok := aliases.Unalias(t).(*types.Pointer); ok { + if ptr, ok := types.Unalias(t).(*types.Pointer); ok { return ptr.Elem() } return t diff --git a/vendor/golang.org/x/tools/internal/versions/toolchain.go b/vendor/golang.org/x/tools/internal/versions/toolchain.go deleted file mode 100644 index 377bf7a5..00000000 --- a/vendor/golang.org/x/tools/internal/versions/toolchain.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2024 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package versions - -// toolchain is maximum version (<1.22) that the go toolchain used -// to build the current tool is known to support. -// -// When a tool is built with >=1.22, the value of toolchain is unused. -// -// x/tools does not support building with go <1.18. So we take this -// as the minimum possible maximum. -var toolchain string = Go1_18 diff --git a/vendor/golang.org/x/tools/internal/versions/toolchain_go120.go b/vendor/golang.org/x/tools/internal/versions/toolchain_go120.go deleted file mode 100644 index 1a9efa12..00000000 --- a/vendor/golang.org/x/tools/internal/versions/toolchain_go120.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2024 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build go1.20 -// +build go1.20 - -package versions - -func init() { - if Compare(toolchain, Go1_20) < 0 { - toolchain = Go1_20 - } -} diff --git a/vendor/golang.org/x/tools/internal/versions/toolchain_go121.go b/vendor/golang.org/x/tools/internal/versions/toolchain_go121.go deleted file mode 100644 index b7ef216d..00000000 --- a/vendor/golang.org/x/tools/internal/versions/toolchain_go121.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2024 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build go1.21 -// +build go1.21 - -package versions - -func init() { - if Compare(toolchain, Go1_21) < 0 { - toolchain = Go1_21 - } -} diff --git a/vendor/golang.org/x/tools/internal/versions/types.go b/vendor/golang.org/x/tools/internal/versions/types.go index 562eef21..f0bb0d15 100644 --- a/vendor/golang.org/x/tools/internal/versions/types.go +++ b/vendor/golang.org/x/tools/internal/versions/types.go @@ -5,15 +5,34 @@ package versions import ( + "go/ast" "go/types" ) -// GoVersion returns the Go version of the type package. -// It returns zero if no version can be determined. -func GoVersion(pkg *types.Package) string { - // TODO(taking): x/tools can call GoVersion() [from 1.21] after 1.25. - if pkg, ok := any(pkg).(interface{ GoVersion() string }); ok { - return pkg.GoVersion() +// FileVersion returns a file's Go version. +// The reported version is an unknown Future version if a +// version cannot be determined. +func FileVersion(info *types.Info, file *ast.File) string { + // In tools built with Go >= 1.22, the Go version of a file + // follow a cascades of sources: + // 1) types.Info.FileVersion, which follows the cascade: + // 1.a) file version (ast.File.GoVersion), + // 1.b) the package version (types.Config.GoVersion), or + // 2) is some unknown Future version. + // + // File versions require a valid package version to be provided to types + // in Config.GoVersion. Config.GoVersion is either from the package's module + // or the toolchain (go run). This value should be provided by go/packages + // or unitchecker.Config.GoVersion. + if v := info.FileVersions[file]; IsValid(v) { + return v } - return "" + // Note: we could instead return runtime.Version() [if valid]. + // This would act as a max version on what a tool can support. + return Future +} + +// InitFileVersions initializes info to record Go versions for Go files. +func InitFileVersions(info *types.Info) { + info.FileVersions = make(map[*ast.File]string) } diff --git a/vendor/golang.org/x/tools/internal/versions/types_go121.go b/vendor/golang.org/x/tools/internal/versions/types_go121.go deleted file mode 100644 index b4345d33..00000000 --- a/vendor/golang.org/x/tools/internal/versions/types_go121.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2023 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !go1.22 -// +build !go1.22 - -package versions - -import ( - "go/ast" - "go/types" -) - -// FileVersion returns a language version (<=1.21) derived from runtime.Version() -// or an unknown future version. -func FileVersion(info *types.Info, file *ast.File) string { - // In x/tools built with Go <= 1.21, we do not have Info.FileVersions - // available. We use a go version derived from the toolchain used to - // compile the tool by default. - // This will be <= go1.21. We take this as the maximum version that - // this tool can support. - // - // There are no features currently in x/tools that need to tell fine grained - // differences for versions <1.22. - return toolchain -} - -// InitFileVersions is a noop when compiled with this Go version. -func InitFileVersions(*types.Info) {} diff --git a/vendor/golang.org/x/tools/internal/versions/types_go122.go b/vendor/golang.org/x/tools/internal/versions/types_go122.go deleted file mode 100644 index aac5db62..00000000 --- a/vendor/golang.org/x/tools/internal/versions/types_go122.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2023 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build go1.22 -// +build go1.22 - -package versions - -import ( - "go/ast" - "go/types" -) - -// FileVersion returns a file's Go version. -// The reported version is an unknown Future version if a -// version cannot be determined. -func FileVersion(info *types.Info, file *ast.File) string { - // In tools built with Go >= 1.22, the Go version of a file - // follow a cascades of sources: - // 1) types.Info.FileVersion, which follows the cascade: - // 1.a) file version (ast.File.GoVersion), - // 1.b) the package version (types.Config.GoVersion), or - // 2) is some unknown Future version. - // - // File versions require a valid package version to be provided to types - // in Config.GoVersion. Config.GoVersion is either from the package's module - // or the toolchain (go run). This value should be provided by go/packages - // or unitchecker.Config.GoVersion. - if v := info.FileVersions[file]; IsValid(v) { - return v - } - // Note: we could instead return runtime.Version() [if valid]. - // This would act as a max version on what a tool can support. - return Future -} - -// InitFileVersions initializes info to record Go versions for Go files. -func InitFileVersions(info *types.Info) { - info.FileVersions = make(map[*ast.File]string) -} diff --git a/vendor/google.golang.org/protobuf/internal/descopts/options.go b/vendor/google.golang.org/protobuf/internal/descopts/options.go index 8401be8c..024ffebd 100644 --- a/vendor/google.golang.org/protobuf/internal/descopts/options.go +++ b/vendor/google.golang.org/protobuf/internal/descopts/options.go @@ -9,7 +9,7 @@ // dependency on the descriptor proto package). package descopts -import pref "google.golang.org/protobuf/reflect/protoreflect" +import "google.golang.org/protobuf/reflect/protoreflect" // These variables are set by the init function in descriptor.pb.go via logic // in internal/filetype. In other words, so long as the descriptor proto package @@ -17,13 +17,13 @@ import pref "google.golang.org/protobuf/reflect/protoreflect" // // Each variable is populated with a nil pointer to the options struct. var ( - File pref.ProtoMessage - Enum pref.ProtoMessage - EnumValue pref.ProtoMessage - Message pref.ProtoMessage - Field pref.ProtoMessage - Oneof pref.ProtoMessage - ExtensionRange pref.ProtoMessage - Service pref.ProtoMessage - Method pref.ProtoMessage + File protoreflect.ProtoMessage + Enum protoreflect.ProtoMessage + EnumValue protoreflect.ProtoMessage + Message protoreflect.ProtoMessage + Field protoreflect.ProtoMessage + Oneof protoreflect.ProtoMessage + ExtensionRange protoreflect.ProtoMessage + Service protoreflect.ProtoMessage + Method protoreflect.ProtoMessage ) diff --git a/vendor/google.golang.org/protobuf/internal/filedesc/desc.go b/vendor/google.golang.org/protobuf/internal/filedesc/desc.go index df53ff40..fa790e0f 100644 --- a/vendor/google.golang.org/protobuf/internal/filedesc/desc.go +++ b/vendor/google.golang.org/protobuf/internal/filedesc/desc.go @@ -258,6 +258,7 @@ type ( StringName stringName IsProto3Optional bool // promoted from google.protobuf.FieldDescriptorProto IsWeak bool // promoted from google.protobuf.FieldOptions + IsLazy bool // promoted from google.protobuf.FieldOptions Default defaultValue ContainingOneof protoreflect.OneofDescriptor // must be consistent with Message.Oneofs.Fields Enum protoreflect.EnumDescriptor @@ -351,6 +352,7 @@ func (fd *Field) IsPacked() bool { } func (fd *Field) IsExtension() bool { return false } func (fd *Field) IsWeak() bool { return fd.L1.IsWeak } +func (fd *Field) IsLazy() bool { return fd.L1.IsLazy } func (fd *Field) IsList() bool { return fd.Cardinality() == protoreflect.Repeated && !fd.IsMap() } func (fd *Field) IsMap() bool { return fd.Message() != nil && fd.Message().IsMapEntry() } func (fd *Field) MapKey() protoreflect.FieldDescriptor { @@ -425,6 +427,7 @@ type ( Extendee protoreflect.MessageDescriptor Cardinality protoreflect.Cardinality Kind protoreflect.Kind + IsLazy bool EditionFeatures EditionFeatures } ExtensionL2 struct { @@ -465,6 +468,7 @@ func (xd *Extension) IsPacked() bool { } func (xd *Extension) IsExtension() bool { return true } func (xd *Extension) IsWeak() bool { return false } +func (xd *Extension) IsLazy() bool { return xd.L1.IsLazy } func (xd *Extension) IsList() bool { return xd.Cardinality() == protoreflect.Repeated } func (xd *Extension) IsMap() bool { return false } func (xd *Extension) MapKey() protoreflect.FieldDescriptor { return nil } diff --git a/vendor/google.golang.org/protobuf/internal/filedesc/desc_init.go b/vendor/google.golang.org/protobuf/internal/filedesc/desc_init.go index 8a57d60b..d2f54949 100644 --- a/vendor/google.golang.org/protobuf/internal/filedesc/desc_init.go +++ b/vendor/google.golang.org/protobuf/internal/filedesc/desc_init.go @@ -495,6 +495,8 @@ func (xd *Extension) unmarshalOptions(b []byte) { switch num { case genid.FieldOptions_Packed_field_number: xd.L1.EditionFeatures.IsPacked = protowire.DecodeBool(v) + case genid.FieldOptions_Lazy_field_number: + xd.L1.IsLazy = protowire.DecodeBool(v) } case protowire.BytesType: v, m := protowire.ConsumeBytes(b) diff --git a/vendor/google.golang.org/protobuf/internal/filedesc/desc_lazy.go b/vendor/google.golang.org/protobuf/internal/filedesc/desc_lazy.go index e56c91a8..67a51b32 100644 --- a/vendor/google.golang.org/protobuf/internal/filedesc/desc_lazy.go +++ b/vendor/google.golang.org/protobuf/internal/filedesc/desc_lazy.go @@ -504,6 +504,8 @@ func (fd *Field) unmarshalOptions(b []byte) { fd.L1.EditionFeatures.IsPacked = protowire.DecodeBool(v) case genid.FieldOptions_Weak_field_number: fd.L1.IsWeak = protowire.DecodeBool(v) + case genid.FieldOptions_Lazy_field_number: + fd.L1.IsLazy = protowire.DecodeBool(v) case FieldOptions_EnforceUTF8: fd.L1.EditionFeatures.IsUTF8Validated = protowire.DecodeBool(v) } diff --git a/vendor/google.golang.org/protobuf/internal/filedesc/editions.go b/vendor/google.golang.org/protobuf/internal/filedesc/editions.go index 11f5f356..fd4d0c83 100644 --- a/vendor/google.golang.org/protobuf/internal/filedesc/editions.go +++ b/vendor/google.golang.org/protobuf/internal/filedesc/editions.go @@ -68,7 +68,7 @@ func unmarshalFeatureSet(b []byte, parent EditionFeatures) EditionFeatures { v, m := protowire.ConsumeBytes(b) b = b[m:] switch num { - case genid.GoFeatures_LegacyUnmarshalJsonEnum_field_number: + case genid.FeatureSet_Go_ext_number: parent = unmarshalGoFeature(v, parent) } } diff --git a/vendor/google.golang.org/protobuf/internal/genid/doc.go b/vendor/google.golang.org/protobuf/internal/genid/doc.go index 45ccd012..d9b9d916 100644 --- a/vendor/google.golang.org/protobuf/internal/genid/doc.go +++ b/vendor/google.golang.org/protobuf/internal/genid/doc.go @@ -6,6 +6,6 @@ // and the well-known types. package genid -import protoreflect "google.golang.org/protobuf/reflect/protoreflect" +import "google.golang.org/protobuf/reflect/protoreflect" const GoogleProtobuf_package protoreflect.FullName = "google.protobuf" diff --git a/vendor/google.golang.org/protobuf/internal/genid/go_features_gen.go b/vendor/google.golang.org/protobuf/internal/genid/go_features_gen.go index 9a652a2b..7f67cbb6 100644 --- a/vendor/google.golang.org/protobuf/internal/genid/go_features_gen.go +++ b/vendor/google.golang.org/protobuf/internal/genid/go_features_gen.go @@ -12,20 +12,25 @@ import ( const File_google_protobuf_go_features_proto = "google/protobuf/go_features.proto" -// Names for google.protobuf.GoFeatures. +// Names for pb.GoFeatures. const ( GoFeatures_message_name protoreflect.Name = "GoFeatures" - GoFeatures_message_fullname protoreflect.FullName = "google.protobuf.GoFeatures" + GoFeatures_message_fullname protoreflect.FullName = "pb.GoFeatures" ) -// Field names for google.protobuf.GoFeatures. +// Field names for pb.GoFeatures. const ( GoFeatures_LegacyUnmarshalJsonEnum_field_name protoreflect.Name = "legacy_unmarshal_json_enum" - GoFeatures_LegacyUnmarshalJsonEnum_field_fullname protoreflect.FullName = "google.protobuf.GoFeatures.legacy_unmarshal_json_enum" + GoFeatures_LegacyUnmarshalJsonEnum_field_fullname protoreflect.FullName = "pb.GoFeatures.legacy_unmarshal_json_enum" ) -// Field numbers for google.protobuf.GoFeatures. +// Field numbers for pb.GoFeatures. const ( GoFeatures_LegacyUnmarshalJsonEnum_field_number protoreflect.FieldNumber = 1 ) + +// Extension numbers +const ( + FeatureSet_Go_ext_number protoreflect.FieldNumber = 1002 +) diff --git a/vendor/google.golang.org/protobuf/internal/genid/map_entry.go b/vendor/google.golang.org/protobuf/internal/genid/map_entry.go index 8f9ea02f..bef5a25f 100644 --- a/vendor/google.golang.org/protobuf/internal/genid/map_entry.go +++ b/vendor/google.golang.org/protobuf/internal/genid/map_entry.go @@ -4,7 +4,7 @@ package genid -import protoreflect "google.golang.org/protobuf/reflect/protoreflect" +import "google.golang.org/protobuf/reflect/protoreflect" // Generic field names and numbers for synthetic map entry messages. const ( diff --git a/vendor/google.golang.org/protobuf/internal/genid/wrappers.go b/vendor/google.golang.org/protobuf/internal/genid/wrappers.go index 429384b8..9404270d 100644 --- a/vendor/google.golang.org/protobuf/internal/genid/wrappers.go +++ b/vendor/google.golang.org/protobuf/internal/genid/wrappers.go @@ -4,7 +4,7 @@ package genid -import protoreflect "google.golang.org/protobuf/reflect/protoreflect" +import "google.golang.org/protobuf/reflect/protoreflect" // Generic field name and number for messages in wrappers.proto. const ( diff --git a/vendor/google.golang.org/protobuf/internal/impl/codec_extension.go b/vendor/google.golang.org/protobuf/internal/impl/codec_extension.go index 4bb0a7a2..0d5b546e 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/codec_extension.go +++ b/vendor/google.golang.org/protobuf/internal/impl/codec_extension.go @@ -67,7 +67,6 @@ type lazyExtensionValue struct { xi *extensionFieldInfo value protoreflect.Value b []byte - fn func() protoreflect.Value } type ExtensionField struct { @@ -158,10 +157,9 @@ func (f *ExtensionField) lazyInit() { } f.lazy.value = val } else { - f.lazy.value = f.lazy.fn() + panic("No support for lazy fns for ExtensionField") } f.lazy.xi = nil - f.lazy.fn = nil f.lazy.b = nil atomic.StoreUint32(&f.lazy.atomicOnce, 1) } @@ -174,13 +172,6 @@ func (f *ExtensionField) Set(t protoreflect.ExtensionType, v protoreflect.Value) f.lazy = nil } -// SetLazy sets the type and a value that is to be lazily evaluated upon first use. -// This must not be called concurrently. -func (f *ExtensionField) SetLazy(t protoreflect.ExtensionType, fn func() protoreflect.Value) { - f.typ = t - f.lazy = &lazyExtensionValue{fn: fn} -} - // Value returns the value of the extension field. // This may be called concurrently. func (f *ExtensionField) Value() protoreflect.Value { diff --git a/vendor/google.golang.org/protobuf/internal/impl/codec_field.go b/vendor/google.golang.org/protobuf/internal/impl/codec_field.go index 78ee47e4..7c1f66c8 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/codec_field.go +++ b/vendor/google.golang.org/protobuf/internal/impl/codec_field.go @@ -65,6 +65,9 @@ func (mi *MessageInfo) initOneofFieldCoders(od protoreflect.OneofDescriptor, si if err != nil { return out, err } + if cf.funcs.isInit == nil { + out.initialized = true + } vi.Set(vw) return out, nil } diff --git a/vendor/google.golang.org/protobuf/internal/impl/codec_message.go b/vendor/google.golang.org/protobuf/internal/impl/codec_message.go index 6b2fdbb7..78be9df3 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/codec_message.go +++ b/vendor/google.golang.org/protobuf/internal/impl/codec_message.go @@ -189,6 +189,9 @@ func (mi *MessageInfo) makeCoderMethods(t reflect.Type, si structInfo) { if mi.methods.Merge == nil { mi.methods.Merge = mi.merge } + if mi.methods.Equal == nil { + mi.methods.Equal = equal + } } // getUnknownBytes returns a *[]byte for the unknown fields. diff --git a/vendor/google.golang.org/protobuf/internal/impl/codec_reflect.go b/vendor/google.golang.org/protobuf/internal/impl/codec_reflect.go deleted file mode 100644 index 145c577b..00000000 --- a/vendor/google.golang.org/protobuf/internal/impl/codec_reflect.go +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build purego || appengine -// +build purego appengine - -package impl - -import ( - "reflect" - - "google.golang.org/protobuf/encoding/protowire" -) - -func sizeEnum(p pointer, f *coderFieldInfo, _ marshalOptions) (size int) { - v := p.v.Elem().Int() - return f.tagsize + protowire.SizeVarint(uint64(v)) -} - -func appendEnum(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) { - v := p.v.Elem().Int() - b = protowire.AppendVarint(b, f.wiretag) - b = protowire.AppendVarint(b, uint64(v)) - return b, nil -} - -func consumeEnum(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, _ unmarshalOptions) (out unmarshalOutput, err error) { - if wtyp != protowire.VarintType { - return out, errUnknown - } - v, n := protowire.ConsumeVarint(b) - if n < 0 { - return out, errDecode - } - p.v.Elem().SetInt(int64(v)) - out.n = n - return out, nil -} - -func mergeEnum(dst, src pointer, _ *coderFieldInfo, _ mergeOptions) { - dst.v.Elem().Set(src.v.Elem()) -} - -var coderEnum = pointerCoderFuncs{ - size: sizeEnum, - marshal: appendEnum, - unmarshal: consumeEnum, - merge: mergeEnum, -} - -func sizeEnumNoZero(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) { - if p.v.Elem().Int() == 0 { - return 0 - } - return sizeEnum(p, f, opts) -} - -func appendEnumNoZero(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) { - if p.v.Elem().Int() == 0 { - return b, nil - } - return appendEnum(b, p, f, opts) -} - -func mergeEnumNoZero(dst, src pointer, _ *coderFieldInfo, _ mergeOptions) { - if src.v.Elem().Int() != 0 { - dst.v.Elem().Set(src.v.Elem()) - } -} - -var coderEnumNoZero = pointerCoderFuncs{ - size: sizeEnumNoZero, - marshal: appendEnumNoZero, - unmarshal: consumeEnum, - merge: mergeEnumNoZero, -} - -func sizeEnumPtr(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) { - return sizeEnum(pointer{p.v.Elem()}, f, opts) -} - -func appendEnumPtr(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) { - return appendEnum(b, pointer{p.v.Elem()}, f, opts) -} - -func consumeEnumPtr(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) { - if wtyp != protowire.VarintType { - return out, errUnknown - } - if p.v.Elem().IsNil() { - p.v.Elem().Set(reflect.New(p.v.Elem().Type().Elem())) - } - return consumeEnum(b, pointer{p.v.Elem()}, wtyp, f, opts) -} - -func mergeEnumPtr(dst, src pointer, _ *coderFieldInfo, _ mergeOptions) { - if !src.v.Elem().IsNil() { - v := reflect.New(dst.v.Type().Elem().Elem()) - v.Elem().Set(src.v.Elem().Elem()) - dst.v.Elem().Set(v) - } -} - -var coderEnumPtr = pointerCoderFuncs{ - size: sizeEnumPtr, - marshal: appendEnumPtr, - unmarshal: consumeEnumPtr, - merge: mergeEnumPtr, -} - -func sizeEnumSlice(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) { - s := p.v.Elem() - for i, llen := 0, s.Len(); i < llen; i++ { - size += protowire.SizeVarint(uint64(s.Index(i).Int())) + f.tagsize - } - return size -} - -func appendEnumSlice(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) { - s := p.v.Elem() - for i, llen := 0, s.Len(); i < llen; i++ { - b = protowire.AppendVarint(b, f.wiretag) - b = protowire.AppendVarint(b, uint64(s.Index(i).Int())) - } - return b, nil -} - -func consumeEnumSlice(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) { - s := p.v.Elem() - if wtyp == protowire.BytesType { - b, n := protowire.ConsumeBytes(b) - if n < 0 { - return out, errDecode - } - for len(b) > 0 { - v, n := protowire.ConsumeVarint(b) - if n < 0 { - return out, errDecode - } - rv := reflect.New(s.Type().Elem()).Elem() - rv.SetInt(int64(v)) - s.Set(reflect.Append(s, rv)) - b = b[n:] - } - out.n = n - return out, nil - } - if wtyp != protowire.VarintType { - return out, errUnknown - } - v, n := protowire.ConsumeVarint(b) - if n < 0 { - return out, errDecode - } - rv := reflect.New(s.Type().Elem()).Elem() - rv.SetInt(int64(v)) - s.Set(reflect.Append(s, rv)) - out.n = n - return out, nil -} - -func mergeEnumSlice(dst, src pointer, _ *coderFieldInfo, _ mergeOptions) { - dst.v.Elem().Set(reflect.AppendSlice(dst.v.Elem(), src.v.Elem())) -} - -var coderEnumSlice = pointerCoderFuncs{ - size: sizeEnumSlice, - marshal: appendEnumSlice, - unmarshal: consumeEnumSlice, - merge: mergeEnumSlice, -} - -func sizeEnumPackedSlice(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) { - s := p.v.Elem() - llen := s.Len() - if llen == 0 { - return 0 - } - n := 0 - for i := 0; i < llen; i++ { - n += protowire.SizeVarint(uint64(s.Index(i).Int())) - } - return f.tagsize + protowire.SizeBytes(n) -} - -func appendEnumPackedSlice(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) { - s := p.v.Elem() - llen := s.Len() - if llen == 0 { - return b, nil - } - b = protowire.AppendVarint(b, f.wiretag) - n := 0 - for i := 0; i < llen; i++ { - n += protowire.SizeVarint(uint64(s.Index(i).Int())) - } - b = protowire.AppendVarint(b, uint64(n)) - for i := 0; i < llen; i++ { - b = protowire.AppendVarint(b, uint64(s.Index(i).Int())) - } - return b, nil -} - -var coderEnumPackedSlice = pointerCoderFuncs{ - size: sizeEnumPackedSlice, - marshal: appendEnumPackedSlice, - unmarshal: consumeEnumSlice, - merge: mergeEnumSlice, -} diff --git a/vendor/google.golang.org/protobuf/internal/impl/codec_unsafe.go b/vendor/google.golang.org/protobuf/internal/impl/codec_unsafe.go index 757642e2..077712c2 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/codec_unsafe.go +++ b/vendor/google.golang.org/protobuf/internal/impl/codec_unsafe.go @@ -2,9 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !purego && !appengine -// +build !purego,!appengine - package impl // When using unsafe pointers, we can just treat enum values as int32s. diff --git a/vendor/google.golang.org/protobuf/internal/impl/convert.go b/vendor/google.golang.org/protobuf/internal/impl/convert.go index e06ece55..f72ddd88 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/convert.go +++ b/vendor/google.golang.org/protobuf/internal/impl/convert.go @@ -322,7 +322,7 @@ func (c *stringConverter) PBValueOf(v reflect.Value) protoreflect.Value { return protoreflect.ValueOfString(v.Convert(stringType).String()) } func (c *stringConverter) GoValueOf(v protoreflect.Value) reflect.Value { - // pref.Value.String never panics, so we go through an interface + // protoreflect.Value.String never panics, so we go through an interface // conversion here to check the type. s := v.Interface().(string) if c.goType.Kind() == reflect.Slice && s == "" { diff --git a/vendor/google.golang.org/protobuf/internal/impl/encode.go b/vendor/google.golang.org/protobuf/internal/impl/encode.go index febd2122..6254f5de 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/encode.go +++ b/vendor/google.golang.org/protobuf/internal/impl/encode.go @@ -10,7 +10,7 @@ import ( "sync/atomic" "google.golang.org/protobuf/internal/flags" - proto "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/proto" piface "google.golang.org/protobuf/runtime/protoiface" ) diff --git a/vendor/google.golang.org/protobuf/internal/impl/equal.go b/vendor/google.golang.org/protobuf/internal/impl/equal.go new file mode 100644 index 00000000..9f6c32a7 --- /dev/null +++ b/vendor/google.golang.org/protobuf/internal/impl/equal.go @@ -0,0 +1,224 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package impl + +import ( + "bytes" + + "google.golang.org/protobuf/encoding/protowire" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/runtime/protoiface" +) + +func equal(in protoiface.EqualInput) protoiface.EqualOutput { + return protoiface.EqualOutput{Equal: equalMessage(in.MessageA, in.MessageB)} +} + +// equalMessage is a fast-path variant of protoreflect.equalMessage. +// It takes advantage of the internal messageState type to avoid +// unnecessary allocations, type assertions. +func equalMessage(mx, my protoreflect.Message) bool { + if mx == nil || my == nil { + return mx == my + } + if mx.Descriptor() != my.Descriptor() { + return false + } + + msx, ok := mx.(*messageState) + if !ok { + return protoreflect.ValueOfMessage(mx).Equal(protoreflect.ValueOfMessage(my)) + } + msy, ok := my.(*messageState) + if !ok { + return protoreflect.ValueOfMessage(mx).Equal(protoreflect.ValueOfMessage(my)) + } + + mi := msx.messageInfo() + miy := msy.messageInfo() + if mi != miy { + return protoreflect.ValueOfMessage(mx).Equal(protoreflect.ValueOfMessage(my)) + } + mi.init() + // Compares regular fields + // Modified Message.Range code that compares two messages of the same type + // while going over the fields. + for _, ri := range mi.rangeInfos { + var fd protoreflect.FieldDescriptor + var vx, vy protoreflect.Value + + switch ri := ri.(type) { + case *fieldInfo: + hx := ri.has(msx.pointer()) + hy := ri.has(msy.pointer()) + if hx != hy { + return false + } + if !hx { + continue + } + fd = ri.fieldDesc + vx = ri.get(msx.pointer()) + vy = ri.get(msy.pointer()) + case *oneofInfo: + fnx := ri.which(msx.pointer()) + fny := ri.which(msy.pointer()) + if fnx != fny { + return false + } + if fnx <= 0 { + continue + } + fi := mi.fields[fnx] + fd = fi.fieldDesc + vx = fi.get(msx.pointer()) + vy = fi.get(msy.pointer()) + } + + if !equalValue(fd, vx, vy) { + return false + } + } + + // Compare extensions. + // This is more complicated because mx or my could have empty/nil extension maps, + // however some populated extension map values are equal to nil extension maps. + emx := mi.extensionMap(msx.pointer()) + emy := mi.extensionMap(msy.pointer()) + if emx != nil { + for k, x := range *emx { + xd := x.Type().TypeDescriptor() + xv := x.Value() + var y ExtensionField + ok := false + if emy != nil { + y, ok = (*emy)[k] + } + // We need to treat empty lists as equal to nil values + if emy == nil || !ok { + if xd.IsList() && xv.List().Len() == 0 { + continue + } + return false + } + + if !equalValue(xd, xv, y.Value()) { + return false + } + } + } + if emy != nil { + // emy may have extensions emx does not have, need to check them as well + for k, y := range *emy { + if emx != nil { + // emx has the field, so we already checked it + if _, ok := (*emx)[k]; ok { + continue + } + } + // Empty lists are equal to nil + if y.Type().TypeDescriptor().IsList() && y.Value().List().Len() == 0 { + continue + } + + // Cant be equal if the extension is populated + return false + } + } + + return equalUnknown(mx.GetUnknown(), my.GetUnknown()) +} + +func equalValue(fd protoreflect.FieldDescriptor, vx, vy protoreflect.Value) bool { + // slow path + if fd.Kind() != protoreflect.MessageKind { + return vx.Equal(vy) + } + + // fast path special cases + if fd.IsMap() { + if fd.MapValue().Kind() == protoreflect.MessageKind { + return equalMessageMap(vx.Map(), vy.Map()) + } + return vx.Equal(vy) + } + + if fd.IsList() { + return equalMessageList(vx.List(), vy.List()) + } + + return equalMessage(vx.Message(), vy.Message()) +} + +// Mostly copied from protoreflect.equalMap. +// This variant only works for messages as map types. +// All other map types should be handled via Value.Equal. +func equalMessageMap(mx, my protoreflect.Map) bool { + if mx.Len() != my.Len() { + return false + } + equal := true + mx.Range(func(k protoreflect.MapKey, vx protoreflect.Value) bool { + if !my.Has(k) { + equal = false + return false + } + vy := my.Get(k) + equal = equalMessage(vx.Message(), vy.Message()) + return equal + }) + return equal +} + +// Mostly copied from protoreflect.equalList. +// The only change is the usage of equalImpl instead of protoreflect.equalValue. +func equalMessageList(lx, ly protoreflect.List) bool { + if lx.Len() != ly.Len() { + return false + } + for i := 0; i < lx.Len(); i++ { + // We only operate on messages here since equalImpl will not call us in any other case. + if !equalMessage(lx.Get(i).Message(), ly.Get(i).Message()) { + return false + } + } + return true +} + +// equalUnknown compares unknown fields by direct comparison on the raw bytes +// of each individual field number. +// Copied from protoreflect.equalUnknown. +func equalUnknown(x, y protoreflect.RawFields) bool { + if len(x) != len(y) { + return false + } + if bytes.Equal([]byte(x), []byte(y)) { + return true + } + + mx := make(map[protoreflect.FieldNumber]protoreflect.RawFields) + my := make(map[protoreflect.FieldNumber]protoreflect.RawFields) + for len(x) > 0 { + fnum, _, n := protowire.ConsumeField(x) + mx[fnum] = append(mx[fnum], x[:n]...) + x = x[n:] + } + for len(y) > 0 { + fnum, _, n := protowire.ConsumeField(y) + my[fnum] = append(my[fnum], y[:n]...) + y = y[n:] + } + if len(mx) != len(my) { + return false + } + + for k, v1 := range mx { + if v2, ok := my[k]; !ok || !bytes.Equal([]byte(v1), []byte(v2)) { + return false + } + } + + return true +} diff --git a/vendor/google.golang.org/protobuf/internal/impl/legacy_extension.go b/vendor/google.golang.org/protobuf/internal/impl/legacy_extension.go index 6e8677ee..b6849d66 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/legacy_extension.go +++ b/vendor/google.golang.org/protobuf/internal/impl/legacy_extension.go @@ -160,6 +160,7 @@ func (x placeholderExtension) HasPresence() bool func (x placeholderExtension) HasOptionalKeyword() bool { return false } func (x placeholderExtension) IsExtension() bool { return true } func (x placeholderExtension) IsWeak() bool { return false } +func (x placeholderExtension) IsLazy() bool { return false } func (x placeholderExtension) IsPacked() bool { return false } func (x placeholderExtension) IsList() bool { return false } func (x placeholderExtension) IsMap() bool { return false } diff --git a/vendor/google.golang.org/protobuf/internal/impl/message.go b/vendor/google.golang.org/protobuf/internal/impl/message.go index 019399d4..741b5ed2 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/message.go +++ b/vendor/google.golang.org/protobuf/internal/impl/message.go @@ -30,8 +30,8 @@ type MessageInfo struct { // Desc is the underlying message descriptor type and must be populated. Desc protoreflect.MessageDescriptor - // Exporter must be provided in a purego environment in order to provide - // access to unexported fields. + // Deprecated: Exporter will be removed the next time we bump + // protoimpl.GenVersion. See https://github.com/golang/protobuf/issues/1640 Exporter exporter // OneofWrappers is list of pointers to oneof wrapper struct types. diff --git a/vendor/google.golang.org/protobuf/internal/impl/pointer_reflect.go b/vendor/google.golang.org/protobuf/internal/impl/pointer_reflect.go deleted file mode 100644 index da685e8a..00000000 --- a/vendor/google.golang.org/protobuf/internal/impl/pointer_reflect.go +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build purego || appengine -// +build purego appengine - -package impl - -import ( - "fmt" - "reflect" - "sync" -) - -const UnsafeEnabled = false - -// Pointer is an opaque pointer type. -type Pointer any - -// offset represents the offset to a struct field, accessible from a pointer. -// The offset is the field index into a struct. -type offset struct { - index int - export exporter -} - -// offsetOf returns a field offset for the struct field. -func offsetOf(f reflect.StructField, x exporter) offset { - if len(f.Index) != 1 { - panic("embedded structs are not supported") - } - if f.PkgPath == "" { - return offset{index: f.Index[0]} // field is already exported - } - if x == nil { - panic("exporter must be provided for unexported field") - } - return offset{index: f.Index[0], export: x} -} - -// IsValid reports whether the offset is valid. -func (f offset) IsValid() bool { return f.index >= 0 } - -// invalidOffset is an invalid field offset. -var invalidOffset = offset{index: -1} - -// zeroOffset is a noop when calling pointer.Apply. -var zeroOffset = offset{index: 0} - -// pointer is an abstract representation of a pointer to a struct or field. -type pointer struct{ v reflect.Value } - -// pointerOf returns p as a pointer. -func pointerOf(p Pointer) pointer { - return pointerOfIface(p) -} - -// pointerOfValue returns v as a pointer. -func pointerOfValue(v reflect.Value) pointer { - return pointer{v: v} -} - -// pointerOfIface returns the pointer portion of an interface. -func pointerOfIface(v any) pointer { - return pointer{v: reflect.ValueOf(v)} -} - -// IsNil reports whether the pointer is nil. -func (p pointer) IsNil() bool { - return p.v.IsNil() -} - -// Apply adds an offset to the pointer to derive a new pointer -// to a specified field. The current pointer must be pointing at a struct. -func (p pointer) Apply(f offset) pointer { - if f.export != nil { - if v := reflect.ValueOf(f.export(p.v.Interface(), f.index)); v.IsValid() { - return pointer{v: v} - } - } - return pointer{v: p.v.Elem().Field(f.index).Addr()} -} - -// AsValueOf treats p as a pointer to an object of type t and returns the value. -// It is equivalent to reflect.ValueOf(p.AsIfaceOf(t)) -func (p pointer) AsValueOf(t reflect.Type) reflect.Value { - if got := p.v.Type().Elem(); got != t { - panic(fmt.Sprintf("invalid type: got %v, want %v", got, t)) - } - return p.v -} - -// AsIfaceOf treats p as a pointer to an object of type t and returns the value. -// It is equivalent to p.AsValueOf(t).Interface() -func (p pointer) AsIfaceOf(t reflect.Type) any { - return p.AsValueOf(t).Interface() -} - -func (p pointer) Bool() *bool { return p.v.Interface().(*bool) } -func (p pointer) BoolPtr() **bool { return p.v.Interface().(**bool) } -func (p pointer) BoolSlice() *[]bool { return p.v.Interface().(*[]bool) } -func (p pointer) Int32() *int32 { return p.v.Interface().(*int32) } -func (p pointer) Int32Ptr() **int32 { return p.v.Interface().(**int32) } -func (p pointer) Int32Slice() *[]int32 { return p.v.Interface().(*[]int32) } -func (p pointer) Int64() *int64 { return p.v.Interface().(*int64) } -func (p pointer) Int64Ptr() **int64 { return p.v.Interface().(**int64) } -func (p pointer) Int64Slice() *[]int64 { return p.v.Interface().(*[]int64) } -func (p pointer) Uint32() *uint32 { return p.v.Interface().(*uint32) } -func (p pointer) Uint32Ptr() **uint32 { return p.v.Interface().(**uint32) } -func (p pointer) Uint32Slice() *[]uint32 { return p.v.Interface().(*[]uint32) } -func (p pointer) Uint64() *uint64 { return p.v.Interface().(*uint64) } -func (p pointer) Uint64Ptr() **uint64 { return p.v.Interface().(**uint64) } -func (p pointer) Uint64Slice() *[]uint64 { return p.v.Interface().(*[]uint64) } -func (p pointer) Float32() *float32 { return p.v.Interface().(*float32) } -func (p pointer) Float32Ptr() **float32 { return p.v.Interface().(**float32) } -func (p pointer) Float32Slice() *[]float32 { return p.v.Interface().(*[]float32) } -func (p pointer) Float64() *float64 { return p.v.Interface().(*float64) } -func (p pointer) Float64Ptr() **float64 { return p.v.Interface().(**float64) } -func (p pointer) Float64Slice() *[]float64 { return p.v.Interface().(*[]float64) } -func (p pointer) String() *string { return p.v.Interface().(*string) } -func (p pointer) StringPtr() **string { return p.v.Interface().(**string) } -func (p pointer) StringSlice() *[]string { return p.v.Interface().(*[]string) } -func (p pointer) Bytes() *[]byte { return p.v.Interface().(*[]byte) } -func (p pointer) BytesPtr() **[]byte { return p.v.Interface().(**[]byte) } -func (p pointer) BytesSlice() *[][]byte { return p.v.Interface().(*[][]byte) } -func (p pointer) WeakFields() *weakFields { return (*weakFields)(p.v.Interface().(*WeakFields)) } -func (p pointer) Extensions() *map[int32]ExtensionField { - return p.v.Interface().(*map[int32]ExtensionField) -} - -func (p pointer) Elem() pointer { - return pointer{v: p.v.Elem()} -} - -// PointerSlice copies []*T from p as a new []pointer. -// This behavior differs from the implementation in pointer_unsafe.go. -func (p pointer) PointerSlice() []pointer { - // TODO: reconsider this - if p.v.IsNil() { - return nil - } - n := p.v.Elem().Len() - s := make([]pointer, n) - for i := 0; i < n; i++ { - s[i] = pointer{v: p.v.Elem().Index(i)} - } - return s -} - -// AppendPointerSlice appends v to p, which must be a []*T. -func (p pointer) AppendPointerSlice(v pointer) { - sp := p.v.Elem() - sp.Set(reflect.Append(sp, v.v)) -} - -// SetPointer sets *p to v. -func (p pointer) SetPointer(v pointer) { - p.v.Elem().Set(v.v) -} - -func growSlice(p pointer, addCap int) { - // TODO: Once we only support Go 1.20 and newer, use reflect.Grow. - in := p.v.Elem() - out := reflect.MakeSlice(in.Type(), in.Len(), in.Len()+addCap) - reflect.Copy(out, in) - p.v.Elem().Set(out) -} - -func (p pointer) growBoolSlice(addCap int) { - growSlice(p, addCap) -} - -func (p pointer) growInt32Slice(addCap int) { - growSlice(p, addCap) -} - -func (p pointer) growUint32Slice(addCap int) { - growSlice(p, addCap) -} - -func (p pointer) growInt64Slice(addCap int) { - growSlice(p, addCap) -} - -func (p pointer) growUint64Slice(addCap int) { - growSlice(p, addCap) -} - -func (p pointer) growFloat64Slice(addCap int) { - growSlice(p, addCap) -} - -func (p pointer) growFloat32Slice(addCap int) { - growSlice(p, addCap) -} - -func (Export) MessageStateOf(p Pointer) *messageState { panic("not supported") } -func (ms *messageState) pointer() pointer { panic("not supported") } -func (ms *messageState) messageInfo() *MessageInfo { panic("not supported") } -func (ms *messageState) LoadMessageInfo() *MessageInfo { panic("not supported") } -func (ms *messageState) StoreMessageInfo(mi *MessageInfo) { panic("not supported") } - -type atomicNilMessage struct { - once sync.Once - m messageReflectWrapper -} - -func (m *atomicNilMessage) Init(mi *MessageInfo) *messageReflectWrapper { - m.once.Do(func() { - m.m.p = pointerOfIface(reflect.Zero(mi.GoReflectType).Interface()) - m.m.mi = mi - }) - return &m.m -} diff --git a/vendor/google.golang.org/protobuf/internal/impl/pointer_unsafe.go b/vendor/google.golang.org/protobuf/internal/impl/pointer_unsafe.go index 5f20ca5d..79e18666 100644 --- a/vendor/google.golang.org/protobuf/internal/impl/pointer_unsafe.go +++ b/vendor/google.golang.org/protobuf/internal/impl/pointer_unsafe.go @@ -2,9 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !purego && !appengine -// +build !purego,!appengine - package impl import ( diff --git a/vendor/google.golang.org/protobuf/internal/strs/strings_pure.go b/vendor/google.golang.org/protobuf/internal/strs/strings_pure.go deleted file mode 100644 index a1f6f333..00000000 --- a/vendor/google.golang.org/protobuf/internal/strs/strings_pure.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build purego || appengine -// +build purego appengine - -package strs - -import pref "google.golang.org/protobuf/reflect/protoreflect" - -func UnsafeString(b []byte) string { - return string(b) -} - -func UnsafeBytes(s string) []byte { - return []byte(s) -} - -type Builder struct{} - -func (*Builder) AppendFullName(prefix pref.FullName, name pref.Name) pref.FullName { - return prefix.Append(name) -} - -func (*Builder) MakeString(b []byte) string { - return string(b) -} diff --git a/vendor/google.golang.org/protobuf/internal/strs/strings_unsafe_go120.go b/vendor/google.golang.org/protobuf/internal/strs/strings_unsafe_go120.go index a008acd0..832a7988 100644 --- a/vendor/google.golang.org/protobuf/internal/strs/strings_unsafe_go120.go +++ b/vendor/google.golang.org/protobuf/internal/strs/strings_unsafe_go120.go @@ -2,8 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !purego && !appengine && !go1.21 -// +build !purego,!appengine,!go1.21 +//go:build !go1.21 package strs diff --git a/vendor/google.golang.org/protobuf/internal/strs/strings_unsafe_go121.go b/vendor/google.golang.org/protobuf/internal/strs/strings_unsafe_go121.go index 60166f2b..1ffddf68 100644 --- a/vendor/google.golang.org/protobuf/internal/strs/strings_unsafe_go121.go +++ b/vendor/google.golang.org/protobuf/internal/strs/strings_unsafe_go121.go @@ -2,8 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !purego && !appengine && go1.21 -// +build !purego,!appengine,go1.21 +//go:build go1.21 package strs diff --git a/vendor/google.golang.org/protobuf/internal/version/version.go b/vendor/google.golang.org/protobuf/internal/version/version.go index dbbf1f68..fb8e15e8 100644 --- a/vendor/google.golang.org/protobuf/internal/version/version.go +++ b/vendor/google.golang.org/protobuf/internal/version/version.go @@ -51,8 +51,8 @@ import ( // 10. Send out the CL for review and submit it. const ( Major = 1 - Minor = 34 - Patch = 2 + Minor = 35 + Patch = 1 PreRelease = "" ) diff --git a/vendor/google.golang.org/protobuf/proto/equal.go b/vendor/google.golang.org/protobuf/proto/equal.go index 1a0be1b0..c36d4a9c 100644 --- a/vendor/google.golang.org/protobuf/proto/equal.go +++ b/vendor/google.golang.org/protobuf/proto/equal.go @@ -8,6 +8,7 @@ import ( "reflect" "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/runtime/protoiface" ) // Equal reports whether two messages are equal, @@ -51,6 +52,14 @@ func Equal(x, y Message) bool { if mx.IsValid() != my.IsValid() { return false } + + // Only one of the messages needs to implement the fast-path for it to work. + pmx := protoMethods(mx) + pmy := protoMethods(my) + if pmx != nil && pmy != nil && pmx.Equal != nil && pmy.Equal != nil { + return pmx.Equal(protoiface.EqualInput{MessageA: mx, MessageB: my}).Equal + } + vx := protoreflect.ValueOfMessage(mx) vy := protoreflect.ValueOfMessage(my) return vx.Equal(vy) diff --git a/vendor/google.golang.org/protobuf/proto/extension.go b/vendor/google.golang.org/protobuf/proto/extension.go index d248f292..78445d11 100644 --- a/vendor/google.golang.org/protobuf/proto/extension.go +++ b/vendor/google.golang.org/protobuf/proto/extension.go @@ -39,6 +39,48 @@ func ClearExtension(m Message, xt protoreflect.ExtensionType) { // If the field is unpopulated, it returns the default value for // scalars and an immutable, empty value for lists or messages. // It panics if xt does not extend m. +// +// The type of the value is dependent on the field type of the extension. +// For extensions generated by protoc-gen-go, the Go type is as follows: +// +// ╔═══════════════════╤═════════════════════════╗ +// ║ Go type │ Protobuf kind ║ +// ╠═══════════════════╪═════════════════════════╣ +// ║ bool │ bool ║ +// ║ int32 │ int32, sint32, sfixed32 ║ +// ║ int64 │ int64, sint64, sfixed64 ║ +// ║ uint32 │ uint32, fixed32 ║ +// ║ uint64 │ uint64, fixed64 ║ +// ║ float32 │ float ║ +// ║ float64 │ double ║ +// ║ string │ string ║ +// ║ []byte │ bytes ║ +// ║ protoreflect.Enum │ enum ║ +// ║ proto.Message │ message, group ║ +// ╚═══════════════════╧═════════════════════════╝ +// +// The protoreflect.Enum and proto.Message types are the concrete Go type +// associated with the named enum or message. Repeated fields are represented +// using a Go slice of the base element type. +// +// If a generated extension descriptor variable is directly passed to +// GetExtension, then the call should be followed immediately by a +// type assertion to the expected output value. For example: +// +// mm := proto.GetExtension(m, foopb.E_MyExtension).(*foopb.MyMessage) +// +// This pattern enables static analysis tools to verify that the asserted type +// matches the Go type associated with the extension field and +// also enables a possible future migration to a type-safe extension API. +// +// Since singular messages are the most common extension type, the pattern of +// calling HasExtension followed by GetExtension may be simplified to: +// +// if mm := proto.GetExtension(m, foopb.E_MyExtension).(*foopb.MyMessage); mm != nil { +// ... // make use of mm +// } +// +// The mm variable is non-nil if and only if HasExtension reports true. func GetExtension(m Message, xt protoreflect.ExtensionType) any { // Treat nil message interface as an empty message; return the default. if m == nil { @@ -51,6 +93,35 @@ func GetExtension(m Message, xt protoreflect.ExtensionType) any { // SetExtension stores the value of an extension field. // It panics if m is invalid, xt does not extend m, or if type of v // is invalid for the specified extension field. +// +// The type of the value is dependent on the field type of the extension. +// For extensions generated by protoc-gen-go, the Go type is as follows: +// +// ╔═══════════════════╤═════════════════════════╗ +// ║ Go type │ Protobuf kind ║ +// ╠═══════════════════╪═════════════════════════╣ +// ║ bool │ bool ║ +// ║ int32 │ int32, sint32, sfixed32 ║ +// ║ int64 │ int64, sint64, sfixed64 ║ +// ║ uint32 │ uint32, fixed32 ║ +// ║ uint64 │ uint64, fixed64 ║ +// ║ float32 │ float ║ +// ║ float64 │ double ║ +// ║ string │ string ║ +// ║ []byte │ bytes ║ +// ║ protoreflect.Enum │ enum ║ +// ║ proto.Message │ message, group ║ +// ╚═══════════════════╧═════════════════════════╝ +// +// The protoreflect.Enum and proto.Message types are the concrete Go type +// associated with the named enum or message. Repeated fields are represented +// using a Go slice of the base element type. +// +// If a generated extension descriptor variable is directly passed to +// SetExtension (e.g., foopb.E_MyExtension), then the value should be a +// concrete type that matches the expected Go type for the extension descriptor +// so that static analysis tools can verify type correctness. +// This also enables a possible future migration to a type-safe extension API. func SetExtension(m Message, xt protoreflect.ExtensionType, v any) { xd := xt.TypeDescriptor() pv := xt.ValueOf(v) diff --git a/vendor/google.golang.org/protobuf/reflect/protoreflect/methods.go b/vendor/google.golang.org/protobuf/reflect/protoreflect/methods.go index d5d5af6e..742cb518 100644 --- a/vendor/google.golang.org/protobuf/reflect/protoreflect/methods.go +++ b/vendor/google.golang.org/protobuf/reflect/protoreflect/methods.go @@ -23,6 +23,7 @@ type ( Unmarshal func(unmarshalInput) (unmarshalOutput, error) Merge func(mergeInput) mergeOutput CheckInitialized func(checkInitializedInput) (checkInitializedOutput, error) + Equal func(equalInput) equalOutput } supportFlags = uint64 sizeInput = struct { @@ -75,4 +76,13 @@ type ( checkInitializedOutput = struct { pragma.NoUnkeyedLiterals } + equalInput = struct { + pragma.NoUnkeyedLiterals + MessageA Message + MessageB Message + } + equalOutput = struct { + pragma.NoUnkeyedLiterals + Equal bool + } ) diff --git a/vendor/google.golang.org/protobuf/reflect/protoreflect/value_pure.go b/vendor/google.golang.org/protobuf/reflect/protoreflect/value_pure.go deleted file mode 100644 index 75f83a2a..00000000 --- a/vendor/google.golang.org/protobuf/reflect/protoreflect/value_pure.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build purego || appengine -// +build purego appengine - -package protoreflect - -import "google.golang.org/protobuf/internal/pragma" - -type valueType int - -const ( - nilType valueType = iota - boolType - int32Type - int64Type - uint32Type - uint64Type - float32Type - float64Type - stringType - bytesType - enumType - ifaceType -) - -// value is a union where only one type can be represented at a time. -// This uses a distinct field for each type. This is type safe in Go, but -// occupies more memory than necessary (72B). -type value struct { - pragma.DoNotCompare // 0B - - typ valueType // 8B - num uint64 // 8B - str string // 16B - bin []byte // 24B - iface any // 16B -} - -func valueOfString(v string) Value { - return Value{typ: stringType, str: v} -} -func valueOfBytes(v []byte) Value { - return Value{typ: bytesType, bin: v} -} -func valueOfIface(v any) Value { - return Value{typ: ifaceType, iface: v} -} - -func (v Value) getString() string { - return v.str -} -func (v Value) getBytes() []byte { - return v.bin -} -func (v Value) getIface() any { - return v.iface -} diff --git a/vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe_go120.go b/vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe_go120.go index 7f3583ea..0015fcb3 100644 --- a/vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe_go120.go +++ b/vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe_go120.go @@ -2,8 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !purego && !appengine && !go1.21 -// +build !purego,!appengine,!go1.21 +//go:build !go1.21 package protoreflect diff --git a/vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe_go121.go b/vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe_go121.go index f7d38699..479527b5 100644 --- a/vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe_go121.go +++ b/vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe_go121.go @@ -2,8 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !purego && !appengine && go1.21 -// +build !purego,!appengine,go1.21 +//go:build go1.21 package protoreflect diff --git a/vendor/google.golang.org/protobuf/runtime/protoiface/methods.go b/vendor/google.golang.org/protobuf/runtime/protoiface/methods.go index 44cf467d..24615656 100644 --- a/vendor/google.golang.org/protobuf/runtime/protoiface/methods.go +++ b/vendor/google.golang.org/protobuf/runtime/protoiface/methods.go @@ -39,6 +39,9 @@ type Methods = struct { // CheckInitialized returns an error if any required fields in the message are not set. CheckInitialized func(CheckInitializedInput) (CheckInitializedOutput, error) + + // Equal compares two messages and returns EqualOutput.Equal == true if they are equal. + Equal func(EqualInput) EqualOutput } // SupportFlags indicate support for optional features. @@ -166,3 +169,18 @@ type CheckInitializedInput = struct { type CheckInitializedOutput = struct { pragma.NoUnkeyedLiterals } + +// EqualInput is input to the Equal method. +type EqualInput = struct { + pragma.NoUnkeyedLiterals + + MessageA protoreflect.Message + MessageB protoreflect.Message +} + +// EqualOutput is output from the Equal method. +type EqualOutput = struct { + pragma.NoUnkeyedLiterals + + Equal bool +} diff --git a/vendor/google.golang.org/protobuf/types/known/timestamppb/timestamp.pb.go b/vendor/google.golang.org/protobuf/types/known/timestamppb/timestamp.pb.go index 83a5a645..0d20722d 100644 --- a/vendor/google.golang.org/protobuf/types/known/timestamppb/timestamp.pb.go +++ b/vendor/google.golang.org/protobuf/types/known/timestamppb/timestamp.pb.go @@ -254,11 +254,9 @@ func (x *Timestamp) check() uint { func (x *Timestamp) Reset() { *x = Timestamp{} - if protoimpl.UnsafeEnabled { - mi := &file_google_protobuf_timestamp_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_google_protobuf_timestamp_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Timestamp) String() string { @@ -269,7 +267,7 @@ func (*Timestamp) ProtoMessage() {} func (x *Timestamp) ProtoReflect() protoreflect.Message { mi := &file_google_protobuf_timestamp_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -348,20 +346,6 @@ func file_google_protobuf_timestamp_proto_init() { if File_google_protobuf_timestamp_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_google_protobuf_timestamp_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*Timestamp); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/vendor/modules.txt b/vendor/modules.txt index b7461958..b4966dc5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,5 +1,5 @@ -# github.com/99designs/gqlgen v0.17.49 -## explicit; go 1.20 +# github.com/99designs/gqlgen v0.17.55 +## explicit; go 1.22.5 github.com/99designs/gqlgen github.com/99designs/gqlgen/api github.com/99designs/gqlgen/codegen @@ -27,14 +27,14 @@ github.com/99designs/gqlgen/plugin/servergen # github.com/KyleBanks/depth v1.2.1 ## explicit github.com/KyleBanks/depth -# github.com/Masterminds/semver/v3 v3.2.1 -## explicit; go 1.18 +# github.com/Masterminds/semver/v3 v3.3.0 +## explicit; go 1.21 github.com/Masterminds/semver/v3 -# github.com/adhocore/gronx v1.19.0 +# github.com/adhocore/gronx v1.19.1 ## explicit; go 1.13 github.com/adhocore/gronx -# github.com/agnivade/levenshtein v1.1.1 -## explicit; go 1.13 +# github.com/agnivade/levenshtein v1.2.0 +## explicit; go 1.21 github.com/agnivade/levenshtein # github.com/andybalholm/brotli v1.1.0 ## explicit; go 1.13 @@ -55,9 +55,10 @@ github.com/beorn7/perks/quantile # github.com/boltdb/bolt v1.3.1 ## explicit github.com/boltdb/bolt -# github.com/caddyserver/certmagic v0.21.3 +# github.com/caddyserver/certmagic v0.21.4 ## explicit; go 1.21.0 github.com/caddyserver/certmagic +github.com/caddyserver/certmagic/internal/atomicfile # github.com/caddyserver/zerossl v0.1.3 ## explicit; go 1.21.0 github.com/caddyserver/zerossl @@ -158,7 +159,7 @@ github.com/go-playground/locales/currency # github.com/go-playground/universal-translator v0.18.1 ## explicit; go 1.18 github.com/go-playground/universal-translator -# github.com/go-playground/validator/v10 v10.22.0 +# github.com/go-playground/validator/v10 v10.22.1 ## explicit; go 1.18 github.com/go-playground/validator/v10 # github.com/gobwas/glob v0.2.3 @@ -219,7 +220,7 @@ github.com/hashicorp/golang-lru/simplelru github.com/hashicorp/golang-lru/v2 github.com/hashicorp/golang-lru/v2/internal github.com/hashicorp/golang-lru/v2/simplelru -# github.com/hashicorp/raft v1.7.0 +# github.com/hashicorp/raft v1.7.1 ## explicit; go 1.20 github.com/hashicorp/raft # github.com/hashicorp/raft-boltdb/v2 v2.3.0 @@ -238,8 +239,8 @@ github.com/joho/godotenv/autoload # github.com/josharian/intern v1.0.0 ## explicit; go 1.5 github.com/josharian/intern -# github.com/klauspost/compress v1.17.9 -## explicit; go 1.20 +# github.com/klauspost/compress v1.17.10 +## explicit; go 1.21 github.com/klauspost/compress github.com/klauspost/compress/flate github.com/klauspost/compress/fse @@ -267,17 +268,16 @@ github.com/labstack/gommon/log ## explicit; go 1.18 github.com/leodido/go-urn github.com/leodido/go-urn/scim/schema -# github.com/lestrrat-go/strftime v1.0.6 -## explicit; go 1.13 +# github.com/lestrrat-go/strftime v1.1.0 +## explicit; go 1.21 github.com/lestrrat-go/strftime -github.com/lestrrat-go/strftime/internal/errors # github.com/libdns/libdns v0.2.2 ## explicit; go 1.18 github.com/libdns/libdns # github.com/lithammer/shortuuid/v4 v4.0.0 ## explicit; go 1.13 github.com/lithammer/shortuuid/v4 -# github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae +# github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 ## explicit; go 1.16 github.com/lufia/plan9stats # github.com/mailru/easyjson v0.7.7 @@ -291,8 +291,8 @@ github.com/mattn/go-colorable # github.com/mattn/go-isatty v0.0.20 ## explicit; go 1.15 github.com/mattn/go-isatty -# github.com/mholt/acmez/v2 v2.0.2 -## explicit; go 1.20 +# github.com/mholt/acmez/v2 v2.0.3 +## explicit; go 1.21.0 github.com/mholt/acmez/v2 github.com/mholt/acmez/v2/acme # github.com/miekg/dns v1.1.62 @@ -301,7 +301,7 @@ github.com/miekg/dns # github.com/minio/md5-simd v1.1.2 ## explicit; go 1.14 github.com/minio/md5-simd -# github.com/minio/minio-go/v7 v7.0.75 +# github.com/minio/minio-go/v7 v7.0.77 ## explicit; go 1.21 github.com/minio/minio-go/v7 github.com/minio/minio-go/v7/pkg/cors @@ -321,9 +321,6 @@ github.com/mitchellh/mapstructure # github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 ## explicit github.com/munnerz/goautoneg -# github.com/pkg/errors v0.9.1 -## explicit -github.com/pkg/errors # github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 ## explicit github.com/pmezard/go-difflib/difflib @@ -333,7 +330,7 @@ github.com/power-devops/perfstat # github.com/prep/average v0.0.0-20200506183628-d26c465f48c3 ## explicit github.com/prep/average -# github.com/prometheus/client_golang v1.20.0 +# github.com/prometheus/client_golang v1.20.4 ## explicit; go 1.20 github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header @@ -343,8 +340,8 @@ github.com/prometheus/client_golang/prometheus/promhttp # github.com/prometheus/client_model v0.6.1 ## explicit; go 1.19 github.com/prometheus/client_model/go -# github.com/prometheus/common v0.55.0 -## explicit; go 1.20 +# github.com/prometheus/common v0.60.0 +## explicit; go 1.21 github.com/prometheus/common/expfmt github.com/prometheus/common/model # github.com/prometheus/procfs v0.15.1 @@ -355,8 +352,8 @@ github.com/prometheus/procfs/internal/util # github.com/puzpuzpuz/xsync/v3 v3.4.0 ## explicit; go 1.18 github.com/puzpuzpuz/xsync/v3 -# github.com/rs/xid v1.5.0 -## explicit; go 1.12 +# github.com/rs/xid v1.6.0 +## explicit; go 1.16 github.com/rs/xid # github.com/russross/blackfriday/v2 v2.1.0 ## explicit @@ -392,10 +389,10 @@ github.com/swaggo/swag # github.com/tklauser/go-sysconf v0.3.14 ## explicit; go 1.18 github.com/tklauser/go-sysconf -# github.com/tklauser/numcpus v0.8.0 +# github.com/tklauser/numcpus v0.9.0 ## explicit; go 1.18 github.com/tklauser/numcpus -# github.com/urfave/cli/v2 v2.27.2 +# github.com/urfave/cli/v2 v2.27.4 ## explicit; go 1.18 github.com/urfave/cli/v2 # github.com/valyala/bytebufferpool v1.0.0 @@ -404,7 +401,7 @@ github.com/valyala/bytebufferpool # github.com/valyala/fasttemplate v1.2.2 ## explicit; go 1.12 github.com/valyala/fasttemplate -# github.com/vektah/gqlparser/v2 v2.5.16 +# github.com/vektah/gqlparser/v2 v2.5.17 ## explicit; go 1.19 github.com/vektah/gqlparser/v2 github.com/vektah/gqlparser/v2/ast @@ -422,8 +419,8 @@ github.com/xeipuuv/gojsonreference # github.com/xeipuuv/gojsonschema v1.2.0 ## explicit github.com/xeipuuv/gojsonschema -# github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 -## explicit; go 1.15.0 +# github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 +## explicit; go 1.15 github.com/xrash/smetrics # github.com/yusufpapurcu/wmi v1.2.4 ## explicit; go 1.16 @@ -440,11 +437,11 @@ github.com/zeebo/blake3/internal/alg/hash/hash_avx2 github.com/zeebo/blake3/internal/alg/hash/hash_pure github.com/zeebo/blake3/internal/consts github.com/zeebo/blake3/internal/utils -# go.etcd.io/bbolt v1.3.10 -## explicit; go 1.21 +# go.etcd.io/bbolt v1.3.11 +## explicit; go 1.22 go.etcd.io/bbolt -# go.uber.org/automaxprocs v1.5.3 -## explicit; go 1.18 +# go.uber.org/automaxprocs v1.6.0 +## explicit; go 1.20 go.uber.org/automaxprocs/internal/cgroups go.uber.org/automaxprocs/internal/runtime go.uber.org/automaxprocs/maxprocs @@ -462,7 +459,7 @@ go.uber.org/zap/internal/exit go.uber.org/zap/internal/pool go.uber.org/zap/internal/stacktrace go.uber.org/zap/zapcore -# golang.org/x/crypto v0.26.0 +# golang.org/x/crypto v0.28.0 ## explicit; go 1.20 golang.org/x/crypto/acme golang.org/x/crypto/acme/autocert @@ -474,12 +471,12 @@ golang.org/x/crypto/ocsp golang.org/x/crypto/pbkdf2 golang.org/x/crypto/scrypt golang.org/x/crypto/sha3 -# golang.org/x/mod v0.20.0 -## explicit; go 1.18 +# golang.org/x/mod v0.21.0 +## explicit; go 1.22.0 golang.org/x/mod/internal/lazyregexp golang.org/x/mod/module golang.org/x/mod/semver -# golang.org/x/net v0.28.0 +# golang.org/x/net v0.30.0 ## explicit; go 1.18 golang.org/x/net/bpf golang.org/x/net/html @@ -497,13 +494,13 @@ golang.org/x/net/publicsuffix # golang.org/x/sync v0.8.0 ## explicit; go 1.18 golang.org/x/sync/errgroup -# golang.org/x/sys v0.24.0 +# golang.org/x/sys v0.26.0 ## explicit; go 1.18 golang.org/x/sys/cpu golang.org/x/sys/unix golang.org/x/sys/windows golang.org/x/sys/windows/registry -# golang.org/x/text v0.17.0 +# golang.org/x/text v0.19.0 ## explicit; go 1.18 golang.org/x/text/cases golang.org/x/text/internal @@ -515,11 +512,11 @@ golang.org/x/text/secure/bidirule golang.org/x/text/transform golang.org/x/text/unicode/bidi golang.org/x/text/unicode/norm -# golang.org/x/time v0.6.0 +# golang.org/x/time v0.7.0 ## explicit; go 1.18 golang.org/x/time/rate -# golang.org/x/tools v0.24.0 -## explicit; go 1.19 +# golang.org/x/tools v0.26.0 +## explicit; go 1.22.0 golang.org/x/tools/go/ast/astutil golang.org/x/tools/go/buildutil golang.org/x/tools/go/gcexportdata @@ -527,6 +524,7 @@ golang.org/x/tools/go/internal/cgo golang.org/x/tools/go/loader golang.org/x/tools/go/packages golang.org/x/tools/go/types/objectpath +golang.org/x/tools/go/types/typeutil golang.org/x/tools/imports golang.org/x/tools/internal/aliases golang.org/x/tools/internal/event @@ -540,11 +538,11 @@ golang.org/x/tools/internal/imports golang.org/x/tools/internal/packagesinternal golang.org/x/tools/internal/pkgbits golang.org/x/tools/internal/stdlib -golang.org/x/tools/internal/tokeninternal +golang.org/x/tools/internal/typeparams golang.org/x/tools/internal/typesinternal golang.org/x/tools/internal/versions -# google.golang.org/protobuf v1.34.2 -## explicit; go 1.20 +# google.golang.org/protobuf v1.35.1 +## explicit; go 1.21 google.golang.org/protobuf/encoding/protodelim google.golang.org/protobuf/encoding/prototext google.golang.org/protobuf/encoding/protowire From 05e4118e0c88e742a8384f5c20025bf5307fd6ee Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Wed, 9 Oct 2024 16:29:55 +0200 Subject: [PATCH 33/64] Use buffer pool --- http/middleware/session/HLS.go | 91 +++++++------- http/middleware/session/HLS_test.go | 112 ++++++++++++++++++ http/middleware/session/HTTP.go | 20 ++-- http/middleware/session/fixtures/segments.txt | 29 +++++ .../fixtures/segments_with_session.txt | 29 +++++ http/middleware/session/session.go | 25 ++-- http/middleware/session/session_test.go | 28 +++++ 7 files changed, 268 insertions(+), 66 deletions(-) create mode 100644 http/middleware/session/HLS_test.go create mode 100644 http/middleware/session/fixtures/segments.txt create mode 100644 http/middleware/session/fixtures/segments_with_session.txt diff --git a/http/middleware/session/HLS.go b/http/middleware/session/HLS.go index fbb714b6..c76dd4d2 100644 --- a/http/middleware/session/HLS.go +++ b/http/middleware/session/HLS.go @@ -37,8 +37,9 @@ func (h *handler) handleHLSIngress(c echo.Context, _ string, data map[string]int // Read out the path of the .ts files and look them up in the ts-map. // Add it as ingress for the respective "sessionId". The "sessionId" is the .m3u8 file name. reader := req.Body - r := &bodyReader{ + r := &segmentReader{ reader: req.Body, + buffer: h.bufferPool.Get(), } req.Body = r @@ -46,6 +47,7 @@ func (h *handler) handleHLSIngress(c echo.Context, _ string, data map[string]int req.Body = reader if r.size == 0 { + h.bufferPool.Put(r.buffer) return } @@ -58,8 +60,10 @@ func (h *handler) handleHLSIngress(c echo.Context, _ string, data map[string]int h.hlsIngressCollector.Extra(path, data) } - h.hlsIngressCollector.Ingress(path, headerSize(req.Header)) + buffer := h.bufferPool.Get() + h.hlsIngressCollector.Ingress(path, headerSize(req.Header, buffer)) h.hlsIngressCollector.Ingress(path, r.size) + h.bufferPool.Put(buffer) segments := r.getSegments(urlpath.Dir(path)) @@ -74,6 +78,8 @@ func (h *handler) handleHLSIngress(c echo.Context, _ string, data map[string]int } h.lock.Unlock() } + + h.bufferPool.Put(r.buffer) }() } else if strings.HasSuffix(path, ".ts") { // Get the size of the .ts file and store it in the ts-map for later use. @@ -87,9 +93,11 @@ func (h *handler) handleHLSIngress(c echo.Context, _ string, data map[string]int req.Body = reader if r.size != 0 { + buffer := h.bufferPool.Get() h.lock.Lock() - h.rxsegments[path] = r.size + headerSize(req.Header) + h.rxsegments[path] = r.size + headerSize(req.Header, buffer) h.lock.Unlock() + h.bufferPool.Put(buffer) } }() } @@ -171,6 +179,7 @@ func (h *handler) handleHLSEgress(c echo.Context, _ string, data map[string]inte // the data that we need to rewrite. rewriter = &sessionRewriter{ ResponseWriter: res.Writer, + buffer: h.bufferPool.Get(), } res.Writer = rewriter @@ -188,21 +197,29 @@ func (h *handler) handleHLSEgress(c echo.Context, _ string, data map[string]inte if rewrite { if res.Status < 200 || res.Status >= 300 { res.Write(rewriter.buffer.Bytes()) + h.bufferPool.Put(rewriter.buffer) return nil } + buffer := h.bufferPool.Get() + // Rewrite the data befor sending it to the client - rewriter.rewriteHLS(sessionID, c.Request().URL) + rewriter.rewriteHLS(sessionID, c.Request().URL, buffer) res.Header().Set("Cache-Control", "private") - res.Write(rewriter.buffer.Bytes()) + res.Write(buffer.Bytes()) + + h.bufferPool.Put(buffer) + h.bufferPool.Put(rewriter.buffer) } if isM3U8 || isTS { if res.Status >= 200 && res.Status < 300 { // Collect how many bytes we've written in this session - h.hlsEgressCollector.Egress(sessionID, headerSize(res.Header())) + buffer := h.bufferPool.Get() + h.hlsEgressCollector.Egress(sessionID, headerSize(res.Header(), buffer)) h.hlsEgressCollector.Egress(sessionID, res.Size) + h.bufferPool.Put(buffer) if isTS { // Activate the session. If the session is already active, this is a noop @@ -214,13 +231,13 @@ func (h *handler) handleHLSEgress(c echo.Context, _ string, data map[string]inte return nil } -type bodyReader struct { +type segmentReader struct { reader io.ReadCloser - buffer bytes.Buffer + buffer *bytes.Buffer size int64 } -func (r *bodyReader) Read(b []byte) (int, error) { +func (r *segmentReader) Read(b []byte) (int, error) { n, err := r.reader.Read(b) if n > 0 { r.buffer.Write(b[:n]) @@ -230,15 +247,15 @@ func (r *bodyReader) Read(b []byte) (int, error) { return n, err } -func (r *bodyReader) Close() error { +func (r *segmentReader) Close() error { return r.reader.Close() } -func (r *bodyReader) getSegments(dir string) []string { +func (r *segmentReader) getSegments(dir string) []string { segments := []string{} // Find all segment URLs in the .m3u8 - scanner := bufio.NewScanner(&r.buffer) + scanner := bufio.NewScanner(r.buffer) for scanner.Scan() { line := scanner.Text() @@ -280,65 +297,49 @@ func (r *bodyReader) getSegments(dir string) []string { return segments } -type bodysizeReader struct { - reader io.ReadCloser - size int64 -} - -func (r *bodysizeReader) Read(b []byte) (int, error) { - n, err := r.reader.Read(b) - r.size += int64(n) - - return n, err -} - -func (r *bodysizeReader) Close() error { - return r.reader.Close() -} - type sessionRewriter struct { http.ResponseWriter - buffer bytes.Buffer + buffer *bytes.Buffer } func (g *sessionRewriter) Write(data []byte) (int, error) { // Write the data into internal buffer for later rewrite - w, err := g.buffer.Write(data) - - return w, err + return g.buffer.Write(data) } -func (g *sessionRewriter) rewriteHLS(sessionID string, requestURL *url.URL) { - var buffer bytes.Buffer - +func (g *sessionRewriter) rewriteHLS(sessionID string, requestURL *url.URL, buffer *bytes.Buffer) { isMaster := false // Find all URLS in the .m3u8 and add the session ID to the query string - scanner := bufio.NewScanner(&g.buffer) + scanner := bufio.NewScanner(g.buffer) for scanner.Scan() { - line := scanner.Text() + byteline := scanner.Bytes() // Write empty lines unmodified - if len(line) == 0 { - buffer.WriteString(line + "\n") + if len(byteline) == 0 { + buffer.Write(byteline) + buffer.WriteByte('\n') continue } // Write comments unmodified - if strings.HasPrefix(line, "#") { - buffer.WriteString(line + "\n") + if byteline[0] == '#' { + buffer.Write(byteline) + buffer.WriteByte('\n') continue } - u, err := url.Parse(line) + u, err := url.Parse(string(byteline)) if err != nil { - buffer.WriteString(line + "\n") + buffer.Write(byteline) + buffer.WriteByte('\n') continue } // Write anything that doesn't end in .m3u8 or .ts unmodified if !strings.HasSuffix(u.Path, ".m3u8") && !strings.HasSuffix(u.Path, ".ts") { - buffer.WriteString(line + "\n") + buffer.Write(byteline) + buffer.WriteByte('\n') continue } @@ -407,6 +408,4 @@ func (g *sessionRewriter) rewriteHLS(sessionID string, requestURL *url.URL) { buffer.WriteString(urlpath.Base(requestURL.Path) + "?" + q.Encode()) } - - g.buffer = buffer } diff --git a/http/middleware/session/HLS_test.go b/http/middleware/session/HLS_test.go new file mode 100644 index 00000000..e5cede1a --- /dev/null +++ b/http/middleware/session/HLS_test.go @@ -0,0 +1,112 @@ +package session + +import ( + "bytes" + "io" + "net/url" + "os" + "testing" + + "github.com/datarhei/core/v16/mem" + "github.com/stretchr/testify/require" +) + +func TestHLSSegmentReader(t *testing.T) { + data, err := os.ReadFile("./fixtures/segments.txt") + require.NoError(t, err) + + r := bytes.NewReader(data) + + br := &segmentReader{ + reader: io.NopCloser(r), + buffer: &bytes.Buffer{}, + } + + _, err = io.ReadAll(br) + require.NoError(t, err) + + segments := br.getSegments("/foobar") + require.Equal(t, []string{ + "/foobar/test_0_0_0303.ts", + "/foobar/test_0_0_0304.ts", + "/foobar/test_0_0_0305.ts", + "/foobar/test_0_0_0306.ts", + "/foobar/test_0_0_0307.ts", + "/foobar/test_0_0_0308.ts", + "/foobar/test_0_0_0309.ts", + "/foobar/test_0_0_0310.ts", + }, segments) +} + +func BenchmarkHLSSegmentReader(b *testing.B) { + pool := mem.NewBufferPool() + + data, err := os.ReadFile("./fixtures/segments.txt") + require.NoError(b, err) + + rd := bytes.NewReader(data) + r := io.NopCloser(rd) + + for i := 0; i < b.N; i++ { + rd.Reset(data) + br := &segmentReader{ + reader: io.NopCloser(r), + buffer: pool.Get(), + } + + _, err := io.ReadAll(br) + require.NoError(b, err) + + pool.Put(br.buffer) + } +} + +func TestHLSRewrite(t *testing.T) { + data, err := os.ReadFile("./fixtures/segments.txt") + require.NoError(t, err) + + br := &sessionRewriter{ + buffer: &bytes.Buffer{}, + } + + _, err = br.Write(data) + require.NoError(t, err) + + u, err := url.Parse("http://example.com/test.m3u8") + require.NoError(t, err) + + buffer := &bytes.Buffer{} + + br.rewriteHLS("oT5GV8eWBbRAh4aib5egoK", u, buffer) + + data, err = os.ReadFile("./fixtures/segments_with_session.txt") + require.NoError(t, err) + + require.Equal(t, data, buffer.Bytes()) +} + +func BenchmarkHLSRewrite(b *testing.B) { + pool := mem.NewBufferPool() + + data, err := os.ReadFile("./fixtures/segments.txt") + require.NoError(b, err) + + u, err := url.Parse("http://example.com/test.m3u8") + require.NoError(b, err) + + for i := 0; i < b.N; i++ { + br := &sessionRewriter{ + buffer: pool.Get(), + } + + _, err = br.Write(data) + require.NoError(b, err) + + buffer := pool.Get() + + br.rewriteHLS("oT5GV8eWBbRAh4aib5egoK", u, buffer) + + pool.Put(br.buffer) + pool.Put(buffer) + } +} diff --git a/http/middleware/session/HTTP.go b/http/middleware/session/HTTP.go index 615b2058..4913171f 100644 --- a/http/middleware/session/HTTP.go +++ b/http/middleware/session/HTTP.go @@ -7,7 +7,7 @@ import ( "github.com/lithammer/shortuuid/v4" ) -func (h *handler) handleHTTP(c echo.Context, ctxuser string, data map[string]interface{}, next echo.HandlerFunc) error { +func (h *handler) handleHTTP(c echo.Context, _ string, data map[string]interface{}, next echo.HandlerFunc) error { req := c.Request() res := c.Response() @@ -30,13 +30,13 @@ func (h *handler) handleHTTP(c echo.Context, ctxuser string, data map[string]int id := shortuuid.New() reader := req.Body - r := &fakeReader{ + r := &bodysizeReader{ reader: req.Body, } req.Body = r writer := res.Writer - w := &fakeWriter{ + w := &bodysizeWriter{ ResponseWriter: res.Writer, } res.Writer = w @@ -44,19 +44,21 @@ func (h *handler) handleHTTP(c echo.Context, ctxuser string, data map[string]int h.httpCollector.RegisterAndActivate(id, "", location, referrer) h.httpCollector.Extra(id, data) - defer h.httpCollector.Close(id) - defer func() { + buffer := h.bufferPool.Get() + req.Body = reader - h.httpCollector.Ingress(id, r.size+headerSize(req.Header)) - }() + h.httpCollector.Ingress(id, r.size+headerSize(req.Header, buffer)) - defer func() { res.Writer = writer - h.httpCollector.Egress(id, w.size+headerSize(res.Header())) + h.httpCollector.Egress(id, w.size+headerSize(res.Header(), buffer)) data["code"] = res.Status h.httpCollector.Extra(id, data) + + h.httpCollector.Close(id) + + h.bufferPool.Put(buffer) }() return next(c) diff --git a/http/middleware/session/fixtures/segments.txt b/http/middleware/session/fixtures/segments.txt new file mode 100644 index 00000000..a4e2348c --- /dev/null +++ b/http/middleware/session/fixtures/segments.txt @@ -0,0 +1,29 @@ +#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-TARGETDURATION:2 +#EXT-X-MEDIA-SEQUENCE:303 +#EXT-X-INDEPENDENT-SEGMENTS +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:35.019+0200 +test_0_0_0303.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:37.019+0200 +test_0_0_0304.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:39.019+0200 +test_0_0_0305.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:41.019+0200 +test_0_0_0306.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:43.019+0200 +test_0_0_0307.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:45.019+0200 +test_0_0_0308.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:47.019+0200 +test_0_0_0309.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:49.019+0200 +test_0_0_0310.ts diff --git a/http/middleware/session/fixtures/segments_with_session.txt b/http/middleware/session/fixtures/segments_with_session.txt new file mode 100644 index 00000000..f59ed305 --- /dev/null +++ b/http/middleware/session/fixtures/segments_with_session.txt @@ -0,0 +1,29 @@ +#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-TARGETDURATION:2 +#EXT-X-MEDIA-SEQUENCE:303 +#EXT-X-INDEPENDENT-SEGMENTS +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:35.019+0200 +test_0_0_0303.ts?session=oT5GV8eWBbRAh4aib5egoK +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:37.019+0200 +test_0_0_0304.ts?session=oT5GV8eWBbRAh4aib5egoK +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:39.019+0200 +test_0_0_0305.ts?session=oT5GV8eWBbRAh4aib5egoK +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:41.019+0200 +test_0_0_0306.ts?session=oT5GV8eWBbRAh4aib5egoK +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:43.019+0200 +test_0_0_0307.ts?session=oT5GV8eWBbRAh4aib5egoK +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:45.019+0200 +test_0_0_0308.ts?session=oT5GV8eWBbRAh4aib5egoK +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:47.019+0200 +test_0_0_0309.ts?session=oT5GV8eWBbRAh4aib5egoK +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:49.019+0200 +test_0_0_0310.ts?session=oT5GV8eWBbRAh4aib5egoK diff --git a/http/middleware/session/session.go b/http/middleware/session/session.go index 684aa4c6..3f12f405 100644 --- a/http/middleware/session/session.go +++ b/http/middleware/session/session.go @@ -13,6 +13,7 @@ import ( "github.com/datarhei/core/v16/glob" "github.com/datarhei/core/v16/http/api" "github.com/datarhei/core/v16/http/handler/util" + "github.com/datarhei/core/v16/mem" "github.com/datarhei/core/v16/net" "github.com/datarhei/core/v16/session" "github.com/lithammer/shortuuid/v4" @@ -44,6 +45,8 @@ type handler struct { rxsegments map[string]int64 lock sync.Mutex + + bufferPool *mem.BufferPool } // New returns a new session middleware with default config @@ -75,6 +78,7 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { hlsIngressCollector: config.HLSIngressCollector, reSessionID: regexp.MustCompile(`^[` + regexp.QuoteMeta(shortuuid.DefaultAlphabet) + `]{22}$`), rxsegments: make(map[string]int64), + bufferPool: mem.NewBufferPool(), } return func(next echo.HandlerFunc) echo.HandlerFunc { @@ -173,43 +177,42 @@ func verifySession(raw interface{}, path, referrer string) (map[string]interface return data, nil } -func headerSize(header http.Header) int64 { - var buffer bytes.Buffer - - header.Write(&buffer) +func headerSize(header http.Header, buffer *bytes.Buffer) int64 { + buffer.Reset() + header.Write(buffer) return int64(buffer.Len()) } -type fakeReader struct { +type bodysizeReader struct { reader io.ReadCloser size int64 } -func (r *fakeReader) Read(b []byte) (int, error) { +func (r *bodysizeReader) Read(b []byte) (int, error) { n, err := r.reader.Read(b) r.size += int64(n) return n, err } -func (r *fakeReader) Close() error { +func (r *bodysizeReader) Close() error { return r.reader.Close() } -type fakeWriter struct { +type bodysizeWriter struct { http.ResponseWriter size int64 code int } -func (w *fakeWriter) WriteHeader(statusCode int) { +func (w *bodysizeWriter) WriteHeader(statusCode int) { w.ResponseWriter.WriteHeader(statusCode) w.code = statusCode } -func (w *fakeWriter) Write(body []byte) (int, error) { +func (w *bodysizeWriter) Write(body []byte) (int, error) { n, err := w.ResponseWriter.Write(body) w.size += int64(n) @@ -217,7 +220,7 @@ func (w *fakeWriter) Write(body []byte) (int, error) { return n, err } -func (w *fakeWriter) Flush() { +func (w *bodysizeWriter) Flush() { flusher, ok := w.ResponseWriter.(http.Flusher) if ok { flusher.Flush() diff --git a/http/middleware/session/session_test.go b/http/middleware/session/session_test.go index 77ef3839..b8633b19 100644 --- a/http/middleware/session/session_test.go +++ b/http/middleware/session/session_test.go @@ -1,6 +1,8 @@ package session import ( + "bytes" + "net/http" "testing" "github.com/datarhei/core/v16/encoding/json" @@ -134,3 +136,29 @@ func TestVerifySessionMultipleRemote(t *testing.T) { _, err = verifySession(rawdata, "/memfs/6faad99a-c440-4df1-9344-963869718d8d/main.m3u8", "http://bar.example.com") require.Error(t, err) } + +func TestHeaderSize(t *testing.T) { + header := http.Header{} + + header.Add("Content-Type", "application/json") + header.Add("Content-Encoding", "gzip") + + buffer := &bytes.Buffer{} + size := headerSize(header, buffer) + + require.Equal(t, "Content-Encoding: gzip\r\nContent-Type: application/json\r\n", buffer.String()) + require.Equal(t, int64(56), size) +} + +func BenchmarkHeaderSize(b *testing.B) { + header := http.Header{} + + header.Add("Content-Type", "application/json") + header.Add("Content-Encoding", "gzip") + + buffer := &bytes.Buffer{} + + for i := 0; i < b.N; i++ { + headerSize(header, buffer) + } +} From 91874e6cafd82a25e96bc0f502fadf6dd2667196 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 10 Oct 2024 12:18:22 +0200 Subject: [PATCH 34/64] Extend http status metrics by method and path --- http/middleware/log/log.go | 34 ++++++++++++++++++++++------------ http/server.go | 20 +++++++++++--------- http/server/server.go | 2 +- monitor/http.go | 9 +++++---- 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/http/middleware/log/log.go b/http/middleware/log/log.go index 2a225c27..3a78a6c9 100644 --- a/http/middleware/log/log.go +++ b/http/middleware/log/log.go @@ -16,7 +16,7 @@ type Config struct { // Skipper defines a function to skip middleware. Skipper middleware.Skipper Logger log.Logger - Status func(code int) + Status func(code int, method, path string, size int64, ttfb time.Duration) } var DefaultConfig = Config{ @@ -76,10 +76,15 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { res.Writer = writer req.Body = reader + if w.ttfb.IsZero() { + w.ttfb = start + } + latency := time.Since(start) + ttfb := time.Since(w.ttfb) if config.Status != nil { - config.Status(res.Status) + config.Status(res.Status, req.Method, c.Path(), w.size, ttfb) } if raw != "" { @@ -87,16 +92,17 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { } logger := config.Logger.WithFields(log.Fields{ - "client": c.RealIP(), - "method": req.Method, - "path": path, - "proto": req.Proto, - "status": res.Status, - "status_text": http.StatusText(res.Status), - "tx_size_bytes": w.size, - "rx_size_bytes": r.size, - "latency_ms": latency.Milliseconds(), - "user_agent": req.Header.Get("User-Agent"), + "client": c.RealIP(), + "method": req.Method, + "path": path, + "proto": req.Proto, + "status": res.Status, + "status_text": http.StatusText(res.Status), + "tx_size_bytes": w.size, + "rx_size_bytes": r.size, + "latency_ms": latency.Milliseconds(), + "latency_ttfb_ms": ttfb.Milliseconds(), + "user_agent": req.Header.Get("User-Agent"), }) logger.Debug().Log("") @@ -110,12 +116,16 @@ type sizeWriter struct { http.ResponseWriter size int64 + ttfb time.Time } func (w *sizeWriter) Write(body []byte) (int, error) { n, err := w.ResponseWriter.Write(body) w.size += int64(n) + if w.ttfb.IsZero() { + w.ttfb = time.Now() + } return n, err } diff --git a/http/server.go b/http/server.go index 09322e87..c7dc68ab 100644 --- a/http/server.go +++ b/http/server.go @@ -30,9 +30,11 @@ package http import ( "fmt" + "maps" "net/http" "strings" "sync" + "time" "github.com/datarhei/core/v16/cluster" cfgstore "github.com/datarhei/core/v16/config/store" @@ -166,7 +168,7 @@ type server struct { metrics struct { lock sync.Mutex - status map[int]uint64 + status map[string]uint64 } } @@ -186,7 +188,7 @@ func NewServer(config Config) (serverhandler.Server, error) { readOnly: config.ReadOnly, } - s.metrics.status = map[int]uint64{} + s.metrics.status = map[string]uint64{} s.filesystems = map[string]*filesystem{} @@ -344,11 +346,13 @@ func NewServer(config Config) (serverhandler.Server, error) { s.middleware.log = mwlog.NewWithConfig(mwlog.Config{ Logger: s.logger, - Status: func(code int) { + Status: func(code int, method, path string, size int64, ttfb time.Duration) { + key := fmt.Sprintf("%d:%s:%s", code, method, path) + s.metrics.lock.Lock() defer s.metrics.lock.Unlock() - s.metrics.status[code]++ + s.metrics.status[key]++ }, }) @@ -483,15 +487,13 @@ func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.router.ServeHTTP(w, r) } -func (s *server) HTTPStatus() map[int]uint64 { - status := map[int]uint64{} +func (s *server) HTTPStatus() map[string]uint64 { + status := map[string]uint64{} s.metrics.lock.Lock() defer s.metrics.lock.Unlock() - for code, value := range s.metrics.status { - status[code] = value - } + maps.Copy(status, s.metrics.status) return status } diff --git a/http/server/server.go b/http/server/server.go index 893bf462..d46ea92e 100644 --- a/http/server/server.go +++ b/http/server/server.go @@ -4,5 +4,5 @@ import "net/http" type Server interface { ServeHTTP(w http.ResponseWriter, r *http.Request) - HTTPStatus() map[int]uint64 + HTTPStatus() map[string]uint64 } diff --git a/monitor/http.go b/monitor/http.go index cd3d82b6..2330d4d1 100644 --- a/monitor/http.go +++ b/monitor/http.go @@ -1,7 +1,7 @@ package monitor import ( - "strconv" + "strings" "github.com/datarhei/core/v16/http/server" "github.com/datarhei/core/v16/monitor/metric" @@ -19,7 +19,7 @@ func NewHTTPCollector(name string, handler server.Server) metric.Collector { name: name, } - c.statusDescr = metric.NewDesc("http_status", "Total return status", []string{"name", "code"}) + c.statusDescr = metric.NewDesc("http_status", "Total return status count", []string{"name", "code", "method", "path"}) return c } @@ -39,8 +39,9 @@ func (c *httpCollector) Collect() metric.Metrics { metrics := metric.NewMetrics() - for code, count := range status { - metrics.Add(metric.NewValue(c.statusDescr, float64(count), c.name, strconv.Itoa(code))) + for key, count := range status { + vals := strings.SplitN(key, ":", 3) + metrics.Add(metric.NewValue(c.statusDescr, float64(count), c.name, vals[0], vals[1], vals[2])) } return metrics From a581f1dbc296a4933a411fbc34a4e52269ae449d Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 10 Oct 2024 15:09:50 +0200 Subject: [PATCH 35/64] User buffer pool where appropriate --- cluster/raft/raft.go | 8 +-- http/client/client.go | 12 +++-- http/client/events.go | 8 +-- http/client/process.go | 44 ++++++++------- http/middleware/hlsrewrite/fixtures/data.txt | 29 ++++++++++ .../hlsrewrite/fixtures/data_rewritten.txt | 29 ++++++++++ http/middleware/hlsrewrite/hlsrewrite.go | 53 ++++++++++--------- http/middleware/hlsrewrite/hlsrewrite_test.go | 49 +++++++++++++++++ io/fs/mem.go | 8 +-- io/fs/s3.go | 9 ++-- mem/buffer.go | 14 +++++ service/api/api.go | 9 ++-- session/registry.go | 2 +- 13 files changed, 207 insertions(+), 67 deletions(-) create mode 100644 http/middleware/hlsrewrite/fixtures/data.txt create mode 100644 http/middleware/hlsrewrite/fixtures/data_rewritten.txt create mode 100644 http/middleware/hlsrewrite/hlsrewrite_test.go diff --git a/cluster/raft/raft.go b/cluster/raft/raft.go index e07f1f1e..996345e0 100644 --- a/cluster/raft/raft.go +++ b/cluster/raft/raft.go @@ -1,7 +1,6 @@ package raft import ( - "bytes" "encoding/base64" "fmt" "io" @@ -16,6 +15,7 @@ import ( "github.com/datarhei/core/v16/cluster/store" "github.com/datarhei/core/v16/encoding/json" "github.com/datarhei/core/v16/log" + "github.com/datarhei/core/v16/mem" "go.etcd.io/bbolt" "github.com/hashicorp/go-hclog" @@ -359,14 +359,14 @@ func (r *raft) Snapshot() (io.ReadCloser, error) { Data: base64.StdEncoding.EncodeToString(data), } - buffer := bytes.Buffer{} - enc := json.NewEncoder(&buffer) + buffer := mem.Get() + enc := json.NewEncoder(buffer) err = enc.Encode(snapshot) if err != nil { return nil, err } - return &readCloserWrapper{&buffer}, nil + return &readCloserWrapper{buffer}, nil } func (r *raft) start(fsm hcraft.FSM, peers []Peer, inmem bool) error { diff --git a/http/client/client.go b/http/client/client.go index ab235c0d..ae25779e 100644 --- a/http/client/client.go +++ b/http/client/client.go @@ -1,7 +1,6 @@ package client import ( - "bytes" "context" "fmt" "io" @@ -15,6 +14,7 @@ import ( "github.com/datarhei/core/v16/encoding/json" "github.com/datarhei/core/v16/glob" "github.com/datarhei/core/v16/http/api" + "github.com/datarhei/core/v16/mem" "github.com/datarhei/core/v16/restream/app" "github.com/Masterminds/semver/v3" @@ -199,6 +199,8 @@ type restclient struct { connectedCore *semver.Version methods map[string][]apiconstraint } + + pool *mem.BufferPool } // New returns a new REST API client for the given config. The error is non-nil @@ -212,6 +214,7 @@ func New(config Config) (RestClient, error) { auth0Token: config.Auth0Token, client: config.Client, clientTimeout: config.Timeout, + pool: mem.NewBufferPool(), } if len(config.AccessToken) != 0 { @@ -652,12 +655,13 @@ func (r *restclient) login() error { login.Password = r.password } - var buf bytes.Buffer + buf := r.pool.Get() + defer r.pool.Put(buf) - e := json.NewEncoder(&buf) + e := json.NewEncoder(buf) e.Encode(login) - req, err := http.NewRequest("POST", r.address+r.prefix+"/login", &buf) + req, err := http.NewRequest("POST", r.address+r.prefix+"/login", buf) if err != nil { return err } diff --git a/http/client/events.go b/http/client/events.go index 36e7063b..e2f25279 100644 --- a/http/client/events.go +++ b/http/client/events.go @@ -1,7 +1,6 @@ package client import ( - "bytes" "context" "io" "net/http" @@ -11,15 +10,16 @@ import ( ) func (r *restclient) Events(ctx context.Context, filters api.EventFilters) (<-chan api.Event, error) { - var buf bytes.Buffer + buf := r.pool.Get() + defer r.pool.Put(buf) - e := json.NewEncoder(&buf) + e := json.NewEncoder(buf) e.Encode(filters) header := make(http.Header) header.Set("Accept", "application/x-json-stream") - stream, err := r.stream(ctx, "POST", "/v3/events", nil, header, "application/json", &buf) + stream, err := r.stream(ctx, "POST", "/v3/events", nil, header, "application/json", buf) if err != nil { return nil, err } diff --git a/http/client/process.go b/http/client/process.go index 38a41d18..485ab313 100644 --- a/http/client/process.go +++ b/http/client/process.go @@ -1,7 +1,6 @@ package client import ( - "bytes" "net/url" "strings" @@ -66,15 +65,16 @@ func (r *restclient) Process(id app.ProcessID, filter []string) (api.Process, er } func (r *restclient) ProcessAdd(p *app.Config, metadata map[string]interface{}) error { - var buf bytes.Buffer + buf := r.pool.Get() + defer r.pool.Put(buf) config := api.ProcessConfig{} config.Unmarshal(p, metadata) - e := json.NewEncoder(&buf) + e := json.NewEncoder(buf) e.Encode(config) - _, err := r.call("POST", "/v3/process", nil, nil, "application/json", &buf) + _, err := r.call("POST", "/v3/process", nil, nil, "application/json", buf) if err != nil { return err } @@ -83,18 +83,19 @@ func (r *restclient) ProcessAdd(p *app.Config, metadata map[string]interface{}) } func (r *restclient) ProcessUpdate(id app.ProcessID, p *app.Config, metadata map[string]interface{}) error { - var buf bytes.Buffer + buf := r.pool.Get() + defer r.pool.Put(buf) config := api.ProcessConfig{} config.Unmarshal(p, metadata) - e := json.NewEncoder(&buf) + e := json.NewEncoder(buf) e.Encode(config) query := &url.Values{} query.Set("domain", id.Domain) - _, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID), query, nil, "application/json", &buf) + _, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID), query, nil, "application/json", buf) if err != nil { return err } @@ -103,18 +104,19 @@ func (r *restclient) ProcessUpdate(id app.ProcessID, p *app.Config, metadata map } func (r *restclient) ProcessReportSet(id app.ProcessID, report *app.Report) error { - var buf bytes.Buffer + buf := r.pool.Get() + defer r.pool.Put(buf) data := api.ProcessReport{} data.Unmarshal(report) - e := json.NewEncoder(&buf) + e := json.NewEncoder(buf) e.Encode(data) query := &url.Values{} query.Set("domain", id.Domain) - _, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID)+"/report", query, nil, "application/json", &buf) + _, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID)+"/report", query, nil, "application/json", buf) if err != nil { return err } @@ -132,9 +134,10 @@ func (r *restclient) ProcessDelete(id app.ProcessID) error { } func (r *restclient) ProcessCommand(id app.ProcessID, command string) error { - var buf bytes.Buffer + buf := r.pool.Get() + defer r.pool.Put(buf) - e := json.NewEncoder(&buf) + e := json.NewEncoder(buf) e.Encode(api.Command{ Command: command, }) @@ -142,7 +145,7 @@ func (r *restclient) ProcessCommand(id app.ProcessID, command string) error { query := &url.Values{} query.Set("domain", id.Domain) - _, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID)+"/command", query, nil, "application/json", &buf) + _, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID)+"/command", query, nil, "application/json", buf) return err } @@ -170,15 +173,16 @@ func (r *restclient) ProcessMetadata(id app.ProcessID, key string) (api.Metadata } func (r *restclient) ProcessMetadataSet(id app.ProcessID, key string, metadata api.Metadata) error { - var buf bytes.Buffer + buf := r.pool.Get() + defer r.pool.Put(buf) - e := json.NewEncoder(&buf) + e := json.NewEncoder(buf) e.Encode(metadata) query := &url.Values{} query.Set("domain", id.Domain) - _, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID)+"/metadata/"+url.PathEscape(key), query, nil, "application/json", &buf) + _, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID)+"/metadata/"+url.PathEscape(key), query, nil, "application/json", buf) return err } @@ -201,15 +205,17 @@ func (r *restclient) ProcessProbe(id app.ProcessID) (api.Probe, error) { func (r *restclient) ProcessProbeConfig(p *app.Config) (api.Probe, error) { var probe api.Probe - var buf bytes.Buffer + + buf := r.pool.Get() + defer r.pool.Put(buf) config := api.ProcessConfig{} config.Unmarshal(p, nil) - e := json.NewEncoder(&buf) + e := json.NewEncoder(buf) e.Encode(config) - data, err := r.call("POST", "/v3/process/probe", nil, nil, "application/json", &buf) + data, err := r.call("POST", "/v3/process/probe", nil, nil, "application/json", buf) if err != nil { return probe, err } diff --git a/http/middleware/hlsrewrite/fixtures/data.txt b/http/middleware/hlsrewrite/fixtures/data.txt new file mode 100644 index 00000000..78bccdda --- /dev/null +++ b/http/middleware/hlsrewrite/fixtures/data.txt @@ -0,0 +1,29 @@ +#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-TARGETDURATION:2 +#EXT-X-MEDIA-SEQUENCE:303 +#EXT-X-INDEPENDENT-SEGMENTS +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:35.019+0200 +/path/to/foobar/test_0_0_0303.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:37.019+0200 +/path/to/foobar/test_0_0_0304.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:39.019+0200 +/path/to/foobar/test_0_0_0305.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:41.019+0200 +/path/to/foobar/test_0_0_0306.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:43.019+0200 +/path/to/foobar/test_0_0_0307.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:45.019+0200 +/path/to/foobar/test_0_0_0308.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:47.019+0200 +/path/to/foobar/test_0_0_0309.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:49.019+0200 +/path/to/foobar/test_0_0_0310.ts diff --git a/http/middleware/hlsrewrite/fixtures/data_rewritten.txt b/http/middleware/hlsrewrite/fixtures/data_rewritten.txt new file mode 100644 index 00000000..a4e2348c --- /dev/null +++ b/http/middleware/hlsrewrite/fixtures/data_rewritten.txt @@ -0,0 +1,29 @@ +#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-TARGETDURATION:2 +#EXT-X-MEDIA-SEQUENCE:303 +#EXT-X-INDEPENDENT-SEGMENTS +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:35.019+0200 +test_0_0_0303.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:37.019+0200 +test_0_0_0304.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:39.019+0200 +test_0_0_0305.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:41.019+0200 +test_0_0_0306.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:43.019+0200 +test_0_0_0307.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:45.019+0200 +test_0_0_0308.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:47.019+0200 +test_0_0_0309.ts +#EXTINF:2.000000, +#EXT-X-PROGRAM-DATE-TIME:2024-10-09T12:56:49.019+0200 +test_0_0_0310.ts diff --git a/http/middleware/hlsrewrite/hlsrewrite.go b/http/middleware/hlsrewrite/hlsrewrite.go index 674228bf..02f21f2c 100644 --- a/http/middleware/hlsrewrite/hlsrewrite.go +++ b/http/middleware/hlsrewrite/hlsrewrite.go @@ -6,6 +6,7 @@ import ( "net/http" "strings" + "github.com/datarhei/core/v16/mem" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" ) @@ -31,7 +32,7 @@ func NewHLSRewrite() echo.MiddlewareFunc { } type hlsrewrite struct { - pathPrefix string + pathPrefix []byte } func NewHLSRewriteWithConfig(config HLSRewriteConfig) echo.MiddlewareFunc { @@ -47,7 +48,7 @@ func NewHLSRewriteWithConfig(config HLSRewriteConfig) echo.MiddlewareFunc { } hls := hlsrewrite{ - pathPrefix: pathPrefix, + pathPrefix: []byte(pathPrefix), } return func(next echo.HandlerFunc) echo.HandlerFunc { @@ -91,6 +92,7 @@ func (h *hlsrewrite) rewrite(c echo.Context, next echo.HandlerFunc) error { // the data that we need to rewrite. rewriter = &hlsRewriter{ ResponseWriter: res.Writer, + buffer: mem.Get(), } res.Writer = rewriter @@ -104,16 +106,20 @@ func (h *hlsrewrite) rewrite(c echo.Context, next echo.HandlerFunc) error { res.Writer = writer if rewrite { - if res.Status != 200 { + if res.Status == 200 { + // Rewrite the data befor sending it to the client + buffer := mem.Get() + defer mem.Put(buffer) + + rewriter.rewrite(h.pathPrefix, buffer) + + res.Header().Set("Cache-Control", "private") + res.Write(buffer.Bytes()) + } else { res.Write(rewriter.buffer.Bytes()) - return nil } - // Rewrite the data befor sending it to the client - rewriter.rewrite(h.pathPrefix) - - res.Header().Set("Cache-Control", "private") - res.Write(rewriter.buffer.Bytes()) + mem.Put(rewriter.buffer) } return nil @@ -121,7 +127,7 @@ func (h *hlsrewrite) rewrite(c echo.Context, next echo.HandlerFunc) error { type hlsRewriter struct { http.ResponseWriter - buffer bytes.Buffer + buffer *bytes.Buffer } func (g *hlsRewriter) Write(data []byte) (int, error) { @@ -131,34 +137,29 @@ func (g *hlsRewriter) Write(data []byte) (int, error) { return w, err } -func (g *hlsRewriter) rewrite(pathPrefix string) { - var buffer bytes.Buffer - +func (g *hlsRewriter) rewrite(pathPrefix []byte, buffer *bytes.Buffer) { // Find all URLS in the .m3u8 and add the session ID to the query string - scanner := bufio.NewScanner(&g.buffer) + scanner := bufio.NewScanner(g.buffer) for scanner.Scan() { - line := scanner.Text() + line := scanner.Bytes() // Write empty lines unmodified if len(line) == 0 { - buffer.WriteString(line + "\n") + buffer.Write(line) + buffer.WriteByte('\n') continue } // Write comments unmodified - if strings.HasPrefix(line, "#") { - buffer.WriteString(line + "\n") + if line[0] == '#' { + buffer.Write(line) + buffer.WriteByte('\n') continue } // Rewrite - line = strings.TrimPrefix(line, pathPrefix) - buffer.WriteString(line + "\n") + line = bytes.TrimPrefix(line, pathPrefix) + buffer.Write(line) + buffer.WriteByte('\n') } - - if err := scanner.Err(); err != nil { - return - } - - g.buffer = buffer } diff --git a/http/middleware/hlsrewrite/hlsrewrite_test.go b/http/middleware/hlsrewrite/hlsrewrite_test.go new file mode 100644 index 00000000..7fb5341e --- /dev/null +++ b/http/middleware/hlsrewrite/hlsrewrite_test.go @@ -0,0 +1,49 @@ +package hlsrewrite + +import ( + "bytes" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestRewrite(t *testing.T) { + data, err := os.ReadFile("./fixtures/data.txt") + require.NoError(t, err) + + rewrittendata, err := os.ReadFile("./fixtures/data_rewritten.txt") + require.NoError(t, err) + + r := &hlsRewriter{ + buffer: &bytes.Buffer{}, + } + + r.Write(data) + + buffer := &bytes.Buffer{} + prefix := []byte("/path/to/foobar/") + r.rewrite(prefix, buffer) + + require.Equal(t, rewrittendata, buffer.Bytes()) +} + +func BenchmarkRewrite(b *testing.B) { + data, err := os.ReadFile("./fixtures/data.txt") + require.NoError(b, err) + + r := &hlsRewriter{ + buffer: &bytes.Buffer{}, + } + + buffer := &bytes.Buffer{} + prefix := []byte("/path/to/foobar/") + + for i := 0; i < b.N; i++ { + r.buffer.Reset() + r.Write(data) + + buffer.Reset() + r.rewrite(prefix, buffer) + } +} diff --git a/io/fs/mem.go b/io/fs/mem.go index ba102ed1..2eb68410 100644 --- a/io/fs/mem.go +++ b/io/fs/mem.go @@ -379,10 +379,12 @@ func (fs *memFilesystem) ReadFile(path string) ([]byte, error) { } } - data := make([]byte, file.data.Len()) - copy(data, file.data.Bytes()) + data := pool.Get() - return data, nil + data.Grow(file.data.Len()) + data.Write(file.data.Bytes()) + + return data.Bytes(), nil } func (fs *memFilesystem) Symlink(oldname, newname string) error { diff --git a/io/fs/s3.go b/io/fs/s3.go index ed9d1ade..c1659aef 100644 --- a/io/fs/s3.go +++ b/io/fs/s3.go @@ -14,6 +14,7 @@ import ( "github.com/datarhei/core/v16/glob" "github.com/datarhei/core/v16/log" + "github.com/datarhei/core/v16/mem" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) @@ -275,7 +276,7 @@ func (fs *s3Filesystem) ReadFile(path string) ([]byte, error) { defer file.Close() - buf := &bytes.Buffer{} + buf := mem.Get() // here we take out a buffer for good _, err := buf.ReadFrom(file) if err != nil { @@ -371,11 +372,13 @@ func (fs *s3Filesystem) AppendFileReader(path string, r io.Reader, sizeHint int) return size, err } - buffer := bytes.Buffer{} + buffer := mem.Get() + defer mem.Put(buffer) + buffer.ReadFrom(object) buffer.ReadFrom(r) - size, _, err := fs.write(path, &buffer) + size, _, err := fs.write(path, buffer) return size, err } diff --git a/mem/buffer.go b/mem/buffer.go index bcadc871..af676c10 100644 --- a/mem/buffer.go +++ b/mem/buffer.go @@ -31,3 +31,17 @@ func (p *BufferPool) Get() *bytes.Buffer { func (p *BufferPool) Put(buf *bytes.Buffer) { p.pool.Put(buf) } + +var DefaultBufferPool *BufferPool + +func init() { + DefaultBufferPool = NewBufferPool() +} + +func Get() *bytes.Buffer { + return DefaultBufferPool.Get() +} + +func Put(buf *bytes.Buffer) { + DefaultBufferPool.Put(buf) +} diff --git a/service/api/api.go b/service/api/api.go index 0ec47d86..bae2cbb1 100644 --- a/service/api/api.go +++ b/service/api/api.go @@ -11,6 +11,7 @@ import ( "github.com/datarhei/core/v16/encoding/json" "github.com/datarhei/core/v16/log" + "github.com/datarhei/core/v16/mem" ) type API interface { @@ -227,8 +228,10 @@ func (a *api) call(method, path string, body io.Reader) ([]byte, error) { } func (a *api) Monitor(id string, monitordata MonitorData) (MonitorResponse, error) { - var data bytes.Buffer - encoder := json.NewEncoder(&data) + data := mem.Get() + defer mem.Put(data) + + encoder := json.NewEncoder(data) if err := encoder.Encode(monitordata); err != nil { return MonitorResponse{}, err } @@ -240,7 +243,7 @@ func (a *api) Monitor(id string, monitordata MonitorData) (MonitorResponse, erro } */ - response, err := a.callWithRetry(http.MethodPut, "api/v1/core/monitor/"+id, &data) + response, err := a.callWithRetry(http.MethodPut, "api/v1/core/monitor/"+id, data) if err != nil { return MonitorResponse{}, fmt.Errorf("error sending request: %w", err) } diff --git a/session/registry.go b/session/registry.go index 1a08b3f0..8a454013 100644 --- a/session/registry.go +++ b/session/registry.go @@ -196,9 +196,9 @@ func (r *registry) sessionPersister(pattern *strftime.Strftime, bufferDuration t "buffer": bufferDuration, }).Log("Session persister started") - buffer := &bytes.Buffer{} path := pattern.FormatString(time.Now()) + buffer := &bytes.Buffer{} enc := json.NewEncoder(buffer) ticker := time.NewTicker(bufferDuration) From 719449a4c8028c0aafac3f71db4f9e00a43ddcc4 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 10 Oct 2024 16:35:39 +0200 Subject: [PATCH 36/64] Implement own byte buffer type --- cluster/raft/raft.go | 18 +--- http/client/client.go | 2 +- http/client/events.go | 2 +- http/client/process.go | 12 +-- http/middleware/compress/compress.go | 3 +- http/middleware/hlsrewrite/hlsrewrite.go | 6 +- http/middleware/hlsrewrite/hlsrewrite_test.go | 10 +- http/middleware/session/HLS.go | 12 +-- http/middleware/session/HLS_test.go | 6 +- http/middleware/session/session.go | 3 +- http/middleware/session/session_test.go | 6 +- io/fs/mem.go | 52 ++-------- io/fs/mem_storage.go | 55 ----------- io/fs/mem_test.go | 34 ------- io/fs/s3.go | 2 +- io/fs/sized.go | 16 ++-- mem/buffer.go | 94 ++++++++++++++----- mem/buffer_test.go | 47 ++++++++++ mem/pool.go | 46 +++++++++ service/api/api.go | 2 +- 20 files changed, 214 insertions(+), 214 deletions(-) create mode 100644 mem/buffer_test.go create mode 100644 mem/pool.go diff --git a/cluster/raft/raft.go b/cluster/raft/raft.go index 996345e0..347a7012 100644 --- a/cluster/raft/raft.go +++ b/cluster/raft/raft.go @@ -1,6 +1,7 @@ package raft import ( + "bytes" "encoding/base64" "fmt" "io" @@ -15,7 +16,6 @@ import ( "github.com/datarhei/core/v16/cluster/store" "github.com/datarhei/core/v16/encoding/json" "github.com/datarhei/core/v16/log" - "github.com/datarhei/core/v16/mem" "go.etcd.io/bbolt" "github.com/hashicorp/go-hclog" @@ -318,18 +318,6 @@ func (r *raft) LeadershipTransfer(id string) error { return nil } -type readCloserWrapper struct { - io.Reader -} - -func (rcw *readCloserWrapper) Read(p []byte) (int, error) { - return rcw.Reader.Read(p) -} - -func (rcw *readCloserWrapper) Close() error { - return nil -} - type Snapshot struct { Metadata *hcraft.SnapshotMeta Data string @@ -359,14 +347,14 @@ func (r *raft) Snapshot() (io.ReadCloser, error) { Data: base64.StdEncoding.EncodeToString(data), } - buffer := mem.Get() + buffer := &bytes.Buffer{} enc := json.NewEncoder(buffer) err = enc.Encode(snapshot) if err != nil { return nil, err } - return &readCloserWrapper{buffer}, nil + return io.NopCloser(buffer), nil } func (r *raft) start(fsm hcraft.FSM, peers []Peer, inmem bool) error { diff --git a/http/client/client.go b/http/client/client.go index ae25779e..a4d2f65c 100644 --- a/http/client/client.go +++ b/http/client/client.go @@ -661,7 +661,7 @@ func (r *restclient) login() error { e := json.NewEncoder(buf) e.Encode(login) - req, err := http.NewRequest("POST", r.address+r.prefix+"/login", buf) + req, err := http.NewRequest("POST", r.address+r.prefix+"/login", buf.Reader()) if err != nil { return err } diff --git a/http/client/events.go b/http/client/events.go index e2f25279..22780e1e 100644 --- a/http/client/events.go +++ b/http/client/events.go @@ -19,7 +19,7 @@ func (r *restclient) Events(ctx context.Context, filters api.EventFilters) (<-ch header := make(http.Header) header.Set("Accept", "application/x-json-stream") - stream, err := r.stream(ctx, "POST", "/v3/events", nil, header, "application/json", buf) + stream, err := r.stream(ctx, "POST", "/v3/events", nil, header, "application/json", buf.Reader()) if err != nil { return nil, err } diff --git a/http/client/process.go b/http/client/process.go index 485ab313..6a5f5e11 100644 --- a/http/client/process.go +++ b/http/client/process.go @@ -74,7 +74,7 @@ func (r *restclient) ProcessAdd(p *app.Config, metadata map[string]interface{}) e := json.NewEncoder(buf) e.Encode(config) - _, err := r.call("POST", "/v3/process", nil, nil, "application/json", buf) + _, err := r.call("POST", "/v3/process", nil, nil, "application/json", buf.Reader()) if err != nil { return err } @@ -95,7 +95,7 @@ func (r *restclient) ProcessUpdate(id app.ProcessID, p *app.Config, metadata map query := &url.Values{} query.Set("domain", id.Domain) - _, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID), query, nil, "application/json", buf) + _, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID), query, nil, "application/json", buf.Reader()) if err != nil { return err } @@ -116,7 +116,7 @@ func (r *restclient) ProcessReportSet(id app.ProcessID, report *app.Report) erro query := &url.Values{} query.Set("domain", id.Domain) - _, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID)+"/report", query, nil, "application/json", buf) + _, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID)+"/report", query, nil, "application/json", buf.Reader()) if err != nil { return err } @@ -145,7 +145,7 @@ func (r *restclient) ProcessCommand(id app.ProcessID, command string) error { query := &url.Values{} query.Set("domain", id.Domain) - _, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID)+"/command", query, nil, "application/json", buf) + _, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID)+"/command", query, nil, "application/json", buf.Reader()) return err } @@ -182,7 +182,7 @@ func (r *restclient) ProcessMetadataSet(id app.ProcessID, key string, metadata a query := &url.Values{} query.Set("domain", id.Domain) - _, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID)+"/metadata/"+url.PathEscape(key), query, nil, "application/json", buf) + _, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID)+"/metadata/"+url.PathEscape(key), query, nil, "application/json", buf.Reader()) return err } @@ -215,7 +215,7 @@ func (r *restclient) ProcessProbeConfig(p *app.Config) (api.Probe, error) { e := json.NewEncoder(buf) e.Encode(config) - data, err := r.call("POST", "/v3/process/probe", nil, nil, "application/json", buf) + data, err := r.call("POST", "/v3/process/probe", nil, nil, "application/json", buf.Reader()) if err != nil { return probe, err } diff --git a/http/middleware/compress/compress.go b/http/middleware/compress/compress.go index 43688d2f..c4244503 100644 --- a/http/middleware/compress/compress.go +++ b/http/middleware/compress/compress.go @@ -2,7 +2,6 @@ package compress import ( "bufio" - "bytes" "fmt" "io" "net" @@ -55,7 +54,7 @@ type compressResponseWriter struct { wroteBody bool minLength int minLengthExceeded bool - buffer *bytes.Buffer + buffer *mem.Buffer code int headerContentLength string scheme string diff --git a/http/middleware/hlsrewrite/hlsrewrite.go b/http/middleware/hlsrewrite/hlsrewrite.go index 02f21f2c..bfc6fd47 100644 --- a/http/middleware/hlsrewrite/hlsrewrite.go +++ b/http/middleware/hlsrewrite/hlsrewrite.go @@ -127,7 +127,7 @@ func (h *hlsrewrite) rewrite(c echo.Context, next echo.HandlerFunc) error { type hlsRewriter struct { http.ResponseWriter - buffer *bytes.Buffer + buffer *mem.Buffer } func (g *hlsRewriter) Write(data []byte) (int, error) { @@ -137,9 +137,9 @@ func (g *hlsRewriter) Write(data []byte) (int, error) { return w, err } -func (g *hlsRewriter) rewrite(pathPrefix []byte, buffer *bytes.Buffer) { +func (g *hlsRewriter) rewrite(pathPrefix []byte, buffer *mem.Buffer) { // Find all URLS in the .m3u8 and add the session ID to the query string - scanner := bufio.NewScanner(g.buffer) + scanner := bufio.NewScanner(g.buffer.Reader()) for scanner.Scan() { line := scanner.Bytes() diff --git a/http/middleware/hlsrewrite/hlsrewrite_test.go b/http/middleware/hlsrewrite/hlsrewrite_test.go index 7fb5341e..a149feaa 100644 --- a/http/middleware/hlsrewrite/hlsrewrite_test.go +++ b/http/middleware/hlsrewrite/hlsrewrite_test.go @@ -1,10 +1,10 @@ package hlsrewrite import ( - "bytes" "os" "testing" + "github.com/datarhei/core/v16/mem" "github.com/stretchr/testify/require" ) @@ -16,12 +16,12 @@ func TestRewrite(t *testing.T) { require.NoError(t, err) r := &hlsRewriter{ - buffer: &bytes.Buffer{}, + buffer: &mem.Buffer{}, } r.Write(data) - buffer := &bytes.Buffer{} + buffer := &mem.Buffer{} prefix := []byte("/path/to/foobar/") r.rewrite(prefix, buffer) @@ -33,10 +33,10 @@ func BenchmarkRewrite(b *testing.B) { require.NoError(b, err) r := &hlsRewriter{ - buffer: &bytes.Buffer{}, + buffer: &mem.Buffer{}, } - buffer := &bytes.Buffer{} + buffer := &mem.Buffer{} prefix := []byte("/path/to/foobar/") for i := 0; i < b.N; i++ { diff --git a/http/middleware/session/HLS.go b/http/middleware/session/HLS.go index c76dd4d2..f8654bd6 100644 --- a/http/middleware/session/HLS.go +++ b/http/middleware/session/HLS.go @@ -3,7 +3,6 @@ package session import ( "bufio" - "bytes" "io" "net/http" "net/url" @@ -11,6 +10,7 @@ import ( "path/filepath" "strings" + "github.com/datarhei/core/v16/mem" "github.com/datarhei/core/v16/net" "github.com/lithammer/shortuuid/v4" @@ -233,7 +233,7 @@ func (h *handler) handleHLSEgress(c echo.Context, _ string, data map[string]inte type segmentReader struct { reader io.ReadCloser - buffer *bytes.Buffer + buffer *mem.Buffer size int64 } @@ -255,7 +255,7 @@ func (r *segmentReader) getSegments(dir string) []string { segments := []string{} // Find all segment URLs in the .m3u8 - scanner := bufio.NewScanner(r.buffer) + scanner := bufio.NewScanner(r.buffer.Reader()) for scanner.Scan() { line := scanner.Text() @@ -299,7 +299,7 @@ func (r *segmentReader) getSegments(dir string) []string { type sessionRewriter struct { http.ResponseWriter - buffer *bytes.Buffer + buffer *mem.Buffer } func (g *sessionRewriter) Write(data []byte) (int, error) { @@ -307,11 +307,11 @@ func (g *sessionRewriter) Write(data []byte) (int, error) { return g.buffer.Write(data) } -func (g *sessionRewriter) rewriteHLS(sessionID string, requestURL *url.URL, buffer *bytes.Buffer) { +func (g *sessionRewriter) rewriteHLS(sessionID string, requestURL *url.URL, buffer *mem.Buffer) { isMaster := false // Find all URLS in the .m3u8 and add the session ID to the query string - scanner := bufio.NewScanner(g.buffer) + scanner := bufio.NewScanner(g.buffer.Reader()) for scanner.Scan() { byteline := scanner.Bytes() diff --git a/http/middleware/session/HLS_test.go b/http/middleware/session/HLS_test.go index e5cede1a..b7bc6564 100644 --- a/http/middleware/session/HLS_test.go +++ b/http/middleware/session/HLS_test.go @@ -19,7 +19,7 @@ func TestHLSSegmentReader(t *testing.T) { br := &segmentReader{ reader: io.NopCloser(r), - buffer: &bytes.Buffer{}, + buffer: &mem.Buffer{}, } _, err = io.ReadAll(br) @@ -66,7 +66,7 @@ func TestHLSRewrite(t *testing.T) { require.NoError(t, err) br := &sessionRewriter{ - buffer: &bytes.Buffer{}, + buffer: &mem.Buffer{}, } _, err = br.Write(data) @@ -75,7 +75,7 @@ func TestHLSRewrite(t *testing.T) { u, err := url.Parse("http://example.com/test.m3u8") require.NoError(t, err) - buffer := &bytes.Buffer{} + buffer := &mem.Buffer{} br.rewriteHLS("oT5GV8eWBbRAh4aib5egoK", u, buffer) diff --git a/http/middleware/session/session.go b/http/middleware/session/session.go index 3f12f405..d30eae08 100644 --- a/http/middleware/session/session.go +++ b/http/middleware/session/session.go @@ -1,7 +1,6 @@ package session import ( - "bytes" "fmt" "io" "net/http" @@ -177,7 +176,7 @@ func verifySession(raw interface{}, path, referrer string) (map[string]interface return data, nil } -func headerSize(header http.Header, buffer *bytes.Buffer) int64 { +func headerSize(header http.Header, buffer *mem.Buffer) int64 { buffer.Reset() header.Write(buffer) diff --git a/http/middleware/session/session_test.go b/http/middleware/session/session_test.go index b8633b19..f5c0c597 100644 --- a/http/middleware/session/session_test.go +++ b/http/middleware/session/session_test.go @@ -1,11 +1,11 @@ package session import ( - "bytes" "net/http" "testing" "github.com/datarhei/core/v16/encoding/json" + "github.com/datarhei/core/v16/mem" "github.com/stretchr/testify/require" ) @@ -143,7 +143,7 @@ func TestHeaderSize(t *testing.T) { header.Add("Content-Type", "application/json") header.Add("Content-Encoding", "gzip") - buffer := &bytes.Buffer{} + buffer := &mem.Buffer{} size := headerSize(header, buffer) require.Equal(t, "Content-Encoding: gzip\r\nContent-Type: application/json\r\n", buffer.String()) @@ -156,7 +156,7 @@ func BenchmarkHeaderSize(b *testing.B) { header.Add("Content-Type", "application/json") header.Add("Content-Encoding", "gzip") - buffer := &bytes.Buffer{} + buffer := &mem.Buffer{} for i := 0; i < b.N; i++ { headerSize(header, buffer) diff --git a/io/fs/mem.go b/io/fs/mem.go index 2eb68410..237d486e 100644 --- a/io/fs/mem.go +++ b/io/fs/mem.go @@ -2,7 +2,6 @@ package fs import ( "bytes" - "errors" "fmt" "io" "io/fs" @@ -69,8 +68,8 @@ func (f *memFileInfo) IsDir() bool { type memFile struct { memFileInfo - data *bytes.Buffer // Contents of the file - r *bytes.Reader + data *mem.Buffer // Contents of the file + r io.ReadSeeker } func (f *memFile) Name() string { @@ -380,9 +379,7 @@ func (fs *memFilesystem) ReadFile(path string) ([]byte, error) { } data := pool.Get() - - data.Grow(file.data.Len()) - data.Write(file.data.Bytes()) + file.data.WriteTo(data) return data.Bytes(), nil } @@ -433,35 +430,6 @@ func (fs *memFilesystem) Symlink(oldname, newname string) error { return nil } -func copyToBufferFromReader(buf *bytes.Buffer, r io.Reader, _ int) (int64, error) { - chunkData := [128 * 1024]byte{} - chunk := chunkData[0:] - - size := int64(0) - - for { - n, err := r.Read(chunk) - if n != 0 { - buf.Write(chunk[:n]) - size += int64(n) - } - - if err != nil { - if errors.Is(err, io.EOF) { - return size, nil - } - - return size, err - } - - if n == 0 { - break - } - } - - return size, nil -} - func (fs *memFilesystem) WriteFileReader(path string, r io.Reader, sizeHint int) (int64, bool, error) { path = fs.cleanPath(path) @@ -480,11 +448,7 @@ func (fs *memFilesystem) WriteFileReader(path string, r io.Reader, sizeHint int) data: pool.Get(), } - if sizeHint > 0 && sizeHint < 5*1024*1024 { - newFile.data.Grow(sizeHint) - } - - size, err := copyToBufferFromReader(newFile.data, r, 8*1024) + size, err := newFile.data.ReadFrom(r) if err != nil { fs.logger.WithFields(log.Fields{ "path": path, @@ -558,10 +522,9 @@ func (fs *memFilesystem) AppendFileReader(path string, r io.Reader, sizeHint int data: pool.Get(), } - newFile.data.Grow(file.data.Len()) - newFile.data.Write(file.data.Bytes()) + file.data.WriteTo(newFile.data) - size, err := copyToBufferFromReader(newFile.data, r, 8*1024) + size, err := newFile.data.ReadFrom(r) if err != nil { fs.logger.WithFields(log.Fields{ "path": path, @@ -720,8 +683,7 @@ func (fs *memFilesystem) Copy(src, dst string) error { data: pool.Get(), } - dstFile.data.Grow(srcFile.data.Len()) - dstFile.data.Write(srcFile.data.Bytes()) + srcFile.data.WriteTo(dstFile.data) f, replace := fs.storage.Store(dst, dstFile) diff --git a/io/fs/mem_storage.go b/io/fs/mem_storage.go index 8267660a..18b447ba 100644 --- a/io/fs/mem_storage.go +++ b/io/fs/mem_storage.go @@ -1,7 +1,6 @@ package fs import ( - "bytes" "sync" "github.com/dolthub/swiss" @@ -122,33 +121,6 @@ func (m *mapStorage) Load(key string) (*memFile, bool) { return v, ok } -func (m *mapStorage) LoadAndCopy(key string) (*memFile, bool) { - m.lock.RLock() - defer m.lock.RUnlock() - - v, ok := m.files[key] - if !ok { - return nil, false - } - - f := &memFile{ - memFileInfo: memFileInfo{ - name: v.name, - size: v.size, - dir: v.dir, - lastMod: v.lastMod, - linkTo: v.linkTo, - }, - r: nil, - } - - if v.data != nil { - f.data = bytes.NewBuffer(v.data.Bytes()) - } - - return f, true -} - func (m *mapStorage) Has(key string) bool { m.lock.RLock() defer m.lock.RUnlock() @@ -214,33 +186,6 @@ func (m *swissMapStorage) Load(key string) (*memFile, bool) { return m.files.Get(key) } -func (m *swissMapStorage) LoadAndCopy(key string) (*memFile, bool) { - token := m.lock.RLock() - defer m.lock.RUnlock(token) - - v, ok := m.files.Get(key) - if !ok { - return nil, false - } - - f := &memFile{ - memFileInfo: memFileInfo{ - name: v.name, - size: v.size, - dir: v.dir, - lastMod: v.lastMod, - linkTo: v.linkTo, - }, - r: nil, - } - - if v.data != nil { - f.data = bytes.NewBuffer(v.data.Bytes()) - } - - return f, true -} - func (m *swissMapStorage) Has(key string) bool { token := m.lock.RLock() defer m.lock.RUnlock(token) diff --git a/io/fs/mem_test.go b/io/fs/mem_test.go index 63a8f0f3..be179d5c 100644 --- a/io/fs/mem_test.go +++ b/io/fs/mem_test.go @@ -1,7 +1,6 @@ package fs import ( - "bytes" "context" "fmt" "io" @@ -231,36 +230,3 @@ func benchmarkMemReadFileWhileWriting(b *testing.B, fs Filesystem) { readerWg.Wait() } - -func BenchmarkBufferReadFrom(b *testing.B) { - data := []byte(rand.StringAlphanumeric(1024 * 1024)) - - for i := 0; i < b.N; i++ { - r := bytes.NewReader(data) - buf := &bytes.Buffer{} - buf.ReadFrom(r) - } -} - -func TestBufferReadChunks(t *testing.T) { - data := []byte(rand.StringAlphanumeric(1024 * 1024)) - - r := bytes.NewReader(data) - buf := &bytes.Buffer{} - - copyToBufferFromReader(buf, r, 32*1024) - - res := bytes.Compare(data, buf.Bytes()) - require.Equal(t, 0, res) -} - -func BenchmarkBufferReadChunks(b *testing.B) { - data := []byte(rand.StringAlphanumeric(1024 * 1024)) - - for i := 0; i < b.N; i++ { - r := bytes.NewReader(data) - buf := &bytes.Buffer{} - - copyToBufferFromReader(buf, r, 32*1024) - } -} diff --git a/io/fs/s3.go b/io/fs/s3.go index c1659aef..316fd7ca 100644 --- a/io/fs/s3.go +++ b/io/fs/s3.go @@ -378,7 +378,7 @@ func (fs *s3Filesystem) AppendFileReader(path string, r io.Reader, sizeHint int) buffer.ReadFrom(object) buffer.ReadFrom(r) - size, _, err := fs.write(path, buffer) + size, _, err := fs.write(path, buffer.Reader()) return size, err } diff --git a/io/fs/sized.go b/io/fs/sized.go index cfa159eb..bd2c3fc6 100644 --- a/io/fs/sized.go +++ b/io/fs/sized.go @@ -71,8 +71,10 @@ func (r *sizedFilesystem) WriteFileReader(path string, rd io.Reader, sizeHint in return r.Filesystem.WriteFileReader(path, rd, sizeHint) } - data := bytes.Buffer{} - size, err := copyToBufferFromReader(&data, rd, 8*1024) + data := pool.Get() + defer pool.Put(data) + + size, err := data.ReadFrom(rd) if err != nil { return -1, false, err } @@ -97,7 +99,7 @@ func (r *sizedFilesystem) WriteFileReader(path string, rd io.Reader, sizeHint in } } - return r.Filesystem.WriteFileReader(path, &data, int(size)) + return r.Filesystem.WriteFileReader(path, data.Reader(), int(size)) } func (r *sizedFilesystem) WriteFile(path string, data []byte) (int64, bool, error) { @@ -141,8 +143,10 @@ func (r *sizedFilesystem) AppendFileReader(path string, rd io.Reader, sizeHint i return r.Filesystem.AppendFileReader(path, rd, sizeHint) } - data := bytes.Buffer{} - size, err := copyToBufferFromReader(&data, rd, 8*1024) + data := pool.Get() + defer pool.Put(data) + + size, err := data.ReadFrom(rd) if err != nil { return -1, err } @@ -162,7 +166,7 @@ func (r *sizedFilesystem) AppendFileReader(path string, rd io.Reader, sizeHint i } } - return r.Filesystem.AppendFileReader(path, &data, int(size)) + return r.Filesystem.AppendFileReader(path, data.Reader(), int(size)) } func (r *sizedFilesystem) Purge(size int64) int64 { diff --git a/mem/buffer.go b/mem/buffer.go index af676c10..e3a39fe9 100644 --- a/mem/buffer.go +++ b/mem/buffer.go @@ -2,46 +2,90 @@ package mem import ( "bytes" - "sync" + "errors" + "io" ) -type BufferPool struct { - pool sync.Pool +type Buffer struct { + data bytes.Buffer } -func NewBufferPool() *BufferPool { - p := &BufferPool{ - pool: sync.Pool{ - New: func() any { - return &bytes.Buffer{} - }, - }, - } +// Len returns the length of the buffer. +func (b *Buffer) Len() int { + return b.data.Len() +} - return p +// Bytes returns the buffer, but keeps ownership. +func (b *Buffer) Bytes() []byte { + return b.data.Bytes() } -func (p *BufferPool) Get() *bytes.Buffer { - buf := p.pool.Get().(*bytes.Buffer) - buf.Reset() +// WriteTo writes the bytes to the writer. +func (b *Buffer) WriteTo(w io.Writer) (int64, error) { + n, err := w.Write(b.data.Bytes()) + return int64(n), err +} - return buf +// Reset empties the buffer and keeps it's capacity. +func (b *Buffer) Reset() { + b.data.Reset() } -func (p *BufferPool) Put(buf *bytes.Buffer) { - p.pool.Put(buf) +// Write appends to the buffer. +func (b *Buffer) Write(p []byte) (int, error) { + return b.data.Write(p) } -var DefaultBufferPool *BufferPool +// ReadFrom reads from the reader and appends to the buffer. +func (b *Buffer) ReadFrom(r io.Reader) (int64, error) { + if br, ok := r.(*bytes.Reader); ok { + b.data.Grow(br.Len()) + } + + chunkData := [128 * 1024]byte{} + chunk := chunkData[0:] + + size := int64(0) + + for { + n, err := r.Read(chunk) + if n != 0 { + b.data.Write(chunk[:n]) + size += int64(n) + } + + if err != nil { + if errors.Is(err, io.EOF) { + return size, nil + } + + return size, err + } + + if n == 0 { + break + } + } + + return size, nil +} + +// WriteByte appends a byte to the buffer. +func (b *Buffer) WriteByte(c byte) error { + return b.data.WriteByte(c) +} -func init() { - DefaultBufferPool = NewBufferPool() +// WriteString appends a string to the buffer. +func (b *Buffer) WriteString(s string) (n int, err error) { + return b.data.WriteString(s) } -func Get() *bytes.Buffer { - return DefaultBufferPool.Get() +// Reader returns a bytes.Reader based on the data in the buffer. +func (b *Buffer) Reader() *bytes.Reader { + return bytes.NewReader(b.Bytes()) } -func Put(buf *bytes.Buffer) { - DefaultBufferPool.Put(buf) +// String returns the data in the buffer a string. +func (b *Buffer) String() string { + return b.data.String() } diff --git a/mem/buffer_test.go b/mem/buffer_test.go new file mode 100644 index 00000000..a19c1a72 --- /dev/null +++ b/mem/buffer_test.go @@ -0,0 +1,47 @@ +package mem + +import ( + "bytes" + "io" + "testing" + + "github.com/datarhei/core/v16/math/rand" + + "github.com/stretchr/testify/require" +) + +func TestBufferReadChunks(t *testing.T) { + data := []byte(rand.StringAlphanumeric(1024 * 1024)) + + r := bytes.NewReader(data) + buf := &Buffer{} + + buf.ReadFrom(r) + + res := bytes.Compare(data, buf.Bytes()) + require.Equal(t, 0, res) +} + +func BenchmarkBufferReadFrom(b *testing.B) { + data := []byte(rand.StringAlphanumeric(1024 * 1024)) + + r := bytes.NewReader(data) + + for i := 0; i < b.N; i++ { + r.Seek(0, io.SeekStart) + buf := &Buffer{} + buf.ReadFrom(r) + } +} + +func BenchmarkBytesBufferReadFrom(b *testing.B) { + data := []byte(rand.StringAlphanumeric(1024 * 1024)) + + r := bytes.NewReader(data) + + for i := 0; i < b.N; i++ { + r.Seek(0, io.SeekStart) + buf := &bytes.Buffer{} + buf.ReadFrom(r) + } +} diff --git a/mem/pool.go b/mem/pool.go new file mode 100644 index 00000000..641791f3 --- /dev/null +++ b/mem/pool.go @@ -0,0 +1,46 @@ +package mem + +import ( + "sync" +) + +type BufferPool struct { + pool sync.Pool +} + +func NewBufferPool() *BufferPool { + p := &BufferPool{ + pool: sync.Pool{ + New: func() any { + return &Buffer{} + }, + }, + } + + return p +} + +func (p *BufferPool) Get() *Buffer { + buf := p.pool.Get().(*Buffer) + buf.Reset() + + return buf +} + +func (p *BufferPool) Put(buf *Buffer) { + p.pool.Put(buf) +} + +var DefaultBufferPool *BufferPool + +func init() { + DefaultBufferPool = NewBufferPool() +} + +func Get() *Buffer { + return DefaultBufferPool.Get() +} + +func Put(buf *Buffer) { + DefaultBufferPool.Put(buf) +} diff --git a/service/api/api.go b/service/api/api.go index bae2cbb1..b34f589a 100644 --- a/service/api/api.go +++ b/service/api/api.go @@ -243,7 +243,7 @@ func (a *api) Monitor(id string, monitordata MonitorData) (MonitorResponse, erro } */ - response, err := a.callWithRetry(http.MethodPut, "api/v1/core/monitor/"+id, data) + response, err := a.callWithRetry(http.MethodPut, "api/v1/core/monitor/"+id, data.Reader()) if err != nil { return MonitorResponse{}, fmt.Errorf("error sending request: %w", err) } From 92decc7111edc383dd854fad671e8844596e3ba2 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Mon, 14 Oct 2024 10:51:35 +0200 Subject: [PATCH 37/64] Use global buffer pool where feasible --- http/client/client.go | 7 +- http/client/events.go | 5 +- http/client/process.go | 25 +++--- http/middleware/cache/cache.go | 5 +- http/middleware/compress/compress.go | 6 +- http/middleware/session/HLS.go | 28 +++--- http/middleware/session/HLS_test.go | 16 ++-- http/middleware/session/HTTP.go | 5 +- http/middleware/session/session.go | 3 - io/fs/mem.go | 112 +++++++---------------- io/fs/mem_storage.go | 100 ++++++++++++++++++++- io/fs/sized.go | 10 ++- mem/buffer.go | 128 +++++++++++++++++++++------ mem/pool.go | 121 +++++++++++++++++++++++-- restream/app/process.go | 4 +- service/api/api.go | 9 +- update/update.go | 9 +- 17 files changed, 404 insertions(+), 189 deletions(-) diff --git a/http/client/client.go b/http/client/client.go index a4d2f65c..d877076b 100644 --- a/http/client/client.go +++ b/http/client/client.go @@ -199,8 +199,6 @@ type restclient struct { connectedCore *semver.Version methods map[string][]apiconstraint } - - pool *mem.BufferPool } // New returns a new REST API client for the given config. The error is non-nil @@ -214,7 +212,6 @@ func New(config Config) (RestClient, error) { auth0Token: config.Auth0Token, client: config.Client, clientTimeout: config.Timeout, - pool: mem.NewBufferPool(), } if len(config.AccessToken) != 0 { @@ -655,8 +652,8 @@ func (r *restclient) login() error { login.Password = r.password } - buf := r.pool.Get() - defer r.pool.Put(buf) + buf := mem.Get() + defer mem.Put(buf) e := json.NewEncoder(buf) e.Encode(login) diff --git a/http/client/events.go b/http/client/events.go index 22780e1e..97909edb 100644 --- a/http/client/events.go +++ b/http/client/events.go @@ -7,11 +7,12 @@ import ( "github.com/datarhei/core/v16/encoding/json" "github.com/datarhei/core/v16/http/api" + "github.com/datarhei/core/v16/mem" ) func (r *restclient) Events(ctx context.Context, filters api.EventFilters) (<-chan api.Event, error) { - buf := r.pool.Get() - defer r.pool.Put(buf) + buf := mem.Get() + defer mem.Put(buf) e := json.NewEncoder(buf) e.Encode(filters) diff --git a/http/client/process.go b/http/client/process.go index 6a5f5e11..8de34aa8 100644 --- a/http/client/process.go +++ b/http/client/process.go @@ -6,6 +6,7 @@ import ( "github.com/datarhei/core/v16/encoding/json" "github.com/datarhei/core/v16/http/api" + "github.com/datarhei/core/v16/mem" "github.com/datarhei/core/v16/restream/app" ) @@ -65,8 +66,8 @@ func (r *restclient) Process(id app.ProcessID, filter []string) (api.Process, er } func (r *restclient) ProcessAdd(p *app.Config, metadata map[string]interface{}) error { - buf := r.pool.Get() - defer r.pool.Put(buf) + buf := mem.Get() + defer mem.Put(buf) config := api.ProcessConfig{} config.Unmarshal(p, metadata) @@ -83,8 +84,8 @@ func (r *restclient) ProcessAdd(p *app.Config, metadata map[string]interface{}) } func (r *restclient) ProcessUpdate(id app.ProcessID, p *app.Config, metadata map[string]interface{}) error { - buf := r.pool.Get() - defer r.pool.Put(buf) + buf := mem.Get() + defer mem.Put(buf) config := api.ProcessConfig{} config.Unmarshal(p, metadata) @@ -104,8 +105,8 @@ func (r *restclient) ProcessUpdate(id app.ProcessID, p *app.Config, metadata map } func (r *restclient) ProcessReportSet(id app.ProcessID, report *app.Report) error { - buf := r.pool.Get() - defer r.pool.Put(buf) + buf := mem.Get() + defer mem.Put(buf) data := api.ProcessReport{} data.Unmarshal(report) @@ -134,8 +135,8 @@ func (r *restclient) ProcessDelete(id app.ProcessID) error { } func (r *restclient) ProcessCommand(id app.ProcessID, command string) error { - buf := r.pool.Get() - defer r.pool.Put(buf) + buf := mem.Get() + defer mem.Put(buf) e := json.NewEncoder(buf) e.Encode(api.Command{ @@ -173,8 +174,8 @@ func (r *restclient) ProcessMetadata(id app.ProcessID, key string) (api.Metadata } func (r *restclient) ProcessMetadataSet(id app.ProcessID, key string, metadata api.Metadata) error { - buf := r.pool.Get() - defer r.pool.Put(buf) + buf := mem.Get() + defer mem.Put(buf) e := json.NewEncoder(buf) e.Encode(metadata) @@ -206,8 +207,8 @@ func (r *restclient) ProcessProbe(id app.ProcessID) (api.Probe, error) { func (r *restclient) ProcessProbeConfig(p *app.Config) (api.Probe, error) { var probe api.Probe - buf := r.pool.Get() - defer r.pool.Put(buf) + buf := mem.Get() + defer mem.Put(buf) config := api.ProcessConfig{} config.Unmarshal(p, nil) diff --git a/http/middleware/cache/cache.go b/http/middleware/cache/cache.go index 3b25fc17..f7d12aa9 100644 --- a/http/middleware/cache/cache.go +++ b/http/middleware/cache/cache.go @@ -2,13 +2,13 @@ package cache import ( - "bytes" "fmt" "net/http" "path" "strings" "github.com/datarhei/core/v16/http/cache" + "github.com/datarhei/core/v16/mem" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" @@ -78,6 +78,7 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { w := &cacheWriter{ header: writer.Header().Clone(), + body: mem.Get(), } res.Writer = w @@ -170,7 +171,7 @@ type cacheObject struct { type cacheWriter struct { code int header http.Header - body bytes.Buffer + body *mem.Buffer } func (w *cacheWriter) Header() http.Header { diff --git a/http/middleware/compress/compress.go b/http/middleware/compress/compress.go index c4244503..b595f47b 100644 --- a/http/middleware/compress/compress.go +++ b/http/middleware/compress/compress.go @@ -138,8 +138,6 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { zstdCompressor = NewZstd(config.Level) } - bufferPool := mem.NewBufferPool() - return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { if config.Skipper(c) { @@ -171,7 +169,7 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { rw := res.Writer compressor.Reset(rw) - buffer := bufferPool.Get() + buffer := mem.Get() grw := &compressResponseWriter{ Compressor: compressor, @@ -208,7 +206,7 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { } } compressor.Close() - bufferPool.Put(buffer) + mem.Put(buffer) compress.Release(compressor) }() diff --git a/http/middleware/session/HLS.go b/http/middleware/session/HLS.go index f8654bd6..fa03d4e2 100644 --- a/http/middleware/session/HLS.go +++ b/http/middleware/session/HLS.go @@ -39,7 +39,7 @@ func (h *handler) handleHLSIngress(c echo.Context, _ string, data map[string]int reader := req.Body r := &segmentReader{ reader: req.Body, - buffer: h.bufferPool.Get(), + buffer: mem.Get(), } req.Body = r @@ -47,7 +47,7 @@ func (h *handler) handleHLSIngress(c echo.Context, _ string, data map[string]int req.Body = reader if r.size == 0 { - h.bufferPool.Put(r.buffer) + mem.Put(r.buffer) return } @@ -60,10 +60,10 @@ func (h *handler) handleHLSIngress(c echo.Context, _ string, data map[string]int h.hlsIngressCollector.Extra(path, data) } - buffer := h.bufferPool.Get() + buffer := mem.Get() h.hlsIngressCollector.Ingress(path, headerSize(req.Header, buffer)) h.hlsIngressCollector.Ingress(path, r.size) - h.bufferPool.Put(buffer) + mem.Put(buffer) segments := r.getSegments(urlpath.Dir(path)) @@ -79,7 +79,7 @@ func (h *handler) handleHLSIngress(c echo.Context, _ string, data map[string]int h.lock.Unlock() } - h.bufferPool.Put(r.buffer) + mem.Put(r.buffer) }() } else if strings.HasSuffix(path, ".ts") { // Get the size of the .ts file and store it in the ts-map for later use. @@ -93,11 +93,11 @@ func (h *handler) handleHLSIngress(c echo.Context, _ string, data map[string]int req.Body = reader if r.size != 0 { - buffer := h.bufferPool.Get() + buffer := mem.Get() h.lock.Lock() h.rxsegments[path] = r.size + headerSize(req.Header, buffer) h.lock.Unlock() - h.bufferPool.Put(buffer) + mem.Put(buffer) } }() } @@ -179,7 +179,7 @@ func (h *handler) handleHLSEgress(c echo.Context, _ string, data map[string]inte // the data that we need to rewrite. rewriter = &sessionRewriter{ ResponseWriter: res.Writer, - buffer: h.bufferPool.Get(), + buffer: mem.Get(), } res.Writer = rewriter @@ -197,11 +197,11 @@ func (h *handler) handleHLSEgress(c echo.Context, _ string, data map[string]inte if rewrite { if res.Status < 200 || res.Status >= 300 { res.Write(rewriter.buffer.Bytes()) - h.bufferPool.Put(rewriter.buffer) + mem.Put(rewriter.buffer) return nil } - buffer := h.bufferPool.Get() + buffer := mem.Get() // Rewrite the data befor sending it to the client rewriter.rewriteHLS(sessionID, c.Request().URL, buffer) @@ -209,17 +209,17 @@ func (h *handler) handleHLSEgress(c echo.Context, _ string, data map[string]inte res.Header().Set("Cache-Control", "private") res.Write(buffer.Bytes()) - h.bufferPool.Put(buffer) - h.bufferPool.Put(rewriter.buffer) + mem.Put(buffer) + mem.Put(rewriter.buffer) } if isM3U8 || isTS { if res.Status >= 200 && res.Status < 300 { // Collect how many bytes we've written in this session - buffer := h.bufferPool.Get() + buffer := mem.Get() h.hlsEgressCollector.Egress(sessionID, headerSize(res.Header(), buffer)) h.hlsEgressCollector.Egress(sessionID, res.Size) - h.bufferPool.Put(buffer) + mem.Put(buffer) if isTS { // Activate the session. If the session is already active, this is a noop diff --git a/http/middleware/session/HLS_test.go b/http/middleware/session/HLS_test.go index b7bc6564..5d27103b 100644 --- a/http/middleware/session/HLS_test.go +++ b/http/middleware/session/HLS_test.go @@ -39,8 +39,6 @@ func TestHLSSegmentReader(t *testing.T) { } func BenchmarkHLSSegmentReader(b *testing.B) { - pool := mem.NewBufferPool() - data, err := os.ReadFile("./fixtures/segments.txt") require.NoError(b, err) @@ -51,13 +49,13 @@ func BenchmarkHLSSegmentReader(b *testing.B) { rd.Reset(data) br := &segmentReader{ reader: io.NopCloser(r), - buffer: pool.Get(), + buffer: mem.Get(), } _, err := io.ReadAll(br) require.NoError(b, err) - pool.Put(br.buffer) + mem.Put(br.buffer) } } @@ -86,8 +84,6 @@ func TestHLSRewrite(t *testing.T) { } func BenchmarkHLSRewrite(b *testing.B) { - pool := mem.NewBufferPool() - data, err := os.ReadFile("./fixtures/segments.txt") require.NoError(b, err) @@ -96,17 +92,17 @@ func BenchmarkHLSRewrite(b *testing.B) { for i := 0; i < b.N; i++ { br := &sessionRewriter{ - buffer: pool.Get(), + buffer: mem.Get(), } _, err = br.Write(data) require.NoError(b, err) - buffer := pool.Get() + buffer := mem.Get() br.rewriteHLS("oT5GV8eWBbRAh4aib5egoK", u, buffer) - pool.Put(br.buffer) - pool.Put(buffer) + mem.Put(br.buffer) + mem.Put(buffer) } } diff --git a/http/middleware/session/HTTP.go b/http/middleware/session/HTTP.go index 4913171f..ccd50127 100644 --- a/http/middleware/session/HTTP.go +++ b/http/middleware/session/HTTP.go @@ -3,6 +3,7 @@ package session import ( "net/url" + "github.com/datarhei/core/v16/mem" "github.com/labstack/echo/v4" "github.com/lithammer/shortuuid/v4" ) @@ -45,7 +46,7 @@ func (h *handler) handleHTTP(c echo.Context, _ string, data map[string]interface h.httpCollector.Extra(id, data) defer func() { - buffer := h.bufferPool.Get() + buffer := mem.Get() req.Body = reader h.httpCollector.Ingress(id, r.size+headerSize(req.Header, buffer)) @@ -58,7 +59,7 @@ func (h *handler) handleHTTP(c echo.Context, _ string, data map[string]interface h.httpCollector.Close(id) - h.bufferPool.Put(buffer) + mem.Put(buffer) }() return next(c) diff --git a/http/middleware/session/session.go b/http/middleware/session/session.go index d30eae08..9cd77955 100644 --- a/http/middleware/session/session.go +++ b/http/middleware/session/session.go @@ -44,8 +44,6 @@ type handler struct { rxsegments map[string]int64 lock sync.Mutex - - bufferPool *mem.BufferPool } // New returns a new session middleware with default config @@ -77,7 +75,6 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { hlsIngressCollector: config.HLSIngressCollector, reSessionID: regexp.MustCompile(`^[` + regexp.QuoteMeta(shortuuid.DefaultAlphabet) + `]{22}$`), rxsegments: make(map[string]int64), - bufferPool: mem.NewBufferPool(), } return func(next echo.HandlerFunc) echo.HandlerFunc { diff --git a/io/fs/mem.go b/io/fs/mem.go index 237d486e..2cc51637 100644 --- a/io/fs/mem.go +++ b/io/fs/mem.go @@ -111,25 +111,12 @@ func (f *memFile) Close() error { f.r = nil - return nil -} - -func (f *memFile) free() { - f.Close() - - if f.data == nil { - return + if f.data != nil { + mem.Put(f.data) + f.data = nil } - pool.Put(f.data) - - f.data = nil -} - -var pool *mem.BufferPool = nil - -func init() { - pool = mem.NewBufferPool() + return nil } type memFilesystem struct { @@ -331,24 +318,13 @@ func (fs *memFilesystem) Files() int64 { func (fs *memFilesystem) Open(path string) File { path = fs.cleanPath(path) - file, ok := fs.storage.Load(path) + newFile, ok := fs.storage.LoadAndCopy(path) if !ok { return nil } - newFile := &memFile{ - memFileInfo: memFileInfo{ - name: file.name, - size: file.size, - dir: file.dir, - lastMod: file.lastMod, - linkTo: file.linkTo, - }, - data: file.data, - } - - if len(file.linkTo) != 0 { - file, ok := fs.storage.Load(file.linkTo) + if len(newFile.linkTo) != 0 { + file, ok := fs.storage.LoadAndCopy(newFile.linkTo) if !ok { return nil } @@ -358,7 +334,7 @@ func (fs *memFilesystem) Open(path string) File { newFile.size = file.size } - newFile.r = bytes.NewReader(newFile.data.Bytes()) + newFile.r = newFile.data.Reader() return newFile } @@ -366,22 +342,19 @@ func (fs *memFilesystem) Open(path string) File { func (fs *memFilesystem) ReadFile(path string) ([]byte, error) { path = fs.cleanPath(path) - file, ok := fs.storage.Load(path) + file, ok := fs.storage.LoadAndCopy(path) if !ok { return nil, ErrNotExist } if len(file.linkTo) != 0 { - file, ok = fs.storage.Load(file.linkTo) + file, ok = fs.storage.LoadAndCopy(file.linkTo) if !ok { return nil, ErrNotExist } } - data := pool.Get() - file.data.WriteTo(data) - - return data.Bytes(), nil + return file.data.Bytes(), nil } func (fs *memFilesystem) Symlink(oldname, newname string) error { @@ -421,7 +394,7 @@ func (fs *memFilesystem) Symlink(oldname, newname string) error { defer fs.sizeLock.Unlock() if replaced { - oldFile.free() + oldFile.Close() fs.currentSize -= oldFile.size } @@ -445,7 +418,7 @@ func (fs *memFilesystem) WriteFileReader(path string, r io.Reader, sizeHint int) size: 0, lastMod: time.Now(), }, - data: pool.Get(), + data: mem.Get(), } size, err := newFile.data.ReadFrom(r) @@ -456,7 +429,7 @@ func (fs *memFilesystem) WriteFileReader(path string, r io.Reader, sizeHint int) "error": err, }).Warn().Log("Incomplete file") - newFile.free() + newFile.Close() return -1, false, fmt.Errorf("incomplete file") } @@ -473,7 +446,7 @@ func (fs *memFilesystem) WriteFileReader(path string, r io.Reader, sizeHint int) defer fs.sizeLock.Unlock() if replace { - oldFile.free() + oldFile.Close() fs.currentSize -= oldFile.size } @@ -506,25 +479,13 @@ func (fs *memFilesystem) WriteFileSafe(path string, data []byte) (int64, bool, e func (fs *memFilesystem) AppendFileReader(path string, r io.Reader, sizeHint int) (int64, error) { path = fs.cleanPath(path) - file, hasFile := fs.storage.Load(path) + file, hasFile := fs.storage.LoadAndCopy(path) if !hasFile { size, _, err := fs.WriteFileReader(path, r, sizeHint) return size, err } - newFile := &memFile{ - memFileInfo: memFileInfo{ - name: path, - dir: false, - size: 0, - lastMod: time.Now(), - }, - data: pool.Get(), - } - - file.data.WriteTo(newFile.data) - - size, err := newFile.data.ReadFrom(r) + size, err := file.data.ReadFrom(r) if err != nil { fs.logger.WithFields(log.Fields{ "path": path, @@ -532,20 +493,21 @@ func (fs *memFilesystem) AppendFileReader(path string, r io.Reader, sizeHint int "error": err, }).Warn().Log("Incomplete file") - newFile.free() + file.Close() return -1, fmt.Errorf("incomplete file") } file.size += size + file.lastMod = time.Now() - oldFile, replace := fs.storage.Store(path, newFile) + oldFile, replace := fs.storage.Store(path, file) fs.sizeLock.Lock() defer fs.sizeLock.Unlock() if replace { - oldFile.free() + oldFile.Close() } fs.currentSize += size @@ -584,7 +546,7 @@ func (fs *memFilesystem) Purge(size int64) int64 { fs.currentSize -= f.size fs.sizeLock.Unlock() - f.free() + f.Close() fs.logger.WithFields(log.Fields{ "path": f.name, @@ -644,7 +606,7 @@ func (fs *memFilesystem) Rename(src, dst string) error { defer fs.sizeLock.Unlock() if replace { - dstFile.free() + dstFile.Close() fs.currentSize -= dstFile.size } @@ -664,28 +626,18 @@ func (fs *memFilesystem) Copy(src, dst string) error { return os.ErrInvalid } - srcFile, ok := fs.storage.Load(src) + file, ok := fs.storage.LoadAndCopy(src) if !ok { return ErrNotExist } - if srcFile.dir { + if file.dir { return ErrNotExist } - dstFile := &memFile{ - memFileInfo: memFileInfo{ - name: dst, - dir: false, - size: srcFile.size, - lastMod: time.Now(), - }, - data: pool.Get(), - } - - srcFile.data.WriteTo(dstFile.data) + file.lastMod = time.Now() - f, replace := fs.storage.Store(dst, dstFile) + replacedFile, replace := fs.storage.Store(dst, file) if !replace { fs.dirs.Add(dst) @@ -695,11 +647,11 @@ func (fs *memFilesystem) Copy(src, dst string) error { defer fs.sizeLock.Unlock() if replace { - f.free() - fs.currentSize -= f.size + replacedFile.Close() + fs.currentSize -= replacedFile.size } - fs.currentSize += dstFile.size + fs.currentSize += file.size return nil } @@ -763,7 +715,7 @@ func (fs *memFilesystem) Remove(path string) int64 { func (fs *memFilesystem) remove(path string) int64 { file, ok := fs.storage.Delete(path) if ok { - file.free() + file.Close() fs.dirs.Remove(path) @@ -853,7 +805,7 @@ func (fs *memFilesystem) RemoveList(path string, options ListOptions) ([]string, fs.dirs.Remove(file.name) - file.free() + file.Close() } fs.sizeLock.Lock() diff --git a/io/fs/mem_storage.go b/io/fs/mem_storage.go index 18b447ba..18d8f156 100644 --- a/io/fs/mem_storage.go +++ b/io/fs/mem_storage.go @@ -3,29 +3,34 @@ package fs import ( "sync" + "github.com/datarhei/core/v16/mem" "github.com/dolthub/swiss" "github.com/puzpuzpuz/xsync/v3" ) type memStorage interface { // Delete deletes a file from the storage. - Delete(key string) (*memFile, bool) + Delete(key string) (file *memFile, ok bool) // Store stores a file to the storage. If there's already a file with // the same key, that value will be returned and replaced with the // new file. - Store(key string, value *memFile) (*memFile, bool) + Store(key string, file *memFile) (oldfile *memFile, ok bool) // Load loads a file from the storage. This is a references to the file, // i.e. all changes to the file will be reflected on the storage. - Load(key string) (value *memFile, ok bool) + Load(key string) (file *memFile, ok bool) + + // LoadAndCopy loads a file from the storage. This is a copy of file + // metadata and content. + LoadAndCopy(key string) (file *memFile, ok bool) // Has checks whether a file exists at path. Has(key string) bool // Range ranges over all files on the storage. The callback needs to return // false in order to stop the iteration. - Range(f func(key string, value *memFile) bool) + Range(f func(key string, file *memFile) bool) } type mapOfStorage struct { @@ -63,6 +68,35 @@ func (m *mapOfStorage) Load(key string) (*memFile, bool) { return m.files.Load(key) } +func (m *mapOfStorage) LoadAndCopy(key string) (*memFile, bool) { + token := m.lock.RLock() + defer m.lock.RUnlock(token) + + file, ok := m.files.Load(key) + if !ok { + return nil, false + } + + newFile := &memFile{ + memFileInfo: memFileInfo{ + name: file.name, + size: file.size, + dir: file.dir, + lastMod: file.lastMod, + linkTo: file.linkTo, + }, + data: nil, + r: nil, + } + + if file.data != nil { + newFile.data = mem.Get() + file.data.WriteTo(newFile.data) + } + + return newFile, true +} + func (m *mapOfStorage) Has(key string) bool { token := m.lock.RLock() defer m.lock.RUnlock(token) @@ -121,6 +155,35 @@ func (m *mapStorage) Load(key string) (*memFile, bool) { return v, ok } +func (m *mapStorage) LoadAndCopy(key string) (*memFile, bool) { + m.lock.RLock() + defer m.lock.RUnlock() + + v, ok := m.files[key] + if !ok { + return nil, false + } + + newFile := &memFile{ + memFileInfo: memFileInfo{ + name: v.name, + size: v.size, + dir: v.dir, + lastMod: v.lastMod, + linkTo: v.linkTo, + }, + data: nil, + r: nil, + } + + if v.data != nil { + newFile.data = mem.Get() + v.data.WriteTo(newFile.data) + } + + return newFile, true +} + func (m *mapStorage) Has(key string) bool { m.lock.RLock() defer m.lock.RUnlock() @@ -186,6 +249,35 @@ func (m *swissMapStorage) Load(key string) (*memFile, bool) { return m.files.Get(key) } +func (m *swissMapStorage) LoadAndCopy(key string) (*memFile, bool) { + token := m.lock.RLock() + defer m.lock.RUnlock(token) + + file, ok := m.files.Get(key) + if !ok { + return nil, false + } + + newFile := &memFile{ + memFileInfo: memFileInfo{ + name: file.name, + size: file.size, + dir: file.dir, + lastMod: file.lastMod, + linkTo: file.linkTo, + }, + data: nil, + r: nil, + } + + if file.data != nil { + newFile.data = mem.Get() + file.data.WriteTo(newFile.data) + } + + return newFile, true +} + func (m *swissMapStorage) Has(key string) bool { token := m.lock.RLock() defer m.lock.RUnlock(token) diff --git a/io/fs/sized.go b/io/fs/sized.go index bd2c3fc6..9e64729f 100644 --- a/io/fs/sized.go +++ b/io/fs/sized.go @@ -4,6 +4,8 @@ import ( "bytes" "fmt" "io" + + "github.com/datarhei/core/v16/mem" ) type SizedFilesystem interface { @@ -71,8 +73,8 @@ func (r *sizedFilesystem) WriteFileReader(path string, rd io.Reader, sizeHint in return r.Filesystem.WriteFileReader(path, rd, sizeHint) } - data := pool.Get() - defer pool.Put(data) + data := mem.Get() + defer mem.Put(data) size, err := data.ReadFrom(rd) if err != nil { @@ -143,8 +145,8 @@ func (r *sizedFilesystem) AppendFileReader(path string, rd io.Reader, sizeHint i return r.Filesystem.AppendFileReader(path, rd, sizeHint) } - data := pool.Get() - defer pool.Put(data) + data := mem.Get() + defer mem.Put(data) size, err := data.ReadFrom(rd) if err != nil { diff --git a/mem/buffer.go b/mem/buffer.go index e3a39fe9..808676fa 100644 --- a/mem/buffer.go +++ b/mem/buffer.go @@ -1,5 +1,7 @@ package mem +// Based on github.com/valyala/bytebufferpool + import ( "bytes" "errors" @@ -7,85 +9,155 @@ import ( ) type Buffer struct { - data bytes.Buffer + data []byte } // Len returns the length of the buffer. func (b *Buffer) Len() int { - return b.data.Len() + return len(b.data) } // Bytes returns the buffer, but keeps ownership. func (b *Buffer) Bytes() []byte { - return b.data.Bytes() + return b.data } // WriteTo writes the bytes to the writer. func (b *Buffer) WriteTo(w io.Writer) (int64, error) { - n, err := w.Write(b.data.Bytes()) + n, err := w.Write(b.data) return int64(n), err } // Reset empties the buffer and keeps it's capacity. func (b *Buffer) Reset() { - b.data.Reset() + b.data = b.data[:0] } // Write appends to the buffer. func (b *Buffer) Write(p []byte) (int, error) { - return b.data.Write(p) + b.data = append(b.data, p...) + return len(p), nil } // ReadFrom reads from the reader and appends to the buffer. func (b *Buffer) ReadFrom(r io.Reader) (int64, error) { - if br, ok := r.(*bytes.Reader); ok { - b.data.Grow(br.Len()) - } + /* + chunkData := [128 * 1024]byte{} + chunk := chunkData[0:] + + size := int64(0) - chunkData := [128 * 1024]byte{} - chunk := chunkData[0:] + for { + n, err := r.Read(chunk) + if n != 0 { + b.data = append(b.data, chunk[:n]...) + size += int64(n) + } - size := int64(0) + if err != nil { + if errors.Is(err, io.EOF) { + return size, nil + } - for { - n, err := r.Read(chunk) - if n != 0 { - b.data.Write(chunk[:n]) - size += int64(n) + return size, err + } + + if n == 0 { + break + } } + return size, nil + */ + p := b.data + nStart := int64(len(p)) + nMax := int64(cap(p)) + n := nStart + if nMax == 0 { + nMax = 64 + p = make([]byte, nMax) + } else { + p = p[:nMax] + } + for { + if n == nMax { + nMax *= 2 + bNew := make([]byte, nMax) + copy(bNew, p) + p = bNew + } + nn, err := r.Read(p[n:]) + n += int64(nn) if err != nil { + b.data = p[:n] + n -= nStart if errors.Is(err, io.EOF) { - return size, nil + return n, nil + } + return n, err + } + } + /* + if br, ok := r.(*bytes.Reader); ok { + if cap(b.data) < br.Len() { + data := make([]byte, br.Len()) + copy(data, b.data) + b.data = data } - - return size, err } - if n == 0 { - break + chunkData := [128 * 1024]byte{} + chunk := chunkData[0:] + + size := int64(0) + + for { + n, err := r.Read(chunk) + if n != 0 { + if cap(b.data) < len(b.data)+n { + data := make([]byte, cap(b.data)+1024*1024) + copy(data, b.data) + b.data = data + } + b.data = append(b.data, chunk[:n]...) + size += int64(n) + } + + if err != nil { + if errors.Is(err, io.EOF) { + return size, nil + } + + return size, err + } + + if n == 0 { + break + } } - } - return size, nil + return size, nil + */ } // WriteByte appends a byte to the buffer. func (b *Buffer) WriteByte(c byte) error { - return b.data.WriteByte(c) + b.data = append(b.data, c) + return nil } // WriteString appends a string to the buffer. func (b *Buffer) WriteString(s string) (n int, err error) { - return b.data.WriteString(s) + b.data = append(b.data, s...) + return len(s), nil } // Reader returns a bytes.Reader based on the data in the buffer. func (b *Buffer) Reader() *bytes.Reader { - return bytes.NewReader(b.Bytes()) + return bytes.NewReader(b.data) } // String returns the data in the buffer a string. func (b *Buffer) String() string { - return b.data.String() + return string(b.data) } diff --git a/mem/pool.go b/mem/pool.go index 641791f3..b2817d83 100644 --- a/mem/pool.go +++ b/mem/pool.go @@ -1,34 +1,137 @@ package mem +// Based on github.com/valyala/bytebufferpool + import ( + "sort" "sync" + "sync/atomic" +) + +const ( + minBitSize = 6 // 2**6=64 is a CPU cache line size + steps = 20 + + minSize = 1 << minBitSize + maxSize = 1 << (minBitSize + steps - 1) + + calibrateCallsThreshold = 42000 + maxPercentile = 0.95 ) type BufferPool struct { + calls [steps]uint64 + calibrating uint64 + + defaultSize uint64 + maxSize uint64 + pool sync.Pool } func NewBufferPool() *BufferPool { p := &BufferPool{ - pool: sync.Pool{ - New: func() any { - return &Buffer{} - }, - }, + pool: sync.Pool{}, } return p } func (p *BufferPool) Get() *Buffer { - buf := p.pool.Get().(*Buffer) - buf.Reset() + v := p.pool.Get() + if v != nil { + return v.(*Buffer) + } - return buf + return &Buffer{ + data: make([]byte, 0, atomic.LoadUint64(&p.defaultSize)), + } } func (p *BufferPool) Put(buf *Buffer) { - p.pool.Put(buf) + idx := index(len(buf.data)) + + if atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold { + p.calibrate() + } + + maxSize := int(atomic.LoadUint64(&p.maxSize)) + if maxSize == 0 || cap(buf.data) <= maxSize { + buf.Reset() + p.pool.Put(buf) + } +} + +func (p *BufferPool) calibrate() { + if !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) { + return + } + + a := make(callSizes, 0, steps) + var callsSum uint64 + for i := uint64(0); i < steps; i++ { + calls := atomic.SwapUint64(&p.calls[i], 0) + callsSum += calls + a = append(a, callSize{ + calls: calls, + size: minSize << i, + }) + } + sort.Sort(a) + + defaultSize := a[0].size + maxSize := defaultSize + + maxSum := uint64(float64(callsSum) * maxPercentile) + callsSum = 0 + for i := 0; i < steps; i++ { + if callsSum > maxSum { + break + } + callsSum += a[i].calls + size := a[i].size + if size > maxSize { + maxSize = size + } + } + + atomic.StoreUint64(&p.defaultSize, defaultSize) + atomic.StoreUint64(&p.maxSize, maxSize) + + atomic.StoreUint64(&p.calibrating, 0) +} + +type callSize struct { + calls uint64 + size uint64 +} + +type callSizes []callSize + +func (ci callSizes) Len() int { + return len(ci) +} + +func (ci callSizes) Less(i, j int) bool { + return ci[i].calls > ci[j].calls +} + +func (ci callSizes) Swap(i, j int) { + ci[i], ci[j] = ci[j], ci[i] +} + +func index(n int) int { + n-- + n >>= minBitSize + idx := 0 + for n > 0 { + n >>= 1 + idx++ + } + if idx >= steps { + idx = steps - 1 + } + return idx } var DefaultBufferPool *BufferPool diff --git a/restream/app/process.go b/restream/app/process.go index 8d58d746..974309cb 100644 --- a/restream/app/process.go +++ b/restream/app/process.go @@ -9,6 +9,7 @@ import ( "sync" "github.com/datarhei/core/v16/ffmpeg/parse" + "github.com/datarhei/core/v16/mem" "github.com/datarhei/core/v16/process" ) @@ -156,7 +157,8 @@ func (config *Config) String() string { } func (config *Config) Hash() []byte { - b := bytes.Buffer{} + b := mem.Get() + defer mem.Put(b) b.WriteString(config.ID) b.WriteString(config.Reference) diff --git a/service/api/api.go b/service/api/api.go index b34f589a..266824c4 100644 --- a/service/api/api.go +++ b/service/api/api.go @@ -1,7 +1,6 @@ package api import ( - "bytes" "errors" "fmt" "io" @@ -88,13 +87,13 @@ func (e statusError) Is(target error) bool { type copyReader struct { reader io.Reader - copy *bytes.Buffer + copy *mem.Buffer } func newCopyReader(r io.Reader) io.Reader { c := ©Reader{ reader: r, - copy: new(bytes.Buffer), + copy: mem.Get(), } return c @@ -106,8 +105,8 @@ func (c *copyReader) Read(p []byte) (int, error) { c.copy.Write(p) if err == io.EOF { - c.reader = c.copy - c.copy = &bytes.Buffer{} + c.reader = c.copy.Reader() + c.copy = mem.Get() } return i, err diff --git a/update/update.go b/update/update.go index 331f6da0..24a88e1f 100644 --- a/update/update.go +++ b/update/update.go @@ -1,7 +1,6 @@ package update import ( - "bytes" "context" "fmt" "io" @@ -12,6 +11,7 @@ import ( "github.com/datarhei/core/v16/encoding/json" "github.com/datarhei/core/v16/log" + "github.com/datarhei/core/v16/mem" "github.com/datarhei/core/v16/monitor/metric" "golang.org/x/mod/semver" ) @@ -156,15 +156,16 @@ func (s *checker) check() error { Timeout: 5 * time.Second, } - var data bytes.Buffer - encoder := json.NewEncoder(&data) + data := mem.Get() + defer mem.Put(data) + encoder := json.NewEncoder(data) if err := encoder.Encode(&request); err != nil { return err } s.logger.Debug().WithField("request", data.String()).Log("") - req, err := http.NewRequest(http.MethodPut, "https://service.datarhei.com/api/v1/app_version", &data) + req, err := http.NewRequest(http.MethodPut, "https://service.datarhei.com/api/v1/app_version", data.Reader()) if err != nil { return err } From 519f0cfb2b62b7705678c9fdd305320a491739af Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Mon, 14 Oct 2024 14:26:15 +0200 Subject: [PATCH 38/64] Fix buffer re-use, use swiss-map backend --- app/api/api.go | 1 + io/fs/mem.go | 6 ++- io/fs/memtest/memtest.go | 25 +++++++---- mem/pool_test.go | 94 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 116 insertions(+), 10 deletions(-) create mode 100644 mem/pool_test.go diff --git a/app/api/api.go b/app/api/api.go index 73e48d8b..bd5b7cf5 100644 --- a/app/api/api.go +++ b/app/api/api.go @@ -847,6 +847,7 @@ func (a *api) start(ctx context.Context) error { "type": "mem", "name": "mem", }), + Storage: "swiss", } var memfs fs.Filesystem = nil if len(cfg.Storage.Memory.Backup.Dir) != 0 { diff --git a/io/fs/mem.go b/io/fs/mem.go index 2cc51637..e1d33aae 100644 --- a/io/fs/mem.go +++ b/io/fs/mem.go @@ -105,8 +105,10 @@ func (f *memFile) Seek(offset int64, whence int) (int64, error) { } func (f *memFile) Close() error { + var err error = nil + if f.r == nil { - return io.EOF + err = io.EOF } f.r = nil @@ -116,7 +118,7 @@ func (f *memFile) Close() error { f.data = nil } - return nil + return err } type memFilesystem struct { diff --git a/io/fs/memtest/memtest.go b/io/fs/memtest/memtest.go index 771eb454..73828e6e 100644 --- a/io/fs/memtest/memtest.go +++ b/io/fs/memtest/memtest.go @@ -29,8 +29,9 @@ func main() { oFiles := 15 oInterval := 1 // seconds oSize := 2 // megabytes - oLimit := false + oLimit := -1 oGC := 0 + oFree := 0 flag.StringVar(&oStorage, "storage", "mapof", "type of mem storage implementation (mapof, map, swiss)") flag.IntVar(&oWriters, "writers", 500, "number of concurrent writers") @@ -38,8 +39,9 @@ func main() { flag.IntVar(&oFiles, "files", 15, "number of files to keep per writer") flag.IntVar(&oInterval, "interval", 1, "interval for writing files in seconds") flag.IntVar(&oSize, "size", 2048, "size of files to write in kilobytes") - flag.BoolVar(&oLimit, "limit", false, "set memory limit") - flag.IntVar(&oGC, "gc", 0, "force garbage collector") + flag.IntVar(&oLimit, "limit", -1, "set memory limit, 0 for automatic, otherwise memory in MB") + flag.IntVar(&oGC, "gc", 100, "GC percentage") + flag.IntVar(&oFree, "free", 0, "force freeing memory") flag.Parse() @@ -47,9 +49,14 @@ func main() { fmt.Printf("Expecting effective memory consumption of %.1fGB\n", estimatedSize) - if oLimit { - fmt.Printf("Setting memory limit to %.1fGB\n", estimatedSize*1.5) - debug.SetMemoryLimit(int64(estimatedSize * 1.5)) + if oLimit >= 0 { + limitSize := estimatedSize * 1.5 * 1024 * 1024 * 1024 + if oLimit > 0 { + limitSize = float64(oLimit) * 1024 * 1024 + } + + fmt.Printf("Setting memory limit to %.1fGB\n", limitSize/1024/1024/1024) + debug.SetMemoryLimit(int64(limitSize)) } memfs, err := fs.NewMemFilesystem(fs.MemConfig{ @@ -206,9 +213,11 @@ func main() { } }(ctx, memfs) - if oGC > 0 { + debug.SetGCPercent(oGC) + + if oFree > 0 { go func(ctx context.Context) { - ticker := time.NewTicker(time.Duration(oGC) * time.Second) + ticker := time.NewTicker(time.Duration(oFree) * time.Second) defer ticker.Stop() for { diff --git a/mem/pool_test.go b/mem/pool_test.go new file mode 100644 index 00000000..c194536c --- /dev/null +++ b/mem/pool_test.go @@ -0,0 +1,94 @@ +package mem + +import ( + "math/rand" + "testing" + "time" +) + +func TestIndex(t *testing.T) { + testIndex(t, 0, 0) + testIndex(t, 1, 0) + + testIndex(t, minSize-1, 0) + testIndex(t, minSize, 0) + testIndex(t, minSize+1, 1) + + testIndex(t, 2*minSize-1, 1) + testIndex(t, 2*minSize, 1) + testIndex(t, 2*minSize+1, 2) + + testIndex(t, maxSize-1, steps-1) + testIndex(t, maxSize, steps-1) + testIndex(t, maxSize+1, steps-1) +} + +func testIndex(t *testing.T, n, expectedIdx int) { + idx := index(n) + if idx != expectedIdx { + t.Fatalf("unexpected idx for n=%d: %d. Expecting %d", n, idx, expectedIdx) + } +} + +func TestPoolCalibrate(t *testing.T) { + for i := 0; i < steps*calibrateCallsThreshold; i++ { + n := 1004 + if i%15 == 0 { + n = rand.Intn(15234) + } + testGetPut(t, n) + } +} + +func TestPoolVariousSizesSerial(t *testing.T) { + testPoolVariousSizes(t) +} + +func TestPoolVariousSizesConcurrent(t *testing.T) { + concurrency := 5 + ch := make(chan struct{}) + for i := 0; i < concurrency; i++ { + go func() { + testPoolVariousSizes(t) + ch <- struct{}{} + }() + } + for i := 0; i < concurrency; i++ { + select { + case <-ch: + case <-time.After(3 * time.Second): + t.Fatalf("timeout") + } + } +} + +func testPoolVariousSizes(t *testing.T) { + for i := 0; i < steps+1; i++ { + n := (1 << uint32(i)) + + testGetPut(t, n) + testGetPut(t, n+1) + testGetPut(t, n-1) + + for j := 0; j < 10; j++ { + testGetPut(t, j+n) + } + } +} + +func testGetPut(t *testing.T, n int) { + bb := Get() + if len(bb.data) > 0 { + t.Fatalf("non-empty byte buffer returned from acquire") + } + bb.data = allocNBytes(bb.data, n) + Put(bb) +} + +func allocNBytes(dst []byte, n int) []byte { + diff := n - cap(dst) + if diff <= 0 { + return dst[:n] + } + return append(dst, make([]byte, diff)...) +} From 7a58c52a17456a8645df755a72601385244a53bb Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Mon, 14 Oct 2024 14:55:29 +0200 Subject: [PATCH 39/64] Collect statistics about buffer pool, export as metrics --- app/api/api.go | 1 + mem/pool.go | 35 +++++++++++++++++++++++++++ monitor/self.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 monitor/self.go diff --git a/app/api/api.go b/app/api/api.go index bd5b7cf5..fa017a88 100644 --- a/app/api/api.go +++ b/app/api/api.go @@ -1235,6 +1235,7 @@ func (a *api) start(ctx context.Context) error { for name, fs := range a.s3fs { metrics.Register(monitor.NewFilesystemCollector(name, fs)) } + metrics.Register(monitor.NewSelfCollector()) metrics.Register(monitor.NewRestreamCollector(a.restream)) metrics.Register(monitor.NewFFmpegCollector(a.ffmpeg)) metrics.Register(monitor.NewSessionCollector(a.sessions, []string{})) diff --git a/mem/pool.go b/mem/pool.go index b2817d83..08ae8519 100644 --- a/mem/pool.go +++ b/mem/pool.go @@ -26,9 +26,22 @@ type BufferPool struct { defaultSize uint64 maxSize uint64 + alloc uint64 + recycle uint64 + dump uint64 + pool sync.Pool } +type PoolStats struct { + Alloc uint64 + Recycle uint64 + Dump uint64 + + DefaultSize uint64 + MaxSize uint64 +} + func NewBufferPool() *BufferPool { p := &BufferPool{ pool: sync.Pool{}, @@ -37,12 +50,26 @@ func NewBufferPool() *BufferPool { return p } +func (p *BufferPool) Stats() PoolStats { + s := PoolStats{ + Alloc: atomic.LoadUint64(&p.alloc), + Recycle: atomic.LoadUint64(&p.recycle), + Dump: atomic.LoadUint64(&p.dump), + DefaultSize: atomic.LoadUint64(&p.defaultSize), + MaxSize: atomic.LoadUint64(&p.maxSize), + } + + return s +} + func (p *BufferPool) Get() *Buffer { v := p.pool.Get() if v != nil { return v.(*Buffer) } + atomic.AddUint64(&p.alloc, 1) + return &Buffer{ data: make([]byte, 0, atomic.LoadUint64(&p.defaultSize)), } @@ -59,6 +86,10 @@ func (p *BufferPool) Put(buf *Buffer) { if maxSize == 0 || cap(buf.data) <= maxSize { buf.Reset() p.pool.Put(buf) + + atomic.AddUint64(&p.recycle, 1) + } else { + atomic.AddUint64(&p.dump, 1) } } @@ -140,6 +171,10 @@ func init() { DefaultBufferPool = NewBufferPool() } +func Stats() PoolStats { + return DefaultBufferPool.Stats() +} + func Get() *Buffer { return DefaultBufferPool.Get() } diff --git a/monitor/self.go b/monitor/self.go new file mode 100644 index 00000000..49317d52 --- /dev/null +++ b/monitor/self.go @@ -0,0 +1,63 @@ +package monitor + +import ( + "runtime" + + "github.com/datarhei/core/v16/mem" + "github.com/datarhei/core/v16/monitor/metric" +) + +type selfCollector struct { + allocDescr *metric.Description + recycleDescr *metric.Description + dumpDescr *metric.Description + defaultSizeDescr *metric.Description + maxSizeDescr *metric.Description +} + +func NewSelfCollector() metric.Collector { + c := &selfCollector{} + + c.allocDescr = metric.NewDesc("bufferpool_alloc", "Number of buffer allocations", nil) + c.recycleDescr = metric.NewDesc("bufferpool_recycle", "Number of buffer recycles", nil) + c.dumpDescr = metric.NewDesc("bufferpool_dump", "Number of buffer dumps", nil) + c.defaultSizeDescr = metric.NewDesc("bufferpool_default_size", "Default buffer size", nil) + c.maxSizeDescr = metric.NewDesc("bufferpool_max_size", "Max. buffer size for recycling", nil) + + return c +} + +func (c *selfCollector) Stop() {} + +func (c *selfCollector) Prefix() string { + return "bufferpool" +} + +func (c *selfCollector) Describe() []*metric.Description { + return []*metric.Description{ + c.allocDescr, + c.recycleDescr, + c.dumpDescr, + c.defaultSizeDescr, + c.maxSizeDescr, + } +} + +func (c *selfCollector) Collect() metric.Metrics { + stats := mem.Stats() + + metrics := metric.NewMetrics() + + metrics.Add(metric.NewValue(c.allocDescr, float64(stats.Alloc))) + metrics.Add(metric.NewValue(c.recycleDescr, float64(stats.Recycle))) + metrics.Add(metric.NewValue(c.dumpDescr, float64(stats.Dump))) + metrics.Add(metric.NewValue(c.defaultSizeDescr, float64(stats.DefaultSize))) + metrics.Add(metric.NewValue(c.maxSizeDescr, float64(stats.MaxSize))) + + memstats := runtime.MemStats{} + runtime.ReadMemStats(&memstats) + + //metrics.Add(metric.NewValue()) + + return metrics +} From fbea34a43aa15635b8141457c0c5bdf993a024d3 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Mon, 14 Oct 2024 15:11:25 +0200 Subject: [PATCH 40/64] Add memory stats about self --- monitor/self.go | 69 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/monitor/self.go b/monitor/self.go index 49317d52..28dccb28 100644 --- a/monitor/self.go +++ b/monitor/self.go @@ -8,21 +8,35 @@ import ( ) type selfCollector struct { - allocDescr *metric.Description - recycleDescr *metric.Description - dumpDescr *metric.Description - defaultSizeDescr *metric.Description - maxSizeDescr *metric.Description + bufferAllocDescr *metric.Description + bufferRecycleDescr *metric.Description + bufferDumpDescr *metric.Description + bufferDefaultSizeDescr *metric.Description + bufferMaxSizeDescr *metric.Description + + heapAllocDescr *metric.Description + totalAllocDescr *metric.Description + heapSysDescr *metric.Description + heapIdleDescr *metric.Description + heapInuseDescr *metric.Description + heapObjectsDescr *metric.Description } func NewSelfCollector() metric.Collector { c := &selfCollector{} - c.allocDescr = metric.NewDesc("bufferpool_alloc", "Number of buffer allocations", nil) - c.recycleDescr = metric.NewDesc("bufferpool_recycle", "Number of buffer recycles", nil) - c.dumpDescr = metric.NewDesc("bufferpool_dump", "Number of buffer dumps", nil) - c.defaultSizeDescr = metric.NewDesc("bufferpool_default_size", "Default buffer size", nil) - c.maxSizeDescr = metric.NewDesc("bufferpool_max_size", "Max. buffer size for recycling", nil) + c.bufferAllocDescr = metric.NewDesc("self_bufferpool_alloc", "Number of buffer allocations", nil) + c.bufferRecycleDescr = metric.NewDesc("self_bufferpool_recycle", "Number of buffer recycles", nil) + c.bufferDumpDescr = metric.NewDesc("self_bufferpool_dump", "Number of buffer dumps", nil) + c.bufferDefaultSizeDescr = metric.NewDesc("self_bufferpool_default_size", "Default buffer size", nil) + c.bufferMaxSizeDescr = metric.NewDesc("self_bufferpool_max_size", "Max. buffer size for recycling", nil) + + c.heapAllocDescr = metric.NewDesc("self_mem_heap_alloc_bytes", "Number of bytes allocated on the heap", nil) + c.totalAllocDescr = metric.NewDesc("self_mem_total_alloc_bytes", "Number of bytes allocated since start", nil) + c.heapSysDescr = metric.NewDesc("self_mem_heap_sys_bytes", "Number of bytes obtained from OS", nil) + c.heapIdleDescr = metric.NewDesc("self_mem_heap_idle_bytes", "Number of unused bytes", nil) + c.heapInuseDescr = metric.NewDesc("self_mem_heap_inuse_bytes", "Number of used bytes", nil) + c.heapObjectsDescr = metric.NewDesc("self_mem_heap_objects", "Number of objects in heap", nil) return c } @@ -35,29 +49,40 @@ func (c *selfCollector) Prefix() string { func (c *selfCollector) Describe() []*metric.Description { return []*metric.Description{ - c.allocDescr, - c.recycleDescr, - c.dumpDescr, - c.defaultSizeDescr, - c.maxSizeDescr, + c.bufferAllocDescr, + c.bufferRecycleDescr, + c.bufferDumpDescr, + c.bufferDefaultSizeDescr, + c.bufferMaxSizeDescr, + c.heapAllocDescr, + c.totalAllocDescr, + c.heapSysDescr, + c.heapIdleDescr, + c.heapInuseDescr, + c.heapObjectsDescr, } } func (c *selfCollector) Collect() metric.Metrics { - stats := mem.Stats() + bufferstats := mem.Stats() metrics := metric.NewMetrics() - metrics.Add(metric.NewValue(c.allocDescr, float64(stats.Alloc))) - metrics.Add(metric.NewValue(c.recycleDescr, float64(stats.Recycle))) - metrics.Add(metric.NewValue(c.dumpDescr, float64(stats.Dump))) - metrics.Add(metric.NewValue(c.defaultSizeDescr, float64(stats.DefaultSize))) - metrics.Add(metric.NewValue(c.maxSizeDescr, float64(stats.MaxSize))) + metrics.Add(metric.NewValue(c.bufferAllocDescr, float64(bufferstats.Alloc))) + metrics.Add(metric.NewValue(c.bufferRecycleDescr, float64(bufferstats.Recycle))) + metrics.Add(metric.NewValue(c.bufferDumpDescr, float64(bufferstats.Dump))) + metrics.Add(metric.NewValue(c.bufferDefaultSizeDescr, float64(bufferstats.DefaultSize))) + metrics.Add(metric.NewValue(c.bufferMaxSizeDescr, float64(bufferstats.MaxSize))) memstats := runtime.MemStats{} runtime.ReadMemStats(&memstats) - //metrics.Add(metric.NewValue()) + metrics.Add(metric.NewValue(c.heapAllocDescr, float64(memstats.HeapAlloc))) + metrics.Add(metric.NewValue(c.totalAllocDescr, float64(memstats.TotalAlloc))) + metrics.Add(metric.NewValue(c.heapSysDescr, float64(memstats.HeapSys))) + metrics.Add(metric.NewValue(c.heapIdleDescr, float64(memstats.HeapIdle))) + metrics.Add(metric.NewValue(c.heapInuseDescr, float64(memstats.HeapInuse))) + metrics.Add(metric.NewValue(c.heapObjectsDescr, float64(memstats.Mallocs-memstats.Frees))) return metrics } From 2f932be97deb5bd16d8cde30e1e43681036d3e2d Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Mon, 14 Oct 2024 15:34:10 +0200 Subject: [PATCH 41/64] Add metrics for mallocs and frees --- monitor/self.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/monitor/self.go b/monitor/self.go index 28dccb28..bd564ae5 100644 --- a/monitor/self.go +++ b/monitor/self.go @@ -20,6 +20,8 @@ type selfCollector struct { heapIdleDescr *metric.Description heapInuseDescr *metric.Description heapObjectsDescr *metric.Description + mallocsDescr *metric.Description + freesDescr *metric.Description } func NewSelfCollector() metric.Collector { @@ -32,11 +34,13 @@ func NewSelfCollector() metric.Collector { c.bufferMaxSizeDescr = metric.NewDesc("self_bufferpool_max_size", "Max. buffer size for recycling", nil) c.heapAllocDescr = metric.NewDesc("self_mem_heap_alloc_bytes", "Number of bytes allocated on the heap", nil) - c.totalAllocDescr = metric.NewDesc("self_mem_total_alloc_bytes", "Number of bytes allocated since start", nil) + c.totalAllocDescr = metric.NewDesc("self_mem_total_alloc_bytes", "Cumulative count of bytes allocated since start", nil) c.heapSysDescr = metric.NewDesc("self_mem_heap_sys_bytes", "Number of bytes obtained from OS", nil) c.heapIdleDescr = metric.NewDesc("self_mem_heap_idle_bytes", "Number of unused bytes", nil) c.heapInuseDescr = metric.NewDesc("self_mem_heap_inuse_bytes", "Number of used bytes", nil) c.heapObjectsDescr = metric.NewDesc("self_mem_heap_objects", "Number of objects in heap", nil) + c.mallocsDescr = metric.NewDesc("self_mem_mallocs", "Cumulative count of heap objects allocated", nil) + c.freesDescr = metric.NewDesc("self_mem_frees", "Cumulative count of heap objects freed", nil) return c } @@ -60,6 +64,8 @@ func (c *selfCollector) Describe() []*metric.Description { c.heapIdleDescr, c.heapInuseDescr, c.heapObjectsDescr, + c.mallocsDescr, + c.freesDescr, } } @@ -82,7 +88,9 @@ func (c *selfCollector) Collect() metric.Metrics { metrics.Add(metric.NewValue(c.heapSysDescr, float64(memstats.HeapSys))) metrics.Add(metric.NewValue(c.heapIdleDescr, float64(memstats.HeapIdle))) metrics.Add(metric.NewValue(c.heapInuseDescr, float64(memstats.HeapInuse))) - metrics.Add(metric.NewValue(c.heapObjectsDescr, float64(memstats.Mallocs-memstats.Frees))) + metrics.Add(metric.NewValue(c.heapObjectsDescr, float64(memstats.HeapObjects))) + metrics.Add(metric.NewValue(c.mallocsDescr, float64(memstats.Mallocs))) + metrics.Add(metric.NewValue(c.freesDescr, float64(memstats.Frees))) return metrics } From bc1212319191ed1aadd243c7c86db5a62e3c70bd Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Mon, 14 Oct 2024 15:49:07 +0200 Subject: [PATCH 42/64] Add statistics about buffer reuse --- mem/pool.go | 4 ++++ monitor/self.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/mem/pool.go b/mem/pool.go index 08ae8519..23a7c1d6 100644 --- a/mem/pool.go +++ b/mem/pool.go @@ -27,6 +27,7 @@ type BufferPool struct { maxSize uint64 alloc uint64 + reuse uint64 recycle uint64 dump uint64 @@ -35,6 +36,7 @@ type BufferPool struct { type PoolStats struct { Alloc uint64 + Reuse uint64 Recycle uint64 Dump uint64 @@ -53,6 +55,7 @@ func NewBufferPool() *BufferPool { func (p *BufferPool) Stats() PoolStats { s := PoolStats{ Alloc: atomic.LoadUint64(&p.alloc), + Reuse: atomic.LoadUint64(&p.reuse), Recycle: atomic.LoadUint64(&p.recycle), Dump: atomic.LoadUint64(&p.dump), DefaultSize: atomic.LoadUint64(&p.defaultSize), @@ -65,6 +68,7 @@ func (p *BufferPool) Stats() PoolStats { func (p *BufferPool) Get() *Buffer { v := p.pool.Get() if v != nil { + atomic.AddUint64(&p.reuse, 1) return v.(*Buffer) } diff --git a/monitor/self.go b/monitor/self.go index bd564ae5..1964340b 100644 --- a/monitor/self.go +++ b/monitor/self.go @@ -9,6 +9,7 @@ import ( type selfCollector struct { bufferAllocDescr *metric.Description + bufferReuseDescr *metric.Description bufferRecycleDescr *metric.Description bufferDumpDescr *metric.Description bufferDefaultSizeDescr *metric.Description @@ -28,6 +29,7 @@ func NewSelfCollector() metric.Collector { c := &selfCollector{} c.bufferAllocDescr = metric.NewDesc("self_bufferpool_alloc", "Number of buffer allocations", nil) + c.bufferReuseDescr = metric.NewDesc("self_bufferpool_reuse", "Number of buffer reuses", nil) c.bufferRecycleDescr = metric.NewDesc("self_bufferpool_recycle", "Number of buffer recycles", nil) c.bufferDumpDescr = metric.NewDesc("self_bufferpool_dump", "Number of buffer dumps", nil) c.bufferDefaultSizeDescr = metric.NewDesc("self_bufferpool_default_size", "Default buffer size", nil) @@ -54,6 +56,7 @@ func (c *selfCollector) Prefix() string { func (c *selfCollector) Describe() []*metric.Description { return []*metric.Description{ c.bufferAllocDescr, + c.bufferReuseDescr, c.bufferRecycleDescr, c.bufferDumpDescr, c.bufferDefaultSizeDescr, @@ -75,6 +78,7 @@ func (c *selfCollector) Collect() metric.Metrics { metrics := metric.NewMetrics() metrics.Add(metric.NewValue(c.bufferAllocDescr, float64(bufferstats.Alloc))) + metrics.Add(metric.NewValue(c.bufferReuseDescr, float64(bufferstats.Reuse))) metrics.Add(metric.NewValue(c.bufferRecycleDescr, float64(bufferstats.Recycle))) metrics.Add(metric.NewValue(c.bufferDumpDescr, float64(bufferstats.Dump))) metrics.Add(metric.NewValue(c.bufferDefaultSizeDescr, float64(bufferstats.DefaultSize))) From 0e191d671ee61c507d7c6464c99334efd379c5a6 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Tue, 15 Oct 2024 15:01:36 +0200 Subject: [PATCH 43/64] Fix resetting compressor on passthrough --- http/middleware/compress/compress.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/http/middleware/compress/compress.go b/http/middleware/compress/compress.go index b595f47b..a92f446b 100644 --- a/http/middleware/compress/compress.go +++ b/http/middleware/compress/compress.go @@ -204,6 +204,8 @@ func NewWithConfig(config Config) echo.MiddlewareFunc { grw.buffer.WriteTo(rw) compressor.Reset(io.Discard) } + } else { + compressor.Reset(io.Discard) } compressor.Close() mem.Put(buffer) From 2dda47b81f85f65c6f41a00f3b8da2ce1b2dba82 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Tue, 15 Oct 2024 17:08:35 +0200 Subject: [PATCH 44/64] Fix tests --- http/middleware/compress/compress_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/http/middleware/compress/compress_test.go b/http/middleware/compress/compress_test.go index b42971d2..2d0d5b8b 100644 --- a/http/middleware/compress/compress_test.go +++ b/http/middleware/compress/compress_test.go @@ -168,7 +168,7 @@ func TestCompressWithPassthrough(t *testing.T) { rec := httptest.NewRecorder() e.ServeHTTP(rec, req) assert.Equal(t, "", rec.Header().Get(echo.HeaderContentEncoding)) - assert.Contains(t, rec.Body.String(), "testtest") + assert.Equal(t, rec.Body.String(), "testtest") req = httptest.NewRequest(http.MethodGet, "/compress", nil) req.Header.Set(echo.HeaderAcceptEncoding, scheme) @@ -206,7 +206,7 @@ func TestCompressWithMinLength(t *testing.T) { rec := httptest.NewRecorder() e.ServeHTTP(rec, req) assert.Equal(t, "", rec.Header().Get(echo.HeaderContentEncoding)) - assert.Contains(t, rec.Body.String(), "test") + assert.Equal(t, rec.Body.String(), "test") req = httptest.NewRequest(http.MethodGet, "/foobar", nil) req.Header.Set(echo.HeaderAcceptEncoding, scheme) From df30a6b8e3786a9b64a513c0acd6169fdaf90e97 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Wed, 23 Oct 2024 11:08:13 +0200 Subject: [PATCH 45/64] Replace timer-based SMA with a timer-less implementation --- cluster/node/cache.go | 18 +- cluster/node/cache_test.go | 26 +- ffmpeg/parse/average.go | 20 +- ffmpeg/parse/parser.go | 30 +- ffmpeg/parse/parser_test.go | 86 ++--- go.mod | 1 - go.sum | 2 - math/average/sma.go | 118 +++++++ math/average/sma_test.go | 301 ++++++++++++++++++ session/collector.go | 19 +- session/session.go | 26 +- time/source.go | 25 ++ vendor/github.com/prep/average/.travis.yml | 28 -- vendor/github.com/prep/average/LICENSE | 27 -- vendor/github.com/prep/average/README.md | 42 --- .../github.com/prep/average/slidingwindow.go | 142 --------- vendor/modules.txt | 3 - 17 files changed, 543 insertions(+), 371 deletions(-) create mode 100644 math/average/sma.go create mode 100644 math/average/sma_test.go create mode 100644 time/source.go delete mode 100644 vendor/github.com/prep/average/.travis.yml delete mode 100644 vendor/github.com/prep/average/LICENSE delete mode 100644 vendor/github.com/prep/average/README.md delete mode 100644 vendor/github.com/prep/average/slidingwindow.go diff --git a/cluster/node/cache.go b/cluster/node/cache.go index bce3db5c..24041f23 100644 --- a/cluster/node/cache.go +++ b/cluster/node/cache.go @@ -4,17 +4,9 @@ import ( "errors" "sync" "time" -) - -type TimeSource interface { - Now() time.Time -} -type StdTimeSource struct{} - -func (s *StdTimeSource) Now() time.Time { - return time.Now() -} + timesrc "github.com/datarhei/core/v16/time" +) type CacheEntry[T any] struct { value T @@ -22,20 +14,20 @@ type CacheEntry[T any] struct { } type Cache[T any] struct { - ts TimeSource + ts timesrc.Source lock sync.Mutex entries map[string]CacheEntry[T] lastPurge time.Time } -func NewCache[T any](ts TimeSource) *Cache[T] { +func NewCache[T any](ts timesrc.Source) *Cache[T] { c := &Cache[T]{ ts: ts, entries: map[string]CacheEntry[T]{}, } if c.ts == nil { - c.ts = &StdTimeSource{} + c.ts = ×rc.StdSource{} } c.lastPurge = c.ts.Now() diff --git a/cluster/node/cache_test.go b/cluster/node/cache_test.go index 2c8d7a8f..444f60d8 100644 --- a/cluster/node/cache_test.go +++ b/cluster/node/cache_test.go @@ -4,20 +4,14 @@ import ( "testing" "time" + timesrc "github.com/datarhei/core/v16/time" + "github.com/stretchr/testify/require" ) -type testTimeSource struct { - now time.Time -} - -func (t *testTimeSource) Now() time.Time { - return t.now -} - func TestCache(t *testing.T) { - ts := &testTimeSource{ - now: time.Unix(0, 0), + ts := ×rc.TestSource{ + N: time.Unix(0, 0), } c := NewCache[string](ts) @@ -31,21 +25,21 @@ func TestCache(t *testing.T) { require.NoError(t, err) require.Equal(t, "bar", v) - ts.now = time.Unix(10, 0) + ts.Set(10, 0) v, err = c.Get("foo") require.NoError(t, err) require.Equal(t, "bar", v) - ts.now = time.Unix(11, 0) + ts.Set(11, 0) _, err = c.Get("foo") require.Error(t, err) } func TestCachePurge(t *testing.T) { - ts := &testTimeSource{ - now: time.Unix(0, 0), + ts := ×rc.TestSource{ + N: time.Unix(0, 0), } c := NewCache[string](ts) @@ -56,14 +50,14 @@ func TestCachePurge(t *testing.T) { require.NoError(t, err) require.Equal(t, "bar", v) - ts.now = time.Unix(59, 0) + ts.Set(59, 0) c.Put("foz", "boz", 10*time.Second) _, ok := c.entries["foo"] require.True(t, ok) - ts.now = time.Unix(61, 0) + ts.Set(61, 0) c.Put("foz", "boz", 10*time.Second) diff --git a/ffmpeg/parse/average.go b/ffmpeg/parse/average.go index 8f715ddd..3d633594 100644 --- a/ffmpeg/parse/average.go +++ b/ffmpeg/parse/average.go @@ -3,23 +3,23 @@ package parse import ( "time" - "github.com/prep/average" + "github.com/datarhei/core/v16/math/average" ) type averager struct { - fps *average.SlidingWindow - pps *average.SlidingWindow - bitrate *average.SlidingWindow + fps *average.SMA + pps *average.SMA + bitrate *average.SMA } func (a *averager) init(window, granularity time.Duration) { - a.fps, _ = average.New(window, granularity) - a.pps, _ = average.New(window, granularity) - a.bitrate, _ = average.New(window, granularity) + a.fps, _ = average.NewSMA(window, granularity) + a.pps, _ = average.NewSMA(window, granularity) + a.bitrate, _ = average.NewSMA(window, granularity) } func (a *averager) stop() { - a.fps.Stop() - a.pps.Stop() - a.bitrate.Stop() + a.fps.Reset() + a.pps.Reset() + a.bitrate.Reset() } diff --git a/ffmpeg/parse/parser.go b/ffmpeg/parse/parser.go index 125e3ee1..b4912af1 100644 --- a/ffmpeg/parse/parser.go +++ b/ffmpeg/parse/parser.go @@ -410,23 +410,15 @@ func (p *parser) Parse(line []byte) uint64 { } } - p.averager.main.fps.Add(int64(p.stats.main.diff.frame)) - p.averager.main.pps.Add(int64(p.stats.main.diff.packet)) - p.averager.main.bitrate.Add(int64(p.stats.main.diff.size) * 8) - - p.progress.ffmpeg.FPS = p.averager.main.fps.Average(p.averager.window) - p.progress.ffmpeg.PPS = p.averager.main.pps.Average(p.averager.window) - p.progress.ffmpeg.Bitrate = p.averager.main.bitrate.Average(p.averager.window) + p.progress.ffmpeg.FPS = p.averager.main.fps.AddAndAverage(float64(p.stats.main.diff.frame)) + p.progress.ffmpeg.PPS = p.averager.main.pps.AddAndAverage(float64(p.stats.main.diff.packet)) + p.progress.ffmpeg.Bitrate = p.averager.main.bitrate.AddAndAverage(float64(p.stats.main.diff.size) * 8) if len(p.averager.input) != 0 && len(p.averager.input) == len(p.progress.ffmpeg.Input) { for i := range p.progress.ffmpeg.Input { - p.averager.input[i].fps.Add(int64(p.stats.input[i].diff.frame)) - p.averager.input[i].pps.Add(int64(p.stats.input[i].diff.packet)) - p.averager.input[i].bitrate.Add(int64(p.stats.input[i].diff.size) * 8) - - p.progress.ffmpeg.Input[i].FPS = p.averager.input[i].fps.Average(p.averager.window) - p.progress.ffmpeg.Input[i].PPS = p.averager.input[i].pps.Average(p.averager.window) - p.progress.ffmpeg.Input[i].Bitrate = p.averager.input[i].bitrate.Average(p.averager.window) + p.progress.ffmpeg.Input[i].FPS = p.averager.input[i].fps.AddAndAverage(float64(p.stats.input[i].diff.frame)) + p.progress.ffmpeg.Input[i].PPS = p.averager.input[i].pps.AddAndAverage(float64(p.stats.input[i].diff.packet)) + p.progress.ffmpeg.Input[i].Bitrate = p.averager.input[i].bitrate.AddAndAverage(float64(p.stats.input[i].diff.size) * 8) if p.collector.IsCollectableIP(p.process.input[i].IP) { p.collector.Activate("") @@ -437,13 +429,9 @@ func (p *parser) Parse(line []byte) uint64 { if len(p.averager.output) != 0 && len(p.averager.output) == len(p.progress.ffmpeg.Output) { for i := range p.progress.ffmpeg.Output { - p.averager.output[i].fps.Add(int64(p.stats.output[i].diff.frame)) - p.averager.output[i].pps.Add(int64(p.stats.output[i].diff.packet)) - p.averager.output[i].bitrate.Add(int64(p.stats.output[i].diff.size) * 8) - - p.progress.ffmpeg.Output[i].FPS = p.averager.output[i].fps.Average(p.averager.window) - p.progress.ffmpeg.Output[i].PPS = p.averager.output[i].pps.Average(p.averager.window) - p.progress.ffmpeg.Output[i].Bitrate = p.averager.output[i].bitrate.Average(p.averager.window) + p.progress.ffmpeg.Output[i].FPS = p.averager.output[i].fps.AddAndAverage(float64(p.stats.output[i].diff.frame)) + p.progress.ffmpeg.Output[i].PPS = p.averager.output[i].pps.AddAndAverage(float64(p.stats.output[i].diff.packet)) + p.progress.ffmpeg.Output[i].Bitrate = p.averager.output[i].bitrate.AddAndAverage(float64(p.stats.output[i].diff.size) * 8) if p.collector.IsCollectableIP(p.process.output[i].IP) { p.collector.Activate("") diff --git a/ffmpeg/parse/parser_test.go b/ffmpeg/parse/parser_test.go index 9caf348d..3e351f48 100644 --- a/ffmpeg/parse/parser_test.go +++ b/ffmpeg/parse/parser_test.go @@ -184,11 +184,11 @@ func TestParserLogHistory(t *testing.T) { require.Equal(t, Progress{ Started: true, Frame: 5968, - FPS: 0, // is calculated with averager + FPS: 5968. / 30, // is calculated with averager Quantizer: 19.4, Size: 453632, Time: d.Seconds(), - Bitrate: 0, // is calculated with averager + Bitrate: 443. * 1024 * 8 / 30, // is calculated with averager Speed: 0.999, Drop: 3522, Dup: 87463, @@ -245,11 +245,11 @@ func TestParserImportLogHistory(t *testing.T) { require.Equal(t, Progress{ Started: true, Frame: 42, - FPS: 0, // is calculated with averager + FPS: 5968. / 30, // is calculated with averager Quantizer: 19.4, Size: 453632, Time: d.Seconds(), - Bitrate: 0, // is calculated with averager + Bitrate: 443. * 1024 * 8 / 30, // is calculated with averager Speed: 0.999, Drop: 3522, Dup: 87463, @@ -312,11 +312,11 @@ func TestParserLogMinimalHistoryLength(t *testing.T) { require.Equal(t, Progress{ Started: true, Frame: 5968, - FPS: 0, // is calculated with averager + FPS: 5968. / 30, // is calculated with averager Quantizer: 19.4, Size: 453632, Time: d.Seconds(), - Bitrate: 0, // is calculated with averager + Bitrate: 443. * 1024 * 8 / 30, // is calculated with averager Speed: 0.999, Drop: 3522, Dup: 87463, @@ -330,11 +330,11 @@ func TestParserLogMinimalHistoryLength(t *testing.T) { require.Equal(t, Progress{ Started: true, Frame: 5968, - FPS: 0, // is calculated with averager + FPS: 5968. / 30, // is calculated with averager Quantizer: 19.4, Size: 453632, Time: d.Seconds(), - Bitrate: 0, // is calculated with averager + Bitrate: 443. * 1024 * 8 / 30, // is calculated with averager Speed: 0.999, Drop: 3522, Dup: 87463, @@ -884,11 +884,11 @@ func TestParserProgressPlayout(t *testing.T) { Coder: "h264", Frame: 7, Keyframe: 1, - FPS: 0, + FPS: 7. / 30, Packet: 11, - PPS: 0, + PPS: 11. / 30, Size: 42, - Bitrate: 0, + Bitrate: 42. * 8 / 30, Pixfmt: "yuvj420p", Quantizer: 0, Width: 1280, @@ -938,11 +938,11 @@ func TestParserProgressPlayout(t *testing.T) { Coder: "libx264", Frame: 7, Keyframe: 1, - FPS: 0, + FPS: 7. / 30, Packet: 0, PPS: 0, Size: 5, - Bitrate: 0, + Bitrate: 5. * 8 / 30, Extradata: 32, Pixfmt: "yuvj420p", Quantizer: 0, @@ -962,11 +962,11 @@ func TestParserProgressPlayout(t *testing.T) { Codec: "h264", Coder: "copy", Frame: 11, - FPS: 0, + FPS: 11. / 30, Packet: 11, - PPS: 0, + PPS: 11. / 30, Size: 231424, - Bitrate: 0, + Bitrate: 231424. * 8 / 30, Pixfmt: "yuvj420p", Quantizer: -1, Width: 1280, @@ -979,12 +979,12 @@ func TestParserProgressPlayout(t *testing.T) { }, Frame: 7, Packet: 0, - FPS: 0, + FPS: 7. / 30, PPS: 0, Quantizer: 0, Size: 231424, Time: 0.56, - Bitrate: 0, + Bitrate: 231424. * 8 / 30, Speed: 0.4, Drop: 0, Dup: 0, @@ -1016,11 +1016,11 @@ func TestParserProgressPlayoutVideo(t *testing.T) { Coder: "h264", Frame: 7, Keyframe: 1, - FPS: 0, + FPS: 7. / 30, Packet: 11, - PPS: 0, + PPS: 11. / 30, Size: 42, - Bitrate: 0, + Bitrate: 42. * 8 / 30, Pixfmt: "yuvj420p", Quantizer: 0, Width: 1280, @@ -1076,11 +1076,11 @@ func TestParserProgressPlayoutVideo(t *testing.T) { Coder: "libx264", Frame: 7, Keyframe: 1, - FPS: 0, + FPS: 7. / 30, Packet: 0, PPS: 0, Size: 5, - Bitrate: 0, + Bitrate: 5. * 8 / 30, Extradata: 32, Pixfmt: "yuvj420p", Quantizer: 0, @@ -1100,11 +1100,11 @@ func TestParserProgressPlayoutVideo(t *testing.T) { Codec: "h264", Coder: "copy", Frame: 11, - FPS: 0, + FPS: 11. / 30, Packet: 11, - PPS: 0, + PPS: 11. / 30, Size: 231424, - Bitrate: 0, + Bitrate: 231424. * 8 / 30, Pixfmt: "yuvj420p", Quantizer: -1, Width: 1280, @@ -1117,12 +1117,12 @@ func TestParserProgressPlayoutVideo(t *testing.T) { }, Frame: 7, Packet: 0, - FPS: 0, + FPS: 7. / 30, PPS: 0, Quantizer: 0, Size: 231424, Time: 0.56, - Bitrate: 0, + Bitrate: 231424. * 8 / 30, Speed: 0.4, Drop: 0, Dup: 0, @@ -1161,11 +1161,11 @@ func TestParserProgressPlayoutAudioVideo(t *testing.T) { Max float64 Average float64 }{25, 25, 25}, - FPS: 0, + FPS: 101. / 30, Packet: 101, - PPS: 0, + PPS: 101. / 30, Size: 530273, - Bitrate: 0, + Bitrate: 530273. * 8 / 30, Pixfmt: "yuv420p", Quantizer: 0, Width: 1280, @@ -1228,11 +1228,11 @@ func TestParserProgressPlayoutAudioVideo(t *testing.T) { Max float64 Average float64 }{43.083, 43.083, 43.083}, - FPS: 0, + FPS: 174. / 30, Packet: 174, - PPS: 0, + PPS: 174. / 30, Size: 713, - Bitrate: 0, + Bitrate: 713. * 8 / 30, Pixfmt: "", Quantizer: 0, Width: 0, @@ -1301,11 +1301,11 @@ func TestParserProgressPlayoutAudioVideo(t *testing.T) { Max float64 Average float64 }{25, 25, 25}, - FPS: 0, + FPS: 101. / 30, Packet: 101, - PPS: 0, + PPS: 101. / 30, Size: 530273, - Bitrate: 0, + Bitrate: 530273. * 8 / 30, Extradata: 0, Pixfmt: "yuv420p", Quantizer: -1, @@ -1333,11 +1333,11 @@ func TestParserProgressPlayoutAudioVideo(t *testing.T) { Max float64 Average float64 }{43.083, 43.083, 43.083}, - FPS: 0, + FPS: 174. / 30, Packet: 174, - PPS: 0, + PPS: 174. / 30, Size: 713, - Bitrate: 0, + Bitrate: 713. * 8 / 30, Pixfmt: "", Quantizer: 0, Width: 0, @@ -1351,12 +1351,12 @@ func TestParserProgressPlayoutAudioVideo(t *testing.T) { }, Frame: 101, Packet: 101, - FPS: 0, - PPS: 0, + FPS: 101. / 30, + PPS: 101. / 30, Quantizer: -1, Size: 530986, Time: 4.3, - Bitrate: 0, + Bitrate: 530986. * 8 / 30, Speed: 1, Drop: 0, Dup: 0, diff --git a/go.mod b/go.mod index 8e8ab4ff..47f22d5c 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,6 @@ require ( github.com/lithammer/shortuuid/v4 v4.0.0 github.com/mattn/go-isatty v0.0.20 github.com/minio/minio-go/v7 v7.0.77 - github.com/prep/average v0.0.0-20200506183628-d26c465f48c3 github.com/prometheus/client_golang v1.20.4 github.com/puzpuzpuz/xsync/v3 v3.4.0 github.com/shirou/gopsutil/v3 v3.24.5 diff --git a/go.sum b/go.sum index 8ea02977..95462ea3 100644 --- a/go.sum +++ b/go.sum @@ -231,8 +231,6 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prep/average v0.0.0-20200506183628-d26c465f48c3 h1:Y7qCvg282QmlyrVQuL2fgGwebuw7zvfnRym09r+dUGc= -github.com/prep/average v0.0.0-20200506183628-d26c465f48c3/go.mod h1:0ZE5gcyWKS151WBDIpmLshHY0l+3edpuKnBUWVVbWKk= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= diff --git a/math/average/sma.go b/math/average/sma.go new file mode 100644 index 00000000..d31e0cd1 --- /dev/null +++ b/math/average/sma.go @@ -0,0 +1,118 @@ +package average + +import ( + "container/ring" + "errors" + gotime "time" + + "github.com/datarhei/core/v16/time" +) + +type SMA struct { + ts time.Source + window int64 + granularity int64 + size int + last int64 + samples *ring.Ring +} + +var ErrWindow = errors.New("window size must be positive") +var ErrGranularity = errors.New("granularity must be positive") +var ErrMultiplier = errors.New("window size has to be a multiplier of the granularity size") + +func NewSMA(window, granularity gotime.Duration) (*SMA, error) { + if window <= 0 { + return nil, ErrWindow + } + + if granularity <= 0 { + return nil, ErrGranularity + } + + if window <= granularity || window%granularity != 0 { + return nil, ErrMultiplier + } + + s := &SMA{ + ts: &time.StdSource{}, + window: window.Nanoseconds(), + granularity: granularity.Nanoseconds(), + } + + s.init() + + return s, nil +} + +func (s *SMA) init() { + s.size = int(s.window / s.granularity) + s.samples = ring.New(s.size) + + s.Reset() + + now := s.ts.Now().UnixNano() + s.last = now - now%s.granularity +} + +func (s *SMA) Add(v float64) { + now := s.ts.Now().UnixNano() + now -= now % s.granularity + + n := (now - s.last) / s.granularity + + if n >= int64(s.samples.Len()) { + // zero everything + s.Reset() + } else { + for i := n; i > 0; i-- { + s.samples = s.samples.Next() + s.samples.Value = float64(0) + } + } + + s.samples.Value = s.samples.Value.(float64) + v + + s.last = now +} + +func (s *SMA) AddAndAverage(v float64) float64 { + s.Add(v) + + total := float64(0) + + s.samples.Do(func(v any) { + total += v.(float64) + }) + + return total / float64(s.samples.Len()) +} + +func (s *SMA) Average() float64 { + total, samplecount := s.Total() + + return total / float64(samplecount) +} + +func (s *SMA) Reset() { + n := s.samples.Len() + + // Initialize the ring buffer with 0 values. + for i := 0; i < n; i++ { + s.samples.Value = float64(0) + s.samples = s.samples.Next() + } +} + +func (s *SMA) Total() (float64, int) { + // Propagate the ringbuffer + s.Add(0) + + total := float64(0) + + s.samples.Do(func(v any) { + total += v.(float64) + }) + + return total, s.samples.Len() +} diff --git a/math/average/sma_test.go b/math/average/sma_test.go new file mode 100644 index 00000000..4e650221 --- /dev/null +++ b/math/average/sma_test.go @@ -0,0 +1,301 @@ +package average + +import ( + "testing" + "time" + + timesrc "github.com/datarhei/core/v16/time" + "github.com/stretchr/testify/require" +) + +func TestNewSMA(t *testing.T) { + _, err := NewSMA(time.Second, time.Second) + require.Error(t, err) + require.ErrorIs(t, err, ErrMultiplier) + + _, err = NewSMA(time.Second, 2*time.Second) + require.Error(t, err) + require.ErrorIs(t, err, ErrMultiplier) + + _, err = NewSMA(3*time.Second, 2*time.Second) + require.Error(t, err) + require.ErrorIs(t, err, ErrMultiplier) + + _, err = NewSMA(0, time.Second) + require.Error(t, err) + require.ErrorIs(t, err, ErrWindow) + + _, err = NewSMA(time.Second, 0) + require.Error(t, err) + require.ErrorIs(t, err, ErrGranularity) + + sme, err := NewSMA(10*time.Second, time.Second) + require.NoError(t, err) + require.NotNil(t, sme) +} + +func TestAddSMA(t *testing.T) { + ts := ×rc.TestSource{ + N: time.Unix(0, 0), + } + + sme := &SMA{ + ts: ts, + window: time.Second.Nanoseconds(), + granularity: time.Millisecond.Nanoseconds(), + } + sme.init() + + sme.Add(42) + + total, samplecount := sme.Total() + require.Equal(t, float64(42), total) + require.Equal(t, int(time.Second/time.Millisecond), samplecount) + + sme.Add(5) + + total, samplecount = sme.Total() + require.Equal(t, float64(47), total) + require.Equal(t, int(time.Second/time.Millisecond), samplecount) + + ts.Set(5, 0) + + total, samplecount = sme.Total() + require.Equal(t, float64(0), total) + require.Equal(t, int(time.Second/time.Millisecond), samplecount) +} + +func TestAverageSMA(t *testing.T) { + ts := ×rc.TestSource{ + N: time.Unix(0, 0), + } + + sme := &SMA{ + ts: ts, + window: time.Second.Nanoseconds(), + granularity: time.Millisecond.Nanoseconds(), + } + sme.init() + + sme.Add(42) + + avg := sme.Average() + require.Equal(t, 42.0/1000, avg) + + sme.Add(5) + + avg = sme.Average() + require.Equal(t, 47.0/1000, avg) + + ts.Set(5, 0) + + avg = sme.Average() + require.Equal(t, .0/1000, avg) +} + +func TestAddAndAverageSMA(t *testing.T) { + ts := ×rc.TestSource{ + N: time.Unix(0, 0), + } + + sme := &SMA{ + ts: ts, + window: time.Second.Nanoseconds(), + granularity: time.Millisecond.Nanoseconds(), + } + sme.init() + + avg := sme.AddAndAverage(42) + require.Equal(t, 42.0/1000, avg) + + avg = sme.AddAndAverage(5) + require.Equal(t, 47.0/1000, avg) + + ts.Set(5, 0) + + avg = sme.Average() + require.Equal(t, .0/1000, avg) +} + +func TestAverageSeriesSMA(t *testing.T) { + ts := ×rc.TestSource{ + N: time.Unix(0, 0), + } + + sme := &SMA{ + ts: ts, + window: 10 * time.Second.Nanoseconds(), + granularity: time.Second.Nanoseconds(), + } + sme.init() + + sme.Add(42) // [42, 0, 0, 0, 0, 0, 0, 0, 0, 0] + + ts.Set(1, 0) + + sme.Add(5) // [5, 42, 0, 0, 0, 0, 0, 0, 0, 0] + + ts.Set(2, 0) + + sme.Add(18) // [18, 5, 42, 0, 0, 0, 0, 0, 0, 0] + + ts.Set(3, 0) + + sme.Add(47) // [47, 18, 5, 42, 0, 0, 0, 0, 0, 0] + + ts.Set(4, 0) + + sme.Add(92) // [92, 47, 18, 5, 42, 0, 0, 0, 0, 0] + + ts.Set(5, 0) + + sme.Add(2) // [2, 92, 47, 18, 5, 42, 0, 0, 0, 0] + + ts.Set(6, 0) + + sme.Add(75) // [75, 2, 92, 47, 18, 5, 42, 0, 0, 0] + + ts.Set(7, 0) + + sme.Add(33) // [33, 75, 2, 92, 47, 18, 5, 42, 0, 0] + + ts.Set(8, 0) + + sme.Add(89) // [89, 33, 75, 2, 92, 47, 18, 5, 42, 0] + + ts.Set(9, 0) + + sme.Add(12) // [12, 89, 33, 75, 2, 92, 47, 18, 5, 42] + + avg := sme.Average() + require.Equal(t, (12+89+33+75+2+92+47+18+5+42)/10., avg) + + ts.Set(10, 0) + + avg = sme.Average() + require.Equal(t, (12+89+33+75+2+92+47+18+5)/10., avg) + + ts.Set(15, 0) + + avg = sme.Average() + require.Equal(t, (12+89+33+75)/10., avg) + + ts.Set(19, 0) + + avg = sme.Average() + require.Equal(t, (0)/10., avg) +} + +func TestResetSMA(t *testing.T) { + ts := ×rc.TestSource{ + N: time.Unix(0, 0), + } + + sme := &SMA{ + ts: ts, + window: 10 * time.Second.Nanoseconds(), + granularity: time.Second.Nanoseconds(), + } + sme.init() + + sme.Add(42) // [42, 0, 0, 0, 0, 0, 0, 0, 0, 0] + + ts.Set(1, 0) + + sme.Add(5) // [5, 42, 0, 0, 0, 0, 0, 0, 0, 0] + + ts.Set(2, 0) + + sme.Add(18) // [18, 5, 42, 0, 0, 0, 0, 0, 0, 0] + + ts.Set(3, 0) + + sme.Add(47) // [47, 18, 5, 42, 0, 0, 0, 0, 0, 0] + + ts.Set(4, 0) + + sme.Add(92) // [92, 47, 18, 5, 42, 0, 0, 0, 0, 0] + + ts.Set(5, 0) + + sme.Add(2) // [2, 92, 47, 18, 5, 42, 0, 0, 0, 0] + + ts.Set(6, 0) + + sme.Add(75) // [75, 2, 92, 47, 18, 5, 42, 0, 0, 0] + + ts.Set(7, 0) + + sme.Add(33) // [33, 75, 2, 92, 47, 18, 5, 42, 0, 0] + + ts.Set(8, 0) + + sme.Add(89) // [89, 33, 75, 2, 92, 47, 18, 5, 42, 0] + + ts.Set(9, 0) + + sme.Add(12) // [12, 89, 33, 75, 2, 92, 47, 18, 5, 42] + + avg := sme.Average() + require.Equal(t, (12+89+33+75+2+92+47+18+5+42)/10., avg) + + sme.Reset() + + avg = sme.Average() + require.Equal(t, 0/10., avg) +} + +func TestTotalSMA(t *testing.T) { + ts := ×rc.TestSource{ + N: time.Unix(0, 0), + } + + sme := &SMA{ + ts: ts, + window: 10 * time.Second.Nanoseconds(), + granularity: time.Second.Nanoseconds(), + } + sme.init() + + sme.Add(42) // [42, 0, 0, 0, 0, 0, 0, 0, 0, 0] + + ts.Set(1, 0) + + sme.Add(5) // [5, 42, 0, 0, 0, 0, 0, 0, 0, 0] + + ts.Set(2, 0) + + sme.Add(18) // [18, 5, 42, 0, 0, 0, 0, 0, 0, 0] + + ts.Set(3, 0) + + sme.Add(47) // [47, 18, 5, 42, 0, 0, 0, 0, 0, 0] + + ts.Set(4, 0) + + sme.Add(92) // [92, 47, 18, 5, 42, 0, 0, 0, 0, 0] + + ts.Set(5, 0) + + sme.Add(2) // [2, 92, 47, 18, 5, 42, 0, 0, 0, 0] + + ts.Set(6, 0) + + sme.Add(75) // [75, 2, 92, 47, 18, 5, 42, 0, 0, 0] + + ts.Set(7, 0) + + sme.Add(33) // [33, 75, 2, 92, 47, 18, 5, 42, 0, 0] + + ts.Set(8, 0) + + sme.Add(89) // [89, 33, 75, 2, 92, 47, 18, 5, 42, 0] + + ts.Set(9, 0) + + sme.Add(12) // [12, 89, 33, 75, 2, 92, 47, 18, 5, 42] + + total, nsamples := sme.Total() + require.Equal(t, float64(12+89+33+75+2+92+47+18+5+42), total) + require.Equal(t, 10, nsamples) +} diff --git a/session/collector.go b/session/collector.go index 1dd0489d..98eda19b 100644 --- a/session/collector.go +++ b/session/collector.go @@ -9,9 +9,8 @@ import ( "github.com/datarhei/core/v16/encoding/json" "github.com/datarhei/core/v16/log" + "github.com/datarhei/core/v16/math/average" "github.com/datarhei/core/v16/net" - - "github.com/prep/average" ) // Session represents an active session @@ -239,8 +238,8 @@ type collector struct { maxTxBitrate float64 maxSessions uint64 - rxBitrate *average.SlidingWindow - txBitrate *average.SlidingWindow + rxBitrate *average.SMA + txBitrate *average.SMA collectHistory bool history history @@ -410,8 +409,8 @@ func (c *collector) start() { c.running = true - c.rxBitrate, _ = average.New(averageWindow, averageGranularity) - c.txBitrate, _ = average.New(averageWindow, averageGranularity) + c.rxBitrate, _ = average.NewSMA(averageWindow, averageGranularity) + c.txBitrate, _ = average.NewSMA(averageWindow, averageGranularity) } func (c *collector) stop() { @@ -648,7 +647,7 @@ func (c *collector) Ingress(id string, size int64) { } if sess.Ingress(size) { - c.rxBitrate.Add(size * 8) + c.rxBitrate.Add(float64(size) * 8) c.rxBytes += uint64(size) } } @@ -667,7 +666,7 @@ func (c *collector) Egress(id string, size int64) { } if sess.Egress(size) { - c.txBitrate.Add(size * 8) + c.txBitrate.Add(float64(size) * 8) c.txBytes += uint64(size) } } @@ -709,11 +708,11 @@ func (c *collector) IsSessionsExceeded() bool { } func (c *collector) IngressBitrate() float64 { - return c.rxBitrate.Average(averageWindow) + return c.rxBitrate.Average() } func (c *collector) EgressBitrate() float64 { - return c.txBitrate.Average(averageWindow) + return c.txBitrate.Average() } func (c *collector) MaxIngressBitrate() float64 { diff --git a/session/session.go b/session/session.go index d727de7f..e943cea3 100644 --- a/session/session.go +++ b/session/session.go @@ -6,7 +6,7 @@ import ( "time" "github.com/datarhei/core/v16/log" - "github.com/prep/average" + "github.com/datarhei/core/v16/math/average" ) type session struct { @@ -27,10 +27,10 @@ type session struct { timeout time.Duration callback func(*session) - rxBitrate *average.SlidingWindow + rxBitrate *average.SMA rxBytes uint64 - txBitrate *average.SlidingWindow + txBitrate *average.SMA txBytes uint64 tickerStop context.CancelFunc @@ -59,8 +59,8 @@ func (s *session) Init(id, reference string, closeCallback func(*session), inact s.peer = "" s.extra = map[string]interface{}{} - s.rxBitrate, _ = average.New(averageWindow, averageGranularity) - s.txBitrate, _ = average.New(averageWindow, averageGranularity) + s.rxBitrate, _ = average.NewSMA(averageWindow, averageGranularity) + s.txBitrate, _ = average.NewSMA(averageWindow, averageGranularity) s.topRxBitrate = 0.0 s.topTxBitrate = 0.0 @@ -105,8 +105,8 @@ func (s *session) close() { s.tickerStop = nil } - s.rxBitrate.Stop() - s.txBitrate.Stop() + s.rxBitrate.Reset() + s.txBitrate.Reset() go s.callback(s) } @@ -157,10 +157,10 @@ func (s *session) Ingress(size int64) bool { s.stale.Stop() s.stale.Reset(s.timeout) - s.rxBitrate.Add(size * 8) + s.rxBitrate.Add(float64(size) * 8) s.rxBytes += uint64(size) - bitrate := s.rxBitrate.Average(averageWindow) + bitrate := s.rxBitrate.Average() if bitrate > s.topRxBitrate { s.topRxBitrate = bitrate } @@ -183,10 +183,10 @@ func (s *session) Egress(size int64) bool { s.stale.Stop() s.stale.Reset(s.timeout) - s.txBitrate.Add(size * 8) + s.txBitrate.Add(float64(size) * 8) s.txBytes += uint64(size) - bitrate := s.txBitrate.Average(averageWindow) + bitrate := s.txBitrate.Average() if bitrate > s.topTxBitrate { s.topTxBitrate = bitrate } @@ -199,11 +199,11 @@ func (s *session) Egress(size int64) bool { } func (s *session) RxBitrate() float64 { - return s.rxBitrate.Average(averageWindow) + return s.rxBitrate.Average() } func (s *session) TxBitrate() float64 { - return s.txBitrate.Average(averageWindow) + return s.txBitrate.Average() } func (s *session) TopRxBitrate() float64 { diff --git a/time/source.go b/time/source.go new file mode 100644 index 00000000..83eff7dd --- /dev/null +++ b/time/source.go @@ -0,0 +1,25 @@ +package time + +import "time" + +type Source interface { + Now() time.Time +} + +type StdSource struct{} + +func (s *StdSource) Now() time.Time { + return time.Now() +} + +type TestSource struct { + N time.Time +} + +func (t *TestSource) Now() time.Time { + return t.N +} + +func (t *TestSource) Set(sec int64, nsec int64) { + t.N = time.Unix(sec, nsec) +} diff --git a/vendor/github.com/prep/average/.travis.yml b/vendor/github.com/prep/average/.travis.yml deleted file mode 100644 index 9fc8e3b1..00000000 --- a/vendor/github.com/prep/average/.travis.yml +++ /dev/null @@ -1,28 +0,0 @@ -language: go - -go: - - 1.9 - - master - -# Skip the install step. Don't `go get` dependencies. Only build with the -# code in vendor/ -install: true - -matrix: - # It's ok if our code fails on unstable development versions of Go. - allow_failures: - - go: master - # Don't wait for tip tests to finish. Mark the test run green if the - # tests pass on the stable versions of Go. - fast_finish: true - -notifications: - email: false - -before_script: - - GO_FILES=$(find . -iname '*.go' -type f | grep -v /vendor/) - -script: - - test -z $(gofmt -s -l $GO_FILES) - - go tool vet . - - go test -v -race ./... diff --git a/vendor/github.com/prep/average/LICENSE b/vendor/github.com/prep/average/LICENSE deleted file mode 100644 index 6a66aea5..00000000 --- a/vendor/github.com/prep/average/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/prep/average/README.md b/vendor/github.com/prep/average/README.md deleted file mode 100644 index 60dbcc50..00000000 --- a/vendor/github.com/prep/average/README.md +++ /dev/null @@ -1,42 +0,0 @@ -average -[![TravisCI](https://travis-ci.org/prep/average.svg?branch=master)](https://travis-ci.org/prep/average.svg?branch=master) -[![Go Report Card](https://goreportcard.com/badge/github.com/prep/average)](https://goreportcard.com/report/github.com/prep/average) -[![GoDoc](https://godoc.org/github.com/prep/average?status.svg)](https://godoc.org/github.com/prep/average) -======= -This stupidly named Go package contains a single struct that is used to implement counters on a sliding time window. - -Usage ------ -```go - -import ( - "fmt" - - "github.com/prep/average" -) - -func main() { - // Create a SlidingWindow that has a window of 15 minutes, with a - // granulity of 1 minute. - sw := average.MustNew(15 * time.Minute, time.Minute) - defer sw.Stop() - - // Do some work. - sw.Add(15) - // Do some more work. - sw.Add(22) - // Do even more work. - sw.Add(22) - - fmt.Printf("Average of last 1m: %f\n", sw.Average(time.Minute) - fmt.Printf("Average of last 5m: %f\n", sw.Average(5 * time.Minute) - fmt.Printf("Average of last 15m: %f\n\n", sw.Average(15 * time.Minute) - - total, numSamples := sw.Total(15 * time.Minute) - fmt.Printf("Counter has a total of %d over %d samples", total, numSamples) -} -``` - -License -------- -This software is created for MessageBird B.V. and distributed under the BSD-style license found in the LICENSE file. diff --git a/vendor/github.com/prep/average/slidingwindow.go b/vendor/github.com/prep/average/slidingwindow.go deleted file mode 100644 index 793422dd..00000000 --- a/vendor/github.com/prep/average/slidingwindow.go +++ /dev/null @@ -1,142 +0,0 @@ -// Package average implements sliding time window. -package average - -import ( - "errors" - "sync" - "time" -) - -// SlidingWindow provides a sliding time window with a custom size and -// granularity to store int64 counters. This can be used to determine the total -// or unweighted mean average of a subset of the window size. -type SlidingWindow struct { - window time.Duration - granularity time.Duration - samples []int64 - pos int - size int - stopOnce sync.Once - stopC chan struct{} - sync.RWMutex -} - -// MustNew returns a new SlidingWindow, but panics if an error occurs. -func MustNew(window, granularity time.Duration) *SlidingWindow { - sw, err := New(window, granularity) - if err != nil { - panic(err.Error()) - } - - return sw -} - -// New returns a new SlidingWindow. -func New(window, granularity time.Duration) (*SlidingWindow, error) { - if window == 0 { - return nil, errors.New("window cannot be 0") - } - if granularity == 0 { - return nil, errors.New("granularity cannot be 0") - } - if window <= granularity || window%granularity != 0 { - return nil, errors.New("window size has to be a multiplier of the granularity size") - } - - sw := &SlidingWindow{ - window: window, - granularity: granularity, - samples: make([]int64, int(window/granularity)), - stopC: make(chan struct{}), - } - - go sw.shifter() - return sw, nil -} - -func (sw *SlidingWindow) shifter() { - ticker := time.NewTicker(sw.granularity) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - sw.Lock() - if sw.pos = sw.pos + 1; sw.pos >= len(sw.samples) { - sw.pos = 0 - } - sw.samples[sw.pos] = 0 - if sw.size < len(sw.samples) { - sw.size++ - } - sw.Unlock() - - case <-sw.stopC: - return - } - } -} - -// Add increments the value of the current sample. -func (sw *SlidingWindow) Add(v int64) { - sw.Lock() - sw.samples[sw.pos] += v - sw.Unlock() -} - -// Average returns the unweighted mean of the specified window. -func (sw *SlidingWindow) Average(window time.Duration) float64 { - total, sampleCount := sw.Total(window) - if sampleCount == 0 { - return 0 - } - - return float64(total) / float64(sampleCount) -} - -// Reset the samples in this sliding time window. -func (sw *SlidingWindow) Reset() { - sw.Lock() - defer sw.Unlock() - - sw.pos, sw.size = 0, 0 - for i := range sw.samples { - sw.samples[i] = 0 - } -} - -// Stop the shifter of this sliding time window. A stopped SlidingWindow cannot -// be started again. -func (sw *SlidingWindow) Stop() { - sw.stopOnce.Do(func() { - sw.stopC <- struct{}{} - }) -} - -// Total returns the sum of all values over the specified window, as well as -// the number of samples. -func (sw *SlidingWindow) Total(window time.Duration) (int64, int) { - if window > sw.window { - window = sw.window - } - - sampleCount := int(window / sw.granularity) - if sampleCount > sw.size { - sampleCount = sw.size - } - - sw.RLock() - defer sw.RUnlock() - - var total int64 - for i := 1; i <= sampleCount; i++ { - pos := sw.pos - i - if pos < 0 { - pos += len(sw.samples) - } - - total += sw.samples[pos] - } - - return total, sampleCount -} diff --git a/vendor/modules.txt b/vendor/modules.txt index b4966dc5..792d3807 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -327,9 +327,6 @@ github.com/pmezard/go-difflib/difflib # github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 ## explicit; go 1.14 github.com/power-devops/perfstat -# github.com/prep/average v0.0.0-20200506183628-d26c465f48c3 -## explicit -github.com/prep/average # github.com/prometheus/client_golang v1.20.4 ## explicit; go 1.20 github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil From 2dbe5b5685e7526d5c740002316921d316e89578 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 24 Oct 2024 15:08:26 +0200 Subject: [PATCH 46/64] Add GPU support --- app/api/api.go | 8 +- cluster/about.go | 42 +- cluster/api.go | 13 + cluster/client/client.go | 31 +- cluster/leader_rebalance.go | 8 +- cluster/leader_relocate.go | 10 +- cluster/leader_synchronize.go | 14 +- cluster/leader_test.go | 634 +++++++----- cluster/node/core.go | 80 +- cluster/node/node.go | 45 +- cluster/resources.go | 159 ++- cluster/resources_test.go | 603 +++++++++++ config/config.go | 17 +- config/data.go | 6 +- config/value/primitives.go | 54 + config/value/primitives_test.go | 26 + ffmpeg/ffmpeg.go | 74 +- ffmpeg/parse/parser.go | 2 +- ffmpeg/parse/types.go | 21 + http/api/process.go | 136 ++- http/api/process_test.go | 35 +- internal/.gitignore | 3 +- internal/testhelper/nvidia-smi/nvidia-smi.go | 973 ++++++++++++++++++ monitor/cpu.go | 6 +- monitor/disk.go | 2 +- monitor/mem.go | 6 +- monitor/net.go | 2 +- process/limiter.go | 503 +++++---- process/limiter_test.go | 154 ++- process/process.go | 205 ++-- process/process_test.go | 18 +- psutil/gpu/gpu.go | 28 +- psutil/gpu/nvidia/fixtures/process.txt | 54 + .../nvidia/fixtures/{data1.xml => query1.xml} | 0 .../nvidia/fixtures/{data2.xml => query2.xml} | 18 + .../nvidia/fixtures/{data3.xml => query3.xml} | 0 psutil/gpu/nvidia/nvidia.go | 288 ++++-- psutil/gpu/nvidia/nvidia_test.go | 406 +++++++- psutil/process.go | 41 +- psutil/psutil.go | 174 ++-- resources/resources.go | 359 +++++-- resources/resources_test.go | 903 ++++++++++++++-- restream/app/process.go | 114 +- restream/app/process_test.go | 10 +- restream/core.go | 194 ++-- restream/core_test.go | 4 +- restream/task.go | 59 +- session/registry_test.go | 1 + 48 files changed, 5390 insertions(+), 1153 deletions(-) create mode 100644 cluster/resources_test.go create mode 100644 internal/testhelper/nvidia-smi/nvidia-smi.go create mode 100644 psutil/gpu/nvidia/fixtures/process.txt rename psutil/gpu/nvidia/fixtures/{data1.xml => query1.xml} (100%) rename psutil/gpu/nvidia/fixtures/{data2.xml => query2.xml} (98%) rename psutil/gpu/nvidia/fixtures/{data3.xml => query3.xml} (100%) diff --git a/app/api/api.go b/app/api/api.go index fa017a88..042354e4 100644 --- a/app/api/api.go +++ b/app/api/api.go @@ -371,9 +371,11 @@ func (a *api) start(ctx context.Context) error { } resources, err := resources.New(resources.Config{ - MaxCPU: cfg.Resources.MaxCPUUsage, - MaxMemory: cfg.Resources.MaxMemoryUsage, - Logger: a.log.logger.core.WithComponent("Resources"), + MaxCPU: cfg.Resources.MaxCPUUsage, + MaxMemory: cfg.Resources.MaxMemoryUsage, + MaxGPU: cfg.Resources.MaxGPUUsage, + MaxGPUMemory: cfg.Resources.MaxGPUMemoryUsage, + Logger: a.log.logger.core.WithComponent("Resources"), }) if err != nil { return fmt.Errorf("failed to initialize resource manager: %w", err) diff --git a/cluster/about.go b/cluster/about.go index 3356faec..60585a38 100644 --- a/cluster/about.go +++ b/cluster/about.go @@ -18,18 +18,29 @@ type ClusterRaft struct { } type ClusterNodeResources struct { - IsThrottling bool // Whether this core is currently throttling - NCPU float64 // Number of CPU on this node - CPU float64 // Current CPU load, 0-100*ncpu - CPULimit float64 // Defined CPU load limit, 0-100*ncpu - CPUCore float64 // Current CPU load of the core itself, 0-100*ncpu - Mem uint64 // Currently used memory in bytes - MemLimit uint64 // Defined memory limit in bytes - MemTotal uint64 // Total available memory in bytes - MemCore uint64 // Current used memory of the core itself in bytes + IsThrottling bool // Whether this core is currently throttling + NCPU float64 // Number of CPU on this node + CPU float64 // Current CPU load, 0-100*ncpu + CPULimit float64 // Defined CPU load limit, 0-100*ncpu + CPUCore float64 // Current CPU load of the core itself, 0-100*ncpu + Mem uint64 // Currently used memory in bytes + MemLimit uint64 // Defined memory limit in bytes + MemTotal uint64 // Total available memory in bytes + MemCore uint64 // Current used memory of the core itself in bytes + GPU []ClusterNodeGPUResources // GPU resources Error error } +type ClusterNodeGPUResources struct { + Mem uint64 // Currently used memory in bytes + MemLimit uint64 // Defined memory limit in bytes + MemTotal uint64 // Total available memory in bytes + Usage float64 // Current general usage, 0-100 + UsageLimit float64 // Defined general usage limit, 0-100 + Encoder float64 // Current encoder usage, 0-100 + Decoder float64 // Current decoder usage, 0-100 +} + type ClusterNode struct { ID string Name string @@ -157,6 +168,19 @@ func (c *cluster) About() (ClusterAbout, error) { }, } + if len(nodeAbout.Resources.GPU) != 0 { + node.Resources.GPU = make([]ClusterNodeGPUResources, len(nodeAbout.Resources.GPU)) + for i, gpu := range nodeAbout.Resources.GPU { + node.Resources.GPU[i].Mem = gpu.Mem + node.Resources.GPU[i].MemLimit = gpu.MemLimit + node.Resources.GPU[i].MemTotal = gpu.MemTotal + node.Resources.GPU[i].Usage = gpu.Usage + node.Resources.GPU[i].UsageLimit = gpu.UsageLimit + node.Resources.GPU[i].Encoder = gpu.Encoder + node.Resources.GPU[i].Decoder = gpu.Decoder + } + } + if s, ok := serversMap[nodeAbout.ID]; ok { node.Voter = s.Voter node.Leader = s.Leader diff --git a/cluster/api.go b/cluster/api.go index de2f865b..38b21695 100644 --- a/cluster/api.go +++ b/cluster/api.go @@ -195,6 +195,19 @@ func (a *api) About(c echo.Context) error { }, } + if len(resources.GPU.GPU) != 0 { + about.Resources.GPU = make([]client.AboutResponseGPUResources, len(resources.GPU.GPU)) + for i, gpu := range resources.GPU.GPU { + about.Resources.GPU[i].Mem = gpu.MemoryUsed + about.Resources.GPU[i].MemLimit = gpu.MemoryLimit + about.Resources.GPU[i].MemTotal = gpu.MemoryTotal + about.Resources.GPU[i].Usage = gpu.Usage + about.Resources.GPU[i].UsageLimit = gpu.UsageLimit + about.Resources.GPU[i].Encoder = gpu.Encoder + about.Resources.GPU[i].Decoder = gpu.Decoder + } + } + if err != nil { about.Resources.Error = err.Error() } diff --git a/cluster/client/client.go b/cluster/client/client.go index 84ab0230..214bf34d 100644 --- a/cluster/client/client.go +++ b/cluster/client/client.go @@ -83,17 +83,28 @@ type AboutResponse struct { Resources AboutResponseResources `json:"resources"` } +type AboutResponseGPUResources struct { + Mem uint64 `json:"memory_bytes"` // Currently used memory in bytes + MemLimit uint64 `json:"memory_limit_bytes"` // Defined memory limit in bytes + MemTotal uint64 `json:"memory_total_bytes"` // Total available memory in bytes + Usage float64 `json:"usage"` // Current general usage, 0-100 + Encoder float64 `json:"encoder"` // Current encoder usage, 0-100 + Decoder float64 `json:"decoder"` // Current decoder usage, 0-100 + UsageLimit float64 `json:"usage_limit"` // Defined general usage limit, 0-100 +} + type AboutResponseResources struct { - IsThrottling bool `json:"is_throttling"` // Whether this core is currently throttling - NCPU float64 `json:"ncpu"` // Number of CPU on this node - CPU float64 `json:"cpu"` // Current CPU load, 0-100*ncpu - CPULimit float64 `json:"cpu_limit"` // Defined CPU load limit, 0-100*ncpu - CPUCore float64 `json:"cpu_core"` // Current CPU load of the core itself, 0-100*ncpu - Mem uint64 `json:"memory_bytes"` // Currently used memory in bytes - MemLimit uint64 `json:"memory_limit_bytes"` // Defined memory limit in bytes - MemTotal uint64 `json:"memory_total_bytes"` // Total available memory in bytes - MemCore uint64 `json:"memory_core_bytes"` // Current used memory of the core itself in bytes - Error string `json:"error"` // Last error + IsThrottling bool `json:"is_throttling"` // Whether this core is currently throttling + NCPU float64 `json:"ncpu"` // Number of CPU on this node + CPU float64 `json:"cpu"` // Current CPU load, 0-100*ncpu + CPULimit float64 `json:"cpu_limit"` // Defined CPU load limit, 0-100*ncpu + CPUCore float64 `json:"cpu_core"` // Current CPU load of the core itself, 0-100*ncpu + Mem uint64 `json:"memory_bytes"` // Currently used memory in bytes + MemLimit uint64 `json:"memory_limit_bytes"` // Defined memory limit in bytes + MemTotal uint64 `json:"memory_total_bytes"` // Total available memory in bytes + MemCore uint64 `json:"memory_core_bytes"` // Current used memory of the core itself in bytes + GPU []AboutResponseGPUResources `json:"gpu"` // Currently used GPU resources + Error string `json:"error"` // Last error } type SetNodeStateRequest struct { diff --git a/cluster/leader_rebalance.go b/cluster/leader_rebalance.go index c583f1ac..3ef2b8f7 100644 --- a/cluster/leader_rebalance.go +++ b/cluster/leader_rebalance.go @@ -78,7 +78,7 @@ func rebalance(have []node.Process, nodes map[string]node.About) ([]interface{}, // Mark nodes as throttling where at least one process is still throttling for _, haveP := range have { - if haveP.Throttling { + if haveP.Resources.Throttling { resources.Throttling(haveP.NodeID, true) } } @@ -126,7 +126,7 @@ func rebalance(have []node.Process, nodes map[string]node.About) ([]interface{}, continue } - if resources.HasNodeEnough(raNodeid, p.Config.LimitCPU, p.Config.LimitMemory) { + if resources.HasNodeEnough(raNodeid, ResourcesFromConfig(p.Config)) { availableNodeid = raNodeid break } @@ -135,7 +135,7 @@ func rebalance(have []node.Process, nodes map[string]node.About) ([]interface{}, // Find the best node with enough resources available. if len(availableNodeid) == 0 { - nodes := resources.FindBestNodes(p.Config.LimitCPU, p.Config.LimitMemory) + nodes := resources.FindBestNodes(ResourcesFromConfig(p.Config)) for _, nodeid := range nodes { if nodeid == overloadedNodeid { continue @@ -169,7 +169,7 @@ func rebalance(have []node.Process, nodes map[string]node.About) ([]interface{}, processes[i] = p // Adjust the resources. - resources.Move(availableNodeid, overloadedNodeid, p.CPU, p.Mem) + resources.Move(availableNodeid, overloadedNodeid, ResourcesFromProcess(p.Resources)) // Adjust the reference affinity. haveReferenceAffinity.Move(p.Config.Reference, p.Config.Domain, overloadedNodeid, availableNodeid) diff --git a/cluster/leader_relocate.go b/cluster/leader_relocate.go index dc5a057a..27ab847b 100644 --- a/cluster/leader_relocate.go +++ b/cluster/leader_relocate.go @@ -95,7 +95,7 @@ func relocate(have []node.Process, nodes map[string]node.About, relocateMap map[ // Mark nodes as throttling where at least one process is still throttling for _, haveP := range have { - if haveP.Throttling { + if haveP.Resources.Throttling { resources.Throttling(haveP.NodeID, true) } } @@ -136,7 +136,7 @@ func relocate(have []node.Process, nodes map[string]node.About, relocateMap map[ if len(targetNodeid) != 0 { _, hasNode := nodes[targetNodeid] - if !hasNode || !resources.HasNodeEnough(targetNodeid, process.Config.LimitCPU, process.Config.LimitMemory) { + if !hasNode || !resources.HasNodeEnough(targetNodeid, ResourcesFromConfig(process.Config)) { targetNodeid = "" } } @@ -152,7 +152,7 @@ func relocate(have []node.Process, nodes map[string]node.About, relocateMap map[ continue } - if resources.HasNodeEnough(raNodeid, process.Config.LimitCPU, process.Config.LimitMemory) { + if resources.HasNodeEnough(raNodeid, ResourcesFromConfig(process.Config)) { targetNodeid = raNodeid break } @@ -161,7 +161,7 @@ func relocate(have []node.Process, nodes map[string]node.About, relocateMap map[ // Find the best node with enough resources available. if len(targetNodeid) == 0 { - nodes := resources.FindBestNodes(process.Config.LimitCPU, process.Config.LimitMemory) + nodes := resources.FindBestNodes(ResourcesFromConfig(process.Config)) for _, nodeid := range nodes { if nodeid == sourceNodeid { continue @@ -194,7 +194,7 @@ func relocate(have []node.Process, nodes map[string]node.About, relocateMap map[ opBudget -= 5 // Adjust the resources. - resources.Move(targetNodeid, sourceNodeid, process.CPU, process.Mem) + resources.Move(targetNodeid, sourceNodeid, ResourcesFromProcess(process.Resources)) // Adjust the reference affinity. haveReferenceAffinity.Move(process.Config.Reference, process.Config.Domain, sourceNodeid, targetNodeid) diff --git a/cluster/leader_synchronize.go b/cluster/leader_synchronize.go index b597d78e..c56e4ad8 100644 --- a/cluster/leader_synchronize.go +++ b/cluster/leader_synchronize.go @@ -143,7 +143,7 @@ func synchronize(wish map[string]string, want []store.Process, have []node.Proce // Mark nodes as throttling where at least one process is still throttling for _, haveP := range have { - if haveP.Throttling { + if haveP.Resources.Throttling { resources.Throttling(haveP.NodeID, true) } } @@ -182,7 +182,7 @@ func synchronize(wish map[string]string, want []store.Process, have []node.Proce processid: haveP.Config.ProcessID(), }) - resources.Remove(haveP.NodeID, haveP.CPU, haveP.Mem) + resources.Remove(haveP.NodeID, ResourcesFromProcess(haveP.Resources)) continue } @@ -219,7 +219,7 @@ func synchronize(wish map[string]string, want []store.Process, have []node.Proce }) // Release the resources. - resources.Remove(haveP.NodeID, haveP.CPU, haveP.Mem) + resources.Remove(haveP.NodeID, ResourcesFromProcess(haveP.Resources)) } } @@ -229,7 +229,7 @@ func synchronize(wish map[string]string, want []store.Process, have []node.Proce for _, haveP := range wantOrderStart { nodeid := haveP.NodeID - resources.Add(nodeid, haveP.Config.LimitCPU, haveP.Config.LimitMemory) + resources.Add(nodeid, ResourcesFromConfig(haveP.Config)) // TODO: check if the current node has actually enough resources available, // otherwise it needs to be moved somewhere else. If the node doesn't @@ -347,7 +347,7 @@ func synchronize(wish map[string]string, want []store.Process, have []node.Proce // Try to add the process to a node where other processes with the same reference currently reside. raNodes := haveReferenceAffinity.Nodes(wantP.Config.Reference, wantP.Config.Domain) for _, raNodeid := range raNodes { - if resources.HasNodeEnough(raNodeid, wantP.Config.LimitCPU, wantP.Config.LimitMemory) { + if resources.HasNodeEnough(raNodeid, ResourcesFromConfig(wantP.Config)) { nodeid = raNodeid break } @@ -355,7 +355,7 @@ func synchronize(wish map[string]string, want []store.Process, have []node.Proce // Find the node with the most resources available. if len(nodeid) == 0 { - nodes := resources.FindBestNodes(wantP.Config.LimitCPU, wantP.Config.LimitMemory) + nodes := resources.FindBestNodes(ResourcesFromConfig(wantP.Config)) if len(nodes) > 0 { nodeid = nodes[0] } @@ -372,7 +372,7 @@ func synchronize(wish map[string]string, want []store.Process, have []node.Proce opBudget -= 3 // Consume the resources - resources.Add(nodeid, wantP.Config.LimitCPU, wantP.Config.LimitMemory) + resources.Add(nodeid, ResourcesFromConfig(wantP.Config)) reality[pid] = nodeid diff --git a/cluster/leader_test.go b/cluster/leader_test.go index 4f1d6bba..af17d9a6 100644 --- a/cluster/leader_test.go +++ b/cluster/leader_test.go @@ -193,11 +193,13 @@ func TestSynchronizeOrderStop(t *testing.T) { have := []node.Process{ { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 12, - Mem: 5, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 12, + Mem: 5, + }, Runtime: 42, UpdatedAt: now, Config: &app.Config{ @@ -285,11 +287,13 @@ func TestSynchronizeOrderStart(t *testing.T) { have := []node.Process{ { - NodeID: "node1", - Order: "stop", - State: "finished", - CPU: 0, - Mem: 0, + NodeID: "node1", + Order: "stop", + State: "finished", + Resources: node.ProcessResources{ + CPU: 0, + Mem: 0, + }, Runtime: 42, UpdatedAt: now, Config: &app.Config{ @@ -388,11 +392,13 @@ func TestSynchronizeAddReferenceAffinity(t *testing.T) { have := []node.Process{ { - NodeID: "node2", - Order: "start", - State: "running", - CPU: 12, - Mem: 5, + NodeID: "node2", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 12, + Mem: 5, + }, Runtime: 42, UpdatedAt: now, Config: &app.Config{ @@ -490,11 +496,13 @@ func TestSynchronizeAddReferenceAffinityMultiple(t *testing.T) { have := []node.Process{ { - NodeID: "node2", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node2", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 2, + }, Runtime: 42, UpdatedAt: now, Config: &app.Config{ @@ -882,11 +890,13 @@ func TestSynchronizeRemove(t *testing.T) { have := []node.Process{ { - NodeID: "node2", - Order: "start", - State: "running", - CPU: 12, - Mem: 5, + NodeID: "node2", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 12, + Mem: 5, + }, Runtime: 42, Config: &app.Config{ ID: "foobar", @@ -967,11 +977,13 @@ func TestSynchronizeAddRemove(t *testing.T) { have := []node.Process{ { - NodeID: "node2", - Order: "start", - State: "running", - CPU: 12, - Mem: 5, + NodeID: "node2", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 12, + Mem: 5, + }, Runtime: 42, Config: &app.Config{ ID: "foobar2", @@ -1064,11 +1076,13 @@ func TestSynchronizeNoUpdate(t *testing.T) { have := []node.Process{ { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 12, - Mem: 5, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 12, + Mem: 5, + }, Runtime: 42, Config: &app.Config{ ID: "foobar", @@ -1133,11 +1147,13 @@ func TestSynchronizeUpdate(t *testing.T) { have := []node.Process{ { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 12, - Mem: 5, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 12, + Mem: 5, + }, Runtime: 42, Config: &app.Config{ ID: "foobar", @@ -1217,11 +1233,13 @@ func TestSynchronizeUpdateMetadata(t *testing.T) { have := []node.Process{ { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 12, - Mem: 5, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 12, + Mem: 5, + }, Runtime: 42, Config: &app.Config{ ID: "foobar", @@ -1313,11 +1331,13 @@ func TestSynchronizeWaitDisconnectedNode(t *testing.T) { have := []node.Process{ { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 12, - Mem: 5, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 12, + Mem: 5, + }, Runtime: 42, UpdatedAt: now, Config: &app.Config{ @@ -1397,11 +1417,13 @@ func TestSynchronizeWaitDisconnectedNodeNoWish(t *testing.T) { have := []node.Process{ { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 12, - Mem: 5, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 12, + Mem: 5, + }, Runtime: 42, UpdatedAt: now, Config: &app.Config{ @@ -1493,11 +1515,13 @@ func TestSynchronizeWaitDisconnectedNodeUnrealisticWish(t *testing.T) { have := []node.Process{ { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 12, - Mem: 5, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 12, + Mem: 5, + }, Runtime: 42, UpdatedAt: now, Config: &app.Config{ @@ -1589,11 +1613,13 @@ func TestSynchronizeTimeoutDisconnectedNode(t *testing.T) { have := []node.Process{ { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 12, - Mem: 5, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 12, + Mem: 5, + }, Runtime: 42, UpdatedAt: now, Config: &app.Config{ @@ -1655,22 +1681,26 @@ func TestSynchronizeTimeoutDisconnectedNode(t *testing.T) { func TestRebalanceNothingToDo(t *testing.T) { processes := []node.Process{ { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 35, - Mem: 20, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 35, + Mem: 20, + }, Runtime: 42, Config: &app.Config{ ID: "foobar1", }, }, { - NodeID: "node2", - Order: "start", - State: "running", - CPU: 12, - Mem: 5, + NodeID: "node2", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 12, + Mem: 5, + }, Runtime: 42, Config: &app.Config{ ID: "foobar2", @@ -1711,33 +1741,39 @@ func TestRebalanceNothingToDo(t *testing.T) { func TestRebalanceOverload(t *testing.T) { processes := []node.Process{ { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 35, - Mem: 20, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 35, + Mem: 20, + }, Runtime: 42, Config: &app.Config{ ID: "foobar1", }, }, { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 17, - Mem: 31, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 17, + Mem: 31, + }, Runtime: 27, Config: &app.Config{ ID: "foobar3", }, }, { - NodeID: "node2", - Order: "start", - State: "running", - CPU: 12, - Mem: 5, + NodeID: "node2", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 12, + Mem: 5, + }, Runtime: 42, Config: &app.Config{ ID: "foobar2", @@ -1806,33 +1842,39 @@ func TestRebalanceOverload(t *testing.T) { func TestRebalanceSkip(t *testing.T) { processes := []node.Process{ { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 35, - Mem: 20, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 35, + Mem: 20, + }, Runtime: 42, Config: &app.Config{ ID: "foobar1", }, }, { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 17, - Mem: 31, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 17, + Mem: 31, + }, Runtime: 27, Config: &app.Config{ ID: "foobar3", }, }, { - NodeID: "node2", - Order: "start", - State: "running", - CPU: 12, - Mem: 5, + NodeID: "node2", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 12, + Mem: 5, + }, Runtime: 42, Config: &app.Config{ ID: "foobar2", @@ -1908,22 +1950,26 @@ func TestRebalanceSkip(t *testing.T) { func TestRebalanceReferenceAffinity(t *testing.T) { processes := []node.Process{ { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 42, Config: &app.Config{ ID: "foobar1", }, }, { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 1, Config: &app.Config{ ID: "foobar2", @@ -1931,11 +1977,13 @@ func TestRebalanceReferenceAffinity(t *testing.T) { }, }, { - NodeID: "node2", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node2", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 42, Config: &app.Config{ ID: "foobar3", @@ -1943,11 +1991,13 @@ func TestRebalanceReferenceAffinity(t *testing.T) { }, }, { - NodeID: "node3", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node3", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 42, Config: &app.Config{ ID: "foobar4", @@ -1955,11 +2005,13 @@ func TestRebalanceReferenceAffinity(t *testing.T) { }, }, { - NodeID: "node3", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node3", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 42, Config: &app.Config{ ID: "foobar5", @@ -2048,33 +2100,39 @@ func TestRebalanceReferenceAffinity(t *testing.T) { func TestRebalanceRelocateTarget(t *testing.T) { processes := []node.Process{ { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 35, - Mem: 20, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 35, + Mem: 20, + }, Runtime: 42, Config: &app.Config{ ID: "foobar1", }, }, { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 17, - Mem: 31, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 17, + Mem: 31, + }, Runtime: 27, Config: &app.Config{ ID: "foobar3", }, }, { - NodeID: "node2", - Order: "start", - State: "running", - CPU: 12, - Mem: 5, + NodeID: "node2", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 12, + Mem: 5, + }, Runtime: 42, Config: &app.Config{ ID: "foobar2", @@ -2165,33 +2223,39 @@ func TestRebalanceRelocateTarget(t *testing.T) { func TestRebalanceRelocateAny(t *testing.T) { processes := []node.Process{ { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 35, - Mem: 20, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 35, + Mem: 20, + }, Runtime: 42, Config: &app.Config{ ID: "foobar1", }, }, { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 17, - Mem: 31, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 17, + Mem: 31, + }, Runtime: 27, Config: &app.Config{ ID: "foobar3", }, }, { - NodeID: "node2", - Order: "start", - State: "running", - CPU: 12, - Mem: 5, + NodeID: "node2", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 12, + Mem: 5, + }, Runtime: 42, Config: &app.Config{ ID: "foobar2", @@ -2319,7 +2383,10 @@ func TestFindBestNodesForProcess(t *testing.T) { resources := NewResourcePlanner(nodes) - list := resources.FindBestNodes(35, 20) + list := resources.FindBestNodes(Resources{ + CPU: 35, + Mem: 20, + }) require.Equal(t, []string{"node3", "node2", "node1"}, list) } @@ -2433,7 +2500,10 @@ func TestFindBestNodesForProcess2(t *testing.T) { }, } - list := resources.FindBestNodes(4.0, 45*1024*1024) + list := resources.FindBestNodes(Resources{ + CPU: 4.0, + Mem: 45 * 1024 * 1024, + }) require.Equal(t, []string{"node10", "node8", "node7", "node1", "node5", "node12", "node4", "node3", "node13", "node6", "node11", "node2"}, list) } @@ -2441,11 +2511,13 @@ func TestFindBestNodesForProcess2(t *testing.T) { func TestCreateNodeProcessMap(t *testing.T) { processes := []node.Process{ { - NodeID: "node1", - Order: "start", - State: "finished", - CPU: 1, - Mem: 1, + NodeID: "node1", + Order: "start", + State: "finished", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 1, Config: &app.Config{ ID: "foobar7", @@ -2453,11 +2525,13 @@ func TestCreateNodeProcessMap(t *testing.T) { }, }, { - NodeID: "node1", - Order: "start", - State: "failed", - CPU: 1, - Mem: 1, + NodeID: "node1", + Order: "start", + State: "failed", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 1, Config: &app.Config{ ID: "foobar8", @@ -2465,22 +2539,26 @@ func TestCreateNodeProcessMap(t *testing.T) { }, }, { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 42, Config: &app.Config{ ID: "foobar1", }, }, { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 1, Config: &app.Config{ ID: "foobar2", @@ -2488,11 +2566,13 @@ func TestCreateNodeProcessMap(t *testing.T) { }, }, { - NodeID: "node2", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node2", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 67, Config: &app.Config{ ID: "foobar3", @@ -2500,11 +2580,13 @@ func TestCreateNodeProcessMap(t *testing.T) { }, }, { - NodeID: "node2", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node2", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 42, Config: &app.Config{ ID: "foobar6", @@ -2512,11 +2594,13 @@ func TestCreateNodeProcessMap(t *testing.T) { }, }, { - NodeID: "node3", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node3", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 41, Config: &app.Config{ ID: "foobar4", @@ -2524,11 +2608,13 @@ func TestCreateNodeProcessMap(t *testing.T) { }, }, { - NodeID: "node3", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node3", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 42, Config: &app.Config{ ID: "foobar5", @@ -2542,11 +2628,13 @@ func TestCreateNodeProcessMap(t *testing.T) { require.Equal(t, map[string][]node.Process{ "node1": { { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 1, Config: &app.Config{ ID: "foobar2", @@ -2554,11 +2642,13 @@ func TestCreateNodeProcessMap(t *testing.T) { }, }, { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 42, Config: &app.Config{ ID: "foobar1", @@ -2567,11 +2657,13 @@ func TestCreateNodeProcessMap(t *testing.T) { }, "node2": { { - NodeID: "node2", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node2", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 42, Config: &app.Config{ ID: "foobar6", @@ -2579,11 +2671,13 @@ func TestCreateNodeProcessMap(t *testing.T) { }, }, { - NodeID: "node2", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node2", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 67, Config: &app.Config{ ID: "foobar3", @@ -2593,11 +2687,13 @@ func TestCreateNodeProcessMap(t *testing.T) { }, "node3": { { - NodeID: "node3", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node3", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 41, Config: &app.Config{ ID: "foobar4", @@ -2605,11 +2701,13 @@ func TestCreateNodeProcessMap(t *testing.T) { }, }, { - NodeID: "node3", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node3", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 42, Config: &app.Config{ ID: "foobar5", @@ -2623,22 +2721,26 @@ func TestCreateNodeProcessMap(t *testing.T) { func TestCreateReferenceAffinityNodeMap(t *testing.T) { processes := []node.Process{ { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 42, Config: &app.Config{ ID: "foobar1", }, }, { - NodeID: "node1", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node1", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 1, Config: &app.Config{ ID: "foobar2", @@ -2646,11 +2748,13 @@ func TestCreateReferenceAffinityNodeMap(t *testing.T) { }, }, { - NodeID: "node2", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node2", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 42, Config: &app.Config{ ID: "foobar3", @@ -2658,11 +2762,13 @@ func TestCreateReferenceAffinityNodeMap(t *testing.T) { }, }, { - NodeID: "node2", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node2", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 42, Config: &app.Config{ ID: "foobar3", @@ -2670,11 +2776,13 @@ func TestCreateReferenceAffinityNodeMap(t *testing.T) { }, }, { - NodeID: "node3", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node3", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 42, Config: &app.Config{ ID: "foobar4", @@ -2682,11 +2790,13 @@ func TestCreateReferenceAffinityNodeMap(t *testing.T) { }, }, { - NodeID: "node3", - Order: "start", - State: "running", - CPU: 1, - Mem: 1, + NodeID: "node3", + Order: "start", + State: "running", + Resources: node.ProcessResources{ + CPU: 1, + Mem: 1, + }, Runtime: 42, Config: &app.Config{ ID: "foobar5", diff --git a/cluster/node/core.go b/cluster/node/core.go index 5341db06..9dc87e87 100644 --- a/cluster/node/core.go +++ b/cluster/node/core.go @@ -747,16 +747,62 @@ func (n *Core) MediaGetInfo(prefix, path string) (int64, time.Time, error) { } type Process struct { - NodeID string - Order string - State string + NodeID string + Order string + State string + Resources ProcessResources + Runtime time.Duration + UpdatedAt time.Time + Config *app.Config + Metadata map[string]interface{} +} + +type ProcessResources struct { CPU float64 // Current CPU load of this process, 0-100*ncpu Mem uint64 // Currently consumed memory of this process in bytes + GPU ProcessGPUResources Throttling bool - Runtime time.Duration - UpdatedAt time.Time - Config *app.Config - Metadata map[string]interface{} +} + +type ProcessGPUResources struct { + Index int // GPU number + Usage float64 // Current GPU load, 0-100 + Encoder float64 // Current GPU encoder load, 0-100 + Decoder float64 // Current GPU decoder load, 0-100 + Mem uint64 // Currently consumed GPU memory of this process in bytes +} + +func (p *ProcessResources) Marshal(a *api.ProcessUsage) { + p.Throttling = a.CPU.IsThrottling + + if x, err := a.CPU.Current.Float64(); err == nil { + p.CPU = x + } else { + p.CPU = 0 + } + + p.Mem = a.Memory.Current + + if x, err := a.GPU.Usage.Current.Float64(); err == nil { + p.GPU.Usage = x + } else { + p.GPU.Usage = 0 + } + + if x, err := a.GPU.Encoder.Current.Float64(); err == nil { + p.GPU.Encoder = x + } else { + p.GPU.Encoder = 0 + } + + if x, err := a.GPU.Decoder.Current.Float64(); err == nil { + p.GPU.Decoder = x + } else { + p.GPU.Decoder = 0 + } + + p.GPU.Mem = a.GPU.Memory.Current + p.GPU.Index = a.GPU.Index } func (n *Core) ClusterProcessList() ([]Process, error) { @@ -780,22 +826,16 @@ func (n *Core) ClusterProcessList() ([]Process, error) { p.Config = &api.ProcessConfig{} } - cpu, err := p.State.Resources.CPU.Current.Float64() - if err != nil { - cpu = 0 - } - process := Process{ - NodeID: nodeid, - Order: p.State.Order, - State: p.State.State, - Mem: p.State.Resources.Memory.Current, - CPU: cpu, - Throttling: p.State.Resources.CPU.IsThrottling, - Runtime: time.Duration(p.State.Runtime) * time.Second, - UpdatedAt: time.Unix(p.UpdatedAt, 0), + NodeID: nodeid, + Order: p.State.Order, + State: p.State.State, + Runtime: time.Duration(p.State.Runtime) * time.Second, + UpdatedAt: time.Unix(p.UpdatedAt, 0), } + process.Resources.Marshal(&p.State.Resources) + config, _ := p.Config.Marshal() process.Config = config diff --git a/cluster/node/node.go b/cluster/node/node.go index 078da13a..c1daf191 100644 --- a/cluster/node/node.go +++ b/cluster/node/node.go @@ -138,17 +138,28 @@ type About struct { Resources Resources } +type ResourcesGPU struct { + Mem uint64 // Currently used memory in bytes + MemLimit uint64 // Defined memory limit in bytes + MemTotal uint64 // Total available memory in bytes + Usage float64 // Current general usage, 0-100 + UsageLimit float64 // Defined general usage limit, 0-100 + Encoder float64 // Current encoder usage, 0-100 + Decoder float64 // Current decoder usage, 0-100 +} + type Resources struct { - IsThrottling bool // Whether this core is currently throttling - NCPU float64 // Number of CPU on this node - CPU float64 // Current CPU load, 0-100*ncpu - CPULimit float64 // Defined CPU load limit, 0-100*ncpu - CPUCore float64 // Current CPU load of the core itself, 0-100*ncpu - Mem uint64 // Currently used memory in bytes - MemLimit uint64 // Defined memory limit in bytes - MemTotal uint64 // Total available memory in bytes - MemCore uint64 // Current used memory of the core itself in bytes - Error error // Last error + IsThrottling bool // Whether this core is currently throttling + NCPU float64 // Number of CPU on this node + CPU float64 // Current CPU load, 0-100*ncpu + CPULimit float64 // Defined CPU load limit, 0-100*ncpu + CPUCore float64 // Current CPU load of the core itself, 0-100*ncpu + Mem uint64 // Currently used memory in bytes + MemLimit uint64 // Defined memory limit in bytes + MemTotal uint64 // Total available memory in bytes + MemCore uint64 // Current used memory of the core itself in bytes + GPU []ResourcesGPU // Currently used GPU resources + Error error // Last error } func (n *Node) About() About { @@ -514,6 +525,20 @@ func (n *Node) ping(ctx context.Context, interval time.Duration) { Error: nil, }, } + + if len(about.Resources.GPU) != 0 { + n.nodeAbout.Resources.GPU = make([]ResourcesGPU, len(about.Resources.GPU)) + for i, gpu := range about.Resources.GPU { + n.nodeAbout.Resources.GPU[i].Mem = gpu.Mem + n.nodeAbout.Resources.GPU[i].MemLimit = gpu.MemLimit + n.nodeAbout.Resources.GPU[i].MemTotal = gpu.MemTotal + n.nodeAbout.Resources.GPU[i].Usage = gpu.Usage + n.nodeAbout.Resources.GPU[i].UsageLimit = gpu.UsageLimit + n.nodeAbout.Resources.GPU[i].Encoder = gpu.Encoder + n.nodeAbout.Resources.GPU[i].Decoder = gpu.Decoder + } + } + if len(about.Resources.Error) != 0 { n.nodeAbout.Resources.Error = errors.New(about.Resources.Error) } diff --git a/cluster/resources.go b/cluster/resources.go index 2b5bb2c9..cc81b828 100644 --- a/cluster/resources.go +++ b/cluster/resources.go @@ -4,8 +4,69 @@ import ( "sort" "github.com/datarhei/core/v16/cluster/node" + "github.com/datarhei/core/v16/restream/app" ) +type Resources struct { + CPU float64 // CPU 0-100*ncpu + Mem uint64 // Memoryin bytes + GPU ResourcesGPU // GPU resources +} + +type ResourcesGPU struct { + Index int // GPU number + Usage float64 // GPU general, 0-100 + Encoder float64 // GPU encoder, 0-100 + Decoder float64 // GPU decoder, 0-100 + Mem uint64 // GPU memory in bytes +} + +func ResourcesFromConfig(c *app.Config) Resources { + r := Resources{} + r.MarshalConfig(c) + return r +} + +func ResourcesFromProcess(c node.ProcessResources) Resources { + r := Resources{} + r.MarshalProcess(c) + return r +} + +func (r *Resources) MarshalConfig(c *app.Config) { + r.CPU = c.LimitCPU + r.Mem = c.LimitMemory + r.GPU.Usage = c.LimitGPU.Usage + r.GPU.Encoder = c.LimitGPU.Encoder + r.GPU.Decoder = c.LimitGPU.Decoder + r.GPU.Index = -1 +} + +func (r *Resources) MarshalProcess(c node.ProcessResources) { + r.CPU = c.CPU + r.Mem = c.Mem + r.GPU.Usage = c.GPU.Usage + r.GPU.Encoder = c.GPU.Encoder + r.GPU.Decoder = c.GPU.Decoder + r.GPU.Index = c.GPU.Index +} + +func (r *Resources) HasGPU() bool { + if r.GPU.Usage > 0 || r.GPU.Encoder > 0 || r.GPU.Decoder > 0 || r.GPU.Mem > 0 { + return true + } + + return false +} + +func (r *Resources) DoesFitGPU(g node.ResourcesGPU) bool { + if g.Usage+r.GPU.Usage < g.UsageLimit && g.Encoder+r.GPU.Encoder < g.UsageLimit && g.Decoder+r.GPU.Decoder < g.UsageLimit && g.Mem+r.GPU.Mem < g.MemLimit { + return true + } + + return false +} + type resourcePlanner struct { nodes map[string]node.Resources blocked map[string]struct{} @@ -39,8 +100,8 @@ func (r *resourcePlanner) Throttling(nodeid string, throttling bool) { } // HasNodeEnough returns whether a node has enough resources available for the -// requested cpu and memory consumption. -func (r *resourcePlanner) HasNodeEnough(nodeid string, cpu float64, mem uint64) bool { +// requested cpu, memory, anf gpu consumption. +func (r *resourcePlanner) HasNodeEnough(nodeid string, req Resources) bool { res, hasNode := r.nodes[nodeid] if !hasNode { return false @@ -50,20 +111,39 @@ func (r *resourcePlanner) HasNodeEnough(nodeid string, cpu float64, mem uint64) return false } - if res.Error == nil && res.CPU+cpu < res.CPULimit && res.Mem+mem < res.MemLimit && !res.IsThrottling { - return true + if res.Error != nil || res.IsThrottling { + return false } - return false + if res.CPU+req.CPU >= res.CPULimit || res.Mem+req.Mem >= res.MemLimit { + return false + } + + if req.HasGPU() { + found := false + + for _, g := range res.GPU { + if req.DoesFitGPU(g) { + found = true + break + } + } + + if !found { + return false + } + } + + return true } -// FindBestNodes returns an array of nodeids that can fit the requested cpu and memory requirements. If no +// FindBestNodes returns an array of nodeids that can fit the requested cpu, memory, and gpu requirements. If no // such node is available, an empty array is returned. The array is sorted by the most suitable node first. -func (r *resourcePlanner) FindBestNodes(cpu float64, mem uint64) []string { +func (r *resourcePlanner) FindBestNodes(req Resources) []string { nodes := []string{} for id := range r.nodes { - if r.HasNodeEnough(id, cpu, mem) { + if r.HasNodeEnough(id, req) { nodes = append(nodes, id) } } @@ -81,43 +161,72 @@ func (r *resourcePlanner) FindBestNodes(cpu float64, mem uint64) []string { return nodes } -// Add adds the resources of the node according to the cpu and memory utilization. -func (r *resourcePlanner) Add(nodeid string, cpu float64, mem uint64) { +// Add adds the resources of the node according to the cpu, memory, and gpu utilization. +func (r *resourcePlanner) Add(nodeid string, req Resources) { res, hasRes := r.nodes[nodeid] if !hasRes { return } - res.CPU += cpu - res.Mem += mem + res.CPU += req.CPU + res.Mem += req.Mem + + if req.HasGPU() { + for i, g := range res.GPU { + if req.DoesFitGPU(g) { + g.Usage += req.GPU.Usage + g.Encoder += req.GPU.Encoder + g.Decoder += req.GPU.Decoder + g.Mem += req.GPU.Mem + res.GPU[i] = g + break + } + } + } + r.nodes[nodeid] = res } -// Remove subtracts the resources from the node according to the cpu and memory utilization. -func (r *resourcePlanner) Remove(nodeid string, cpu float64, mem uint64) { +// Remove subtracts the resources from the node according to the cpu, memory, and gpu utilization. +func (r *resourcePlanner) Remove(nodeid string, req Resources) { res, hasRes := r.nodes[nodeid] if !hasRes { return } - res.CPU -= cpu - if res.CPU < 0 { - res.CPU = 0 - } - if mem >= res.Mem { - res.Mem = 0 - } else { - res.Mem -= mem + res.CPU -= min(res.CPU, req.CPU) + res.Mem -= min(res.Mem, req.Mem) + + if req.HasGPU() { + if req.GPU.Index > 0 && req.GPU.Index < len(res.GPU) { + gpu := res.GPU[req.GPU.Index] + gpu.Usage -= min(gpu.Usage, req.GPU.Usage) + gpu.Encoder -= min(gpu.Encoder, req.GPU.Encoder) + gpu.Decoder -= min(gpu.Decoder, req.GPU.Decoder) + gpu.Mem -= min(gpu.Mem, req.GPU.Mem) + res.GPU[req.GPU.Index] = gpu + } } + r.nodes[nodeid] = res } // Move adjusts the resources from the target and source node according to the cpu and memory utilization. -func (r *resourcePlanner) Move(target, source string, cpu float64, mem uint64) { - r.Add(target, cpu, mem) - r.Remove(source, cpu, mem) +func (r *resourcePlanner) Move(target, source string, req Resources) { + r.Add(target, req) + r.Remove(source, req) } func (r *resourcePlanner) Map() map[string]node.Resources { return r.nodes } + +func (r *resourcePlanner) Blocked() []string { + nodes := []string{} + + for nodeid := range r.blocked { + nodes = append(nodes, nodeid) + } + + return nodes +} diff --git a/cluster/resources_test.go b/cluster/resources_test.go new file mode 100644 index 00000000..2f938a31 --- /dev/null +++ b/cluster/resources_test.go @@ -0,0 +1,603 @@ +package cluster + +import ( + "testing" + + "github.com/datarhei/core/v16/cluster/node" + "github.com/stretchr/testify/require" +) + +func TestResources(t *testing.T) { + r := Resources{ + CPU: 1, + Mem: 1, + } + + require.False(t, r.HasGPU()) + + r.GPU = ResourcesGPU{ + Index: 0, + Usage: 1, + Encoder: 0, + Decoder: 0, + Mem: 1, + } + + require.True(t, r.HasGPU()) +} + +func TestResourcePlanner(t *testing.T) { + nodes := map[string]node.About{ + "node1": { + State: "online", + Resources: node.Resources{ + NCPU: 1, + CPU: 7, + Mem: 35, + CPULimit: 90, + MemLimit: 90, + }, + }, + "node2": { + State: "online", + Resources: node.Resources{ + NCPU: 1, + CPU: 85, + Mem: 11, + CPULimit: 90, + MemLimit: 90, + }, + }, + } + + planner := NewResourcePlanner(nodes) + + require.Equal(t, map[string]node.Resources{ + "node1": { + NCPU: 1, + CPU: 7, + Mem: 35, + CPULimit: 90, + MemLimit: 90, + }, + "node2": { + NCPU: 1, + CPU: 85, + Mem: 11, + CPULimit: 90, + MemLimit: 90, + }, + }, planner.Map()) +} + +func TestResourcePlannerBlocked(t *testing.T) { + nodes := map[string]node.About{ + "node1": { + State: "degraded", + Resources: node.Resources{ + NCPU: 1, + CPU: 7, + Mem: 35, + CPULimit: 90, + MemLimit: 90, + }, + }, + "node2": { + State: "online", + Resources: node.Resources{ + NCPU: 1, + CPU: 85, + Mem: 11, + CPULimit: 90, + MemLimit: 90, + }, + }, + } + + planner := NewResourcePlanner(nodes) + + require.Equal(t, []string{"node1"}, planner.Blocked()) +} + +func TestResourcePlannerThrottling(t *testing.T) { + nodes := map[string]node.About{ + "node1": { + State: "online", + Resources: node.Resources{ + NCPU: 1, + CPU: 7, + Mem: 35, + CPULimit: 90, + MemLimit: 90, + }, + }, + "node2": { + State: "online", + Resources: node.Resources{ + NCPU: 1, + CPU: 85, + Mem: 11, + CPULimit: 90, + MemLimit: 90, + }, + }, + } + + planner := NewResourcePlanner(nodes) + + require.True(t, planner.HasNodeEnough("node1", Resources{ + CPU: 30, + Mem: 5, + })) + + planner.Throttling("node1", true) + + require.False(t, planner.HasNodeEnough("node1", Resources{ + CPU: 30, + Mem: 5, + })) + + planner.Throttling("node1", false) + + require.True(t, planner.HasNodeEnough("node1", Resources{ + CPU: 30, + Mem: 5, + })) +} + +func TestRecourcePlannerHasNodeEnough(t *testing.T) { + nodes := map[string]node.About{ + "node1": { + State: "online", + Resources: node.Resources{ + NCPU: 1, + CPU: 7, + Mem: 35, + CPULimit: 90, + MemLimit: 90, + GPU: []node.ResourcesGPU{ + { + Mem: 5, + MemLimit: 90, + Usage: 53, + UsageLimit: 90, + Encoder: 32, + Decoder: 26, + }, + { + Mem: 85, + MemLimit: 90, + Usage: 64, + UsageLimit: 90, + Encoder: 43, + Decoder: 12, + }, + }, + }, + }, + "node2": { + State: "online", + Resources: node.Resources{ + NCPU: 1, + CPU: 85, + Mem: 11, + CPULimit: 90, + MemLimit: 90, + GPU: []node.ResourcesGPU{ + { + Mem: 5, + MemLimit: 90, + Usage: 53, + UsageLimit: 90, + Encoder: 32, + Decoder: 26, + }, + }, + }, + }, + } + + planner := NewResourcePlanner(nodes) + + require.True(t, planner.HasNodeEnough("node1", Resources{ + CPU: 30, + Mem: 5, + })) + + require.False(t, planner.HasNodeEnough("node2", Resources{ + CPU: 30, + Mem: 5, + })) + + require.True(t, planner.HasNodeEnough("node1", Resources{ + CPU: 30, + Mem: 5, + GPU: ResourcesGPU{ + Usage: 0, + Encoder: 0, + Decoder: 0, + Mem: 50, + }, + })) + + require.False(t, planner.HasNodeEnough("node1", Resources{ + CPU: 30, + Mem: 5, + GPU: ResourcesGPU{ + Usage: 0, + Encoder: 0, + Decoder: 0, + Mem: 86, + }, + })) + + require.True(t, planner.HasNodeEnough("node1", Resources{ + CPU: 30, + Mem: 5, + GPU: ResourcesGPU{ + Usage: 0, + Encoder: 50, + Decoder: 0, + Mem: 50, + }, + })) +} + +func TestResourcePlannerAdd(t *testing.T) { + nodes := map[string]node.About{ + "node1": { + State: "online", + Resources: node.Resources{ + NCPU: 1, + CPU: 7, + Mem: 35, + CPULimit: 90, + MemLimit: 90, + }, + }, + } + + planner := NewResourcePlanner(nodes) + + planner.Add("node1", Resources{ + CPU: 42, + Mem: 33, + }) + + require.Equal(t, map[string]node.Resources{ + "node1": { + NCPU: 1, + CPU: 49, + Mem: 68, + CPULimit: 90, + MemLimit: 90, + }, + }, planner.Map()) +} + +func TestResourcePlannerNoGPUAddGPU(t *testing.T) { + nodes := map[string]node.About{ + "node1": { + State: "online", + Resources: node.Resources{ + NCPU: 1, + CPU: 7, + Mem: 35, + CPULimit: 90, + MemLimit: 90, + }, + }, + } + + planner := NewResourcePlanner(nodes) + + planner.Add("node1", Resources{ + CPU: 42, + Mem: 33, + GPU: ResourcesGPU{ + Index: 0, + Usage: 1, + Encoder: 2, + Decoder: 3, + Mem: 4, + }, + }) + + require.Equal(t, map[string]node.Resources{ + "node1": { + NCPU: 1, + CPU: 49, + Mem: 68, + CPULimit: 90, + MemLimit: 90, + }, + }, planner.Map()) +} + +func TestResourcePlannerAddGPU(t *testing.T) { + nodes := map[string]node.About{ + "node1": { + State: "online", + Resources: node.Resources{ + NCPU: 1, + CPU: 7, + Mem: 35, + CPULimit: 90, + MemLimit: 90, + GPU: []node.ResourcesGPU{ + { + Mem: 0, + MemLimit: 0, + Usage: 0, + UsageLimit: 0, + Encoder: 0, + Decoder: 0, + }, + { + Mem: 0, + MemLimit: 100, + Usage: 0, + UsageLimit: 100, + Encoder: 0, + Decoder: 0, + }, + }, + }, + }, + } + + planner := NewResourcePlanner(nodes) + + planner.Add("node1", Resources{ + CPU: 42, + Mem: 33, + GPU: ResourcesGPU{ + Usage: 1, + Encoder: 2, + Decoder: 3, + Mem: 4, + }, + }) + + require.Equal(t, map[string]node.Resources{ + "node1": { + NCPU: 1, + CPU: 49, + Mem: 68, + CPULimit: 90, + MemLimit: 90, + GPU: []node.ResourcesGPU{ + { + Mem: 0, + MemLimit: 0, + Usage: 0, + UsageLimit: 0, + Encoder: 0, + Decoder: 0, + }, + { + Mem: 4, + MemLimit: 100, + Usage: 1, + UsageLimit: 100, + Encoder: 2, + Decoder: 3, + }, + }, + }, + }, planner.Map()) +} + +func TestResourcePlannerRemove(t *testing.T) { + nodes := map[string]node.About{ + "node1": { + State: "online", + Resources: node.Resources{ + NCPU: 1, + CPU: 53, + Mem: 35, + CPULimit: 90, + MemLimit: 90, + }, + }, + } + + planner := NewResourcePlanner(nodes) + + planner.Remove("node1", Resources{ + CPU: 13, + Mem: 20, + }) + + require.Equal(t, map[string]node.Resources{ + "node1": { + NCPU: 1, + CPU: 40, + Mem: 15, + CPULimit: 90, + MemLimit: 90, + }, + }, planner.Map()) +} + +func TestResourcePlannerRemoveTooMuch(t *testing.T) { + nodes := map[string]node.About{ + "node1": { + State: "online", + Resources: node.Resources{ + NCPU: 1, + CPU: 53, + Mem: 35, + CPULimit: 90, + MemLimit: 90, + }, + }, + } + + planner := NewResourcePlanner(nodes) + + planner.Remove("node1", Resources{ + CPU: 100, + Mem: 100, + }) + + require.Equal(t, map[string]node.Resources{ + "node1": { + NCPU: 1, + CPU: 0, + Mem: 0, + CPULimit: 90, + MemLimit: 90, + }, + }, planner.Map()) +} + +func TestResourcePlannerRemoveGPU(t *testing.T) { + nodes := map[string]node.About{ + "node1": { + State: "online", + Resources: node.Resources{ + NCPU: 1, + CPU: 53, + Mem: 35, + CPULimit: 90, + MemLimit: 90, + GPU: []node.ResourcesGPU{ + { + Mem: 4, + MemLimit: 100, + Usage: 1, + UsageLimit: 100, + Encoder: 2, + Decoder: 3, + }, + { + Mem: 23, + MemLimit: 100, + Usage: 43, + UsageLimit: 100, + Encoder: 95, + Decoder: 12, + }, + }, + }, + }, + } + + planner := NewResourcePlanner(nodes) + + planner.Remove("node1", Resources{ + CPU: 13, + Mem: 20, + GPU: ResourcesGPU{ + Index: 1, + Usage: 3, + Encoder: 40, + Decoder: 0, + Mem: 5, + }, + }) + + require.Equal(t, map[string]node.Resources{ + "node1": { + NCPU: 1, + CPU: 40, + Mem: 15, + CPULimit: 90, + MemLimit: 90, + GPU: []node.ResourcesGPU{ + { + Mem: 4, + MemLimit: 100, + Usage: 1, + UsageLimit: 100, + Encoder: 2, + Decoder: 3, + }, + { + Mem: 18, + MemLimit: 100, + Usage: 40, + UsageLimit: 100, + Encoder: 55, + Decoder: 12, + }, + }, + }, + }, planner.Map()) +} + +func TestResourcePlannerRemoveGPUTooMuch(t *testing.T) { + nodes := map[string]node.About{ + "node1": { + State: "online", + Resources: node.Resources{ + NCPU: 1, + CPU: 53, + Mem: 35, + CPULimit: 90, + MemLimit: 90, + GPU: []node.ResourcesGPU{ + { + Mem: 4, + MemLimit: 100, + Usage: 1, + UsageLimit: 100, + Encoder: 2, + Decoder: 3, + }, + { + Mem: 23, + MemLimit: 100, + Usage: 43, + UsageLimit: 100, + Encoder: 95, + Decoder: 12, + }, + }, + }, + }, + } + + planner := NewResourcePlanner(nodes) + + planner.Remove("node1", Resources{ + CPU: 13, + Mem: 20, + GPU: ResourcesGPU{ + Index: 1, + Usage: 100, + Encoder: 100, + Decoder: 100, + Mem: 100, + }, + }) + + require.Equal(t, map[string]node.Resources{ + "node1": { + NCPU: 1, + CPU: 40, + Mem: 15, + CPULimit: 90, + MemLimit: 90, + GPU: []node.ResourcesGPU{ + { + Mem: 4, + MemLimit: 100, + Usage: 1, + UsageLimit: 100, + Encoder: 2, + Decoder: 3, + }, + { + Mem: 0, + MemLimit: 100, + Usage: 0, + UsageLimit: 100, + Encoder: 0, + Decoder: 0, + }, + }, + }, + }, planner.Map()) +} diff --git a/config/config.go b/config/config.go index e0364c03..a878065b 100644 --- a/config/config.go +++ b/config/config.go @@ -306,8 +306,10 @@ func (d *Config) init() { d.vars.Register(value.NewDir(&d.Router.UIPath, "", d.fs), "router.ui_path", "CORE_ROUTER_UI_PATH", nil, "Path to a directory holding UI files mounted as /ui", false, false) // Resources - d.vars.Register(value.NewFloat(&d.Resources.MaxCPUUsage, 0), "resources.max_cpu_usage", "CORE_RESOURCES_MAX_CPU_USAGE", nil, "Maximum system CPU usage in percent, from 0 (no limit) to 100", false, false) - d.vars.Register(value.NewFloat(&d.Resources.MaxMemoryUsage, 0), "resources.max_memory_usage", "CORE_RESOURCES_MAX_MEMORY_USAGE", nil, "Maximum system usage in percent, from 0 (no limit) to 100", false, false) + d.vars.Register(value.NewFloatRange(&d.Resources.MaxCPUUsage, 0, 0, 100), "resources.max_cpu_usage", "CORE_RESOURCES_MAX_CPU_USAGE", nil, "Maximum system CPU usage in percent, from 0 (no limit) to 100", false, false) + d.vars.Register(value.NewFloatRange(&d.Resources.MaxMemoryUsage, 0, 0, 100), "resources.max_memory_usage", "CORE_RESOURCES_MAX_MEMORY_USAGE", nil, "Maximum system usage in percent, from 0 (no limit) to 100", false, false) + d.vars.Register(value.NewFloatRange(&d.Resources.MaxGPUUsage, 0, 0, 100), "resources.max_gpu_usage", "CORE_RESOURCES_MAX_GPU_USAGE", nil, "Maximum general, encoder, and decoder GPU usage in percent per GPU, from 0 (no limit) to 100", false, false) + d.vars.Register(value.NewFloatRange(&d.Resources.MaxGPUMemoryUsage, 0, 0, 100), "resources.max_gpu_memory_usage", "CORE_RESOURCES_MAX_GPU_MEMORY_USAGE", nil, "Maximum GPU memory usage in percent per GPU, from 0 (no limit) to 100", false, false) // Cluster d.vars.Register(value.NewBool(&d.Cluster.Enable, false), "cluster.enable", "CORE_CLUSTER_ENABLE", nil, "Enable cluster mode", false, false) @@ -494,17 +496,6 @@ func (d *Config) Validate(resetLogs bool) { } } - // If resource limits are given, all values must be set - if d.Resources.MaxCPUUsage > 0 || d.Resources.MaxMemoryUsage > 0 { - if d.Resources.MaxCPUUsage <= 0 || d.Resources.MaxCPUUsage > 100 { - d.vars.Log("error", "resources.max_cpu_usage", "must be greater than 0 and smaller or equal to 100") - } - - if d.Resources.MaxMemoryUsage <= 0 { - d.vars.Log("error", "resources.max_memory_usage", "must be greater than 0 and smaller or equal to 100") - } - } - // If cluster mode is enabled, a proper address must be provided if d.Cluster.Enable { if len(d.Cluster.Address) == 0 { diff --git a/config/data.go b/config/data.go index 26c77054..f7f057cf 100644 --- a/config/data.go +++ b/config/data.go @@ -184,8 +184,10 @@ type Data struct { UIPath string `json:"ui_path"` } `json:"router"` Resources struct { - MaxCPUUsage float64 `json:"max_cpu_usage"` // percent 0-100 - MaxMemoryUsage float64 `json:"max_memory_usage"` // percent 0-100 + MaxCPUUsage float64 `json:"max_cpu_usage"` // percent 0-100 + MaxMemoryUsage float64 `json:"max_memory_usage"` // percent 0-100 + MaxGPUUsage float64 `json:"max_gpu_usage"` // percent 0-100 + MaxGPUMemoryUsage float64 `json:"max_gpu_memory_usage"` // percent 0-100 } `json:"resources"` Cluster struct { Enable bool `json:"enable"` diff --git a/config/value/primitives.go b/config/value/primitives.go index 4d1258fd..4c1ae54a 100644 --- a/config/value/primitives.go +++ b/config/value/primitives.go @@ -1,6 +1,7 @@ package value import ( + "fmt" "sort" "strconv" "strings" @@ -310,3 +311,56 @@ func (u *Float64) Validate() error { func (u *Float64) IsEmpty() bool { return float64(*u) == 0 } + +// float64 range + +type Float64Range struct { + p *float64 + from float64 + to float64 +} + +func NewFloatRange(p *float64, val, from, to float64) *Float64Range { + v := &Float64Range{ + p: p, + from: from, + to: to, + } + + *p = val + + return v +} + +func (s *Float64Range) Set(val string) error { + v, err := strconv.ParseFloat(val, 64) + if err != nil { + return err + } + + *s.p = v + + return nil +} + +func (s *Float64Range) String() string { + if s.IsEmpty() { + return "(empty)" + } + + return fmt.Sprintf("%.3f", *s.p) +} + +func (s *Float64Range) Validate() error { + val := *s.p + + if val < s.from || val > s.to { + return fmt.Errorf("value %f is not in range [%f, %f]", val, s.from, s.to) + } + + return nil +} + +func (s *Float64Range) IsEmpty() bool { + return *s.p == 0 +} diff --git a/config/value/primitives_test.go b/config/value/primitives_test.go index 4406d8b0..2ee865ff 100644 --- a/config/value/primitives_test.go +++ b/config/value/primitives_test.go @@ -165,3 +165,29 @@ func TestFloat64Value(t *testing.T) { require.Equal(t, float64(77.7), x) } + +func TestFloat64RangeValue(t *testing.T) { + var x float64 + + val := NewFloatRange(&x, 11.1, 0, 100) + + require.Equal(t, "11.100", val.String()) + require.NoError(t, val.Validate()) + require.Equal(t, false, val.IsEmpty()) + + x = 42.5 + + require.Equal(t, "42.500", val.String()) + require.NoError(t, val.Validate()) + require.Equal(t, false, val.IsEmpty()) + + val.Set("77.7") + + require.Equal(t, float64(77.7), x) + + val.Set("101.9") + + require.Equal(t, "101.900", val.String()) + require.Error(t, val.Validate()) + require.Equal(t, false, val.IsEmpty()) +} diff --git a/ffmpeg/ffmpeg.go b/ffmpeg/ffmpeg.go index 3b1e9710..1b3c96af 100644 --- a/ffmpeg/ffmpeg.go +++ b/ffmpeg/ffmpeg.go @@ -29,23 +29,26 @@ type FFmpeg interface { } type ProcessConfig struct { - Reconnect bool // Whether to reconnect - ReconnectDelay time.Duration // Duration until next reconnect - StaleTimeout time.Duration // Duration to wait until killing the process if there is no progress in the process - Timeout time.Duration // Duration to wait until killing the process - LimitCPU float64 // Kill the process if the CPU usage in percent is above this value. - LimitMemory uint64 // Kill the process if the memory consumption in bytes is above this value. - LimitDuration time.Duration // Kill the process if the limits are exceeded for this duration. - LimitMode string // How to limit the process, "hard" or "soft" - Scheduler string // A scheduler for starting the process, either a concrete date (RFC3339) or in crontab syntax - Args []string // Arguments for the process - Parser process.Parser // Parser for the process output - Logger log.Logger // Logger - OnArgs func([]string) []string // Callback before starting the process to retrieve new arguments - OnBeforeStart func() error // Callback which is called before the process will be started. If error is non-nil, the start will be refused. - OnStart func() // Callback called after process has been started - OnExit func(state string) // Callback called after the process stopped with exit state as argument - OnStateChange func(from, to string) // Callback called on state change + Reconnect bool // Whether to reconnect + ReconnectDelay time.Duration // Duration until next reconnect + StaleTimeout time.Duration // Duration to wait until killing the process if there is no progress in the process + Timeout time.Duration // Duration to wait until killing the process + LimitCPU float64 // Kill the process if the CPU usage in percent is above this value. + LimitMemory uint64 // Kill the process if the memory consumption in bytes is above this value. + LimitGPUUsage float64 // Kill the process id the GPU usage (general) in percent is above this value. + LimitGPUEncoder float64 // Kill the process id the GPU usage (encoder) in percent is above this value. + LimitGPUDecoder float64 // Kill the process id the GPU usage (decoder) in percent is above this value. + LimitGPUMemory uint64 // Kill the process if the GPU memory consumption in bytes is above this value. + LimitDuration time.Duration // Kill the process if the limits are exceeded for this duration. + LimitMode string // How to limit the process, "hard" or "soft" + Scheduler string // A scheduler for starting the process, either a concrete date (RFC3339) or in crontab syntax + Args []string // Arguments for the process + Parser process.Parser // Parser for the process output + Logger log.Logger // Logger + OnBeforeStart func([]string) ([]string, error) // Callback which is called before the process will be started. The string slice is the list of arguments which can be modified. If error is non-nil, the start will be refused. + OnStart func() // Callback called after process has been started + OnExit func(state string) // Callback called after the process stopped with exit state as argument + OnStateChange func(from, to string) // Callback called on state change } // Config is the configuration for ffmpeg that is part of the configuration @@ -138,23 +141,26 @@ func (f *ffmpeg) New(config ProcessConfig) (process.Process, error) { } ffmpeg, err := process.New(process.Config{ - Binary: f.binary, - Args: config.Args, - Reconnect: config.Reconnect, - ReconnectDelay: config.ReconnectDelay, - StaleTimeout: config.StaleTimeout, - Timeout: config.Timeout, - LimitCPU: config.LimitCPU, - LimitMemory: config.LimitMemory, - LimitDuration: config.LimitDuration, - LimitMode: limitMode, - Scheduler: scheduler, - Parser: config.Parser, - Logger: config.Logger, - OnArgs: config.OnArgs, - OnBeforeStart: config.OnBeforeStart, - OnStart: config.OnStart, - OnExit: config.OnExit, + Binary: f.binary, + Args: config.Args, + Reconnect: config.Reconnect, + ReconnectDelay: config.ReconnectDelay, + StaleTimeout: config.StaleTimeout, + Timeout: config.Timeout, + LimitCPU: config.LimitCPU, + LimitMemory: config.LimitMemory, + LimitGPUUsage: config.LimitGPUUsage, + LimitGPUEncoder: config.LimitGPUEncoder, + LimitGPUDecoder: config.LimitGPUDecoder, + LimitGPUMemory: config.LimitGPUMemory, + LimitDuration: config.LimitDuration, + LimitMode: limitMode, + Scheduler: scheduler, + Parser: config.Parser, + Logger: config.Logger, + OnBeforeStart: config.OnBeforeStart, + OnStart: config.OnStart, + OnExit: config.OnExit, OnStateChange: func(from, to string) { f.statesLock.Lock() switch to { diff --git a/ffmpeg/parse/parser.go b/ffmpeg/parse/parser.go index b4912af1..2259159c 100644 --- a/ffmpeg/parse/parser.go +++ b/ffmpeg/parse/parser.go @@ -619,7 +619,7 @@ func (p *parser) Stop(state string, pusage process.Usage) { usage.CPU.Max = pusage.CPU.Max usage.CPU.Limit = pusage.CPU.Limit - usage.Memory.Average = pusage.Memory.Average + usage.Memory.Average = uint64(pusage.Memory.Average) usage.Memory.Max = pusage.Memory.Max usage.Memory.Limit = pusage.Memory.Limit diff --git a/ffmpeg/parse/types.go b/ffmpeg/parse/types.go index a3eb31fc..1c98f6e8 100644 --- a/ffmpeg/parse/types.go +++ b/ffmpeg/parse/types.go @@ -576,6 +576,7 @@ type AVstream struct { type Usage struct { CPU UsageCPU Memory UsageMemory + GPU UsageGPU } type UsageCPU struct { @@ -586,7 +587,27 @@ type UsageCPU struct { } type UsageMemory struct { + Average uint64 + Max uint64 + Limit uint64 +} + +type UsageGPU struct { + Index int + Usage UsageGPUUsage + Encoder UsageGPUUsage + Decoder UsageGPUUsage + Memory UsageGPUMemory +} + +type UsageGPUUsage struct { Average float64 + Max float64 + Limit float64 +} + +type UsageGPUMemory struct { + Average uint64 Max uint64 Limit uint64 } diff --git a/http/api/process.go b/http/api/process.go index baf87707..43a9fce9 100644 --- a/http/api/process.go +++ b/http/api/process.go @@ -155,9 +155,13 @@ type ProcessConfigIOCleanup struct { } type ProcessConfigLimits struct { - CPU float64 `json:"cpu_usage" jsonschema:"minimum=0"` - Memory uint64 `json:"memory_mbytes" jsonschema:"minimum=0" format:"uint64"` - WaitFor uint64 `json:"waitfor_seconds" jsonschema:"minimum=0" format:"uint64"` + CPU float64 `json:"cpu_usage" jsonschema:"minimum=0"` + Memory uint64 `json:"memory_mbytes" jsonschema:"minimum=0" format:"uint64"` + GPUUsage float64 `json:"gpu_usage" jsonschema:"minimum=0"` + GPUEncoder float64 `json:"gpu_encoder" jsonschema:"minimum=0"` + GPUDecoder float64 `json:"gpu_decoder" jsonschema:"minimum=0"` + GPUMemory uint64 `json:"gpu_memory_mbytes" jsonschema:"minimum=0" format:"uint64"` + WaitFor uint64 `json:"waitfor_seconds" jsonschema:"minimum=0" format:"uint64"` } // ProcessConfig represents the configuration of an ffmpeg process @@ -197,7 +201,13 @@ func (cfg *ProcessConfig) Marshal() (*app.Config, map[string]interface{}) { Scheduler: cfg.Scheduler, LimitCPU: cfg.Limits.CPU, LimitMemory: cfg.Limits.Memory * 1024 * 1024, - LimitWaitFor: cfg.Limits.WaitFor, + LimitGPU: app.ConfigLimitGPU{ + Usage: cfg.Limits.GPUUsage, + Encoder: cfg.Limits.GPUEncoder, + Decoder: cfg.Limits.GPUDecoder, + Memory: cfg.Limits.GPUMemory * 1024 * 1024, + }, + LimitWaitFor: cfg.Limits.WaitFor, } cfg.generateInputOutputIDs(cfg.Input) @@ -283,6 +293,10 @@ func (cfg *ProcessConfig) Unmarshal(c *app.Config, metadata map[string]interface cfg.Scheduler = c.Scheduler cfg.Limits.CPU = c.LimitCPU cfg.Limits.Memory = c.LimitMemory / 1024 / 1024 + cfg.Limits.GPUUsage = c.LimitGPU.Usage + cfg.Limits.GPUEncoder = c.LimitGPU.Encoder + cfg.Limits.GPUDecoder = c.LimitGPU.Decoder + cfg.Limits.GPUMemory = c.LimitGPU.Memory / 1024 / 1024 cfg.Limits.WaitFor = c.LimitWaitFor cfg.Options = make([]string, len(c.Options)) @@ -364,20 +378,7 @@ func (s *ProcessState) Unmarshal(state *app.State) { s.Memory = state.Memory s.CPU = json.ToNumber(state.CPU) s.LimitMode = state.LimitMode - s.Resources.CPU = ProcessUsageCPU{ - NCPU: json.ToNumber(state.Resources.CPU.NCPU), - Current: json.ToNumber(state.Resources.CPU.Current), - Average: json.ToNumber(state.Resources.CPU.Average), - Max: json.ToNumber(state.Resources.CPU.Max), - Limit: json.ToNumber(state.Resources.CPU.Limit), - IsThrottling: state.Resources.CPU.IsThrottling, - } - s.Resources.Memory = ProcessUsageMemory{ - Current: state.Resources.Memory.Current, - Average: json.ToNumber(state.Resources.Memory.Average), - Max: state.Resources.Memory.Max, - Limit: state.Resources.Memory.Limit, - } + s.Resources.Unmarshal(&state.Resources) s.Command = state.Command s.Progress.Unmarshal(&state.Progress) @@ -430,15 +431,15 @@ func (p *ProcessUsageCPU) Marshal() app.ProcessUsageCPU { } type ProcessUsageMemory struct { - Current uint64 `json:"cur" format:"uint64"` - Average json.Number `json:"avg" swaggertype:"number" jsonschema:"type=number"` - Max uint64 `json:"max" format:"uint64"` - Limit uint64 `json:"limit" format:"uint64"` + Current uint64 `json:"cur" format:"uint64"` + Average uint64 `json:"avg" format:"uint64"` + Max uint64 `json:"max" format:"uint64"` + Limit uint64 `json:"limit" format:"uint64"` } func (p *ProcessUsageMemory) Unmarshal(pp *app.ProcessUsageMemory) { p.Current = pp.Current - p.Average = json.ToNumber(pp.Average) + p.Average = pp.Average p.Max = pp.Max p.Limit = pp.Limit } @@ -446,31 +447,120 @@ func (p *ProcessUsageMemory) Unmarshal(pp *app.ProcessUsageMemory) { func (p *ProcessUsageMemory) Marshal() app.ProcessUsageMemory { pp := app.ProcessUsageMemory{ Current: p.Current, + Average: p.Average, Max: p.Max, Limit: p.Limit, } + return pp +} + +type ProcessUsageGPUMemory struct { + Current uint64 `json:"cur" format:"uint64"` + Average uint64 `json:"avg" format:"uint64"` + Max uint64 `json:"max" format:"uint64"` + Limit uint64 `json:"limit" format:"uint64"` +} + +func (p *ProcessUsageGPUMemory) Unmarshal(pp *app.ProcessUsageGPUMemory) { + p.Current = pp.Current + p.Average = pp.Average + p.Max = pp.Max + p.Limit = pp.Limit +} + +func (p *ProcessUsageGPUMemory) Marshal() app.ProcessUsageGPUMemory { + pp := app.ProcessUsageGPUMemory{ + Current: p.Current, + Average: p.Average, + Max: p.Max, + Limit: p.Limit, + } + + return pp +} + +type ProcessUsageGPUUsage struct { + Current json.Number `json:"cur" swaggertype:"number" jsonschema:"type=number"` + Average json.Number `json:"avg" swaggertype:"number" jsonschema:"type=number"` + Max json.Number `json:"max" swaggertype:"number" jsonschema:"type=number"` + Limit json.Number `json:"limit" swaggertype:"number" jsonschema:"type=number"` +} + +func (p *ProcessUsageGPUUsage) Unmarshal(pp *app.ProcessUsageGPUUsage) { + p.Current = json.ToNumber(pp.Current) + p.Average = json.ToNumber(pp.Average) + p.Max = json.ToNumber(pp.Max) + p.Limit = json.ToNumber(pp.Limit) +} + +func (p *ProcessUsageGPUUsage) Marshal() app.ProcessUsageGPUUsage { + pp := app.ProcessUsageGPUUsage{} + + if x, err := p.Current.Float64(); err == nil { + pp.Current = x + } + if x, err := p.Average.Float64(); err == nil { pp.Average = x } + if x, err := p.Max.Float64(); err == nil { + pp.Max = x + } + + if x, err := p.Limit.Float64(); err == nil { + pp.Limit = x + } + + return pp +} + +type ProcessUsageGPU struct { + Index int `json:"index"` + Memory ProcessUsageGPUMemory `json:"memory_bytes"` + Usage ProcessUsageGPUUsage `json:"usage"` + Encoder ProcessUsageGPUUsage `json:"encoder"` + Decoder ProcessUsageGPUUsage `json:"decoder"` +} + +func (p *ProcessUsageGPU) Unmarshal(pp *app.ProcessUsageGPU) { + p.Index = pp.Index + p.Memory.Unmarshal(&pp.Memory) + p.Usage.Unmarshal(&pp.Usage) + p.Encoder.Unmarshal(&pp.Encoder) + p.Decoder.Unmarshal(&pp.Decoder) +} + +func (p *ProcessUsageGPU) Marshal() app.ProcessUsageGPU { + pp := app.ProcessUsageGPU{ + Index: p.Index, + Memory: p.Memory.Marshal(), + Usage: p.Usage.Marshal(), + Encoder: p.Encoder.Marshal(), + Decoder: p.Decoder.Marshal(), + } + return pp } type ProcessUsage struct { CPU ProcessUsageCPU `json:"cpu_usage"` Memory ProcessUsageMemory `json:"memory_bytes"` + GPU ProcessUsageGPU `json:"gpu"` } func (p *ProcessUsage) Unmarshal(pp *app.ProcessUsage) { p.CPU.Unmarshal(&pp.CPU) p.Memory.Unmarshal(&pp.Memory) + p.GPU.Unmarshal(&pp.GPU) } func (p *ProcessUsage) Marshal() app.ProcessUsage { pp := app.ProcessUsage{ CPU: p.CPU.Marshal(), Memory: p.Memory.Marshal(), + GPU: p.GPU.Marshal(), } return pp diff --git a/http/api/process_test.go b/http/api/process_test.go index 6dddce39..ddbdfbf8 100644 --- a/http/api/process_test.go +++ b/http/api/process_test.go @@ -56,6 +56,33 @@ func TestProcessUsage(t *testing.T) { Max: 150, Limit: 200, }, + GPU: app.ProcessUsageGPU{ + Index: 3, + Memory: app.ProcessUsageGPUMemory{ + Current: 48, + Average: 43, + Max: 88, + Limit: 34, + }, + Usage: app.ProcessUsageGPUUsage{ + Current: 47, + Average: 22, + Max: 90, + Limit: 80, + }, + Encoder: app.ProcessUsageGPUUsage{ + Current: 48, + Average: 46, + Max: 74, + Limit: 46, + }, + Decoder: app.ProcessUsageGPUUsage{ + Current: 21, + Average: 42, + Max: 30, + Limit: 99, + }, + }, } p := ProcessUsage{} @@ -103,7 +130,13 @@ func TestProcessConfig(t *testing.T) { LogPatterns: []string{"bla", "blubb"}, LimitCPU: 10, LimitMemory: 100 * 1024 * 1024, - LimitWaitFor: 20, + LimitGPU: app.ConfigLimitGPU{ + Usage: 50, + Encoder: 90, + Decoder: 80, + Memory: 24 * 1024 * 1024 * 1024, + }, + LimitWaitFor: 20, } p := ProcessConfig{} diff --git a/internal/.gitignore b/internal/.gitignore index 9872bd8c..ad8efa9c 100644 --- a/internal/.gitignore +++ b/internal/.gitignore @@ -2,4 +2,5 @@ testhelper/ignoresigint/ignoresigint testhelper/sigint/sigint testhelper/sigintwait/sigintwait testhelper/sigpropagate/sigpropagate -testhelper/ffmpeg/ffmpeg \ No newline at end of file +testhelper/ffmpeg/ffmpeg +testhelper/nvidia-smi/nvidia-smi \ No newline at end of file diff --git a/internal/testhelper/nvidia-smi/nvidia-smi.go b/internal/testhelper/nvidia-smi/nvidia-smi.go new file mode 100644 index 00000000..36f6a78c --- /dev/null +++ b/internal/testhelper/nvidia-smi/nvidia-smi.go @@ -0,0 +1,973 @@ +package main + +import ( + "context" + "fmt" + "os" + "os/signal" + "time" +) + +var pmondata = `# gpu pid type sm mem enc dec fb command +# Idx # C/G % % % % MB name + 0 7372 C 2 0 2 - 136 ffmpeg + 0 12176 C 5 2 3 7 782 ffmpeg + 1 20035 C 8 2 4 1 1145 ffmpeg + 1 20141 C 2 1 1 3 429 ffmpeg + 0 29591 C 2 1 - 2 435 ffmpeg ` + +var querydata = ` + + + Mon Jul 15 13:41:56 2024 + 555.42.06 + 12.5 + 2 + + NVIDIA L4 + NVIDIA + Ada Lovelace + Enabled + Disabled + Disabled + None + + N/A + N/A + + + None + + Disabled + 4000 + + N/A + N/A + + 1654523003308 + GPU-c5533cd4-5a60-059e-348d-b6d7466932e4 + 1 + 95.04.29.00.06 + No + 0x100 + 900-2G193-0000-001 + 27B8-895-A1 + N/A + 1 + + G193.0200.00.01 + 2.1 + 6.16 + N/A + + + N/A + N/A + + + N/A + N/A + + N/A + + None + N/A + N/A + + + No + N/A + + 555.42.06 + + N/A + + + 01 + 00 + 0000 + 3 + 2 + 27B810DE + 00000000:01:00.0 + 16CA10DE + + + 4 + 4 + 4 + 4 + 5 + + + 16x + 16x + + + + N/A + N/A + + 0 + 0 + 0 KB/s + 0 KB/s + N/A + N/A + + N/A + P0 + + Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + + N/A + + 23034 MiB + 434 MiB + 1 MiB + 22601 MiB + + + 32768 MiB + 1 MiB + 32767 MiB + + + 0 MiB + 0 MiB + 0 MiB + + Default + + 2 % + 0 % + 0 % + 0 % + 0 % + 0 % + + + 0 + 0 + 0 + + + 0 + 0 + 0 + + + Enabled + Enabled + + + + 0 + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + No + + + 0 + 0 + 0 + 0 + 0 + + + + + N/A + N/A + + + N/A + N/A + + N/A + N/A + + + 0 + 0 + No + No + + 96 bank(s) + 0 bank(s) + 0 bank(s) + 0 bank(s) + 0 bank(s) + + + + 45 C + 39 C + -5 C + -2 C + 0 C + N/A + N/A + N/A + + + N/A + N/A + + + P0 + 27.22 W + 72.00 W + 72.00 W + 72.00 W + 40.00 W + 72.00 W + + + N/A + + + P0 + N/A + N/A + N/A + N/A + N/A + N/A + + + 2040 MHz + 2040 MHz + 6250 MHz + 1770 MHz + + + 2040 MHz + 6251 MHz + + + 2040 MHz + 6251 MHz + + + N/A + + + 2040 MHz + 2040 MHz + 6251 MHz + 1770 MHz + + + 2040 MHz + + + N/A + N/A + + + 885.000 mV + + + N/A + N/A + N/A + N/A + + N/A + + + + + 6251 MHz + 2040 MHz + 2025 MHz + 2010 MHz + 1995 MHz + 1980 MHz + 1965 MHz + 1950 MHz + 1935 MHz + 1920 MHz + 1905 MHz + 1890 MHz + 1875 MHz + 1860 MHz + 1845 MHz + 1830 MHz + 1815 MHz + 1800 MHz + 1785 MHz + 1770 MHz + 1755 MHz + 1740 MHz + 1725 MHz + 1710 MHz + 1695 MHz + 1680 MHz + 1665 MHz + 1650 MHz + 1635 MHz + 1620 MHz + 1605 MHz + 1590 MHz + 1575 MHz + 1560 MHz + 1545 MHz + 1530 MHz + 1515 MHz + 1500 MHz + 1485 MHz + 1470 MHz + 1455 MHz + 1440 MHz + 1425 MHz + 1410 MHz + 1395 MHz + 1380 MHz + 1365 MHz + 1350 MHz + 1335 MHz + 1320 MHz + 1305 MHz + 1290 MHz + 1275 MHz + 1260 MHz + 1245 MHz + 1230 MHz + 1215 MHz + 1200 MHz + 1185 MHz + 1170 MHz + 1155 MHz + 1140 MHz + 1125 MHz + 1110 MHz + 1095 MHz + 1080 MHz + 1065 MHz + 1050 MHz + 1035 MHz + 1020 MHz + 1005 MHz + 990 MHz + 975 MHz + 960 MHz + 945 MHz + 930 MHz + 915 MHz + 900 MHz + 885 MHz + 870 MHz + 855 MHz + 840 MHz + 825 MHz + 810 MHz + 795 MHz + 780 MHz + 765 MHz + 750 MHz + 735 MHz + 720 MHz + 705 MHz + 690 MHz + 675 MHz + 660 MHz + 645 MHz + 630 MHz + 615 MHz + 600 MHz + 585 MHz + 570 MHz + 555 MHz + 540 MHz + 525 MHz + 510 MHz + 495 MHz + 480 MHz + 465 MHz + 450 MHz + 435 MHz + 420 MHz + 405 MHz + 390 MHz + 375 MHz + 360 MHz + 345 MHz + 330 MHz + 315 MHz + 300 MHz + 285 MHz + 270 MHz + 255 MHz + 240 MHz + 225 MHz + 210 MHz + + + 405 MHz + 645 MHz + 630 MHz + 615 MHz + 600 MHz + 585 MHz + 570 MHz + 555 MHz + 540 MHz + 525 MHz + 510 MHz + 495 MHz + 480 MHz + 465 MHz + 450 MHz + 435 MHz + 420 MHz + 405 MHz + 390 MHz + 375 MHz + 360 MHz + 345 MHz + 330 MHz + 315 MHz + 300 MHz + 285 MHz + 270 MHz + 255 MHz + 240 MHz + 225 MHz + 210 MHz + + + + + 10131 + C + ffmpeg + 389 MiB + + + 13597 + C + ffmpeg + 1054 MiB + + + + + + disabled + + + + + NVIDIA L4 + NVIDIA + Ada Lovelace + Enabled + Disabled + Disabled + None + + N/A + N/A + + + None + + Disabled + 4000 + + N/A + N/A + + 1654523001128 + GPU-128ab6fb-6ec9-fd74-b479-4a5fd14f55bd + 0 + 95.04.29.00.06 + No + 0xc100 + 900-2G193-0000-001 + 27B8-895-A1 + N/A + 1 + + G193.0200.00.01 + 2.1 + 6.16 + N/A + + + N/A + N/A + + + N/A + N/A + + N/A + + None + N/A + N/A + + + No + N/A + + 555.42.06 + + N/A + + + C1 + 00 + 0000 + 3 + 2 + 27B810DE + 00000000:C1:00.0 + 16CA10DE + + + 4 + 4 + 4 + 4 + 5 + + + 16x + 1x + + + + N/A + N/A + + 0 + 0 + 0 KB/s + 0 KB/s + N/A + N/A + + N/A + P0 + + Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + Not Active + + N/A + + 23034 MiB + 434 MiB + 1 MiB + 22601 MiB + + + 32768 MiB + 1 MiB + 32767 MiB + + + 0 MiB + 0 MiB + 0 MiB + + Default + + 3 % + 0 % + 0 % + 0 % + 0 % + 0 % + + + 0 + 0 + 0 + + + 0 + 0 + 0 + + + Enabled + Enabled + + + + 0 + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + No + + + 0 + 0 + 0 + 0 + 0 + + + + + N/A + N/A + + + N/A + N/A + + N/A + N/A + + + 0 + 0 + No + No + + 96 bank(s) + 0 bank(s) + 0 bank(s) + 0 bank(s) + 0 bank(s) + + + + 40 C + 43 C + -5 C + -2 C + 0 C + N/A + N/A + N/A + + + N/A + N/A + + + P0 + 29.54 W + 72.00 W + 72.00 W + 72.00 W + 40.00 W + 72.00 W + + + N/A + + + P0 + N/A + N/A + N/A + N/A + N/A + N/A + + + 2040 MHz + 2040 MHz + 6250 MHz + 1770 MHz + + + 2040 MHz + 6251 MHz + + + 2040 MHz + 6251 MHz + + + N/A + + + 2040 MHz + 2040 MHz + 6251 MHz + 1770 MHz + + + 2040 MHz + + + N/A + N/A + + + 910.000 mV + + + N/A + N/A + N/A + N/A + + N/A + + + + + 6251 MHz + 2040 MHz + 2025 MHz + 2010 MHz + 1995 MHz + 1980 MHz + 1965 MHz + 1950 MHz + 1935 MHz + 1920 MHz + 1905 MHz + 1890 MHz + 1875 MHz + 1860 MHz + 1845 MHz + 1830 MHz + 1815 MHz + 1800 MHz + 1785 MHz + 1770 MHz + 1755 MHz + 1740 MHz + 1725 MHz + 1710 MHz + 1695 MHz + 1680 MHz + 1665 MHz + 1650 MHz + 1635 MHz + 1620 MHz + 1605 MHz + 1590 MHz + 1575 MHz + 1560 MHz + 1545 MHz + 1530 MHz + 1515 MHz + 1500 MHz + 1485 MHz + 1470 MHz + 1455 MHz + 1440 MHz + 1425 MHz + 1410 MHz + 1395 MHz + 1380 MHz + 1365 MHz + 1350 MHz + 1335 MHz + 1320 MHz + 1305 MHz + 1290 MHz + 1275 MHz + 1260 MHz + 1245 MHz + 1230 MHz + 1215 MHz + 1200 MHz + 1185 MHz + 1170 MHz + 1155 MHz + 1140 MHz + 1125 MHz + 1110 MHz + 1095 MHz + 1080 MHz + 1065 MHz + 1050 MHz + 1035 MHz + 1020 MHz + 1005 MHz + 990 MHz + 975 MHz + 960 MHz + 945 MHz + 930 MHz + 915 MHz + 900 MHz + 885 MHz + 870 MHz + 855 MHz + 840 MHz + 825 MHz + 810 MHz + 795 MHz + 780 MHz + 765 MHz + 750 MHz + 735 MHz + 720 MHz + 705 MHz + 690 MHz + 675 MHz + 660 MHz + 645 MHz + 630 MHz + 615 MHz + 600 MHz + 585 MHz + 570 MHz + 555 MHz + 540 MHz + 525 MHz + 510 MHz + 495 MHz + 480 MHz + 465 MHz + 450 MHz + 435 MHz + 420 MHz + 405 MHz + 390 MHz + 375 MHz + 360 MHz + 345 MHz + 330 MHz + 315 MHz + 300 MHz + 285 MHz + 270 MHz + 255 MHz + 240 MHz + 225 MHz + 210 MHz + + + 405 MHz + 645 MHz + 630 MHz + 615 MHz + 600 MHz + 585 MHz + 570 MHz + 555 MHz + 540 MHz + 525 MHz + 510 MHz + 495 MHz + 480 MHz + 465 MHz + 450 MHz + 435 MHz + 420 MHz + 405 MHz + 390 MHz + 375 MHz + 360 MHz + 345 MHz + 330 MHz + 315 MHz + 300 MHz + 285 MHz + 270 MHz + 255 MHz + 240 MHz + 225 MHz + 210 MHz + + + + + 16870 + C + ffmpeg + 549 MiB + + + + + + disabled + + + +` + +func main() { + if len(os.Args) == 1 { + os.Exit(1) + } + + ctx, cancel := context.WithCancel(context.Background()) + + if os.Args[1] == "pmon" { + go func(ctx context.Context) { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + fmt.Fprintf(os.Stdout, "%s\n", pmondata) + } + } + }(ctx) + } else { + go func(ctx context.Context) { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + fmt.Fprintf(os.Stdout, "%s\n", querydata) + } + } + }(ctx) + } + + // Wait for interrupt signal to gracefully shutdown the app + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + + cancel() + + os.Exit(0) +} diff --git a/monitor/cpu.go b/monitor/cpu.go index 83869653..8a10850a 100644 --- a/monitor/cpu.go +++ b/monitor/cpu.go @@ -33,7 +33,7 @@ func NewCPUCollector(rsc resources.Resources) metric.Collector { c.limitDescr = metric.NewDesc("cpu_limit", "Percentage of CPU to be consumed", nil) c.throttleDescr = metric.NewDesc("cpu_throttling", "Whether the CPU is currently throttled", nil) - if ncpu, err := psutil.CPUCounts(true); err == nil { + if ncpu, err := psutil.CPUCounts(); err == nil { c.ncpu = ncpu } @@ -63,11 +63,11 @@ func (c *cpuCollector) Collect() metric.Metrics { metrics.Add(metric.NewValue(c.ncpuDescr, c.ncpu)) - limit, _ := c.resources.Limits() + limit, _, _, _ := c.resources.Limits() metrics.Add(metric.NewValue(c.limitDescr, limit)) - cpu, _ := c.resources.ShouldLimit() + cpu, _, _ := c.resources.ShouldLimit() throttling := .0 if cpu { throttling = 1 diff --git a/monitor/disk.go b/monitor/disk.go index 7e1ba86d..fda2f24d 100644 --- a/monitor/disk.go +++ b/monitor/disk.go @@ -37,7 +37,7 @@ func (c *diskCollector) Describe() []*metric.Description { func (c *diskCollector) Collect() metric.Metrics { metrics := metric.NewMetrics() - stat, err := psutil.DiskUsage(c.path) + stat, err := psutil.Disk(c.path) if err != nil { return metrics } diff --git a/monitor/mem.go b/monitor/mem.go index 10a66f7f..986b2be5 100644 --- a/monitor/mem.go +++ b/monitor/mem.go @@ -44,11 +44,11 @@ func (c *memCollector) Describe() []*metric.Description { func (c *memCollector) Collect() metric.Metrics { metrics := metric.NewMetrics() - _, limit := c.resources.Limits() + _, limit, _, _ := c.resources.Limits() metrics.Add(metric.NewValue(c.limitDescr, float64(limit))) - _, memory := c.resources.ShouldLimit() + _, memory, _ := c.resources.ShouldLimit() throttling := .0 if memory { throttling = 1 @@ -56,7 +56,7 @@ func (c *memCollector) Collect() metric.Metrics { metrics.Add(metric.NewValue(c.throttleDescr, throttling)) - stat, err := psutil.VirtualMemory() + stat, err := psutil.Memory() if err != nil { return metrics } diff --git a/monitor/net.go b/monitor/net.go index 87b2b8a3..270e0948 100644 --- a/monitor/net.go +++ b/monitor/net.go @@ -33,7 +33,7 @@ func (c *netCollector) Describe() []*metric.Description { func (c *netCollector) Collect() metric.Metrics { metrics := metric.NewMetrics() - devs, err := psutil.NetIOCounters(true) + devs, err := psutil.Network() if err != nil { return metrics } diff --git a/process/limiter.go b/process/limiter.go index ea5df9c2..699294dc 100644 --- a/process/limiter.go +++ b/process/limiter.go @@ -25,9 +25,36 @@ type Usage struct { Max uint64 // bytes Limit uint64 // bytes } + GPU struct { + Index int // number of the GPU + Memory struct { + Current uint64 // bytes + Average float64 // bytes + Max uint64 // bytes + Limit uint64 // bytes + } + Usage struct { + Current float64 // percent 0-100 + Average float64 // percent 0-100 + Max float64 // percent 0-100 + Limit float64 // percent 0-100 + } + Encoder struct { + Current float64 // percent 0-100 + Average float64 // percent 0-100 + Max float64 // percent 0-100 + Limit float64 // percent 0-100 + } + Decoder struct { + Current float64 // percent 0-100 + Average float64 // percent 0-100 + Max float64 // percent 0-100 + Limit float64 // percent 0-100 + } + } } -type LimitFunc func(cpu float64, memory uint64) +type LimitFunc func(cpu float64, memory uint64, gpuusage, gpuencoder, gpudecoder float64, gpumemory uint64) type LimitMode int @@ -44,18 +71,22 @@ func (m LimitMode) String() string { } const ( - LimitModeHard LimitMode = 0 // Killing the process if either CPU or memory is above the limit for a certain time - LimitModeSoft LimitMode = 1 // Throttling the CPU if activated, killing the process if memory is above the limit for a certain time + LimitModeHard LimitMode = 0 // Killing the process if either resource is above the limit for a certain time. + LimitModeSoft LimitMode = 1 // If activated, will throttle the CPU, otherwise killing the process if resources are above the limit. ) type LimiterConfig struct { - CPU float64 // Max. CPU usage in percent 0-100 in hard mode, 0-100*ncpu in softmode - Memory uint64 // Max. memory usage in bytes - WaitFor time.Duration // Duration for one of the limits has to be above the limit until OnLimit gets triggered - OnLimit LimitFunc // Function to be triggered if limits are exceeded - Mode LimitMode // How to limit CPU usage - PSUtil psutil.Util - Logger log.Logger + CPU float64 // Max. CPU usage in percent 0-100 in hard mode, 0-100*ncpu in soft mode. + Memory uint64 // Max. memory usage in bytes. + GPUUsage float64 // Max. GPU general usage in percent 0-100. + GPUEncoder float64 // Max. GPU encoder usage in percent 0-100. + GPUDecoder float64 // Max. GPU decoder usage in percent 0-100. + GPUMemory uint64 // Max. GPU memory usage in bytes. + WaitFor time.Duration // Duration for one of the limits has to be above the limit until OnLimit gets triggered. + OnLimit LimitFunc // Function to be triggered if limits are exceeded. + Mode LimitMode // How to limit CPU usage. + PSUtil psutil.Util + Logger log.Logger } type Limiter interface { @@ -65,26 +96,135 @@ type Limiter interface { // Stop stops the limiter. The limiter can be reused by calling Start() again Stop() - // Current returns the current CPU and memory values - // Deprecated: use Usage() - Current() (cpu float64, memory uint64) - - // Limits returns the defined CPU and memory limits. Values <= 0 means no limit - // Deprecated: use Usage() - Limits() (cpu float64, memory uint64) - // Usage returns the current state of the limiter, such as current, average, max, and // limit values for CPU and memory. Usage() Usage // Limit enables or disables the throttling of the CPU or killing because of to much - // memory consumption. - Limit(cpu, memory bool) error + // memory or GPU consumption. + Limit(cpu, memory, gpu bool) error // Mode returns in which mode the limiter is running in. Mode() LimitMode } +type numbers interface { + ~uint64 | ~float64 +} + +type metric[T numbers] struct { + limit T // Limit + current T // Current load value + last T // Last load value + max T // Max. load value + top T // Decaying max. load value + avg float64 // Average load value + avgCounter uint64 // Counter for average calculation + limitSince time.Time // Time when the limit has been reached (hard limiter mode) + limitEnable bool +} + +func (x *metric[T]) Reset() { + var zero T + + x.current = zero + x.last = zero + x.max = zero + x.top = zero + x.avg = 0 + x.avgCounter = 0 + x.limitEnable = false +} + +func (x *metric[T]) Current() T { + return x.current +} + +func (x *metric[T]) Top() T { + return x.top +} + +func (x *metric[T]) Max() T { + return x.max +} + +func (x *metric[T]) Avg() float64 { + return x.avg +} + +func (x *metric[T]) SetLimit(limit T) { + x.limit = limit +} + +func (x *metric[T]) Limit() T { + return x.limit +} + +func (x *metric[T]) DoLimit(limit bool) (enabled, changed bool) { + if x.limitEnable != limit { + x.limitEnable = limit + changed = true + } + + enabled = x.limitEnable + + return +} + +func (x *metric[T]) IsLimitEnabled() bool { + return x.limitEnable +} + +func (x *metric[T]) Update(value T) { + x.last, x.current = x.current, value + + if x.current > x.max { + x.max = x.current + } + + if x.current > x.top { + x.top = x.current + } else { + x.top = T(float64(x.top) * 0.95) + } + + x.avgCounter++ + + x.avg = ((x.avg * float64(x.avgCounter-1)) + float64(x.current)) / float64(x.avgCounter) +} + +func (x *metric[T]) IsExceeded(waitFor time.Duration, mode LimitMode) bool { + if x.limit <= 0 { + return false + } + + if mode == LimitModeSoft { + // Check if we actually should limit. + if !x.limitEnable { + return false + } + + // If we are currently above the limit, the limit is exceeded. + if x.current > x.limit { + return true + } + } else { + if x.current > x.limit { + // Current value is higher than the limit. + if x.last <= x.limit { + // If the previous value is below the limit, then we reached the limit as of now. + x.limitSince = time.Now() + } + + if time.Since(x.limitSince) >= waitFor { + return true + } + } + } + + return false +} + type limiter struct { psutil psutil.Util @@ -98,40 +238,27 @@ type limiter struct { lastUsage Usage lastUsageLock sync.RWMutex - cpu float64 // CPU limit - cpuCurrent float64 // Current CPU load of this process - cpuLast float64 // Last CPU load of this process - cpuMax float64 // Max. CPU load of this process - cpuTop float64 // Decaying max. CPU load of this process - cpuAvg float64 // Average CPU load of this process - cpuAvgCounter uint64 // Counter for average calculation - cpuLimitSince time.Time // Time when the CPU limit has been reached (hard limiter mode) - cpuLimitEnable bool // Whether CPU throttling is enabled (soft limiter mode) - cpuThrottling bool // Whether CPU throttling is currently active (soft limiter mode) - - memory uint64 // Memory limit (bytes) - memoryCurrent uint64 // Current memory usage - memoryLast uint64 // Last memory usage - memoryMax uint64 // Max. memory usage - memoryTop uint64 // Decaying max. memory usage - memoryAvg float64 // Average memory usage - memoryAvgCounter uint64 // Counter for average memory calculation - memoryLimitSince time.Time // Time when the memory limit has been reached (hard limiter mode) - memoryLimitEnable bool // Whether memory limiting is enabled (soft limiter mode) + cpu metric[float64] // CPU limit + cpuThrottling bool // Whether CPU throttling is currently active (soft limiter mode) + + memory metric[uint64] // Memory limit (bytes) + + gpu struct { + memory metric[uint64] // GPU memory limit (0-100 percent) + usage metric[float64] // GPU load limit (0-100 percent) + encoder metric[float64] // GPU encoder limit (0-100 percent) + decoder metric[float64] // GPU decoder limit (0-100 percent) + } waitFor time.Duration mode LimitMode - cancelLimit context.CancelFunc - logger log.Logger } // NewLimiter returns a new Limiter func NewLimiter(config LimiterConfig) Limiter { l := &limiter{ - cpu: config.CPU, - memory: config.Memory, waitFor: config.WaitFor, onLimit: config.OnLimit, mode: config.Mode, @@ -139,6 +266,13 @@ func NewLimiter(config LimiterConfig) Limiter { logger: config.Logger, } + l.cpu.SetLimit(config.CPU / 100) + l.memory.SetLimit(config.Memory) + l.gpu.memory.SetLimit(config.GPUMemory) + l.gpu.usage.SetLimit(config.GPUUsage / 100) + l.gpu.encoder.SetLimit(config.GPUEncoder / 100) + l.gpu.decoder.SetLimit(config.GPUDecoder / 100) + if l.logger == nil { l.logger = log.New("") } @@ -147,57 +281,56 @@ func NewLimiter(config LimiterConfig) Limiter { l.psutil = psutil.DefaultUtil } - if ncpu, err := l.psutil.CPUCounts(true); err != nil { + if ncpu, err := l.psutil.CPUCounts(); err != nil { l.ncpu = 1 } else { l.ncpu = ncpu } l.lastUsage.CPU.NCPU = l.ncpu - l.lastUsage.CPU.Limit = l.cpu * l.ncpu - l.lastUsage.Memory.Limit = l.memory + l.lastUsage.CPU.Limit = l.cpu.Limit() * 100 * l.ncpu + l.lastUsage.Memory.Limit = l.memory.Limit() + l.lastUsage.GPU.Memory.Limit = l.gpu.memory.Limit() + l.lastUsage.GPU.Usage.Limit = l.gpu.usage.Limit() * 100 + l.lastUsage.GPU.Encoder.Limit = l.gpu.encoder.Limit() * 100 + l.lastUsage.GPU.Decoder.Limit = l.gpu.decoder.Limit() * 100 l.ncpuFactor = 1 mode := "hard" if l.mode == LimitModeSoft { mode = "soft" - l.cpu /= l.ncpu + l.cpu.SetLimit(l.cpu.Limit() / l.ncpu) l.ncpuFactor = l.ncpu } - l.cpu /= 100 - if l.onLimit == nil { - l.onLimit = func(float64, uint64) {} + l.onLimit = func(float64, uint64, float64, float64, float64, uint64) {} } l.logger = l.logger.WithFields(log.Fields{ - "cpu": l.cpu * l.ncpuFactor, - "memory": l.memory, - "mode": mode, + "cpu": l.cpu.Limit() * l.ncpuFactor, + "memory": l.memory.Limit(), + "gpumemory": l.gpu.memory.Limit(), + "gpuusage": l.gpu.usage.Limit(), + "gpuencoder": l.gpu.encoder.Limit(), + "gpudecoder": l.gpu.decoder.Limit(), + "mode": mode, }) return l } func (l *limiter) reset() { - l.cpuCurrent = 0 - l.cpuLast = 0 - l.cpuAvg = 0 - l.cpuAvgCounter = 0 - l.cpuMax = 0 - l.cpuTop = 0 - l.cpuLimitEnable = false + l.cpu.Reset() l.cpuThrottling = false - l.memoryCurrent = 0 - l.memoryLast = 0 - l.memoryAvg = 0 - l.memoryAvgCounter = 0 - l.memoryMax = 0 - l.memoryTop = 0 - l.memoryLimitEnable = false + l.memory.Reset() + + l.gpu.memory.Reset() + l.gpu.usage.Reset() + l.gpu.encoder.Reset() + l.gpu.decoder.Reset() } func (l *limiter) Start(process psutil.Process) error { @@ -218,10 +351,7 @@ func (l *limiter) Start(process psutil.Process) error { go l.ticker(ctx, time.Second) if l.mode == LimitModeSoft { - ctx, cancel = context.WithCancel(context.Background()) - l.cancelLimit = cancel - - go l.limitCPU(ctx, l.cpu, time.Second) + go l.limitCPU(ctx, l.cpu.Limit(), time.Second) } return nil @@ -237,11 +367,6 @@ func (l *limiter) Stop() { l.cancel() - if l.cancelLimit != nil { - l.cancelLimit() - l.cancelLimit = nil - } - l.proc.Stop() l.proc = nil @@ -256,13 +381,13 @@ func (l *limiter) ticker(ctx context.Context, interval time.Duration) { select { case <-ctx.Done(): return - case t := <-ticker.C: - l.collect(t) + case <-ticker.C: + l.collect() } } } -func (l *limiter) collect(_ time.Time) { +func (l *limiter) collect() { l.lock.Lock() proc := l.proc l.lock.Unlock() @@ -271,118 +396,108 @@ func (l *limiter) collect(_ time.Time) { return } - mstat, merr := proc.VirtualMemory() - cpustat, cerr := proc.CPUPercent() + mstat, merr := proc.Memory() + cpustat, cerr := proc.CPU() + gstat, gerr := proc.GPU() + gindex := -1 l.lock.Lock() + defer l.lock.Unlock() if merr == nil { - l.memoryLast, l.memoryCurrent = l.memoryCurrent, mstat - - if l.memoryCurrent > l.memoryMax { - l.memoryMax = l.memoryCurrent - } - - if l.memoryCurrent > l.memoryTop { - l.memoryTop = l.memoryCurrent - } else { - l.memoryTop = uint64(float64(l.memoryTop) * 0.95) - } - - l.memoryAvgCounter++ - - l.memoryAvg = ((l.memoryAvg * float64(l.memoryAvgCounter-1)) + float64(l.memoryCurrent)) / float64(l.memoryAvgCounter) + l.memory.Update(mstat) } if cerr == nil { - l.cpuLast, l.cpuCurrent = l.cpuCurrent, (cpustat.System+cpustat.User+cpustat.Other)/100 - - if l.cpuCurrent > l.cpuMax { - l.cpuMax = l.cpuCurrent - } - - if l.cpuCurrent > l.cpuTop { - l.cpuTop = l.cpuCurrent - } else { - l.cpuTop = l.cpuTop * 0.95 - } - - l.cpuAvgCounter++ + l.cpu.Update((cpustat.System + cpustat.User + cpustat.Other) / 100) + } - l.cpuAvg = ((l.cpuAvg * float64(l.cpuAvgCounter-1)) + l.cpuCurrent) / float64(l.cpuAvgCounter) + if gerr == nil { + l.gpu.memory.Update(gstat.MemoryUsed) + l.gpu.usage.Update(gstat.Usage / 100) + l.gpu.encoder.Update(gstat.Encoder / 100) + l.gpu.decoder.Update(gstat.Decoder / 100) + gindex = gstat.Index } isLimitExceeded := false if l.mode == LimitModeHard { - if l.cpu > 0 { - if l.cpuCurrent > l.cpu { - // Current value is higher than the limit - if l.cpuLast <= l.cpu { - // If the previous value is below the limit, then we reached the - // limit as of now - l.cpuLimitSince = time.Now() - } - - if time.Since(l.cpuLimitSince) >= l.waitFor { - l.logger.Warn().Log("CPU limit exceeded") - isLimitExceeded = true - } - } + if l.cpu.IsExceeded(l.waitFor, l.mode) { + l.logger.Warn().Log("CPU limit exceeded") + isLimitExceeded = true } + } - if l.memory > 0 { - if l.memoryCurrent > l.memory { - // Current value is higher than the limit - if l.memoryLast <= l.memory { - // If the previous value is below the limit, then we reached the - // limit as of now - l.memoryLimitSince = time.Now() - } + if l.memory.IsExceeded(l.waitFor, l.mode) { + l.logger.Warn().Log("Memory limit exceeded") + isLimitExceeded = true + } - if time.Since(l.memoryLimitSince) >= l.waitFor { - l.logger.Warn().Log("Memory limit exceeded") - isLimitExceeded = true - } - } - } - } else { - if l.memory > 0 && l.memoryLimitEnable { - if l.memoryCurrent > l.memory { - // Current value is higher than the limit - l.logger.Warn().Log("Memory limit exceeded") - isLimitExceeded = true - } - } + if l.gpu.memory.IsExceeded(l.waitFor, l.mode) { + l.logger.Warn().Log("GPU memory limit exceeded") + isLimitExceeded = true + } + + if l.gpu.usage.IsExceeded(l.waitFor, l.mode) { + l.logger.Warn().Log("GPU usage limit exceeded") + isLimitExceeded = true + } + + if l.gpu.encoder.IsExceeded(l.waitFor, l.mode) { + l.logger.Warn().Log("GPU encoder limit exceeded") + isLimitExceeded = true + } + + if l.gpu.decoder.IsExceeded(l.waitFor, l.mode) { + l.logger.Warn().Log("GPU decoder limit exceeded") + isLimitExceeded = true } l.logger.Debug().WithFields(log.Fields{ - "cur_cpu": l.cpuCurrent * l.ncpuFactor, - "top_cpu": l.cpuTop * l.ncpuFactor, - "cur_mem": l.memoryCurrent, - "top_mem": l.memoryTop, - "exceeded": isLimitExceeded, + "cur_cpu": l.cpu.Current() * l.ncpuFactor, + "top_cpu": l.cpu.Top() * l.ncpuFactor, + "cur_mem": l.memory.Current(), + "top_mem": l.memory.Top(), + "cur_gpu_mem": l.gpu.memory.Current(), + "top_gpu_mem": l.gpu.memory.Top(), + "exceeded": isLimitExceeded, }).Log("Observation") if isLimitExceeded { - go l.onLimit(l.cpuCurrent*l.ncpuFactor*100, l.memoryCurrent) + go l.onLimit(l.cpu.Current()*l.ncpuFactor*100, l.memory.Current(), l.gpu.usage.Current(), l.gpu.encoder.Current(), l.gpu.decoder.Current(), l.gpu.memory.Current()) } l.lastUsageLock.Lock() - l.lastUsage.CPU.Current = l.cpuCurrent * l.ncpu * 100 - l.lastUsage.CPU.Average = l.cpuAvg * l.ncpu * 100 - l.lastUsage.CPU.Max = l.cpuMax * l.ncpu * 100 + l.lastUsage.CPU.Current = l.cpu.Current() * l.ncpu * 100 + l.lastUsage.CPU.Average = l.cpu.Avg() * l.ncpu * 100 + l.lastUsage.CPU.Max = l.cpu.Max() * l.ncpu * 100 l.lastUsage.CPU.IsThrottling = l.cpuThrottling - l.lastUsage.Memory.Current = l.memoryCurrent - l.lastUsage.Memory.Average = l.memoryAvg - l.lastUsage.Memory.Max = l.memoryMax - l.lastUsageLock.Unlock() + l.lastUsage.Memory.Current = l.memory.Current() + l.lastUsage.Memory.Average = l.memory.Avg() + l.lastUsage.Memory.Max = l.memory.Max() - l.lock.Unlock() + l.lastUsage.GPU.Index = gindex + l.lastUsage.GPU.Memory.Current = l.gpu.memory.Current() * 100 + l.lastUsage.GPU.Memory.Average = l.gpu.memory.Avg() * 100 + l.lastUsage.GPU.Memory.Max = l.gpu.memory.Max() * 100 + + l.lastUsage.GPU.Usage.Current = l.gpu.usage.Current() * 100 + l.lastUsage.GPU.Usage.Average = l.gpu.usage.Avg() * 100 + l.lastUsage.GPU.Usage.Max = l.gpu.usage.Max() * 100 + + l.lastUsage.GPU.Encoder.Current = l.gpu.encoder.Current() * 100 + l.lastUsage.GPU.Encoder.Average = l.gpu.encoder.Avg() * 100 + l.lastUsage.GPU.Encoder.Max = l.gpu.encoder.Max() * 100 + + l.lastUsage.GPU.Decoder.Current = l.gpu.decoder.Current() * 100 + l.lastUsage.GPU.Decoder.Average = l.gpu.decoder.Avg() * 100 + l.lastUsage.GPU.Decoder.Max = l.gpu.decoder.Max() * 100 + l.lastUsageLock.Unlock() } -func (l *limiter) Limit(cpu, memory bool) error { +func (l *limiter) Limit(cpu, memory, gpu bool) error { l.lock.Lock() defer l.lock.Unlock() @@ -390,35 +505,31 @@ func (l *limiter) Limit(cpu, memory bool) error { return nil } - if memory { - if !l.memoryLimitEnable { - l.memoryLimitEnable = true - - l.logger.Debug().Log("Memory limiter enabled") - } - } else { - if l.memoryLimitEnable { - l.memoryLimitEnable = false - - l.logger.Debug().Log("Memory limiter disabled") - } + enabled, changed := l.cpu.DoLimit(cpu) + if enabled && changed { + l.logger.Debug().Log("CPU limiter enabled") + } else if !enabled && changed { + l.logger.Debug().Log("CPU limiter disabled") } - if cpu { - if !l.cpuLimitEnable { - l.cpuLimitEnable = true - - l.logger.Debug().Log("CPU limiter enabled") - } - } else { - if l.cpuLimitEnable { - l.cpuLimitEnable = false - - l.logger.Debug().Log("CPU limiter disabled") - } + enabled, changed = l.memory.DoLimit(memory) + if enabled && changed { + l.logger.Debug().Log("Memory limiter enabled") + } else if !enabled && changed { + l.logger.Debug().Log("Memory limiter disabled") + } + enabled, changed = l.gpu.memory.DoLimit(gpu) + if enabled && changed { + l.logger.Debug().Log("GPU limiter enabled") + } else if !enabled && changed { + l.logger.Debug().Log("GPU limiter disabled") } + l.gpu.usage.DoLimit(gpu) + l.gpu.encoder.DoLimit(gpu) + l.gpu.decoder.DoLimit(gpu) + return nil } @@ -453,7 +564,7 @@ func (l *limiter) limitCPU(ctx context.Context, limit float64, interval time.Dur l.lock.Lock() - if !l.cpuLimitEnable { + if !l.cpu.IsLimitEnabled() { if factorTopLimit > 0 { factorTopLimit -= 10 } else { @@ -469,7 +580,7 @@ func (l *limiter) limitCPU(ctx context.Context, limit float64, interval time.Dur } } else { factorTopLimit = 100 - topLimit = l.cpuTop - limit + topLimit = l.cpu.Top() - limit l.cpuThrottling = true } @@ -482,7 +593,7 @@ func (l *limiter) limitCPU(ctx context.Context, limit float64, interval time.Dur lim += (100 - factorTopLimit) / 100 * topLimit } - pcpu := l.cpuCurrent + pcpu := l.cpu.Current() l.lock.Unlock() @@ -526,16 +637,6 @@ func (l *limiter) limitCPU(ctx context.Context, limit float64, interval time.Dur } } -func (l *limiter) Current() (cpu float64, memory uint64) { - l.lastUsageLock.RLock() - defer l.lastUsageLock.RUnlock() - - cpu = l.lastUsage.CPU.Current / l.ncpu - memory = l.lastUsage.Memory.Current - - return -} - func (l *limiter) Usage() Usage { l.lastUsageLock.RLock() defer l.lastUsageLock.RUnlock() @@ -543,10 +644,6 @@ func (l *limiter) Usage() Usage { return l.lastUsage } -func (l *limiter) Limits() (cpu float64, memory uint64) { - return l.cpu * 100, l.memory -} - func (l *limiter) Mode() LimitMode { return l.mode } diff --git a/process/limiter_test.go b/process/limiter_test.go index c9e31127..0ec98333 100644 --- a/process/limiter_test.go +++ b/process/limiter_test.go @@ -7,13 +7,13 @@ import ( "github.com/datarhei/core/v16/psutil" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type psproc struct{} -func (p *psproc) CPUPercent() (*psutil.CPUInfoStat, error) { - return &psutil.CPUInfoStat{ +func (p *psproc) CPU() (*psutil.CPUInfo, error) { + return &psutil.CPUInfo{ System: 50, User: 0, Idle: 0, @@ -21,10 +21,22 @@ func (p *psproc) CPUPercent() (*psutil.CPUInfoStat, error) { }, nil } -func (p *psproc) VirtualMemory() (uint64, error) { +func (p *psproc) Memory() (uint64, error) { return 197, nil } +func (p *psproc) GPU() (*psutil.GPUInfo, error) { + return &psutil.GPUInfo{ + Index: 0, + Name: "L4", + MemoryTotal: 128, + MemoryUsed: 91, + Usage: 3, + Encoder: 9, + Decoder: 5, + }, nil +} + func (p *psproc) Stop() {} func (p *psproc) Suspend() error { return nil } func (p *psproc) Resume() error { return nil } @@ -42,7 +54,7 @@ func TestCPULimit(t *testing.T) { l := NewLimiter(LimiterConfig{ CPU: 42, - OnLimit: func(float64, uint64) { + OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, }) @@ -57,7 +69,7 @@ func TestCPULimit(t *testing.T) { lock.Unlock() }() - assert.Eventually(t, func() bool { + require.Eventually(t, func() bool { lock.Lock() defer lock.Unlock() @@ -79,7 +91,7 @@ func TestCPULimitWaitFor(t *testing.T) { l := NewLimiter(LimiterConfig{ CPU: 42, WaitFor: 3 * time.Second, - OnLimit: func(float64, uint64) { + OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, }) @@ -94,7 +106,7 @@ func TestCPULimitWaitFor(t *testing.T) { lock.Unlock() }() - assert.Eventually(t, func() bool { + require.Eventually(t, func() bool { lock.Lock() defer lock.Unlock() @@ -115,7 +127,7 @@ func TestMemoryLimit(t *testing.T) { l := NewLimiter(LimiterConfig{ Memory: 42, - OnLimit: func(float64, uint64) { + OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, }) @@ -130,7 +142,7 @@ func TestMemoryLimit(t *testing.T) { lock.Unlock() }() - assert.Eventually(t, func() bool { + require.Eventually(t, func() bool { lock.Lock() defer lock.Unlock() @@ -152,7 +164,80 @@ func TestMemoryLimitWaitFor(t *testing.T) { l := NewLimiter(LimiterConfig{ Memory: 42, WaitFor: 3 * time.Second, - OnLimit: func(float64, uint64) { + OnLimit: func(float64, uint64, float64, float64, float64, uint64) { + wg.Done() + }, + }) + + l.Start(&psproc{}) + defer l.Stop() + + wg.Wait() + + lock.Lock() + done = true + lock.Unlock() + }() + + require.Eventually(t, func() bool { + lock.Lock() + defer lock.Unlock() + + return done + }, 10*time.Second, 1*time.Second) +} + +func TestGPUMemoryLimit(t *testing.T) { + lock := sync.Mutex{} + + lock.Lock() + done := false + lock.Unlock() + + go func() { + wg := sync.WaitGroup{} + wg.Add(1) + + l := NewLimiter(LimiterConfig{ + GPUMemory: 42, + OnLimit: func(float64, uint64, float64, float64, float64, uint64) { + wg.Done() + }, + }) + + l.Start(&psproc{}) + defer l.Stop() + + wg.Wait() + + lock.Lock() + done = true + lock.Unlock() + }() + + require.Eventually(t, func() bool { + lock.Lock() + defer lock.Unlock() + + return done + }, 2*time.Second, 100*time.Millisecond) +} + +func TestGPUMemoryLimitWaitFor(t *testing.T) { + lock := sync.Mutex{} + + lock.Lock() + done := false + lock.Unlock() + + go func() { + wg := sync.WaitGroup{} + wg.Add(1) + + l := NewLimiter(LimiterConfig{ + GPUMemory: 42, + WaitFor: 3 * time.Second, + OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, }) @@ -167,7 +252,7 @@ func TestMemoryLimitWaitFor(t *testing.T) { lock.Unlock() }() - assert.Eventually(t, func() bool { + require.Eventually(t, func() bool { lock.Lock() defer lock.Unlock() @@ -189,7 +274,46 @@ func TestMemoryLimitSoftMode(t *testing.T) { l := NewLimiter(LimiterConfig{ Memory: 42, Mode: LimitModeSoft, - OnLimit: func(float64, uint64) { + OnLimit: func(float64, uint64, float64, float64, float64, uint64) { + wg.Done() + }, + }) + + l.Start(&psproc{}) + defer l.Stop() + + l.Limit(false, true, false) + + wg.Wait() + + lock.Lock() + done = true + lock.Unlock() + }() + + require.Eventually(t, func() bool { + lock.Lock() + defer lock.Unlock() + + return done + }, 2*time.Second, 100*time.Millisecond) +} + +func TestGPUMemoryLimitSoftMode(t *testing.T) { + lock := sync.Mutex{} + + lock.Lock() + done := false + lock.Unlock() + + go func() { + wg := sync.WaitGroup{} + wg.Add(1) + + l := NewLimiter(LimiterConfig{ + GPUMemory: 42, + Mode: LimitModeSoft, + OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, }) @@ -197,7 +321,7 @@ func TestMemoryLimitSoftMode(t *testing.T) { l.Start(&psproc{}) defer l.Stop() - l.Limit(false, true) + l.Limit(false, false, true) wg.Wait() @@ -206,7 +330,7 @@ func TestMemoryLimitSoftMode(t *testing.T) { lock.Unlock() }() - assert.Eventually(t, func() bool { + require.Eventually(t, func() bool { lock.Lock() defer lock.Unlock() diff --git a/process/process.go b/process/process.go index 0fe0d45f..430cb7e2 100644 --- a/process/process.go +++ b/process/process.go @@ -46,29 +46,32 @@ type Process interface { // Limit enables or disables CPU and memory limiting. CPU will be throttled // into the configured limit. If memory consumption is above the configured // limit, the process will be killed. - Limit(cpu, memory bool) error + Limit(cpu, memory, gpu bool) error } // Config is the configuration of a process type Config struct { - Binary string // Path to the ffmpeg binary. - Args []string // List of arguments for the binary. - Reconnect bool // Whether to restart the process if it exited. - ReconnectDelay time.Duration // Duration to wait before restarting the process. - StaleTimeout time.Duration // Kill the process after this duration if it doesn't produce any output. - Timeout time.Duration // Kill the process after this duration. - LimitCPU float64 // Kill the process if the CPU usage in percent is above this value. - LimitMemory uint64 // Kill the process if the memory consumption in bytes is above this value. - LimitDuration time.Duration // Kill the process if the limits are exceeded for this duration. - LimitMode LimitMode // Select limiting mode - Scheduler Scheduler // A scheduler. - Parser Parser // A parser for the output of the process. - OnArgs func(args []string) []string // A callback which is called right before the process will start with the command args. - OnBeforeStart func() error // A callback which is called before the process will be started. If error is non-nil, the start will be refused. - OnStart func() // A callback which is called after the process started. - OnExit func(state string) // A callback which is called after the process exited with the exit state. - OnStateChange func(from, to string) // A callback which is called after a state changed. - Logger log.Logger + Binary string // Path to the ffmpeg binary. + Args []string // List of arguments for the binary. + Reconnect bool // Whether to restart the process if it exited. + ReconnectDelay time.Duration // Duration to wait before restarting the process. + StaleTimeout time.Duration // Kill the process after this duration if it doesn't produce any output. + Timeout time.Duration // Kill the process after this duration. + LimitCPU float64 // Kill the process if the CPU usage in percent is above this value, in percent 0-100 in hard mode, 0-100*ncpu in soft mode. + LimitMemory uint64 // Kill the process if the memory consumption in bytes is above this value. + LimitGPUUsage float64 // Kill the process if the GPU usage in percent is above this value, in percent 0-100. + LimitGPUEncoder float64 // Kill the process if the GPU encoder usage in percent is above this value, in percent 0-100. + LimitGPUDecoder float64 // Kill the process if the GPU decoder usage in percent is above this value, in percent 0-100. + LimitGPUMemory uint64 // Kill the process if the GPU memory consumption in bytes is above this value. + LimitDuration time.Duration // Kill the process if the limits are exceeded for this duration. + LimitMode LimitMode // Select limiting mode + Scheduler Scheduler // A scheduler. + Parser Parser // A parser for the output of the process. + OnBeforeStart func(args []string) ([]string, error) // A callback which is called before the process will be started. The string slice is the arguments of the command line. If error is non-nil, the start will be refused. + OnStart func() // A callback which is called after the process started. + OnExit func(state string) // A callback which is called after the process exited with the exit state. + OnStateChange func(from, to string) // A callback which is called after a state changed. + Logger log.Logger } // Status represents the current status of a process @@ -81,20 +84,47 @@ type Status struct { Time time.Time // Time is the time of the last change of the state CommandArgs []string // Currently running command arguments LimitMode string // The limiting mode - CPU struct { - NCPU float64 // Number of logical CPUs - Current float64 // Currently consumed CPU in percent - Average float64 // Average consumed CPU in percent - Max float64 // Max. consumed CPU in percent - Limit float64 // Usage limit in percent - IsThrottling bool // Whether the CPU is currently limited - } // Used CPU in percent - Memory struct { - Current uint64 // Currently consumed memory in bytes - Average float64 // Average consumed memory in bytes - Max uint64 // Max. consumed memory in bytes - Limit uint64 // Usage limit in bytes - } // Used memory in bytes + CPU StatusCPU // CPU consumption in percent + Memory StatusMemory // Memory consumption in bytes + GPU StatusGPU // GPU consumption +} + +type StatusCPU struct { + NCPU float64 // Number of logical CPUs + Current float64 // Currently consumed CPU in percent + Average float64 // Average consumed CPU in percent + Max float64 // Max. consumed CPU in percent + Limit float64 // Usage limit in percent + IsThrottling bool // Whether the CPU is currently limited +} + +type StatusMemory struct { + Current uint64 // Currently consumed memory in bytes + Average uint64 // Average consumed memory in bytes + Max uint64 // Max. consumed memory in bytes + Limit uint64 // Usage limit in bytes +} + +type StatusGPUMemory struct { + Current uint64 // Currently consumed memory in bytes + Average uint64 // Average consumed memory in bytes + Max uint64 // Max. consumed memory in bytes + Limit uint64 // Usage limit in bytes +} + +type StatusGPUUsage struct { + Current float64 // Currently consumed GPU usage in percent + Average float64 // Average consumed GPU usage in percent + Max float64 // Max. consumed GPU usage in percent + Limit float64 // Usage limit in percent +} + +type StatusGPU struct { + Index int + Memory StatusGPUMemory // GPU memory consumption + Usage StatusGPUUsage // GPU usage in percent + Encoder StatusGPUUsage // GPU encoder usage in percent + Decoder StatusGPUUsage // GPU decoder usage in percent } // States @@ -206,8 +236,7 @@ type process struct { logger log.Logger debuglogger log.Logger callbacks struct { - onArgs func(args []string) []string - onBeforeStart func() error + onBeforeStart func(args []string) ([]string, error) onStart func() onExit func(state string) onStateChange func(from, to string) @@ -263,28 +292,35 @@ func New(config Config) (Process, error) { p.stale.last = time.Now() p.stale.timeout = config.StaleTimeout - p.callbacks.onArgs = config.OnArgs p.callbacks.onBeforeStart = config.OnBeforeStart p.callbacks.onStart = config.OnStart p.callbacks.onExit = config.OnExit p.callbacks.onStateChange = config.OnStateChange p.limits = NewLimiter(LimiterConfig{ - CPU: config.LimitCPU, - Memory: config.LimitMemory, - WaitFor: config.LimitDuration, - Mode: config.LimitMode, - Logger: p.logger.WithComponent("ProcessLimiter"), - OnLimit: func(cpu float64, memory uint64) { + CPU: config.LimitCPU, + Memory: config.LimitMemory, + GPUUsage: config.LimitGPUUsage, + GPUEncoder: config.LimitGPUEncoder, + GPUDecoder: config.LimitGPUDecoder, + GPUMemory: config.LimitGPUMemory, + WaitFor: config.LimitDuration, + Mode: config.LimitMode, + Logger: p.logger.WithComponent("ProcessLimiter"), + OnLimit: func(cpu float64, memory uint64, gpuusage, gpuencoder, gpudecoder float64, gpumemory uint64) { if !p.isRunning() { return } p.logger.WithFields(log.Fields{ - "cpu": cpu, - "memory": memory, + "cpu": cpu, + "memory": memory, + "gpuusage": gpuusage, + "gpuencoder": gpuencoder, + "gpudecoder": gpudecoder, + "gpumemmory": gpumemory, }).Warn().Log("Killed because limits are exceeded") - p.Kill(false, fmt.Sprintf("Killed because limits are exceeded (mode: %s, tolerance: %s): %.2f (%.2f) CPU, %d (%d) bytes memory", config.LimitMode.String(), config.LimitDuration.String(), cpu, config.LimitCPU, memory, config.LimitMemory)) + p.Kill(false, fmt.Sprintf("Killed because limits are exceeded (mode: %s, tolerance: %s): %.2f (%.2f) CPU, %d (%d) bytes memory, %.2f/%.2f/%.2f (%.2f) GPU usage, %d (%d) bytes GPU memory", config.LimitMode.String(), config.LimitDuration.String(), cpu, config.LimitCPU, memory, config.LimitMemory, gpuusage, gpuencoder, gpudecoder, config.LimitGPUUsage, gpumemory, config.LimitGPUMemory)) }, }) @@ -467,8 +503,47 @@ func (p *process) Status() Status { Duration: time.Since(stateTime), Time: stateTime, LimitMode: p.limits.Mode().String(), - CPU: usage.CPU, - Memory: usage.Memory, + CPU: StatusCPU{ + NCPU: usage.CPU.NCPU, + Current: usage.CPU.Current, + Average: usage.CPU.Average, + Max: usage.CPU.Max, + Limit: usage.CPU.Limit, + IsThrottling: usage.CPU.IsThrottling, + }, + Memory: StatusMemory{ + Current: usage.Memory.Current, + Average: uint64(usage.Memory.Average), + Max: usage.Memory.Max, + Limit: usage.Memory.Limit, + }, + GPU: StatusGPU{ + Index: usage.GPU.Index, + Memory: StatusGPUMemory{ + Current: usage.GPU.Memory.Current, + Average: uint64(usage.GPU.Memory.Average), + Max: usage.GPU.Memory.Max, + Limit: usage.GPU.Memory.Limit, + }, + Usage: StatusGPUUsage{ + Current: usage.GPU.Usage.Current, + Average: usage.GPU.Usage.Average, + Max: usage.GPU.Usage.Max, + Limit: usage.GPU.Usage.Limit, + }, + Encoder: StatusGPUUsage{ + Current: usage.GPU.Encoder.Current, + Average: usage.GPU.Encoder.Average, + Max: usage.GPU.Encoder.Max, + Limit: usage.GPU.Encoder.Limit, + }, + Decoder: StatusGPUUsage{ + Current: usage.GPU.Decoder.Current, + Average: usage.GPU.Decoder.Average, + Max: usage.GPU.Decoder.Max, + Limit: usage.GPU.Decoder.Limit, + }, + }, } s.CommandArgs = make([]string, len(p.args)) @@ -488,7 +563,7 @@ func (p *process) IsRunning() bool { return p.isRunning() } -func (p *process) Limit(cpu, memory bool) error { +func (p *process) Limit(cpu, memory, gpu bool) error { if !p.isRunning() { return nil } @@ -498,11 +573,12 @@ func (p *process) Limit(cpu, memory bool) error { } p.logger.Warn().WithFields(log.Fields{ - "limit_cpu": cpu, - "limit_memory": memory, + "limit_cpu": cpu, + "limit_memory": memory, + "limit_gpumemory": gpu, }).Log("Limiter triggered") - return p.limits.Limit(cpu, memory) + return p.limits.Limit(cpu, memory, gpu) } // Start will start the process and sets the order to "start". If the @@ -559,11 +635,21 @@ func (p *process) start() error { args := p.args - if p.callbacks.onArgs != nil { + if p.callbacks.onBeforeStart != nil { args = make([]string, len(p.args)) copy(args, p.args) - args = p.callbacks.onArgs(args) + args, err = p.callbacks.onBeforeStart(args) + if err != nil { + p.setState(stateFailed) + + p.parser.Parse([]byte(err.Error())) + p.logger.WithError(err).Error().Log("Starting failed") + + p.reconnect(p.delay(stateFailed)) + + return err + } } p.cmd = exec.Command(p.binary, args...) @@ -582,19 +668,6 @@ func (p *process) start() error { return err } - if p.callbacks.onBeforeStart != nil { - if err := p.callbacks.onBeforeStart(); err != nil { - p.setState(stateFailed) - - p.parser.Parse([]byte(err.Error())) - p.logger.WithError(err).Error().Log("Starting failed") - - p.reconnect(p.delay(stateFailed)) - - return err - } - } - if err := p.cmd.Start(); err != nil { p.setState(stateFailed) diff --git a/process/process_test.go b/process/process_test.go index 11c669b9..6ddba58a 100644 --- a/process/process_test.go +++ b/process/process_test.go @@ -606,21 +606,15 @@ func TestProcessCallbacks(t *testing.T) { "2", }, Reconnect: false, - OnArgs: func(a []string) []string { - lock.Lock() - defer lock.Unlock() - - args = make([]string, len(a)) - copy(args, a) - return a - }, - OnBeforeStart: func() error { + OnBeforeStart: func(a []string) ([]string, error) { lock.Lock() defer lock.Unlock() onBeforeStart = true - return nil + args = make([]string, len(a)) + copy(args, a) + return a, nil }, OnStart: func() { lock.Lock() @@ -681,8 +675,8 @@ func TestProcessCallbacksOnBeforeStart(t *testing.T) { Parser: parser, Reconnect: true, ReconnectDelay: 10 * time.Second, - OnBeforeStart: func() error { - return fmt.Errorf("no, not now") + OnBeforeStart: func(a []string) ([]string, error) { + return a, fmt.Errorf("no, not now") }, }) require.NoError(t, err) diff --git a/psutil/gpu/gpu.go b/psutil/gpu/gpu.go index 7feb19bd..cb8dcf00 100644 --- a/psutil/gpu/gpu.go +++ b/psutil/gpu/gpu.go @@ -3,21 +3,25 @@ package gpu import "errors" type Process struct { - PID int32 - Memory uint64 + PID int32 + Index int + Memory uint64 // bytes + Usage float64 // percent 0-100 + Encoder float64 // percent 0-100 + Decoder float64 // percent 0-100 } type Stats struct { + ID string Name string Architecture string - MemoryTotal uint64 - MemoryUsed uint64 + MemoryTotal uint64 // bytes + MemoryUsed uint64 // bytes - Usage float64 - MemoryUsage float64 - EncoderUsage float64 - DecoderUsage float64 + Usage float64 // percent 0-100 + Encoder float64 // percent 0-100 + Decoder float64 // percent 0-100 Process []Process @@ -25,9 +29,17 @@ type Stats struct { } type GPU interface { + // Count returns the number of GPU in the system. Count() (int, error) + + // Stats returns current GPU stats. Stats() ([]Stats, error) + + // Process returns a Process. Process(pid int32) (Process, error) + + // Close stops all GPU collection processes + Close() } var ErrProcessNotFound = errors.New("process not found") diff --git a/psutil/gpu/nvidia/fixtures/process.txt b/psutil/gpu/nvidia/fixtures/process.txt new file mode 100644 index 00000000..55d7bcf4 --- /dev/null +++ b/psutil/gpu/nvidia/fixtures/process.txt @@ -0,0 +1,54 @@ +# gpu pid type sm mem enc dec fb command +# Idx # C/G % % % % MB name + 0 7372 C 2 0 2 - 136 ffmpeg + 0 12176 C 5 2 3 7 782 ffmpeg + 0 20035 C 8 2 4 1 1145 ffmpeg + 0 20141 C 2 1 1 3 429 ffmpeg + 0 29591 C 2 1 - 2 435 ffmpeg + 0 7372 C 2 0 - - 136 ffmpeg + 0 12176 C 8 3 7 9 782 ffmpeg + 0 20035 C 8 2 3 1 1145 ffmpeg + 0 20141 C - - 1 1 429 ffmpeg + 0 29591 C 3 1 - 2 435 ffmpeg + 0 7372 C 2 1 1 - 136 ffmpeg + 0 12176 C 5 1 5 7 782 ffmpeg + 0 20035 C 8 3 1 4 1145 ffmpeg + 0 20141 C 2 0 1 - 429 ffmpeg + 0 29591 C 2 0 1 3 435 ffmpeg + 0 7372 C 2 0 - - 136 ffmpeg + 0 12176 C 5 1 5 3 782 ffmpeg + 0 20035 C 8 2 5 4 1145 ffmpeg + 0 20141 C 3 1 - 5 429 ffmpeg + 0 29591 C 2 0 - 1 435 ffmpeg + 0 7372 C 2 1 - - 136 ffmpeg + 0 12176 C 10 3 6 8 782 ffmpeg + 0 20035 C 3 1 1 1 1145 ffmpeg + 0 20141 C - - 4 1 429 ffmpeg + 0 29591 C 5 2 - 2 435 ffmpeg + 0 7372 C 5 1 2 - 136 ffmpeg + 0 12176 C 6 2 4 7 782 ffmpeg + 0 20035 C - - - - 1145 ffmpeg + 0 20141 C 5 1 1 3 429 ffmpeg + 0 29591 C 5 2 2 4 435 ffmpeg + 0 7372 C - - 1 - 136 ffmpeg + 0 12176 C 7 2 3 4 782 ffmpeg + 0 20035 C 2 0 - 1 1145 ffmpeg + 0 20141 C 7 2 4 4 429 ffmpeg + 0 29591 C 5 1 2 3 435 ffmpeg + 0 7372 C 2 0 1 - 136 ffmpeg + 0 12176 C 9 3 3 6 782 ffmpeg + 0 20035 C 2 1 - 1 1145 ffmpeg + 0 20141 C 4 1 4 5 429 ffmpeg + 0 29591 C 2 0 2 1 435 ffmpeg + 0 7372 C - - - - 136 ffmpeg + 0 12176 C 10 3 4 8 782 ffmpeg + 0 20035 C 4 1 2 1 1145 ffmpeg + 0 20141 C 7 2 3 3 429 ffmpeg +# gpu pid type sm mem enc dec fb command +# Idx # C/G % % % % MB name + 0 29591 C - - 1 1 435 ffmpeg + 0 7372 C 2 0 2 - 136 ffmpeg + 0 12176 C 7 2 2 6 782 ffmpeg + 0 20035 C 7 2 4 3 1145 ffmpeg + 0 20141 C 5 1 1 3 429 ffmpeg + 0 29591 C - - 1 1 435 ffmpeg \ No newline at end of file diff --git a/psutil/gpu/nvidia/fixtures/data1.xml b/psutil/gpu/nvidia/fixtures/query1.xml similarity index 100% rename from psutil/gpu/nvidia/fixtures/data1.xml rename to psutil/gpu/nvidia/fixtures/query1.xml diff --git a/psutil/gpu/nvidia/fixtures/data2.xml b/psutil/gpu/nvidia/fixtures/query2.xml similarity index 98% rename from psutil/gpu/nvidia/fixtures/data2.xml rename to psutil/gpu/nvidia/fixtures/query2.xml index cd45d707..4d93cac0 100644 --- a/psutil/gpu/nvidia/fixtures/data2.xml +++ b/psutil/gpu/nvidia/fixtures/query2.xml @@ -438,6 +438,18 @@ + + 10131 + C + ffmpeg + 389 MiB + + + 13597 + C + ffmpeg + 1054 MiB + @@ -879,6 +891,12 @@ + + 16870 + C + ffmpeg + 549 MiB + diff --git a/psutil/gpu/nvidia/fixtures/data3.xml b/psutil/gpu/nvidia/fixtures/query3.xml similarity index 100% rename from psutil/gpu/nvidia/fixtures/data3.xml rename to psutil/gpu/nvidia/fixtures/query3.xml diff --git a/psutil/gpu/nvidia/nvidia.go b/psutil/gpu/nvidia/nvidia.go index ba45e2fa..98ad1520 100644 --- a/psutil/gpu/nvidia/nvidia.go +++ b/psutil/gpu/nvidia/nvidia.go @@ -6,6 +6,9 @@ import ( "encoding/xml" "fmt" "os/exec" + "regexp" + "slices" + "strconv" "sync" "time" @@ -47,11 +50,19 @@ func (u *Utilization) UnmarshalText(text []byte) error { } type Process struct { - PID int32 `xml:"pid"` - Memory Megabytes `xml:"used_memory"` + Index int + PID int32 + Memory uint64 // bytes + + Usage float64 // percent 0-100 + Encoder float64 // percent 0-100 + Decoder float64 // percent 0-100 + + lastSeen time.Time } type GPUStats struct { + ID string `xml:"id,attr"` Name string `xml:"product_name"` Architecture string `xml:"product_architecture"` @@ -59,31 +70,17 @@ type GPUStats struct { MemoryUsed Megabytes `xml:"fb_memory_usage>used"` Usage Utilization `xml:"utilization>gpu_util"` - MemoryUsage Utilization `xml:"utilization>memory_util"` - EncoderUsage Utilization `xml:"utilization>encoder_util"` - DecoderUsage Utilization `xml:"utilization>decoder_util"` - - Process []Process `xml:"processes>process_info"` + UsageEncoder Utilization `xml:"utilization>encoder_util"` + UsageDecoder Utilization `xml:"utilization>decoder_util"` } type Stats struct { GPU []GPUStats `xml:"gpu"` } -func parse(data []byte) (Stats, error) { - nv := Stats{} - - err := xml.Unmarshal(data, &nv) - if err != nil { - return nv, fmt.Errorf("parsing report: %w", err) - } - - return nv, nil -} - type nvidia struct { - cmd *exec.Cmd - wr *writer + wrQuery *writerQuery + wrProcess *writerProcess lock sync.RWMutex cancel context.CancelFunc @@ -97,33 +94,80 @@ type dummy struct{} func (d *dummy) Count() (int, error) { return 0, nil } func (d *dummy) Stats() ([]gpu.Stats, error) { return nil, nil } func (d *dummy) Process(pid int32) (gpu.Process, error) { return gpu.Process{}, gpu.ErrProcessNotFound } +func (d *dummy) Close() {} + +type writerQuery struct { + buf bytes.Buffer + ch chan Stats + terminator []byte +} + +func (w *writerQuery) Write(data []byte) (int, error) { + n, err := w.buf.Write(data) + if err != nil { + return n, err + } + + for { + idx := bytes.Index(w.buf.Bytes(), w.terminator) + if idx == -1 { + break + } + + content := make([]byte, idx+len(w.terminator)) + n, err := w.buf.Read(content) + if err != nil || n != len(content) { + break + } + + s, err := w.parse(content) + if err != nil { + continue + } -type writer struct { - buf bytes.Buffer - ch chan Stats + w.ch <- s + } + + return n, nil } -var terminator = []byte("\n") +func (w *writerQuery) parse(data []byte) (Stats, error) { + nv := Stats{} -func (w *writer) Write(data []byte) (int, error) { + err := xml.Unmarshal(data, &nv) + if err != nil { + return nv, fmt.Errorf("parsing report: %w", err) + } + + return nv, nil +} + +type writerProcess struct { + buf bytes.Buffer + ch chan Process + re *regexp.Regexp + terminator []byte +} + +func (w *writerProcess) Write(data []byte) (int, error) { n, err := w.buf.Write(data) if err != nil { return n, err } for { - idx := bytes.Index(w.buf.Bytes(), terminator) + idx := bytes.Index(w.buf.Bytes(), w.terminator) if idx == -1 { break } - content := make([]byte, idx+len(terminator)) + content := make([]byte, idx+len(w.terminator)) n, err := w.buf.Read(content) if err != nil || n != len(content) { break } - s, err := parse(content) + s, err := w.parse(content) if err != nil { continue } @@ -134,19 +178,85 @@ func (w *writer) Write(data []byte) (int, error) { return n, nil } +func (w *writerProcess) parse(data []byte) (Process, error) { + p := Process{} + + if len(data) == 0 { + return p, fmt.Errorf("empty line") + } + + if data[0] == '#' { + return p, fmt.Errorf("comment") + } + + matches := w.re.FindStringSubmatch(string(data)) + if matches == nil { + return p, fmt.Errorf("no matches found") + } + + if len(matches) != 7 { + return p, fmt.Errorf("not the expected number of matches found") + } + + if d, err := strconv.ParseInt(matches[1], 10, 0); err == nil { + p.Index = int(d) + } + + if d, err := strconv.ParseInt(matches[2], 10, 32); err == nil { + p.PID = int32(d) + } + + if matches[3][0] != '-' { + if d, err := strconv.ParseFloat(matches[3], 64); err == nil { + p.Usage = d + } + } + + if matches[4][0] != '-' { + if d, err := strconv.ParseFloat(matches[4], 64); err == nil { + p.Encoder = d + } + } + + if matches[5][0] != '-' { + if d, err := strconv.ParseFloat(matches[5], 64); err == nil { + p.Decoder = d + } + } + + if d, err := strconv.ParseUint(matches[6], 10, 64); err == nil { + p.Memory = d * 1024 * 1024 + } + + return p, nil +} + func New(path string) gpu.GPU { if len(path) == 0 { path = "nvidia-smi" } - _, err := exec.LookPath(path) + path, err := exec.LookPath(path) if err != nil { return &dummy{} } n := &nvidia{ - wr: &writer{ - ch: make(chan Stats, 1), + wrQuery: &writerQuery{ + ch: make(chan Stats, 1), + terminator: []byte("\n"), + }, + wrProcess: &writerProcess{ + ch: make(chan Process, 32), + // # gpu pid type sm mem enc dec fb command + // # Idx # C/G % % % % MB name + // 0 7372 C 2 0 2 - 136 ffmpeg + // 0 12176 C 5 2 3 7 782 ffmpeg + // 0 20035 C 8 2 4 1 1145 ffmpeg + // 0 20141 C 2 1 1 3 429 ffmpeg + // 0 29591 C 2 1 - 2 435 ffmpeg + re: regexp.MustCompile(`^\s*([0-9]+)\s+([0-9]+)\s+[A-Z]\s+([0-9-]+)\s+[0-9-]+\s+([0-9-]+)\s+([0-9-]+)\s+([0-9]+).*`), + terminator: []byte("\n"), }, process: map[int32]Process{}, } @@ -154,7 +264,8 @@ func New(path string) gpu.GPU { ctx, cancel := context.WithCancel(context.Background()) n.cancel = cancel - go n.runner(ctx, path) + go n.runnerQuery(ctx, path) + go n.runnerProcess(ctx, path) go n.reader(ctx) return n @@ -165,13 +276,18 @@ func (n *nvidia) reader(ctx context.Context) { select { case <-ctx.Done(): return - case stats := <-n.wr.ch: + case stats := <-n.wrQuery.ch: n.lock.Lock() n.stats = stats - n.process = map[int32]Process{} - for _, g := range n.stats.GPU { - for _, p := range g.Process { - n.process[p.PID] = p + n.lock.Unlock() + case process := <-n.wrProcess.ch: + process.lastSeen = time.Now() + n.lock.Lock() + n.process[process.PID] = process + + for pid, p := range n.process { + if time.Since(p.lastSeen) > 11*time.Second { + delete(n.process, pid) } } n.lock.Unlock() @@ -179,11 +295,39 @@ func (n *nvidia) reader(ctx context.Context) { } } -func (n *nvidia) runner(ctx context.Context, path string) { +func (n *nvidia) runnerQuery(ctx context.Context, path string) { + for { + cmd := exec.CommandContext(ctx, path, "-q", "-x", "-l", "1") + cmd.Stdout = n.wrQuery + err := cmd.Start() + if err != nil { + n.lock.Lock() + n.err = err + n.lock.Unlock() + + time.Sleep(3 * time.Second) + continue + } + + err = cmd.Wait() + + n.lock.Lock() + n.err = err + n.lock.Unlock() + + select { + case <-ctx.Done(): + return + default: + } + } +} + +func (n *nvidia) runnerProcess(ctx context.Context, path string) { for { - n.cmd = exec.Command(path, "-q", "-x", "-l", "1") - n.cmd.Stdout = n.wr - err := n.cmd.Start() + cmd := exec.CommandContext(ctx, path, "pmon", "-s", "um", "-d", "5") + cmd.Stdout = n.wrProcess + err := cmd.Start() if err != nil { n.lock.Lock() n.err = err @@ -193,7 +337,7 @@ func (n *nvidia) runner(ctx context.Context, path string) { continue } - err = n.cmd.Wait() + err = cmd.Wait() n.lock.Lock() n.err = err @@ -219,39 +363,55 @@ func (n *nvidia) Count() (int, error) { } func (n *nvidia) Stats() ([]gpu.Stats, error) { - s := []gpu.Stats{} + stats := []gpu.Stats{} n.lock.RLock() defer n.lock.RUnlock() if n.err != nil { - return s, n.err + return stats, n.err } for _, nv := range n.stats.GPU { - stats := gpu.Stats{ + s := gpu.Stats{ + ID: nv.ID, Name: nv.Name, Architecture: nv.Architecture, MemoryTotal: uint64(nv.MemoryTotal), MemoryUsed: uint64(nv.MemoryUsed), Usage: float64(nv.Usage), - MemoryUsage: float64(nv.MemoryUsage), - EncoderUsage: float64(nv.EncoderUsage), - DecoderUsage: float64(nv.DecoderUsage), + Encoder: float64(nv.UsageEncoder), + Decoder: float64(nv.UsageDecoder), Process: []gpu.Process{}, } - for _, p := range nv.Process { - stats.Process = append(stats.Process, gpu.Process{ - PID: p.PID, - Memory: uint64(p.Memory), - }) + stats = append(stats, s) + } + + for _, p := range n.process { + if p.Index >= len(stats) { + continue } - s = append(s, stats) + stats[p.Index].Process = append(stats[p.Index].Process, gpu.Process{ + PID: p.PID, + Index: p.Index, + Memory: p.Memory, + Usage: p.Usage, + Encoder: p.Encoder, + Decoder: p.Decoder, + }) } - return s, nil + for i := range stats { + p := stats[i].Process + slices.SortFunc(p, func(a, b gpu.Process) int { + return int(a.PID - b.PID) + }) + stats[i].Process = p + } + + return stats, nil } func (n *nvidia) Process(pid int32) (gpu.Process, error) { @@ -259,14 +419,18 @@ func (n *nvidia) Process(pid int32) (gpu.Process, error) { defer n.lock.RUnlock() p, hasProcess := n.process[pid] - if !hasProcess { - return gpu.Process{}, gpu.ErrProcessNotFound + if hasProcess { + return gpu.Process{ + PID: p.PID, + Index: p.Index, + Memory: p.Memory, + Usage: p.Usage, + Encoder: p.Encoder, + Decoder: p.Decoder, + }, nil } - return gpu.Process{ - PID: p.PID, - Memory: uint64(p.Memory), - }, nil + return gpu.Process{Index: -1}, gpu.ErrProcessNotFound } func (n *nvidia) Close() { @@ -279,6 +443,4 @@ func (n *nvidia) Close() { n.cancel() n.cancel = nil - - n.cmd.Process.Kill() } diff --git a/psutil/gpu/nvidia/nvidia_test.go b/psutil/gpu/nvidia/nvidia_test.go index f18310b2..51954eb8 100644 --- a/psutil/gpu/nvidia/nvidia_test.go +++ b/psutil/gpu/nvidia/nvidia_test.go @@ -1,102 +1,430 @@ package nvidia import ( + "bytes" "os" + "regexp" + "sync" "testing" + "time" + "github.com/datarhei/core/v16/internal/testhelper" + "github.com/datarhei/core/v16/psutil/gpu" "github.com/stretchr/testify/require" ) -func TestParseNV(t *testing.T) { - data, err := os.ReadFile("./fixtures/data1.xml") +func TestParseQuery(t *testing.T) { + data, err := os.ReadFile("./fixtures/query1.xml") require.NoError(t, err) - nv, err := parse(data) + wr := &writerQuery{} + + nv, err := wr.parse(data) require.NoError(t, err) require.Equal(t, Stats{ GPU: []GPUStats{ { + ID: "00000000:01:00.0", Name: "NVIDIA GeForce GTX 1080", Architecture: "Pascal", MemoryTotal: 8119 * 1024 * 1024, MemoryUsed: 918 * 1024 * 1024, Usage: 15, - MemoryUsage: 7, - EncoderUsage: 3, - DecoderUsage: 0, - Process: []Process{ - { - PID: 18179, - Memory: 916 * 1024 * 1024, - }, - }, + UsageEncoder: 3, + UsageDecoder: 0, }, }, }, nv) - data, err = os.ReadFile("./fixtures/data2.xml") + data, err = os.ReadFile("./fixtures/query2.xml") require.NoError(t, err) - nv, err = parse(data) + nv, err = wr.parse(data) require.NoError(t, err) require.Equal(t, Stats{ GPU: []GPUStats{ { + ID: "00000000:01:00.0", Name: "NVIDIA L4", Architecture: "Ada Lovelace", MemoryTotal: 23034 * 1024 * 1024, MemoryUsed: 1 * 1024 * 1024, Usage: 2, - MemoryUsage: 0, - EncoderUsage: 0, - DecoderUsage: 0, + UsageEncoder: 0, + UsageDecoder: 0, }, { + ID: "00000000:C1:00.0", Name: "NVIDIA L4", Architecture: "Ada Lovelace", MemoryTotal: 23034 * 1024 * 1024, MemoryUsed: 1 * 1024 * 1024, Usage: 3, - MemoryUsage: 0, - EncoderUsage: 0, - DecoderUsage: 0, + UsageEncoder: 0, + UsageDecoder: 0, }, }, }, nv) - data, err = os.ReadFile("./fixtures/data3.xml") + data, err = os.ReadFile("./fixtures/query3.xml") require.NoError(t, err) - nv, err = parse(data) + nv, err = wr.parse(data) require.NoError(t, err) require.Equal(t, Stats{ GPU: []GPUStats{ { + ID: "00000000:01:00.0", Name: "GeForce GTX 1080", MemoryTotal: 8119 * 1024 * 1024, MemoryUsed: 2006 * 1024 * 1024, Usage: 32, - MemoryUsage: 11, - EncoderUsage: 17, - DecoderUsage: 25, - Process: []Process{ - { - PID: 10131, - Memory: 389 * 1024 * 1024, - }, - { - PID: 13597, - Memory: 1054 * 1024 * 1024, - }, - { - PID: 16870, - Memory: 549 * 1024 * 1024, - }, - }, + UsageEncoder: 17, + UsageDecoder: 25, }, }, }, nv) } + +func TestParseProcess(t *testing.T) { + data, err := os.ReadFile("./fixtures/process.txt") + require.NoError(t, err) + + wr := &writerProcess{ + re: regexp.MustCompile(`^\s*([0-9]+)\s+([0-9]+)\s+[A-Z]\s+([0-9-]+)\s+[0-9-]+\s+([0-9-]+)\s+([0-9-]+)\s+([0-9]+).*`), + } + + lines := bytes.Split(data, []byte("\n")) + process := map[int32]Process{} + + for _, line := range lines { + p, err := wr.parse(line) + if err != nil { + continue + } + + process[p.PID] = p + } + + require.Equal(t, map[int32]Process{ + 7372: { + Index: 0, + PID: 7372, + Memory: 136 * 1024 * 1024, + Usage: 2, + Encoder: 2, + Decoder: 0, + }, + 12176: { + Index: 0, + PID: 12176, + Memory: 782 * 1024 * 1024, + Usage: 7, + Encoder: 2, + Decoder: 6, + }, + 20035: { + Index: 0, + PID: 20035, + Memory: 1145 * 1024 * 1024, + Usage: 7, + Encoder: 4, + Decoder: 3, + }, + 20141: { + Index: 0, + PID: 20141, + Memory: 429 * 1024 * 1024, + Usage: 5, + Encoder: 1, + Decoder: 3, + }, + 29591: { + Index: 0, + PID: 29591, + Memory: 435 * 1024 * 1024, + Usage: 0, + Encoder: 1, + Decoder: 1, + }, + }, process) +} + +func TestWriterQuery(t *testing.T) { + data, err := os.ReadFile("./fixtures/query2.xml") + require.NoError(t, err) + + wr := &writerQuery{ + ch: make(chan Stats, 1), + terminator: []byte(""), + } + + stats := Stats{} + wg := sync.WaitGroup{} + wg.Add(1) + + go func() { + defer wg.Done() + + for s := range wr.ch { + stats = s + } + }() + + _, err = wr.Write(data) + require.NoError(t, err) + + close(wr.ch) + + wg.Wait() + + require.Equal(t, Stats{ + GPU: []GPUStats{ + { + ID: "00000000:01:00.0", + Name: "NVIDIA L4", + Architecture: "Ada Lovelace", + MemoryTotal: 23034 * 1024 * 1024, + MemoryUsed: 1 * 1024 * 1024, + Usage: 2, + UsageEncoder: 0, + UsageDecoder: 0, + }, + { + ID: "00000000:C1:00.0", + Name: "NVIDIA L4", + Architecture: "Ada Lovelace", + MemoryTotal: 23034 * 1024 * 1024, + MemoryUsed: 1 * 1024 * 1024, + Usage: 3, + UsageEncoder: 0, + UsageDecoder: 0, + }, + }, + }, stats) +} + +func TestWriterProcess(t *testing.T) { + data, err := os.ReadFile("./fixtures/process.txt") + require.NoError(t, err) + + wr := &writerProcess{ + ch: make(chan Process, 32), + re: regexp.MustCompile(`^\s*([0-9]+)\s+([0-9]+)\s+[A-Z]\s+([0-9-]+)\s+[0-9-]+\s+([0-9-]+)\s+([0-9-]+)\s+([0-9]+).*`), + terminator: []byte("\n"), + } + + process := map[int32]Process{} + wg := sync.WaitGroup{} + wg.Add(1) + + go func() { + defer wg.Done() + for p := range wr.ch { + process[p.PID] = p + } + }() + + _, err = wr.Write(data) + require.NoError(t, err) + + close(wr.ch) + + wg.Wait() + + require.Equal(t, map[int32]Process{ + 7372: { + Index: 0, + PID: 7372, + Memory: 136 * 1024 * 1024, + Usage: 2, + Encoder: 2, + Decoder: 0, + }, + 12176: { + Index: 0, + PID: 12176, + Memory: 782 * 1024 * 1024, + Usage: 7, + Encoder: 2, + Decoder: 6, + }, + 20035: { + Index: 0, + PID: 20035, + Memory: 1145 * 1024 * 1024, + Usage: 7, + Encoder: 4, + Decoder: 3, + }, + 20141: { + Index: 0, + PID: 20141, + Memory: 429 * 1024 * 1024, + Usage: 5, + Encoder: 1, + Decoder: 3, + }, + 29591: { + Index: 0, + PID: 29591, + Memory: 435 * 1024 * 1024, + Usage: 0, + Encoder: 1, + Decoder: 1, + }, + }, process) +} + +func TestNvidiaGPUCount(t *testing.T) { + binary, err := testhelper.BuildBinary("nvidia-smi", "../../../internal/testhelper") + require.NoError(t, err, "Failed to build helper program") + + nv := New(binary) + + t.Cleanup(func() { + nv.Close() + }) + + _, ok := nv.(*dummy) + require.False(t, ok) + + require.Eventually(t, func() bool { + count, _ := nv.Count() + return count != 0 + }, 5*time.Second, time.Second) +} + +func TestNvidiaGPUStats(t *testing.T) { + binary, err := testhelper.BuildBinary("nvidia-smi", "../../../internal/testhelper") + require.NoError(t, err, "Failed to build helper program") + + nv := New(binary) + + t.Cleanup(func() { + nv.Close() + }) + + _, ok := nv.(*dummy) + require.False(t, ok) + + require.Eventually(t, func() bool { + stats, _ := nv.Stats() + + if len(stats) != 2 { + return false + } + + if len(stats[0].Process) != 3 { + return false + } + + if len(stats[1].Process) != 2 { + return false + } + + return true + }, 5*time.Second, time.Second) + + stats, err := nv.Stats() + require.NoError(t, err) + require.Equal(t, []gpu.Stats{ + { + ID: "00000000:01:00.0", + Name: "NVIDIA L4", + Architecture: "Ada Lovelace", + MemoryTotal: 23034 * 1024 * 1024, + MemoryUsed: 1 * 1024 * 1024, + Usage: 2, + Encoder: 0, + Decoder: 0, + Process: []gpu.Process{ + { + Index: 0, + PID: 7372, + Memory: 136 * 1024 * 1024, + Usage: 2, + Encoder: 2, + Decoder: 0, + }, + { + Index: 0, + PID: 12176, + Memory: 782 * 1024 * 1024, + Usage: 5, + Encoder: 3, + Decoder: 7, + }, + { + Index: 0, + PID: 29591, + Memory: 435 * 1024 * 1024, + Usage: 2, + Encoder: 0, + Decoder: 2, + }, + }, + }, + { + ID: "00000000:C1:00.0", + Name: "NVIDIA L4", + Architecture: "Ada Lovelace", + MemoryTotal: 23034 * 1024 * 1024, + MemoryUsed: 1 * 1024 * 1024, + Usage: 3, + Encoder: 0, + Decoder: 0, + Process: []gpu.Process{ + { + Index: 1, + PID: 20035, + Memory: 1145 * 1024 * 1024, + Usage: 8, + Encoder: 4, + Decoder: 1, + }, + { + Index: 1, + PID: 20141, + Memory: 429 * 1024 * 1024, + Usage: 2, + Encoder: 1, + Decoder: 3, + }, + }, + }, + }, stats) +} + +func TestNvidiaGPUProcess(t *testing.T) { + binary, err := testhelper.BuildBinary("nvidia-smi", "../../../internal/testhelper") + require.NoError(t, err, "Failed to build helper program") + + nv := New(binary) + + t.Cleanup(func() { + nv.Close() + }) + + _, ok := nv.(*dummy) + require.False(t, ok) + + require.Eventually(t, func() bool { + _, err := nv.Process(12176) + return err == nil + }, 5*time.Second, time.Second) + + proc, err := nv.Process(12176) + require.NoError(t, err) + require.Equal(t, gpu.Process{ + Index: 0, + PID: 12176, + Memory: 782 * 1024 * 1024, + Usage: 5, + Encoder: 3, + Decoder: 7, + }, proc) +} diff --git a/psutil/process.go b/psutil/process.go index 0789f553..bec312ca 100644 --- a/psutil/process.go +++ b/psutil/process.go @@ -5,24 +5,28 @@ import ( "sync" "time" + "github.com/datarhei/core/v16/psutil/gpu/nvidia" psprocess "github.com/shirou/gopsutil/v3/process" ) type Process interface { - // CPUPercent returns the current CPU load for this process only. The values + // CPU returns the current CPU load for this process only. The values // are normed to the range of 0 to 100. - CPUPercent() (*CPUInfoStat, error) + CPU() (*CPUInfo, error) - // VirtualMemory returns the current memory usage in bytes of this process only. - VirtualMemory() (uint64, error) + // Memory returns the current memory usage in bytes of this process only. + Memory() (uint64, error) + + // GPU returns the current GPU memory in bytes and usage in percent (0-100) of this process only. + GPU() (*GPUInfo, error) // Stop will stop collecting CPU and memory data for this process. Stop() - // Suspend will send SIGSTOP to the process + // Suspend will send SIGSTOP to the process. Suspend() error - // Resume will send SIGCONT to the process + // Resume will send SIGCONT to the process. Resume() error } @@ -142,7 +146,7 @@ func (p *process) Resume() error { return p.proc.Resume() } -func (p *process) CPUPercent() (*CPUInfoStat, error) { +func (p *process) CPU() (*CPUInfo, error) { var diff float64 for { @@ -167,7 +171,7 @@ func (p *process) CPUPercent() (*CPUInfoStat, error) { diff = p.statCurrentTime.Sub(p.statPreviousTime).Seconds() * p.ncpu } - s := &CPUInfoStat{ + s := &CPUInfo{ System: 0, User: 0, Idle: 0, @@ -186,9 +190,28 @@ func (p *process) CPUPercent() (*CPUInfoStat, error) { return s, nil } -func (p *process) VirtualMemory() (uint64, error) { +func (p *process) Memory() (uint64, error) { p.lock.RLock() defer p.lock.RUnlock() return p.memRSS, nil } + +func (p *process) GPU() (*GPUInfo, error) { + info := &GPUInfo{ + Index: -1, + } + + proc, err := nvidia.Default.Process(p.pid) + if err != nil { + return info, nil + } + + info.Index = proc.Index + info.MemoryUsed = proc.Memory + info.Usage = proc.Usage + info.Encoder = proc.Encoder + info.Decoder = proc.Decoder + + return info, nil +} diff --git a/psutil/psutil.go b/psutil/psutil.go index be5e1844..079e933d 100644 --- a/psutil/psutil.go +++ b/psutil/psutil.go @@ -47,29 +47,44 @@ func init() { DefaultUtil, _ = New("/sys/fs/cgroup") } -type MemoryInfoStat struct { +type DiskInfo struct { + Path string + Fstype string + Total uint64 + Used uint64 + InodesTotal uint64 + InodesUsed uint64 +} + +type MemoryInfo struct { Total uint64 // bytes Available uint64 // bytes Used uint64 // bytes } -type CPUInfoStat struct { +type NetworkInfo struct { + Name string // interface name + BytesSent uint64 // number of bytes sent + BytesRecv uint64 // number of bytes received +} + +type CPUInfo struct { System float64 // percent 0-100 User float64 // percent 0-100 Idle float64 // percent 0-100 Other float64 // percent 0-100 } -type GPUInfoStat struct { - Name string +type GPUInfo struct { + Index int // Index of the GPU + Name string // Name of the GPU (not populated for a specific process) - MemoryTotal uint64 // bytes + MemoryTotal uint64 // bytes (not populated for a specific process) MemoryUsed uint64 // bytes - Usage float64 // percent 0-100 - MemoryUsage float64 // percent 0-100 - EncoderUsage float64 // percent 0-100 - DecoderUsage float64 // percent 0-100 + Usage float64 // percent 0-100 + Encoder float64 // percent 0-100 + Decoder float64 // percent 0-100 } type cpuTimesStat struct { @@ -85,18 +100,23 @@ type Util interface { Stop() // CPUCounts returns the number of cores, either logical or physical. - CPUCounts(logical bool) (float64, error) + CPUCounts() (float64, error) - // GPUCounts returns the number of GPU cores. - GPUCounts() (float64, error) - - // CPUPercent returns the current CPU load in percent. The values range + // CPU returns the current CPU load in percent. The values range // from 0 to 100, independently of the number of logical cores. - CPUPercent() (*CPUInfoStat, error) - DiskUsage(path string) (*disk.UsageStat, error) - VirtualMemory() (*MemoryInfoStat, error) - NetIOCounters(pernic bool) ([]net.IOCountersStat, error) - GPUStats() ([]GPUInfoStat, error) + CPU() (*CPUInfo, error) + + // Disk returns the current usage of the partition specified by the path. + Disk(path string) (*DiskInfo, error) + + // Memory return the current memory usage. + Memory() (*MemoryInfo, error) + + // Network returns the current network interface statistics per network adapter. + Network() ([]NetworkInfo, error) + + // GPU return the current usage for each CPU. + GPU() ([]GPUInfo, error) // Process returns a process observer for a process with the given pid. Process(pid int32) (Process, error) @@ -120,7 +140,7 @@ type util struct { statPrevious cpuTimesStat statPreviousTime time.Time nTicks uint64 - mem MemoryInfoStat + mem MemoryInfo } // New returns a new util, it will be started automatically @@ -140,7 +160,7 @@ func New(root string) (Util, error) { if u.ncpu == 0 { var err error - u.ncpu, err = u.CPUCounts(true) + u.ncpu, err = u.CPUCounts() if err != nil { return nil, err } @@ -311,7 +331,7 @@ func (u *util) tickMemory(ctx context.Context, interval time.Duration) { } } -func (u *util) collectMemory() *MemoryInfoStat { +func (u *util) collectMemory() *MemoryInfo { stat, err := u.virtualMemory() if err != nil { return nil @@ -320,12 +340,12 @@ func (u *util) collectMemory() *MemoryInfoStat { return stat } -func (u *util) CPUCounts(logical bool) (float64, error) { +func (u *util) CPUCounts() (float64, error) { if u.hasCgroup && u.ncpu > 0 { return u.ncpu, nil } - ncpu, err := cpu.Counts(logical) + ncpu, err := cpu.Counts(true) if err != nil { return 0, err } @@ -333,18 +353,8 @@ func (u *util) CPUCounts(logical bool) (float64, error) { return float64(ncpu), nil } -func CPUCounts(logical bool) (float64, error) { - return DefaultUtil.CPUCounts(logical) -} - -func (u *util) GPUCounts() (float64, error) { - count, err := nvidia.Default.Count() - - return float64(count), err -} - -func GPUCounts() (float64, error) { - return DefaultUtil.GPUCounts() +func CPUCounts() (float64, error) { + return DefaultUtil.CPUCounts() } // cpuTimes returns the current cpu usage times in seconds. @@ -381,7 +391,7 @@ func (u *util) cpuTimes() (*cpuTimesStat, error) { return s, nil } -func (u *util) CPUPercent() (*CPUInfoStat, error) { +func (u *util) CPU() (*CPUInfo, error) { var total float64 for { @@ -406,7 +416,7 @@ func (u *util) CPUPercent() (*CPUInfoStat, error) { total = (u.statCurrent.total - u.statPrevious.total) } - s := &CPUInfoStat{ + s := &CPUInfo{ System: 0, User: 0, Idle: 100, @@ -429,8 +439,8 @@ func (u *util) CPUPercent() (*CPUInfoStat, error) { return s, nil } -func CPUPercent() (*CPUInfoStat, error) { - return DefaultUtil.CPUPercent() +func CPUPercent() (*CPUInfo, error) { + return DefaultUtil.CPU() } func (u *util) cgroupCPUTimes(version int) (*cpuTimesStat, error) { @@ -466,15 +476,29 @@ func (u *util) cgroupCPUTimes(version int) (*cpuTimesStat, error) { return info, nil } -func (u *util) DiskUsage(path string) (*disk.UsageStat, error) { - return disk.Usage(path) +func (u *util) Disk(path string) (*DiskInfo, error) { + usage, err := disk.Usage(path) + if err != nil { + return nil, err + } + + info := &DiskInfo{ + Path: usage.Path, + Fstype: usage.Fstype, + Total: usage.Total, + Used: usage.Used, + InodesTotal: usage.InodesTotal, + InodesUsed: usage.InodesUsed, + } + + return info, nil } -func DiskUsage(path string) (*disk.UsageStat, error) { - return DefaultUtil.DiskUsage(path) +func Disk(path string) (*DiskInfo, error) { + return DefaultUtil.Disk(path) } -func (u *util) virtualMemory() (*MemoryInfoStat, error) { +func (u *util) virtualMemory() (*MemoryInfo, error) { info, err := mem.VirtualMemory() if err != nil { return nil, err @@ -489,18 +513,18 @@ func (u *util) virtualMemory() (*MemoryInfoStat, error) { } } - return &MemoryInfoStat{ + return &MemoryInfo{ Total: info.Total, Available: info.Available, Used: info.Used, }, nil } -func (u *util) VirtualMemory() (*MemoryInfoStat, error) { +func (u *util) Memory() (*MemoryInfo, error) { u.lock.RLock() defer u.lock.RUnlock() - stat := &MemoryInfoStat{ + stat := &MemoryInfo{ Total: u.mem.Total, Available: u.mem.Available, Used: u.mem.Used, @@ -509,12 +533,12 @@ func (u *util) VirtualMemory() (*MemoryInfoStat, error) { return stat, nil } -func VirtualMemory() (*MemoryInfoStat, error) { - return DefaultUtil.VirtualMemory() +func Memory() (*MemoryInfo, error) { + return DefaultUtil.Memory() } -func (u *util) cgroupVirtualMemory(version int) (*MemoryInfoStat, error) { - info := &MemoryInfoStat{} +func (u *util) cgroupVirtualMemory(version int) (*MemoryInfo, error) { + info := &MemoryInfo{} if version == 1 { lines, err := u.readFile("memory/memory.limit_in_bytes") @@ -569,12 +593,27 @@ func (u *util) cgroupVirtualMemory(version int) (*MemoryInfoStat, error) { return info, nil } -func (u *util) NetIOCounters(pernic bool) ([]net.IOCountersStat, error) { - return net.IOCounters(pernic) +func (u *util) Network() ([]NetworkInfo, error) { + netio, err := net.IOCounters(true) + if err != nil { + return nil, err + } + + info := []NetworkInfo{} + + for _, io := range netio { + info = append(info, NetworkInfo{ + Name: io.Name, + BytesSent: io.BytesSent, + BytesRecv: io.BytesRecv, + }) + } + + return info, nil } -func NetIOCounters(pernic bool) ([]net.IOCountersStat, error) { - return DefaultUtil.NetIOCounters(pernic) +func Network() ([]NetworkInfo, error) { + return DefaultUtil.Network() } func (u *util) readFile(path string) ([]string, error) { @@ -613,29 +652,28 @@ func cpuTotal(c *cpu.TimesStat) float64 { c.Softirq + c.Steal + c.Guest + c.GuestNice } -func (u *util) GPUStats() ([]GPUInfoStat, error) { +func (u *util) GPU() ([]GPUInfo, error) { nvstats, err := nvidia.Default.Stats() if err != nil { return nil, err } - stats := []GPUInfoStat{} + stats := []GPUInfo{} for _, nv := range nvstats { - stats = append(stats, GPUInfoStat{ - Name: nv.Name, - MemoryTotal: nv.MemoryTotal, - MemoryUsed: nv.MemoryUsed, - Usage: nv.Usage, - MemoryUsage: nv.MemoryUsage, - EncoderUsage: nv.EncoderUsage, - DecoderUsage: nv.DecoderUsage, + stats = append(stats, GPUInfo{ + Name: nv.Name, + MemoryTotal: nv.MemoryTotal, + MemoryUsed: nv.MemoryUsed, + Usage: nv.Usage, + Encoder: nv.Encoder, + Decoder: nv.Decoder, }) } return stats, nil } -func GPUStats() ([]GPUInfoStat, error) { - return DefaultUtil.GPUStats() +func GPU() ([]GPUInfo, error) { + return DefaultUtil.GPU() } diff --git a/resources/resources.go b/resources/resources.go index d7255f05..5a4043d5 100644 --- a/resources/resources.go +++ b/resources/resources.go @@ -9,11 +9,13 @@ import ( "github.com/datarhei/core/v16/log" "github.com/datarhei/core/v16/psutil" + "github.com/datarhei/core/v16/slices" ) type Info struct { Mem MemoryInfo CPU CPUInfo + GPU GPUInfo } type MemoryInfo struct { @@ -38,6 +40,44 @@ type CPUInfo struct { Error error } +type GPUInfo struct { + NGPU float64 // number of gpus + GPU []GPUInfoStat + Error error +} + +type GPUInfoStat struct { + Index int + Name string + + // Memory + MemoryTotal uint64 // bytes + MemoryUsed uint64 // bytes + MemoryAvailable uint64 // bytes + MemoryLimit uint64 // bytes + + // GPU + Usage float64 // percent 0-100 + Encoder float64 // percent 0-100 + Decoder float64 // percent 0-100 + UsageLimit float64 // percent 0-100 + + Throttling bool +} + +type Request struct { + CPU float64 // percent 0-100*ncpu + Memory uint64 // bytes + GPUUsage float64 // percent 0-100 + GPUEncoder float64 // percent 0-100 + GPUDecoder float64 // percent 0-100 + GPUMemory uint64 // bytes +} + +type Response struct { + GPU int // GPU number, hwdevice +} + type resources struct { psutil psutil.Util @@ -45,9 +85,14 @@ type resources struct { maxCPU float64 // percent 0-100*ncpu maxMemory uint64 // bytes + ngpu int + maxGPU float64 // general usage, percent 0-100 + maxGPUMemory float64 // memory usage, percent 0-100 + isUnlimited bool isCPULimiting bool isMemoryLimiting bool + isGPULimiting []bool self psutil.Process @@ -67,30 +112,46 @@ type Resources interface { // HasLimits returns whether any limits have been set. HasLimits() bool - // Limits returns the CPU (percent 0-100) and memory (bytes) limits. - Limits() (float64, uint64) + // Limits returns the CPU (percent 0-100), memory (bytes) limits, and GPU limits (usage and memory each in percent 0-100). + Limits() (float64, uint64, float64, float64) - // ShouldLimit returns whether cpu and/or memory is currently limited. - ShouldLimit() (bool, bool) + // ShouldLimit returns whether cpu, memory, and/or GPU is currently limited. + ShouldLimit() (bool, bool, []bool) // Request checks whether the requested resources are available. - Request(cpu float64, memory uint64) error + Request(req Request) (Response, error) - // Info returns the current resource usage + // Info returns the current resource usage. Info() Info } type Config struct { - MaxCPU float64 // percent 0-100 - MaxMemory float64 // percent 0-100 - PSUtil psutil.Util - Logger log.Logger + MaxCPU float64 // percent 0-100 + MaxMemory float64 // percent 0-100 + MaxGPU float64 // general,encoder,decoder usage, percent 0-100 + MaxGPUMemory float64 // memory usage, percent 0-100 + PSUtil psutil.Util + Logger log.Logger } func New(config Config) (Resources, error) { + if config.PSUtil == nil { + config.PSUtil = psutil.DefaultUtil + } + + gpu, err := config.PSUtil.GPU() + if err != nil { + return nil, fmt.Errorf("unable to determine number of GPUs: %w", err) + } + + if len(gpu) == 0 { + config.MaxGPU = 0 + config.MaxGPUMemory = 0 + } + isUnlimited := false - if config.MaxCPU <= 0 && config.MaxMemory <= 0 { + if config.MaxCPU <= 0 && config.MaxMemory <= 0 && config.MaxGPU <= 0 && config.MaxGPUMemory <= 0 { isUnlimited = true } @@ -102,31 +163,39 @@ func New(config Config) (Resources, error) { config.MaxMemory = 100 } - if config.MaxCPU > 100 || config.MaxMemory > 100 { - return nil, fmt.Errorf("both MaxCPU and MaxMemory must have a range of 0-100") + if config.MaxGPU <= 0 { + config.MaxGPU = 100 + } + + if config.MaxGPUMemory <= 0 { + config.MaxGPUMemory = 100 + } + + if config.MaxCPU > 100 || config.MaxMemory > 100 || config.MaxGPU > 100 || config.MaxGPUMemory > 100 { + return nil, fmt.Errorf("all Max... values must have a range of 0-100") } r := &resources{ - maxCPU: config.MaxCPU, - psutil: config.PSUtil, - isUnlimited: isUnlimited, - logger: config.Logger, + maxCPU: config.MaxCPU, + maxGPU: config.MaxGPU, + maxGPUMemory: config.MaxGPUMemory, + psutil: config.PSUtil, + isUnlimited: isUnlimited, + ngpu: len(gpu), + isGPULimiting: make([]bool, len(gpu)), + logger: config.Logger, } if r.logger == nil { r.logger = log.New("") } - if r.psutil == nil { - r.psutil = psutil.DefaultUtil - } - - vmstat, err := r.psutil.VirtualMemory() + vmstat, err := r.psutil.Memory() if err != nil { return nil, fmt.Errorf("unable to determine available memory: %w", err) } - ncpu, err := r.psutil.CPUCounts(true) + ncpu, err := r.psutil.CPUCounts() if err != nil { return nil, fmt.Errorf("unable to determine number of logical CPUs: %w", err) } @@ -137,12 +206,15 @@ func New(config Config) (Resources, error) { r.maxMemory = uint64(float64(vmstat.Total) * config.MaxMemory / 100) r.logger = r.logger.WithFields(log.Fields{ - "ncpu": r.ncpu, - "max_cpu": r.maxCPU, - "max_memory": r.maxMemory, + "ncpu": r.ncpu, + "max_cpu": r.maxCPU, + "max_memory": r.maxMemory, + "ngpu": len(gpu), + "max_gpu": r.maxGPU, + "max_gpu_memory": r.maxGPUMemory, }) - r.self, err = psutil.NewProcess(int32(os.Getpid()), false) + r.self, err = r.psutil.Process(int32(os.Getpid())) if err != nil { return nil, fmt.Errorf("unable to create process observer for self: %w", err) } @@ -189,7 +261,12 @@ func (r *resources) observe(ctx context.Context, interval time.Duration) { case <-ctx.Done(): return case <-ticker.C: - cpustat, err := r.psutil.CPUPercent() + if r.isUnlimited { + // If there aren't any limits imposed, don't do anything + continue + } + + cpustat, err := r.psutil.CPU() if err != nil { r.logger.Warn().WithError(err).Log("Failed to determine system CPU usage") continue @@ -197,12 +274,18 @@ func (r *resources) observe(ctx context.Context, interval time.Duration) { cpuload := (cpustat.User + cpustat.System + cpustat.Other) * r.ncpu - vmstat, err := r.psutil.VirtualMemory() + vmstat, err := r.psutil.Memory() if err != nil { r.logger.Warn().WithError(err).Log("Failed to determine system memory usage") continue } + gpustat, err := r.psutil.GPU() + if err != nil { + r.logger.Warn().WithError(err).Log("Failed to determine GPU usage") + continue + } + r.logger.Debug().WithFields(log.Fields{ "cur_cpu": cpuload, "cur_memory": vmstat.Used, @@ -210,34 +293,46 @@ func (r *resources) observe(ctx context.Context, interval time.Duration) { doCPULimit := false - if !r.isUnlimited { - if !r.isCPULimiting { - if cpuload >= r.maxCPU { - r.logger.Debug().WithField("cpu", cpuload).Log("CPU limit reached") - doCPULimit = true - } - } else { + if !r.isCPULimiting { + if cpuload >= r.maxCPU { + r.logger.Debug().WithField("cpu", cpuload).Log("CPU limit reached") doCPULimit = true - if cpuload < r.maxCPU { - r.logger.Debug().WithField("cpu", cpuload).Log("CPU limit released") - doCPULimit = false - } + } + } else { + doCPULimit = true + if cpuload < r.maxCPU { + r.logger.Debug().WithField("cpu", cpuload).Log("CPU limit released") + doCPULimit = false } } doMemoryLimit := false - if !r.isUnlimited { - if !r.isMemoryLimiting { - if vmstat.Used >= r.maxMemory { - r.logger.Debug().WithField("memory", vmstat.Used).Log("Memory limit reached") - doMemoryLimit = true + if !r.isMemoryLimiting { + if vmstat.Used >= r.maxMemory { + r.logger.Debug().WithField("memory", vmstat.Used).Log("Memory limit reached") + doMemoryLimit = true + } + } else { + doMemoryLimit = true + if vmstat.Used < r.maxMemory { + r.logger.Debug().WithField("memory", vmstat.Used).Log("Memory limit released") + doMemoryLimit = false + } + } + + doGPULimit := make([]bool, r.ngpu) + + for i, limiting := range r.isGPULimiting { + maxMemory := uint64(r.maxGPUMemory * float64(gpustat[i].MemoryTotal) / 100) + if !limiting { + if gpustat[i].MemoryUsed >= maxMemory || (gpustat[i].Usage >= r.maxGPU && gpustat[i].Encoder >= r.maxGPU && gpustat[i].Decoder >= r.maxGPU) { + doGPULimit[i] = true } } else { - doMemoryLimit = true - if vmstat.Used < r.maxMemory { - r.logger.Debug().WithField("memory", vmstat.Used).Log("Memory limit released") - doMemoryLimit = false + doGPULimit[i] = true + if gpustat[i].MemoryUsed < maxMemory && (gpustat[i].Usage < r.maxGPU || gpustat[i].Encoder < r.maxGPU || gpustat[i].Decoder < r.maxGPU) { + doGPULimit[i] = false } } } @@ -247,17 +342,26 @@ func (r *resources) observe(ctx context.Context, interval time.Duration) { r.logger.Warn().WithFields(log.Fields{ "enabled": doCPULimit, }).Log("Limiting CPU") - - r.isCPULimiting = doCPULimit } + r.isCPULimiting = doCPULimit if r.isMemoryLimiting != doMemoryLimit { r.logger.Warn().WithFields(log.Fields{ "enabled": doMemoryLimit, }).Log("Limiting memory") - - r.isMemoryLimiting = doMemoryLimit } + r.isMemoryLimiting = doMemoryLimit + + for i, limiting := range r.isGPULimiting { + if limiting != doGPULimit[i] { + r.logger.Warn().WithFields(log.Fields{ + "enabled": doGPULimit, + "index": i, + }).Log("Limiting GPU") + } + } + r.isGPULimiting = doGPULimit + r.lock.Unlock() } } @@ -267,60 +371,136 @@ func (r *resources) HasLimits() bool { return !r.isUnlimited } -func (r *resources) Limits() (float64, uint64) { - return r.maxCPU / r.ncpu, r.maxMemory +func (r *resources) Limits() (float64, uint64, float64, float64) { + return r.maxCPU / r.ncpu, r.maxMemory, r.maxGPU, r.maxGPUMemory } -func (r *resources) ShouldLimit() (bool, bool) { +func (r *resources) ShouldLimit() (bool, bool, []bool) { r.lock.RLock() defer r.lock.RUnlock() - return r.isCPULimiting, r.isMemoryLimiting + return r.isCPULimiting, r.isMemoryLimiting, slices.Copy(r.isGPULimiting) } -func (r *resources) Request(cpu float64, memory uint64) error { +func (r *resources) Request(req Request) (Response, error) { + res := Response{ + GPU: -1, + } + r.lock.RLock() defer r.lock.RUnlock() logger := r.logger.WithFields(log.Fields{ - "req_cpu": cpu, - "req_memory": memory, + "req_cpu": req.CPU, + "req_memory": req.Memory, + "req_gpu": req.GPUUsage, + "req_gpu_encoder": req.GPUEncoder, + "req_gpu_decoder": req.GPUDecoder, + "req_gpu_memory": req.GPUMemory, }) logger.Debug().Log("Request for acquiring resources") + // Check if anything is currently limiting. if r.isCPULimiting || r.isMemoryLimiting { logger.Debug().Log("Rejected, currently limiting") - return fmt.Errorf("resources are currenlty actively limited") + return res, fmt.Errorf("resources are currenlty actively limited") } - if cpu <= 0 || memory == 0 { + // Check if the requested resources are valid. + if req.CPU <= 0 || req.Memory == 0 { logger.Debug().Log("Rejected, invalid values") - return fmt.Errorf("the cpu and/or memory values are invalid: cpu=%f, memory=%d", cpu, memory) + return res, fmt.Errorf("the cpu and/or memory values are invalid: cpu=%f, memory=%d", req.CPU, req.Memory) } - cpustat, err := r.psutil.CPUPercent() + // Get current CPU and memory values. + cpustat, err := r.psutil.CPU() if err != nil { r.logger.Warn().WithError(err).Log("Failed to determine system CPU usage") - return fmt.Errorf("the system CPU usage couldn't be determined") + return res, fmt.Errorf("the system CPU usage couldn't be determined") } cpuload := (cpustat.User + cpustat.System + cpustat.Other) * r.ncpu - vmstat, err := r.psutil.VirtualMemory() + vmstat, err := r.psutil.Memory() if err != nil { r.logger.Warn().WithError(err).Log("Failed to determine system memory usage") - return fmt.Errorf("the system memory usage couldn't be determined") + return res, fmt.Errorf("the system memory usage couldn't be determined") } - if cpuload+cpu > r.maxCPU { + // Check if enough resources are available + if cpuload+req.CPU > r.maxCPU { logger.Debug().WithField("cur_cpu", cpuload).Log("Rejected, CPU limit exceeded") - return fmt.Errorf("the CPU limit would be exceeded: %f + %f > %f", cpuload, cpu, r.maxCPU) + return res, fmt.Errorf("the CPU limit would be exceeded: %f + %f > %f", cpuload, req.CPU, r.maxCPU) } - if vmstat.Used+memory > r.maxMemory { + if vmstat.Used+req.Memory > r.maxMemory { logger.Debug().WithField("cur_memory", vmstat.Used).Log("Rejected, memory limit exceeded") - return fmt.Errorf("the memory limit would be exceeded: %d + %d > %d", vmstat.Used, memory, r.maxMemory) + return res, fmt.Errorf("the memory limit would be exceeded: %d + %d > %d", vmstat.Used, req.Memory, r.maxMemory) + } + + // Check if any GPU resources are requested + if req.GPUUsage > 0 || req.GPUEncoder > 0 || req.GPUDecoder > 0 || req.GPUMemory > 0 { + if req.GPUUsage < 0 || req.GPUEncoder < 0 || req.GPUDecoder < 0 || req.GPUMemory == 0 { + logger.Debug().Log("Rejected, invalid values") + return res, fmt.Errorf("the gpu usage and memory values are invalid: usage=%f, encoder=%f, decoder=%f, memory=%d", req.GPUUsage, req.GPUEncoder, req.GPUDecoder, req.GPUMemory) + } + + // Get current GPU values + gpustat, err := r.psutil.GPU() + if err != nil { + r.logger.Warn().WithError(err).Log("Failed to determine GPU usage") + return res, fmt.Errorf("the GPU usage couldn't be determined") + } + + if len(gpustat) == 0 { + r.logger.Debug().WithError(err).Log("GPU resources requested but no GPU available") + return res, fmt.Errorf("some GPU resources requested but no GPU available") + } + + foundGPU := -1 + for _, g := range gpustat { + if req.GPUUsage > 0 && g.Usage+req.GPUUsage > r.maxGPU { + logger.Debug().WithFields(log.Fields{"id": g.Index, "cur_gpu": g.Usage}).Log("Rejected, GPU usage limit exceeded") + continue + } + + if req.GPUEncoder > 0 && g.Encoder+req.GPUEncoder > r.maxGPU { + logger.Debug().WithFields(log.Fields{"id": g.Index, "cur_gpu_encoder": g.Usage}).Log("Rejected, GPU encoder usage limit exceeded") + continue + } + + if req.GPUDecoder > 0 && g.Decoder+req.GPUDecoder > r.maxGPU { + logger.Debug().WithFields(log.Fields{"id": g.Index, "cur_gpu_decoder": g.Usage}).Log("Rejected, GPU decoder usage limit exceeded") + continue + } + + gpuMemoryUsage := float64(g.MemoryUsed) / float64(g.MemoryTotal) * 100 + requestedGPUMemoryUsage := float64(req.GPUMemory) / float64(g.MemoryTotal) * 100 + + if gpuMemoryUsage+requestedGPUMemoryUsage > r.maxGPUMemory { + logger.Debug().WithFields(log.Fields{"id": g.Index, "cur_gpu_memory": gpuMemoryUsage}).Log("Rejected, GPU memory usage limit exceeded") + continue + } + + foundGPU = g.Index + + logger = logger.Debug().WithFields(log.Fields{ + "cur_gpu": foundGPU, + "cur_gpu_general": g.Usage, + "cur_gpu_encoder": g.Encoder, + "cur_gpu_decoder": g.Decoder, + "cur_gpu_memory": gpuMemoryUsage, + }) + + break + } + + if foundGPU < 0 { + return res, fmt.Errorf("all GPU usage limits are exceeded") + } + + res.GPU = foundGPU } logger.Debug().WithFields(log.Fields{ @@ -328,17 +508,18 @@ func (r *resources) Request(cpu float64, memory uint64) error { "cur_memory": vmstat.Used, }).Log("Acquiring approved") - return nil + return res, nil } func (r *resources) Info() Info { - cpulimit, memlimit := r.Limits() - cputhrottling, memthrottling := r.ShouldLimit() + cpulimit, memlimit, gpulimit, gpumemlimit := r.Limits() + cputhrottling, memthrottling, gputhrottling := r.ShouldLimit() - cpustat, cpuerr := r.psutil.CPUPercent() - memstat, memerr := r.psutil.VirtualMemory() - selfcpu, _ := r.self.CPUPercent() - selfmem, _ := r.self.VirtualMemory() + cpustat, cpuerr := r.psutil.CPU() + memstat, memerr := r.psutil.Memory() + gpustat, gpuerr := r.psutil.GPU() + selfcpu, _ := r.self.CPU() + selfmem, _ := r.self.Memory() cpuinfo := CPUInfo{ NCPU: r.ncpu, @@ -362,9 +543,31 @@ func (r *resources) Info() Info { Error: memerr, } + gpuinfo := GPUInfo{ + NGPU: float64(len(gpustat)), + Error: gpuerr, + } + + for i, g := range gpustat { + gpuinfo.GPU = append(gpuinfo.GPU, GPUInfoStat{ + Index: g.Index, + Name: g.Name, + MemoryTotal: g.MemoryTotal, + MemoryUsed: g.MemoryUsed, + MemoryAvailable: g.MemoryTotal - g.MemoryUsed, + MemoryLimit: uint64(float64(g.MemoryTotal) * gpumemlimit / 100), + Usage: g.Usage, + Encoder: g.Encoder, + Decoder: g.Decoder, + UsageLimit: gpulimit, + Throttling: gputhrottling[i], + }) + } + i := Info{ CPU: cpuinfo, Mem: meminfo, + GPU: gpuinfo, } return i diff --git a/resources/resources_test.go b/resources/resources_test.go index 3d26c40c..a1ee4244 100644 --- a/resources/resources_test.go +++ b/resources/resources_test.go @@ -1,68 +1,170 @@ package resources import ( + "slices" "sync" "testing" "time" "github.com/datarhei/core/v16/psutil" - "github.com/shirou/gopsutil/v3/disk" - "github.com/shirou/gopsutil/v3/net" "github.com/stretchr/testify/require" ) -type util struct{} +type util struct { + lock sync.Mutex + + cpu psutil.CPUInfo + mem psutil.MemoryInfo + gpu []psutil.GPUInfo +} + +func newUtil(ngpu int) *util { + u := &util{ + cpu: psutil.CPUInfo{ + System: 10, + User: 50, + Idle: 35, + Other: 5, + }, + mem: psutil.MemoryInfo{ + Total: 200, + Available: 40, + Used: 160, + }, + } + + for i := 0; i < ngpu; i++ { + u.gpu = append(u.gpu, psutil.GPUInfo{ + Index: i, + Name: "L4", + MemoryTotal: 24 * 1024 * 1024 * 1024, + MemoryUsed: uint64(12+i) * 1024 * 1024 * 1024, + Usage: 50 - float64((i+1)*5), + Encoder: 50 - float64((i+1)*10), + Decoder: 50 - float64((i+1)*3), + }) + } + + return u +} func (u *util) Start() {} func (u *util) Stop() {} -func (u *util) CPUCounts(logical bool) (float64, error) { +func (u *util) CPUCounts() (float64, error) { return 2, nil } -func (u *util) GPUCounts() (float64, error) { - return 0, nil -} +func (u *util) CPU() (*psutil.CPUInfo, error) { + u.lock.Lock() + defer u.lock.Unlock() -func (u *util) CPUPercent() (*psutil.CPUInfoStat, error) { - return &psutil.CPUInfoStat{ - System: 10, - User: 50, - Idle: 35, - Other: 5, - }, nil + cpu := u.cpu + + return &cpu, nil } -func (u *util) DiskUsage(path string) (*disk.UsageStat, error) { - return &disk.UsageStat{}, nil +func (u *util) Disk(path string) (*psutil.DiskInfo, error) { + return &psutil.DiskInfo{}, nil } -func (u *util) VirtualMemory() (*psutil.MemoryInfoStat, error) { - return &psutil.MemoryInfoStat{ - Total: 200, - Available: 40, - Used: 160, - }, nil +func (u *util) Memory() (*psutil.MemoryInfo, error) { + u.lock.Lock() + defer u.lock.Unlock() + + mem := u.mem + + return &mem, nil } -func (u *util) NetIOCounters(pernic bool) ([]net.IOCountersStat, error) { +func (u *util) Network() ([]psutil.NetworkInfo, error) { return nil, nil } -func (u *util) GPUStats() ([]psutil.GPUInfoStat, error) { - return nil, nil +func (u *util) GPU() ([]psutil.GPUInfo, error) { + u.lock.Lock() + defer u.lock.Unlock() + + gpu := []psutil.GPUInfo{} + + gpu = append(gpu, u.gpu...) + + return gpu, nil } func (u *util) Process(pid int32) (psutil.Process, error) { - return nil, nil + return &process{}, nil +} + +type process struct{} + +func (p *process) CPU() (*psutil.CPUInfo, error) { + s := &psutil.CPUInfo{ + System: 1, + User: 2, + Idle: 0, + Other: 3, + } + + return s, nil +} + +func (p *process) Memory() (uint64, error) { return 42, nil } +func (p *process) GPU() (*psutil.GPUInfo, error) { + return &psutil.GPUInfo{ + Index: 0, + Name: "L4", + MemoryTotal: 128, + MemoryUsed: 42, + Usage: 5, + Encoder: 9, + Decoder: 7, + }, nil +} +func (p *process) Stop() {} +func (p *process) Suspend() error { return nil } +func (p *process) Resume() error { return nil } + +func TestConfigNoLimits(t *testing.T) { + _, err := New(Config{ + PSUtil: newUtil(0), + }) + require.NoError(t, err) +} + +func TestConfigWrongLimits(t *testing.T) { + _, err := New(Config{ + MaxCPU: 102, + MaxMemory: 573, + PSUtil: newUtil(0), + }) + require.Error(t, err) + + _, err = New(Config{ + MaxCPU: 0, + MaxMemory: 0, + MaxGPU: 101, + MaxGPUMemory: 103, + PSUtil: newUtil(0), + }) + require.NoError(t, err) + + _, err = New(Config{ + MaxCPU: 0, + MaxMemory: 0, + MaxGPU: 101, + MaxGPUMemory: 103, + PSUtil: newUtil(1), + }) + require.Error(t, err) } func TestMemoryLimit(t *testing.T) { r, err := New(Config{ MaxCPU: 100, MaxMemory: 150. / 200. * 100, - PSUtil: &util{}, + PSUtil: newUtil(0), Logger: nil, }) require.NoError(t, err) @@ -86,7 +188,7 @@ func TestMemoryLimit(t *testing.T) { for { select { case <-ticker.C: - _, limit = r.ShouldLimit() + _, limit, _ = r.ShouldLimit() if limit { return } @@ -102,14 +204,19 @@ func TestMemoryLimit(t *testing.T) { require.True(t, limit) + _, err = r.Request(Request{CPU: 5, Memory: 10}) + require.Error(t, err) + r.Stop() } -func TestCPULimit(t *testing.T) { +func TestMemoryUnlimit(t *testing.T) { + util := newUtil(0) + r, err := New(Config{ - MaxCPU: 50., - MaxMemory: 100, - PSUtil: &util{}, + MaxCPU: 100, + MaxMemory: 150. / 200. * 100, + PSUtil: util, Logger: nil, }) require.NoError(t, err) @@ -133,7 +240,7 @@ func TestCPULimit(t *testing.T) { for { select { case <-ticker.C: - limit, _ = r.ShouldLimit() + _, limit, _ = r.ShouldLimit() if limit { return } @@ -149,59 +256,733 @@ func TestCPULimit(t *testing.T) { require.True(t, limit) + _, limit, _ = r.ShouldLimit() + require.True(t, limit) + + util.lock.Lock() + util.mem.Used = 140 + util.lock.Unlock() + + wg.Add(1) + + go func() { + defer func() { + wg.Done() + }() + + timer := time.NewTimer(10 * time.Second) + defer timer.Stop() + + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + _, limit, _ = r.ShouldLimit() + if !limit { + return + } + case <-timer.C: + return + } + } + }() + + wg.Wait() + + require.False(t, limit) + r.Stop() } -func TestRequest(t *testing.T) { +func TestCPULimit(t *testing.T) { r, err := New(Config{ - MaxCPU: 70., - MaxMemory: 170. / 200. * 100, - PSUtil: &util{}, + MaxCPU: 50., + MaxMemory: 100, + PSUtil: newUtil(0), Logger: nil, }) require.NoError(t, err) - err = r.Request(-1, 0) - require.Error(t, err) + wg := sync.WaitGroup{} + wg.Add(1) - err = r.Request(5, 10) - require.NoError(t, err) + limit := false + + go func() { + defer func() { + wg.Done() + }() + + timer := time.NewTimer(10 * time.Second) + defer timer.Stop() + + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + limit, _, _ = r.ShouldLimit() + if limit { + return + } + case <-timer.C: + return + } + } + }() + + r.Start() + + wg.Wait() + + require.True(t, limit) - err = r.Request(5, 20) + _, err = r.Request(Request{CPU: 5, Memory: 10}) require.Error(t, err) - err = r.Request(10, 10) - require.NoError(t, err) + r.Stop() } -func TestHasLimits(t *testing.T) { +func TestCPUUnlimit(t *testing.T) { + util := newUtil(0) + r, err := New(Config{ - MaxCPU: 70., - MaxMemory: 170. / 200. * 100, - PSUtil: &util{}, + MaxCPU: 50., + MaxMemory: 100, + PSUtil: util, Logger: nil, }) require.NoError(t, err) - require.True(t, r.HasLimits()) + wg := sync.WaitGroup{} + wg.Add(1) - r, err = New(Config{ - MaxCPU: 100, - MaxMemory: 100, - PSUtil: &util{}, - Logger: nil, + limit := false + + go func() { + defer func() { + wg.Done() + }() + + timer := time.NewTimer(10 * time.Second) + defer timer.Stop() + + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + limit, _, _ = r.ShouldLimit() + if limit { + return + } + case <-timer.C: + return + } + } + }() + + r.Start() + + wg.Wait() + + require.True(t, limit) + + limit, _, _ = r.ShouldLimit() + require.True(t, limit) + + util.lock.Lock() + util.cpu.User = 20 + util.lock.Unlock() + + wg.Add(1) + + go func() { + defer func() { + wg.Done() + }() + + timer := time.NewTimer(10 * time.Second) + defer timer.Stop() + + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + limit, _, _ = r.ShouldLimit() + if !limit { + return + } + case <-timer.C: + return + } + } + }() + + wg.Wait() + + require.False(t, limit) + + r.Stop() +} + +func TestGPULimitMemory(t *testing.T) { + r, err := New(Config{ + MaxCPU: 100, + MaxMemory: 100, + MaxGPU: 100, + MaxGPUMemory: 20, + PSUtil: newUtil(2), + Logger: nil, }) require.NoError(t, err) - require.True(t, r.HasLimits()) + wg := sync.WaitGroup{} + wg.Add(1) - r, err = New(Config{ - MaxCPU: 0, - MaxMemory: 0, - PSUtil: &util{}, - Logger: nil, + limit := []bool{} + + go func() { + defer func() { + wg.Done() + }() + + timer := time.NewTimer(10 * time.Second) + defer timer.Stop() + + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + _, _, limit = r.ShouldLimit() + if slices.Contains(limit, true) { + return + } + case <-timer.C: + return + } + } + }() + + r.Start() + + wg.Wait() + + require.Contains(t, limit, true) + + _, err = r.Request(Request{CPU: 5, Memory: 10, GPUUsage: 10, GPUMemory: 10}) + require.Error(t, err) + + r.Stop() +} + +func TestGPUUnlimitMemory(t *testing.T) { + util := newUtil(2) + + r, err := New(Config{ + MaxCPU: 100, + MaxMemory: 100, + MaxGPU: 100, + MaxGPUMemory: 20, + PSUtil: util, + Logger: nil, }) require.NoError(t, err) - require.False(t, r.HasLimits()) + wg := sync.WaitGroup{} + wg.Add(1) + + limit := []bool{} + + go func() { + defer func() { + wg.Done() + }() + + timer := time.NewTimer(10 * time.Second) + defer timer.Stop() + + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + _, _, limit = r.ShouldLimit() + if slices.Contains(limit, true) { + return + } + case <-timer.C: + return + } + } + }() + + r.Start() + + wg.Wait() + + require.Contains(t, limit, true) + + util.lock.Lock() + util.gpu[0].MemoryUsed = 10 + util.gpu[1].MemoryUsed = 10 + util.lock.Unlock() + + wg.Add(1) + + go func() { + defer func() { + wg.Done() + }() + + timer := time.NewTimer(10 * time.Second) + defer timer.Stop() + + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + _, _, limit = r.ShouldLimit() + if !slices.Contains(limit, true) { + return + } + case <-timer.C: + return + } + } + }() + + wg.Wait() + + require.NotContains(t, limit, true) + + r.Stop() +} + +func TestGPULimitMemorySome(t *testing.T) { + r, err := New(Config{ + MaxCPU: 100, + MaxMemory: 100, + MaxGPU: 100, + MaxGPUMemory: 14. / 24. * 100., + PSUtil: newUtil(4), + Logger: nil, + }) + require.NoError(t, err) + + wg := sync.WaitGroup{} + wg.Add(1) + + limit := []bool{} + + go func() { + defer func() { + wg.Done() + }() + + timer := time.NewTimer(10 * time.Second) + defer timer.Stop() + + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + _, _, limit = r.ShouldLimit() + if slices.Contains(limit, true) { + return + } + case <-timer.C: + return + } + } + }() + + r.Start() + + wg.Wait() + + require.Equal(t, []bool{false, false, true, true}, limit) + + _, err = r.Request(Request{CPU: 5, Memory: 10, GPUUsage: 10, GPUMemory: 10}) + require.NoError(t, err) + + r.Stop() +} + +func TestGPULimitUsage(t *testing.T) { + r, err := New(Config{ + MaxCPU: 100, + MaxMemory: 100, + MaxGPU: 40, + MaxGPUMemory: 100, + PSUtil: newUtil(3), + Logger: nil, + }) + require.NoError(t, err) + + wg := sync.WaitGroup{} + wg.Add(1) + + limit := []bool{} + + go func() { + defer func() { + wg.Done() + }() + + timer := time.NewTimer(10 * time.Second) + defer timer.Stop() + + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + _, _, limit = r.ShouldLimit() + if slices.Contains(limit, true) { + return + } + case <-timer.C: + return + } + } + }() + + r.Start() + + wg.Wait() + + require.Equal(t, []bool{true, false, false}, limit) + + _, err = r.Request(Request{CPU: 5, Memory: 10, GPUUsage: 10, GPUMemory: 10}) + require.Error(t, err) + + _, err = r.Request(Request{CPU: 5, Memory: 10, GPUEncoder: 10, GPUMemory: 10}) + require.NoError(t, err) + + r.Stop() +} + +func TestGPUUnlimitUsage(t *testing.T) { + util := newUtil(3) + + r, err := New(Config{ + MaxCPU: 100, + MaxMemory: 100, + MaxGPU: 40, + MaxGPUMemory: 100, + PSUtil: util, + Logger: nil, + }) + require.NoError(t, err) + + wg := sync.WaitGroup{} + wg.Add(1) + + limit := []bool{} + + go func() { + defer func() { + wg.Done() + }() + + timer := time.NewTimer(10 * time.Second) + defer timer.Stop() + + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + _, _, limit = r.ShouldLimit() + if slices.Contains(limit, true) { + return + } + case <-timer.C: + return + } + } + }() + + r.Start() + + wg.Wait() + + require.Equal(t, []bool{true, false, false}, limit) + + util.lock.Lock() + util.gpu[0].Usage = 30 + util.gpu[0].Encoder = 30 + util.gpu[0].Decoder = 30 + util.lock.Unlock() + + wg.Add(1) + + go func() { + defer func() { + wg.Done() + }() + + timer := time.NewTimer(10 * time.Second) + defer timer.Stop() + + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + _, _, limit = r.ShouldLimit() + if !slices.Contains(limit, true) { + return + } + case <-timer.C: + return + } + } + }() + + wg.Wait() + + require.Equal(t, []bool{false, false, false}, limit) + + r.Stop() +} + +func TestRequestCPU(t *testing.T) { + r, err := New(Config{ + MaxCPU: 70., + PSUtil: newUtil(0), + }) + require.NoError(t, err) + + _, err = r.Request(Request{CPU: 0, Memory: 0}) + require.Error(t, err) + + _, err = r.Request(Request{CPU: 5, Memory: 10}) + require.NoError(t, err) + + _, err = r.Request(Request{CPU: 30, Memory: 10}) + require.Error(t, err) +} + +func TestRequestMemory(t *testing.T) { + r, err := New(Config{ + MaxMemory: 170. / 200. * 100, + PSUtil: newUtil(0), + }) + require.NoError(t, err) + + _, err = r.Request(Request{CPU: 5, Memory: 0}) + require.Error(t, err) + + _, err = r.Request(Request{CPU: 5, Memory: 10}) + require.NoError(t, err) + + _, err = r.Request(Request{CPU: 50, Memory: 20}) + require.Error(t, err) +} + +func TestRequestNoGPU(t *testing.T) { + r, err := New(Config{ + MaxCPU: 100, + MaxMemory: 100, + PSUtil: newUtil(0), + }) + require.NoError(t, err) + + _, err = r.Request(Request{CPU: 10, Memory: 10, GPUEncoder: 30, GPUMemory: 10}) + require.Error(t, err) +} + +func TestRequestInvalidGPURequest(t *testing.T) { + r, err := New(Config{ + MaxCPU: 100, + MaxMemory: 100, + PSUtil: newUtil(1), + }) + require.NoError(t, err) + + _, err = r.Request(Request{CPU: 10, Memory: 10, GPUEncoder: 30, GPUMemory: 0}) + require.Error(t, err) + + _, err = r.Request(Request{CPU: 10, Memory: 10, GPUUsage: -1, GPUEncoder: 30, GPUMemory: 0}) + require.Error(t, err) +} + +func TestRequestGPULimitsOneGPU(t *testing.T) { + r, err := New(Config{ + MaxCPU: 100, + MaxMemory: 100, + MaxGPU: 50, + MaxGPUMemory: 60, + PSUtil: newUtil(1), + }) + require.NoError(t, err) + + _, err = r.Request(Request{CPU: 10, Memory: 10, GPUUsage: 50, GPUMemory: 10}) + require.Error(t, err) + + _, err = r.Request(Request{CPU: 10, Memory: 10, GPUEncoder: 50, GPUMemory: 10}) + require.Error(t, err) + + _, err = r.Request(Request{CPU: 10, Memory: 10, GPUDecoder: 50, GPUMemory: 10}) + require.Error(t, err) + + _, err = r.Request(Request{CPU: 10, Memory: 10, GPUEncoder: 10, GPUMemory: 5 * 1024 * 1024 * 1024}) + require.Error(t, err) + + res, err := r.Request(Request{CPU: 10, Memory: 10, GPUEncoder: 10, GPUMemory: 10}) + require.NoError(t, err) + require.Equal(t, 0, res.GPU) +} + +func TestRequestGPULimitsMoreGPU(t *testing.T) { + r, err := New(Config{ + MaxCPU: 100, + MaxMemory: 100, + MaxGPU: 60, + MaxGPUMemory: 60, + PSUtil: newUtil(2), + }) + require.NoError(t, err) + + _, err = r.Request(Request{CPU: 10, Memory: 10, GPUEncoder: 50, GPUMemory: 10}) + require.Error(t, err) + + res, err := r.Request(Request{CPU: 10, Memory: 10, GPUEncoder: 30, GPUMemory: 10}) + require.NoError(t, err) + require.Equal(t, 1, res.GPU) +} + +func TestHasLimits(t *testing.T) { + r, err := New(Config{ + MaxCPU: 70., + MaxMemory: 170. / 200. * 100, + PSUtil: newUtil(0), + Logger: nil, + }) + require.NoError(t, err) + + require.True(t, r.HasLimits()) + + r, err = New(Config{ + MaxCPU: 100, + MaxMemory: 100, + PSUtil: newUtil(0), + Logger: nil, + }) + require.NoError(t, err) + + require.True(t, r.HasLimits()) + + r, err = New(Config{ + MaxCPU: 0, + MaxMemory: 0, + PSUtil: newUtil(0), + Logger: nil, + }) + require.NoError(t, err) + + require.False(t, r.HasLimits()) + + r, err = New(Config{ + MaxCPU: 0, + MaxMemory: 0, + MaxGPU: 10, + PSUtil: newUtil(1), + Logger: nil, + }) + require.NoError(t, err) + + require.True(t, r.HasLimits()) + + r, err = New(Config{ + MaxCPU: 0, + MaxMemory: 0, + MaxGPU: 10, + PSUtil: newUtil(0), + Logger: nil, + }) + require.NoError(t, err) + + require.False(t, r.HasLimits()) +} + +func TestInfo(t *testing.T) { + r, err := New(Config{ + MaxCPU: 90, + MaxMemory: 90, + MaxGPU: 11, + MaxGPUMemory: 50, + PSUtil: newUtil(2), + }) + require.NoError(t, err) + + info := r.Info() + + require.Equal(t, Info{ + Mem: MemoryInfo{ + Total: 200, + Available: 40, + Used: 160, + Limit: 180, + Core: 42, + Throttling: false, + Error: nil, + }, + CPU: CPUInfo{ + NCPU: 2, + System: 10, + User: 50, + Idle: 35, + Other: 5, + Limit: 90, + Core: 6, + Throttling: false, + Error: nil, + }, + GPU: GPUInfo{ + NGPU: 2, + GPU: []GPUInfoStat{{ + Index: 0, + Name: "L4", + MemoryTotal: 24 * 1024 * 1024 * 1024, + MemoryUsed: 12 * 1024 * 1024 * 1024, + MemoryAvailable: 12 * 1024 * 1024 * 1024, + MemoryLimit: 12 * 1024 * 1024 * 1024, + Usage: 45, + Encoder: 40, + Decoder: 47, + UsageLimit: 11, + }, { + Index: 1, + Name: "L4", + MemoryTotal: 24 * 1024 * 1024 * 1024, + MemoryUsed: 13 * 1024 * 1024 * 1024, + MemoryAvailable: 11 * 1024 * 1024 * 1024, + MemoryLimit: 12 * 1024 * 1024 * 1024, + Usage: 40, + Encoder: 30, + Decoder: 44, + UsageLimit: 11, + }}, + Error: nil, + }, + }, info) } diff --git a/restream/app/process.go b/restream/app/process.go index 974309cb..e02cd69a 100644 --- a/restream/app/process.go +++ b/restream/app/process.go @@ -79,13 +79,21 @@ type Config struct { Reconnect bool ReconnectDelay uint64 // seconds Autostart bool - StaleTimeout uint64 // seconds - Timeout uint64 // seconds - Scheduler string // crontab pattern or RFC3339 timestamp - LogPatterns []string // will be interpreted as regular expressions - LimitCPU float64 // percent - LimitMemory uint64 // bytes - LimitWaitFor uint64 // seconds + StaleTimeout uint64 // seconds + Timeout uint64 // seconds + Scheduler string // crontab pattern or RFC3339 timestamp + LogPatterns []string // will be interpreted as regular expressions + LimitCPU float64 // percent + LimitMemory uint64 // bytes + LimitGPU ConfigLimitGPU // GPU limits + LimitWaitFor uint64 // seconds +} + +type ConfigLimitGPU struct { + Usage float64 // percent 0-100 + Encoder float64 // percent 0-100 + Decoder float64 // percent 0-100 + Memory uint64 // bytes } func (config *Config) Clone() *Config { @@ -103,6 +111,7 @@ func (config *Config) Clone() *Config { Scheduler: config.Scheduler, LimitCPU: config.LimitCPU, LimitMemory: config.LimitMemory, + LimitGPU: config.LimitGPU, LimitWaitFor: config.LimitWaitFor, } @@ -175,6 +184,10 @@ func (config *Config) Hash() []byte { b.WriteString(strconv.FormatUint(config.LimitMemory, 10)) b.WriteString(strconv.FormatUint(config.LimitWaitFor, 10)) b.WriteString(strconv.FormatFloat(config.LimitCPU, 'f', -1, 64)) + b.WriteString(strconv.FormatFloat(config.LimitGPU.Usage, 'f', -1, 64)) + b.WriteString(strconv.FormatFloat(config.LimitGPU.Encoder, 'f', -1, 64)) + b.WriteString(strconv.FormatFloat(config.LimitGPU.Decoder, 'f', -1, 64)) + b.WriteString(strconv.FormatUint(config.LimitGPU.Memory, 10)) for _, x := range config.Input { b.WriteString(x.HashString()) @@ -294,7 +307,7 @@ type State struct { Memory uint64 // Current memory consumption in bytes CPU float64 // Current CPU consumption in percent LimitMode string // How the process is limited (hard or soft) - Resources ProcessUsage // Current resource usage, include CPU and memory consumption + Resources ProcessUsage // Current resource usage, include CPU, memory and GPU consumption Command []string // ffmpeg command line parameters } @@ -326,10 +339,10 @@ func (p *ProcessUsageCPU) MarshalParser() parse.UsageCPU { } type ProcessUsageMemory struct { - Current uint64 // bytes - Average float64 // bytes - Max uint64 // bytes - Limit uint64 // bytes + Current uint64 // bytes + Average uint64 // bytes + Max uint64 // bytes + Limit uint64 // bytes } func (p *ProcessUsageMemory) UnmarshalParser(pp *parse.UsageMemory) { @@ -348,20 +361,97 @@ func (p *ProcessUsageMemory) MarshalParser() parse.UsageMemory { return pp } +type ProcessUsageGPU struct { + Index int + Usage ProcessUsageGPUUsage + Encoder ProcessUsageGPUUsage + Decoder ProcessUsageGPUUsage + Memory ProcessUsageGPUMemory +} + +func (p *ProcessUsageGPU) UnmarshalParser(pp *parse.UsageGPU) { + p.Index = pp.Index + p.Usage.UnmarshalParser(&pp.Usage) + p.Encoder.UnmarshalParser(&pp.Encoder) + p.Decoder.UnmarshalParser(&pp.Decoder) + p.Memory.UnmarshalParser(&pp.Memory) +} + +func (p *ProcessUsageGPU) MarshalParser() parse.UsageGPU { + pp := parse.UsageGPU{ + Index: p.Index, + Usage: p.Usage.MarshalParser(), + Encoder: p.Encoder.MarshalParser(), + Decoder: p.Decoder.MarshalParser(), + Memory: p.Memory.MarshalParser(), + } + + return pp +} + +type ProcessUsageGPUUsage struct { + Current float64 // percent 0-100 + Average float64 // percent 0-100 + Max float64 // percent 0-100 + Limit float64 // percent 0-100 +} + +func (p *ProcessUsageGPUUsage) UnmarshalParser(pp *parse.UsageGPUUsage) { + p.Average = pp.Average + p.Max = pp.Max + p.Limit = pp.Limit +} + +func (p *ProcessUsageGPUUsage) MarshalParser() parse.UsageGPUUsage { + pp := parse.UsageGPUUsage{ + Average: p.Average, + Max: p.Max, + Limit: p.Limit, + } + + return pp +} + +type ProcessUsageGPUMemory struct { + Current uint64 // bytes + Average uint64 // bytes + Max uint64 // bytes + Limit uint64 // bytes +} + +func (p *ProcessUsageGPUMemory) UnmarshalParser(pp *parse.UsageGPUMemory) { + p.Average = pp.Average + p.Max = pp.Max + p.Limit = pp.Limit +} + +func (p *ProcessUsageGPUMemory) MarshalParser() parse.UsageGPUMemory { + pp := parse.UsageGPUMemory{ + Average: p.Average, + Max: p.Max, + Limit: p.Limit, + } + + return pp +} + type ProcessUsage struct { CPU ProcessUsageCPU Memory ProcessUsageMemory + GPU ProcessUsageGPU } func (p *ProcessUsage) UnmarshalParser(pp *parse.Usage) { p.CPU.UnmarshalParser(&pp.CPU) p.Memory.UnmarshalParser(&pp.Memory) + p.GPU.UnmarshalParser(&pp.GPU) } func (p *ProcessUsage) MarshalParser() parse.Usage { pp := parse.Usage{ CPU: p.CPU.MarshalParser(), Memory: p.Memory.MarshalParser(), + GPU: p.GPU.MarshalParser(), } return pp diff --git a/restream/app/process_test.go b/restream/app/process_test.go index 96889697..2aa6168b 100644 --- a/restream/app/process_test.go +++ b/restream/app/process_test.go @@ -46,12 +46,18 @@ func TestConfigHash(t *testing.T) { LogPatterns: []string{"^libx264"}, LimitCPU: 50, LimitMemory: 3 * 1024 * 1024, - LimitWaitFor: 20, + LimitGPU: ConfigLimitGPU{ + Usage: 10, + Encoder: 42, + Decoder: 14, + Memory: 500 * 1024 * 1024, + }, + LimitWaitFor: 20, } hash1 := config.Hash() - require.Equal(t, []byte{0x7e, 0xae, 0x5b, 0xc3, 0xad, 0xe3, 0x9a, 0xfc, 0xd3, 0x49, 0x15, 0x28, 0x93, 0x17, 0xc5, 0xbf}, hash1) + require.Equal(t, []byte{0x5e, 0x85, 0xc3, 0xc5, 0x44, 0xfd, 0x3e, 0x10, 0x13, 0x76, 0x36, 0x8b, 0xbe, 0x7e, 0xa6, 0xbb}, hash1) config.Reconnect = false diff --git a/restream/core.go b/restream/core.go index e3f64f9d..bbe1a72c 100644 --- a/restream/core.go +++ b/restream/core.go @@ -279,13 +279,14 @@ func (r *restream) resourceObserver(ctx context.Context, rsc resources.Resources defer ticker.Stop() limitCPU, limitMemory := false, false + var limitGPUs []bool = nil for { select { case <-ctx.Done(): return case <-ticker.C: - cpu, memory := rsc.ShouldLimit() + cpu, memory, gpu := rsc.ShouldLimit() hasChanges := false @@ -299,17 +300,34 @@ func (r *restream) resourceObserver(ctx context.Context, rsc resources.Resources hasChanges = true } + if limitGPUs == nil { + limitGPUs = make([]bool, len(gpu)) + } + + for i, g := range gpu { + if g != limitGPUs[i] { + limitGPUs[i] = g + hasChanges = true + } + } + if !hasChanges { break } r.tasks.Range(func(id app.ProcessID, t *task) bool { - if t.Limit(limitCPU, limitMemory) { + limitGPU := false + gpuindex := t.GetHWDevice() + if gpuindex >= 0 { + limitGPU = limitGPUs[gpuindex] + } + if t.Limit(limitCPU, limitMemory, limitGPU) { r.logger.Debug().WithFields(log.Fields{ "limit_cpu": limitCPU, "limit_memory": limitMemory, + "limit_gpu": limitGPU, "id": id, - }).Log("Limiting process CPU and memory consumption") + }).Log("Limiting process CPU, memory, and GPU consumption") } return true @@ -391,7 +409,11 @@ func (r *restream) load() error { // Validate config with all placeholders replaced. However, we need to take care // that the config with the task keeps its dynamic placeholders for process starts. config := t.config.Clone() - resolveDynamicPlaceholder(config, r.replace) + resolveDynamicPlaceholder(config, r.replace, map[string]string{ + "hwdevice": "0", + }, map[string]string{ + "timestamp": time.Now().UTC().Format(time.RFC3339), + }) t.usesDisk, err = validateConfig(config, r.fs.list, r.ffmpeg) if err != nil { @@ -414,30 +436,23 @@ func (r *restream) load() error { } ffmpeg, err := r.ffmpeg.New(ffmpeg.ProcessConfig{ - Reconnect: t.config.Reconnect, - ReconnectDelay: time.Duration(t.config.ReconnectDelay) * time.Second, - StaleTimeout: time.Duration(t.config.StaleTimeout) * time.Second, - Timeout: time.Duration(t.config.Timeout) * time.Second, - LimitCPU: t.config.LimitCPU, - LimitMemory: t.config.LimitMemory, - LimitDuration: time.Duration(t.config.LimitWaitFor) * time.Second, - LimitMode: limitMode, - Scheduler: t.config.Scheduler, - Args: t.command, - Parser: t.parser, - Logger: t.logger, - OnArgs: r.onArgs(t.config.Clone()), - OnBeforeStart: func() error { - if !r.enableSoftLimit { - return nil - } - - if err := r.resources.Request(t.config.LimitCPU, t.config.LimitMemory); err != nil { - return err - } - - return nil - }, + Reconnect: t.config.Reconnect, + ReconnectDelay: time.Duration(t.config.ReconnectDelay) * time.Second, + StaleTimeout: time.Duration(t.config.StaleTimeout) * time.Second, + Timeout: time.Duration(t.config.Timeout) * time.Second, + LimitCPU: t.config.LimitCPU, + LimitMemory: t.config.LimitMemory, + LimitGPUUsage: t.config.LimitGPU.Usage, + LimitGPUEncoder: t.config.LimitGPU.Encoder, + LimitGPUDecoder: t.config.LimitGPU.Decoder, + LimitGPUMemory: t.config.LimitGPU.Memory, + LimitDuration: time.Duration(t.config.LimitWaitFor) * time.Second, + LimitMode: limitMode, + Scheduler: t.config.Scheduler, + Args: t.command, + Parser: t.parser, + Logger: t.logger, + OnBeforeStart: r.onBeforeStart(t.config.Clone()), }) if err != nil { return true @@ -578,7 +593,11 @@ func (r *restream) createTask(config *app.Config) (*task, error) { // Validate config with all placeholders replaced. However, we need to take care // that the config with the task keeps its dynamic placeholders for process starts. config := t.config.Clone() - resolveDynamicPlaceholder(config, r.replace) + resolveDynamicPlaceholder(config, r.replace, map[string]string{ + "hwdevice": "0", + }, map[string]string{ + "timestamp": time.Now().UTC().Format(time.RFC3339), + }) t.usesDisk, err = validateConfig(config, r.fs.list, r.ffmpeg) if err != nil { @@ -600,30 +619,23 @@ func (r *restream) createTask(config *app.Config) (*task, error) { } ffmpeg, err := r.ffmpeg.New(ffmpeg.ProcessConfig{ - Reconnect: t.config.Reconnect, - ReconnectDelay: time.Duration(t.config.ReconnectDelay) * time.Second, - StaleTimeout: time.Duration(t.config.StaleTimeout) * time.Second, - Timeout: time.Duration(t.config.Timeout) * time.Second, - LimitCPU: t.config.LimitCPU, - LimitMemory: t.config.LimitMemory, - LimitDuration: time.Duration(t.config.LimitWaitFor) * time.Second, - LimitMode: limitMode, - Scheduler: t.config.Scheduler, - Args: t.command, - Parser: t.parser, - Logger: t.logger, - OnArgs: r.onArgs(t.config.Clone()), - OnBeforeStart: func() error { - if !r.enableSoftLimit { - return nil - } - - if err := r.resources.Request(t.config.LimitCPU, t.config.LimitMemory); err != nil { - return err - } - - return nil - }, + Reconnect: t.config.Reconnect, + ReconnectDelay: time.Duration(t.config.ReconnectDelay) * time.Second, + StaleTimeout: time.Duration(t.config.StaleTimeout) * time.Second, + Timeout: time.Duration(t.config.Timeout) * time.Second, + LimitCPU: t.config.LimitCPU, + LimitMemory: t.config.LimitMemory, + LimitGPUUsage: t.config.LimitGPU.Usage, + LimitGPUEncoder: t.config.LimitGPU.Encoder, + LimitGPUDecoder: t.config.LimitGPU.Decoder, + LimitGPUMemory: t.config.LimitGPU.Memory, + LimitDuration: time.Duration(t.config.LimitWaitFor) * time.Second, + LimitMode: limitMode, + Scheduler: t.config.Scheduler, + Args: t.command, + Parser: t.parser, + Logger: t.logger, + OnBeforeStart: r.onBeforeStart(t.config.Clone()), }) if err != nil { return nil, err @@ -636,21 +648,45 @@ func (r *restream) createTask(config *app.Config) (*task, error) { return t, nil } -// onArgs is a callback that gets called by a process before it will be started. -// It evalutes the dynamic placeholders in a process config and returns the -// resulting command line to the process. -func (r *restream) onArgs(cfg *app.Config) func([]string) []string { - return func(args []string) []string { +// onBeforeStart is a callback that gets called by a process before it will be started. +// It evalutes the dynamic placeholders in a process config and returns the resulting command line to the process. +func (r *restream) onBeforeStart(cfg *app.Config) func([]string) ([]string, error) { + return func(args []string) ([]string, error) { + selectedGPU := -1 + if r.enableSoftLimit { + res, err := r.resources.Request(resources.Request{ + CPU: cfg.LimitCPU, + Memory: cfg.LimitMemory, + GPUUsage: cfg.LimitGPU.Usage, + GPUEncoder: cfg.LimitGPU.Encoder, + GPUDecoder: cfg.LimitGPU.Decoder, + GPUMemory: cfg.LimitGPU.Memory, + }) + if err != nil { + return []string{}, err + } + + selectedGPU = res.GPU + } + + if t, hasTask := r.tasks.Load(cfg.ProcessID()); hasTask { + t.SetHWDevice(selectedGPU) + } + config := cfg.Clone() - resolveDynamicPlaceholder(config, r.replace) + resolveDynamicPlaceholder(config, r.replace, map[string]string{ + "hwdevice": fmt.Sprintf("%d", selectedGPU), + }, map[string]string{ + "timestamp": time.Now().UTC().Format(time.RFC3339), + }) _, err := validateConfig(config, r.fs.list, r.ffmpeg) if err != nil { - return []string{} + return []string{}, err } - return config.CreateCommand() + return config.CreateCommand(), nil } } @@ -1448,7 +1484,11 @@ func (r *restream) Probe(config *app.Config, timeout time.Duration) app.Probe { return probe } - resolveDynamicPlaceholder(config, r.replace) + resolveDynamicPlaceholder(config, r.replace, map[string]string{ + "hwdevice": "0", + }, map[string]string{ + "timestamp": time.Now().UTC().Format(time.RFC3339), + }) _, err = validateConfig(config, r.fs.list, r.ffmpeg) if err != nil { @@ -1712,22 +1752,26 @@ func resolveStaticPlaceholders(config *app.Config, r replace.Replacer) { // resolveDynamicPlaceholder replaces placeholders in the config that should be replaced at process start. // The config will be modified in place. -func resolveDynamicPlaceholder(config *app.Config, r replace.Replacer) { - vars := map[string]string{ - "timestamp": time.Now().UTC().Format(time.RFC3339), - } +func resolveDynamicPlaceholder(config *app.Config, r replace.Replacer, values map[string]string, vars map[string]string) { + placeholders := []string{"date", "hwdevice"} for i, option := range config.Options { - option = r.Replace(option, "date", "", vars, config, "global") + for _, placeholder := range placeholders { + option = r.Replace(option, placeholder, values[placeholder], vars, config, "global") + } config.Options[i] = option } for i, input := range config.Input { - input.Address = r.Replace(input.Address, "date", "", vars, config, "input") + for _, placeholder := range placeholders { + input.Address = r.Replace(input.Address, placeholder, values[placeholder], vars, config, "input") + } for j, option := range input.Options { - option = r.Replace(option, "date", "", vars, config, "input") + for _, placeholder := range placeholders { + option = r.Replace(option, placeholder, values[placeholder], vars, config, "input") + } input.Options[j] = option } @@ -1736,16 +1780,22 @@ func resolveDynamicPlaceholder(config *app.Config, r replace.Replacer) { } for i, output := range config.Output { - output.Address = r.Replace(output.Address, "date", "", vars, config, "output") + for _, placeholder := range placeholders { + output.Address = r.Replace(output.Address, placeholder, values[placeholder], vars, config, "output") + } for j, option := range output.Options { - option = r.Replace(option, "date", "", vars, config, "output") + for _, placeholder := range placeholders { + option = r.Replace(option, placeholder, values[placeholder], vars, config, "output") + } output.Options[j] = option } for j, cleanup := range output.Cleanup { - cleanup.Pattern = r.Replace(cleanup.Pattern, "date", "", vars, config, "output") + for _, placeholder := range placeholders { + cleanup.Pattern = r.Replace(cleanup.Pattern, placeholder, values[placeholder], vars, config, "output") + } output.Cleanup[j] = cleanup } diff --git a/restream/core_test.go b/restream/core_test.go index 3d9e1a68..48d79d89 100644 --- a/restream/core_test.go +++ b/restream/core_test.go @@ -1261,7 +1261,7 @@ func TestReplacer(t *testing.T) { require.Equal(t, wantprocess, process) - resolveDynamicPlaceholder(process, replacer) + resolveDynamicPlaceholder(process, replacer, nil, nil) wantprocess.Input = []app.ConfigIO{ { @@ -1531,7 +1531,7 @@ func TestProcessLimit(t *testing.T) { status := task.ffmpeg.Status() - ncpu, err := psutil.CPUCounts(true) + ncpu, err := psutil.CPUCounts() require.NoError(t, err) require.Equal(t, ncpu*process.LimitCPU, status.CPU.Limit) diff --git a/restream/task.go b/restream/task.go index 3073b506..40cb74c4 100644 --- a/restream/task.go +++ b/restream/task.go @@ -3,6 +3,7 @@ package restream import ( "errors" "maps" + "sync/atomic" "time" "github.com/datarhei/core/v16/ffmpeg/parse" @@ -31,7 +32,8 @@ type task struct { parser parse.Parser playout map[string]int logger log.Logger - usesDisk bool // Whether this task uses the disk + usesDisk bool // Whether this task uses the disk + hwdevice atomic.Int32 // Index of the GPU this task uses metadata map[string]interface{} lock *xsync.RBMutex @@ -234,8 +236,47 @@ func (t *task) State() (*app.State, error) { state.Memory = status.Memory.Current state.CPU = status.CPU.Current / status.CPU.NCPU state.LimitMode = status.LimitMode - state.Resources.CPU = status.CPU - state.Resources.Memory = status.Memory + state.Resources.CPU = app.ProcessUsageCPU{ + NCPU: status.CPU.NCPU, + Current: status.CPU.Current, + Average: status.CPU.Average, + Max: status.CPU.Max, + Limit: status.CPU.Limit, + IsThrottling: status.CPU.IsThrottling, + } + state.Resources.Memory = app.ProcessUsageMemory{ + Current: status.Memory.Current, + Average: status.Memory.Average, + Max: status.Memory.Max, + Limit: status.Memory.Limit, + } + state.Resources.GPU = app.ProcessUsageGPU{ + Index: status.GPU.Index, + Usage: app.ProcessUsageGPUUsage{ + Current: status.GPU.Usage.Current, + Average: status.GPU.Usage.Average, + Max: status.GPU.Usage.Max, + Limit: status.GPU.Usage.Limit, + }, + Encoder: app.ProcessUsageGPUUsage{ + Current: status.GPU.Encoder.Current, + Average: status.GPU.Encoder.Average, + Max: status.GPU.Encoder.Max, + Limit: status.GPU.Encoder.Limit, + }, + Decoder: app.ProcessUsageGPUUsage{ + Current: status.GPU.Decoder.Current, + Average: status.GPU.Decoder.Average, + Max: status.GPU.Decoder.Max, + Limit: status.GPU.Decoder.Limit, + }, + Memory: app.ProcessUsageGPUMemory{ + Current: status.GPU.Memory.Current, + Average: status.GPU.Memory.Average, + Max: status.GPU.Memory.Max, + Limit: status.GPU.Memory.Limit, + }, + } state.Duration = status.Duration.Round(10 * time.Millisecond).Seconds() state.Reconnect = -1 state.Command = status.CommandArgs @@ -420,7 +461,7 @@ func (t *task) ExportMetadata() map[string]interface{} { return t.metadata } -func (t *task) Limit(cpu, memory bool) bool { +func (t *task) Limit(cpu, memory, gpu bool) bool { token := t.lock.RLock() defer t.lock.RUnlock(token) @@ -428,11 +469,19 @@ func (t *task) Limit(cpu, memory bool) bool { return false } - t.ffmpeg.Limit(cpu, memory) + t.ffmpeg.Limit(cpu, memory, gpu) return true } +func (t *task) SetHWDevice(index int) { + t.hwdevice.Store(int32(index)) +} + +func (t *task) GetHWDevice() int { + return int(t.hwdevice.Load()) +} + func (t *task) Equal(config *app.Config) bool { token := t.lock.RLock() defer t.lock.RUnlock(token) diff --git a/session/registry_test.go b/session/registry_test.go index 7b1d987d..5cba9ec1 100644 --- a/session/registry_test.go +++ b/session/registry_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/datarhei/core/v16/io/fs" + "github.com/lestrrat-go/strftime" "github.com/stretchr/testify/require" ) From b6daea1a02944d22f76223ce433409336b036ac1 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 24 Oct 2024 15:52:56 +0200 Subject: [PATCH 47/64] Remove unused parameter --- process/process.go | 2 +- psutil/process.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/process/process.go b/process/process.go index 430cb7e2..916ca5c9 100644 --- a/process/process.go +++ b/process/process.go @@ -703,7 +703,7 @@ func (p *process) start() error { p.pid = int32(p.cmd.Process.Pid) - if proc, err := psutil.NewProcess(p.pid, false); err == nil { + if proc, err := psutil.NewProcess(p.pid); err == nil { p.limits.Start(proc) } diff --git a/psutil/process.go b/psutil/process.go index bec312ca..ee17489f 100644 --- a/psutil/process.go +++ b/psutil/process.go @@ -71,7 +71,7 @@ func (u *util) Process(pid int32) (Process, error) { return p, nil } -func NewProcess(pid int32, limit bool) (Process, error) { +func NewProcess(pid int32) (Process, error) { return DefaultUtil.Process(pid) } From e2def57a6f3896d66b94e580271aeb409ec5382c Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 24 Oct 2024 16:51:14 +0200 Subject: [PATCH 48/64] Update API docs --- cluster/docs/ClusterAPI_docs.go | 57 ++++++++++++ cluster/docs/ClusterAPI_swagger.json | 57 ++++++++++++ cluster/docs/ClusterAPI_swagger.yaml | 38 ++++++++ docs/docs.go | 133 ++++++++++++++++++++++++++- docs/swagger.json | 133 ++++++++++++++++++++++++++- docs/swagger.yaml | 91 +++++++++++++++++- 6 files changed, 506 insertions(+), 3 deletions(-) diff --git a/cluster/docs/ClusterAPI_docs.go b/cluster/docs/ClusterAPI_docs.go index 2adc2b3c..32d3693b 100644 --- a/cluster/docs/ClusterAPI_docs.go +++ b/cluster/docs/ClusterAPI_docs.go @@ -1307,6 +1307,14 @@ const docTemplateClusterAPI = `{ "description": "percent", "type": "number" }, + "limitGPU": { + "description": "GPU limits", + "allOf": [ + { + "$ref": "#/definitions/app.ConfigLimitGPU" + } + ] + }, "limitMemory": { "description": "bytes", "type": "integer" @@ -1401,6 +1409,27 @@ const docTemplateClusterAPI = `{ } } }, + "app.ConfigLimitGPU": { + "type": "object", + "properties": { + "decoder": { + "description": "percent 0-100", + "type": "number" + }, + "encoder": { + "description": "percent 0-100", + "type": "number" + }, + "memory": { + "description": "bytes", + "type": "integer" + }, + "usage": { + "description": "percent 0-100", + "type": "number" + } + } + }, "client.AddIdentityRequest": { "type": "object", "properties": { @@ -1669,6 +1698,26 @@ const docTemplateClusterAPI = `{ } } }, + "compress": { + "type": "object", + "properties": { + "encoding": { + "type": "array", + "items": { + "type": "string" + } + }, + "mimetypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "min_length": { + "type": "integer" + } + } + }, "created_at": { "description": "When this config has been persisted", "type": "string" @@ -1861,6 +1910,14 @@ const docTemplateClusterAPI = `{ "description": "percent 0-100", "type": "number" }, + "max_gpu_memory_usage": { + "description": "percent 0-100", + "type": "number" + }, + "max_gpu_usage": { + "description": "percent 0-100", + "type": "number" + }, "max_memory_usage": { "description": "percent 0-100", "type": "number" diff --git a/cluster/docs/ClusterAPI_swagger.json b/cluster/docs/ClusterAPI_swagger.json index debab33c..5e25097f 100644 --- a/cluster/docs/ClusterAPI_swagger.json +++ b/cluster/docs/ClusterAPI_swagger.json @@ -1300,6 +1300,14 @@ "description": "percent", "type": "number" }, + "limitGPU": { + "description": "GPU limits", + "allOf": [ + { + "$ref": "#/definitions/app.ConfigLimitGPU" + } + ] + }, "limitMemory": { "description": "bytes", "type": "integer" @@ -1394,6 +1402,27 @@ } } }, + "app.ConfigLimitGPU": { + "type": "object", + "properties": { + "decoder": { + "description": "percent 0-100", + "type": "number" + }, + "encoder": { + "description": "percent 0-100", + "type": "number" + }, + "memory": { + "description": "bytes", + "type": "integer" + }, + "usage": { + "description": "percent 0-100", + "type": "number" + } + } + }, "client.AddIdentityRequest": { "type": "object", "properties": { @@ -1662,6 +1691,26 @@ } } }, + "compress": { + "type": "object", + "properties": { + "encoding": { + "type": "array", + "items": { + "type": "string" + } + }, + "mimetypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "min_length": { + "type": "integer" + } + } + }, "created_at": { "description": "When this config has been persisted", "type": "string" @@ -1854,6 +1903,14 @@ "description": "percent 0-100", "type": "number" }, + "max_gpu_memory_usage": { + "description": "percent 0-100", + "type": "number" + }, + "max_gpu_usage": { + "description": "percent 0-100", + "type": "number" + }, "max_memory_usage": { "description": "percent 0-100", "type": "number" diff --git a/cluster/docs/ClusterAPI_swagger.yaml b/cluster/docs/ClusterAPI_swagger.yaml index 1e6f1745..9ab3c982 100644 --- a/cluster/docs/ClusterAPI_swagger.yaml +++ b/cluster/docs/ClusterAPI_swagger.yaml @@ -17,6 +17,10 @@ definitions: limitCPU: description: percent type: number + limitGPU: + allOf: + - $ref: '#/definitions/app.ConfigLimitGPU' + description: GPU limits limitMemory: description: bytes type: integer @@ -81,6 +85,21 @@ definitions: purgeOnDelete: type: boolean type: object + app.ConfigLimitGPU: + properties: + decoder: + description: percent 0-100 + type: number + encoder: + description: percent 0-100 + type: number + memory: + description: bytes + type: integer + usage: + description: percent 0-100 + type: number + type: object client.AddIdentityRequest: properties: identity: @@ -256,6 +275,19 @@ definitions: format: int64 type: integer type: object + compress: + properties: + encoding: + items: + type: string + type: array + mimetypes: + items: + type: string + type: array + min_length: + type: integer + type: object created_at: description: When this config has been persisted type: string @@ -387,6 +419,12 @@ definitions: max_cpu_usage: description: percent 0-100 type: number + max_gpu_memory_usage: + description: percent 0-100 + type: number + max_gpu_usage: + description: percent 0-100 + type: number max_memory_usage: description: percent 0-100 type: number diff --git a/docs/docs.go b/docs/docs.go index c8d44e9f..11b9c66e 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -5726,6 +5726,26 @@ const docTemplate = `{ } } }, + "compress": { + "type": "object", + "properties": { + "encoding": { + "type": "array", + "items": { + "type": "string" + } + }, + "mimetypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "min_length": { + "type": "integer" + } + } + }, "created_at": { "description": "When this config has been persisted", "type": "string" @@ -5918,6 +5938,14 @@ const docTemplate = `{ "description": "percent 0-100", "type": "number" }, + "max_gpu_memory_usage": { + "description": "percent 0-100", + "type": "number" + }, + "max_gpu_usage": { + "description": "percent 0-100", + "type": "number" + }, "max_memory_usage": { "description": "percent 0-100", "type": "number" @@ -7056,6 +7084,19 @@ const docTemplate = `{ "cpu_usage": { "type": "number" }, + "gpu_decoder": { + "type": "number" + }, + "gpu_encoder": { + "type": "number" + }, + "gpu_memory_mbytes": { + "type": "integer", + "format": "uint64" + }, + "gpu_usage": { + "type": "number" + }, "memory_mbytes": { "type": "integer", "format": "uint64" @@ -7230,6 +7271,9 @@ const docTemplate = `{ "cpu_usage": { "$ref": "#/definitions/api.ProcessUsageCPU" }, + "gpu": { + "$ref": "#/definitions/api.ProcessUsageGPU" + }, "memory_bytes": { "$ref": "#/definitions/api.ProcessUsageMemory" } @@ -7258,12 +7302,71 @@ const docTemplate = `{ } } }, - "api.ProcessUsageMemory": { + "api.ProcessUsageGPU": { + "type": "object", + "properties": { + "decoder": { + "$ref": "#/definitions/api.ProcessUsageGPUUsage" + }, + "encoder": { + "$ref": "#/definitions/api.ProcessUsageGPUUsage" + }, + "index": { + "type": "integer" + }, + "memory_bytes": { + "$ref": "#/definitions/api.ProcessUsageGPUMemory" + }, + "usage": { + "$ref": "#/definitions/api.ProcessUsageGPUUsage" + } + } + }, + "api.ProcessUsageGPUMemory": { + "type": "object", + "properties": { + "avg": { + "type": "integer", + "format": "uint64" + }, + "cur": { + "type": "integer", + "format": "uint64" + }, + "limit": { + "type": "integer", + "format": "uint64" + }, + "max": { + "type": "integer", + "format": "uint64" + } + } + }, + "api.ProcessUsageGPUUsage": { "type": "object", "properties": { "avg": { "type": "number" }, + "cur": { + "type": "number" + }, + "limit": { + "type": "number" + }, + "max": { + "type": "number" + } + } + }, + "api.ProcessUsageMemory": { + "type": "object", + "properties": { + "avg": { + "type": "integer", + "format": "uint64" + }, "cur": { "type": "integer", "format": "uint64" @@ -8113,6 +8216,26 @@ const docTemplate = `{ } } }, + "compress": { + "type": "object", + "properties": { + "encoding": { + "type": "array", + "items": { + "type": "string" + } + }, + "mimetypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "min_length": { + "type": "integer" + } + } + }, "created_at": { "description": "When this config has been persisted", "type": "string" @@ -8305,6 +8428,14 @@ const docTemplate = `{ "description": "percent 0-100", "type": "number" }, + "max_gpu_memory_usage": { + "description": "percent 0-100", + "type": "number" + }, + "max_gpu_usage": { + "description": "percent 0-100", + "type": "number" + }, "max_memory_usage": { "description": "percent 0-100", "type": "number" diff --git a/docs/swagger.json b/docs/swagger.json index eab2ce04..ce21a8ea 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -5719,6 +5719,26 @@ } } }, + "compress": { + "type": "object", + "properties": { + "encoding": { + "type": "array", + "items": { + "type": "string" + } + }, + "mimetypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "min_length": { + "type": "integer" + } + } + }, "created_at": { "description": "When this config has been persisted", "type": "string" @@ -5911,6 +5931,14 @@ "description": "percent 0-100", "type": "number" }, + "max_gpu_memory_usage": { + "description": "percent 0-100", + "type": "number" + }, + "max_gpu_usage": { + "description": "percent 0-100", + "type": "number" + }, "max_memory_usage": { "description": "percent 0-100", "type": "number" @@ -7049,6 +7077,19 @@ "cpu_usage": { "type": "number" }, + "gpu_decoder": { + "type": "number" + }, + "gpu_encoder": { + "type": "number" + }, + "gpu_memory_mbytes": { + "type": "integer", + "format": "uint64" + }, + "gpu_usage": { + "type": "number" + }, "memory_mbytes": { "type": "integer", "format": "uint64" @@ -7223,6 +7264,9 @@ "cpu_usage": { "$ref": "#/definitions/api.ProcessUsageCPU" }, + "gpu": { + "$ref": "#/definitions/api.ProcessUsageGPU" + }, "memory_bytes": { "$ref": "#/definitions/api.ProcessUsageMemory" } @@ -7251,12 +7295,71 @@ } } }, - "api.ProcessUsageMemory": { + "api.ProcessUsageGPU": { + "type": "object", + "properties": { + "decoder": { + "$ref": "#/definitions/api.ProcessUsageGPUUsage" + }, + "encoder": { + "$ref": "#/definitions/api.ProcessUsageGPUUsage" + }, + "index": { + "type": "integer" + }, + "memory_bytes": { + "$ref": "#/definitions/api.ProcessUsageGPUMemory" + }, + "usage": { + "$ref": "#/definitions/api.ProcessUsageGPUUsage" + } + } + }, + "api.ProcessUsageGPUMemory": { + "type": "object", + "properties": { + "avg": { + "type": "integer", + "format": "uint64" + }, + "cur": { + "type": "integer", + "format": "uint64" + }, + "limit": { + "type": "integer", + "format": "uint64" + }, + "max": { + "type": "integer", + "format": "uint64" + } + } + }, + "api.ProcessUsageGPUUsage": { "type": "object", "properties": { "avg": { "type": "number" }, + "cur": { + "type": "number" + }, + "limit": { + "type": "number" + }, + "max": { + "type": "number" + } + } + }, + "api.ProcessUsageMemory": { + "type": "object", + "properties": { + "avg": { + "type": "integer", + "format": "uint64" + }, "cur": { "type": "integer", "format": "uint64" @@ -8106,6 +8209,26 @@ } } }, + "compress": { + "type": "object", + "properties": { + "encoding": { + "type": "array", + "items": { + "type": "string" + } + }, + "mimetypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "min_length": { + "type": "integer" + } + } + }, "created_at": { "description": "When this config has been persisted", "type": "string" @@ -8298,6 +8421,14 @@ "description": "percent 0-100", "type": "number" }, + "max_gpu_memory_usage": { + "description": "percent 0-100", + "type": "number" + }, + "max_gpu_usage": { + "description": "percent 0-100", + "type": "number" + }, "max_memory_usage": { "description": "percent 0-100", "type": "number" diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 5a5c0746..9ad7331a 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -444,6 +444,19 @@ definitions: format: int64 type: integer type: object + compress: + properties: + encoding: + items: + type: string + type: array + mimetypes: + items: + type: string + type: array + min_length: + type: integer + type: object created_at: description: When this config has been persisted type: string @@ -575,6 +588,12 @@ definitions: max_cpu_usage: description: percent 0-100 type: number + max_gpu_memory_usage: + description: percent 0-100 + type: number + max_gpu_usage: + description: percent 0-100 + type: number max_memory_usage: description: percent 0-100 type: number @@ -1339,6 +1358,15 @@ definitions: properties: cpu_usage: type: number + gpu_decoder: + type: number + gpu_encoder: + type: number + gpu_memory_mbytes: + format: uint64 + type: integer + gpu_usage: + type: number memory_mbytes: format: uint64 type: integer @@ -1457,6 +1485,8 @@ definitions: properties: cpu_usage: $ref: '#/definitions/api.ProcessUsageCPU' + gpu: + $ref: '#/definitions/api.ProcessUsageGPU' memory_bytes: $ref: '#/definitions/api.ProcessUsageMemory' type: object @@ -1475,10 +1505,50 @@ definitions: throttling: type: boolean type: object - api.ProcessUsageMemory: + api.ProcessUsageGPU: + properties: + decoder: + $ref: '#/definitions/api.ProcessUsageGPUUsage' + encoder: + $ref: '#/definitions/api.ProcessUsageGPUUsage' + index: + type: integer + memory_bytes: + $ref: '#/definitions/api.ProcessUsageGPUMemory' + usage: + $ref: '#/definitions/api.ProcessUsageGPUUsage' + type: object + api.ProcessUsageGPUMemory: + properties: + avg: + format: uint64 + type: integer + cur: + format: uint64 + type: integer + limit: + format: uint64 + type: integer + max: + format: uint64 + type: integer + type: object + api.ProcessUsageGPUUsage: properties: avg: type: number + cur: + type: number + limit: + type: number + max: + type: number + type: object + api.ProcessUsageMemory: + properties: + avg: + format: uint64 + type: integer cur: format: uint64 type: integer @@ -2122,6 +2192,19 @@ definitions: format: int64 type: integer type: object + compress: + properties: + encoding: + items: + type: string + type: array + mimetypes: + items: + type: string + type: array + min_length: + type: integer + type: object created_at: description: When this config has been persisted type: string @@ -2253,6 +2336,12 @@ definitions: max_cpu_usage: description: percent 0-100 type: number + max_gpu_memory_usage: + description: percent 0-100 + type: number + max_gpu_usage: + description: percent 0-100 + type: number max_memory_usage: description: percent 0-100 type: number From 175fb1a9b481b9f890104c1282e8098ed8c0845d Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Mon, 28 Oct 2024 10:27:38 +0100 Subject: [PATCH 49/64] Don't directly access psutils --- monitor/cpu.go | 29 ++++++++--------------------- monitor/mem.go | 17 +++++------------ 2 files changed, 13 insertions(+), 33 deletions(-) diff --git a/monitor/cpu.go b/monitor/cpu.go index 8a10850a..c2b9ca86 100644 --- a/monitor/cpu.go +++ b/monitor/cpu.go @@ -2,7 +2,6 @@ package monitor import ( "github.com/datarhei/core/v16/monitor/metric" - "github.com/datarhei/core/v16/psutil" "github.com/datarhei/core/v16/resources" ) @@ -15,13 +14,11 @@ type cpuCollector struct { limitDescr *metric.Description throttleDescr *metric.Description - ncpu float64 resources resources.Resources } func NewCPUCollector(rsc resources.Resources) metric.Collector { c := &cpuCollector{ - ncpu: 1, resources: rsc, } @@ -33,10 +30,6 @@ func NewCPUCollector(rsc resources.Resources) metric.Collector { c.limitDescr = metric.NewDesc("cpu_limit", "Percentage of CPU to be consumed", nil) c.throttleDescr = metric.NewDesc("cpu_throttling", "Whether the CPU is currently throttled", nil) - if ncpu, err := psutil.CPUCounts(); err == nil { - c.ncpu = ncpu - } - return c } @@ -61,29 +54,23 @@ func (c *cpuCollector) Describe() []*metric.Description { func (c *cpuCollector) Collect() metric.Metrics { metrics := metric.NewMetrics() - metrics.Add(metric.NewValue(c.ncpuDescr, c.ncpu)) + rinfo := c.resources.Info() - limit, _, _, _ := c.resources.Limits() + metrics.Add(metric.NewValue(c.ncpuDescr, rinfo.CPU.NCPU)) - metrics.Add(metric.NewValue(c.limitDescr, limit)) + metrics.Add(metric.NewValue(c.limitDescr, rinfo.CPU.Limit)) - cpu, _, _ := c.resources.ShouldLimit() throttling := .0 - if cpu { + if rinfo.CPU.Throttling { throttling = 1 } metrics.Add(metric.NewValue(c.throttleDescr, throttling)) - stat, err := psutil.CPUPercent() - if err != nil { - return metrics - } - - metrics.Add(metric.NewValue(c.systemDescr, stat.System)) - metrics.Add(metric.NewValue(c.userDescr, stat.User)) - metrics.Add(metric.NewValue(c.idleDescr, stat.Idle)) - metrics.Add(metric.NewValue(c.otherDescr, stat.Other)) + metrics.Add(metric.NewValue(c.systemDescr, rinfo.CPU.System)) + metrics.Add(metric.NewValue(c.userDescr, rinfo.CPU.User)) + metrics.Add(metric.NewValue(c.idleDescr, rinfo.CPU.Idle)) + metrics.Add(metric.NewValue(c.otherDescr, rinfo.CPU.Other)) return metrics } diff --git a/monitor/mem.go b/monitor/mem.go index 986b2be5..4f6b97d8 100644 --- a/monitor/mem.go +++ b/monitor/mem.go @@ -2,7 +2,6 @@ package monitor import ( "github.com/datarhei/core/v16/monitor/metric" - "github.com/datarhei/core/v16/psutil" "github.com/datarhei/core/v16/resources" ) @@ -44,25 +43,19 @@ func (c *memCollector) Describe() []*metric.Description { func (c *memCollector) Collect() metric.Metrics { metrics := metric.NewMetrics() - _, limit, _, _ := c.resources.Limits() + rinfo := c.resources.Info() - metrics.Add(metric.NewValue(c.limitDescr, float64(limit))) + metrics.Add(metric.NewValue(c.limitDescr, float64(rinfo.Mem.Limit))) - _, memory, _ := c.resources.ShouldLimit() throttling := .0 - if memory { + if rinfo.Mem.Throttling { throttling = 1 } metrics.Add(metric.NewValue(c.throttleDescr, throttling)) - stat, err := psutil.Memory() - if err != nil { - return metrics - } - - metrics.Add(metric.NewValue(c.totalDescr, float64(stat.Total))) - metrics.Add(metric.NewValue(c.freeDescr, float64(stat.Available))) + metrics.Add(metric.NewValue(c.totalDescr, float64(rinfo.Mem.Total))) + metrics.Add(metric.NewValue(c.freeDescr, float64(rinfo.Mem.Available))) return metrics } From 412fbedea301985531bd4135e5efac1ca05e76de Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Mon, 28 Oct 2024 16:13:13 +0100 Subject: [PATCH 50/64] Make psutil a submodule of resources, remove default psutil --- app/api/api.go | 20 +++--- ffmpeg/ffmpeg.go | 11 ++++ http/api/process.go | 14 ++--- http/mock/mock.go | 7 +++ monitor/disk.go | 12 ++-- monitor/net.go | 12 ++-- process/limiter.go | 8 +-- process/limiter_test.go | 26 +++++--- process/process.go | 22 ++++++- process/process_test.go | 29 +++++++++ .../cgroup-limited/cpu/cpu.cfs_period_us | 0 .../cgroup-limited/cpu/cpu.cfs_quota_us | 0 .../cgroup-limited/cpuacct/cpuacct.usage | 0 .../memory/memory.limit_in_bytes | 0 .../memory/memory.usage_in_bytes | 0 .../fixtures/cgroup/cpu/cpu.cfs_period_us | 0 .../fixtures/cgroup/cpu/cpu.cfs_quota_us | 0 .../fixtures/cgroup/cpuacct/cpuacct.usage | 0 .../cgroup/memory/memory.limit_in_bytes | 0 .../cgroup/memory/memory.usage_in_bytes | 0 .../psutil}/fixtures/cgroup2-limited/cpu.max | 0 .../psutil}/fixtures/cgroup2-limited/cpu.stat | 0 .../fixtures/cgroup2-limited/memory.current | 0 .../fixtures/cgroup2-limited/memory.max | 0 .../psutil}/fixtures/cgroup2/cpu.max | 0 .../psutil}/fixtures/cgroup2/cpu.stat | 0 .../psutil}/fixtures/cgroup2/memory.current | 0 .../psutil}/fixtures/cgroup2/memory.max | 0 {psutil => resources/psutil}/gpu/gpu.go | 11 ++++ .../psutil}/gpu/nvidia/fixtures/process.txt | 0 .../psutil}/gpu/nvidia/fixtures/query1.xml | 0 .../psutil}/gpu/nvidia/fixtures/query2.xml | 0 .../psutil}/gpu/nvidia/fixtures/query3.xml | 0 .../psutil}/gpu/nvidia/nvidia.go | 8 +-- .../psutil}/gpu/nvidia/nvidia_test.go | 8 +-- {psutil => resources/psutil}/process.go | 11 ++-- {psutil => resources/psutil}/process_linux.go | 0 {psutil => resources/psutil}/process_other.go | 0 {psutil => resources/psutil}/psutil.go | 47 +++++--------- {psutil => resources/psutil}/psutil_test.go | 2 +- resources/resources.go | 63 ++++++++++++++++++- resources/resources_test.go | 2 +- restream/core_test.go | 20 +++++- 43 files changed, 234 insertions(+), 99 deletions(-) rename {psutil => resources/psutil}/fixtures/cgroup-limited/cpu/cpu.cfs_period_us (100%) rename {psutil => resources/psutil}/fixtures/cgroup-limited/cpu/cpu.cfs_quota_us (100%) rename {psutil => resources/psutil}/fixtures/cgroup-limited/cpuacct/cpuacct.usage (100%) rename {psutil => resources/psutil}/fixtures/cgroup-limited/memory/memory.limit_in_bytes (100%) rename {psutil => resources/psutil}/fixtures/cgroup-limited/memory/memory.usage_in_bytes (100%) rename {psutil => resources/psutil}/fixtures/cgroup/cpu/cpu.cfs_period_us (100%) rename {psutil => resources/psutil}/fixtures/cgroup/cpu/cpu.cfs_quota_us (100%) rename {psutil => resources/psutil}/fixtures/cgroup/cpuacct/cpuacct.usage (100%) rename {psutil => resources/psutil}/fixtures/cgroup/memory/memory.limit_in_bytes (100%) rename {psutil => resources/psutil}/fixtures/cgroup/memory/memory.usage_in_bytes (100%) rename {psutil => resources/psutil}/fixtures/cgroup2-limited/cpu.max (100%) rename {psutil => resources/psutil}/fixtures/cgroup2-limited/cpu.stat (100%) rename {psutil => resources/psutil}/fixtures/cgroup2-limited/memory.current (100%) rename {psutil => resources/psutil}/fixtures/cgroup2-limited/memory.max (100%) rename {psutil => resources/psutil}/fixtures/cgroup2/cpu.max (100%) rename {psutil => resources/psutil}/fixtures/cgroup2/cpu.stat (100%) rename {psutil => resources/psutil}/fixtures/cgroup2/memory.current (100%) rename {psutil => resources/psutil}/fixtures/cgroup2/memory.max (100%) rename {psutil => resources/psutil}/gpu/gpu.go (70%) rename {psutil => resources/psutil}/gpu/nvidia/fixtures/process.txt (100%) rename {psutil => resources/psutil}/gpu/nvidia/fixtures/query1.xml (100%) rename {psutil => resources/psutil}/gpu/nvidia/fixtures/query2.xml (100%) rename {psutil => resources/psutil}/gpu/nvidia/fixtures/query3.xml (100%) rename {psutil => resources/psutil}/gpu/nvidia/nvidia.go (98%) rename {psutil => resources/psutil}/gpu/nvidia/nvidia_test.go (96%) rename {psutil => resources/psutil}/process.go (95%) rename {psutil => resources/psutil}/process_linux.go (100%) rename {psutil => resources/psutil}/process_other.go (100%) rename {psutil => resources/psutil}/psutil.go (95%) rename {psutil => resources/psutil}/psutil_test.go (98%) diff --git a/app/api/api.go b/app/api/api.go index 042354e4..05689098 100644 --- a/app/api/api.go +++ b/app/api/api.go @@ -36,8 +36,8 @@ import ( "github.com/datarhei/core/v16/monitor" "github.com/datarhei/core/v16/net" "github.com/datarhei/core/v16/prometheus" - "github.com/datarhei/core/v16/psutil" "github.com/datarhei/core/v16/resources" + "github.com/datarhei/core/v16/resources/psutil" "github.com/datarhei/core/v16/restream" restreamapp "github.com/datarhei/core/v16/restream/app" "github.com/datarhei/core/v16/restream/replace" @@ -127,8 +127,6 @@ type api struct { state string undoMaxprocs func() - - process psutil.Process } // ErrConfigReload is an error returned to indicate that a reload of @@ -370,12 +368,18 @@ func (a *api) start(ctx context.Context) error { debug.SetMemoryLimit(math.MaxInt64) } + psutil, err := psutil.New("", nil) + if err != nil { + return fmt.Errorf("failed to initialize psutils: %w", err) + } + resources, err := resources.New(resources.Config{ MaxCPU: cfg.Resources.MaxCPUUsage, MaxMemory: cfg.Resources.MaxMemoryUsage, MaxGPU: cfg.Resources.MaxGPUUsage, MaxGPUMemory: cfg.Resources.MaxGPUMemoryUsage, Logger: a.log.logger.core.WithComponent("Resources"), + PSUtil: psutil, }) if err != nil { return fmt.Errorf("failed to initialize resource manager: %w", err) @@ -509,6 +513,7 @@ func (a *api) start(ctx context.Context) error { ValidatorOutput: validatorOut, Portrange: portrange, Collector: a.sessions.Collector("ffmpeg"), + PSUtil: psutil, }) if err != nil { return fmt.Errorf("unable to create ffmpeg: %w", err) @@ -1230,8 +1235,8 @@ func (a *api) start(ctx context.Context) error { metrics.Register(monitor.NewUptimeCollector()) metrics.Register(monitor.NewCPUCollector(a.resources)) metrics.Register(monitor.NewMemCollector(a.resources)) - metrics.Register(monitor.NewNetCollector()) - metrics.Register(monitor.NewDiskCollector(a.diskfs.Metadata("base"))) + metrics.Register(monitor.NewNetCollector(a.resources)) + metrics.Register(monitor.NewDiskCollector(a.diskfs.Metadata("base"), a.resources)) metrics.Register(monitor.NewFilesystemCollector("diskfs", a.diskfs)) metrics.Register(monitor.NewFilesystemCollector("memfs", a.memfs)) for name, fs := range a.s3fs { @@ -1888,11 +1893,6 @@ func (a *api) stop() { a.service = nil } - if a.process != nil { - a.process.Stop() - a.process = nil - } - // Unregister all collectors if a.metrics != nil { a.metrics.UnregisterAll() diff --git a/ffmpeg/ffmpeg.go b/ffmpeg/ffmpeg.go index 1b3c96af..5e64fd1e 100644 --- a/ffmpeg/ffmpeg.go +++ b/ffmpeg/ffmpeg.go @@ -12,6 +12,7 @@ import ( "github.com/datarhei/core/v16/log" "github.com/datarhei/core/v16/net" "github.com/datarhei/core/v16/process" + "github.com/datarhei/core/v16/resources/psutil" "github.com/datarhei/core/v16/session" ) @@ -63,6 +64,7 @@ type Config struct { ValidatorOutput Validator Portrange net.Portranger Collector session.Collector + PSUtil psutil.Util } type ffmpeg struct { @@ -80,11 +82,19 @@ type ffmpeg struct { states process.States statesLock sync.RWMutex + + psutil psutil.Util } func New(config Config) (FFmpeg, error) { f := &ffmpeg{} + if config.PSUtil == nil { + return nil, fmt.Errorf("psutils required") + } + + f.psutil = config.PSUtil + binary, err := exec.LookPath(config.Binary) if err != nil { return nil, fmt.Errorf("invalid ffmpeg binary given: %w", err) @@ -184,6 +194,7 @@ func (f *ffmpeg) New(config ProcessConfig) (process.Process, error) { config.OnStateChange(from, to) } }, + PSUtil: f.psutil, }) return ffmpeg, err diff --git a/http/api/process.go b/http/api/process.go index 43a9fce9..c7cae209 100644 --- a/http/api/process.go +++ b/http/api/process.go @@ -155,13 +155,13 @@ type ProcessConfigIOCleanup struct { } type ProcessConfigLimits struct { - CPU float64 `json:"cpu_usage" jsonschema:"minimum=0"` - Memory uint64 `json:"memory_mbytes" jsonschema:"minimum=0" format:"uint64"` - GPUUsage float64 `json:"gpu_usage" jsonschema:"minimum=0"` - GPUEncoder float64 `json:"gpu_encoder" jsonschema:"minimum=0"` - GPUDecoder float64 `json:"gpu_decoder" jsonschema:"minimum=0"` - GPUMemory uint64 `json:"gpu_memory_mbytes" jsonschema:"minimum=0" format:"uint64"` - WaitFor uint64 `json:"waitfor_seconds" jsonschema:"minimum=0" format:"uint64"` + CPU float64 `json:"cpu_usage" jsonschema:"minimum=0"` // percent 0-100*ncpu + Memory uint64 `json:"memory_mbytes" jsonschema:"minimum=0" format:"uint64"` // megabytes + GPUUsage float64 `json:"gpu_usage" jsonschema:"minimum=0"` // percent 0-100 + GPUEncoder float64 `json:"gpu_encoder" jsonschema:"minimum=0"` // percent 0-100 + GPUDecoder float64 `json:"gpu_decoder" jsonschema:"minimum=0"` // percent 0-100 + GPUMemory uint64 `json:"gpu_memory_mbytes" jsonschema:"minimum=0" format:"uint64"` // megabytes + WaitFor uint64 `json:"waitfor_seconds" jsonschema:"minimum=0" format:"uint64"` // seconds } // ProcessConfig represents the configuration of an ffmpeg process diff --git a/http/mock/mock.go b/http/mock/mock.go index 51e3b8a0..7487c9af 100644 --- a/http/mock/mock.go +++ b/http/mock/mock.go @@ -17,6 +17,7 @@ import ( "github.com/datarhei/core/v16/http/validator" "github.com/datarhei/core/v16/internal/testhelper" "github.com/datarhei/core/v16/io/fs" + "github.com/datarhei/core/v16/resources/psutil" "github.com/datarhei/core/v16/restream" jsonstore "github.com/datarhei/core/v16/restream/store/json" @@ -44,10 +45,16 @@ func DummyRestreamer(pathPrefix string) (restream.Restreamer, error) { return nil, err } + psutil, err := psutil.New("", nil) + if err != nil { + return nil, err + } + ffmpeg, err := ffmpeg.New(ffmpeg.Config{ Binary: binary, MaxLogLines: 100, LogHistoryLength: 3, + PSUtil: psutil, }) if err != nil { return nil, err diff --git a/monitor/disk.go b/monitor/disk.go index fda2f24d..562d5539 100644 --- a/monitor/disk.go +++ b/monitor/disk.go @@ -2,19 +2,21 @@ package monitor import ( "github.com/datarhei/core/v16/monitor/metric" - "github.com/datarhei/core/v16/psutil" + "github.com/datarhei/core/v16/resources" ) type diskCollector struct { - path string + path string + resources resources.Resources totalDescr *metric.Description usageDescr *metric.Description } -func NewDiskCollector(path string) metric.Collector { +func NewDiskCollector(path string, rsc resources.Resources) metric.Collector { c := &diskCollector{ - path: path, + path: path, + resources: rsc, } c.totalDescr = metric.NewDesc("disk_total", "Total size of the disk in bytes", []string{"path"}) @@ -37,7 +39,7 @@ func (c *diskCollector) Describe() []*metric.Description { func (c *diskCollector) Collect() metric.Metrics { metrics := metric.NewMetrics() - stat, err := psutil.Disk(c.path) + stat, err := c.resources.Disk(c.path) if err != nil { return metrics } diff --git a/monitor/net.go b/monitor/net.go index 270e0948..f961aa1e 100644 --- a/monitor/net.go +++ b/monitor/net.go @@ -2,16 +2,20 @@ package monitor import ( "github.com/datarhei/core/v16/monitor/metric" - "github.com/datarhei/core/v16/psutil" + "github.com/datarhei/core/v16/resources" ) type netCollector struct { rxDescr *metric.Description txDescr *metric.Description + + resources resources.Resources } -func NewNetCollector() metric.Collector { - c := &netCollector{} +func NewNetCollector(rsc resources.Resources) metric.Collector { + c := &netCollector{ + resources: rsc, + } c.rxDescr = metric.NewDesc("net_rx", "Number of received bytes", []string{"interface"}) c.txDescr = metric.NewDesc("net_tx", "Number of transmitted bytes", []string{"interface"}) @@ -33,7 +37,7 @@ func (c *netCollector) Describe() []*metric.Description { func (c *netCollector) Collect() metric.Metrics { metrics := metric.NewMetrics() - devs, err := psutil.Network() + devs, err := c.resources.Network() if err != nil { return metrics } diff --git a/process/limiter.go b/process/limiter.go index 699294dc..cd9cc1ec 100644 --- a/process/limiter.go +++ b/process/limiter.go @@ -7,7 +7,7 @@ import ( "time" "github.com/datarhei/core/v16/log" - "github.com/datarhei/core/v16/psutil" + "github.com/datarhei/core/v16/resources/psutil" ) type Usage struct { @@ -257,7 +257,7 @@ type limiter struct { } // NewLimiter returns a new Limiter -func NewLimiter(config LimiterConfig) Limiter { +func NewLimiter(config LimiterConfig) (Limiter, error) { l := &limiter{ waitFor: config.WaitFor, onLimit: config.OnLimit, @@ -278,7 +278,7 @@ func NewLimiter(config LimiterConfig) Limiter { } if l.psutil == nil { - l.psutil = psutil.DefaultUtil + return nil, fmt.Errorf("no psutil provided") } if ncpu, err := l.psutil.CPUCounts(); err != nil { @@ -318,7 +318,7 @@ func NewLimiter(config LimiterConfig) Limiter { "mode": mode, }) - return l + return l, nil } func (l *limiter) reset() { diff --git a/process/limiter_test.go b/process/limiter_test.go index 0ec98333..535b8ccd 100644 --- a/process/limiter_test.go +++ b/process/limiter_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/datarhei/core/v16/psutil" + "github.com/datarhei/core/v16/resources/psutil" "github.com/stretchr/testify/require" ) @@ -52,11 +52,12 @@ func TestCPULimit(t *testing.T) { wg := sync.WaitGroup{} wg.Add(1) - l := NewLimiter(LimiterConfig{ + l, _ := NewLimiter(LimiterConfig{ CPU: 42, OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, + PSUtil: newPSUtil(), }) l.Start(&psproc{}) @@ -88,12 +89,13 @@ func TestCPULimitWaitFor(t *testing.T) { wg := sync.WaitGroup{} wg.Add(1) - l := NewLimiter(LimiterConfig{ + l, _ := NewLimiter(LimiterConfig{ CPU: 42, WaitFor: 3 * time.Second, OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, + PSUtil: newPSUtil(), }) l.Start(&psproc{}) @@ -125,11 +127,12 @@ func TestMemoryLimit(t *testing.T) { wg := sync.WaitGroup{} wg.Add(1) - l := NewLimiter(LimiterConfig{ + l, _ := NewLimiter(LimiterConfig{ Memory: 42, OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, + PSUtil: newPSUtil(), }) l.Start(&psproc{}) @@ -161,12 +164,13 @@ func TestMemoryLimitWaitFor(t *testing.T) { wg := sync.WaitGroup{} wg.Add(1) - l := NewLimiter(LimiterConfig{ + l, _ := NewLimiter(LimiterConfig{ Memory: 42, WaitFor: 3 * time.Second, OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, + PSUtil: newPSUtil(), }) l.Start(&psproc{}) @@ -198,11 +202,12 @@ func TestGPUMemoryLimit(t *testing.T) { wg := sync.WaitGroup{} wg.Add(1) - l := NewLimiter(LimiterConfig{ + l, _ := NewLimiter(LimiterConfig{ GPUMemory: 42, OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, + PSUtil: newPSUtil(), }) l.Start(&psproc{}) @@ -234,12 +239,13 @@ func TestGPUMemoryLimitWaitFor(t *testing.T) { wg := sync.WaitGroup{} wg.Add(1) - l := NewLimiter(LimiterConfig{ + l, _ := NewLimiter(LimiterConfig{ GPUMemory: 42, WaitFor: 3 * time.Second, OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, + PSUtil: newPSUtil(), }) l.Start(&psproc{}) @@ -271,12 +277,13 @@ func TestMemoryLimitSoftMode(t *testing.T) { wg := sync.WaitGroup{} wg.Add(1) - l := NewLimiter(LimiterConfig{ + l, _ := NewLimiter(LimiterConfig{ Memory: 42, Mode: LimitModeSoft, OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, + PSUtil: newPSUtil(), }) l.Start(&psproc{}) @@ -310,12 +317,13 @@ func TestGPUMemoryLimitSoftMode(t *testing.T) { wg := sync.WaitGroup{} wg.Add(1) - l := NewLimiter(LimiterConfig{ + l, _ := NewLimiter(LimiterConfig{ GPUMemory: 42, Mode: LimitModeSoft, OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, + PSUtil: newPSUtil(), }) l.Start(&psproc{}) diff --git a/process/process.go b/process/process.go index 916ca5c9..0d5b50ca 100644 --- a/process/process.go +++ b/process/process.go @@ -18,7 +18,7 @@ import ( "unicode/utf8" "github.com/datarhei/core/v16/log" - "github.com/datarhei/core/v16/psutil" + "github.com/datarhei/core/v16/resources/psutil" ) // Process represents a process and ways to control it @@ -71,6 +71,7 @@ type Config struct { OnStart func() // A callback which is called after the process started. OnExit func(state string) // A callback which is called after the process exited with the exit state. OnStateChange func(from, to string) // A callback which is called after a state changed. + PSUtil psutil.Util Logger log.Logger } @@ -244,6 +245,7 @@ type process struct { } limits Limiter scheduler Scheduler + psutil psutil.Util } var _ Process = &process{} @@ -257,6 +259,11 @@ func New(config Config) (Process, error) { parser: config.Parser, logger: config.Logger, scheduler: config.Scheduler, + psutil: config.PSUtil, + } + + if p.psutil == nil { + return nil, fmt.Errorf("no psutils given") } p.args = make([]string, len(config.Args)) @@ -269,6 +276,10 @@ func New(config Config) (Process, error) { return nil, fmt.Errorf("no valid binary given") } + if p.psutil == nil { + return nil, fmt.Errorf("no psutils provided") + } + if p.parser == nil { p.parser = NewNullParser() } @@ -297,7 +308,7 @@ func New(config Config) (Process, error) { p.callbacks.onExit = config.OnExit p.callbacks.onStateChange = config.OnStateChange - p.limits = NewLimiter(LimiterConfig{ + limits, err := NewLimiter(LimiterConfig{ CPU: config.LimitCPU, Memory: config.LimitMemory, GPUUsage: config.LimitGPUUsage, @@ -322,7 +333,12 @@ func New(config Config) (Process, error) { }).Warn().Log("Killed because limits are exceeded") p.Kill(false, fmt.Sprintf("Killed because limits are exceeded (mode: %s, tolerance: %s): %.2f (%.2f) CPU, %d (%d) bytes memory, %.2f/%.2f/%.2f (%.2f) GPU usage, %d (%d) bytes GPU memory", config.LimitMode.String(), config.LimitDuration.String(), cpu, config.LimitCPU, memory, config.LimitMemory, gpuusage, gpuencoder, gpudecoder, config.LimitGPUUsage, gpumemory, config.LimitGPUMemory)) }, + PSUtil: p.psutil, }) + if err != nil { + return nil, fmt.Errorf("failed to initialize limiter") + } + p.limits = limits p.logger.Info().Log("Created") p.debuglogger.Debug().Log("Created") @@ -703,7 +719,7 @@ func (p *process) start() error { p.pid = int32(p.cmd.Process.Pid) - if proc, err := psutil.NewProcess(p.pid); err == nil { + if proc, err := p.psutil.Process(p.pid); err == nil { p.limits.Start(proc) } diff --git a/process/process_test.go b/process/process_test.go index 6ddba58a..88045b9d 100644 --- a/process/process_test.go +++ b/process/process_test.go @@ -10,9 +10,15 @@ import ( "github.com/datarhei/core/v16/internal/testhelper" "github.com/datarhei/core/v16/math/rand" + "github.com/datarhei/core/v16/resources/psutil" "github.com/stretchr/testify/require" ) +func newPSUtil() psutil.Util { + util, _ := psutil.New("", nil) + return util +} + func TestProcess(t *testing.T) { p, _ := New(Config{ Binary: "sleep", @@ -21,6 +27,7 @@ func TestProcess(t *testing.T) { }, Reconnect: false, StaleTimeout: 0, + PSUtil: newPSUtil(), }) require.Equal(t, "finished", p.Status().State) @@ -59,6 +66,7 @@ func TestReconnectProcess(t *testing.T) { OnExit: func(string) { wg.Done() }, + PSUtil: newPSUtil(), }) p.Start() @@ -104,6 +112,7 @@ func TestStaleProcess(t *testing.T) { }, Reconnect: false, StaleTimeout: 2 * time.Second, + PSUtil: newPSUtil(), }) p.Start() @@ -126,6 +135,7 @@ func TestStaleReconnectProcess(t *testing.T) { Reconnect: true, ReconnectDelay: 2 * time.Second, StaleTimeout: 3 * time.Second, + PSUtil: newPSUtil(), }) p.Start() @@ -156,6 +166,7 @@ func TestNonExistingProcess(t *testing.T) { Reconnect: false, ReconnectDelay: 5 * time.Second, StaleTimeout: 0, + PSUtil: newPSUtil(), }) p.Start() @@ -180,6 +191,7 @@ func TestNonExistingReconnectProcess(t *testing.T) { Reconnect: true, ReconnectDelay: 2 * time.Second, StaleTimeout: 0, + PSUtil: newPSUtil(), }) p.Start() @@ -203,6 +215,7 @@ func TestProcessFailed(t *testing.T) { }, Reconnect: false, StaleTimeout: 0, + PSUtil: newPSUtil(), }) p.Start() @@ -228,6 +241,7 @@ func TestFFmpegWaitStop(t *testing.T) { OnExit: func(state string) { time.Sleep(3 * time.Second) }, + PSUtil: newPSUtil(), }) err = p.Start() @@ -255,6 +269,7 @@ func TestFFmpegKill(t *testing.T) { Args: []string{}, Reconnect: false, StaleTimeout: 0, + PSUtil: newPSUtil(), }) err = p.Start() @@ -280,6 +295,7 @@ func TestProcessForceKill(t *testing.T) { Args: []string{}, Reconnect: false, StaleTimeout: 0, + PSUtil: newPSUtil(), }) err = p.Start() @@ -312,6 +328,7 @@ func TestProcessDuration(t *testing.T) { Binary: binary, Args: []string{}, Timeout: 3 * time.Second, + PSUtil: newPSUtil(), }) require.NoError(t, err) @@ -358,6 +375,7 @@ func TestProcessSchedulePointInTime(t *testing.T) { }, Reconnect: false, Scheduler: s, + PSUtil: newPSUtil(), }) status := p.Status() @@ -399,6 +417,7 @@ func TestProcessSchedulePointInTimeGone(t *testing.T) { }, Reconnect: false, Scheduler: s, + PSUtil: newPSUtil(), }) status := p.Status() @@ -424,6 +443,7 @@ func TestProcessScheduleCron(t *testing.T) { }, Reconnect: false, Scheduler: s, + PSUtil: newPSUtil(), }) status := p.Status() @@ -454,6 +474,7 @@ func TestProcessDelayNoScheduler(t *testing.T) { Binary: "sleep", Reconnect: false, ReconnectDelay: 5 * time.Second, + PSUtil: newPSUtil(), }) px := p.(*process) @@ -470,6 +491,7 @@ func TestProcessDelayNoScheduler(t *testing.T) { Binary: "sleep", Reconnect: true, ReconnectDelay: 5 * time.Second, + PSUtil: newPSUtil(), }) px = p.(*process) @@ -493,6 +515,7 @@ func TestProcessDelaySchedulerNoReconnect(t *testing.T) { Reconnect: false, ReconnectDelay: 1 * time.Second, Scheduler: s, + PSUtil: newPSUtil(), }) px := p.(*process) @@ -514,6 +537,7 @@ func TestProcessDelaySchedulerNoReconnect(t *testing.T) { Reconnect: false, ReconnectDelay: 1 * time.Second, Scheduler: s, + PSUtil: newPSUtil(), }) px = p.(*process) @@ -537,6 +561,7 @@ func TestProcessDelaySchedulerReconnect(t *testing.T) { Reconnect: true, ReconnectDelay: 1 * time.Second, Scheduler: s, + PSUtil: newPSUtil(), }) px := p.(*process) @@ -558,6 +583,7 @@ func TestProcessDelaySchedulerReconnect(t *testing.T) { Reconnect: true, ReconnectDelay: 1 * time.Second, Scheduler: s, + PSUtil: newPSUtil(), }) px = p.(*process) @@ -579,6 +605,7 @@ func TestProcessDelaySchedulerReconnect(t *testing.T) { Reconnect: true, ReconnectDelay: 10 * time.Second, Scheduler: s, + PSUtil: newPSUtil(), }) px = p.(*process) @@ -636,6 +663,7 @@ func TestProcessCallbacks(t *testing.T) { onState = append(onState, from+"/"+to) }, + PSUtil: newPSUtil(), }) require.NoError(t, err) @@ -678,6 +706,7 @@ func TestProcessCallbacksOnBeforeStart(t *testing.T) { OnBeforeStart: func(a []string) ([]string, error) { return a, fmt.Errorf("no, not now") }, + PSUtil: newPSUtil(), }) require.NoError(t, err) diff --git a/psutil/fixtures/cgroup-limited/cpu/cpu.cfs_period_us b/resources/psutil/fixtures/cgroup-limited/cpu/cpu.cfs_period_us similarity index 100% rename from psutil/fixtures/cgroup-limited/cpu/cpu.cfs_period_us rename to resources/psutil/fixtures/cgroup-limited/cpu/cpu.cfs_period_us diff --git a/psutil/fixtures/cgroup-limited/cpu/cpu.cfs_quota_us b/resources/psutil/fixtures/cgroup-limited/cpu/cpu.cfs_quota_us similarity index 100% rename from psutil/fixtures/cgroup-limited/cpu/cpu.cfs_quota_us rename to resources/psutil/fixtures/cgroup-limited/cpu/cpu.cfs_quota_us diff --git a/psutil/fixtures/cgroup-limited/cpuacct/cpuacct.usage b/resources/psutil/fixtures/cgroup-limited/cpuacct/cpuacct.usage similarity index 100% rename from psutil/fixtures/cgroup-limited/cpuacct/cpuacct.usage rename to resources/psutil/fixtures/cgroup-limited/cpuacct/cpuacct.usage diff --git a/psutil/fixtures/cgroup-limited/memory/memory.limit_in_bytes b/resources/psutil/fixtures/cgroup-limited/memory/memory.limit_in_bytes similarity index 100% rename from psutil/fixtures/cgroup-limited/memory/memory.limit_in_bytes rename to resources/psutil/fixtures/cgroup-limited/memory/memory.limit_in_bytes diff --git a/psutil/fixtures/cgroup-limited/memory/memory.usage_in_bytes b/resources/psutil/fixtures/cgroup-limited/memory/memory.usage_in_bytes similarity index 100% rename from psutil/fixtures/cgroup-limited/memory/memory.usage_in_bytes rename to resources/psutil/fixtures/cgroup-limited/memory/memory.usage_in_bytes diff --git a/psutil/fixtures/cgroup/cpu/cpu.cfs_period_us b/resources/psutil/fixtures/cgroup/cpu/cpu.cfs_period_us similarity index 100% rename from psutil/fixtures/cgroup/cpu/cpu.cfs_period_us rename to resources/psutil/fixtures/cgroup/cpu/cpu.cfs_period_us diff --git a/psutil/fixtures/cgroup/cpu/cpu.cfs_quota_us b/resources/psutil/fixtures/cgroup/cpu/cpu.cfs_quota_us similarity index 100% rename from psutil/fixtures/cgroup/cpu/cpu.cfs_quota_us rename to resources/psutil/fixtures/cgroup/cpu/cpu.cfs_quota_us diff --git a/psutil/fixtures/cgroup/cpuacct/cpuacct.usage b/resources/psutil/fixtures/cgroup/cpuacct/cpuacct.usage similarity index 100% rename from psutil/fixtures/cgroup/cpuacct/cpuacct.usage rename to resources/psutil/fixtures/cgroup/cpuacct/cpuacct.usage diff --git a/psutil/fixtures/cgroup/memory/memory.limit_in_bytes b/resources/psutil/fixtures/cgroup/memory/memory.limit_in_bytes similarity index 100% rename from psutil/fixtures/cgroup/memory/memory.limit_in_bytes rename to resources/psutil/fixtures/cgroup/memory/memory.limit_in_bytes diff --git a/psutil/fixtures/cgroup/memory/memory.usage_in_bytes b/resources/psutil/fixtures/cgroup/memory/memory.usage_in_bytes similarity index 100% rename from psutil/fixtures/cgroup/memory/memory.usage_in_bytes rename to resources/psutil/fixtures/cgroup/memory/memory.usage_in_bytes diff --git a/psutil/fixtures/cgroup2-limited/cpu.max b/resources/psutil/fixtures/cgroup2-limited/cpu.max similarity index 100% rename from psutil/fixtures/cgroup2-limited/cpu.max rename to resources/psutil/fixtures/cgroup2-limited/cpu.max diff --git a/psutil/fixtures/cgroup2-limited/cpu.stat b/resources/psutil/fixtures/cgroup2-limited/cpu.stat similarity index 100% rename from psutil/fixtures/cgroup2-limited/cpu.stat rename to resources/psutil/fixtures/cgroup2-limited/cpu.stat diff --git a/psutil/fixtures/cgroup2-limited/memory.current b/resources/psutil/fixtures/cgroup2-limited/memory.current similarity index 100% rename from psutil/fixtures/cgroup2-limited/memory.current rename to resources/psutil/fixtures/cgroup2-limited/memory.current diff --git a/psutil/fixtures/cgroup2-limited/memory.max b/resources/psutil/fixtures/cgroup2-limited/memory.max similarity index 100% rename from psutil/fixtures/cgroup2-limited/memory.max rename to resources/psutil/fixtures/cgroup2-limited/memory.max diff --git a/psutil/fixtures/cgroup2/cpu.max b/resources/psutil/fixtures/cgroup2/cpu.max similarity index 100% rename from psutil/fixtures/cgroup2/cpu.max rename to resources/psutil/fixtures/cgroup2/cpu.max diff --git a/psutil/fixtures/cgroup2/cpu.stat b/resources/psutil/fixtures/cgroup2/cpu.stat similarity index 100% rename from psutil/fixtures/cgroup2/cpu.stat rename to resources/psutil/fixtures/cgroup2/cpu.stat diff --git a/psutil/fixtures/cgroup2/memory.current b/resources/psutil/fixtures/cgroup2/memory.current similarity index 100% rename from psutil/fixtures/cgroup2/memory.current rename to resources/psutil/fixtures/cgroup2/memory.current diff --git a/psutil/fixtures/cgroup2/memory.max b/resources/psutil/fixtures/cgroup2/memory.max similarity index 100% rename from psutil/fixtures/cgroup2/memory.max rename to resources/psutil/fixtures/cgroup2/memory.max diff --git a/psutil/gpu/gpu.go b/resources/psutil/gpu/gpu.go similarity index 70% rename from psutil/gpu/gpu.go rename to resources/psutil/gpu/gpu.go index cb8dcf00..dc7f5634 100644 --- a/psutil/gpu/gpu.go +++ b/resources/psutil/gpu/gpu.go @@ -43,3 +43,14 @@ type GPU interface { } var ErrProcessNotFound = errors.New("process not found") + +type dummy struct{} + +func (d *dummy) Count() (int, error) { return 0, nil } +func (d *dummy) Stats() ([]Stats, error) { return nil, nil } +func (d *dummy) Process(pid int32) (Process, error) { return Process{}, ErrProcessNotFound } +func (d *dummy) Close() {} + +func NewNilGPU() GPU { + return &dummy{} +} diff --git a/psutil/gpu/nvidia/fixtures/process.txt b/resources/psutil/gpu/nvidia/fixtures/process.txt similarity index 100% rename from psutil/gpu/nvidia/fixtures/process.txt rename to resources/psutil/gpu/nvidia/fixtures/process.txt diff --git a/psutil/gpu/nvidia/fixtures/query1.xml b/resources/psutil/gpu/nvidia/fixtures/query1.xml similarity index 100% rename from psutil/gpu/nvidia/fixtures/query1.xml rename to resources/psutil/gpu/nvidia/fixtures/query1.xml diff --git a/psutil/gpu/nvidia/fixtures/query2.xml b/resources/psutil/gpu/nvidia/fixtures/query2.xml similarity index 100% rename from psutil/gpu/nvidia/fixtures/query2.xml rename to resources/psutil/gpu/nvidia/fixtures/query2.xml diff --git a/psutil/gpu/nvidia/fixtures/query3.xml b/resources/psutil/gpu/nvidia/fixtures/query3.xml similarity index 100% rename from psutil/gpu/nvidia/fixtures/query3.xml rename to resources/psutil/gpu/nvidia/fixtures/query3.xml diff --git a/psutil/gpu/nvidia/nvidia.go b/resources/psutil/gpu/nvidia/nvidia.go similarity index 98% rename from psutil/gpu/nvidia/nvidia.go rename to resources/psutil/gpu/nvidia/nvidia.go index 98ad1520..a2aaf92f 100644 --- a/psutil/gpu/nvidia/nvidia.go +++ b/resources/psutil/gpu/nvidia/nvidia.go @@ -12,15 +12,9 @@ import ( "sync" "time" - "github.com/datarhei/core/v16/psutil/gpu" + "github.com/datarhei/core/v16/resources/psutil/gpu" ) -var Default gpu.GPU - -func init() { - Default = New("") -} - type Megabytes uint64 func (m *Megabytes) UnmarshalText(text []byte) error { diff --git a/psutil/gpu/nvidia/nvidia_test.go b/resources/psutil/gpu/nvidia/nvidia_test.go similarity index 96% rename from psutil/gpu/nvidia/nvidia_test.go rename to resources/psutil/gpu/nvidia/nvidia_test.go index 51954eb8..ddc48722 100644 --- a/psutil/gpu/nvidia/nvidia_test.go +++ b/resources/psutil/gpu/nvidia/nvidia_test.go @@ -9,7 +9,7 @@ import ( "time" "github.com/datarhei/core/v16/internal/testhelper" - "github.com/datarhei/core/v16/psutil/gpu" + "github.com/datarhei/core/v16/resources/psutil/gpu" "github.com/stretchr/testify/require" ) @@ -280,7 +280,7 @@ func TestWriterProcess(t *testing.T) { } func TestNvidiaGPUCount(t *testing.T) { - binary, err := testhelper.BuildBinary("nvidia-smi", "../../../internal/testhelper") + binary, err := testhelper.BuildBinary("nvidia-smi", "../../../../internal/testhelper") require.NoError(t, err, "Failed to build helper program") nv := New(binary) @@ -299,7 +299,7 @@ func TestNvidiaGPUCount(t *testing.T) { } func TestNvidiaGPUStats(t *testing.T) { - binary, err := testhelper.BuildBinary("nvidia-smi", "../../../internal/testhelper") + binary, err := testhelper.BuildBinary("nvidia-smi", "../../../../internal/testhelper") require.NoError(t, err, "Failed to build helper program") nv := New(binary) @@ -400,7 +400,7 @@ func TestNvidiaGPUStats(t *testing.T) { } func TestNvidiaGPUProcess(t *testing.T) { - binary, err := testhelper.BuildBinary("nvidia-smi", "../../../internal/testhelper") + binary, err := testhelper.BuildBinary("nvidia-smi", "../../../../internal/testhelper") require.NoError(t, err, "Failed to build helper program") nv := New(binary) diff --git a/psutil/process.go b/resources/psutil/process.go similarity index 95% rename from psutil/process.go rename to resources/psutil/process.go index ee17489f..636f85b6 100644 --- a/psutil/process.go +++ b/resources/psutil/process.go @@ -5,7 +5,7 @@ import ( "sync" "time" - "github.com/datarhei/core/v16/psutil/gpu/nvidia" + "github.com/datarhei/core/v16/resources/psutil/gpu" psprocess "github.com/shirou/gopsutil/v3/process" ) @@ -46,6 +46,8 @@ type process struct { statPreviousTime time.Time nTicks uint64 memRSS uint64 + + gpu gpu.GPU } func (u *util) Process(pid int32) (Process, error) { @@ -54,6 +56,7 @@ func (u *util) Process(pid int32) (Process, error) { hasCgroup: u.hasCgroup, cpuLimit: u.cpuLimit, ncpu: u.ncpu, + gpu: u.gpu, } proc, err := psprocess.NewProcess(pid) @@ -71,10 +74,6 @@ func (u *util) Process(pid int32) (Process, error) { return p, nil } -func NewProcess(pid int32) (Process, error) { - return DefaultUtil.Process(pid) -} - func (p *process) tickCPU(ctx context.Context, interval time.Duration) { ticker := time.NewTicker(interval) defer ticker.Stop() @@ -202,7 +201,7 @@ func (p *process) GPU() (*GPUInfo, error) { Index: -1, } - proc, err := nvidia.Default.Process(p.pid) + proc, err := p.gpu.Process(p.pid) if err != nil { return info, nil } diff --git a/psutil/process_linux.go b/resources/psutil/process_linux.go similarity index 100% rename from psutil/process_linux.go rename to resources/psutil/process_linux.go diff --git a/psutil/process_other.go b/resources/psutil/process_other.go similarity index 100% rename from psutil/process_other.go rename to resources/psutil/process_other.go diff --git a/psutil/psutil.go b/resources/psutil/psutil.go similarity index 95% rename from psutil/psutil.go rename to resources/psutil/psutil.go index 079e933d..34507534 100644 --- a/psutil/psutil.go +++ b/resources/psutil/psutil.go @@ -13,7 +13,7 @@ import ( "sync" "time" - "github.com/datarhei/core/v16/psutil/gpu/nvidia" + psutilgpu "github.com/datarhei/core/v16/resources/psutil/gpu" "github.com/shirou/gopsutil/v3/cpu" "github.com/shirou/gopsutil/v3/disk" @@ -41,12 +41,6 @@ var cgroup2Files = []string{ // https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/resource_management_guide/sect-cpu-example_usage // https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html -var DefaultUtil Util - -func init() { - DefaultUtil, _ = New("/sys/fs/cgroup") -} - type DiskInfo struct { Path string Fstype string @@ -141,10 +135,16 @@ type util struct { statPreviousTime time.Time nTicks uint64 mem MemoryInfo + + gpu psutilgpu.GPU } // New returns a new util, it will be started automatically -func New(root string) (Util, error) { +func New(root string, gpu psutilgpu.GPU) (Util, error) { + if len(root) == 0 { + root = "/sys/fs/cgroup" + } + u := &util{ root: os.DirFS(root), } @@ -173,6 +173,11 @@ func New(root string) (Util, error) { u.mem = *mem + u.gpu = gpu + if u.gpu == nil { + u.gpu = psutilgpu.NewNilGPU() + } + u.stopOnce.Do(func() {}) u.Start() @@ -353,10 +358,6 @@ func (u *util) CPUCounts() (float64, error) { return float64(ncpu), nil } -func CPUCounts() (float64, error) { - return DefaultUtil.CPUCounts() -} - // cpuTimes returns the current cpu usage times in seconds. func (u *util) cpuTimes() (*cpuTimesStat, error) { if u.hasCgroup && u.cpuLimit > 0 { @@ -439,10 +440,6 @@ func (u *util) CPU() (*CPUInfo, error) { return s, nil } -func CPUPercent() (*CPUInfo, error) { - return DefaultUtil.CPU() -} - func (u *util) cgroupCPUTimes(version int) (*cpuTimesStat, error) { info := &cpuTimesStat{} @@ -494,10 +491,6 @@ func (u *util) Disk(path string) (*DiskInfo, error) { return info, nil } -func Disk(path string) (*DiskInfo, error) { - return DefaultUtil.Disk(path) -} - func (u *util) virtualMemory() (*MemoryInfo, error) { info, err := mem.VirtualMemory() if err != nil { @@ -533,10 +526,6 @@ func (u *util) Memory() (*MemoryInfo, error) { return stat, nil } -func Memory() (*MemoryInfo, error) { - return DefaultUtil.Memory() -} - func (u *util) cgroupVirtualMemory(version int) (*MemoryInfo, error) { info := &MemoryInfo{} @@ -612,10 +601,6 @@ func (u *util) Network() ([]NetworkInfo, error) { return info, nil } -func Network() ([]NetworkInfo, error) { - return DefaultUtil.Network() -} - func (u *util) readFile(path string) ([]string, error) { file, err := u.root.Open(path) if err != nil { @@ -653,7 +638,7 @@ func cpuTotal(c *cpu.TimesStat) float64 { } func (u *util) GPU() ([]GPUInfo, error) { - nvstats, err := nvidia.Default.Stats() + nvstats, err := u.gpu.Stats() if err != nil { return nil, err } @@ -673,7 +658,3 @@ func (u *util) GPU() ([]GPUInfo, error) { return stats, nil } - -func GPU() ([]GPUInfo, error) { - return DefaultUtil.GPU() -} diff --git a/psutil/psutil_test.go b/resources/psutil/psutil_test.go similarity index 98% rename from psutil/psutil_test.go rename to resources/psutil/psutil_test.go index b4aaae9f..ae5b3095 100644 --- a/psutil/psutil_test.go +++ b/resources/psutil/psutil_test.go @@ -8,7 +8,7 @@ import ( ) func getUtil(path string) *util { - u, _ := New(path) + u, _ := New(path, nil) return u.(*util) } diff --git a/resources/resources.go b/resources/resources.go index 5a4043d5..c95f92a7 100644 --- a/resources/resources.go +++ b/resources/resources.go @@ -8,7 +8,7 @@ import ( "time" "github.com/datarhei/core/v16/log" - "github.com/datarhei/core/v16/psutil" + "github.com/datarhei/core/v16/resources/psutil" "github.com/datarhei/core/v16/slices" ) @@ -18,6 +18,21 @@ type Info struct { GPU GPUInfo } +type DiskInfo struct { + Path string + Fstype string + Total uint64 + Used uint64 + InodesTotal uint64 + InodesUsed uint64 +} + +type NetworkInfo struct { + Name string // interface name + BytesSent uint64 // number of bytes sent + BytesRecv uint64 // number of bytes received +} + type MemoryInfo struct { Total uint64 // bytes Available uint64 // bytes @@ -123,6 +138,9 @@ type Resources interface { // Info returns the current resource usage. Info() Info + + Disk(path string) (*DiskInfo, error) + Network() ([]NetworkInfo, error) } type Config struct { @@ -136,7 +154,11 @@ type Config struct { func New(config Config) (Resources, error) { if config.PSUtil == nil { - config.PSUtil = psutil.DefaultUtil + psutil, err := psutil.New("", nil) + if err != nil { + return nil, fmt.Errorf("unable to initialize psutils: %w", err) + } + config.PSUtil = psutil } gpu, err := config.PSUtil.GPU() @@ -572,3 +594,40 @@ func (r *resources) Info() Info { return i } + +func (r *resources) Disk(path string) (*DiskInfo, error) { + info, err := r.psutil.Disk(path) + if err != nil { + return nil, err + } + + diskinfo := &DiskInfo{ + Path: info.Path, + Fstype: info.Fstype, + Total: info.Total, + Used: info.Used, + InodesTotal: info.InodesTotal, + InodesUsed: info.InodesUsed, + } + + return diskinfo, nil +} + +func (r *resources) Network() ([]NetworkInfo, error) { + netio, err := r.psutil.Network() + if err != nil { + return nil, err + } + + info := []NetworkInfo{} + + for _, io := range netio { + info = append(info, NetworkInfo{ + Name: io.Name, + BytesSent: io.BytesSent, + BytesRecv: io.BytesRecv, + }) + } + + return info, nil +} diff --git a/resources/resources_test.go b/resources/resources_test.go index a1ee4244..84293817 100644 --- a/resources/resources_test.go +++ b/resources/resources_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/datarhei/core/v16/psutil" + "github.com/datarhei/core/v16/resources/psutil" "github.com/stretchr/testify/require" ) diff --git a/restream/core_test.go b/restream/core_test.go index 48d79d89..d1b644ee 100644 --- a/restream/core_test.go +++ b/restream/core_test.go @@ -16,7 +16,8 @@ import ( "github.com/datarhei/core/v16/internal/testhelper" "github.com/datarhei/core/v16/io/fs" "github.com/datarhei/core/v16/net" - "github.com/datarhei/core/v16/psutil" + "github.com/datarhei/core/v16/resources" + "github.com/datarhei/core/v16/resources/psutil" "github.com/datarhei/core/v16/restream/app" rfs "github.com/datarhei/core/v16/restream/fs" "github.com/datarhei/core/v16/restream/replace" @@ -32,12 +33,18 @@ func getDummyRestreamer(portrange net.Portranger, validatorIn, validatorOut ffmp return nil, fmt.Errorf("failed to build helper program: %w", err) } + psutil, err := psutil.New("", nil) + if err != nil { + return nil, err + } + ffmpeg, err := ffmpeg.New(ffmpeg.Config{ Binary: binary, LogHistoryLength: 3, Portrange: portrange, ValidatorInput: validatorIn, ValidatorOutput: validatorOut, + PSUtil: psutil, }) if err != nil { return nil, err @@ -81,11 +88,19 @@ func getDummyRestreamer(portrange net.Portranger, validatorIn, validatorOut ffmp return nil, err } + resources, err := resources.New(resources.Config{ + PSUtil: psutil, + }) + if err != nil { + return nil, err + } + rs, err := New(Config{ FFmpeg: ffmpeg, Replace: replacer, Filesystems: []fs.Filesystem{memfs}, Rewrite: rewriter, + Resources: resources, }) if err != nil { return nil, err @@ -1531,8 +1546,7 @@ func TestProcessLimit(t *testing.T) { status := task.ffmpeg.Status() - ncpu, err := psutil.CPUCounts() - require.NoError(t, err) + ncpu := rs.resources.Info().CPU.NCPU require.Equal(t, ncpu*process.LimitCPU, status.CPU.Limit) require.Equal(t, process.LimitMemory, status.Memory.Limit) From fbf62bf7e5078a05ca8f1dac942a5715d87c1ea5 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Mon, 28 Oct 2024 17:12:31 +0100 Subject: [PATCH 51/64] Remove Start() function, rename Stop() to Cancel() --- app/api/api.go | 4 +--- process/limiter.go | 2 +- process/limiter_test.go | 2 +- resources/psutil/process.go | 6 +++--- resources/psutil/psutil.go | 23 +++++++------------- resources/resources.go | 33 +++++++++++------------------ resources/resources_test.go | 42 +++++++++++-------------------------- 7 files changed, 38 insertions(+), 74 deletions(-) diff --git a/app/api/api.go b/app/api/api.go index 05689098..da0496fe 100644 --- a/app/api/api.go +++ b/app/api/api.go @@ -385,8 +385,6 @@ func (a *api) start(ctx context.Context) error { return fmt.Errorf("failed to initialize resource manager: %w", err) } - resources.Start() - a.resources = resources if cfg.Sessions.Enable { @@ -1915,7 +1913,7 @@ func (a *api) stop() { // Stop resource observer if a.resources != nil { - a.resources.Stop() + a.resources.Cancel() } // Stop the session tracker diff --git a/process/limiter.go b/process/limiter.go index cd9cc1ec..8bb650dc 100644 --- a/process/limiter.go +++ b/process/limiter.go @@ -367,7 +367,7 @@ func (l *limiter) Stop() { l.cancel() - l.proc.Stop() + l.proc.Cancel() l.proc = nil l.reset() diff --git a/process/limiter_test.go b/process/limiter_test.go index 535b8ccd..5d93bb68 100644 --- a/process/limiter_test.go +++ b/process/limiter_test.go @@ -37,7 +37,7 @@ func (p *psproc) GPU() (*psutil.GPUInfo, error) { }, nil } -func (p *psproc) Stop() {} +func (p *psproc) Cancel() {} func (p *psproc) Suspend() error { return nil } func (p *psproc) Resume() error { return nil } diff --git a/resources/psutil/process.go b/resources/psutil/process.go index 636f85b6..bb2f9064 100644 --- a/resources/psutil/process.go +++ b/resources/psutil/process.go @@ -20,8 +20,8 @@ type Process interface { // GPU returns the current GPU memory in bytes and usage in percent (0-100) of this process only. GPU() (*GPUInfo, error) - // Stop will stop collecting CPU and memory data for this process. - Stop() + // Cancel will stop collecting CPU and memory data for this process. + Cancel() // Suspend will send SIGSTOP to the process. Suspend() error @@ -133,7 +133,7 @@ func (p *process) collectMemory() uint64 { return info.RSS } -func (p *process) Stop() { +func (p *process) Cancel() { p.stopTicker() } diff --git a/resources/psutil/psutil.go b/resources/psutil/psutil.go index 34507534..c231792b 100644 --- a/resources/psutil/psutil.go +++ b/resources/psutil/psutil.go @@ -90,8 +90,7 @@ type cpuTimesStat struct { } type Util interface { - Start() - Stop() + Cancel() // CPUCounts returns the number of cores, either logical or physical. CPUCounts() (float64, error) @@ -178,24 +177,18 @@ func New(root string, gpu psutilgpu.GPU) (Util, error) { u.gpu = psutilgpu.NewNilGPU() } - u.stopOnce.Do(func() {}) + ctx, cancel := context.WithCancel(context.Background()) + u.stopTicker = cancel - u.Start() + go u.tickCPU(ctx, time.Second) + go u.tickMemory(ctx, time.Second) - return u, nil -} - -func (u *util) Start() { - u.startOnce.Do(func() { - ctx, cancel := context.WithCancel(context.Background()) - u.stopTicker = cancel + u.stopOnce = sync.Once{} - go u.tickCPU(ctx, time.Second) - go u.tickMemory(ctx, time.Second) - }) + return u, nil } -func (u *util) Stop() { +func (u *util) Cancel() { u.stopOnce.Do(func() { u.stopTicker() diff --git a/resources/resources.go b/resources/resources.go index c95f92a7..2130b1f4 100644 --- a/resources/resources.go +++ b/resources/resources.go @@ -113,16 +113,14 @@ type resources struct { cancelObserver context.CancelFunc - lock sync.RWMutex - startOnce sync.Once - stopOnce sync.Once + lock sync.RWMutex + stopOnce sync.Once logger log.Logger } type Resources interface { - Start() - Stop() + Cancel() // HasLimits returns whether any limits have been set. HasLimits() bool @@ -243,30 +241,23 @@ func New(config Config) (Resources, error) { r.logger.Debug().Log("Created") - r.stopOnce.Do(func() {}) + ctx, cancel := context.WithCancel(context.Background()) + r.cancelObserver = cancel - return r, nil -} - -func (r *resources) Start() { - r.startOnce.Do(func() { - ctx, cancel := context.WithCancel(context.Background()) - r.cancelObserver = cancel + go r.observe(ctx, time.Second) - go r.observe(ctx, time.Second) + r.stopOnce = sync.Once{} - r.stopOnce = sync.Once{} + r.logger.Info().Log("Started") - r.logger.Info().Log("Started") - }) + return r, nil } -func (r *resources) Stop() { +func (r *resources) Cancel() { r.stopOnce.Do(func() { r.cancelObserver() - r.self.Stop() - - r.startOnce = sync.Once{} + r.psutil.Cancel() + r.self.Cancel() r.logger.Info().Log("Stopped") }) diff --git a/resources/resources_test.go b/resources/resources_test.go index 84293817..4b6b6bae 100644 --- a/resources/resources_test.go +++ b/resources/resources_test.go @@ -49,8 +49,8 @@ func newUtil(ngpu int) *util { return u } -func (u *util) Start() {} -func (u *util) Stop() {} +func (u *util) Start() {} +func (u *util) Cancel() {} func (u *util) CPUCounts() (float64, error) { return 2, nil @@ -122,7 +122,7 @@ func (p *process) GPU() (*psutil.GPUInfo, error) { Decoder: 7, }, nil } -func (p *process) Stop() {} +func (p *process) Cancel() {} func (p *process) Suspend() error { return nil } func (p *process) Resume() error { return nil } @@ -198,8 +198,6 @@ func TestMemoryLimit(t *testing.T) { } }() - r.Start() - wg.Wait() require.True(t, limit) @@ -207,7 +205,7 @@ func TestMemoryLimit(t *testing.T) { _, err = r.Request(Request{CPU: 5, Memory: 10}) require.Error(t, err) - r.Stop() + r.Cancel() } func TestMemoryUnlimit(t *testing.T) { @@ -250,8 +248,6 @@ func TestMemoryUnlimit(t *testing.T) { } }() - r.Start() - wg.Wait() require.True(t, limit) @@ -293,7 +289,7 @@ func TestMemoryUnlimit(t *testing.T) { require.False(t, limit) - r.Stop() + r.Cancel() } func TestCPULimit(t *testing.T) { @@ -334,8 +330,6 @@ func TestCPULimit(t *testing.T) { } }() - r.Start() - wg.Wait() require.True(t, limit) @@ -343,7 +337,7 @@ func TestCPULimit(t *testing.T) { _, err = r.Request(Request{CPU: 5, Memory: 10}) require.Error(t, err) - r.Stop() + r.Cancel() } func TestCPUUnlimit(t *testing.T) { @@ -386,8 +380,6 @@ func TestCPUUnlimit(t *testing.T) { } }() - r.Start() - wg.Wait() require.True(t, limit) @@ -429,7 +421,7 @@ func TestCPUUnlimit(t *testing.T) { require.False(t, limit) - r.Stop() + r.Cancel() } func TestGPULimitMemory(t *testing.T) { @@ -472,8 +464,6 @@ func TestGPULimitMemory(t *testing.T) { } }() - r.Start() - wg.Wait() require.Contains(t, limit, true) @@ -481,7 +471,7 @@ func TestGPULimitMemory(t *testing.T) { _, err = r.Request(Request{CPU: 5, Memory: 10, GPUUsage: 10, GPUMemory: 10}) require.Error(t, err) - r.Stop() + r.Cancel() } func TestGPUUnlimitMemory(t *testing.T) { @@ -526,8 +516,6 @@ func TestGPUUnlimitMemory(t *testing.T) { } }() - r.Start() - wg.Wait() require.Contains(t, limit, true) @@ -567,7 +555,7 @@ func TestGPUUnlimitMemory(t *testing.T) { require.NotContains(t, limit, true) - r.Stop() + r.Cancel() } func TestGPULimitMemorySome(t *testing.T) { @@ -610,8 +598,6 @@ func TestGPULimitMemorySome(t *testing.T) { } }() - r.Start() - wg.Wait() require.Equal(t, []bool{false, false, true, true}, limit) @@ -619,7 +605,7 @@ func TestGPULimitMemorySome(t *testing.T) { _, err = r.Request(Request{CPU: 5, Memory: 10, GPUUsage: 10, GPUMemory: 10}) require.NoError(t, err) - r.Stop() + r.Cancel() } func TestGPULimitUsage(t *testing.T) { @@ -662,8 +648,6 @@ func TestGPULimitUsage(t *testing.T) { } }() - r.Start() - wg.Wait() require.Equal(t, []bool{true, false, false}, limit) @@ -674,7 +658,7 @@ func TestGPULimitUsage(t *testing.T) { _, err = r.Request(Request{CPU: 5, Memory: 10, GPUEncoder: 10, GPUMemory: 10}) require.NoError(t, err) - r.Stop() + r.Cancel() } func TestGPUUnlimitUsage(t *testing.T) { @@ -719,8 +703,6 @@ func TestGPUUnlimitUsage(t *testing.T) { } }() - r.Start() - wg.Wait() require.Equal(t, []bool{true, false, false}, limit) @@ -761,7 +743,7 @@ func TestGPUUnlimitUsage(t *testing.T) { require.Equal(t, []bool{false, false, false}, limit) - r.Stop() + r.Cancel() } func TestRequestCPU(t *testing.T) { From 2ee7fa7e4112ef78b2a98c99a98685a21120a803 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Tue, 29 Oct 2024 12:25:39 +0100 Subject: [PATCH 52/64] Make resources the only direct user of psutil --- app/api/api.go | 5 +- ffmpeg/ffmpeg.go | 14 ++--- http/mock/mock.go | 10 +++- process/limiter.go | 58 ++++++++----------- process/limiter_test.go | 80 ++++++++++++--------------- process/process.go | 22 ++++---- process/process_test.go | 61 ++++++++++---------- resources/resources.go | 107 ++++++++++++++++++++++++++++++++++++ resources/resources_test.go | 90 +++++++++++++++--------------- restream/core_test.go | 16 +++--- 10 files changed, 280 insertions(+), 183 deletions(-) diff --git a/app/api/api.go b/app/api/api.go index da0496fe..6665d619 100644 --- a/app/api/api.go +++ b/app/api/api.go @@ -38,6 +38,7 @@ import ( "github.com/datarhei/core/v16/prometheus" "github.com/datarhei/core/v16/resources" "github.com/datarhei/core/v16/resources/psutil" + "github.com/datarhei/core/v16/resources/psutil/gpu/nvidia" "github.com/datarhei/core/v16/restream" restreamapp "github.com/datarhei/core/v16/restream/app" "github.com/datarhei/core/v16/restream/replace" @@ -368,7 +369,7 @@ func (a *api) start(ctx context.Context) error { debug.SetMemoryLimit(math.MaxInt64) } - psutil, err := psutil.New("", nil) + psutil, err := psutil.New("", nvidia.New("")) if err != nil { return fmt.Errorf("failed to initialize psutils: %w", err) } @@ -511,7 +512,7 @@ func (a *api) start(ctx context.Context) error { ValidatorOutput: validatorOut, Portrange: portrange, Collector: a.sessions.Collector("ffmpeg"), - PSUtil: psutil, + Resource: a.resources, }) if err != nil { return fmt.Errorf("unable to create ffmpeg: %w", err) diff --git a/ffmpeg/ffmpeg.go b/ffmpeg/ffmpeg.go index 5e64fd1e..b3c4b445 100644 --- a/ffmpeg/ffmpeg.go +++ b/ffmpeg/ffmpeg.go @@ -12,7 +12,7 @@ import ( "github.com/datarhei/core/v16/log" "github.com/datarhei/core/v16/net" "github.com/datarhei/core/v16/process" - "github.com/datarhei/core/v16/resources/psutil" + "github.com/datarhei/core/v16/resources" "github.com/datarhei/core/v16/session" ) @@ -64,7 +64,7 @@ type Config struct { ValidatorOutput Validator Portrange net.Portranger Collector session.Collector - PSUtil psutil.Util + Resource resources.Resources } type ffmpeg struct { @@ -83,17 +83,17 @@ type ffmpeg struct { states process.States statesLock sync.RWMutex - psutil psutil.Util + resources resources.Resources } func New(config Config) (FFmpeg, error) { f := &ffmpeg{} - if config.PSUtil == nil { - return nil, fmt.Errorf("psutils required") + if config.Resource == nil { + return nil, fmt.Errorf("resources are required") } - f.psutil = config.PSUtil + f.resources = config.Resource binary, err := exec.LookPath(config.Binary) if err != nil { @@ -194,7 +194,7 @@ func (f *ffmpeg) New(config ProcessConfig) (process.Process, error) { config.OnStateChange(from, to) } }, - PSUtil: f.psutil, + Resources: f.resources, }) return ffmpeg, err diff --git a/http/mock/mock.go b/http/mock/mock.go index 7487c9af..f6b516fc 100644 --- a/http/mock/mock.go +++ b/http/mock/mock.go @@ -17,6 +17,7 @@ import ( "github.com/datarhei/core/v16/http/validator" "github.com/datarhei/core/v16/internal/testhelper" "github.com/datarhei/core/v16/io/fs" + "github.com/datarhei/core/v16/resources" "github.com/datarhei/core/v16/resources/psutil" "github.com/datarhei/core/v16/restream" jsonstore "github.com/datarhei/core/v16/restream/store/json" @@ -50,11 +51,18 @@ func DummyRestreamer(pathPrefix string) (restream.Restreamer, error) { return nil, err } + resources, err := resources.New(resources.Config{ + PSUtil: psutil, + }) + if err != nil { + return nil, err + } + ffmpeg, err := ffmpeg.New(ffmpeg.Config{ Binary: binary, MaxLogLines: 100, LogHistoryLength: 3, - PSUtil: psutil, + Resource: resources, }) if err != nil { return nil, err diff --git a/process/limiter.go b/process/limiter.go index 8bb650dc..b30d4731 100644 --- a/process/limiter.go +++ b/process/limiter.go @@ -7,7 +7,7 @@ import ( "time" "github.com/datarhei/core/v16/log" - "github.com/datarhei/core/v16/resources/psutil" + "github.com/datarhei/core/v16/resources" ) type Usage struct { @@ -85,13 +85,13 @@ type LimiterConfig struct { WaitFor time.Duration // Duration for one of the limits has to be above the limit until OnLimit gets triggered. OnLimit LimitFunc // Function to be triggered if limits are exceeded. Mode LimitMode // How to limit CPU usage. - PSUtil psutil.Util + NCPU float64 // Number of available CPU Logger log.Logger } type Limiter interface { // Start starts the limiter with a psutil.Process. - Start(process psutil.Process) error + Start(process resources.Process) error // Stop stops the limiter. The limiter can be reused by calling Start() again Stop() @@ -226,11 +226,9 @@ func (x *metric[T]) IsExceeded(waitFor time.Duration, mode LimitMode) bool { } type limiter struct { - psutil psutil.Util - ncpu float64 ncpuFactor float64 - proc psutil.Process + proc resources.Process lock sync.RWMutex cancel context.CancelFunc onLimit LimitFunc @@ -259,13 +257,17 @@ type limiter struct { // NewLimiter returns a new Limiter func NewLimiter(config LimiterConfig) (Limiter, error) { l := &limiter{ + ncpu: config.NCPU, waitFor: config.WaitFor, onLimit: config.OnLimit, mode: config.Mode, - psutil: config.PSUtil, logger: config.Logger, } + if l.ncpu <= 0 { + l.ncpu = 1 + } + l.cpu.SetLimit(config.CPU / 100) l.memory.SetLimit(config.Memory) l.gpu.memory.SetLimit(config.GPUMemory) @@ -277,16 +279,6 @@ func NewLimiter(config LimiterConfig) (Limiter, error) { l.logger = log.New("") } - if l.psutil == nil { - return nil, fmt.Errorf("no psutil provided") - } - - if ncpu, err := l.psutil.CPUCounts(); err != nil { - l.ncpu = 1 - } else { - l.ncpu = ncpu - } - l.lastUsage.CPU.NCPU = l.ncpu l.lastUsage.CPU.Limit = l.cpu.Limit() * 100 * l.ncpu l.lastUsage.Memory.Limit = l.memory.Limit() @@ -333,7 +325,7 @@ func (l *limiter) reset() { l.gpu.decoder.Reset() } -func (l *limiter) Start(process psutil.Process) error { +func (l *limiter) Start(process resources.Process) error { l.lock.Lock() defer l.lock.Unlock() @@ -396,28 +388,24 @@ func (l *limiter) collect() { return } - mstat, merr := proc.Memory() - cpustat, cerr := proc.CPU() - gstat, gerr := proc.GPU() + pinfo, err := proc.Info() + + //mstat, merr := proc.Memory() + //cpustat, cerr := proc.CPU() + //gstat, gerr := proc.GPU() gindex := -1 l.lock.Lock() defer l.lock.Unlock() - if merr == nil { - l.memory.Update(mstat) - } - - if cerr == nil { - l.cpu.Update((cpustat.System + cpustat.User + cpustat.Other) / 100) - } - - if gerr == nil { - l.gpu.memory.Update(gstat.MemoryUsed) - l.gpu.usage.Update(gstat.Usage / 100) - l.gpu.encoder.Update(gstat.Encoder / 100) - l.gpu.decoder.Update(gstat.Decoder / 100) - gindex = gstat.Index + if err == nil { + l.memory.Update(pinfo.Memory) + l.cpu.Update((pinfo.CPU.System + pinfo.CPU.User + pinfo.CPU.Other) / 100) + l.gpu.memory.Update(pinfo.GPU.MemoryUsed) + l.gpu.usage.Update(pinfo.GPU.Usage / 100) + l.gpu.encoder.Update(pinfo.GPU.Encoder / 100) + l.gpu.decoder.Update(pinfo.GPU.Decoder / 100) + gindex = pinfo.GPU.Index } isLimitExceeded := false diff --git a/process/limiter_test.go b/process/limiter_test.go index 5d93bb68..56813958 100644 --- a/process/limiter_test.go +++ b/process/limiter_test.go @@ -5,41 +5,39 @@ import ( "testing" "time" - "github.com/datarhei/core/v16/resources/psutil" + "github.com/datarhei/core/v16/resources" "github.com/stretchr/testify/require" ) -type psproc struct{} - -func (p *psproc) CPU() (*psutil.CPUInfo, error) { - return &psutil.CPUInfo{ - System: 50, - User: 0, - Idle: 0, - Other: 0, - }, nil -} - -func (p *psproc) Memory() (uint64, error) { - return 197, nil -} - -func (p *psproc) GPU() (*psutil.GPUInfo, error) { - return &psutil.GPUInfo{ - Index: 0, - Name: "L4", - MemoryTotal: 128, - MemoryUsed: 91, - Usage: 3, - Encoder: 9, - Decoder: 5, - }, nil +type proc struct{} + +func (p *proc) Info() (resources.ProcessInfo, error) { + info := resources.ProcessInfo{ + CPU: resources.ProcessInfoCPU{ + System: 50, + User: 0, + Idle: 0, + Other: 0, + }, + Memory: 197, + GPU: resources.ProcessInfoGPU{ + Index: 0, + Name: "L4", + MemoryTotal: 128, + MemoryUsed: 91, + Usage: 3, + Encoder: 9, + Decoder: 5, + }, + } + + return info, nil } -func (p *psproc) Cancel() {} -func (p *psproc) Suspend() error { return nil } -func (p *psproc) Resume() error { return nil } +func (p *proc) Cancel() {} +func (p *proc) Suspend() error { return nil } +func (p *proc) Resume() error { return nil } func TestCPULimit(t *testing.T) { lock := sync.Mutex{} @@ -57,10 +55,9 @@ func TestCPULimit(t *testing.T) { OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, - PSUtil: newPSUtil(), }) - l.Start(&psproc{}) + l.Start(&proc{}) defer l.Stop() wg.Wait() @@ -95,10 +92,9 @@ func TestCPULimitWaitFor(t *testing.T) { OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, - PSUtil: newPSUtil(), }) - l.Start(&psproc{}) + l.Start(&proc{}) defer l.Stop() wg.Wait() @@ -132,10 +128,9 @@ func TestMemoryLimit(t *testing.T) { OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, - PSUtil: newPSUtil(), }) - l.Start(&psproc{}) + l.Start(&proc{}) defer l.Stop() wg.Wait() @@ -170,10 +165,9 @@ func TestMemoryLimitWaitFor(t *testing.T) { OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, - PSUtil: newPSUtil(), }) - l.Start(&psproc{}) + l.Start(&proc{}) defer l.Stop() wg.Wait() @@ -207,10 +201,9 @@ func TestGPUMemoryLimit(t *testing.T) { OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, - PSUtil: newPSUtil(), }) - l.Start(&psproc{}) + l.Start(&proc{}) defer l.Stop() wg.Wait() @@ -245,10 +238,9 @@ func TestGPUMemoryLimitWaitFor(t *testing.T) { OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, - PSUtil: newPSUtil(), }) - l.Start(&psproc{}) + l.Start(&proc{}) defer l.Stop() wg.Wait() @@ -283,10 +275,9 @@ func TestMemoryLimitSoftMode(t *testing.T) { OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, - PSUtil: newPSUtil(), }) - l.Start(&psproc{}) + l.Start(&proc{}) defer l.Stop() l.Limit(false, true, false) @@ -323,10 +314,9 @@ func TestGPUMemoryLimitSoftMode(t *testing.T) { OnLimit: func(float64, uint64, float64, float64, float64, uint64) { wg.Done() }, - PSUtil: newPSUtil(), }) - l.Start(&psproc{}) + l.Start(&proc{}) defer l.Stop() l.Limit(false, false, true) diff --git a/process/process.go b/process/process.go index 0d5b50ca..be0a9854 100644 --- a/process/process.go +++ b/process/process.go @@ -18,7 +18,7 @@ import ( "unicode/utf8" "github.com/datarhei/core/v16/log" - "github.com/datarhei/core/v16/resources/psutil" + "github.com/datarhei/core/v16/resources" ) // Process represents a process and ways to control it @@ -71,7 +71,7 @@ type Config struct { OnStart func() // A callback which is called after the process started. OnExit func(state string) // A callback which is called after the process exited with the exit state. OnStateChange func(from, to string) // A callback which is called after a state changed. - PSUtil psutil.Util + Resources resources.Resources Logger log.Logger } @@ -245,7 +245,7 @@ type process struct { } limits Limiter scheduler Scheduler - psutil psutil.Util + resources resources.Resources } var _ Process = &process{} @@ -259,11 +259,11 @@ func New(config Config) (Process, error) { parser: config.Parser, logger: config.Logger, scheduler: config.Scheduler, - psutil: config.PSUtil, + resources: config.Resources, } - if p.psutil == nil { - return nil, fmt.Errorf("no psutils given") + if p.resources == nil { + return nil, fmt.Errorf("resources are required") } p.args = make([]string, len(config.Args)) @@ -276,10 +276,6 @@ func New(config Config) (Process, error) { return nil, fmt.Errorf("no valid binary given") } - if p.psutil == nil { - return nil, fmt.Errorf("no psutils provided") - } - if p.parser == nil { p.parser = NewNullParser() } @@ -308,8 +304,11 @@ func New(config Config) (Process, error) { p.callbacks.onExit = config.OnExit p.callbacks.onStateChange = config.OnStateChange + ncpu := p.resources.Info().CPU.NCPU + limits, err := NewLimiter(LimiterConfig{ CPU: config.LimitCPU, + NCPU: ncpu, Memory: config.LimitMemory, GPUUsage: config.LimitGPUUsage, GPUEncoder: config.LimitGPUEncoder, @@ -333,7 +332,6 @@ func New(config Config) (Process, error) { }).Warn().Log("Killed because limits are exceeded") p.Kill(false, fmt.Sprintf("Killed because limits are exceeded (mode: %s, tolerance: %s): %.2f (%.2f) CPU, %d (%d) bytes memory, %.2f/%.2f/%.2f (%.2f) GPU usage, %d (%d) bytes GPU memory", config.LimitMode.String(), config.LimitDuration.String(), cpu, config.LimitCPU, memory, config.LimitMemory, gpuusage, gpuencoder, gpudecoder, config.LimitGPUUsage, gpumemory, config.LimitGPUMemory)) }, - PSUtil: p.psutil, }) if err != nil { return nil, fmt.Errorf("failed to initialize limiter") @@ -719,7 +717,7 @@ func (p *process) start() error { p.pid = int32(p.cmd.Process.Pid) - if proc, err := p.psutil.Process(p.pid); err == nil { + if proc, err := p.resources.Process(p.pid); err == nil { p.limits.Start(proc) } diff --git a/process/process_test.go b/process/process_test.go index 88045b9d..ce4cc01b 100644 --- a/process/process_test.go +++ b/process/process_test.go @@ -10,13 +10,18 @@ import ( "github.com/datarhei/core/v16/internal/testhelper" "github.com/datarhei/core/v16/math/rand" + "github.com/datarhei/core/v16/resources" "github.com/datarhei/core/v16/resources/psutil" "github.com/stretchr/testify/require" ) -func newPSUtil() psutil.Util { +func newResources() resources.Resources { util, _ := psutil.New("", nil) - return util + res, _ := resources.New(resources.Config{ + PSUtil: util, + }) + + return res } func TestProcess(t *testing.T) { @@ -27,7 +32,7 @@ func TestProcess(t *testing.T) { }, Reconnect: false, StaleTimeout: 0, - PSUtil: newPSUtil(), + Resources: newResources(), }) require.Equal(t, "finished", p.Status().State) @@ -66,7 +71,7 @@ func TestReconnectProcess(t *testing.T) { OnExit: func(string) { wg.Done() }, - PSUtil: newPSUtil(), + Resources: newResources(), }) p.Start() @@ -112,7 +117,7 @@ func TestStaleProcess(t *testing.T) { }, Reconnect: false, StaleTimeout: 2 * time.Second, - PSUtil: newPSUtil(), + Resources: newResources(), }) p.Start() @@ -135,7 +140,7 @@ func TestStaleReconnectProcess(t *testing.T) { Reconnect: true, ReconnectDelay: 2 * time.Second, StaleTimeout: 3 * time.Second, - PSUtil: newPSUtil(), + Resources: newResources(), }) p.Start() @@ -166,7 +171,7 @@ func TestNonExistingProcess(t *testing.T) { Reconnect: false, ReconnectDelay: 5 * time.Second, StaleTimeout: 0, - PSUtil: newPSUtil(), + Resources: newResources(), }) p.Start() @@ -191,7 +196,7 @@ func TestNonExistingReconnectProcess(t *testing.T) { Reconnect: true, ReconnectDelay: 2 * time.Second, StaleTimeout: 0, - PSUtil: newPSUtil(), + Resources: newResources(), }) p.Start() @@ -215,7 +220,7 @@ func TestProcessFailed(t *testing.T) { }, Reconnect: false, StaleTimeout: 0, - PSUtil: newPSUtil(), + Resources: newResources(), }) p.Start() @@ -241,7 +246,7 @@ func TestFFmpegWaitStop(t *testing.T) { OnExit: func(state string) { time.Sleep(3 * time.Second) }, - PSUtil: newPSUtil(), + Resources: newResources(), }) err = p.Start() @@ -269,7 +274,7 @@ func TestFFmpegKill(t *testing.T) { Args: []string{}, Reconnect: false, StaleTimeout: 0, - PSUtil: newPSUtil(), + Resources: newResources(), }) err = p.Start() @@ -295,7 +300,7 @@ func TestProcessForceKill(t *testing.T) { Args: []string{}, Reconnect: false, StaleTimeout: 0, - PSUtil: newPSUtil(), + Resources: newResources(), }) err = p.Start() @@ -325,10 +330,10 @@ func TestProcessDuration(t *testing.T) { require.NoError(t, err, "Failed to build helper program") p, err := New(Config{ - Binary: binary, - Args: []string{}, - Timeout: 3 * time.Second, - PSUtil: newPSUtil(), + Binary: binary, + Args: []string{}, + Timeout: 3 * time.Second, + Resources: newResources(), }) require.NoError(t, err) @@ -375,7 +380,7 @@ func TestProcessSchedulePointInTime(t *testing.T) { }, Reconnect: false, Scheduler: s, - PSUtil: newPSUtil(), + Resources: newResources(), }) status := p.Status() @@ -417,7 +422,7 @@ func TestProcessSchedulePointInTimeGone(t *testing.T) { }, Reconnect: false, Scheduler: s, - PSUtil: newPSUtil(), + Resources: newResources(), }) status := p.Status() @@ -443,7 +448,7 @@ func TestProcessScheduleCron(t *testing.T) { }, Reconnect: false, Scheduler: s, - PSUtil: newPSUtil(), + Resources: newResources(), }) status := p.Status() @@ -474,7 +479,7 @@ func TestProcessDelayNoScheduler(t *testing.T) { Binary: "sleep", Reconnect: false, ReconnectDelay: 5 * time.Second, - PSUtil: newPSUtil(), + Resources: newResources(), }) px := p.(*process) @@ -491,7 +496,7 @@ func TestProcessDelayNoScheduler(t *testing.T) { Binary: "sleep", Reconnect: true, ReconnectDelay: 5 * time.Second, - PSUtil: newPSUtil(), + Resources: newResources(), }) px = p.(*process) @@ -515,7 +520,7 @@ func TestProcessDelaySchedulerNoReconnect(t *testing.T) { Reconnect: false, ReconnectDelay: 1 * time.Second, Scheduler: s, - PSUtil: newPSUtil(), + Resources: newResources(), }) px := p.(*process) @@ -537,7 +542,7 @@ func TestProcessDelaySchedulerNoReconnect(t *testing.T) { Reconnect: false, ReconnectDelay: 1 * time.Second, Scheduler: s, - PSUtil: newPSUtil(), + Resources: newResources(), }) px = p.(*process) @@ -561,7 +566,7 @@ func TestProcessDelaySchedulerReconnect(t *testing.T) { Reconnect: true, ReconnectDelay: 1 * time.Second, Scheduler: s, - PSUtil: newPSUtil(), + Resources: newResources(), }) px := p.(*process) @@ -583,7 +588,7 @@ func TestProcessDelaySchedulerReconnect(t *testing.T) { Reconnect: true, ReconnectDelay: 1 * time.Second, Scheduler: s, - PSUtil: newPSUtil(), + Resources: newResources(), }) px = p.(*process) @@ -605,7 +610,7 @@ func TestProcessDelaySchedulerReconnect(t *testing.T) { Reconnect: true, ReconnectDelay: 10 * time.Second, Scheduler: s, - PSUtil: newPSUtil(), + Resources: newResources(), }) px = p.(*process) @@ -663,7 +668,7 @@ func TestProcessCallbacks(t *testing.T) { onState = append(onState, from+"/"+to) }, - PSUtil: newPSUtil(), + Resources: newResources(), }) require.NoError(t, err) @@ -706,7 +711,7 @@ func TestProcessCallbacksOnBeforeStart(t *testing.T) { OnBeforeStart: func(a []string) ([]string, error) { return a, fmt.Errorf("no, not now") }, - PSUtil: newPSUtil(), + Resources: newResources(), }) require.NoError(t, err) diff --git a/resources/resources.go b/resources/resources.go index 2130b1f4..17c77e4e 100644 --- a/resources/resources.go +++ b/resources/resources.go @@ -139,6 +139,8 @@ type Resources interface { Disk(path string) (*DiskInfo, error) Network() ([]NetworkInfo, error) + + Process(pid int32) (Process, error) } type Config struct { @@ -622,3 +624,108 @@ func (r *resources) Network() ([]NetworkInfo, error) { return info, nil } + +func (r *resources) Process(pid int32) (Process, error) { + proc, err := r.psutil.Process(pid) + if err != nil { + return nil, err + } + + p := &process{ + proc: proc, + } + + return p, nil +} + +type Process interface { + Info() (ProcessInfo, error) + + // Cancel will stop collecting CPU and memory data for this process. + Cancel() + + // Suspend will send SIGSTOP to the process. + Suspend() error + + // Resume will send SIGCONT to the process. + Resume() error +} + +type process struct { + proc psutil.Process +} + +type ProcessInfoCPU struct { + System float64 // percent 0-100 + User float64 // percent 0-100 + Idle float64 // percent 0-100 + Other float64 // percent 0-100 +} + +type ProcessInfoGPU struct { + Index int // Index of the GPU + Name string // Name of the GPU (not populated for a specific process) + + MemoryTotal uint64 // bytes (not populated for a specific process) + MemoryUsed uint64 // bytes + + Usage float64 // percent 0-100 + Encoder float64 // percent 0-100 + Decoder float64 // percent 0-100 +} + +type ProcessInfo struct { + CPU ProcessInfoCPU + Memory uint64 + GPU ProcessInfoGPU +} + +func (p *process) Info() (ProcessInfo, error) { + cpu, err := p.proc.CPU() + if err != nil { + return ProcessInfo{}, err + } + + mem, err := p.proc.Memory() + if err != nil { + return ProcessInfo{}, err + } + + gpu, err := p.proc.GPU() + if err != nil { + return ProcessInfo{}, err + } + + pi := ProcessInfo{ + CPU: ProcessInfoCPU{ + System: cpu.System, + User: cpu.User, + Idle: cpu.Idle, + Other: cpu.Other, + }, + Memory: mem, + GPU: ProcessInfoGPU{ + Index: gpu.Index, + Name: gpu.Name, + MemoryTotal: gpu.MemoryTotal, + MemoryUsed: gpu.MemoryUsed, + Usage: gpu.Usage, + Encoder: gpu.Encoder, + Decoder: gpu.Decoder, + }, + } + + return pi, nil +} + +func (p *process) Cancel() { + p.proc.Cancel() +} + +func (p *process) Suspend() error { + return p.proc.Suspend() +} + +func (p *process) Resume() error { + return p.proc.Resume() +} diff --git a/resources/resources_test.go b/resources/resources_test.go index 4b6b6bae..f590b881 100644 --- a/resources/resources_test.go +++ b/resources/resources_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" ) -type util struct { +type mockUtil struct { lock sync.Mutex cpu psutil.CPUInfo @@ -19,8 +19,8 @@ type util struct { gpu []psutil.GPUInfo } -func newUtil(ngpu int) *util { - u := &util{ +func newMockUtil(ngpu int) *mockUtil { + u := &mockUtil{ cpu: psutil.CPUInfo{ System: 10, User: 50, @@ -49,14 +49,14 @@ func newUtil(ngpu int) *util { return u } -func (u *util) Start() {} -func (u *util) Cancel() {} +func (u *mockUtil) Start() {} +func (u *mockUtil) Cancel() {} -func (u *util) CPUCounts() (float64, error) { +func (u *mockUtil) CPUCounts() (float64, error) { return 2, nil } -func (u *util) CPU() (*psutil.CPUInfo, error) { +func (u *mockUtil) CPU() (*psutil.CPUInfo, error) { u.lock.Lock() defer u.lock.Unlock() @@ -65,11 +65,11 @@ func (u *util) CPU() (*psutil.CPUInfo, error) { return &cpu, nil } -func (u *util) Disk(path string) (*psutil.DiskInfo, error) { +func (u *mockUtil) Disk(path string) (*psutil.DiskInfo, error) { return &psutil.DiskInfo{}, nil } -func (u *util) Memory() (*psutil.MemoryInfo, error) { +func (u *mockUtil) Memory() (*psutil.MemoryInfo, error) { u.lock.Lock() defer u.lock.Unlock() @@ -78,11 +78,11 @@ func (u *util) Memory() (*psutil.MemoryInfo, error) { return &mem, nil } -func (u *util) Network() ([]psutil.NetworkInfo, error) { +func (u *mockUtil) Network() ([]psutil.NetworkInfo, error) { return nil, nil } -func (u *util) GPU() ([]psutil.GPUInfo, error) { +func (u *mockUtil) GPU() ([]psutil.GPUInfo, error) { u.lock.Lock() defer u.lock.Unlock() @@ -93,13 +93,13 @@ func (u *util) GPU() ([]psutil.GPUInfo, error) { return gpu, nil } -func (u *util) Process(pid int32) (psutil.Process, error) { - return &process{}, nil +func (u *mockUtil) Process(pid int32) (psutil.Process, error) { + return &mockProcess{}, nil } -type process struct{} +type mockProcess struct{} -func (p *process) CPU() (*psutil.CPUInfo, error) { +func (p *mockProcess) CPU() (*psutil.CPUInfo, error) { s := &psutil.CPUInfo{ System: 1, User: 2, @@ -110,8 +110,8 @@ func (p *process) CPU() (*psutil.CPUInfo, error) { return s, nil } -func (p *process) Memory() (uint64, error) { return 42, nil } -func (p *process) GPU() (*psutil.GPUInfo, error) { +func (p *mockProcess) Memory() (uint64, error) { return 42, nil } +func (p *mockProcess) GPU() (*psutil.GPUInfo, error) { return &psutil.GPUInfo{ Index: 0, Name: "L4", @@ -122,13 +122,13 @@ func (p *process) GPU() (*psutil.GPUInfo, error) { Decoder: 7, }, nil } -func (p *process) Cancel() {} -func (p *process) Suspend() error { return nil } -func (p *process) Resume() error { return nil } +func (p *mockProcess) Cancel() {} +func (p *mockProcess) Suspend() error { return nil } +func (p *mockProcess) Resume() error { return nil } func TestConfigNoLimits(t *testing.T) { _, err := New(Config{ - PSUtil: newUtil(0), + PSUtil: newMockUtil(0), }) require.NoError(t, err) } @@ -137,7 +137,7 @@ func TestConfigWrongLimits(t *testing.T) { _, err := New(Config{ MaxCPU: 102, MaxMemory: 573, - PSUtil: newUtil(0), + PSUtil: newMockUtil(0), }) require.Error(t, err) @@ -146,7 +146,7 @@ func TestConfigWrongLimits(t *testing.T) { MaxMemory: 0, MaxGPU: 101, MaxGPUMemory: 103, - PSUtil: newUtil(0), + PSUtil: newMockUtil(0), }) require.NoError(t, err) @@ -155,7 +155,7 @@ func TestConfigWrongLimits(t *testing.T) { MaxMemory: 0, MaxGPU: 101, MaxGPUMemory: 103, - PSUtil: newUtil(1), + PSUtil: newMockUtil(1), }) require.Error(t, err) } @@ -164,7 +164,7 @@ func TestMemoryLimit(t *testing.T) { r, err := New(Config{ MaxCPU: 100, MaxMemory: 150. / 200. * 100, - PSUtil: newUtil(0), + PSUtil: newMockUtil(0), Logger: nil, }) require.NoError(t, err) @@ -209,7 +209,7 @@ func TestMemoryLimit(t *testing.T) { } func TestMemoryUnlimit(t *testing.T) { - util := newUtil(0) + util := newMockUtil(0) r, err := New(Config{ MaxCPU: 100, @@ -296,7 +296,7 @@ func TestCPULimit(t *testing.T) { r, err := New(Config{ MaxCPU: 50., MaxMemory: 100, - PSUtil: newUtil(0), + PSUtil: newMockUtil(0), Logger: nil, }) require.NoError(t, err) @@ -341,7 +341,7 @@ func TestCPULimit(t *testing.T) { } func TestCPUUnlimit(t *testing.T) { - util := newUtil(0) + util := newMockUtil(0) r, err := New(Config{ MaxCPU: 50., @@ -430,7 +430,7 @@ func TestGPULimitMemory(t *testing.T) { MaxMemory: 100, MaxGPU: 100, MaxGPUMemory: 20, - PSUtil: newUtil(2), + PSUtil: newMockUtil(2), Logger: nil, }) require.NoError(t, err) @@ -475,7 +475,7 @@ func TestGPULimitMemory(t *testing.T) { } func TestGPUUnlimitMemory(t *testing.T) { - util := newUtil(2) + util := newMockUtil(2) r, err := New(Config{ MaxCPU: 100, @@ -564,7 +564,7 @@ func TestGPULimitMemorySome(t *testing.T) { MaxMemory: 100, MaxGPU: 100, MaxGPUMemory: 14. / 24. * 100., - PSUtil: newUtil(4), + PSUtil: newMockUtil(4), Logger: nil, }) require.NoError(t, err) @@ -614,7 +614,7 @@ func TestGPULimitUsage(t *testing.T) { MaxMemory: 100, MaxGPU: 40, MaxGPUMemory: 100, - PSUtil: newUtil(3), + PSUtil: newMockUtil(3), Logger: nil, }) require.NoError(t, err) @@ -662,7 +662,7 @@ func TestGPULimitUsage(t *testing.T) { } func TestGPUUnlimitUsage(t *testing.T) { - util := newUtil(3) + util := newMockUtil(3) r, err := New(Config{ MaxCPU: 100, @@ -749,7 +749,7 @@ func TestGPUUnlimitUsage(t *testing.T) { func TestRequestCPU(t *testing.T) { r, err := New(Config{ MaxCPU: 70., - PSUtil: newUtil(0), + PSUtil: newMockUtil(0), }) require.NoError(t, err) @@ -766,7 +766,7 @@ func TestRequestCPU(t *testing.T) { func TestRequestMemory(t *testing.T) { r, err := New(Config{ MaxMemory: 170. / 200. * 100, - PSUtil: newUtil(0), + PSUtil: newMockUtil(0), }) require.NoError(t, err) @@ -784,7 +784,7 @@ func TestRequestNoGPU(t *testing.T) { r, err := New(Config{ MaxCPU: 100, MaxMemory: 100, - PSUtil: newUtil(0), + PSUtil: newMockUtil(0), }) require.NoError(t, err) @@ -796,7 +796,7 @@ func TestRequestInvalidGPURequest(t *testing.T) { r, err := New(Config{ MaxCPU: 100, MaxMemory: 100, - PSUtil: newUtil(1), + PSUtil: newMockUtil(1), }) require.NoError(t, err) @@ -813,7 +813,7 @@ func TestRequestGPULimitsOneGPU(t *testing.T) { MaxMemory: 100, MaxGPU: 50, MaxGPUMemory: 60, - PSUtil: newUtil(1), + PSUtil: newMockUtil(1), }) require.NoError(t, err) @@ -840,7 +840,7 @@ func TestRequestGPULimitsMoreGPU(t *testing.T) { MaxMemory: 100, MaxGPU: 60, MaxGPUMemory: 60, - PSUtil: newUtil(2), + PSUtil: newMockUtil(2), }) require.NoError(t, err) @@ -856,7 +856,7 @@ func TestHasLimits(t *testing.T) { r, err := New(Config{ MaxCPU: 70., MaxMemory: 170. / 200. * 100, - PSUtil: newUtil(0), + PSUtil: newMockUtil(0), Logger: nil, }) require.NoError(t, err) @@ -866,7 +866,7 @@ func TestHasLimits(t *testing.T) { r, err = New(Config{ MaxCPU: 100, MaxMemory: 100, - PSUtil: newUtil(0), + PSUtil: newMockUtil(0), Logger: nil, }) require.NoError(t, err) @@ -876,7 +876,7 @@ func TestHasLimits(t *testing.T) { r, err = New(Config{ MaxCPU: 0, MaxMemory: 0, - PSUtil: newUtil(0), + PSUtil: newMockUtil(0), Logger: nil, }) require.NoError(t, err) @@ -887,7 +887,7 @@ func TestHasLimits(t *testing.T) { MaxCPU: 0, MaxMemory: 0, MaxGPU: 10, - PSUtil: newUtil(1), + PSUtil: newMockUtil(1), Logger: nil, }) require.NoError(t, err) @@ -898,7 +898,7 @@ func TestHasLimits(t *testing.T) { MaxCPU: 0, MaxMemory: 0, MaxGPU: 10, - PSUtil: newUtil(0), + PSUtil: newMockUtil(0), Logger: nil, }) require.NoError(t, err) @@ -912,7 +912,7 @@ func TestInfo(t *testing.T) { MaxMemory: 90, MaxGPU: 11, MaxGPUMemory: 50, - PSUtil: newUtil(2), + PSUtil: newMockUtil(2), }) require.NoError(t, err) diff --git a/restream/core_test.go b/restream/core_test.go index d1b644ee..1924849b 100644 --- a/restream/core_test.go +++ b/restream/core_test.go @@ -38,13 +38,20 @@ func getDummyRestreamer(portrange net.Portranger, validatorIn, validatorOut ffmp return nil, err } + resources, err := resources.New(resources.Config{ + PSUtil: psutil, + }) + if err != nil { + return nil, err + } + ffmpeg, err := ffmpeg.New(ffmpeg.Config{ Binary: binary, LogHistoryLength: 3, Portrange: portrange, ValidatorInput: validatorIn, ValidatorOutput: validatorOut, - PSUtil: psutil, + Resource: resources, }) if err != nil { return nil, err @@ -88,13 +95,6 @@ func getDummyRestreamer(portrange net.Portranger, validatorIn, validatorOut ffmp return nil, err } - resources, err := resources.New(resources.Config{ - PSUtil: psutil, - }) - if err != nil { - return nil, err - } - rs, err := New(Config{ FFmpeg: ffmpeg, Replace: replacer, From de9a30a108a5209f55e99239137753f33b7db5d5 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Tue, 29 Oct 2024 14:55:55 +0100 Subject: [PATCH 53/64] Add internal mock modules --- ffmpeg/skills/skills_test.go | 4 +- http/handler/api/about_test.go | 3 +- http/handler/api/process_test.go | 3 +- http/handler/api/widget_test.go | 5 +- http/mock/mock.go | 61 ------- internal/mock/psutil/psutil.go | 122 +++++++++++++ internal/mock/resources/resources.go | 14 ++ internal/mock/restream/restream.go | 99 +++++++++++ internal/testhelper/testhelper.go | 6 +- process/process_test.go | 8 +- resources/psutil/gpu/nvidia/nvidia_test.go | 6 +- resources/resources_test.go | 197 +++++---------------- restream/core_test.go | 17 +- 13 files changed, 299 insertions(+), 246 deletions(-) create mode 100644 internal/mock/psutil/psutil.go create mode 100644 internal/mock/resources/resources.go create mode 100644 internal/mock/restream/restream.go diff --git a/ffmpeg/skills/skills_test.go b/ffmpeg/skills/skills_test.go index 0ee8b74e..f8f67bfd 100644 --- a/ffmpeg/skills/skills_test.go +++ b/ffmpeg/skills/skills_test.go @@ -35,7 +35,7 @@ func TestNewInvalidBinary(t *testing.T) { } func TestNew(t *testing.T) { - binary, err := testhelper.BuildBinary("ffmpeg", "../../internal/testhelper") + binary, err := testhelper.BuildBinary("ffmpeg") require.NoError(t, err, "Failed to build helper program") skills, err := New(binary) @@ -326,7 +326,7 @@ func TestEqualEmptySkills(t *testing.T) { } func TestEqualSkills(t *testing.T) { - binary, err := testhelper.BuildBinary("ffmpeg", "../../internal/testhelper") + binary, err := testhelper.BuildBinary("ffmpeg") require.NoError(t, err, "Failed to build helper program") s1, err := New(binary) diff --git a/http/handler/api/about_test.go b/http/handler/api/about_test.go index 192b5e98..2ac0877a 100644 --- a/http/handler/api/about_test.go +++ b/http/handler/api/about_test.go @@ -6,6 +6,7 @@ import ( "github.com/datarhei/core/v16/http/api" "github.com/datarhei/core/v16/http/mock" + "github.com/datarhei/core/v16/internal/mock/restream" "github.com/stretchr/testify/require" "github.com/labstack/echo/v4" @@ -14,7 +15,7 @@ import ( func getDummyAboutRouter() (*echo.Echo, error) { router := mock.DummyEcho() - rs, err := mock.DummyRestreamer("../../mock") + rs, err := restream.New(nil, nil, nil, nil) if err != nil { return nil, err } diff --git a/http/handler/api/process_test.go b/http/handler/api/process_test.go index bf076af3..ff607405 100644 --- a/http/handler/api/process_test.go +++ b/http/handler/api/process_test.go @@ -14,6 +14,7 @@ import ( "github.com/datarhei/core/v16/iam" "github.com/datarhei/core/v16/iam/identity" "github.com/datarhei/core/v16/iam/policy" + "github.com/datarhei/core/v16/internal/mock/restream" "github.com/datarhei/core/v16/io/fs" "github.com/labstack/echo/v4" @@ -27,7 +28,7 @@ type Response struct { } func getDummyRestreamHandler() (*ProcessHandler, error) { - rs, err := mock.DummyRestreamer("../../mock") + rs, err := restream.New(nil, nil, nil, nil) if err != nil { return nil, err } diff --git a/http/handler/api/widget_test.go b/http/handler/api/widget_test.go index 46ab913d..f3a42b12 100644 --- a/http/handler/api/widget_test.go +++ b/http/handler/api/widget_test.go @@ -8,10 +8,11 @@ import ( "github.com/datarhei/core/v16/encoding/json" "github.com/datarhei/core/v16/http/api" "github.com/datarhei/core/v16/http/mock" + mockrs "github.com/datarhei/core/v16/internal/mock/restream" "github.com/datarhei/core/v16/restream" - "github.com/stretchr/testify/require" "github.com/labstack/echo/v4" + "github.com/stretchr/testify/require" ) func getDummyWidgetHandler(rs restream.Restreamer) (*WidgetHandler, error) { @@ -37,7 +38,7 @@ func getDummyWidgetRouter(rs restream.Restreamer) (*echo.Echo, error) { } func TestWidget(t *testing.T) { - rs, err := mock.DummyRestreamer("../../mock") + rs, err := mockrs.New(nil, nil, nil, nil) require.NoError(t, err) router, err := getDummyWidgetRouter(rs) diff --git a/http/mock/mock.go b/http/mock/mock.go index f6b516fc..39605431 100644 --- a/http/mock/mock.go +++ b/http/mock/mock.go @@ -2,25 +2,16 @@ package mock import ( "bytes" - "fmt" "io" "net/http" "net/http/httptest" "os" - "path/filepath" "strings" "github.com/datarhei/core/v16/encoding/json" - "github.com/datarhei/core/v16/ffmpeg" "github.com/datarhei/core/v16/http/api" "github.com/datarhei/core/v16/http/errorhandler" "github.com/datarhei/core/v16/http/validator" - "github.com/datarhei/core/v16/internal/testhelper" - "github.com/datarhei/core/v16/io/fs" - "github.com/datarhei/core/v16/resources" - "github.com/datarhei/core/v16/resources/psutil" - "github.com/datarhei/core/v16/restream" - jsonstore "github.com/datarhei/core/v16/restream/store/json" "github.com/invopop/jsonschema" "github.com/labstack/echo/v4" @@ -28,58 +19,6 @@ import ( "github.com/xeipuuv/gojsonschema" ) -func DummyRestreamer(pathPrefix string) (restream.Restreamer, error) { - binary, err := testhelper.BuildBinary("ffmpeg", filepath.Join(pathPrefix, "../../internal/testhelper")) - if err != nil { - return nil, fmt.Errorf("failed to build helper program: %w", err) - } - - memfs, err := fs.NewMemFilesystem(fs.MemConfig{}) - if err != nil { - return nil, fmt.Errorf("failed to create memory filesystem: %w", err) - } - - store, err := jsonstore.New(jsonstore.Config{ - Filesystem: memfs, - }) - if err != nil { - return nil, err - } - - psutil, err := psutil.New("", nil) - if err != nil { - return nil, err - } - - resources, err := resources.New(resources.Config{ - PSUtil: psutil, - }) - if err != nil { - return nil, err - } - - ffmpeg, err := ffmpeg.New(ffmpeg.Config{ - Binary: binary, - MaxLogLines: 100, - LogHistoryLength: 3, - Resource: resources, - }) - if err != nil { - return nil, err - } - - rs, err := restream.New(restream.Config{ - Store: store, - FFmpeg: ffmpeg, - Filesystems: []fs.Filesystem{memfs}, - }) - if err != nil { - return nil, err - } - - return rs, nil -} - func DummyEcho() *echo.Echo { router := echo.New() router.HideBanner = true diff --git a/internal/mock/psutil/psutil.go b/internal/mock/psutil/psutil.go new file mode 100644 index 00000000..942bf405 --- /dev/null +++ b/internal/mock/psutil/psutil.go @@ -0,0 +1,122 @@ +package psutil + +import ( + "sync" + + "github.com/datarhei/core/v16/resources/psutil" +) + +type MockPSUtil struct { + Lock sync.Mutex + + CPUInfo psutil.CPUInfo + MemInfo psutil.MemoryInfo + GPUInfo []psutil.GPUInfo +} + +func New(ngpu int) *MockPSUtil { + u := &MockPSUtil{ + CPUInfo: psutil.CPUInfo{ + System: 10, + User: 50, + Idle: 35, + Other: 5, + }, + MemInfo: psutil.MemoryInfo{ + Total: 200, + Available: 40, + Used: 160, + }, + } + + for i := 0; i < ngpu; i++ { + u.GPUInfo = append(u.GPUInfo, psutil.GPUInfo{ + Index: i, + Name: "L4", + MemoryTotal: 24 * 1024 * 1024 * 1024, + MemoryUsed: uint64(12+i) * 1024 * 1024 * 1024, + Usage: 50 - float64((i+1)*5), + Encoder: 50 - float64((i+1)*10), + Decoder: 50 - float64((i+1)*3), + }) + } + + return u +} + +func (u *MockPSUtil) Start() {} +func (u *MockPSUtil) Cancel() {} + +func (u *MockPSUtil) CPUCounts() (float64, error) { + return 2, nil +} + +func (u *MockPSUtil) CPU() (*psutil.CPUInfo, error) { + u.Lock.Lock() + defer u.Lock.Unlock() + + cpu := u.CPUInfo + + return &cpu, nil +} + +func (u *MockPSUtil) Disk(path string) (*psutil.DiskInfo, error) { + return &psutil.DiskInfo{}, nil +} + +func (u *MockPSUtil) Memory() (*psutil.MemoryInfo, error) { + u.Lock.Lock() + defer u.Lock.Unlock() + + mem := u.MemInfo + + return &mem, nil +} + +func (u *MockPSUtil) Network() ([]psutil.NetworkInfo, error) { + return nil, nil +} + +func (u *MockPSUtil) GPU() ([]psutil.GPUInfo, error) { + u.Lock.Lock() + defer u.Lock.Unlock() + + gpu := []psutil.GPUInfo{} + + gpu = append(gpu, u.GPUInfo...) + + return gpu, nil +} + +func (u *MockPSUtil) Process(pid int32) (psutil.Process, error) { + return &mockPSUtilProcess{}, nil +} + +type mockPSUtilProcess struct{} + +func (p *mockPSUtilProcess) CPU() (*psutil.CPUInfo, error) { + s := &psutil.CPUInfo{ + System: 1, + User: 2, + Idle: 0, + Other: 3, + } + + return s, nil +} + +func (p *mockPSUtilProcess) Memory() (uint64, error) { return 42, nil } +func (p *mockPSUtilProcess) GPU() (*psutil.GPUInfo, error) { + return &psutil.GPUInfo{ + Index: 0, + Name: "L4", + MemoryTotal: 128, + MemoryUsed: 42, + Usage: 5, + Encoder: 9, + Decoder: 7, + }, nil +} +func (p *mockPSUtilProcess) Cancel() {} +func (p *mockPSUtilProcess) Suspend() error { return nil } +func (p *mockPSUtilProcess) Resume() error { return nil } diff --git a/internal/mock/resources/resources.go b/internal/mock/resources/resources.go new file mode 100644 index 00000000..84f050f5 --- /dev/null +++ b/internal/mock/resources/resources.go @@ -0,0 +1,14 @@ +package resources + +import ( + "github.com/datarhei/core/v16/internal/mock/psutil" + "github.com/datarhei/core/v16/resources" +) + +func New() resources.Resources { + res, _ := resources.New(resources.Config{ + PSUtil: psutil.New(1), + }) + + return res +} diff --git a/internal/mock/restream/restream.go b/internal/mock/restream/restream.go new file mode 100644 index 00000000..af9a9876 --- /dev/null +++ b/internal/mock/restream/restream.go @@ -0,0 +1,99 @@ +package restream + +import ( + "fmt" + + "github.com/datarhei/core/v16/ffmpeg" + "github.com/datarhei/core/v16/iam" + iamidentity "github.com/datarhei/core/v16/iam/identity" + "github.com/datarhei/core/v16/iam/policy" + "github.com/datarhei/core/v16/internal/mock/resources" + "github.com/datarhei/core/v16/internal/testhelper" + "github.com/datarhei/core/v16/io/fs" + "github.com/datarhei/core/v16/net" + "github.com/datarhei/core/v16/restream" + "github.com/datarhei/core/v16/restream/replace" + "github.com/datarhei/core/v16/restream/rewrite" + jsonstore "github.com/datarhei/core/v16/restream/store/json" +) + +func New(portrange net.Portranger, validatorIn, validatorOut ffmpeg.Validator, replacer replace.Replacer) (restream.Restreamer, error) { + binary, err := testhelper.BuildBinary("ffmpeg") + if err != nil { + return nil, fmt.Errorf("failed to build helper program: %w", err) + } + + resources := resources.New() + + ffmpeg, err := ffmpeg.New(ffmpeg.Config{ + Binary: binary, + LogHistoryLength: 3, + MaxLogLines: 100, + Portrange: portrange, + ValidatorInput: validatorIn, + ValidatorOutput: validatorOut, + Resource: resources, + }) + if err != nil { + return nil, err + } + + memfs, err := fs.NewMemFilesystem(fs.MemConfig{}) + if err != nil { + return nil, err + } + + store, err := jsonstore.New(jsonstore.Config{ + Filesystem: memfs, + }) + if err != nil { + return nil, err + } + + policyAdapter, err := policy.NewJSONAdapter(memfs, "./policy.json", nil) + if err != nil { + return nil, err + } + + identityAdapter, err := iamidentity.NewJSONAdapter(memfs, "./users.json", nil) + if err != nil { + return nil, err + } + + iam, err := iam.New(iam.Config{ + PolicyAdapter: policyAdapter, + IdentityAdapter: identityAdapter, + Superuser: iamidentity.User{ + Name: "foobar", + }, + JWTRealm: "", + JWTSecret: "", + Logger: nil, + }) + if err != nil { + return nil, err + } + + iam.AddPolicy("$anon", "$none", []string{"process"}, "*", []string{"CREATE", "GET", "DELETE", "UPDATE", "COMMAND", "PROBE", "METADATA", "PLAYOUT"}) + + rewriter, err := rewrite.New(rewrite.Config{ + IAM: iam, + }) + if err != nil { + return nil, err + } + + rs, err := restream.New(restream.Config{ + Store: store, + FFmpeg: ffmpeg, + Replace: replacer, + Filesystems: []fs.Filesystem{memfs}, + Rewrite: rewriter, + Resources: resources, + }) + if err != nil { + return nil, err + } + + return rs, nil +} diff --git a/internal/testhelper/testhelper.go b/internal/testhelper/testhelper.go index 0a6ac32d..70004ab8 100644 --- a/internal/testhelper/testhelper.go +++ b/internal/testhelper/testhelper.go @@ -4,10 +4,12 @@ import ( "fmt" "os/exec" "path/filepath" + "runtime" ) -func BuildBinary(name, pathprefix string) (string, error) { - dir := filepath.Join(pathprefix, name) +func BuildBinary(name string) (string, error) { + _, filename, _, _ := runtime.Caller(0) + dir := filepath.Join(filepath.Dir(filename), name) aout := filepath.Join(dir, name) err := exec.Command("go", "build", "-o", aout, dir).Run() diff --git a/process/process_test.go b/process/process_test.go index ce4cc01b..7ce6542d 100644 --- a/process/process_test.go +++ b/process/process_test.go @@ -235,7 +235,7 @@ func TestProcessFailed(t *testing.T) { } func TestFFmpegWaitStop(t *testing.T) { - binary, err := testhelper.BuildBinary("sigintwait", "../internal/testhelper") + binary, err := testhelper.BuildBinary("sigintwait") require.NoError(t, err, "Failed to build helper program") p, _ := New(Config{ @@ -266,7 +266,7 @@ func TestFFmpegWaitStop(t *testing.T) { } func TestFFmpegKill(t *testing.T) { - binary, err := testhelper.BuildBinary("sigint", "../internal/testhelper") + binary, err := testhelper.BuildBinary("sigint") require.NoError(t, err, "Failed to build helper program") p, _ := New(Config{ @@ -292,7 +292,7 @@ func TestFFmpegKill(t *testing.T) { } func TestProcessForceKill(t *testing.T) { - binary, err := testhelper.BuildBinary("ignoresigint", "../internal/testhelper") + binary, err := testhelper.BuildBinary("ignoresigint") require.NoError(t, err, "Failed to build helper program") p, _ := New(Config{ @@ -326,7 +326,7 @@ func TestProcessForceKill(t *testing.T) { } func TestProcessDuration(t *testing.T) { - binary, err := testhelper.BuildBinary("sigint", "../internal/testhelper") + binary, err := testhelper.BuildBinary("sigint") require.NoError(t, err, "Failed to build helper program") p, err := New(Config{ diff --git a/resources/psutil/gpu/nvidia/nvidia_test.go b/resources/psutil/gpu/nvidia/nvidia_test.go index ddc48722..37f18690 100644 --- a/resources/psutil/gpu/nvidia/nvidia_test.go +++ b/resources/psutil/gpu/nvidia/nvidia_test.go @@ -280,7 +280,7 @@ func TestWriterProcess(t *testing.T) { } func TestNvidiaGPUCount(t *testing.T) { - binary, err := testhelper.BuildBinary("nvidia-smi", "../../../../internal/testhelper") + binary, err := testhelper.BuildBinary("nvidia-smi") require.NoError(t, err, "Failed to build helper program") nv := New(binary) @@ -299,7 +299,7 @@ func TestNvidiaGPUCount(t *testing.T) { } func TestNvidiaGPUStats(t *testing.T) { - binary, err := testhelper.BuildBinary("nvidia-smi", "../../../../internal/testhelper") + binary, err := testhelper.BuildBinary("nvidia-smi") require.NoError(t, err, "Failed to build helper program") nv := New(binary) @@ -400,7 +400,7 @@ func TestNvidiaGPUStats(t *testing.T) { } func TestNvidiaGPUProcess(t *testing.T) { - binary, err := testhelper.BuildBinary("nvidia-smi", "../../../../internal/testhelper") + binary, err := testhelper.BuildBinary("nvidia-smi") require.NoError(t, err, "Failed to build helper program") nv := New(binary) diff --git a/resources/resources_test.go b/resources/resources_test.go index f590b881..77bb7116 100644 --- a/resources/resources_test.go +++ b/resources/resources_test.go @@ -6,129 +6,14 @@ import ( "testing" "time" - "github.com/datarhei/core/v16/resources/psutil" + "github.com/datarhei/core/v16/internal/mock/psutil" "github.com/stretchr/testify/require" ) -type mockUtil struct { - lock sync.Mutex - - cpu psutil.CPUInfo - mem psutil.MemoryInfo - gpu []psutil.GPUInfo -} - -func newMockUtil(ngpu int) *mockUtil { - u := &mockUtil{ - cpu: psutil.CPUInfo{ - System: 10, - User: 50, - Idle: 35, - Other: 5, - }, - mem: psutil.MemoryInfo{ - Total: 200, - Available: 40, - Used: 160, - }, - } - - for i := 0; i < ngpu; i++ { - u.gpu = append(u.gpu, psutil.GPUInfo{ - Index: i, - Name: "L4", - MemoryTotal: 24 * 1024 * 1024 * 1024, - MemoryUsed: uint64(12+i) * 1024 * 1024 * 1024, - Usage: 50 - float64((i+1)*5), - Encoder: 50 - float64((i+1)*10), - Decoder: 50 - float64((i+1)*3), - }) - } - - return u -} - -func (u *mockUtil) Start() {} -func (u *mockUtil) Cancel() {} - -func (u *mockUtil) CPUCounts() (float64, error) { - return 2, nil -} - -func (u *mockUtil) CPU() (*psutil.CPUInfo, error) { - u.lock.Lock() - defer u.lock.Unlock() - - cpu := u.cpu - - return &cpu, nil -} - -func (u *mockUtil) Disk(path string) (*psutil.DiskInfo, error) { - return &psutil.DiskInfo{}, nil -} - -func (u *mockUtil) Memory() (*psutil.MemoryInfo, error) { - u.lock.Lock() - defer u.lock.Unlock() - - mem := u.mem - - return &mem, nil -} - -func (u *mockUtil) Network() ([]psutil.NetworkInfo, error) { - return nil, nil -} - -func (u *mockUtil) GPU() ([]psutil.GPUInfo, error) { - u.lock.Lock() - defer u.lock.Unlock() - - gpu := []psutil.GPUInfo{} - - gpu = append(gpu, u.gpu...) - - return gpu, nil -} - -func (u *mockUtil) Process(pid int32) (psutil.Process, error) { - return &mockProcess{}, nil -} - -type mockProcess struct{} - -func (p *mockProcess) CPU() (*psutil.CPUInfo, error) { - s := &psutil.CPUInfo{ - System: 1, - User: 2, - Idle: 0, - Other: 3, - } - - return s, nil -} - -func (p *mockProcess) Memory() (uint64, error) { return 42, nil } -func (p *mockProcess) GPU() (*psutil.GPUInfo, error) { - return &psutil.GPUInfo{ - Index: 0, - Name: "L4", - MemoryTotal: 128, - MemoryUsed: 42, - Usage: 5, - Encoder: 9, - Decoder: 7, - }, nil -} -func (p *mockProcess) Cancel() {} -func (p *mockProcess) Suspend() error { return nil } -func (p *mockProcess) Resume() error { return nil } - func TestConfigNoLimits(t *testing.T) { _, err := New(Config{ - PSUtil: newMockUtil(0), + PSUtil: psutil.New(0), }) require.NoError(t, err) } @@ -137,7 +22,7 @@ func TestConfigWrongLimits(t *testing.T) { _, err := New(Config{ MaxCPU: 102, MaxMemory: 573, - PSUtil: newMockUtil(0), + PSUtil: psutil.New(0), }) require.Error(t, err) @@ -146,7 +31,7 @@ func TestConfigWrongLimits(t *testing.T) { MaxMemory: 0, MaxGPU: 101, MaxGPUMemory: 103, - PSUtil: newMockUtil(0), + PSUtil: psutil.New(0), }) require.NoError(t, err) @@ -155,7 +40,7 @@ func TestConfigWrongLimits(t *testing.T) { MaxMemory: 0, MaxGPU: 101, MaxGPUMemory: 103, - PSUtil: newMockUtil(1), + PSUtil: psutil.New(1), }) require.Error(t, err) } @@ -164,7 +49,7 @@ func TestMemoryLimit(t *testing.T) { r, err := New(Config{ MaxCPU: 100, MaxMemory: 150. / 200. * 100, - PSUtil: newMockUtil(0), + PSUtil: psutil.New(0), Logger: nil, }) require.NoError(t, err) @@ -209,7 +94,7 @@ func TestMemoryLimit(t *testing.T) { } func TestMemoryUnlimit(t *testing.T) { - util := newMockUtil(0) + util := psutil.New(0) r, err := New(Config{ MaxCPU: 100, @@ -255,9 +140,9 @@ func TestMemoryUnlimit(t *testing.T) { _, limit, _ = r.ShouldLimit() require.True(t, limit) - util.lock.Lock() - util.mem.Used = 140 - util.lock.Unlock() + util.Lock.Lock() + util.MemInfo.Used = 140 + util.Lock.Unlock() wg.Add(1) @@ -296,7 +181,7 @@ func TestCPULimit(t *testing.T) { r, err := New(Config{ MaxCPU: 50., MaxMemory: 100, - PSUtil: newMockUtil(0), + PSUtil: psutil.New(0), Logger: nil, }) require.NoError(t, err) @@ -341,7 +226,7 @@ func TestCPULimit(t *testing.T) { } func TestCPUUnlimit(t *testing.T) { - util := newMockUtil(0) + util := psutil.New(0) r, err := New(Config{ MaxCPU: 50., @@ -387,9 +272,9 @@ func TestCPUUnlimit(t *testing.T) { limit, _, _ = r.ShouldLimit() require.True(t, limit) - util.lock.Lock() - util.cpu.User = 20 - util.lock.Unlock() + util.Lock.Lock() + util.CPUInfo.User = 20 + util.Lock.Unlock() wg.Add(1) @@ -430,7 +315,7 @@ func TestGPULimitMemory(t *testing.T) { MaxMemory: 100, MaxGPU: 100, MaxGPUMemory: 20, - PSUtil: newMockUtil(2), + PSUtil: psutil.New(2), Logger: nil, }) require.NoError(t, err) @@ -475,7 +360,7 @@ func TestGPULimitMemory(t *testing.T) { } func TestGPUUnlimitMemory(t *testing.T) { - util := newMockUtil(2) + util := psutil.New(2) r, err := New(Config{ MaxCPU: 100, @@ -520,10 +405,10 @@ func TestGPUUnlimitMemory(t *testing.T) { require.Contains(t, limit, true) - util.lock.Lock() - util.gpu[0].MemoryUsed = 10 - util.gpu[1].MemoryUsed = 10 - util.lock.Unlock() + util.Lock.Lock() + util.GPUInfo[0].MemoryUsed = 10 + util.GPUInfo[1].MemoryUsed = 10 + util.Lock.Unlock() wg.Add(1) @@ -564,7 +449,7 @@ func TestGPULimitMemorySome(t *testing.T) { MaxMemory: 100, MaxGPU: 100, MaxGPUMemory: 14. / 24. * 100., - PSUtil: newMockUtil(4), + PSUtil: psutil.New(4), Logger: nil, }) require.NoError(t, err) @@ -614,7 +499,7 @@ func TestGPULimitUsage(t *testing.T) { MaxMemory: 100, MaxGPU: 40, MaxGPUMemory: 100, - PSUtil: newMockUtil(3), + PSUtil: psutil.New(3), Logger: nil, }) require.NoError(t, err) @@ -662,7 +547,7 @@ func TestGPULimitUsage(t *testing.T) { } func TestGPUUnlimitUsage(t *testing.T) { - util := newMockUtil(3) + util := psutil.New(3) r, err := New(Config{ MaxCPU: 100, @@ -707,11 +592,11 @@ func TestGPUUnlimitUsage(t *testing.T) { require.Equal(t, []bool{true, false, false}, limit) - util.lock.Lock() - util.gpu[0].Usage = 30 - util.gpu[0].Encoder = 30 - util.gpu[0].Decoder = 30 - util.lock.Unlock() + util.Lock.Lock() + util.GPUInfo[0].Usage = 30 + util.GPUInfo[0].Encoder = 30 + util.GPUInfo[0].Decoder = 30 + util.Lock.Unlock() wg.Add(1) @@ -749,7 +634,7 @@ func TestGPUUnlimitUsage(t *testing.T) { func TestRequestCPU(t *testing.T) { r, err := New(Config{ MaxCPU: 70., - PSUtil: newMockUtil(0), + PSUtil: psutil.New(0), }) require.NoError(t, err) @@ -766,7 +651,7 @@ func TestRequestCPU(t *testing.T) { func TestRequestMemory(t *testing.T) { r, err := New(Config{ MaxMemory: 170. / 200. * 100, - PSUtil: newMockUtil(0), + PSUtil: psutil.New(0), }) require.NoError(t, err) @@ -784,7 +669,7 @@ func TestRequestNoGPU(t *testing.T) { r, err := New(Config{ MaxCPU: 100, MaxMemory: 100, - PSUtil: newMockUtil(0), + PSUtil: psutil.New(0), }) require.NoError(t, err) @@ -796,7 +681,7 @@ func TestRequestInvalidGPURequest(t *testing.T) { r, err := New(Config{ MaxCPU: 100, MaxMemory: 100, - PSUtil: newMockUtil(1), + PSUtil: psutil.New(1), }) require.NoError(t, err) @@ -813,7 +698,7 @@ func TestRequestGPULimitsOneGPU(t *testing.T) { MaxMemory: 100, MaxGPU: 50, MaxGPUMemory: 60, - PSUtil: newMockUtil(1), + PSUtil: psutil.New(1), }) require.NoError(t, err) @@ -840,7 +725,7 @@ func TestRequestGPULimitsMoreGPU(t *testing.T) { MaxMemory: 100, MaxGPU: 60, MaxGPUMemory: 60, - PSUtil: newMockUtil(2), + PSUtil: psutil.New(2), }) require.NoError(t, err) @@ -856,7 +741,7 @@ func TestHasLimits(t *testing.T) { r, err := New(Config{ MaxCPU: 70., MaxMemory: 170. / 200. * 100, - PSUtil: newMockUtil(0), + PSUtil: psutil.New(0), Logger: nil, }) require.NoError(t, err) @@ -866,7 +751,7 @@ func TestHasLimits(t *testing.T) { r, err = New(Config{ MaxCPU: 100, MaxMemory: 100, - PSUtil: newMockUtil(0), + PSUtil: psutil.New(0), Logger: nil, }) require.NoError(t, err) @@ -876,7 +761,7 @@ func TestHasLimits(t *testing.T) { r, err = New(Config{ MaxCPU: 0, MaxMemory: 0, - PSUtil: newMockUtil(0), + PSUtil: psutil.New(0), Logger: nil, }) require.NoError(t, err) @@ -887,7 +772,7 @@ func TestHasLimits(t *testing.T) { MaxCPU: 0, MaxMemory: 0, MaxGPU: 10, - PSUtil: newMockUtil(1), + PSUtil: psutil.New(1), Logger: nil, }) require.NoError(t, err) @@ -898,7 +783,7 @@ func TestHasLimits(t *testing.T) { MaxCPU: 0, MaxMemory: 0, MaxGPU: 10, - PSUtil: newMockUtil(0), + PSUtil: psutil.New(0), Logger: nil, }) require.NoError(t, err) @@ -912,7 +797,7 @@ func TestInfo(t *testing.T) { MaxMemory: 90, MaxGPU: 11, MaxGPUMemory: 50, - PSUtil: newMockUtil(2), + PSUtil: psutil.New(2), }) require.NoError(t, err) diff --git a/restream/core_test.go b/restream/core_test.go index 1924849b..eb05d878 100644 --- a/restream/core_test.go +++ b/restream/core_test.go @@ -13,11 +13,10 @@ import ( "github.com/datarhei/core/v16/iam" iamidentity "github.com/datarhei/core/v16/iam/identity" "github.com/datarhei/core/v16/iam/policy" + "github.com/datarhei/core/v16/internal/mock/resources" "github.com/datarhei/core/v16/internal/testhelper" "github.com/datarhei/core/v16/io/fs" "github.com/datarhei/core/v16/net" - "github.com/datarhei/core/v16/resources" - "github.com/datarhei/core/v16/resources/psutil" "github.com/datarhei/core/v16/restream/app" rfs "github.com/datarhei/core/v16/restream/fs" "github.com/datarhei/core/v16/restream/replace" @@ -28,22 +27,12 @@ import ( ) func getDummyRestreamer(portrange net.Portranger, validatorIn, validatorOut ffmpeg.Validator, replacer replace.Replacer) (Restreamer, error) { - binary, err := testhelper.BuildBinary("ffmpeg", "../internal/testhelper") + binary, err := testhelper.BuildBinary("ffmpeg") if err != nil { return nil, fmt.Errorf("failed to build helper program: %w", err) } - psutil, err := psutil.New("", nil) - if err != nil { - return nil, err - } - - resources, err := resources.New(resources.Config{ - PSUtil: psutil, - }) - if err != nil { - return nil, err - } + resources := resources.New() ffmpeg, err := ffmpeg.New(ffmpeg.Config{ Binary: binary, From 2393dbc4c0c159c95abe192316b371382967af57 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Tue, 29 Oct 2024 16:43:47 +0100 Subject: [PATCH 54/64] Add replacer tests, fix command in state with dynamic placeholders --- internal/mock/psutil/psutil.go | 2 +- process/limiter.go | 6 +- process/process.go | 21 ++++-- restream/core_test.go | 133 ++++++++++++++++++++++++++++++++- 4 files changed, 146 insertions(+), 16 deletions(-) diff --git a/internal/mock/psutil/psutil.go b/internal/mock/psutil/psutil.go index 942bf405..c0f37048 100644 --- a/internal/mock/psutil/psutil.go +++ b/internal/mock/psutil/psutil.go @@ -114,7 +114,7 @@ func (p *mockPSUtilProcess) GPU() (*psutil.GPUInfo, error) { MemoryUsed: 42, Usage: 5, Encoder: 9, - Decoder: 7, + Decoder: 11, }, nil } func (p *mockPSUtilProcess) Cancel() {} diff --git a/process/limiter.go b/process/limiter.go index b30d4731..dfe90d60 100644 --- a/process/limiter.go +++ b/process/limiter.go @@ -467,9 +467,9 @@ func (l *limiter) collect() { l.lastUsage.Memory.Max = l.memory.Max() l.lastUsage.GPU.Index = gindex - l.lastUsage.GPU.Memory.Current = l.gpu.memory.Current() * 100 - l.lastUsage.GPU.Memory.Average = l.gpu.memory.Avg() * 100 - l.lastUsage.GPU.Memory.Max = l.gpu.memory.Max() * 100 + l.lastUsage.GPU.Memory.Current = l.gpu.memory.Current() + l.lastUsage.GPU.Memory.Average = l.gpu.memory.Avg() + l.lastUsage.GPU.Memory.Max = l.gpu.memory.Max() l.lastUsage.GPU.Usage.Current = l.gpu.usage.Current() * 100 l.lastUsage.GPU.Usage.Average = l.gpu.usage.Avg() * 100 diff --git a/process/process.go b/process/process.go index be0a9854..1f757d74 100644 --- a/process/process.go +++ b/process/process.go @@ -198,12 +198,13 @@ type States struct { // Process represents a ffmpeg process type process struct { - binary string - args []string - cmd *exec.Cmd - pid int32 - stdout io.ReadCloser - state struct { + binary string + args []string + cmdArgs []string + cmd *exec.Cmd + pid int32 + stdout io.ReadCloser + state struct { state stateType time time.Time states States @@ -268,6 +269,8 @@ func New(config Config) (Process, error) { p.args = make([]string, len(config.Args)) copy(p.args, config.Args) + p.cmdArgs = make([]string, len(config.Args)) + copy(p.cmdArgs, config.Args) // This is a loose check on purpose. If the e.g. the binary // doesn't exist or it is not executable, it will be @@ -560,8 +563,8 @@ func (p *process) Status() Status { }, } - s.CommandArgs = make([]string, len(p.args)) - copy(s.CommandArgs, p.args) + s.CommandArgs = make([]string, len(p.cmdArgs)) + copy(s.CommandArgs, p.cmdArgs) if order == "start" && !state.IsRunning() { p.reconn.lock.Lock() @@ -664,6 +667,8 @@ func (p *process) start() error { return err } + + p.cmdArgs = args } p.cmd = exec.Command(p.binary, args...) diff --git a/restream/core_test.go b/restream/core_test.go index eb05d878..3c8dfd02 100644 --- a/restream/core_test.go +++ b/restream/core_test.go @@ -522,6 +522,68 @@ func TestStartProcess(t *testing.T) { rs.StopProcess(tid) } +func TestProcessResources(t *testing.T) { + rs, err := getDummyRestreamer(nil, nil, nil, nil) + require.NoError(t, err) + + process := getDummyProcess() + tid := app.ProcessID{ID: process.ID} + + rs.AddProcess(process) + + err = rs.StartProcess(tid) + require.Equal(t, nil, err, "should be able to start existing process") + + time.Sleep(2 * time.Second) + + state, _ := rs.GetProcessState(tid) + require.Equal(t, "start", state.Order, "Process should be started") + + require.Equal(t, app.ProcessUsage{ + CPU: app.ProcessUsageCPU{ + NCPU: 2, + Current: 12, + Average: 12, + Max: 12, + Limit: 0, + IsThrottling: false, + }, + Memory: app.ProcessUsageMemory{ + Current: 42, + Average: 42, + Max: 42, + Limit: 0, + }, + GPU: app.ProcessUsageGPU{ + Index: 0, + Usage: app.ProcessUsageGPUUsage{ + Current: 5, + Average: 5, + Max: 5, + Limit: 0, + }, + Encoder: app.ProcessUsageGPUUsage{ + Current: 9, + Average: 9, + Max: 9, + Limit: 0, + }, + Decoder: app.ProcessUsageGPUUsage{ + Current: 11, + Average: 11, + Max: 11, + Limit: 0, + }, + Memory: app.ProcessUsageGPUMemory{ + Current: 42, + Average: 42, + Max: 42, + Limit: 0, + }, + }, + }, state.Resources) +} + func TestStopProcess(t *testing.T) { rs, err := getDummyRestreamer(nil, nil, nil, nil) require.NoError(t, err) @@ -1142,7 +1204,7 @@ func TestReplacer(t *testing.T) { Input: []app.ConfigIO{ { ID: "in_{processid}_{reference}", - Address: "input:{inputid}_process:{processid}_reference:{reference}_diskfs:{diskfs}/disk.txt_memfs:{memfs}/mem.txt_fsdisk:{fs:disk}/fsdisk.txt_fsmem:{fs:mem}/fsmem.txt_rtmp:{rtmp,name=pmtr}_srt:{srt,name=trs}_rtmp:{rtmp,name=$inputid}", + Address: "input:{inputid}_process:{processid}_reference:{reference}_diskfs:{diskfs}/disk.txt_memfs:{memfs}/mem.txt_fsdisk:{fs:disk}/fsdisk.txt_fsmem:{fs:mem}/fsmem.txt_rtmp:{rtmp,name=pmtr}_srt:{srt,name=trs}_rtmp:{rtmp,name=$inputid}_hwdevice:{hwdevice}", Options: []string{ "-f", "lavfi", @@ -1154,6 +1216,7 @@ func TestReplacer(t *testing.T) { "memfs:{memfs}/mem.txt", "fsdisk:{fs:disk}/fsdisk_{date,format=%Y%m%d_%H%M%S}.txt", "fsmem:{fs:mem}/$inputid.txt", + "hwdevice:{hwdevice}", }, }, }, @@ -1191,6 +1254,7 @@ func TestReplacer(t *testing.T) { "{memfs}/foobar_in_mem.txt", "{fs:disk}/foobar_on_disk_aswell.txt", "{fs:mem}/foobar_in_mem_aswell.txt", + "hwdevice:{hwdevice}", }, Reconnect: true, ReconnectDelay: 10, @@ -1207,7 +1271,7 @@ func TestReplacer(t *testing.T) { Input: []app.ConfigIO{ { ID: "in_314159265359_refref", - Address: "input:in_314159265359_refref_process:314159265359_reference:refref_diskfs:/mnt/diskfs/disk.txt_memfs:http://localhost/mnt/memfs/mem.txt_fsdisk:/mnt/diskfs/fsdisk.txt_fsmem:http://localhost/mnt/memfs/fsmem.txt_rtmp:rtmp://localhost/app/pmtr?token=foobar_srt:srt://localhost:6000?mode=caller&transtype=live&latency=20000&streamid=trs,mode:request,token:abcfoobar&passphrase=secret_rtmp:rtmp://localhost/app/in_314159265359_refref?token=foobar", + Address: "input:in_314159265359_refref_process:314159265359_reference:refref_diskfs:/mnt/diskfs/disk.txt_memfs:http://localhost/mnt/memfs/mem.txt_fsdisk:/mnt/diskfs/fsdisk.txt_fsmem:http://localhost/mnt/memfs/fsmem.txt_rtmp:rtmp://localhost/app/pmtr?token=foobar_srt:srt://localhost:6000?mode=caller&transtype=live&latency=20000&streamid=trs,mode:request,token:abcfoobar&passphrase=secret_rtmp:rtmp://localhost/app/in_314159265359_refref?token=foobar_hwdevice:{hwdevice}", Options: []string{ "-f", "lavfi", @@ -1219,6 +1283,7 @@ func TestReplacer(t *testing.T) { "memfs:http://localhost/mnt/memfs/mem.txt", "fsdisk:/mnt/diskfs/fsdisk_{date,format=%Y%m%d_%H%M%S}.txt", "fsmem:http://localhost/mnt/memfs/$inputid.txt", + "hwdevice:{hwdevice}", }, }, }, @@ -1256,6 +1321,7 @@ func TestReplacer(t *testing.T) { "{memfs}/foobar_in_mem.txt", "/mnt/diskfs/foobar_on_disk_aswell.txt", "http://localhost/mnt/memfs/foobar_in_mem_aswell.txt", + "hwdevice:{hwdevice}", }, Reconnect: true, ReconnectDelay: 10, @@ -1265,12 +1331,23 @@ func TestReplacer(t *testing.T) { require.Equal(t, wantprocess, process) - resolveDynamicPlaceholder(process, replacer, nil, nil) + resolveDynamicPlaceholder(process, replacer, map[string]string{ + "hwdevice": fmt.Sprintf("%d", -1), + }, nil) + wantprocess.Options = []string{ + "-loglevel", + "info", + "/mnt/diskfs/foobar_on_disk.txt", + "{memfs}/foobar_in_mem.txt", + "/mnt/diskfs/foobar_on_disk_aswell.txt", + "http://localhost/mnt/memfs/foobar_in_mem_aswell.txt", + "hwdevice:-1", + } wantprocess.Input = []app.ConfigIO{ { ID: "in_314159265359_refref", - Address: "input:in_314159265359_refref_process:314159265359_reference:refref_diskfs:/mnt/diskfs/disk.txt_memfs:http://localhost/mnt/memfs/mem.txt_fsdisk:/mnt/diskfs/fsdisk.txt_fsmem:http://localhost/mnt/memfs/fsmem.txt_rtmp:rtmp://localhost/app/pmtr?token=foobar_srt:srt://localhost:6000?mode=caller&transtype=live&latency=20000&streamid=trs,mode:request,token:abcfoobar&passphrase=secret_rtmp:rtmp://localhost/app/in_314159265359_refref?token=foobar", + Address: "input:in_314159265359_refref_process:314159265359_reference:refref_diskfs:/mnt/diskfs/disk.txt_memfs:http://localhost/mnt/memfs/mem.txt_fsdisk:/mnt/diskfs/fsdisk.txt_fsmem:http://localhost/mnt/memfs/fsmem.txt_rtmp:rtmp://localhost/app/pmtr?token=foobar_srt:srt://localhost:6000?mode=caller&transtype=live&latency=20000&streamid=trs,mode:request,token:abcfoobar&passphrase=secret_rtmp:rtmp://localhost/app/in_314159265359_refref?token=foobar_hwdevice:-1", Options: []string{ "-f", "lavfi", @@ -1282,6 +1359,7 @@ func TestReplacer(t *testing.T) { "memfs:http://localhost/mnt/memfs/mem.txt", "fsdisk:/mnt/diskfs/fsdisk_20191012_072050.txt", "fsmem:http://localhost/mnt/memfs/$inputid.txt", + "hwdevice:-1", }, }, } @@ -1365,6 +1443,7 @@ func TestProcessReplacer(t *testing.T) { "memfs:{memfs}/mem.txt", "fsdisk:{fs:disk}/fsdisk_{date,format=%Y%m%d_%H%M%S}.txt", "fsmem:{fs:mem}/$inputid.txt", + "hwdevice:{hwdevice}", }, }, }, @@ -1402,6 +1481,7 @@ func TestProcessReplacer(t *testing.T) { "{memfs}/foobar_in_mem.txt", "{fs:disk}/foobar_on_disk_aswell.txt", "{fs:mem}/foobar_in_mem_aswell.txt", + "hwdevice:{hwdevice}", }, Reconnect: true, ReconnectDelay: 10, @@ -1433,6 +1513,7 @@ func TestProcessReplacer(t *testing.T) { "memfs:http://localhost/mnt/memfs/mem.txt", "fsdisk:/mnt/diskfs/fsdisk_{date,format=%Y%m%d_%H%M%S}.txt", "fsmem:http://localhost/mnt/memfs/$inputid.txt", + "hwdevice:{hwdevice}", }, Cleanup: []app.ConfigIOCleanup{}, }, @@ -1471,6 +1552,7 @@ func TestProcessReplacer(t *testing.T) { "{memfs}/foobar_in_mem.txt", "/mnt/diskfs/foobar_on_disk_aswell.txt", "http://localhost/mnt/memfs/foobar_in_mem_aswell.txt", + "hwdevice:{hwdevice}", }, Reconnect: true, ReconnectDelay: 10, @@ -1483,6 +1565,49 @@ func TestProcessReplacer(t *testing.T) { require.True(t, ok) require.Equal(t, process, task.config) + + err = rsi.StartProcess(app.ProcessID{ID: "314159265359"}) + require.NoError(t, err) + + state, err := rsi.GetProcessState(app.ProcessID{ID: "314159265359"}) + require.NoError(t, err) + + require.Equal(t, []string{ + "-loglevel", + "info", + "/mnt/diskfs/foobar_on_disk.txt", + "{memfs}/foobar_in_mem.txt", + "/mnt/diskfs/foobar_on_disk_aswell.txt", + "http://localhost/mnt/memfs/foobar_in_mem_aswell.txt", + "hwdevice:-1", + "-f", + "lavfi", + "-re", + "input:in_314159265359_refref", + "process:314159265359", + "reference:refref", + "diskfs:/mnt/diskfs/disk.txt", + "memfs:http://localhost/mnt/memfs/mem.txt", + "fsdisk:/mnt/diskfs/fsdisk_20191012_072050.txt", + "fsmem:http://localhost/mnt/memfs/$inputid.txt", + "hwdevice:-1", + "-i", + "input:in_314159265359_refref_process:314159265359_reference:refref_diskfs:/mnt/diskfs/disk.txt_memfs:http://localhost/mnt/memfs/mem.txt_fsdisk:/mnt/diskfs/fsdisk.txt_fsmem:http://localhost/mnt/memfs/fsmem.txt_rtmp:rtmp://localhost/app/pmtr?token=foobar_srt:srt://localhost:6000?mode=caller&transtype=live&latency=20000&streamid=trs,mode:request,token:abcfoobar&passphrase=secret_rtmp:rtmp://localhost/app/in_314159265359_refref?token=foobar", + "-codec", + "copy", + "-f", + "null", + "output:out_314159265359_refref", + "process:314159265359", + "reference:refref", + "diskfs:/mnt/diskfs/disk.txt", + "memfs:http://localhost/mnt/memfs/mem.txt", + "fsdisk:/mnt/diskfs/fsdisk.txt", + "fsmem:http://localhost/mnt/memfs/$outputid.txt", + "output:out_314159265359_refref_process:314159265359_reference:refref_diskfs:/mnt/diskfs/disk.txt_memfs:http://localhost/mnt/memfs/mem.txt_fsdisk:/mnt/diskfs/fsdisk.txt_fsmem:http://localhost/mnt/memfs/fsmem.txt_rtmp:rtmp://localhost/app/314159265359?token=foobar_srt:srt://localhost:6000?mode=caller&transtype=live&latency=42&streamid=refref,mode:publish,token:abcfoobar&passphrase=secret_rtmp:rtmp://localhost/app/out_314159265359_refref?token=foobar", + }, state.Command) + + rsi.StopProcess(app.ProcessID{ID: "314159265359"}) } func TestProcessLogPattern(t *testing.T) { From dd8906e56f3611fc4afde0fb80db2c512e3dbeda Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Tue, 29 Oct 2024 17:04:07 +0100 Subject: [PATCH 55/64] Add test for dynamic placeholder via restream --- internal/mock/resources/resources.go | 12 +++ restream/core_test.go | 119 ++++++++++++++++++--------- 2 files changed, 90 insertions(+), 41 deletions(-) diff --git a/internal/mock/resources/resources.go b/internal/mock/resources/resources.go index 84f050f5..127e870d 100644 --- a/internal/mock/resources/resources.go +++ b/internal/mock/resources/resources.go @@ -12,3 +12,15 @@ func New() resources.Resources { return res } + +func NewWithLimits() resources.Resources { + res, _ := resources.New(resources.Config{ + MaxCPU: 100, + MaxMemory: 100, + MaxGPU: 100, + MaxGPUMemory: 100, + PSUtil: psutil.New(1), + }) + + return res +} diff --git a/restream/core_test.go b/restream/core_test.go index 3c8dfd02..306f4cf0 100644 --- a/restream/core_test.go +++ b/restream/core_test.go @@ -13,10 +13,11 @@ import ( "github.com/datarhei/core/v16/iam" iamidentity "github.com/datarhei/core/v16/iam/identity" "github.com/datarhei/core/v16/iam/policy" - "github.com/datarhei/core/v16/internal/mock/resources" + mock "github.com/datarhei/core/v16/internal/mock/resources" "github.com/datarhei/core/v16/internal/testhelper" "github.com/datarhei/core/v16/io/fs" "github.com/datarhei/core/v16/net" + "github.com/datarhei/core/v16/resources" "github.com/datarhei/core/v16/restream/app" rfs "github.com/datarhei/core/v16/restream/fs" "github.com/datarhei/core/v16/restream/replace" @@ -26,13 +27,18 @@ import ( "github.com/stretchr/testify/require" ) -func getDummyRestreamer(portrange net.Portranger, validatorIn, validatorOut ffmpeg.Validator, replacer replace.Replacer) (Restreamer, error) { +func getDummyRestreamer(portrange net.Portranger, validatorIn, validatorOut ffmpeg.Validator, replacer replace.Replacer, limits bool) (Restreamer, error) { binary, err := testhelper.BuildBinary("ffmpeg") if err != nil { return nil, fmt.Errorf("failed to build helper program: %w", err) } - resources := resources.New() + var res resources.Resources + if limits { + res = mock.NewWithLimits() + } else { + res = mock.New() + } ffmpeg, err := ffmpeg.New(ffmpeg.Config{ Binary: binary, @@ -40,7 +46,7 @@ func getDummyRestreamer(portrange net.Portranger, validatorIn, validatorOut ffmp Portrange: portrange, ValidatorInput: validatorIn, ValidatorOutput: validatorOut, - Resource: resources, + Resource: res, }) if err != nil { return nil, err @@ -89,7 +95,7 @@ func getDummyRestreamer(portrange net.Portranger, validatorIn, validatorOut ffmp Replace: replacer, Filesystems: []fs.Filesystem{memfs}, Rewrite: rewriter, - Resources: resources, + Resources: res, }) if err != nil { return nil, err @@ -136,7 +142,7 @@ func getDummyProcess() *app.Config { } func TestAddProcess(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -158,7 +164,7 @@ func TestAddProcess(t *testing.T) { } func TestAutostartProcess(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -175,7 +181,7 @@ func TestAutostartProcess(t *testing.T) { } func TestAddInvalidProcess(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) // Invalid process ID @@ -243,7 +249,7 @@ func TestAddInvalidProcess(t *testing.T) { } func TestRemoveProcess(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -260,7 +266,7 @@ func TestRemoveProcess(t *testing.T) { } func TestUpdateProcess(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process1 := getDummyProcess() @@ -311,7 +317,7 @@ func TestUpdateProcess(t *testing.T) { } func TestUpdateSameHashProcess(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) config := getDummyProcess() @@ -340,7 +346,7 @@ func TestUpdateSameHashProcess(t *testing.T) { } func TestUpdateProcessLogHistoryTransfer(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) p := getDummyProcess() @@ -394,7 +400,7 @@ func TestUpdateProcessLogHistoryTransfer(t *testing.T) { } func TestUpdateProcessMetadataTransfer(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) p := getDummyProcess() @@ -429,7 +435,7 @@ func TestUpdateProcessMetadataTransfer(t *testing.T) { } func TestGetProcess(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process1 := getDummyProcess() @@ -496,7 +502,7 @@ func TestGetProcess(t *testing.T) { } func TestStartProcess(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -522,8 +528,39 @@ func TestStartProcess(t *testing.T) { rs.StopProcess(tid) } +func TestStartProcessWithLimits(t *testing.T) { + rs, err := getDummyRestreamer(nil, nil, nil, nil, true) + require.NoError(t, err) + + process := getDummyProcess() + process.LimitCPU = 1 + process.LimitMemory = 1 + process.LimitGPU = app.ConfigLimitGPU{ + Usage: 1, + Encoder: 1, + Decoder: 1, + Memory: 1, + } + process.Options = append(process.Options, "-hwdevice", "{hwdevice}") + tid := app.ProcessID{ID: process.ID} + + rs.AddProcess(process) + + err = rs.StartProcess(tid) + require.Equal(t, nil, err, "should be able to start existing process") + + state, _ := rs.GetProcessState(tid) + require.Equal(t, "start", state.Order, "Process should be started") + + require.Equal(t, []string{ + "-loglevel", "info", "-hwdevice", "0", "-f", "lavfi", "-re", "-i", "testsrc=size=1280x720:rate=25", "-codec", "copy", "-f", "null", "-", + }, state.Command) + + rs.StopProcess(tid) +} + func TestProcessResources(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -585,7 +622,7 @@ func TestProcessResources(t *testing.T) { } func TestStopProcess(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -611,7 +648,7 @@ func TestStopProcess(t *testing.T) { } func TestRestartProcess(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -637,7 +674,7 @@ func TestRestartProcess(t *testing.T) { } func TestReloadProcess(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -669,7 +706,7 @@ func TestReloadProcess(t *testing.T) { } func TestParseProcessPattern(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -692,7 +729,7 @@ func TestParseProcessPattern(t *testing.T) { } func TestProbeProcess(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -702,7 +739,7 @@ func TestProbeProcess(t *testing.T) { } func TestProbeProcessWithReference(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -718,7 +755,7 @@ func TestProbeProcessWithReference(t *testing.T) { } func TestProcessMetadata(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -743,7 +780,7 @@ func TestProcessMetadata(t *testing.T) { } func TestLog(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -782,7 +819,7 @@ func TestLog(t *testing.T) { } func TestLogTransfer(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -808,7 +845,7 @@ func TestLogTransfer(t *testing.T) { } func TestPlayoutNoRange(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -832,7 +869,7 @@ func TestPlayoutRange(t *testing.T) { portrange, err := net.NewPortrange(3000, 3001) require.NoError(t, err) - rs, err := getDummyRestreamer(portrange, nil, nil, nil) + rs, err := getDummyRestreamer(portrange, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -888,7 +925,7 @@ func TestParseAddressReference(t *testing.T) { } func TestAddressReference(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process1 := getDummyProcess() @@ -919,7 +956,7 @@ func TestAddressReference(t *testing.T) { } func TestTeeAddressReference(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process1 := getDummyProcess() @@ -965,7 +1002,7 @@ func TestTeeAddressReference(t *testing.T) { } func TestConfigValidation(t *testing.T) { - rsi, err := getDummyRestreamer(nil, nil, nil, nil) + rsi, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) rs := rsi.(*restream) @@ -1013,7 +1050,7 @@ func TestConfigValidation(t *testing.T) { } func TestConfigValidationWithMkdir(t *testing.T) { - rsi, err := getDummyRestreamer(nil, nil, nil, nil) + rsi, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) rs := rsi.(*restream) @@ -1057,7 +1094,7 @@ func TestConfigValidationFFmpeg(t *testing.T) { valOut, err := ffmpeg.NewValidator([]string{"^https?://", "^rtmp://"}, nil) require.NoError(t, err) - rsi, err := getDummyRestreamer(nil, valIn, valOut, nil) + rsi, err := getDummyRestreamer(nil, valIn, valOut, nil, false) require.NoError(t, err) rs := rsi.(*restream) @@ -1083,7 +1120,7 @@ func TestConfigValidationFFmpeg(t *testing.T) { } func TestOutputAddressValidation(t *testing.T) { - rsi, err := getDummyRestreamer(nil, nil, nil, nil) + rsi, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) rs := rsi.(*restream) @@ -1124,7 +1161,7 @@ func TestOutputAddressValidation(t *testing.T) { } func TestMetadata(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -1422,7 +1459,7 @@ func TestProcessReplacer(t *testing.T) { "latency": "20000", // 20 milliseconds, FFmpeg requires microseconds }) - rsi, err := getDummyRestreamer(nil, nil, nil, replacer) + rsi, err := getDummyRestreamer(nil, nil, nil, replacer, false) require.NoError(t, err) process := &app.Config{ @@ -1611,7 +1648,7 @@ func TestProcessReplacer(t *testing.T) { } func TestProcessLogPattern(t *testing.T) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -1642,7 +1679,7 @@ func TestProcessLogPattern(t *testing.T) { } func TestProcessLimit(t *testing.T) { - rsi, err := getDummyRestreamer(nil, nil, nil, nil) + rsi, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) process := getDummyProcess() @@ -1667,7 +1704,7 @@ func TestProcessLimit(t *testing.T) { } func BenchmarkGetProcessIDs(b *testing.B) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(b, err) for i := 0; i < 1000; i++ { @@ -1688,7 +1725,7 @@ func BenchmarkGetProcessIDs(b *testing.B) { } func BenchmarkGetProcess(b *testing.B) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(b, err) for i := 0; i < 1000; i++ { @@ -1712,7 +1749,7 @@ func BenchmarkGetProcess(b *testing.B) { } func BenchmarkGetProcessState(b *testing.B) { - rs, err := getDummyRestreamer(nil, nil, nil, nil) + rs, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(b, err) n := 10 @@ -1745,7 +1782,7 @@ func BenchmarkGetProcessState(b *testing.B) { } func TestProcessCleanup(t *testing.T) { - rsi, err := getDummyRestreamer(nil, nil, nil, nil) + rsi, err := getDummyRestreamer(nil, nil, nil, nil, false) require.NoError(t, err) rsi.Start() From ed5357cde3acb8a934d4c752b946d4c05811d2be Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Wed, 30 Oct 2024 15:07:05 +0100 Subject: [PATCH 56/64] Add GPU metrics --- app/api/api.go | 1 + monitor/gpu.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 monitor/gpu.go diff --git a/app/api/api.go b/app/api/api.go index 6665d619..5fab94ec 100644 --- a/app/api/api.go +++ b/app/api/api.go @@ -1234,6 +1234,7 @@ func (a *api) start(ctx context.Context) error { metrics.Register(monitor.NewUptimeCollector()) metrics.Register(monitor.NewCPUCollector(a.resources)) metrics.Register(monitor.NewMemCollector(a.resources)) + metrics.Register(monitor.NewGPUCollector(a.resources)) metrics.Register(monitor.NewNetCollector(a.resources)) metrics.Register(monitor.NewDiskCollector(a.diskfs.Metadata("base"), a.resources)) metrics.Register(monitor.NewFilesystemCollector("diskfs", a.diskfs)) diff --git a/monitor/gpu.go b/monitor/gpu.go new file mode 100644 index 00000000..c0fcb9f9 --- /dev/null +++ b/monitor/gpu.go @@ -0,0 +1,79 @@ +package monitor + +import ( + "fmt" + + "github.com/datarhei/core/v16/monitor/metric" + "github.com/datarhei/core/v16/resources" +) + +type gpuCollector struct { + ngpuDescr *metric.Description + usageDescr *metric.Description + encoderDescr *metric.Description + decoderDescr *metric.Description + memoryTotalDescr *metric.Description + memoryFreeDescr *metric.Description + memoryLimitDescr *metric.Description + limitDescr *metric.Description + + resources resources.Resources +} + +func NewGPUCollector(rsc resources.Resources) metric.Collector { + c := &gpuCollector{ + resources: rsc, + } + + c.ngpuDescr = metric.NewDesc("gpu_ngpu", "Number of GPUs in the system", nil) + c.usageDescr = metric.NewDesc("gpu_usage", "Percentage of GPU used ", []string{"index"}) + c.encoderDescr = metric.NewDesc("gpu_encoder", "Percentage of GPU encoder used", []string{"index"}) + c.decoderDescr = metric.NewDesc("gpu_decoder", "Percentage of GPU decoder used", []string{"index"}) + c.memoryTotalDescr = metric.NewDesc("gpu_mem_total", "GPU memory total in bytes", []string{"index"}) + c.memoryFreeDescr = metric.NewDesc("gpu_mem_free", "GPU memory available in bytes", []string{"index"}) + c.memoryLimitDescr = metric.NewDesc("gpu_mem_limit", "GPU memory limit in bytes", []string{"index"}) + c.limitDescr = metric.NewDesc("gpu_limit", "Percentage of GPU to be consumed", []string{"index"}) + + return c +} + +func (c *gpuCollector) Stop() {} + +func (c *gpuCollector) Prefix() string { + return "cpu" +} + +func (c *gpuCollector) Describe() []*metric.Description { + return []*metric.Description{ + c.ngpuDescr, + c.usageDescr, + c.encoderDescr, + c.decoderDescr, + c.memoryTotalDescr, + c.memoryFreeDescr, + c.memoryLimitDescr, + c.limitDescr, + } +} + +func (c *gpuCollector) Collect() metric.Metrics { + metrics := metric.NewMetrics() + + rinfo := c.resources.Info() + + metrics.Add(metric.NewValue(c.ngpuDescr, rinfo.GPU.NGPU)) + + for i, gpu := range rinfo.GPU.GPU { + index := fmt.Sprintf("%d", i) + metrics.Add(metric.NewValue(c.usageDescr, gpu.Usage, index)) + metrics.Add(metric.NewValue(c.encoderDescr, gpu.Encoder, index)) + metrics.Add(metric.NewValue(c.decoderDescr, gpu.Decoder, index)) + metrics.Add(metric.NewValue(c.limitDescr, gpu.UsageLimit, index)) + + metrics.Add(metric.NewValue(c.memoryTotalDescr, float64(gpu.MemoryTotal), index)) + metrics.Add(metric.NewValue(c.memoryFreeDescr, float64(gpu.MemoryAvailable), index)) + metrics.Add(metric.NewValue(c.memoryLimitDescr, float64(gpu.MemoryLimit), index)) + } + + return metrics +} From 22a94e1089b7812396cf434b1510006eb15d062f Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Wed, 30 Oct 2024 15:16:10 +0100 Subject: [PATCH 57/64] Add GPU resources --- docs/docs.go | 47 +++++++++++++++++++++++++++++++++++++++ docs/swagger.json | 47 +++++++++++++++++++++++++++++++++++++++ docs/swagger.yaml | 36 ++++++++++++++++++++++++++++++ http/api/about.go | 29 ++++++++++++++++-------- http/handler/api/about.go | 14 ++++++++++++ 5 files changed, 164 insertions(+), 9 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index 11b9c66e..2c69ea84 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -5208,6 +5208,39 @@ const docTemplate = `{ } } }, + "api.AboutGPUResources": { + "type": "object", + "properties": { + "memory_limit_bytes": { + "description": "Defined memory limit in bytes", + "type": "integer" + }, + "memory_total_bytes": { + "description": "Total available memory in bytes", + "type": "integer" + }, + "memory_used_bytes": { + "description": "Currently used memory in bytes", + "type": "integer" + }, + "usage_decoder": { + "description": "Current decoder usage, 0-100", + "type": "number" + }, + "usage_encoder": { + "description": "Current encoder usage, 0-100", + "type": "number" + }, + "usage_general": { + "description": "Current general usage, 0-100", + "type": "number" + }, + "usage_limit": { + "description": "Defined general usage limit, 0-100", + "type": "number" + } + } + }, "api.AboutResources": { "type": "object", "properties": { @@ -5223,6 +5256,13 @@ const docTemplate = `{ "description": "Current CPU load, 0-100*ncpu", "type": "number" }, + "gpu": { + "description": "GPU resources", + "type": "array", + "items": { + "$ref": "#/definitions/api.AboutGPUResources" + } + }, "is_throttling": { "description": "Whether this core is currently throttling", "type": "boolean" @@ -7082,26 +7122,33 @@ const docTemplate = `{ "type": "object", "properties": { "cpu_usage": { + "description": "percent 0-100*ncpu", "type": "number" }, "gpu_decoder": { + "description": "percent 0-100", "type": "number" }, "gpu_encoder": { + "description": "percent 0-100", "type": "number" }, "gpu_memory_mbytes": { + "description": "megabytes", "type": "integer", "format": "uint64" }, "gpu_usage": { + "description": "percent 0-100", "type": "number" }, "memory_mbytes": { + "description": "megabytes", "type": "integer", "format": "uint64" }, "waitfor_seconds": { + "description": "seconds", "type": "integer", "format": "uint64" } diff --git a/docs/swagger.json b/docs/swagger.json index ce21a8ea..400e5078 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -5201,6 +5201,39 @@ } } }, + "api.AboutGPUResources": { + "type": "object", + "properties": { + "memory_limit_bytes": { + "description": "Defined memory limit in bytes", + "type": "integer" + }, + "memory_total_bytes": { + "description": "Total available memory in bytes", + "type": "integer" + }, + "memory_used_bytes": { + "description": "Currently used memory in bytes", + "type": "integer" + }, + "usage_decoder": { + "description": "Current decoder usage, 0-100", + "type": "number" + }, + "usage_encoder": { + "description": "Current encoder usage, 0-100", + "type": "number" + }, + "usage_general": { + "description": "Current general usage, 0-100", + "type": "number" + }, + "usage_limit": { + "description": "Defined general usage limit, 0-100", + "type": "number" + } + } + }, "api.AboutResources": { "type": "object", "properties": { @@ -5216,6 +5249,13 @@ "description": "Current CPU load, 0-100*ncpu", "type": "number" }, + "gpu": { + "description": "GPU resources", + "type": "array", + "items": { + "$ref": "#/definitions/api.AboutGPUResources" + } + }, "is_throttling": { "description": "Whether this core is currently throttling", "type": "boolean" @@ -7075,26 +7115,33 @@ "type": "object", "properties": { "cpu_usage": { + "description": "percent 0-100*ncpu", "type": "number" }, "gpu_decoder": { + "description": "percent 0-100", "type": "number" }, "gpu_encoder": { + "description": "percent 0-100", "type": "number" }, "gpu_memory_mbytes": { + "description": "megabytes", "type": "integer", "format": "uint64" }, "gpu_usage": { + "description": "percent 0-100", "type": "number" }, "memory_mbytes": { + "description": "megabytes", "type": "integer", "format": "uint64" }, "waitfor_seconds": { + "description": "seconds", "type": "integer", "format": "uint64" } diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 9ad7331a..5e12a2a2 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -95,6 +95,30 @@ definitions: version: $ref: '#/definitions/api.AboutVersion' type: object + api.AboutGPUResources: + properties: + memory_limit_bytes: + description: Defined memory limit in bytes + type: integer + memory_total_bytes: + description: Total available memory in bytes + type: integer + memory_used_bytes: + description: Currently used memory in bytes + type: integer + usage_decoder: + description: Current decoder usage, 0-100 + type: number + usage_encoder: + description: Current encoder usage, 0-100 + type: number + usage_general: + description: Current general usage, 0-100 + type: number + usage_limit: + description: Defined general usage limit, 0-100 + type: number + type: object api.AboutResources: properties: cpu_core: @@ -106,6 +130,11 @@ definitions: cpu_used: description: Current CPU load, 0-100*ncpu type: number + gpu: + description: GPU resources + items: + $ref: '#/definitions/api.AboutGPUResources' + type: array is_throttling: description: Whether this core is currently throttling type: boolean @@ -1357,20 +1386,27 @@ definitions: api.ProcessConfigLimits: properties: cpu_usage: + description: percent 0-100*ncpu type: number gpu_decoder: + description: percent 0-100 type: number gpu_encoder: + description: percent 0-100 type: number gpu_memory_mbytes: + description: megabytes format: uint64 type: integer gpu_usage: + description: percent 0-100 type: number memory_mbytes: + description: megabytes format: uint64 type: integer waitfor_seconds: + description: seconds format: uint64 type: integer type: object diff --git a/http/api/about.go b/http/api/about.go index 332420a9..f660d6e5 100644 --- a/http/api/about.go +++ b/http/api/about.go @@ -24,15 +24,26 @@ type AboutVersion struct { // AboutResources holds information about the current resource usage type AboutResources struct { - IsThrottling bool `json:"is_throttling"` // Whether this core is currently throttling - NCPU float64 `json:"ncpu"` // Number of CPU on this node - CPU float64 `json:"cpu_used"` // Current CPU load, 0-100*ncpu - CPULimit float64 `json:"cpu_limit"` // Defined CPU load limit, 0-100*ncpu - CPUCore float64 `json:"cpu_core"` // Current CPU load of the core itself, 0-100*ncpu - Mem uint64 `json:"memory_used_bytes"` // Currently used memory in bytes - MemLimit uint64 `json:"memory_limit_bytes"` // Defined memory limit in bytes - MemTotal uint64 `json:"memory_total_bytes"` // Total available memory in bytes - MemCore uint64 `json:"memory_core_bytes"` // Current used memory of the core itself in bytes + IsThrottling bool `json:"is_throttling"` // Whether this core is currently throttling + NCPU float64 `json:"ncpu"` // Number of CPU on this node + CPU float64 `json:"cpu_used"` // Current CPU load, 0-100*ncpu + CPULimit float64 `json:"cpu_limit"` // Defined CPU load limit, 0-100*ncpu + CPUCore float64 `json:"cpu_core"` // Current CPU load of the core itself, 0-100*ncpu + Mem uint64 `json:"memory_used_bytes"` // Currently used memory in bytes + MemLimit uint64 `json:"memory_limit_bytes"` // Defined memory limit in bytes + MemTotal uint64 `json:"memory_total_bytes"` // Total available memory in bytes + MemCore uint64 `json:"memory_core_bytes"` // Current used memory of the core itself in bytes + GPU []AboutGPUResources `json:"gpu"` // GPU resources +} + +type AboutGPUResources struct { + Mem uint64 `json:"memory_used_bytes"` // Currently used memory in bytes + MemLimit uint64 `json:"memory_limit_bytes"` // Defined memory limit in bytes + MemTotal uint64 `json:"memory_total_bytes"` // Total available memory in bytes + Usage float64 `json:"usage_general"` // Current general usage, 0-100 + UsageLimit float64 `json:"usage_limit"` // Defined general usage limit, 0-100 + Encoder float64 `json:"usage_encoder"` // Current encoder usage, 0-100 + Decoder float64 `json:"usage_decoder"` // Current decoder usage, 0-100 } // MinimalAbout is the minimal information about the API diff --git a/http/handler/api/about.go b/http/handler/api/about.go index cba674dd..01b8d578 100644 --- a/http/handler/api/about.go +++ b/http/handler/api/about.go @@ -71,6 +71,7 @@ func (p *AboutHandler) About(c echo.Context) error { if p.resources != nil { res := p.resources.Info() + about.Resources.IsThrottling = res.CPU.Throttling about.Resources.NCPU = res.CPU.NCPU about.Resources.CPU = (100 - res.CPU.Idle) * res.CPU.NCPU @@ -80,6 +81,19 @@ func (p *AboutHandler) About(c echo.Context) error { about.Resources.MemLimit = res.Mem.Limit about.Resources.MemTotal = res.Mem.Total about.Resources.MemCore = res.Mem.Core + + about.Resources.GPU = make([]api.AboutGPUResources, len(res.GPU.GPU)) + for i, gpu := range res.GPU.GPU { + about.Resources.GPU[i] = api.AboutGPUResources{ + Mem: gpu.MemoryUsed, + MemLimit: gpu.MemoryLimit, + MemTotal: gpu.MemoryTotal, + Usage: gpu.Usage, + UsageLimit: gpu.UsageLimit, + Encoder: gpu.Encoder, + Decoder: gpu.Decoder, + } + } } return c.JSON(http.StatusOK, about) From 55015bcf6f14a9b64336c832c3aab15e377e2587 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Wed, 30 Oct 2024 17:12:29 +0100 Subject: [PATCH 58/64] Read out GPU specs at util start --- http/handler/api/about_test.go | 3 +- internal/testhelper/nvidia-smi/nvidia-smi.go | 65 +++++---- .../nvidia/fixtures/process_noprocesses.txt | 3 + resources/psutil/gpu/nvidia/nvidia.go | 124 ++++++++++++++---- resources/psutil/gpu/nvidia/nvidia_test.go | 66 ++++------ 5 files changed, 171 insertions(+), 90 deletions(-) create mode 100644 resources/psutil/gpu/nvidia/fixtures/process_noprocesses.txt diff --git a/http/handler/api/about_test.go b/http/handler/api/about_test.go index 2ac0877a..5ef491fa 100644 --- a/http/handler/api/about_test.go +++ b/http/handler/api/about_test.go @@ -6,6 +6,7 @@ import ( "github.com/datarhei/core/v16/http/api" "github.com/datarhei/core/v16/http/mock" + "github.com/datarhei/core/v16/internal/mock/resources" "github.com/datarhei/core/v16/internal/mock/restream" "github.com/stretchr/testify/require" @@ -20,7 +21,7 @@ func getDummyAboutRouter() (*echo.Echo, error) { return nil, err } - handler := NewAbout(rs, nil, func() []string { return []string{} }) + handler := NewAbout(rs, resources.New(), func() []string { return []string{} }) router.Add("GET", "/", handler.About) diff --git a/internal/testhelper/nvidia-smi/nvidia-smi.go b/internal/testhelper/nvidia-smi/nvidia-smi.go index 36f6a78c..7bab74a3 100644 --- a/internal/testhelper/nvidia-smi/nvidia-smi.go +++ b/internal/testhelper/nvidia-smi/nvidia-smi.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "os/signal" + "slices" "time" ) @@ -931,41 +932,53 @@ func main() { } ctx, cancel := context.WithCancel(context.Background()) + wait := false if os.Args[1] == "pmon" { - go func(ctx context.Context) { - ticker := time.NewTicker(time.Second) - defer ticker.Stop() + if slices.Contains(os.Args[1:], "-c") { + fmt.Fprintf(os.Stdout, "%s\n", pmondata) + } else { + go func(ctx context.Context) { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - fmt.Fprintf(os.Stdout, "%s\n", pmondata) + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + fmt.Fprintf(os.Stdout, "%s\n", pmondata) + } } - } - }(ctx) + }(ctx) + } } else { - go func(ctx context.Context) { - ticker := time.NewTicker(time.Second) - defer ticker.Stop() + if !slices.Contains(os.Args[1:], "-l") { + fmt.Fprintf(os.Stdout, "%s\n", querydata) + } else { + wait = true + go func(ctx context.Context) { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - fmt.Fprintf(os.Stdout, "%s\n", querydata) + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + fmt.Fprintf(os.Stdout, "%s\n", querydata) + } } - } - }(ctx) + }(ctx) + } } - // Wait for interrupt signal to gracefully shutdown the app - quit := make(chan os.Signal, 1) - signal.Notify(quit, os.Interrupt) - <-quit + if wait { + // Wait for interrupt signal to gracefully shutdown the app + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + <-quit + } cancel() diff --git a/resources/psutil/gpu/nvidia/fixtures/process_noprocesses.txt b/resources/psutil/gpu/nvidia/fixtures/process_noprocesses.txt new file mode 100644 index 00000000..7d9f98ed --- /dev/null +++ b/resources/psutil/gpu/nvidia/fixtures/process_noprocesses.txt @@ -0,0 +1,3 @@ +# gpu pid type sm mem enc dec fb command +# Idx # C/G % % % % MB name + 0 - - - - - - - - \ No newline at end of file diff --git a/resources/psutil/gpu/nvidia/nvidia.go b/resources/psutil/gpu/nvidia/nvidia.go index a2aaf92f..2618023a 100644 --- a/resources/psutil/gpu/nvidia/nvidia.go +++ b/resources/psutil/gpu/nvidia/nvidia.go @@ -114,7 +114,7 @@ func (w *writerQuery) Write(data []byte) (int, error) { break } - s, err := w.parse(content) + s, err := parseQuery(content) if err != nil { continue } @@ -125,7 +125,7 @@ func (w *writerQuery) Write(data []byte) (int, error) { return n, nil } -func (w *writerQuery) parse(data []byte) (Stats, error) { +func parseQuery(data []byte) (Stats, error) { nv := Stats{} err := xml.Unmarshal(data, &nv) @@ -139,7 +139,6 @@ func (w *writerQuery) parse(data []byte) (Stats, error) { type writerProcess struct { buf bytes.Buffer ch chan Process - re *regexp.Regexp terminator []byte } @@ -161,7 +160,7 @@ func (w *writerProcess) Write(data []byte) (int, error) { break } - s, err := w.parse(content) + s, err := parseProcess(content) if err != nil { continue } @@ -172,7 +171,19 @@ func (w *writerProcess) Write(data []byte) (int, error) { return n, nil } -func (w *writerProcess) parse(data []byte) (Process, error) { +const processMatcher = `^\s*([0-9]+)\s+([0-9]+)\s+[A-Z]\s+([0-9-]+)\s+[0-9-]+\s+([0-9-]+)\s+([0-9-]+)\s+([0-9]+).*` + +// # gpu pid type sm mem enc dec fb command +// # Idx # C/G % % % % MB name +// +// 0 7372 C 2 0 2 - 136 ffmpeg +// 0 12176 C 5 2 3 7 782 ffmpeg +// 0 20035 C 8 2 4 1 1145 ffmpeg +// 0 20141 C 2 1 1 3 429 ffmpeg +// 0 29591 C 2 1 - 2 435 ffmpeg +var reProcessMatcher = regexp.MustCompile(processMatcher) + +func parseProcess(data []byte) (Process, error) { p := Process{} if len(data) == 0 { @@ -183,7 +194,7 @@ func (w *writerProcess) parse(data []byte) (Process, error) { return p, fmt.Errorf("comment") } - matches := w.re.FindStringSubmatch(string(data)) + matches := reProcessMatcher.FindStringSubmatch(string(data)) if matches == nil { return p, fmt.Errorf("no matches found") } @@ -236,31 +247,38 @@ func New(path string) gpu.GPU { } n := &nvidia{ - wrQuery: &writerQuery{ - ch: make(chan Stats, 1), - terminator: []byte("\n"), - }, - wrProcess: &writerProcess{ - ch: make(chan Process, 32), - // # gpu pid type sm mem enc dec fb command - // # Idx # C/G % % % % MB name - // 0 7372 C 2 0 2 - 136 ffmpeg - // 0 12176 C 5 2 3 7 782 ffmpeg - // 0 20035 C 8 2 4 1 1145 ffmpeg - // 0 20141 C 2 1 1 3 429 ffmpeg - // 0 29591 C 2 1 - 2 435 ffmpeg - re: regexp.MustCompile(`^\s*([0-9]+)\s+([0-9]+)\s+[A-Z]\s+([0-9-]+)\s+[0-9-]+\s+([0-9-]+)\s+([0-9-]+)\s+([0-9]+).*`), - terminator: []byte("\n"), - }, process: map[int32]Process{}, } + stats, err := n.runQueryOnce(path) + if err != nil { + return &dummy{} + } + + n.stats = stats + + process, err := n.runProcessOnce(path) + if err != nil { + return &dummy{} + } + + n.process = process + + n.wrQuery = &writerQuery{ + ch: make(chan Stats, 1), + terminator: []byte("\n"), + } + n.wrProcess = &writerProcess{ + ch: make(chan Process, 32), + terminator: []byte("\n"), + } + ctx, cancel := context.WithCancel(context.Background()) n.cancel = cancel + go n.reader(ctx) go n.runnerQuery(ctx, path) go n.runnerProcess(ctx, path) - go n.reader(ctx) return n } @@ -289,6 +307,32 @@ func (n *nvidia) reader(ctx context.Context) { } } +func (n *nvidia) runQueryOnce(path string) (Stats, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + data := &bytes.Buffer{} + + cmd := exec.CommandContext(ctx, path, "-q", "-x") + cmd.Stdout = data + err := cmd.Start() + if err != nil { + return Stats{}, err + } + + err = cmd.Wait() + if err != nil { + return Stats{}, err + } + + stats, err := parseQuery(data.Bytes()) + if err != nil { + return Stats{}, err + } + + return stats, nil +} + func (n *nvidia) runnerQuery(ctx context.Context, path string) { for { cmd := exec.CommandContext(ctx, path, "-q", "-x", "-l", "1") @@ -317,6 +361,40 @@ func (n *nvidia) runnerQuery(ctx context.Context, path string) { } } +func (n *nvidia) runProcessOnce(path string) (map[int32]Process, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + data := &bytes.Buffer{} + + cmd := exec.CommandContext(ctx, path, "pmon", "-s", "um", "-c", "1") + cmd.Stdout = data + err := cmd.Start() + if err != nil { + return nil, err + } + + err = cmd.Wait() + if err != nil { + return nil, err + } + + lines := bytes.Split(data.Bytes(), []byte{'\n'}) + + process := map[int32]Process{} + + for _, line := range lines { + p, err := parseProcess(line) + if err != nil { + continue + } + + process[p.PID] = p + } + + return process, nil +} + func (n *nvidia) runnerProcess(ctx context.Context, path string) { for { cmd := exec.CommandContext(ctx, path, "pmon", "-s", "um", "-d", "5") diff --git a/resources/psutil/gpu/nvidia/nvidia_test.go b/resources/psutil/gpu/nvidia/nvidia_test.go index 37f18690..cad4d1a0 100644 --- a/resources/psutil/gpu/nvidia/nvidia_test.go +++ b/resources/psutil/gpu/nvidia/nvidia_test.go @@ -3,10 +3,8 @@ package nvidia import ( "bytes" "os" - "regexp" "sync" "testing" - "time" "github.com/datarhei/core/v16/internal/testhelper" "github.com/datarhei/core/v16/resources/psutil/gpu" @@ -17,9 +15,7 @@ func TestParseQuery(t *testing.T) { data, err := os.ReadFile("./fixtures/query1.xml") require.NoError(t, err) - wr := &writerQuery{} - - nv, err := wr.parse(data) + nv, err := parseQuery(data) require.NoError(t, err) require.Equal(t, Stats{ @@ -40,7 +36,7 @@ func TestParseQuery(t *testing.T) { data, err = os.ReadFile("./fixtures/query2.xml") require.NoError(t, err) - nv, err = wr.parse(data) + nv, err = parseQuery(data) require.NoError(t, err) require.Equal(t, Stats{ @@ -71,7 +67,7 @@ func TestParseQuery(t *testing.T) { data, err = os.ReadFile("./fixtures/query3.xml") require.NoError(t, err) - nv, err = wr.parse(data) + nv, err = parseQuery(data) require.NoError(t, err) require.Equal(t, Stats{ @@ -93,15 +89,11 @@ func TestParseProcess(t *testing.T) { data, err := os.ReadFile("./fixtures/process.txt") require.NoError(t, err) - wr := &writerProcess{ - re: regexp.MustCompile(`^\s*([0-9]+)\s+([0-9]+)\s+[A-Z]\s+([0-9-]+)\s+[0-9-]+\s+([0-9-]+)\s+([0-9-]+)\s+([0-9]+).*`), - } - lines := bytes.Split(data, []byte("\n")) process := map[int32]Process{} for _, line := range lines { - p, err := wr.parse(line) + p, err := parseProcess(line) if err != nil { continue } @@ -153,6 +145,25 @@ func TestParseProcess(t *testing.T) { }, process) } +func TestParseProcessNoProcesses(t *testing.T) { + data, err := os.ReadFile("./fixtures/process_noprocesses.txt") + require.NoError(t, err) + + lines := bytes.Split(data, []byte("\n")) + process := map[int32]Process{} + + for _, line := range lines { + p, err := parseProcess(line) + if err != nil { + continue + } + + process[p.PID] = p + } + + require.Equal(t, map[int32]Process{}, process) +} + func TestWriterQuery(t *testing.T) { data, err := os.ReadFile("./fixtures/query2.xml") require.NoError(t, err) @@ -213,7 +224,6 @@ func TestWriterProcess(t *testing.T) { wr := &writerProcess{ ch: make(chan Process, 32), - re: regexp.MustCompile(`^\s*([0-9]+)\s+([0-9]+)\s+[A-Z]\s+([0-9-]+)\s+[0-9-]+\s+([0-9-]+)\s+([0-9-]+)\s+([0-9]+).*`), terminator: []byte("\n"), } @@ -292,10 +302,9 @@ func TestNvidiaGPUCount(t *testing.T) { _, ok := nv.(*dummy) require.False(t, ok) - require.Eventually(t, func() bool { - count, _ := nv.Count() - return count != 0 - }, 5*time.Second, time.Second) + count, err := nv.Count() + require.NoError(t, err) + require.NotEqual(t, 0, count) } func TestNvidiaGPUStats(t *testing.T) { @@ -311,24 +320,6 @@ func TestNvidiaGPUStats(t *testing.T) { _, ok := nv.(*dummy) require.False(t, ok) - require.Eventually(t, func() bool { - stats, _ := nv.Stats() - - if len(stats) != 2 { - return false - } - - if len(stats[0].Process) != 3 { - return false - } - - if len(stats[1].Process) != 2 { - return false - } - - return true - }, 5*time.Second, time.Second) - stats, err := nv.Stats() require.NoError(t, err) require.Equal(t, []gpu.Stats{ @@ -412,11 +403,6 @@ func TestNvidiaGPUProcess(t *testing.T) { _, ok := nv.(*dummy) require.False(t, ok) - require.Eventually(t, func() bool { - _, err := nv.Process(12176) - return err == nil - }, 5*time.Second, time.Second) - proc, err := nv.Process(12176) require.NoError(t, err) require.Equal(t, gpu.Process{ From aa3a5b4978dc5c9e35442b26641e0ad0270bb2d3 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 31 Oct 2024 12:17:53 +0100 Subject: [PATCH 59/64] Prevent panic if index is out of bounds --- resources/resources.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/resources/resources.go b/resources/resources.go index 17c77e4e..1e322a14 100644 --- a/resources/resources.go +++ b/resources/resources.go @@ -575,8 +575,11 @@ func (r *resources) Info() Info { Encoder: g.Encoder, Decoder: g.Decoder, UsageLimit: gpulimit, - Throttling: gputhrottling[i], }) + + if i < len(gputhrottling) { + gpuinfo.GPU[i].Throttling = gputhrottling[i] + } } i := Info{ From d73afc141c2160f6f3cee7ac4906f43845d7f728 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 31 Oct 2024 12:18:26 +0100 Subject: [PATCH 60/64] Assign default GPU if no softlimit is given --- restream/core.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/restream/core.go b/restream/core.go index bbe1a72c..6824f83c 100644 --- a/restream/core.go +++ b/restream/core.go @@ -667,6 +667,8 @@ func (r *restream) onBeforeStart(cfg *app.Config) func([]string) ([]string, erro } selectedGPU = res.GPU + } else { + selectedGPU = 0 } if t, hasTask := r.tasks.Load(cfg.ProcessID()); hasTask { From eb4d0430b6f263d2733c7cf2c28abb2b5e8a5063 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 31 Oct 2024 13:49:48 +0100 Subject: [PATCH 61/64] Fix test --- restream/core_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/restream/core_test.go b/restream/core_test.go index 306f4cf0..d0c4fb5e 100644 --- a/restream/core_test.go +++ b/restream/core_test.go @@ -1616,7 +1616,7 @@ func TestProcessReplacer(t *testing.T) { "{memfs}/foobar_in_mem.txt", "/mnt/diskfs/foobar_on_disk_aswell.txt", "http://localhost/mnt/memfs/foobar_in_mem_aswell.txt", - "hwdevice:-1", + "hwdevice:0", "-f", "lavfi", "-re", @@ -1627,7 +1627,7 @@ func TestProcessReplacer(t *testing.T) { "memfs:http://localhost/mnt/memfs/mem.txt", "fsdisk:/mnt/diskfs/fsdisk_20191012_072050.txt", "fsmem:http://localhost/mnt/memfs/$inputid.txt", - "hwdevice:-1", + "hwdevice:0", "-i", "input:in_314159265359_refref_process:314159265359_reference:refref_diskfs:/mnt/diskfs/disk.txt_memfs:http://localhost/mnt/memfs/mem.txt_fsdisk:/mnt/diskfs/fsdisk.txt_fsmem:http://localhost/mnt/memfs/fsmem.txt_rtmp:rtmp://localhost/app/pmtr?token=foobar_srt:srt://localhost:6000?mode=caller&transtype=live&latency=20000&streamid=trs,mode:request,token:abcfoobar&passphrase=secret_rtmp:rtmp://localhost/app/in_314159265359_refref?token=foobar", "-codec", From bfb54ca177e3d993876d13070dee210e9d0abb67 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 31 Oct 2024 14:32:18 +0100 Subject: [PATCH 62/64] Add GPU usage in cluster about API endpoint --- cluster/api.go | 4 +- cluster/docs/ClusterAPI_docs.go | 144 ++++++++++++++++++++++++++- cluster/docs/ClusterAPI_swagger.json | 144 ++++++++++++++++++++++++++- cluster/docs/ClusterAPI_swagger.yaml | 102 ++++++++++++++++++- docs/docs.go | 40 ++++++++ docs/swagger.json | 40 ++++++++ docs/swagger.yaml | 29 ++++++ http/api/cluster.go | 31 ++++-- http/handler/api/cluster.go | 13 +++ 9 files changed, 529 insertions(+), 18 deletions(-) diff --git a/cluster/api.go b/cluster/api.go index 38b21695..7c99f9b8 100644 --- a/cluster/api.go +++ b/cluster/api.go @@ -171,7 +171,7 @@ func (a *api) Version(c echo.Context) error { // @Tags v1.0.0 // @ID cluster-1-about // @Produce json -// @Success 200 {string} About +// @Success 200 {object} client.AboutResponse // @Success 500 {object} Error // @Router /v1/about [get] func (a *api) About(c echo.Context) error { @@ -413,7 +413,7 @@ func (a *api) ProcessAdd(c echo.Context) error { // @Param id path string true "Process ID" // @Param domain query string false "Domain to act on" // @Param X-Cluster-Origin header string false "Origin ID of request" -// @Success 200 {string} string +// @Success 200 {object} client.GetProcessResponse // @Failure 404 {object} Error // @Failure 500 {object} Error // @Failure 508 {object} Error diff --git a/cluster/docs/ClusterAPI_docs.go b/cluster/docs/ClusterAPI_docs.go index 32d3693b..ce72fb69 100644 --- a/cluster/docs/ClusterAPI_docs.go +++ b/cluster/docs/ClusterAPI_docs.go @@ -65,7 +65,7 @@ const docTemplateClusterAPI = `{ "200": { "description": "OK", "schema": { - "type": "string" + "$ref": "#/definitions/client.AboutResponse" } }, "500": { @@ -803,7 +803,7 @@ const docTemplateClusterAPI = `{ "200": { "description": "OK", "schema": { - "type": "string" + "$ref": "#/definitions/client.GetProcessResponse" } }, "404": { @@ -1430,6 +1430,111 @@ const docTemplateClusterAPI = `{ } } }, + "client.AboutResponse": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "id": { + "type": "string" + }, + "resources": { + "$ref": "#/definitions/client.AboutResponseResources" + }, + "started_at": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "client.AboutResponseGPUResources": { + "type": "object", + "properties": { + "decoder": { + "description": "Current decoder usage, 0-100", + "type": "number" + }, + "encoder": { + "description": "Current encoder usage, 0-100", + "type": "number" + }, + "memory_bytes": { + "description": "Currently used memory in bytes", + "type": "integer" + }, + "memory_limit_bytes": { + "description": "Defined memory limit in bytes", + "type": "integer" + }, + "memory_total_bytes": { + "description": "Total available memory in bytes", + "type": "integer" + }, + "usage": { + "description": "Current general usage, 0-100", + "type": "number" + }, + "usage_limit": { + "description": "Defined general usage limit, 0-100", + "type": "number" + } + } + }, + "client.AboutResponseResources": { + "type": "object", + "properties": { + "cpu": { + "description": "Current CPU load, 0-100*ncpu", + "type": "number" + }, + "cpu_core": { + "description": "Current CPU load of the core itself, 0-100*ncpu", + "type": "number" + }, + "cpu_limit": { + "description": "Defined CPU load limit, 0-100*ncpu", + "type": "number" + }, + "error": { + "description": "Last error", + "type": "string" + }, + "gpu": { + "description": "Currently used GPU resources", + "type": "array", + "items": { + "$ref": "#/definitions/client.AboutResponseGPUResources" + } + }, + "is_throttling": { + "description": "Whether this core is currently throttling", + "type": "boolean" + }, + "memory_bytes": { + "description": "Currently used memory in bytes", + "type": "integer" + }, + "memory_core_bytes": { + "description": "Current used memory of the core itself in bytes", + "type": "integer" + }, + "memory_limit_bytes": { + "description": "Defined memory limit in bytes", + "type": "integer" + }, + "memory_total_bytes": { + "description": "Total available memory in bytes", + "type": "integer" + }, + "ncpu": { + "description": "Number of CPU on this node", + "type": "number" + } + } + }, "client.AddIdentityRequest": { "type": "object", "properties": { @@ -1446,6 +1551,17 @@ const docTemplateClusterAPI = `{ } } }, + "client.GetProcessResponse": { + "type": "object", + "properties": { + "nodeid": { + "type": "string" + }, + "process": { + "$ref": "#/definitions/github_com_datarhei_core_v16_cluster_store.Process" + } + } + }, "client.JoinRequest": { "type": "object", "properties": { @@ -2210,6 +2326,30 @@ const docTemplateClusterAPI = `{ } } }, + "github_com_datarhei_core_v16_cluster_store.Process": { + "type": "object", + "properties": { + "config": { + "$ref": "#/definitions/app.Config" + }, + "createdAt": { + "type": "string" + }, + "error": { + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": true + }, + "order": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, "identity.Auth0Tenant": { "type": "object", "properties": { diff --git a/cluster/docs/ClusterAPI_swagger.json b/cluster/docs/ClusterAPI_swagger.json index 5e25097f..4932b2da 100644 --- a/cluster/docs/ClusterAPI_swagger.json +++ b/cluster/docs/ClusterAPI_swagger.json @@ -58,7 +58,7 @@ "200": { "description": "OK", "schema": { - "type": "string" + "$ref": "#/definitions/client.AboutResponse" } }, "500": { @@ -796,7 +796,7 @@ "200": { "description": "OK", "schema": { - "type": "string" + "$ref": "#/definitions/client.GetProcessResponse" } }, "404": { @@ -1423,6 +1423,111 @@ } } }, + "client.AboutResponse": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "id": { + "type": "string" + }, + "resources": { + "$ref": "#/definitions/client.AboutResponseResources" + }, + "started_at": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "client.AboutResponseGPUResources": { + "type": "object", + "properties": { + "decoder": { + "description": "Current decoder usage, 0-100", + "type": "number" + }, + "encoder": { + "description": "Current encoder usage, 0-100", + "type": "number" + }, + "memory_bytes": { + "description": "Currently used memory in bytes", + "type": "integer" + }, + "memory_limit_bytes": { + "description": "Defined memory limit in bytes", + "type": "integer" + }, + "memory_total_bytes": { + "description": "Total available memory in bytes", + "type": "integer" + }, + "usage": { + "description": "Current general usage, 0-100", + "type": "number" + }, + "usage_limit": { + "description": "Defined general usage limit, 0-100", + "type": "number" + } + } + }, + "client.AboutResponseResources": { + "type": "object", + "properties": { + "cpu": { + "description": "Current CPU load, 0-100*ncpu", + "type": "number" + }, + "cpu_core": { + "description": "Current CPU load of the core itself, 0-100*ncpu", + "type": "number" + }, + "cpu_limit": { + "description": "Defined CPU load limit, 0-100*ncpu", + "type": "number" + }, + "error": { + "description": "Last error", + "type": "string" + }, + "gpu": { + "description": "Currently used GPU resources", + "type": "array", + "items": { + "$ref": "#/definitions/client.AboutResponseGPUResources" + } + }, + "is_throttling": { + "description": "Whether this core is currently throttling", + "type": "boolean" + }, + "memory_bytes": { + "description": "Currently used memory in bytes", + "type": "integer" + }, + "memory_core_bytes": { + "description": "Current used memory of the core itself in bytes", + "type": "integer" + }, + "memory_limit_bytes": { + "description": "Defined memory limit in bytes", + "type": "integer" + }, + "memory_total_bytes": { + "description": "Total available memory in bytes", + "type": "integer" + }, + "ncpu": { + "description": "Number of CPU on this node", + "type": "number" + } + } + }, "client.AddIdentityRequest": { "type": "object", "properties": { @@ -1439,6 +1544,17 @@ } } }, + "client.GetProcessResponse": { + "type": "object", + "properties": { + "nodeid": { + "type": "string" + }, + "process": { + "$ref": "#/definitions/github_com_datarhei_core_v16_cluster_store.Process" + } + } + }, "client.JoinRequest": { "type": "object", "properties": { @@ -2203,6 +2319,30 @@ } } }, + "github_com_datarhei_core_v16_cluster_store.Process": { + "type": "object", + "properties": { + "config": { + "$ref": "#/definitions/app.Config" + }, + "createdAt": { + "type": "string" + }, + "error": { + "type": "string" + }, + "metadata": { + "type": "object", + "additionalProperties": true + }, + "order": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, "identity.Auth0Tenant": { "type": "object", "properties": { diff --git a/cluster/docs/ClusterAPI_swagger.yaml b/cluster/docs/ClusterAPI_swagger.yaml index 9ab3c982..4374ccca 100644 --- a/cluster/docs/ClusterAPI_swagger.yaml +++ b/cluster/docs/ClusterAPI_swagger.yaml @@ -100,6 +100,81 @@ definitions: description: percent 0-100 type: number type: object + client.AboutResponse: + properties: + address: + type: string + id: + type: string + resources: + $ref: '#/definitions/client.AboutResponseResources' + started_at: + type: string + version: + type: string + type: object + client.AboutResponseGPUResources: + properties: + decoder: + description: Current decoder usage, 0-100 + type: number + encoder: + description: Current encoder usage, 0-100 + type: number + memory_bytes: + description: Currently used memory in bytes + type: integer + memory_limit_bytes: + description: Defined memory limit in bytes + type: integer + memory_total_bytes: + description: Total available memory in bytes + type: integer + usage: + description: Current general usage, 0-100 + type: number + usage_limit: + description: Defined general usage limit, 0-100 + type: number + type: object + client.AboutResponseResources: + properties: + cpu: + description: Current CPU load, 0-100*ncpu + type: number + cpu_core: + description: Current CPU load of the core itself, 0-100*ncpu + type: number + cpu_limit: + description: Defined CPU load limit, 0-100*ncpu + type: number + error: + description: Last error + type: string + gpu: + description: Currently used GPU resources + items: + $ref: '#/definitions/client.AboutResponseGPUResources' + type: array + is_throttling: + description: Whether this core is currently throttling + type: boolean + memory_bytes: + description: Currently used memory in bytes + type: integer + memory_core_bytes: + description: Current used memory of the core itself in bytes + type: integer + memory_limit_bytes: + description: Defined memory limit in bytes + type: integer + memory_total_bytes: + description: Total available memory in bytes + type: integer + ncpu: + description: Number of CPU on this node + type: number + type: object client.AddIdentityRequest: properties: identity: @@ -110,6 +185,13 @@ definitions: config: $ref: '#/definitions/app.Config' type: object + client.GetProcessResponse: + properties: + nodeid: + type: string + process: + $ref: '#/definitions/github_com_datarhei_core_v16_cluster_store.Process' + type: object client.JoinRequest: properties: id: @@ -620,6 +702,22 @@ definitions: format: int64 type: integer type: object + github_com_datarhei_core_v16_cluster_store.Process: + properties: + config: + $ref: '#/definitions/app.Config' + createdAt: + type: string + error: + type: string + metadata: + additionalProperties: true + type: object + order: + type: string + updatedAt: + type: string + type: object identity.Auth0Tenant: properties: audience: @@ -944,7 +1042,7 @@ paths: "200": description: OK schema: - type: string + $ref: '#/definitions/client.AboutResponse' "500": description: Internal Server Error schema: @@ -1471,7 +1569,7 @@ paths: "200": description: OK schema: - type: string + $ref: '#/definitions/client.GetProcessResponse' "404": description: Not Found schema: diff --git a/docs/docs.go b/docs/docs.go index 2c69ea84..8f175707 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -5484,6 +5484,39 @@ const docTemplate = `{ } } }, + "api.ClusterNodeGPUResources": { + "type": "object", + "properties": { + "memory_limit_bytes": { + "description": "Defined memory limit in bytes", + "type": "integer" + }, + "memory_total_bytes": { + "description": "Total available memory in bytes", + "type": "integer" + }, + "memory_used_bytes": { + "description": "Currently used memory in bytes", + "type": "integer" + }, + "usage_decoder": { + "description": "Current decoder usage, 0-100", + "type": "number" + }, + "usage_encoder": { + "description": "Current encoder usage, 0-100", + "type": "number" + }, + "usage_general": { + "description": "Current general usage, 0-100", + "type": "number" + }, + "usage_limit": { + "description": "Defined general usage limit, 0-100", + "type": "number" + } + } + }, "api.ClusterNodeID": { "type": "object", "properties": { @@ -5510,6 +5543,13 @@ const docTemplate = `{ "error": { "type": "string" }, + "gpu": { + "description": "GPU resources", + "type": "array", + "items": { + "$ref": "#/definitions/api.ClusterNodeGPUResources" + } + }, "is_throttling": { "type": "boolean" }, diff --git a/docs/swagger.json b/docs/swagger.json index 400e5078..448d8718 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -5477,6 +5477,39 @@ } } }, + "api.ClusterNodeGPUResources": { + "type": "object", + "properties": { + "memory_limit_bytes": { + "description": "Defined memory limit in bytes", + "type": "integer" + }, + "memory_total_bytes": { + "description": "Total available memory in bytes", + "type": "integer" + }, + "memory_used_bytes": { + "description": "Currently used memory in bytes", + "type": "integer" + }, + "usage_decoder": { + "description": "Current decoder usage, 0-100", + "type": "number" + }, + "usage_encoder": { + "description": "Current encoder usage, 0-100", + "type": "number" + }, + "usage_general": { + "description": "Current general usage, 0-100", + "type": "number" + }, + "usage_limit": { + "description": "Defined general usage limit, 0-100", + "type": "number" + } + } + }, "api.ClusterNodeID": { "type": "object", "properties": { @@ -5503,6 +5536,13 @@ "error": { "type": "string" }, + "gpu": { + "description": "GPU resources", + "type": "array", + "items": { + "$ref": "#/definitions/api.ClusterNodeGPUResources" + } + }, "is_throttling": { "type": "boolean" }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 5e12a2a2..5e3730cf 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -283,6 +283,30 @@ definitions: description: unix timestamp type: integer type: object + api.ClusterNodeGPUResources: + properties: + memory_limit_bytes: + description: Defined memory limit in bytes + type: integer + memory_total_bytes: + description: Total available memory in bytes + type: integer + memory_used_bytes: + description: Currently used memory in bytes + type: integer + usage_decoder: + description: Current decoder usage, 0-100 + type: number + usage_encoder: + description: Current encoder usage, 0-100 + type: number + usage_general: + description: Current general usage, 0-100 + type: number + usage_limit: + description: Defined general usage limit, 0-100 + type: number + type: object api.ClusterNodeID: properties: id: @@ -301,6 +325,11 @@ definitions: type: number error: type: string + gpu: + description: GPU resources + items: + $ref: '#/definitions/api.ClusterNodeGPUResources' + type: array is_throttling: type: boolean memory_core_bytes: diff --git a/http/api/cluster.go b/http/api/cluster.go index 1bfb45bc..eeed2b8b 100644 --- a/http/api/cluster.go +++ b/http/api/cluster.go @@ -39,16 +39,27 @@ type ClusterNodeCore struct { } type ClusterNodeResources struct { - IsThrottling bool `json:"is_throttling"` - NCPU float64 `json:"ncpu"` - CPU float64 `json:"cpu_used"` // percent 0-100*npcu - CPULimit float64 `json:"cpu_limit"` // percent 0-100*npcu - CPUCore float64 `json:"cpu_core"` // percent 0-100*ncpu - Mem uint64 `json:"memory_used_bytes"` // bytes - MemLimit uint64 `json:"memory_limit_bytes"` // bytes - MemTotal uint64 `json:"memory_total_bytes"` // bytes - MemCore uint64 `json:"memory_core_bytes"` // bytes - Error string `json:"error"` + IsThrottling bool `json:"is_throttling"` + NCPU float64 `json:"ncpu"` + CPU float64 `json:"cpu_used"` // percent 0-100*npcu + CPULimit float64 `json:"cpu_limit"` // percent 0-100*npcu + CPUCore float64 `json:"cpu_core"` // percent 0-100*ncpu + Mem uint64 `json:"memory_used_bytes"` // bytes + MemLimit uint64 `json:"memory_limit_bytes"` // bytes + MemTotal uint64 `json:"memory_total_bytes"` // bytes + MemCore uint64 `json:"memory_core_bytes"` // bytes + GPU []ClusterNodeGPUResources `json:"gpu"` // GPU resources + Error string `json:"error"` +} + +type ClusterNodeGPUResources struct { + Mem uint64 `json:"memory_used_bytes"` // Currently used memory in bytes + MemLimit uint64 `json:"memory_limit_bytes"` // Defined memory limit in bytes + MemTotal uint64 `json:"memory_total_bytes"` // Total available memory in bytes + Usage float64 `json:"usage_general"` // Current general usage, 0-100 + UsageLimit float64 `json:"usage_limit"` // Defined general usage limit, 0-100 + Encoder float64 `json:"usage_encoder"` // Current encoder usage, 0-100 + Decoder float64 `json:"usage_decoder"` // Current decoder usage, 0-100 } type ClusterRaft struct { diff --git a/http/handler/api/cluster.go b/http/handler/api/cluster.go index 10841647..35185e96 100644 --- a/http/handler/api/cluster.go +++ b/http/handler/api/cluster.go @@ -123,9 +123,22 @@ func (h *ClusterHandler) marshalClusterNode(node cluster.ClusterNode) api.Cluste MemLimit: node.Resources.MemLimit, MemTotal: node.Resources.MemTotal, MemCore: node.Resources.MemCore, + GPU: []api.ClusterNodeGPUResources{}, }, } + for _, gpu := range node.Resources.GPU { + n.Resources.GPU = append(n.Resources.GPU, api.ClusterNodeGPUResources{ + Mem: gpu.Mem, + MemLimit: gpu.MemLimit, + MemTotal: gpu.MemTotal, + Usage: gpu.Usage, + UsageLimit: gpu.UsageLimit, + Encoder: gpu.Encoder, + Decoder: gpu.Decoder, + }) + } + if node.Error != nil { n.Error = node.Error.Error() } From d591a2383eaa2787c92b9cf0fa98478985925dc7 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 31 Oct 2024 14:59:22 +0100 Subject: [PATCH 63/64] Fix GPU index numbering, promote the GPU ID --- internal/mock/psutil/psutil.go | 1 + process/limiter_test.go | 12 +++++------- resources/psutil/psutil.go | 5 ++++- resources/resources.go | 20 +++++++++----------- resources/resources_test.go | 2 ++ 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/internal/mock/psutil/psutil.go b/internal/mock/psutil/psutil.go index c0f37048..e03af2a0 100644 --- a/internal/mock/psutil/psutil.go +++ b/internal/mock/psutil/psutil.go @@ -32,6 +32,7 @@ func New(ngpu int) *MockPSUtil { for i := 0; i < ngpu; i++ { u.GPUInfo = append(u.GPUInfo, psutil.GPUInfo{ Index: i, + ID: "00000000:01:00.0", Name: "L4", MemoryTotal: 24 * 1024 * 1024 * 1024, MemoryUsed: uint64(12+i) * 1024 * 1024 * 1024, diff --git a/process/limiter_test.go b/process/limiter_test.go index 56813958..bcb11045 100644 --- a/process/limiter_test.go +++ b/process/limiter_test.go @@ -22,13 +22,11 @@ func (p *proc) Info() (resources.ProcessInfo, error) { }, Memory: 197, GPU: resources.ProcessInfoGPU{ - Index: 0, - Name: "L4", - MemoryTotal: 128, - MemoryUsed: 91, - Usage: 3, - Encoder: 9, - Decoder: 5, + Index: 0, + MemoryUsed: 91, + Usage: 3, + Encoder: 9, + Decoder: 5, }, } diff --git a/resources/psutil/psutil.go b/resources/psutil/psutil.go index c231792b..6dc13982 100644 --- a/resources/psutil/psutil.go +++ b/resources/psutil/psutil.go @@ -71,6 +71,7 @@ type CPUInfo struct { type GPUInfo struct { Index int // Index of the GPU + ID string // Physical ID of the GPU (not populated for a specific process) Name string // Name of the GPU (not populated for a specific process) MemoryTotal uint64 // bytes (not populated for a specific process) @@ -638,8 +639,10 @@ func (u *util) GPU() ([]GPUInfo, error) { stats := []GPUInfo{} - for _, nv := range nvstats { + for i, nv := range nvstats { stats = append(stats, GPUInfo{ + Index: i, + ID: nv.ID, Name: nv.Name, MemoryTotal: nv.MemoryTotal, MemoryUsed: nv.MemoryUsed, diff --git a/resources/resources.go b/resources/resources.go index 1e322a14..33edd2f2 100644 --- a/resources/resources.go +++ b/resources/resources.go @@ -63,6 +63,7 @@ type GPUInfo struct { type GPUInfoStat struct { Index int + ID string Name string // Memory @@ -566,6 +567,7 @@ func (r *resources) Info() Info { for i, g := range gpustat { gpuinfo.GPU = append(gpuinfo.GPU, GPUInfoStat{ Index: g.Index, + ID: g.ID, Name: g.Name, MemoryTotal: g.MemoryTotal, MemoryUsed: g.MemoryUsed, @@ -666,11 +668,9 @@ type ProcessInfoCPU struct { } type ProcessInfoGPU struct { - Index int // Index of the GPU - Name string // Name of the GPU (not populated for a specific process) + Index int // Index of the GPU - MemoryTotal uint64 // bytes (not populated for a specific process) - MemoryUsed uint64 // bytes + MemoryUsed uint64 // bytes Usage float64 // percent 0-100 Encoder float64 // percent 0-100 @@ -708,13 +708,11 @@ func (p *process) Info() (ProcessInfo, error) { }, Memory: mem, GPU: ProcessInfoGPU{ - Index: gpu.Index, - Name: gpu.Name, - MemoryTotal: gpu.MemoryTotal, - MemoryUsed: gpu.MemoryUsed, - Usage: gpu.Usage, - Encoder: gpu.Encoder, - Decoder: gpu.Decoder, + Index: gpu.Index, + MemoryUsed: gpu.MemoryUsed, + Usage: gpu.Usage, + Encoder: gpu.Encoder, + Decoder: gpu.Decoder, }, } diff --git a/resources/resources_test.go b/resources/resources_test.go index 77bb7116..dba43503 100644 --- a/resources/resources_test.go +++ b/resources/resources_test.go @@ -828,6 +828,7 @@ func TestInfo(t *testing.T) { NGPU: 2, GPU: []GPUInfoStat{{ Index: 0, + ID: "00000000:01:00.0", Name: "L4", MemoryTotal: 24 * 1024 * 1024 * 1024, MemoryUsed: 12 * 1024 * 1024 * 1024, @@ -839,6 +840,7 @@ func TestInfo(t *testing.T) { UsageLimit: 11, }, { Index: 1, + ID: "00000000:01:00.0", Name: "L4", MemoryTotal: 24 * 1024 * 1024 * 1024, MemoryUsed: 13 * 1024 * 1024 * 1024, From abc821fe4b4993ad0c2bb618e548b5a7d6d60b8f Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Thu, 31 Oct 2024 15:23:24 +0100 Subject: [PATCH 64/64] Create GPU index in actual driver --- resources/psutil/gpu/gpu.go | 1 + resources/psutil/gpu/nvidia/nvidia.go | 3 ++- resources/psutil/gpu/nvidia/nvidia_test.go | 2 ++ resources/psutil/psutil.go | 4 ++-- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/resources/psutil/gpu/gpu.go b/resources/psutil/gpu/gpu.go index dc7f5634..dee49f5e 100644 --- a/resources/psutil/gpu/gpu.go +++ b/resources/psutil/gpu/gpu.go @@ -12,6 +12,7 @@ type Process struct { } type Stats struct { + Index int ID string Name string Architecture string diff --git a/resources/psutil/gpu/nvidia/nvidia.go b/resources/psutil/gpu/nvidia/nvidia.go index 2618023a..c46bbce1 100644 --- a/resources/psutil/gpu/nvidia/nvidia.go +++ b/resources/psutil/gpu/nvidia/nvidia.go @@ -444,8 +444,9 @@ func (n *nvidia) Stats() ([]gpu.Stats, error) { return stats, n.err } - for _, nv := range n.stats.GPU { + for i, nv := range n.stats.GPU { s := gpu.Stats{ + Index: i, ID: nv.ID, Name: nv.Name, Architecture: nv.Architecture, diff --git a/resources/psutil/gpu/nvidia/nvidia_test.go b/resources/psutil/gpu/nvidia/nvidia_test.go index cad4d1a0..918d235e 100644 --- a/resources/psutil/gpu/nvidia/nvidia_test.go +++ b/resources/psutil/gpu/nvidia/nvidia_test.go @@ -324,6 +324,7 @@ func TestNvidiaGPUStats(t *testing.T) { require.NoError(t, err) require.Equal(t, []gpu.Stats{ { + Index: 0, ID: "00000000:01:00.0", Name: "NVIDIA L4", Architecture: "Ada Lovelace", @@ -360,6 +361,7 @@ func TestNvidiaGPUStats(t *testing.T) { }, }, { + Index: 1, ID: "00000000:C1:00.0", Name: "NVIDIA L4", Architecture: "Ada Lovelace", diff --git a/resources/psutil/psutil.go b/resources/psutil/psutil.go index 6dc13982..89070762 100644 --- a/resources/psutil/psutil.go +++ b/resources/psutil/psutil.go @@ -639,9 +639,9 @@ func (u *util) GPU() ([]GPUInfo, error) { stats := []GPUInfo{} - for i, nv := range nvstats { + for _, nv := range nvstats { stats = append(stats, GPUInfo{ - Index: i, + Index: nv.Index, ID: nv.ID, Name: nv.Name, MemoryTotal: nv.MemoryTotal,