Skip to content
This repository was archived by the owner on Jan 30, 2020. It is now read-only.

fleetctl: Support service uptime as part of a list-units output #1293

Closed
wants to merge 1 commit into from
Closed
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 agent/unit_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@ func TestMarshalJSON(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error marshalling: %v", err)
}
want = `{"Cache":{"bar.service":{"LoadState":"","ActiveState":"inactive","SubState":"","MachineID":"asdf","UnitHash":"","UnitName":"bar.service"},"foo.service":{"LoadState":"","ActiveState":"active","SubState":"","MachineID":"asdf","UnitHash":"","UnitName":"foo.service"}},"ToPublish":{"woof.service":{"LoadState":"","ActiveState":"active","SubState":"","MachineID":"asdf","UnitHash":"","UnitName":"woof.service"}}}`
want = `{"Cache":{"bar.service":{"LoadState":"","ActiveState":"inactive","SubState":"","MachineID":"asdf","UnitHash":"","UnitName":"bar.service","ActiveEnterTimestamp":0},"foo.service":{"LoadState":"","ActiveState":"active","SubState":"","MachineID":"asdf","UnitHash":"","UnitName":"foo.service","ActiveEnterTimestamp":0}},"ToPublish":{"woof.service":{"LoadState":"","ActiveState":"active","SubState":"","MachineID":"asdf","UnitHash":"","UnitName":"woof.service","ActiveEnterTimestamp":0}}}`
if string(got) != want {
t.Fatalf("Bad JSON representation: got\n%s\n\nwant\n%s", string(got), want)
}
Expand Down
11 changes: 10 additions & 1 deletion fleetctl/list_units.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ import (
"fmt"
"sort"
"strings"
"time"

"github.com/coreos/fleet/machine"
"github.com/coreos/fleet/schema"
)

const (
defaultListUnitsFields = "unit,machine,active,sub"
defaultListUnitsFields = "unit,machine,active,sub,uptime"
)

var (
Expand Down Expand Up @@ -90,6 +91,14 @@ Or, choose the columns to display:
}
return us.Hash
},
"uptime": func(us *schema.UnitState, full bool) string {
if us == nil || us.SystemdActiveState != "active" {
return "-"
}
tm := time.Unix(0, int64(us.SystemdActiveEnterTimestamp)*1000)
Copy link
Contributor

Choose a reason for hiding this comment

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

why this instead of time.Unix(int64(us.SystemdActiveEnterTimestamp), 0)?

duration := time.Now().Sub(tm)
return fmt.Sprintf("%s, Since %ss", tm.Format("2006-01-02 03:04:05 PM"), strings.Split(duration.String(), ".")[0])
Copy link
Contributor

Choose a reason for hiding this comment

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

pull tm.Format("2006-01-02 03:04:05 PM") into a global var block, and add the timezone

},
}
)

Expand Down
31 changes: 17 additions & 14 deletions registry/unit_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,12 @@ func (r *EtcdRegistry) RemoveUnitState(jobName string) error {
}

type unitStateModel struct {
LoadState string `json:"loadState"`
ActiveState string `json:"activeState"`
SubState string `json:"subState"`
MachineState *machine.MachineState `json:"machineState"`
UnitHash string `json:"unitHash"`
LoadState string `json:"loadState"`
ActiveState string `json:"activeState"`
SubState string `json:"subState"`
MachineState *machine.MachineState `json:"machineState"`
UnitHash string `json:"unitHash"`
ActiveEnterTimestamp uint64 `json:"ActiveEnterTimestamp"`
}

