Skip to content

Commit

Permalink
chore: provide a resource to peek into Linux clock adjustments
Browse files Browse the repository at this point in the history
This is a follow-up for #7567, which won't be backported to 1.5.

This allows to get an output like:

```
$ talosctl -n 172.20.0.5 get adjtimestatus -w
NODE         *   NAMESPACE TYPE            ID     VERSION   OFFSET        ESTERROR   MAXERROR   STATUS               SYNC
172.20.0.5   +   runtime   AdjtimeStatus   node   47        -18.14306ms   0s         191.5ms    STA_PLL | STA_NANO   true
172.20.0.5       runtime   AdjtimeStatus   node   48        -17.109555ms  0s         206.5ms    STA_NANO | STA_PLL   true
172.20.0.5       runtime   AdjtimeStatus   node   49        -16.134923ms  0s         221.5ms    STA_NANO | STA_PLL   true
172.20.0.5       runtime   AdjtimeStatus   node   50        -15.21581ms   0s         236.5ms    STA_PLL | STA_NANO   true
```

Signed-off-by: Andrey Smirnov <[email protected]>
  • Loading branch information
smira committed Aug 3, 2023
1 parent 4eab301 commit 0f1920b
Show file tree
Hide file tree
Showing 12 changed files with 901 additions and 27 deletions.
14 changes: 14 additions & 0 deletions api/resource/definitions/time/time.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ package talos.resource.definitions.time;

option go_package = "github.com/siderolabs/talos/pkg/machinery/api/resource/definitions/time";

import "google/protobuf/duration.proto";

// AdjtimeStatusSpec describes Linux internal adjtime state.
message AdjtimeStatusSpec {
google.protobuf.Duration offset = 1;
double frequency_adjustment_ratio = 2;
google.protobuf.Duration max_error = 3;
google.protobuf.Duration est_error = 4;
string status = 5;
int64 constant = 6;
bool sync_status = 7;
string state = 8;
}

// StatusSpec describes time sync state.
message StatusSpec {
bool synced = 1;
Expand Down
97 changes: 97 additions & 0 deletions internal/app/machined/pkg/controllers/time/adjtime_status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package time

import (
"context"
"fmt"
stdtime "time"

"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/safe"
"go.uber.org/zap"
"golang.org/x/sys/unix"

v1alpha1runtime "github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/internal/pkg/timex"
"github.com/siderolabs/talos/pkg/machinery/resources/time"
)

// AdjtimeStatusController manages time.AdjtimeStatus based on Linux kernel info.
type AdjtimeStatusController struct {
V1Alpha1Mode v1alpha1runtime.Mode
}

// Name implements controller.Controller interface.
func (ctrl *AdjtimeStatusController) Name() string {
return "time.AdjtimeStatusController"
}

// Inputs implements controller.Controller interface.
func (ctrl *AdjtimeStatusController) Inputs() []controller.Input {
return nil
}

// Outputs implements controller.Controller interface.
func (ctrl *AdjtimeStatusController) Outputs() []controller.Output {
return []controller.Output{
{
Type: time.AdjtimeStatusType,
Kind: controller.OutputExclusive,
},
}
}

// Run implements controller.Controller interface.
func (ctrl *AdjtimeStatusController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
if ctrl.V1Alpha1Mode == v1alpha1runtime.ModeContainer {
// in container mode, clock is managed by the host
return nil
}

const pollInterval = 30 * stdtime.Second

pollTicker := stdtime.NewTicker(pollInterval)
defer pollTicker.Stop()

for {
select {
case <-ctx.Done():
return nil
case <-r.EventCh():
case <-pollTicker.C:
}

var timexBuf unix.Timex

state, err := timex.Adjtimex(&timexBuf)
if err != nil {
return fmt.Errorf("failed to get adjtimex state: %w", err)
}

scale := stdtime.Nanosecond

if timexBuf.Status&unix.STA_NANO == 0 {
scale = stdtime.Microsecond
}

if err := safe.WriterModify(ctx, r, time.NewAdjtimeStatus(), func(status *time.AdjtimeStatus) error {
status.TypedSpec().Offset = stdtime.Duration(timexBuf.Offset) * scale //nolint:durationcheck
status.TypedSpec().FrequencyAdjustmentRatio = 1 + float64(timexBuf.Freq)/65536.0/1000000.0
status.TypedSpec().MaxError = stdtime.Duration(timexBuf.Maxerror) * stdtime.Microsecond //nolint:durationcheck
status.TypedSpec().EstError = stdtime.Duration(timexBuf.Esterror) * stdtime.Microsecond //nolint:durationcheck
status.TypedSpec().Status = timex.Status(timexBuf.Status).String()
status.TypedSpec().State = state.String()
status.TypedSpec().Constant = int(timexBuf.Constant)
status.TypedSpec().SyncStatus = timexBuf.Status&unix.STA_UNSYNC == 0

return nil
}); err != nil {
return fmt.Errorf("failed to update adjtime status: %w", err)
}

r.ResetRestartBackoff()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,9 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
Cmdline: procfs.ProcCmdline(),
},
&siderolink.ManagerController{},
&timecontrollers.AdjtimeStatusController{
V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(),
},
&timecontrollers.SyncController{
V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ func NewState() (*State, error) {
&secrets.OSRoot{},
&secrets.Trustd{},
&siderolink.Config{},
&time.AdjtimeStatus{},
&time.Status{},
&v1alpha1.AcquireConfigSpec{},
&v1alpha1.AcquireConfigStatus{},
Expand Down
192 changes: 169 additions & 23 deletions pkg/machinery/api/resource/definitions/time/time.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 0f1920b

Please sign in to comment.