Skip to content

Commit

Permalink
fleetctl: Support service uptime as part of a list-units output
Browse files Browse the repository at this point in the history
We can get the uptime from systemctl, the resolve is like below:
localhost # /home/wood/fleet/fleetctl list-units
UNIT                    MACHINE                         ACTIVE  SUB     UPTIME
world.service           06ecd4f7.../192.168.122.30      active  running 2015-07-06 07:38:38 AM, Since 11m39s
world2.service          1d3430ef.../192.168.122.31      active  running 2015-07-06 07:48:24 AM, Since 1m54s
world_glob.service      06ecd4f7.../192.168.122.30      active  running 2015-07-06 07:48:00 AM, Since 2m18s
world_glob.service      1d3430ef.../192.168.122.31      active  running 2015-07-06 07:47:59 AM, Since 2m18s

Fixed coreos#1128
  • Loading branch information
wuqixuan committed Jul 8, 2015
1 parent a2174a2 commit 6f36f88
Show file tree
Hide file tree
Showing 12 changed files with 135 additions and 97 deletions.
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)
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])
},
}
)

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")
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

0 comments on commit 6f36f88

Please sign in to comment.