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
1 change: 1 addition & 0 deletions man/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ man3 = [
man8 = [
'openrc.8',
'openrc-run.8',
'rc-analyze.8',
'rc-service.8',
'rc-status.8',
'rc-update.8',
Expand Down
46 changes: 46 additions & 0 deletions man/rc-analyze.8
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.\" Copyright (c) 2026 The OpenRC Authors.
.\" See the Authors file at the top-level directory of this distribution and
.\" https://github.com/OpenRC/openrc/blob/HEAD/AUTHORS
.\"
.\" This file is part of OpenRC. It is subject to the license terms in
.\" the LICENSE file found in the top-level directory of this
.\" distribution and at https://github.com/OpenRC/openrc/blob/HEAD/LICENSE
.\" This file may not be copied, modified, propagated, or distributed
.\" except according to the terms contained in the LICENSE file.
.\"
.Dd April 2, 2026
.Dt RC-ANALYZE 8 SMM
.Os OpenRC
.Sh NAME
.Nm rc-analyze
.Nd analyze OpenRC startup performance
.Sh SYNOPSIS
.Nm
.Ar blame | critical-chain | time
.Sh DESCRIPTION
The
.Nm
utility provides commands to analyze the boot performance of an OpenRC-based
system.
.Sh COMMANDS
The following commands are available:
.Bl -tag -width ".Fl critical-chain"
.It Nm blame
Prints a list of all services that were started, sorted by the time they took
to start up (longest first). This helps identify the slowest services during
boot.
.It Nm critical-chain
.Op Ar service
Shows the chain of dependencies that led to the specified
.Ar service
starting. If no service is specified, it shows the chain for the last service
to start in the default runlevel. The output is a tree where each dependency
is a parent of the service that depends on it. The time at which each service
became active and the time it took to start are printed.
.It Nm time
Prints a summary of the time spent getting to the default runlevel.
.El
.Sh SEE ALSO
.Xr openrc 8 ,
.Sh AUTHORS
.An The OpenRC Authors <openrc@lists.gentoo.org>
4 changes: 4 additions & 0 deletions src/librc/librc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,10 @@ rc_service_mark(const char *service, const RC_SERVICE state)
closedir(dp);
}
}

/* Log the service state change to the eventlog */
rc_eventlog_service(service, state);

free(init);
return true;
}
Expand Down
26 changes: 26 additions & 0 deletions src/librc/librc.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,30 @@ static const char *const dirnames[RC_DIR_SYS_MAX] =
RC_STRINGLIST *config_list(int dirfd, const char *pathname);
void clear_dirfds(void);

/*
* Log an event for a specific service.
* Events are logged to <RC_DIR_SVCDIR>/events/services/<service>
*
* @param service name of the service
* @param state new state of the service
*/
void rc_eventlog_service(const char *service, RC_SERVICE state);

/*
* Log a global system event.
* Events are logged to <RC_DIR_SVCDIR>/events/global
*
* @param event_type type of event (e.g., "runlevel", "boot", "shutdown")
* @param message event message with details
*/
void rc_eventlog_global(const char *event_type, const char *message);

/*
* Initialize the eventlog system.
* Creates necessary directories under <RC_DIR_SVCDIR>/events/
*
* @return 0 on success, -1 on failure
*/
int rc_eventlog_init(void);

#endif
1 change: 1 addition & 0 deletions src/librc/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ librc_sources = [
'librc-depend.c',
'librc-misc.c',
'librc-stringlist.c',
'rc-eventlog.c',
]

