Andrey Smirnov 0f1920bdda
chore: provide a resource to peek into Linux clock adjustments
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 <andrey.smirnov@talos-systems.com>
2023-08-03 22:06:53 +04:00

98 lines
2.9 KiB
Go

// 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()
}
}