diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6bcf873..3954c43 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,7 +1,6 @@ { - "image": "ghcr.io/uliegecsm/hwloc-xml-parser/hwloc_xml_parser:latest", + "image": "ghcr.io/uliegecsm/hwloc-xml-parser", "extensions" : [ - "ms-vscode.cmake-tools", "eamodio.gitlens", "mhutchie.git-graph", "ms-azuretools.vscode-docker" diff --git a/.github/workflows/build.image.yml b/.github/workflows/build.image.yml index e5a5e88..9653e1e 100644 --- a/.github/workflows/build.image.yml +++ b/.github/workflows/build.image.yml @@ -6,19 +6,21 @@ on: - main paths: - 'requirements/*' + - 'docker/dockerfile' pull_request: branches: - main paths: - 'requirements/*' + - 'docker/dockerfile' jobs: - set_vars: + set-vars: uses: ./.github/workflows/set-vars.yml build-image: - needs: [set_vars] + needs: [set-vars] runs-on: [ubuntu-latest] container: image: docker:latest @@ -37,7 +39,7 @@ jobs: - name: Login to GitHub Container Registry. uses: docker/login-action@v3 with: - registry: ${{ needs.set_vars.outputs.CI_REGISTRY }} + registry: ${{ needs.set-vars.outputs.CI_REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} @@ -45,10 +47,10 @@ jobs: uses: docker/build-push-action@v5 with: context: . - platforms: linux/amd64,linux/arm64 - push: true #${{ github.ref == 'refs/heads/main' }} - file: docker/dockerfile - tags: '${{ needs.set_vars.outputs.CI_IMAGE }}:latest' - cache-from: type=registry,ref='${{ needs.set_vars.outputs.CI_IMAGE }}:latest' - cache-to: type=inline - labels: "org.opencontainers.image.source=${{ github.repositoryUrl }}" + platforms: linux/amd64,linux/arm64 + push: ${{ github.ref == 'refs/heads/main' }} + file: docker/dockerfile + tags: ${{ needs.set-vars.outputs.CI_IMAGE }} + cache-from: type=registry,ref=${{ needs.set-vars.outputs.CI_IMAGE }} + cache-to: type=inline + labels: "org.opencontainers.image.source=${{ github.repositoryUrl }}" diff --git a/.github/workflows/set-vars.yml b/.github/workflows/set-vars.yml index fd4a7c6..bd930b3 100644 --- a/.github/workflows/set-vars.yml +++ b/.github/workflows/set-vars.yml @@ -2,16 +2,16 @@ on: workflow_call: outputs: CI_IMAGE: - value: ${{ jobs.set_vars.outputs.CI_IMAGE }} + value: ${{ jobs.set-vars.outputs.CI_IMAGE }} CI_REGISTRY: - value: ${{ jobs.set_vars.outputs.CI_REGISTRY }} + value: ${{ jobs.set-vars.outputs.CI_REGISTRY }} env: REGISTRY: ghcr.io jobs: - set_vars: + set-vars: runs-on: [ubuntu-latest] outputs: CI_IMAGE : ${{ steps.common.outputs.CI_IMAGE }} @@ -20,5 +20,5 @@ jobs: - name: Export common variables. id : common run : | - echo "CI_IMAGE=${{ env.REGISTRY }}/${{ github.repository }}/hwloc_xml_parser" >> $GITHUB_OUTPUT - echo "CI_REGISTRY=${{ env.REGISTRY }}" >> $GITHUB_OUTPUT + echo "CI_IMAGE=${{ env.REGISTRY }}/${{ github.repository }}" >> $GITHUB_OUTPUT + echo "CI_REGISTRY=${{ env.REGISTRY }}" >> $GITHUB_OUTPUT diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8d2e138..8f2ccea 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,14 +9,14 @@ on: - main jobs: - set_vars: + set-vars: uses: ./.github/workflows/set-vars.yml test: - needs: [set_vars] + needs: [set-vars] runs-on: [ubuntu-latest] container: - image: '${{ needs.set_vars.outputs.CI_IMAGE }}:latest' + image: ${{ needs.set-vars.outputs.CI_IMAGE }} steps: - name: Checkout code. uses: actions/checkout@v4 @@ -26,10 +26,10 @@ jobs: python -m pytest tests/test_topology.py install-as-package-and-test: - needs: [set_vars] + needs: [set-vars] runs-on: [ubuntu-latest] container: - image: '${{ needs.set_vars.outputs.CI_IMAGE }}:latest' + image: ${{ needs.set-vars.outputs.CI_IMAGE }} steps: - name: Install as package. run : | diff --git a/hwloc_xml_parser/topology.py b/hwloc_xml_parser/topology.py index 70af08c..ad19d1b 100644 --- a/hwloc_xml_parser/topology.py +++ b/hwloc_xml_parser/topology.py @@ -90,7 +90,7 @@ def get_num_pus(self) -> int: @typeguard.typechecked def all_equal_num_pus_per_core(self) -> bool: """ - Returns True if all cores have the same number of processing units. + Returns `True` if all cores have the same number of processing units. """ return all([core.get_num_pus() == self.cores[0].get_num_pus() for core in self.cores]) @@ -226,6 +226,6 @@ def get_num_pus(self) -> int: @typeguard.typechecked def all_equal_num_pus_per_core(self) -> bool: """ - Returns True if all cores have the same number of processing units. + Returns `True` if all cores have the same number of processing units. """ return all([package.all_equal_num_pus_per_core() for package in self.packages]) diff --git a/tests/data/single-apple-m2.xml b/tests/data/single-apple-m2.xml new file mode 100644 index 0000000..051df53 --- /dev/null +++ b/tests/data/single-apple-m2.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/single-nvidia-jetson-xavier-agx.xml b/tests/data/single-nvidia-jetson-xavier-agx.xml new file mode 100644 index 0000000..253f61e --- /dev/null +++ b/tests/data/single-nvidia-jetson-xavier-agx.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test_topology.py b/tests/test_topology.py index 9301a33..e656d93 100644 --- a/tests/test_topology.py +++ b/tests/test_topology.py @@ -12,7 +12,7 @@ class TestSystemTopology: def test_parse_single_intel_core_i7_4790(self): """ The test reads an `xml` file with the output of `lstopo-no-graphics` - for a single Intel Core i7 4790 machine with the following topology: + for a single `Intel Core i7 4790` machine with the following topology: .. code-block:: python @@ -69,41 +69,49 @@ def test_parse_single_intel_core_i7_4790(self): assert st.get_num_packages() == 1 # The package has 4 cores. - assert len(st.packages[0].cores) == 4 - assert st.packages[0].get_num_cores() == 4 + package = st.packages[0] + + assert len(package.cores) == 4 + assert package.get_num_cores() == 4 # The first core of the package has 2 PUs. - assert len(st.packages[0].cores[0].pus) == 2 - assert st.packages[0].cores[0].get_num_pus() == 2 + assert len(package.cores[0].pus) == 2 + assert package.cores[0].get_num_pus() == 2 # The first PU of the second core of the package has OS index 1 and logical index 2. - assert st.packages[0].cores[1].pus[0].os_index == 1 - assert st.packages[0].cores[1].pus[0].logical_index == 2 + assert package.cores[1].pus[0].os_index == 1 + assert package.cores[1].pus[0].logical_index == 2 + # Repeat the last assertions, but using this time the generator returned by the + # method :py:meth:`hwloc_xml_parser.topology.SystemTopology.recurse_cores`. gen_cores = st.recurse_cores() _ = next(gen_cores) second_core = next(gen_cores) + assert second_core.pus[0].os_index == 1 assert second_core.pus[0].logical_index == 2 + # Repeat the last assertions, but using this time the generator returned by the + # method :py:meth:`hwloc_xml_parser.topology.SystemTopology.recurse_pus`. gen_pus = st.recurse_pus() _ = next(gen_pus) _ = next(gen_pus) third_pu = next(gen_pus) + assert third_pu.os_index == 1 assert third_pu.logical_index == 2 # The second PU of the second core of the package has OS index 5 and logical index 3. - assert st.packages[0].cores[1].pus[1].os_index == 5 - assert st.packages[0].cores[1].pus[1].logical_index == 3 + assert package.cores[1].pus[1].os_index == 5 + assert package.cores[1].pus[1].logical_index == 3 # All cores of the package have 2 PUs. - assert st.packages[0].all_equal_num_pus_per_core() + assert package.all_equal_num_pus_per_core() # The machine has 8 PUs in total. assert st.get_num_cores() == 4 # The machine has 8 PUs in total. assert st.get_num_pus() == 8 - assert st.packages[0].get_num_pus() == 8 + assert package.get_num_pus() == 8 # All cores of the machine have the same number of PUs. assert st.all_equal_num_pus_per_core() @@ -111,7 +119,7 @@ def test_parse_single_intel_core_i7_4790(self): def test_parse_dual_intel_xeon_gold_6126(self): """ The test reads an `xml` file with the output of `lstopo-no-graphics` - for a dual Intel Xeon Gold 6126 machine with the following topology: + for a dual `Intel Xeon Gold 6126` machine with the following topology: .. code-block:: python @@ -232,15 +240,17 @@ def test_parse_dual_intel_xeon_gold_6126(self): assert st.get_num_packages() == 2 # The first package has 12 cores. - assert len(st.packages[0].cores) == 12 - assert st.packages[0].get_num_cores() == 12 + first_package = st.packages[0] + + assert len(first_package.cores) == 12 + assert first_package.get_num_cores() == 12 # The first core of the first package has 2 PUs. - assert len(st.packages[0].cores[0].pus) == 2 - assert st.packages[0].cores[0].get_num_pus() == 2 + assert len(first_package.cores[0].pus) == 2 + assert first_package.cores[0].get_num_pus() == 2 # All cores of the first package have 2 PUs. - assert st.packages[0].all_equal_num_pus_per_core() + assert first_package.all_equal_num_pus_per_core() # The machine has 24 cores in total. assert st.get_num_cores() == 24 @@ -250,3 +260,67 @@ def test_parse_dual_intel_xeon_gold_6126(self): # All cores of the machine have the same number of PUs. assert st.all_equal_num_pus_per_core() + + def test_parse_single_nvidia_jetson_xavier_agx(self): + """ + The test reads an `xml` file with the output of `lstopo-no-graphics` + for a single `Nvidia Jetson Xavier AGX` machine. + """ + hwloc_calc_values = [ + b'0,1,2,3', + b'0,1,2,3,4,5,6,7', + b'0,1,2,3,4,5,6,7' + ] + + with unittest.mock.patch( + target = 'subprocess.check_output', + side_effect = hwloc_calc_values, + ): + st = SystemTopology(load = False) + st._parse(filename = 'tests/data/single-nvidia-jetson-xavier-agx.xml') + + # There are 4 packages. + # This is how `hwloc` reports it. Physically, there is a single package, with 4 clusters of 2 cores each. + # + # See also: + # - https://www.anandtech.com/show/13584/nvidia-xavier-agx-hands-on-carmel-and-more + assert st.get_num_packages() == 4 + + # The machine has 8 cores in total. + assert st.get_num_cores() == 8 + + # The machine has 8 PUs in total. + assert st.get_num_pus() == 8 + + # All cores of the machine have the same number of PUs. + assert st.all_equal_num_pus_per_core() + + def test_parse_single_apple_m2(self): + """ + The test reads an `xml` file with the output of `lstopo-no-graphics` + for a single `Apple M2` machine. + """ + hwloc_calc_values = [ + b'0', + b'0,1,2,3,4,5,6,7', + b'0,1,2,3,4,5,6,7' + ] + + with unittest.mock.patch( + target = 'subprocess.check_output', + side_effect = hwloc_calc_values, + ): + st = SystemTopology(load = False) + st._parse(filename = 'tests/data/single-apple-m2.xml') + + # The machine has 1 package. + assert st.get_num_packages() == 1 + + # The machine has 8 cores in total. + assert st.get_num_cores() == 8 + + # The machine has 8 PUs in total. + assert st.get_num_pus() == 8 + + # All cores of the machine have the same number of PUs. + assert st.all_equal_num_pus_per_core()