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

Commit 90f4b09

Browse files
author
wuqixuan
committed
fleetctl: Support service uptime as part of a list-units output
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 #1128
1 parent a2174a2 commit 90f4b09

File tree

12 files changed

+134
-96
lines changed

12 files changed

+134
-96
lines changed

agent/unit_state_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,7 @@ func TestMarshalJSON(t *testing.T) {
780780
if err != nil {
781781
t.Fatalf("unexpected error marshalling: %v", err)
782782
}
783-
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"}}}`
783+
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}}}`
784784
if string(got) != want {
785785
t.Fatalf("Bad JSON representation: got\n%s\n\nwant\n%s", string(got), want)
786786
}

fleetctl/list_units.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ import (
1818
"fmt"
1919
"sort"
2020
"strings"
21+
"time"
2122

2223
"github.com/coreos/fleet/machine"
2324
"github.com/coreos/fleet/schema"
2425
)
2526

2627
const (
27-
defaultListUnitsFields = "unit,machine,active,sub"
28+
defaultListUnitsFields = "unit,machine,active,sub,uptime"
2829
)
2930

3031
var (
@@ -90,6 +91,14 @@ Or, choose the columns to display:
9091
}
9192
return us.Hash
9293
},
94+
"uptime": func(us *schema.UnitState, full bool) string {
95+
if us == nil || us.SystemdActiveState != "active" {
96+
return "-"
97+
}
98+
tm := time.Unix(0, int64(us.SystemdActiveEnterTimestamp)*1000)
99+
duration := time.Now().Sub(tm)
100+
return fmt.Sprintf("%s, Since %ss", tm.Format("2006-01-02 03:04:05 PM"), strings.Split(duration.String(), ".")[0])
101+
},
93102
}
94103
)
95104

registry/unit_state.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -190,11 +190,12 @@ func (r *EtcdRegistry) RemoveUnitState(jobName string) error {
190190
}
191191

192192
type unitStateModel struct {
193-
LoadState string `json:"loadState"`
194-
ActiveState string `json:"activeState"`
195-
SubState string `json:"subState"`
196-
MachineState *machine.MachineState `json:"machineState"`
197-
UnitHash string `json:"unitHash"`
193+
LoadState string `json:"loadState"`
194+
ActiveState string `json:"activeState"`
195+
SubState string `json:"subState"`
196+
MachineState *machine.MachineState `json:"machineState"`
197+
UnitHash string `json:"unitHash"`
198+
ActiveEnterTimestamp uint64 `json:"ActiveEnterTimestamp"`
198199
}
199200

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

205206
us := unit.UnitState{
206-
LoadState: usm.LoadState,
207-
ActiveState: usm.ActiveState,
208-
SubState: usm.SubState,
209-
UnitHash: usm.UnitHash,
210-
UnitName: name,
207+
LoadState: usm.LoadState,
208+
ActiveState: usm.ActiveState,
209+
SubState: usm.SubState,
210+
UnitHash: usm.UnitHash,
211+
UnitName: name,
212+
ActiveEnterTimestamp: usm.ActiveEnterTimestamp,
211213
}
212214

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

231233
usm := unitStateModel{
232-
LoadState: us.LoadState,
233-
ActiveState: us.ActiveState,
234-
SubState: us.SubState,
235-
UnitHash: us.UnitHash,
234+
LoadState: us.LoadState,
235+
ActiveState: us.ActiveState,
236+
SubState: us.SubState,
237+
UnitHash: us.UnitHash,
238+
ActiveEnterTimestamp: us.ActiveEnterTimestamp,
236239
}
237240

238241
if us.MachineID != "" {

registry/unit_state_test.go

Lines changed: 57 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ func TestSaveUnitState(t *testing.T) {
101101
r := &EtcdRegistry{kAPI: e, keyPrefix: "/fleet/"}
102102
j := "foo.service"
103103
mID := "mymachine"
104-
us := unit.NewUnitState("abc", "def", "ghi", mID)
104+
us := unit.NewUnitState("abc", "def", "ghi", mID, 1234567890)
105105

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

126-
json := `{"loadState":"abc","activeState":"def","subState":"ghi","machineState":{"ID":"mymachine","PublicIP":"","Metadata":null,"Version":""},"unitHash":"quickbrownfox"}`
126+
json := `{"loadState":"abc","activeState":"def","subState":"ghi","machineState":{"ID":"mymachine","PublicIP":"","Metadata":null,"Version":""},"unitHash":"quickbrownfox","ActiveEnterTimestamp":1234567890}`
127127
p1 := "/fleet/state/foo.service"
128128
p2 := "/fleet/states/foo.service/mymachine"
129129
want := []action{
@@ -198,54 +198,60 @@ func TestUnitStateToModel(t *testing.T) {
198198
// Unit state with no hash and no machineID is OK
199199
// See https://github.com/coreos/fleet/issues/720
200200
in: &unit.UnitState{
201-
LoadState: "foo",
202-
ActiveState: "bar",
203-
SubState: "baz",
204-
MachineID: "",
205-
UnitHash: "",
206-
UnitName: "name",
201+
LoadState: "foo",
202+
ActiveState: "bar",
203+
SubState: "baz",
204+
MachineID: "",
205+
UnitHash: "",
206+
UnitName: "name",
207+
ActiveEnterTimestamp: 0,
207208
},
208209
want: &unitStateModel{
209-
LoadState: "foo",
210-
ActiveState: "bar",
211-
SubState: "baz",
212-
MachineState: nil,
213-
UnitHash: "",
210+
LoadState: "foo",
211+
ActiveState: "bar",
212+
SubState: "baz",
213+
MachineState: nil,
214+
UnitHash: "",
215+
ActiveEnterTimestamp: 0,
214216
},
215217
},
216218
{
217219
// Unit state with hash but no machineID is OK
218220
in: &unit.UnitState{
219-
LoadState: "foo",
220-
ActiveState: "bar",
221-
SubState: "baz",
222-
MachineID: "",
223-
UnitHash: "heh",
224-
UnitName: "name",
221+
LoadState: "foo",
222+
ActiveState: "bar",
223+
SubState: "baz",
224+
MachineID: "",
225+
UnitHash: "heh",
226+
UnitName: "name",
227+
ActiveEnterTimestamp: 1234567890,
225228
},
226229
want: &unitStateModel{
227-
LoadState: "foo",
228-
ActiveState: "bar",
229-
SubState: "baz",
230-
MachineState: nil,
231-
UnitHash: "heh",
230+
LoadState: "foo",
231+
ActiveState: "bar",
232+
SubState: "baz",
233+
MachineState: nil,
234+
UnitHash: "heh",
235+
ActiveEnterTimestamp: 1234567890,
232236
},
233237
},
234238
{
235239
in: &unit.UnitState{
236-
LoadState: "foo",
237-
ActiveState: "bar",
238-
SubState: "baz",
239-
MachineID: "woof",
240-
UnitHash: "miaow",
241-
UnitName: "name",
240+
LoadState: "foo",
241+
ActiveState: "bar",
242+
SubState: "baz",
243+
MachineID: "woof",
244+
UnitHash: "miaow",
245+
UnitName: "name",
246+
ActiveEnterTimestamp: 54321,
242247
},
243248
want: &unitStateModel{
244-
LoadState: "foo",
245-
ActiveState: "bar",
246-
SubState: "baz",
247-
MachineState: &machine.MachineState{ID: "woof"},
248-
UnitHash: "miaow",
249+
LoadState: "foo",
250+
ActiveState: "bar",
251+
SubState: "baz",
252+
MachineState: &machine.MachineState{ID: "woof"},
253+
UnitHash: "miaow",
254+
ActiveEnterTimestamp: 54321,
249255
},
250256
},
251257
} {
@@ -266,25 +272,27 @@ func TestModelToUnitState(t *testing.T) {
266272
want: nil,
267273
},
268274
{
269-
in: &unitStateModel{"foo", "bar", "baz", nil, ""},
275+
in: &unitStateModel{"foo", "bar", "baz", nil, "", 1234567890},
270276
want: &unit.UnitState{
271-
LoadState: "foo",
272-
ActiveState: "bar",
273-
SubState: "baz",
274-
MachineID: "",
275-
UnitHash: "",
276-
UnitName: "name",
277+
LoadState: "foo",
278+
ActiveState: "bar",
279+
SubState: "baz",
280+
MachineID: "",
281+
UnitHash: "",
282+
UnitName: "name",
283+
ActiveEnterTimestamp: 1234567890,
277284
},
278285
},
279286
{
280-
in: &unitStateModel{"z", "x", "y", &machine.MachineState{ID: "abcd"}, ""},
287+
in: &unitStateModel{"z", "x", "y", &machine.MachineState{ID: "abcd"}, "", 987654321},
281288
want: &unit.UnitState{
282-
LoadState: "z",
283-
ActiveState: "x",
284-
SubState: "y",
285-
MachineID: "abcd",
286-
UnitHash: "",
287-
UnitName: "name",
289+
LoadState: "z",
290+
ActiveState: "x",
291+
SubState: "y",
292+
MachineID: "abcd",
293+
UnitHash: "",
294+
UnitName: "name",
295+
ActiveEnterTimestamp: 987654321,
288296
},
289297
},
290298
} {

schema/mapper.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,13 @@ func MapUnitStatesToSchemaUnitStates(entities []*unit.UnitState) []*UnitState {
115115

116116
func MapUnitStateToSchemaUnitState(entity *unit.UnitState) *UnitState {
117117
us := UnitState{
118-
Name: entity.UnitName,
119-
Hash: entity.UnitHash,
120-
MachineID: entity.MachineID,
121-
SystemdLoadState: entity.LoadState,
122-
SystemdActiveState: entity.ActiveState,
123-
SystemdSubState: entity.SubState,
118+
Name: entity.UnitName,
119+
Hash: entity.UnitHash,
120+
MachineID: entity.MachineID,
121+
SystemdLoadState: entity.LoadState,
122+
SystemdActiveState: entity.ActiveState,
123+
SystemdSubState: entity.SubState,
124+
SystemdActiveEnterTimestamp: entity.ActiveEnterTimestamp,
124125
}
125126

126127
return &us
@@ -130,12 +131,13 @@ func MapSchemaUnitStatesToUnitStates(entities []*UnitState) []*unit.UnitState {
130131
us := make([]*unit.UnitState, len(entities))
131132
for i, e := range entities {
132133
us[i] = &unit.UnitState{
133-
UnitName: e.Name,
134-
UnitHash: e.Hash,
135-
MachineID: e.MachineID,
136-
LoadState: e.SystemdLoadState,
137-
ActiveState: e.SystemdActiveState,
138-
SubState: e.SystemdSubState,
134+
UnitName: e.Name,
135+
UnitHash: e.Hash,
136+
MachineID: e.MachineID,
137+
LoadState: e.SystemdLoadState,
138+
ActiveState: e.SystemdActiveState,
139+
SubState: e.SystemdSubState,
140+
ActiveEnterTimestamp: e.SystemdActiveEnterTimestamp,
139141
}
140142
}
141143

schema/v1-gen.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ type UnitState struct {
141141
SystemdLoadState string `json:"systemdLoadState,omitempty"`
142142

143143
SystemdSubState string `json:"systemdSubState,omitempty"`
144+
145+
SystemdActiveEnterTimestamp uint64 `json:"systemdActiveEnterTimestamp,omitempty"`
144146
}
145147

146148
type UnitStatePage struct {

systemd/manager.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,17 @@ func (m *systemdUnitManager) GetUnitStates(filter pkg.Set) (map[string]*unit.Uni
238238
states[name] = us
239239
}
240240

241+
// add Active enter time to UnitState
242+
for name, us := range states {
243+
prop, err := m.systemd.GetUnitProperty(name, "ActiveEnterTimestamp")
244+
if err != nil {
245+
return nil, err
246+
}
247+
248+
us.ActiveEnterTimestamp = prop.Value.Value().(uint64)
249+
states[name] = us
250+
}
251+
241252
return states, nil
242253
}
243254

unit/fake.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func (fum *FakeUnitManager) GetUnitStates(filter pkg.Set) (map[string]*UnitState
8383
states := make(map[string]*UnitState)
8484
for _, name := range filter.Values() {
8585
if _, ok := fum.u[name]; ok {
86-
states[name] = &UnitState{"loaded", "active", "running", "", "", name}
86+
states[name] = &UnitState{"loaded", "active", "running", "", "", name, 0}
8787
}
8888
}
8989

unit/fake_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func TestFakeUnitManagerLoadUnload(t *testing.T) {
6060
t.Fatalf("Expected non-nil UnitState")
6161
}
6262

63-
eus := NewUnitState("loaded", "active", "running", "")
63+
eus := NewUnitState("loaded", "active", "running", "", 0)
6464
if !reflect.DeepEqual(*us, *eus) {
6565
t.Fatalf("Expected UnitState %v, got %v", eus, *us)
6666
}

unit/generator_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func TestUnitStateGeneratorSubscribeLifecycle(t *testing.T) {
4949

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

unit/unit.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -171,20 +171,22 @@ func (h *Hash) Empty() bool {
171171

172172
// UnitState encodes the current state of a unit loaded into a fleet agent
173173
type UnitState struct {
174-
LoadState string
175-
ActiveState string
176-
SubState string
177-
MachineID string
178-
UnitHash string
179-
UnitName string
174+
LoadState string
175+
ActiveState string
176+
SubState string
177+
MachineID string
178+
UnitHash string
179+
UnitName string
180+
ActiveEnterTimestamp uint64
180181
}
181182

182-
func NewUnitState(loadState, activeState, subState, mID string) *UnitState {
183+
func NewUnitState(loadState, activeState, subState, mID string, activeEnterTimestamp uint64) *UnitState {
183184
return &UnitState{
184-
LoadState: loadState,
185-
ActiveState: activeState,
186-
SubState: subState,
187-
MachineID: mID,
185+
LoadState: loadState,
186+
ActiveState: activeState,
187+
SubState: subState,
188+
MachineID: mID,
189+
ActiveEnterTimestamp: activeEnterTimestamp,
188190
}
189191
}
190192

unit/unit_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,14 @@ func TestDefaultUnitType(t *testing.T) {
9393

9494
func TestNewUnitState(t *testing.T) {
9595
want := &UnitState{
96-
LoadState: "ls",
97-
ActiveState: "as",
98-
SubState: "ss",
99-
MachineID: "id",
96+
LoadState: "ls",
97+
ActiveState: "as",
98+
SubState: "ss",
99+
MachineID: "id",
100+
ActiveEnterTimestamp: 1234567890,
100101
}
101102

102-
got := NewUnitState("ls", "as", "ss", "id")
103+
got := NewUnitState("ls", "as", "ss", "id", 1234567890)
103104
if !reflect.DeepEqual(got, want) {
104105
t.Fatalf("NewUnitState did not create a correct UnitState: got %s, want %s", got, want)
105106
}

0 commit comments

Comments
 (0)