Skip to content

feat: Logging license type usage - #268

Open
dominicprice-lowrisc wants to merge 6 commits into
lowRISC:masterfrom
dominicprice-lowrisc:logging-license-type-usage
Open

dominicprice-lowrisc wants to merge 6 commits into
lowRISC:masterfrom
dominicprice-lowrisc:logging-license-type-usage

Conversation

@dominicprice-lowrisc

Copy link
Copy Markdown

Description

This PR addresses #267 by adding additional logging at the debug level. There is already logging for when a job has changed status (e.g. from scheduled to queued), and now a table is also logged showing per tool, the number of jobs with each status. I have attached an image and text copy of how these logs look when run in the OpenTitan repository. I have also added a test case to intercept the logs and check that each of the simulation tools is present in the logs at the debug level.

The way I implemented this was to create an index in the ResourceManager to track job statuses per tool, and register callbacks in the Scheduler to log this information whenever a job changes status. I also modified tool_meta_factory in the tests file to randomly select real simulation tools names, rather than just using "test_tool" by default.

(The exact command I used to generate the attached screenshot was uv run --no-sync dvsim hw/top_earlgrey/dv/top_earlgrey_sim_cfgs.hjson -i smoke --scratch ~/scratch --fixed-seed 1 --verbose=debug --cov -R A=20, with my fork of DVSim installed).

dvsim_logs_cropped
[D 261005 10:18:38 backend:101] Job cover_reg_top completed execution: P
[V 261005 10:18:38 core:286] Status change to [P: Passed] for csrng:cover_reg_top
[D 261005 10:18:38 resources:134] [Tool: vcs            ] [ S:   181, Q:    51, R:    12, P:     0, F:     0, K:     0 ]
[D 261005 10:18:38 resources:134] [Tool: xcelium        ] [ S:    46, Q:    14, R:     3, P:     1, F:     0, K:     0 ]
[V 261005 10:18:38 core:286] Status change to [Q: Queued] for csrng:0.csrng_csr_hw_reset.1
[D 261005 10:18:38 resources:134] [Tool: vcs            ] [ S:   181, Q:    51, R:    12, P:     0, F:     0, K:     0 ]
[D 261005 10:18:38 resources:134] [Tool: xcelium        ] [ S:    45, Q:    15, R:     3, P:     1, F:     0, K:     0 ]
[V 261005 10:18:38 core:286] Status change to [Q: Queued] for csrng:0.csrng_csr_rw.1
[D 261005 10:18:38 resources:134] [Tool: vcs            ] [ S:   181, Q:    51, R:    12, P:     0, F:     0, K:     0 ]
[D 261005 10:18:38 resources:134] [Tool: xcelium        ] [ S:    44, Q:    16, R:     3, P:     1, F:     0, K:     0 ]
[V 261005 10:18:38 logging:67] [local]: Dispatching jobs: hmac:cover_reg_top
[D 261005 10:18:38 resources:134] [Tool: vcs            ] [ S:   181, Q:    50, R:    13, P:     0, F:     0, K:     0 ]
[D 261005 10:18:38 resources:134] [Tool: xcelium        ] [ S:    44, Q:    16, R:     3, P:     1, F:     0, K:     0 ]
[D 261005 10:18:39 backend:101] Job cover_reg_top completed execution: P
[V 261005 10:18:39 core:286] Status change to [P: Passed] for aes_unmasked:cover_reg_top
[D 261005 10:18:39 resources:134] [Tool: vcs            ] [ S:   181, Q:    50, R:    13, P:     0, F:     0, K:     0 ]
[D 261005 10:18:39 resources:134] [Tool: xcelium        ] [ S:    44, Q:    16, R:     2, P:     2, F:     0, K:     0 ]
[V 261005 10:18:39 core:286] Status change to [Q: Queued] for aes_unmasked:0.aes_csr_rw.1
[D 261005 10:18:39 resources:134] [Tool: vcs            ] [ S:   181, Q:    50, R:    13, P:     0, F:     0, K:     0 ]
[D 261005 10:18:39 resources:134] [Tool: xcelium        ] [ S:    43, Q:    17, R:     2, P:     2, F:     0, K:     0 ]
[V 261005 10:18:39 core:286] Status change to [Q: Queued] for aes_unmasked:0.aes_csr_hw_reset.1
[D 261005 10:18:39 resources:134] [Tool: vcs            ] [ S:   181, Q:    50, R:    13, P:     0, F:     0, K:     0 ]
[D 261005 10:18:39 resources:134] [Tool: xcelium        ] [ S:    42, Q:    18, R:     2, P:     2, F:     0, K:     0 ]
[V 261005 10:18:39 logging:67] [local]: Dispatching jobs: i2c:cover_reg_top
[D 261005 10:18:39 resources:134] [Tool: vcs            ] [ S:   181, Q:    49, R:    14, P:     0, F:     0, K:     0 ]
[D 261005 10:18:39 resources:134] [Tool: xcelium        ] [ S:    42, Q:    18, R:     2, P:     2, F:     0, K:     0 ]
[D 261005 10:18:39 backend:101] Job cover_reg_top completed execution: P
[V 261005 10:18:39 core:286] Status change to [P: Passed] for entropy_src_rng_4bits:cover_reg_top
[D 261005 10:18:39 resources:134] [Tool: vcs            ] [ S:   181, Q:    49, R:    14, P:     0, F:     0, K:     0 ]
[D 261005 10:18:39 resources:134] [Tool: xcelium        ] [ S:    42, Q:    18, R:     1, P:     3, F:     0, K:     0 ]
[V 261005 10:18:39 core:286] Status change to [Q: Queued] for entropy_src_rng_4bits:0.entropy_src_csr_hw_reset.1
[D 261005 10:18:39 resources:134] [Tool: vcs            ] [ S:   181, Q:    49, R:    14, P:     0, F:     0, K:     0 ]
[D 261005 10:18:39 resources:134] [Tool: xcelium        ] [ S:    41, Q:    19, R:     1, P:     3, F:     0, K:     0 ]
[V 261005 10:18:39 core:286] Status change to [Q: Queued] for entropy_src_rng_4bits:0.entropy_src_csr_rw.1
[D 261005 10:18:39 resources:134] [Tool: vcs            ] [ S:   181, Q:    49, R:    14, P:     0, F:     0, K:     0 ]
[D 261005 10:18:39 resources:134] [Tool: xcelium        ] [ S:    40, Q:    20, R:     1, P:     3, F:     0, K:     0 ]
[V 261005 10:18:39 logging:67] [local]: Dispatching jobs: keymgr_dpe_earlgrey:cover_reg_top
[D 261005 10:18:39 resources:134] [Tool: vcs            ] [ S:   181, Q:    48, R:    15, P:     0, F:     0, K:     0 ]
[D 261005 10:18:39 resources:134] [Tool: xcelium        ] [ S:    40, Q:    20, R:     1, P:     3, F:     0, K:     0 ]
[D 261005 10:18:40 backend:101] Job cover_reg_top completed execution: P
[V 261005 10:18:40 core:286] Status change to [P: Passed] for aes_masked:cover_reg_top
[D 261005 10:18:40 resources:134] [Tool: vcs            ] [ S:   181, Q:    48, R:    15, P:     0, F:     0, K:     0 ]
[D 261005 10:18:40 resources:134] [Tool: xcelium        ] [ S:    40, Q:    20, R:     0, P:     4, F:     0, K:     0 ]
[V 261005 10:18:40 core:286] Status change to [Q: Queued] for aes_masked:0.aes_csr_rw.1
[D 261005 10:18:40 resources:134] [Tool: vcs            ] [ S:   181, Q:    48, R:    15, P:     0, F:     0, K:     0 ]
[D 261005 10:18:40 resources:134] [Tool: xcelium        ] [ S:    39, Q:    21, R:     0, P:     4, F:     0, K:     0 ]

