Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion web/api/report_server.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ struct RunData {
1: i64 runId, // Unique id of the run.
2: string runDate, // Date of the run last updated.
3: string name, // Human-given identifier.
4: i64 duration, // Duration of the run (-1 if not finished).
4: i64 duration, // Duration of the run in milliseconds (-1 if not finished).
5: i64 resultCount, // Number of unresolved results (review status is not FALSE_POSITIVE or INTENTIONAL) in the run.
6: string runCmd, // The used check command. !!!DEPRECATED!!! This field will be empty so use the getCheckCommand API function to get the check command for a run.
7: map<DetectionStatus, i32> detectionStatusCount, // Number of reports with a particular detection status.
Expand Down
2 changes: 1 addition & 1 deletion web/client/codechecker_client/cmd_line_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,7 @@ def handle_list_runs(args):
'Duration', 'Description', 'CodeChecker version']
rows = []
for run in runs:
duration = str(timedelta(seconds=run.duration)) \
duration = str(timedelta(milliseconds=run.duration)) \
if run.duration > -1 else 'Not finished'

analyzer_statistics = []
Expand Down
24 changes: 24 additions & 0 deletions web/client/tests/unit/test_cmd_line_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,27 @@ def test_enabled_checkers_json(self, get_run_data, setup_client, init_logger):
stats = run_data["analyzerStatistics"]["clangsa"]
self.assertIn("enabledCheckers", stats)
self.assertEqual(stats["enabledCheckers"], ["a"])


class DurationMillisecondPrecisionTest(unittest.TestCase):
@patch("codechecker_client.cmd_line_client.init_logger")
@patch("codechecker_client.cmd_line_client.setup_client")
@patch("codechecker_client.cmd_line_client.get_run_data")
def test_duration_shows_milliseconds(self, get_run_data, setup_client, init_logger):
run = DummyRun(1, "test_run")
run.duration = 1234
get_run_data.return_value = [run]

args = Args(
product_url="dummy",
sort_type="name",
sort_order="asc",
output_format="plaintext"
)

buf = io.StringIO()
with redirect_stdout(buf):
cmd_line_client.handle_list_runs(args)

output = buf.getvalue()
self.assertIn("0:00:01.234", output)
2 changes: 1 addition & 1 deletion web/server/codechecker_server/api/mass_store_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -954,7 +954,7 @@ def __store_analysis_statistics(
})

for mip in self.__mips.values():
self.__duration += int(sum(mip.check_durations))
self.__duration += int(sum(mip.check_durations) * 1000)

for analyzer_type, res in mip.analyzer_statistics.items():
if "version" in res:
Expand Down
8 changes: 5 additions & 3 deletions web/server/codechecker_server/database/run_db_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class Run(Base):

id = Column(Integer, autoincrement=True, primary_key=True)
date = Column(DateTime)
duration = Column(Integer) # Seconds, -1 if unfinished.
duration = Column(Integer) # Milliseconds, -1 if unfinished.
name = Column(String)
version = Column(String)
can_delete = Column(Boolean, nullable=False, server_default=true(),
Expand All @@ -110,8 +110,10 @@ def __init__(self, name, version):
self.duration = -1

def mark_finished(self):
if self.duration == -1:
self.duration = ceil((datetime.now() - self.date).total_seconds())
if self.duration != -1:
return
runtime_ms = (datetime.now() - self.date) / timedelta(milliseconds=1)
self.duration = ceil(runtime_ms)


class RunLock(Base):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
Duration millisecond precision

Revision ID: a1b2c3d4e5f6
Revises: 198654dac219
Create Date: 2026-02-24 13:14:00.000000
"""

from alembic import op


revision = 'a1b2c3d4e5f6'
down_revision = '198654dac219'
branch_labels = None
depends_on = None


def upgrade():
op.execute("""
UPDATE runs
SET duration = duration * 1000
WHERE duration != -1
""")


def downgrade():
op.execute("""
UPDATE runs
SET duration = duration / 1000
WHERE duration != -1
""")
5 changes: 3 additions & 2 deletions web/server/vue-cli/src/views/RunList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -519,8 +519,9 @@ export default {
this.analyzerStatisticsDialog = true;
},

prettifyDuration(seconds) {
if (seconds >= 0) {
prettifyDuration(milliseconds) {
if (milliseconds >= 0) {
const seconds = Math.floor(milliseconds / 1000);
const durHours = Math.floor(seconds / 3600);
const durMins = Math.floor(seconds / 60) - durHours * 60;
const durSecs = seconds - durMins * 60 - durHours * 3600;
Expand Down