rc_h = configure_file(input : 'rc.h.in', output : 'rc.h',
Expand Down
145 changes: 145 additions & 0 deletions src/librc/rc-eventlog.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* rc-eventlog.c
* Event logging for OpenRC services and system events
*/

/*
* Copyright (c) 2024 The OpenRC Authors.
* See the Authors file at the top-level directory of this distribution and
* https://github.com/OpenRC/openrc/blob/HEAD/AUTHORS
*
* This file is part of OpenRC. It is subject to the license terms in
* the LICENSE file found in the top-level directory of this
* distribution and at https://github.com/OpenRC/openrc/blob/HEAD/LICENSE
* This file may not be copied, modified, propagated, or distributed
* except according to the terms contained in the LICENSE file.
*/

#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#include "librc.h"

#define RC_EVENTLOG_DIR "events"
#define RC_EVENTLOG_SERVICES "events/"
#define RC_EVENTLOG_GLOBAL "events/rc"

/*
* Get the name of a service state.
*/
static const char *rc_service_state_name(RC_SERVICE state)
{
int i;

for (i = 0; rc_service_state_names[i].name; i++) {
if (rc_service_state_names[i].state == state)
return rc_service_state_names[i].name;
}

return "unknown";
}

/*
* Get the current monotonic time in milliseconds.
*/
static int64_t tm_now(void)
{
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)

return (tv.tv_sec * sec_to_ms) + ((tv.tv_nsec + round_up) / ns_to_ms);
}

/*
* Initialize the eventlog system.
* Creates necessary directories under <RC_DIR_SVCDIR>/events/
*/
int rc_eventlog_init(void)
{
int svcfd = rc_dirfd(RC_DIR_SVCDIR);

if (svcfd < 0)
return -1;

if (mkdirat(svcfd, RC_EVENTLOG_DIR, 0755) == -1 && errno != EEXIST)
return -1;

if (mkdirat(svcfd, RC_EVENTLOG_SERVICES, 0755) == -1 && errno != EEXIST)
return -1;

return 0;
}

/*
* Log an event for a specific service.
*/
void rc_eventlog_service(const char *service, RC_SERVICE state)
{
int svcfd;
int eventsfd;
FILE *fp;
const char *state_name;

if (!service)
return;

svcfd = rc_dirfd(RC_DIR_SVCDIR);
if (svcfd < 0)
return;

if (rc_eventlog_init() != 0)
return;

eventsfd = openat(svcfd, RC_EVENTLOG_SERVICES, O_RDONLY | O_DIRECTORY);
if (eventsfd < 0)
return;

service = basename_c(service);

state_name = rc_service_state_name(state);

fp = do_fopenat(eventsfd, service, O_WRONLY | O_CREAT | O_APPEND);
close(eventsfd);

if (!fp)
return;

fprintf(fp, "%" PRId64 " %s\n", tm_now(), state_name);
fclose(fp);
}

/*
* Log a global system event.
*/
void rc_eventlog_global(const char *event_type, const char *message)
{
int svcfd;
FILE *fp;

if (!event_type || !message)
return;

svcfd = rc_dirfd(RC_DIR_SVCDIR);
if (svcfd < 0)
return;

if (rc_eventlog_init() != 0)
return;

fp = do_fopenat(svcfd, RC_EVENTLOG_GLOBAL, O_WRONLY | O_CREAT | O_APPEND);
if (!fp)
return;

fprintf(fp, "%" PRId64 " %s %s\n", tm_now(), event_type, message);
fclose(fp);
}
1 change: 1 addition & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ subdir('openrc-user')
subdir('pam_openrc')
subdir('poweroff')
subdir('rc-abort')
subdir('rc-analyze')
subdir('rc-depend')
subdir('rc-service')
subdir('rc-sstat')
Expand Down
2 changes: 2 additions & 0 deletions src/openrc/rc.c
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,7 @@ int main(int argc, char **argv)
if (access(RC_KRUNLEVEL, F_OK) != 0)
set_krunlevel(runlevel);
rc_runlevel_set(newlevel);
rc_eventlog_global("runlevel", newlevel);
setenv("RC_RUNLEVEL", newlevel, 1);
setenv("RC_GOINGDOWN", "YES", 1);
} else {
Expand Down Expand Up @@ -1059,6 +1060,7 @@ int main(int argc, char **argv)
/* Store the new runlevel */
if (newlevel) {
rc_runlevel_set(newlevel);
rc_eventlog_global("runlevel", newlevel);
free(runlevel);
runlevel = xstrdup(newlevel);
setenv("RC_RUNLEVEL", runlevel, 1);
Expand Down
7 changes: 7 additions & 0 deletions src/rc-analyze/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
libm = cc.find_library('m', required: true)

executable('rc-analyze', 'rc-analyze.c',
dependencies: [rc, einfo, shared, libm],
include_directories: incdir,
install: true,
install_dir: bindir)
Loading
Loading