From 352bc16ba56605f58d74b25eb38b124ec44b5c16 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Mon, 28 Sep 2026 18:50:42 +0400 Subject: [PATCH] feat(project-setup): add project structure and pyproject.toml --- data/samples/scan_latency_job0.csv | 67 ++++++++++++++++++++ data/samples/scan_latency_job0_latency.png | Bin 0 -> 57183 bytes data/samples/scan_latency_job1.csv | 68 +++++++++++++++++++++ pyproject.toml | 53 ++++++++++++++++ src/disk_checker/__init__.py | 3 + src/disk_checker/core/__init__.py | 14 +++++ src/disk_checker/plotting/__init__.py | 5 ++ src/disk_checker/web/__init__.py | 1 + tests/__init__.py | 1 + 9 files changed, 212 insertions(+) create mode 100644 data/samples/scan_latency_job0.csv create mode 100644 data/samples/scan_latency_job0_latency.png create mode 100644 data/samples/scan_latency_job1.csv create mode 100644 pyproject.toml create mode 100644 src/disk_checker/__init__.py create mode 100644 src/disk_checker/core/__init__.py create mode 100644 src/disk_checker/plotting/__init__.py create mode 100644 src/disk_checker/web/__init__.py create mode 100644 tests/__init__.py diff --git a/data/samples/scan_latency_job0.csv b/data/samples/scan_latency_job0.csv new file mode 100644 index 0000000..9c9826d --- /dev/null +++ b/data/samples/scan_latency_job0.csv @@ -0,0 +1,67 @@ +nsec, read_slat_ns_count, read_slat_ns_cumulative, read_slat_ns_percentile, read_clat_ns_count, read_clat_ns_cumulative, read_clat_ns_percentile, read_lat_ns_count, read_lat_ns_cumulative, read_lat_ns_percentile, write_slat_ns_count, write_slat_ns_cumulative, write_slat_ns_percentile, write_clat_ns_count, write_clat_ns_cumulative, write_clat_ns_percentile, write_lat_ns_count, write_lat_ns_cumulative, write_lat_ns_percentile, trim_slat_ns_count, trim_slat_ns_cumulative, trim_slat_ns_percentile, trim_clat_ns_count, trim_clat_ns_cumulative, trim_clat_ns_percentile, trim_lat_ns_count, trim_lat_ns_cumulative, trim_lat_ns_percentile, +19791872, , , , 1, 1, 0.00016775708773695687, , , , , , , , , , , , , , , , , , , , , , +22413312, , , , 4, 5, 0.0008387854386847844, , , , , , , , , , , , , , , , , , , , , , +22675456, , , , 9, 14, 0.0023485992283173965, , , , , , , , , , , , , , , , , , , , , , +22937600, , , , 2, 16, 0.00268411340379131, , , , , , , , , , , , , , , , , , , , , , +23461888, , , , 2, 18, 0.003019627579265224, , , , , , , , , , , , , , , , , , , , , , +23724032, , , , 7, 25, 0.004193927193423922, , , , , , , , , , , , , , , , , , , , , , +23986176, , , , 14, 39, 0.006542526421741319, , , , , , , , , , , , , , , , , , , , , , +24248320, , , , 1, 40, 0.006710283509478276, , , , , , , , , , , , , , , , , , , , , , +24772608, , , , 2, 42, 0.007045797684952189, , , , , , , , , , , , , , , , , , , , , , +25034752, , , , 2, 44, 0.007381311860426103, , , , , , , , , , , , , , , , , , , , , , +25296896, , , , 4, 48, 0.00805234021137393, , , , , , , , , , , , , , , , , , , , , , +25559040, , , , 8, 56, 0.009394396913269586, , , , , , , , , , , , , , , , , , , , , , +25821184, , , , 9, 65, 0.010904210702902198, , , , , , , , , , , , , , , , , , , , , , +26083328, , , , 9, 74, 0.012414024492534809, , , , , , , , , , , , , , , , , , , , , , +26345472, , , , 16, 90, 0.01509813789632612, , , , , , , , , , , , , , , , , , , , , , +26607616, , , , 10, 100, 0.016775708773695688, , , , , , , , , , , , , , , , , , , , , , +26869760, , , , 34, 134, 0.022479449756752222, , , , , , , , , , , , , , , , , , , , , , +27131904, , , , 38, 172, 0.028854219090756583, , , , , , , , , , , , , , , , , , , , , , +27394048, , , , 49, 221, 0.037074316389867475, , , , , , , , , , , , , , , , , , , , , , +27656192, , , , 57, 278, 0.04663647039087401, , , , , , , , , , , , , , , , , , , , , , +27918336, , , , 90, 368, 0.061734608287200135, , , , , , , , , , , , , , , , , , , , , , +28180480, , , , 91, 459, 0.0770005032712632, , , , , , , , , , , , , , , , , , , , , , +28442624, , , , 105, 564, 0.09461499748364369, , , , , , , , , , , , , , , , , , , , , , +28704768, , , , 149, 713, 0.11961080355645026, , , , , , , , , , , , , , , , , , , , , , +28966912, , , , 152, 865, 0.1451098808924677, , , , , , , , , , , , , , , , , , , , , , +29229056, , , , 170, 1035, 0.17362858580775037, , , , , , , , , , , , , , , , , , , , , , +29491200, , , , 183, 1218, 0.20432813286361348, , , , , , , , , , , , , , , , , , , , , , +29753344, , , , 171, 1389, 0.23301459486663312, , , , , , , , , , , , , , , , , , , , , , +30015488, , , , 174, 1563, 0.2622043281328636, , , , , , , , , , , , , , , , , , , , , , +30277632, , , , 187, 1750, 0.2935749035396745, , , , , , , , , , , , , , , , , , , , , , +30539776, , , , 158, 1908, 0.32008052340211374, , , , , , , , , , , , , , , , , , , , , , +30801920, , , , 154, 2062, 0.3459151149136051, , , , , , , , , , , , , , , , , , , , , , +31064064, , , , 167, 2229, 0.3739305485656769, , , , , , , , , , , , , , , , , , , , , , +31326208, , , , 174, 2403, 0.4031202818319074, , , , , , , , , , , , , , , , , , , , , , +31588352, , , , 175, 2578, 0.43247777218587485, , , , , , , , , , , , , , , , , , , , , , +31850496, , , , 174, 2752, 0.46166750545210533, , , , , , , , , , , , , , , , , , , , , , +32112640, , , , 162, 2914, 0.4888441536654924, , , , , , , , , , , , , , , , , , , , , , +32374784, , , , 162, 3076, 0.5160208018788793, , , , , , , , , , , , , , , , , , , , , , +32636928, , , , 209, 3285, 0.5510820332159033, , , , , , , , , , , , , , , , , , , , , , +32899072, , , , 168, 3453, 0.5792652239557121, , , , , , , , , , , , , , , , , , , , , , +33161216, , , , 188, 3641, 0.6108035564502601, , , , , , , , , , , , , , , , , , , , , , +33423360, , , , 162, 3803, 0.637980204663647, , , , , , , , , , , , , , , , , , , , , , +33816576, , , , 324, 4127, 0.692333501090421, , , , , , , , , , , , , , , , , , , , , , +34340864, , , , 352, 4479, 0.7513839959738299, , , , , , , , , , , , , , , , , , , , , , +34865152, , , , 315, 4794, 0.8042274786109713, , , , , , , , , , , , , , , , , , , , , , +35389440, , , , 291, 5085, 0.8530447911424258, , , , , , , , , , , , , , , , , , , , , , +35913728, , , , 259, 5344, 0.8964938768662976, , , , , , , , , , , , , , , , , , , , , , +36438016, , , , 157, 5501, 0.9228317396409998, , , , , , , , , , , , , , , , , , , , , , +36962304, , , , 98, 5599, 0.9392719342392216, , , , , , , , , , , , , , , , , , , , , , +37486592, , , , 75, 5674, 0.9518537158194934, , , , , , , , , , , , , , , , , , , , , , +38010880, , , , 26, 5700, 0.9562154001006542, , , , , , , , , , , , , , , , , , , , , , +38535168, , , , 22, 5722, 0.9599060560308673, , , , , , , , , , , , , , , , , , , , , , +39059456, , , , 25, 5747, 0.9640999832242912, , , , , , , , , , , , , , , , , , , , , , +39583744, , , , 17, 5764, 0.9669518537158195, , , , , , , , , , , , , , , , , , , , , , +40108032, , , , 26, 5790, 0.9713135379969804, , , , , , , , , , , , , , , , , , , , , , +40632320, , , , 23, 5813, 0.9751719510149304, , , , , , , , , , , , , , , , , , , , , , +41156608, , , , 18, 5831, 0.9781915785941956, , , , , , , , , , , , , , , , , , , , , , +41680896, , , , 21, 5852, 0.9817144774366717, , , , , , , , , , , , , , , , , , , , , , +42205184, , , , 22, 5874, 0.9854051333668847, , , , , , , , , , , , , , , , , , , , , , +42729472, , , , 15, 5889, 0.9879214896829391, , , , , , , , , , , , , , , , , , , , , , +43253760, , , , 18, 5907, 0.9909411172622044, , , , , , , , , , , , , , , , , , , , , , +43778048, , , , 25, 5932, 0.9951350444556283, , , , , , , , , , , , , , , , , , , , , , +44302336, , , , 15, 5947, 0.9976514007716826, , , , , , , , , , , , , , , , , , , , , , +44826624, , , , 8, 5955, 0.9989934574735783, , , , , , , , , , , , , , , , , , , , , , +45350912, , , , 3, 5958, 0.9994967287367891, , , , , , , , , , , , , , , , , , , , , , +45875200, , , , 3, 5961, 1.0, , , , , , , , , , , , , , , , , , , , , , diff --git a/data/samples/scan_latency_job0_latency.png b/data/samples/scan_latency_job0_latency.png new file mode 100644 index 0000000000000000000000000000000000000000..29b8668ec116cee5326743ad3822d1e5c19eb389 GIT binary patch literal 57183 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 literal 0 HcmV?d00001 diff --git a/data/samples/scan_latency_job1.csv b/data/samples/scan_latency_job1.csv new file mode 100644 index 0000000..44c87ae --- /dev/null +++ b/data/samples/scan_latency_job1.csv @@ -0,0 +1,68 @@ +nsec, read_slat_ns_count, read_slat_ns_cumulative, read_slat_ns_percentile, read_clat_ns_count, read_clat_ns_cumulative, read_clat_ns_percentile, read_lat_ns_count, read_lat_ns_cumulative, read_lat_ns_percentile, write_slat_ns_count, write_slat_ns_cumulative, write_slat_ns_percentile, write_clat_ns_count, write_clat_ns_cumulative, write_clat_ns_percentile, write_lat_ns_count, write_lat_ns_cumulative, write_lat_ns_percentile, trim_slat_ns_count, trim_slat_ns_cumulative, trim_slat_ns_percentile, trim_clat_ns_count, trim_clat_ns_cumulative, trim_clat_ns_percentile, trim_lat_ns_count, trim_lat_ns_cumulative, trim_lat_ns_percentile, +22151168, , , , 3, 3, 0.00025161452654533256, , , , , , , , , , , , , , , , , , , , , , +22413312, , , , 4, 7, 0.0005871005619391093, , , , , , , , , , , , , , , , , , , , , , +22675456, , , , 8, 15, 0.0012580726327266628, , , , , , , , , , , , , , , , , , , , , , +22937600, , , , 5, 20, 0.0016774301769688837, , , , , , , , , , , , , , , , , , , , , , +23461888, , , , 2, 22, 0.001845173194665772, , , , , , , , , , , , , , , , , , , , , , +23724032, , , , 5, 27, 0.002264530738907993, , , , , , , , , , , , , , , , , , , , , , +23986176, , , , 7, 34, 0.0028516313008471023, , , , , , , , , , , , , , , , , , , , , , +25034752, , , , 3, 37, 0.0031032458273924346, , , , , , , , , , , , , , , , , , , , , , +25296896, , , , 1, 38, 0.003187117336240879, , , , , , , , , , , , , , , , , , , , , , +25559040, , , , 4, 42, 0.003522603371634656, , , , , , , , , , , , , , , , , , , , , , +25821184, , , , 2, 44, 0.003690346389331544, , , , , , , , , , , , , , , , , , , , , , +26083328, , , , 10, 54, 0.004529061477815986, , , , , , , , , , , , , , , , , , , , , , +26345472, , , , 15, 69, 0.005787134110542649, , , , , , , , , , , , , , , , , , , , , , +26607616, , , , 22, 91, 0.007632307305208421, , , , , , , , , , , , , , , , , , , , , , +26869760, , , , 19, 110, 0.00922586597332886, , , , , , , , , , , , , , , , , , , , , , +27131904, , , , 35, 145, 0.012161368783024406, , , , , , , , , , , , , , , , , , , , , , +27394048, , , , 60, 205, 0.017193659313931058, , , , , , , , , , , , , , , , , , , , , , +27656192, , , , 53, 258, 0.0216388492828986, , , , , , , , , , , , , , , , , , , , , , +27918336, , , , 78, 336, 0.028180826973077246, , , , , , , , , , , , , , , , , , , , , , +28180480, , , , 90, 426, 0.03572926276943722, , , , , , , , , , , , , , , , , , , , , , +28442624, , , , 128, 554, 0.046464815902038076, , , , , , , , , , , , , , , , , , , , , , +28704768, , , , 174, 728, 0.061058458441667365, , , , , , , , , , , , , , , , , , , , , , +28966912, , , , 209, 937, 0.0785876037909922, , , , , , , , , , , , , , , , , , , , , , +29229056, , , , 222, 1159, 0.09720707875534682, , , , , , , , , , , , , , , , , , , , , , +29491200, , , , 250, 1409, 0.11817495596745785, , , , , , , , , , , , , , , , , , , , , , +29753344, , , , 310, 1719, 0.14417512371047556, , , , , , , , , , , , , , , , , , , , , , +30015488, , , , 289, 2008, 0.1684139897676759, , , , , , , , , , , , , , , , , , , , , , +30277632, , , , 325, 2333, 0.19567223014342028, , , , , , , , , , , , , , , , , , , , , , +30539776, , , , 341, 2674, 0.22427241466073974, , , , , , , , , , , , , , , , , , , , , , +30801920, , , , 338, 3012, 0.2526209846515139, , , , , , , , , , , , , , , , , , , , , , +31064064, , , , 352, 3364, 0.28214375576616624, , , , , , , , , , , , , , , , , , , , , , +31326208, , , , 368, 3732, 0.3130084710223937, , , , , , , , , , , , , , , , , , , , , , +31588352, , , , 310, 4042, 0.3390086387654114, , , , , , , , , , , , , , , , , , , , , , +31850496, , , , 314, 4356, 0.3653442925438229, , , , , , , , , , , , , , , , , , , , , , +32112640, , , , 370, 4726, 0.3963767508177472, , , , , , , , , , , , , , , , , , , , , , +32374784, , , , 377, 5103, 0.4279963096536107, , , , , , , , , , , , , , , , , , , , , , +32636928, , , , 346, 5449, 0.45701585171517234, , , , , , , , , , , , , , , , , , , , , , +32899072, , , , 349, 5798, 0.48628700830327937, , , , , , , , , , , , , , , , , , , , , , +33161216, , , , 334, 6132, 0.5143000922586597, , , , , , , , , , , , , , , , , , , , , , +33423360, , , , 313, 6445, 0.5405518745282227, , , , , , , , , , , , , , , , , , , , , , +33816576, , , , 740, 7185, 0.6026167910760715, , , , , , , , , , , , , , , , , , , , , , +34340864, , , , 724, 7909, 0.663339763482345, , , , , , , , , , , , , , , , , , , , , , +34865152, , , , 653, 8562, 0.7181078587603791, , , , , , , , , , , , , , , , , , , , , , +35389440, , , , 666, 9228, 0.7739662836534429, , , , , , , , , , , , , , , , , , , , , , +35913728, , , , 584, 9812, 0.8229472448209343, , , , , , , , , , , , , , , , , , , , , , +36438016, , , , 499, 10311, 0.864799127736308, , , , , , , , , , , , , , , , , , , , , , +36962304, , , , 399, 10710, 0.8982638597668372, , , , , , , , , , , , , , , , , , , , , , +37486592, , , , 298, 11008, 0.9232575694036735, , , , , , , , , , , , , , , , , , , , , , +38010880, , , , 192, 11200, 0.9393608991025748, , , , , , , , , , , , , , , , , , , , , , +38535168, , , , 108, 11308, 0.9484190220582068, , , , , , , , , , , , , , , , , , , , , , +39059456, , , , 71, 11379, 0.9543738991864463, , , , , , , , , , , , , , , , , , , , , , +39583744, , , , 57, 11436, 0.9591545751908077, , , , , , , , , , , , , , , , , , , , , , +40108032, , , , 47, 11483, 0.9630965361066846, , , , , , , , , , , , , , , , , , , , , , +40632320, , , , 46, 11529, 0.966954625513713, , , , , , , , , , , , , , , , , , , , , , +41156608, , , , 42, 11571, 0.9704772288853476, , , , , , , , , , , , , , , , , , , , , , +41680896, , , , 38, 11609, 0.9736643462215885, , , , , , , , , , , , , , , , , , , , , , +42205184, , , , 38, 11647, 0.9768514635578294, , , , , , , , , , , , , , , , , , , , , , +42729472, , , , 47, 11694, 0.9807934244737063, , , , , , , , , , , , , , , , , , , , , , +43253760, , , , 37, 11731, 0.9838966703010987, , , , , , , , , , , , , , , , , , , , , , +43778048, , , , 51, 11782, 0.9881741172523694, , , , , , , , , , , , , , , , , , , , , , +44302336, , , , 40, 11822, 0.9915289776063071, , , , , , , , , , , , , , , , , , , , , , +44826624, , , , 41, 11863, 0.9949677094690933, , , , , , , , , , , , , , , , , , , , , , +45350912, , , , 29, 11892, 0.9973999832256982, , , , , , , , , , , , , , , , , , , , , , +45875200, , , , 16, 11908, 0.9987419273672733, , , , , , , , , , , , , , , , , , , , , , +46399488, , , , 10, 11918, 0.9995806424557577, , , , , , , , , , , , , , , , , , , , , , +46923776, , , , 4, 11922, 0.9999161284911515, , , , , , , , , , , , , , , , , , , , , , +73924608, , , , 1, 11923, 1.0, , , , , , , , , , , , , , , , , , , , , , diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..acb03f9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,53 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "disk-checker" +version = "0.1.0" +description = "Disk benchmarking and latency visualization tool" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "pandas>=2.0", + "numpy>=1.24", + "matplotlib>=3.7", + "fastapi>=0.100", + "uvicorn[standard]>=0.23", + "python-multipart>=0.0.6", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.0", + "pytest-asyncio>=0.21", + "pytest-cov>=4.0", + "black>=23.0", + "ruff>=0.1.0", + "mypy>=1.0", +] + +[project.scripts] +disk-checker = "disk_checker.scripts.cli:main" +disk-checker-serve = "disk_checker.scripts.serve:main" + +[tool.hatch.build.targets.wheel] +packages = ["src/disk_checker"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +python_functions = ["test_*"] + +[tool.black] +line-length = 100 +target-version = ["py310"] + +[tool.ruff] +line-length = 100 +target-version = "py310" + +[tool.mypy] +python_version = "3.10" +warn_return_any = true +warn_unused_configs = true \ No newline at end of file diff --git a/src/disk_checker/__init__.py b/src/disk_checker/__init__.py new file mode 100644 index 0000000..3b76229 --- /dev/null +++ b/src/disk_checker/__init__.py @@ -0,0 +1,3 @@ +"""Disk Checker - Disk benchmarking and latency visualization tool.""" + +__version__ = "0.1.0" \ No newline at end of file diff --git a/src/disk_checker/core/__init__.py b/src/disk_checker/core/__init__.py new file mode 100644 index 0000000..90ca7a2 --- /dev/null +++ b/src/disk_checker/core/__init__.py @@ -0,0 +1,14 @@ +"""Core business logic module.""" + +from .extractor import load_latency_csv +from .converter import to_unit, UNIT_FACTORS, UNIT_LABELS +from .helpers import human_count, percentile_table + +__all__ = [ + "load_latency_csv", + "to_unit", + "UNIT_FACTORS", + "UNIT_LABELS", + "human_count", + "percentile_table", +] \ No newline at end of file diff --git a/src/disk_checker/plotting/__init__.py b/src/disk_checker/plotting/__init__.py new file mode 100644 index 0000000..0718ebf --- /dev/null +++ b/src/disk_checker/plotting/__init__.py @@ -0,0 +1,5 @@ +"""Plotting module for disk latency visualization.""" + +from .plots import draw_histogram, draw_cdf, generate_plot + +__all__ = ["draw_histogram", "draw_cdf", "generate_plot"] \ No newline at end of file diff --git a/src/disk_checker/web/__init__.py b/src/disk_checker/web/__init__.py new file mode 100644 index 0000000..e814ef1 --- /dev/null +++ b/src/disk_checker/web/__init__.py @@ -0,0 +1 @@ +"""Web backend module for disk-checker API.""" \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..ca3d1c4 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for disk-checker.""" \ No newline at end of file