1 Star 0 Fork 1

mysnapcore/mysnapd

forked from tupelo-shen/mysnapd 
加入 Gitee
与超过 1400万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
cmd_debug_state.go 14.24 KB
一键复制 编辑 原始数据 按行查看 历史
tupelo-shen 提交于 2022-11-08 22:37 +08:00 . fix: cmd commit
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2019 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package main
import (
"errors"
"fmt"
"os"
"sort"
"strconv"
"strings"
"text/tabwriter"
"gopkg.in/yaml.v2"
"github.com/jessevdk/go-flags"
"gitee.com/mysnapcore/mysnapd/i18n"
"gitee.com/mysnapcore/mysnapd/interfaces"
"gitee.com/mysnapcore/mysnapd/overlord/ifacestate/schema"
"gitee.com/mysnapcore/mysnapd/overlord/state"
"gitee.com/mysnapcore/mysnapd/strutil"
)
type cmdDebugState struct {
timeMixin
Changes bool `long:"changes"`
TaskID string `long:"task"`
ChangeID string `long:"change"`
Check bool `long:"check"`
Connections bool `long:"connections"`
Connection string `long:"connection"`
IsSeeded bool `long:"is-seeded"`
// flags for --change=N output
DotOutput bool `long:"dot"` // XXX: mildly useful (too crowded in many cases), but let's have it just in case
// When inspecting errors/undone tasks, those in Hold state are usually irrelevant, make it possible to ignore them
NoHoldState bool `long:"no-hold"`
Positional struct {
StateFilePath string `positional-args:"yes" positional-arg-name:"<state-file>"`
} `positional-args:"yes"`
}
var cmdDebugStateShortHelp = i18n.G("Inspect a snapd state file.")
var cmdDebugStateLongHelp = i18n.G("Inspect a snapd state file, bypassing snapd API.")
type byChangeSpawnTime []*state.Change
func (c byChangeSpawnTime) Len() int { return len(c) }
func (c byChangeSpawnTime) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c byChangeSpawnTime) Less(i, j int) bool { return c[i].SpawnTime().Before(c[j].SpawnTime()) }
func loadState(path string) (*state.State, error) {
if path == "" {
path = "state.json"
}
r, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("cannot read the state file: %s", err)
}
defer r.Close()
return state.ReadState(nil, r)
}
func init() {
addDebugCommand("state", cmdDebugStateShortHelp, cmdDebugStateLongHelp, func() flags.Commander {
return &cmdDebugState{}
}, timeDescs.also(map[string]string{
// TRANSLATORS: This should not start with a lowercase letter.
"change": i18n.G("ID of the change to inspect"),
"task": i18n.G("ID of the task to inspect"),
"dot": i18n.G("Dot (graphviz) output"),
"no-hold": i18n.G("Omit tasks in 'Hold' state in the change output"),
"changes": i18n.G("List all changes"),
"connections": i18n.G("List all connections"),
"connection": i18n.G("Show details of the matching connections (snap or snap:plug,snap:slot or snap:plug-or-slot"),
"is-seeded": i18n.G("Output seeding status (true or false)"),
"check": i18n.G("Check change consistency"),
}), nil)
}
type byLaneAndWaitTaskChain []*state.Task
func (t byLaneAndWaitTaskChain) Len() int { return len(t) }
func (t byLaneAndWaitTaskChain) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
func (t byLaneAndWaitTaskChain) Less(i, j int) bool {
if t[i].ID() == t[j].ID() {
return false
}
// cover the typical case (just one lane), and order by first lane
if t[i].Lanes()[0] == t[j].Lanes()[0] {
seenTasks := make(map[string]bool)
return t.waitChainSearch(t[i], t[j], seenTasks)
}
return t[i].Lanes()[0] < t[j].Lanes()[0]
}
func (t *byLaneAndWaitTaskChain) waitChainSearch(startT, searchT *state.Task, seenTasks map[string]bool) bool {
if seenTasks[startT.ID()] {
return false
}
seenTasks[startT.ID()] = true
for _, cand := range startT.HaltTasks() {
if cand == searchT {
return true
}
if t.waitChainSearch(cand, searchT, seenTasks) {
return true
}
}
return false
}
func (c *cmdDebugState) writeDotOutput(st *state.State, changeID string) error {
st.Lock()
defer st.Unlock()
chg := st.Change(changeID)
if chg == nil {
return fmt.Errorf("no such change: %s", changeID)
}
fmt.Fprintf(Stdout, "digraph D{\n")
tasks := chg.Tasks()
for _, t := range tasks {
if c.NoHoldState && t.Status() == state.HoldStatus {
continue
}
fmt.Fprintf(Stdout, " %s [label=%q];\n", t.ID(), t.Kind())
for _, wt := range t.WaitTasks() {
if c.NoHoldState && wt.Status() == state.HoldStatus {
continue
}
fmt.Fprintf(Stdout, " %s -> %s;\n", t.ID(), wt.ID())
}
}
fmt.Fprintf(Stdout, "}\n")
return nil
}
func (c *cmdDebugState) showTasks(st *state.State, changeID string) error {
st.Lock()
defer st.Unlock()
chg := st.Change(changeID)
if chg == nil {
return fmt.Errorf("no such change: %s", changeID)
}
tasks := chg.Tasks()
sort.Sort(byLaneAndWaitTaskChain(tasks))
w := tabwriter.NewWriter(Stdout, 5, 3, 2, ' ', 0)
fmt.Fprintf(w, "Lanes\tID\tStatus\tSpawn\tReady\tKind\tSummary\n")
for _, t := range tasks {
if c.NoHoldState && t.Status() == state.HoldStatus {
continue
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
strutil.IntsToCommaSeparated(t.Lanes()),
t.ID(),
t.Status().String(),
c.fmtTime(t.SpawnTime()),
c.fmtTime(t.ReadyTime()),
t.Kind(),
t.Summary())
}
w.Flush()
for _, t := range tasks {
logs := t.Log()
if len(logs) > 0 {
fmt.Fprintf(Stdout, "---\n")
fmt.Fprintf(Stdout, "%s %s\n", t.ID(), t.Summary())
for _, log := range logs {
fmt.Fprintf(Stdout, " %s\n", log)
}
}
}
return nil
}
func (c *cmdDebugState) checkTasks(st *state.State, changeID string) error {
st.Lock()
defer st.Unlock()
showAtMostTasks := 3
formatAtMostTaskIDs := func(tasks []*state.Task) string {
var b strings.Builder
b.WriteRune('[')
atMostTasks := tasks
trimmed := false
if len(atMostTasks) > showAtMostTasks {
atMostTasks = tasks[:showAtMostTasks]
trimmed = true
}
for i, t := range atMostTasks {
b.WriteString(t.ID())
if i < len(atMostTasks)-1 {
b.WriteRune(',')
}
}
if trimmed {
b.WriteString(",...")
}
b.WriteRune(']')
return b.String()
}
chg := st.Change(changeID)
if chg == nil {
return fmt.Errorf("no such change: %s", changeID)
}
err := chg.CheckTaskDependencies()
if err != nil {
if tdcErr, ok := err.(*state.TaskDependencyCycleError); ok {
fmt.Fprintf(Stdout, "Detected task dependency cycle involving tasks:\n")
w := tabwriter.NewWriter(Stdout, 5, 3, 2, ' ', 0)
fmt.Fprintf(w, "Lanes\tID\tStatus\tSpawn\tReady\tKind\tSummary\tAfter\tBefore\n")
for _, tid := range tdcErr.IDs {
t := st.Task(tid)
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%v\t%v\n",
strutil.IntsToCommaSeparated(t.Lanes()),
t.ID(),
t.Status().String(),
c.fmtTime(t.SpawnTime()),
c.fmtTime(t.ReadyTime()),
t.Kind(),
t.Summary(),
formatAtMostTaskIDs(t.WaitTasks()),
formatAtMostTaskIDs(t.HaltTasks()),
)
}
w.Flush()
} else {
return err
}
}
return nil
}
func (c *cmdDebugState) showChanges(st *state.State) error {
st.Lock()
defer st.Unlock()
changes := st.Changes()
sort.Sort(byChangeSpawnTime(changes))
w := tabwriter.NewWriter(Stdout, 5, 3, 2, ' ', 0)
fmt.Fprintf(w, "ID\tStatus\tSpawn\tReady\tLabel\tSummary\n")
for _, chg := range changes {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n",
chg.ID(),
chg.Status().String(),
c.fmtTime(chg.SpawnTime()),
c.fmtTime(chg.ReadyTime()),
chg.Kind(),
chg.Summary())
}
w.Flush()
return nil
}
func (c *cmdDebugState) showIsSeeded(st *state.State) error {
st.Lock()
defer st.Unlock()
var isSeeded bool
err := st.Get("seeded", &isSeeded)
if err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
fmt.Fprintf(Stdout, "%v\n", isSeeded)
return nil
}
type connectionInfo struct {
PlugSnap string
PlugName string
SlotSnap string
SlotName string
schema.ConnState
}
type byPlug []*connectionInfo
func (c byPlug) Len() int { return len(c) }
func (c byPlug) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c byPlug) Less(i, j int) bool {
a, b := c[i], c[j]
return a.PlugSnap < b.PlugSnap || (a.PlugSnap == b.PlugSnap && a.PlugName < b.PlugName)
}
func (c *cmdDebugState) showConnectionDetails(st *state.State, connArg string) error {
st.Lock()
defer st.Unlock()
p := strings.FieldsFunc(connArg, func(r rune) bool {
return r == ' ' || r == ','
})
var plugMatch, slotMatch SnapAndName
if err := plugMatch.UnmarshalFlag(p[0]); err != nil {
return err
}
if len(p) > 1 {
if err := slotMatch.UnmarshalFlag(p[1]); err != nil {
return err
}
}
var conns map[string]*schema.ConnState
if err := st.Get("conns", &conns); err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
// sort by connection ID
connIDs := make([]string, 0, len(conns))
for connID := range conns {
connIDs = append(connIDs, connID)
}
sort.Strings(connIDs)
for _, connID := range connIDs {
connRef, err := interfaces.ParseConnRef(connID)
if err != nil {
return err
}
refMatch := func(x SnapAndName, y interface{ String() string }) bool {
parts := strings.Split(y.String(), ":")
return len(parts) == 2 && x.Snap == parts[0] && x.Name == parts[1]
}
plug, slot := connRef.PlugRef, connRef.SlotRef
switch {
// command invoked with 'snap:plug,snap:slot'
case slotMatch.Name != "" && slotMatch.Snap != "" && plugMatch.Snap != "" && plugMatch.Name != "":
// should match the connection exactly
if !refMatch(plugMatch, plug) || !refMatch(slotMatch, slot) {
continue
}
// command invoked with 'snap:plug-or-slot'
case plugMatch.Snap != "" && plugMatch.Name != "" && slotMatch.Snap == "" && slotMatch.Name == "":
// should match either the connection's slot or plug
if !refMatch(plugMatch, plug) && !refMatch(plugMatch, slot) {
continue
}
// command invoked with 'snap' only
case plugMatch.Snap != "" && plugMatch.Name == "" && slotMatch.Snap == "" && slotMatch.Name == "":
// should match one of the snap names
if plugMatch.Snap != slot.Snap && plugMatch.Snap != plug.Snap {
continue
}
default:
return fmt.Errorf("invalid command with connection args: %s", connArg)
}
conn := conns[connID]
// the output of 'debug connection' is yaml
fmt.Fprintf(Stdout, "id: %s\n", connID)
out, err := yaml.Marshal(conn)
if err != nil {
return err
}
fmt.Fprintf(Stdout, "%s\n", out)
}
return nil
}
func (c *cmdDebugState) showConnections(st *state.State) error {
st.Lock()
defer st.Unlock()
var conns map[string]*schema.ConnState
if err := st.Get("conns", &conns); err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
all := make([]*connectionInfo, 0, len(conns))
for connID, conn := range conns {
p := strings.Split(connID, " ")
if len(p) != 2 {
return fmt.Errorf("cannot parse connection ID %q", connID)
}
plug := strings.Split(p[0], ":")
slot := strings.Split(p[1], ":")
c := &connectionInfo{
PlugSnap: plug[0],
PlugName: plug[1],
SlotSnap: slot[0],
SlotName: slot[1],
ConnState: *conn,
}
all = append(all, c)
}
sort.Sort(byPlug(all))
w := tabwriter.NewWriter(Stdout, 5, 3, 2, ' ', 0)
fmt.Fprintf(w, "Interface\tPlug\tSlot\tNotes\n")
for _, conn := range all {
var notes []string
if conn.Auto {
notes = append(notes, "auto")
}
if conn.Undesired {
notes = append(notes, "undesired")
}
if conn.ByGadget {
notes = append(notes, "by-gadget")
}
fmt.Fprintf(w, "%s\t%s:%s\t%s:%s\t%s\n", conn.Interface, conn.PlugSnap, conn.PlugName, conn.SlotSnap, conn.SlotName, strings.Join(notes, ","))
}
w.Flush()
return nil
}
func (c *cmdDebugState) showTask(st *state.State, taskID string) error {
st.Lock()
defer st.Unlock()
task := st.Task(taskID)
if task == nil {
return fmt.Errorf("no such task: %s", taskID)
}
termWidth, _ := termSize()
termWidth -= 3
if termWidth > 100 {
// any wider than this and it gets hard to read
termWidth = 100
}
// the output of 'debug task' is yaml'ish
fmt.Fprintf(Stdout, "id: %s\nkind: %s\nsummary: %s\nstatus: %s\n",
taskID, task.Kind(),
task.Summary(),
task.Status().String())
log := task.Log()
if len(log) > 0 {
fmt.Fprintf(Stdout, "log: |\n")
for _, msg := range log {
if err := strutil.WordWrapPadded(Stdout, []rune(msg), " ", termWidth); err != nil {
break
}
}
fmt.Fprintln(Stdout)
}
fmt.Fprintf(Stdout, "halt-tasks:")
if len(task.HaltTasks()) == 0 {
fmt.Fprintln(Stdout, " []")
} else {
fmt.Fprintln(Stdout)
for _, ht := range task.HaltTasks() {
fmt.Fprintf(Stdout, " - %s (%s)\n", ht.Kind(), ht.ID())
}
}
return nil
}
func (c *cmdDebugState) Execute(args []string) error {
st, err := loadState(c.Positional.StateFilePath)
if err != nil {
return err
}
// check valid combinations of args
var cmds []string
if c.Changes {
cmds = append(cmds, "--changes")
}
if c.ChangeID != "" {
cmds = append(cmds, "--change=")
}
if c.TaskID != "" {
cmds = append(cmds, "--task=")
}
if c.IsSeeded {
cmds = append(cmds, "--is-seeded")
}
if c.Connections {
cmds = append(cmds, "--connections")
}
if len(cmds) > 1 {
return fmt.Errorf("cannot use %s and %s together", cmds[0], cmds[1])
}
if c.IsSeeded {
return c.showIsSeeded(st)
}
if c.DotOutput && c.ChangeID == "" {
return fmt.Errorf("--dot can only be used with --change=")
}
if c.NoHoldState && c.ChangeID == "" {
return fmt.Errorf("--no-hold can only be used with --change=")
}
if c.Check && c.ChangeID == "" {
return fmt.Errorf("--check can only be used with --change")
}
if c.Changes {
return c.showChanges(st)
}
if c.ChangeID != "" {
_, err := strconv.ParseInt(c.ChangeID, 0, 64)
if err != nil {
return fmt.Errorf("invalid change: %s", c.ChangeID)
}
if c.DotOutput {
return c.writeDotOutput(st, c.ChangeID)
}
if c.Check {
return c.checkTasks(st, c.ChangeID)
}
return c.showTasks(st, c.ChangeID)
}
if c.TaskID != "" {
_, err := strconv.ParseInt(c.TaskID, 0, 64)
if err != nil {
return fmt.Errorf("invalid task: %s", c.TaskID)
}
return c.showTask(st, c.TaskID)
}
if c.Connections {
return c.showConnections(st)
}
if c.Connection != "" {
return c.showConnectionDetails(st, c.Connection)
}
// show changes by default
return c.showChanges(st)
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/mysnapcore/mysnapd.git
git@gitee.com:mysnapcore/mysnapd.git
mysnapcore
mysnapd
mysnapd
v0.1.0

搜索帮助