func modelToUnitState(usm *unitStateModel, name string) *unit.UnitState {
Expand All @@ -203,11 +204,12 @@ func modelToUnitState(usm *unitStateModel, name string) *unit.UnitState {
}

us := unit.UnitState{
LoadState: usm.LoadState,
ActiveState: usm.ActiveState,
SubState: usm.SubState,
UnitHash: usm.UnitHash,
UnitName: name,
LoadState: usm.LoadState,
ActiveState: usm.ActiveState,
SubState: usm.SubState,
UnitHash: usm.UnitHash,
UnitName: name,
ActiveEnterTimestamp: usm.ActiveEnterTimestamp,
}

if usm.MachineState != nil {
Expand All @@ -229,10 +231,11 @@ func unitStateToModel(us *unit.UnitState) *unitStateModel {
//}

usm := unitStateModel{
LoadState: us.LoadState,
ActiveState: us.ActiveState,
SubState: us.SubState,
UnitHash: us.UnitHash,
LoadState: us.LoadState,
ActiveState: us.ActiveState,
SubState: us.SubState,
UnitHash: us.UnitHash,
ActiveEnterTimestamp: us.ActiveEnterTimestamp,
}

if us.MachineID != "" {
Expand Down
106 changes: 57 additions & 49 deletions registry/unit_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func TestSaveUnitState(t *testing.T) {
r := &EtcdRegistry{kAPI: e, keyPrefix: "/fleet/"}
j := "foo.service"
mID := "mymachine"
us := unit.NewUnitState("abc", "def", "ghi", mID)
us := unit.NewUnitState("abc", "def", "ghi", mID, 1234567890)

// Saving nil unit state should fail
r.SaveUnitState(j, nil, time.Second)
Expand All @@ -123,7 +123,7 @@ func TestSaveUnitState(t *testing.T) {
us.UnitHash = "quickbrownfox"
r.SaveUnitState(j, us, time.Second)

json := `{"loadState":"abc","activeState":"def","subState":"ghi","machineState":{"ID":"mymachine","PublicIP":"","Metadata":null,"Version":""},"unitHash":"quickbrownfox"}`
json := `{"loadState":"abc","activeState":"def","subState":"ghi","machineState":{"ID":"mymachine","PublicIP":"","Metadata":null,"Version":""},"unitHash":"quickbrownfox","ActiveEnterTimestamp":1234567890}`
p1 := "/fleet/state/foo.service"
p2 := "/fleet/states/foo.service/mymachine"
want := []action{
Expand Down Expand Up @@ -198,54 +198,60 @@ func TestUnitStateToModel(t *testing.T) {
// Unit state with no hash and no machineID is OK
// See https://github.com/coreos/fleet/issues/720
in: &unit.UnitState{
LoadState: "foo",
ActiveState: "bar",
SubState: "baz",
MachineID: "",
UnitHash: "",
UnitName: "name",
LoadState: "foo",
ActiveState: "bar",
SubState: "baz",
MachineID: "",
UnitHash: "",
UnitName: "name",
ActiveEnterTimestamp: 0,
},
want: &unitStateModel{
LoadState: "foo",
ActiveState: "bar",
SubState: "baz",
MachineState: nil,
UnitHash: "",
LoadState: "foo",
ActiveState: "bar",
SubState: "baz",
MachineState: nil,
UnitHash: "",
ActiveEnterTimestamp: 0,
},
},
{
// Unit state with hash but no machineID is OK
in: &unit.UnitState{
LoadState: "foo",
ActiveState: "bar",
SubState: "baz",
MachineID: "",
UnitHash: "heh",
UnitName: "name",
LoadState: "foo",
ActiveState: "bar",
SubState: "baz",
MachineID: "",
UnitHash: "heh",
UnitName: "name",
ActiveEnterTimestamp: 1234567890,
},
want: &unitStateModel{
LoadState: "foo",
ActiveState: "bar",
SubState: "baz",
MachineState: nil,
UnitHash: "heh",
LoadState: "foo",
ActiveState: "bar",
SubState: "baz",
MachineState: nil,
UnitHash: "heh",
ActiveEnterTimestamp: 1234567890,
},
},
{
in: &unit.UnitState{
LoadState: "foo",
ActiveState: "bar",
SubState: "baz",
MachineID: "woof",
UnitHash: "miaow",
UnitName: "name",
LoadState: "foo",
ActiveState: "bar",
SubState: "baz",
MachineID: "woof",
UnitHash: "miaow",
UnitName: "name",
ActiveEnterTimestamp: 54321,
},
want: &unitStateModel{
LoadState: "foo",
ActiveState: "bar",
SubState: "baz",
MachineState: &machine.MachineState{ID: "woof"},
UnitHash: "miaow",
LoadState: "foo",
ActiveState: "bar",
SubState: "baz",
MachineState: &machine.MachineState{ID: "woof"},
UnitHash: "miaow",
ActiveEnterTimestamp: 54321,
},
},
} {
Expand All @@ -266,25 +272,27 @@ func TestModelToUnitState(t *testing.T) {
want: nil,
},
{
in: &unitStateModel{"foo", "bar", "baz", nil, ""},
in: &unitStateModel{"foo", "bar", "baz", nil, "", 1234567890},
want: &unit.UnitState{
LoadState: "foo",
ActiveState: "bar",
SubState: "baz",
MachineID: "",
UnitHash: "",
UnitName: "name",
LoadState: "foo",
ActiveState: "bar",
SubState: "baz",
MachineID: "",
UnitHash: "",
UnitName: "name",
ActiveEnterTimestamp: 1234567890,
},
},
{
in: &unitStateModel{"z", "x", "y", &machine.MachineState{ID: "abcd"}, ""},
in: &unitStateModel{"z", "x", "y", &machine.MachineState{ID: "abcd"}, "", 987654321},
want: &unit.UnitState{
LoadState: "z",
ActiveState: "x",
SubState: "y",
MachineID: "abcd",
UnitHash: "",
UnitName: "name",
LoadState: "z",
ActiveState: "x",
SubState: "y",
MachineID: "abcd",
UnitHash: "",
UnitName: "name",
ActiveEnterTimestamp: 987654321,
},
},
} {
Expand Down
26 changes: 14 additions & 12 deletions schema/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,13 @@ func MapUnitStatesToSchemaUnitStates(entities []*unit.UnitState) []*UnitState {

func MapUnitStateToSchemaUnitState(entity *unit.UnitState) *UnitState {
us := UnitState{
Name: entity.UnitName,
Hash: entity.UnitHash,
MachineID: entity.MachineID,
SystemdLoadState: entity.LoadState,
SystemdActiveState: entity.ActiveState,
SystemdSubState: entity.SubState,
Name: entity.UnitName,
Hash: entity.UnitHash,
MachineID: entity.MachineID,
SystemdLoadState: entity.LoadState,
SystemdActiveState: entity.ActiveState,
SystemdSubState: entity.SubState,
SystemdActiveEnterTimestamp: entity.ActiveEnterTimestamp,
}

return &us
Expand All @@ -130,12 +131,13 @@ func MapSchemaUnitStatesToUnitStates(entities []*UnitState) []*unit.UnitState {
us := make([]*unit.UnitState, len(entities))
for i, e := range entities {
us[i] = &unit.UnitState{
UnitName: e.Name,
UnitHash: e.Hash,
MachineID: e.MachineID,
LoadState: e.SystemdLoadState,
ActiveState: e.SystemdActiveState,
SubState: e.SystemdSubState,
UnitName: e.Name,
UnitHash: e.Hash,
MachineID: e.MachineID,
LoadState: e.SystemdLoadState,
ActiveState: e.SystemdActiveState,
SubState: e.SystemdSubState,
ActiveEnterTimestamp: e.SystemdActiveEnterTimestamp,
}
}

Expand Down
2 changes: 2 additions & 0 deletions schema/v1-gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ type UnitState struct {
SystemdLoadState string `json:"systemdLoadState,omitempty"`

SystemdSubState string `json:"systemdSubState,omitempty"`

SystemdActiveEnterTimestamp uint64 `json:"systemdActiveEnterTimestamp,omitempty"`
}

type UnitStatePage struct {
Expand Down
11 changes: 11 additions & 0 deletions systemd/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,17 @@ func (m *systemdUnitManager) GetUnitStates(filter pkg.Set) (map[string]*unit.Uni
states[name] = us
}

// add Active enter time to UnitState
for name, us := range states {
prop, err := m.systemd.GetUnitProperty(name, "ActiveEnterTimestamp")
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this not available in the properties available in getUnitState? making an extra dbus call per unit is pretty expensive...

if err != nil {
return nil, err
}

us.ActiveEnterTimestamp = prop.Value.Value().(uint64)
states[name] = us
}

return states, nil
}

Expand Down
2 changes: 1 addition & 1 deletion unit/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (fum *FakeUnitManager) GetUnitStates(filter pkg.Set) (map[string]*UnitState
states := make(map[string]*UnitState)
for _, name := range filter.Values() {
if _, ok := fum.u[name]; ok {
states[name] = &UnitState{"loaded", "active", "running", "", "", name}
states[name] = &UnitState{"loaded", "active", "running", "", "", name, 0}
}
}

Expand Down
2 changes: 1 addition & 1 deletion unit/fake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func TestFakeUnitManagerLoadUnload(t *testing.T) {
t.Fatalf("Expected non-nil UnitState")
}

eus := NewUnitState("loaded", "active", "running", "")
eus := NewUnitState("loaded", "active", "running", "", 0)
if !reflect.DeepEqual(*us, *eus) {
t.Fatalf("Expected UnitState %v, got %v", eus, *us)
}
Expand Down
2 changes: 1 addition & 1 deletion unit/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestUnitStateGeneratorSubscribeLifecycle(t *testing.T) {

// subscribed to foo.service so we should get a heartbeat
expect := []UnitStateHeartbeat{
UnitStateHeartbeat{Name: "foo.service", State: &UnitState{"loaded", "active", "running", "", "", "foo.service"}},
UnitStateHeartbeat{Name: "foo.service", State: &UnitState{"loaded", "active", "running", "", "", "foo.service", 0}},
}
assertGenerateUnitStateHeartbeats(t, um, gen, expect)

Expand Down
24 changes: 13 additions & 11 deletions unit/unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,20 +171,22 @@ func (h *Hash) Empty() bool {

// UnitState encodes the current state of a unit loaded into a fleet agent
type UnitState struct {
LoadState string
ActiveState string
SubState string
MachineID string
UnitHash string
UnitName string
LoadState string
ActiveState string
SubState string
MachineID string
UnitHash string
UnitName string
ActiveEnterTimestamp uint64
}

func NewUnitState(loadState, activeState, subState, mID string) *UnitState {
func NewUnitState(loadState, activeState, subState, mID string, activeEnterTimestamp uint64) *UnitState {
return &UnitState{
LoadState: loadState,
ActiveState: activeState,
SubState: subState,
MachineID: mID,
LoadState: loadState,
ActiveState: activeState,
SubState: subState,
MachineID: mID,
ActiveEnterTimestamp: activeEnterTimestamp,
}
}

Expand Down
13 changes: 7 additions & 6 deletions unit/unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,16 @@ func TestDefaultUnitType(t *testing.T) {

func TestNewUnitState(t *testing.T) {
want := &UnitState{
LoadState: "ls",
ActiveState: "as",
SubState: "ss",
MachineID: "id",
LoadState: "ls",
ActiveState: "as",
SubState: "ss",
MachineID: "id",
ActiveEnterTimestamp: 1234567890,
}

got := NewUnitState("ls", "as", "ss", "id")
got := NewUnitState("ls", "as", "ss", "id", 1234567890)
if !reflect.DeepEqual(got, want) {
t.Fatalf("NewUnitState did not create a correct UnitState: got %s, want %s", got, want)
t.Fatalf("NewUnitState did not create a correct UnitState: got %v, want %v", got, want)
}

}
Expand Down