Skip to content

Add rc-analyze utility#998

Open
axtloss wants to merge 2 commits intoOpenRC:masterfrom
axtloss:feat/rc-analyze
Open

Add rc-analyze utility#998
axtloss wants to merge 2 commits intoOpenRC:masterfrom
axtloss:feat/rc-analyze

Conversation

@axtloss
Copy link
Copy Markdown

@axtloss axtloss commented Apr 2, 2026

Add an eventlog and rc-analyze to allow users to view the openrc startup time

eventlog

A log of all events is stored in openrc/events, a global eventlog specifically for runlevel switches is stored in events/global, service specific events are stored in events/service/<service name>.

The events have a predictable format and contain an ISO 8061 timestamp with milliseconds

rc-analyze

Reads the eventlog to show how long specific services or the entire startup took.

Implements

  • time to view how long openrc took to reach the default runlevel
  • blame to view a sorted list showing how long each service took to launch
  • critical-chain shows a tree of a service including all dependencies of the service

blame:

cherryblossom ~ # rc-analyze blame
   10.149s hwclock
    1.381s sysuserd
    1.101s sshd
     278ms udev-trigger
     222ms localmount
     166ms swap
     164ms dhcpcd
     151ms procfs
     138ms agetty.tty3
     134ms devfs

time:

cherryblossom ~ # rc-analyze time
openrc startup finished in 16.331s

critical-chain:

cherryblossom ~ # rc-analyze critical-chain userdata
The time when unit became active is printed after '@'.
The time the unit took to start is printed after '+'.

dmesg @0.334s +61ms
└─modules @0.942s +114ms
  └─userdata @11.418s +121ms

Closes: #997

* Format the current time as an ISO 8601 timestamp with milliseconds.
* Returns a dynamically allocated string that must be freed by the caller.
*/
static char *format_timestamp(void)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

could probaby go in src/shared/timeutils.c

also would be better if this took the time as an argument, and a statically sized buffer to work on since it seems to have a known bound size (32 + enough to serialize 3 longs?)

tm = localtime(&ts.tv_sec);
ms = (int)(ts.tv_nsec / 1000000);

strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%S", tm);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we can consider using https://cr.yp.to/libtai/tai64.html TAI64N instead of a pretty formatted name, since the less parsing we have to do the better

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

or maybe easier, serialize the ms integer that tm_now() gives us?

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.

+1 on using easier to serialize format, unless this is something that is supposed to be viewed by a human directly.

Also I'm not sure if CLOCK_REALTIME is a good idea of we're going to use this time for benchmark/analysis purposes since that clock can be changed (e.g via ntp).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The analyzer (with the current format) reads the timezone as specified in the timestamp, so clock changes (should) be accounted for. At least it was in my testing before I forgot to account for the timestamp and it started reporting a boot time of over an hour

Using the easier format is something I've tried, but as mentioned in the most recent comment it causes issues with the build system so I haven't pushed that yet

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

CLOCK_MONOTONIC is probably simpler since then no accounting for timezones and ntp is needed, if we combine it with the simpler serialization

i'll try to resolve the build system thing since the shared libs being unusable in librc has annoyed me for a while now

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I switched it to CLOCK_MONOTONIC now by duplicating tm_now into rc-eventlog.c as you suggested

@navi-desu
Copy link
Copy Markdown
Member

didn't look much into rc-analyze yet, will later today

Store global and per-service event changes in the runtime dir under events.
Stores runlevel changes in the global log, service state changes in per-service log
@axtloss
Copy link
Copy Markdown
Author

axtloss commented Apr 2, 2026

I applied most reviews, I held back on the format_timestamp changes as using tm_now() inside librc causes a circular dependency between librc and shared

Adds a commandline utility to view various runtime statistics using the event log

Closes: OpenRC#997
struct timespec tv;
int64_t sec_to_ms = 1000, round_up = 500000, ns_to_ms = 1000000;

clock_gettime(CLOCK_MONOTONIC, &tv);
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.

You likely want to use CLOCK_BOOTTIME in linux (see timeutils.c and openrc-init.c).

What build system issue were you running into anyways? The duplication in openrc-init.c is already not too good, duplicating this a 3rd time doesn't seem nice.

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.

We could turn timeutils/tm_now into a "header only" thing like src/shared/helpers.h but I'm not sure how much I like that either.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

src/shared has stuff that uses librc, so librc can't use it, because meson ordering on subdir()

i am going to move most the shared things into their own individual subprojects i think, so that not only is the split clear but we avoid this going forward

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You likely want to use CLOCK_BOOTTIME in linux (see timeutils.c and openrc-init.c).

is the manpage for clock_gettime wrong? because i don't think we want to count suspended time on init time analytics? (that is if the user does suspend their device during boot, which is odd)

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.

Add ability to see the boot time the same way that systemd-analyze does

3 participants