Signed-off-by: Dominic Price <dominic.price@lowrisc.org>
Signed-off-by: Dominic Price <dominic.price@lowrisc.org>
Signed-off-by: Dominic Price <dominic.price@lowrisc.org>
Signed-off-by: Dominic Price <dominic.price@lowrisc.org>
Signed-off-by: Dominic Price <dominic.price@lowrisc.org>
@dominicprice-lowrisc
dominicprice-lowrisc force-pushed the logging-license-type-usage branch from 1f66ad8 to b6114ba Compare October 5, 2026 09:52
@dominicprice-lowrisc dominicprice-lowrisc changed the title Logging license type usage feat: Logging license type usage Oct 5, 2026
Signed-off-by: Dominic Price <dominic.price@lowrisc.org>

@AlexJones0 AlexJones0 left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the work on this @dominicprice-lowrisc, the overall approach LGTM.

I've left a lot of comments but most are very nitpicky, and hopefully quite easy to address or defer. The important comments are those on OpenTitan's commit guidelines and about handling resources vs. tools in the ResourceManager.

if log.isEnabledFor(log.DEBUG) and self._resources:
self.add_job_status_change_callback(
lambda spec, old, new, resources=self._resources:
lambda spec, old, new, resources=self._resources: (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: For OpenTitan & related projects, the contribution guidelines recommends that commits are made atomic, without a series of fixups.

Can you squash this down so that each commit is independently passing CI / formatting etc. in isolation? It could all be in 1 commit, or maybe you could make 3 commits with e.g. (a) add license type usage logging, (b) add a test and (c) improve the logging? The change to the tool_meta_factory could also reasonably be its own commit: it's up to you :)

self._on_kill_signal: list[OnSchedulerKillCb] = []

# Register callbacks for debug logging
if log.isEnabledFor(log.DEBUG) and self._resources:

@AlexJones0 AlexJones0 Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not 100% sure if this is best to exist here as a "native" concept in the Scheduler / ResourceManager, or if it should be a separate ResourceInstrumentation that hooks in.

I think that in reality this kind of resource logging probably better fits as an instrumentation hook, so that it can easily be enabled/disabled on the command line rather than being hid behind behind a logging level. But at the same time the resources are already tracked in the ResourceManager which is a first-class concept since it limits parallelism, so it also makes sense to have the logging here.

I think for now this approach makes good sense, though if we end up needing to expand this in the future it might be a good idea to revisit this. From my perspective, in an ideal world there would be some API via which an external observer could query the running dvsim process, and this would probably hook into the instrumentation -- rather than needing to parse log strings directly.

Comment on lines +160 to +170
if log.isEnabledFor(log.DEBUG) and self._resources:
self.add_job_status_change_callback(
lambda spec, old, new, resources=self._resources: (
resources.update_job_status_count_per_tool(spec, old, new)
)
)
self.add_job_status_change_callback(
lambda _spec, _old, _new, resources=self._resources: (
resources.log_job_status_count_per_tool()
)
)

@AlexJones0 AlexJones0 Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I would combine these into one callback -- in practice the callbacks should be handled sequentially in terms of the order registered, however this is not explicitly defined anywhere and we should not make this assumption (even internally 😅)

I would also probably move all of this under log.VERBOSE instead of log.DEBUG: if we need to, we can hide this logging behind a CLI option.

self._provider = provider
self._missing_policy = missing_policy
self._usage = defaultdict(int)
self._job_status_count_per_tool: dict[str, dict[JobStatus, int]] = {}

@AlexJones0 AlexJones0 Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use a more strong

Suggested change
self._job_status_count_per_tool: dict[str, dict[JobStatus, int]] = {}
self._job_status_count_per_tool = defaultdict(lambda: defaultdict(int))

And then log an error in ResourceManager.update_job_status_count_per_tool if we didn't see the tool during initialization. Or do you see a problem with this approach?


spec: JobSpec
backend_key: str # either spec.backend, or the default backend if not given
tool: str

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be left-over code? Given that all the accesses that I can see just go through JobHandle.spec.tool?

self, spec: JobSpec, old: JobStatus, new: JobStatus
) -> None:
"""Update the index that tracks job status counts per resource."""
if status_counts := self._job_status_count_per_tool.get(spec.tool.name):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is native to the ResourceManager, it should be able to abstract generic resources (and generic amounts of resources) instead of just tool names.

spec.resources is an optional mapping of resource name -> resource amount, so it allows jobs to define arbitrary amounts of resources. So while this is currently used for simulation tools, it could also denote physical resources, requirements for multiple simultaneous licenses (e.g. VCS & Z01X), etc. Then future job types just need to implement the resources that they need, and users can customize their resource availability on the command-line to limit parallelism.

I would probably initialize the index with the sum of the total resource usage, following the same approach as you currently have -- and then track this iterating over the resources of each job.

"""Initialise an index tracking the number of jobs with each status, per tool."""
for job in jobs:
tool = job.tool.name
if job_status_counts := self._job_status_count_per_tool.get(tool, None):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: redundant None

Suggested change
if job_status_counts := self._job_status_count_per_tool.get(tool, None):
if job_status_counts := self._job_status_count_per_tool.get(tool):

Comment thread tests/test_scheduler.py
return ToolMeta(name=name, version=version)
if name:
return ToolMeta(name=name, version=version)
# Do not need cryptographically secure PRNG for generating values to test on.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I would just drop this comment, I think it's understandable from the noqa.

Comment thread tests/test_scheduler.py
assert_that(proc.exitcode, equal_to(0))


class TestLogging:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can you give this a quick docstring? Something like

    """Unit tests for the logging functionality of the scheduler."""

Comment thread tests/test_scheduler.py
@staticmethod
@pytest.mark.asyncio
@pytest.mark.timeout(DEFAULT_TIMEOUT)
async def test_blocked_weight_starvation_logs(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine to base this test on test_blocked_weight_starvation, but I would suggest the name is more clear about what is actually being tested. Maybe something like:

Suggested change
async def test_blocked_weight_starvation_logs(
async def test_resource_logging(

@AlexJones0
AlexJones0 requested a review from machshev October 5, 2026 16:53

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants