github.com/rigado/snapd@v2.42.5-go-mod+incompatible/cmd/snap/cmd_debug_state.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package main 21 22 import ( 23 "fmt" 24 "os" 25 "sort" 26 "strconv" 27 "strings" 28 "text/tabwriter" 29 30 "github.com/jessevdk/go-flags" 31 "github.com/snapcore/snapd/i18n" 32 "github.com/snapcore/snapd/overlord/state" 33 ) 34 35 type cmdDebugState struct { 36 timeMixin 37 38 st *state.State 39 40 Changes bool `long:"changes"` 41 TaskID string `long:"task"` 42 ChangeID string `long:"change"` 43 44 // flags for --change=N output 45 DotOutput bool `long:"dot"` // XXX: mildly useful (too crowded in many cases), but let's have it just in case 46 // When inspecting errors/undone tasks, those in Hold state are usually irrelevant, make it possible to ignore them 47 NoHoldState bool `long:"no-hold"` 48 49 Positional struct { 50 StateFilePath string `positional-args:"yes" positional-arg-name:"<state-file>"` 51 } `positional-args:"yes"` 52 } 53 54 var cmdDebugStateShortHelp = i18n.G("Inspect a snapd state file.") 55 var cmdDebugStateLongHelp = i18n.G("Inspect a snapd state file, bypassing snapd API.") 56 57 type byChangeID []*state.Change 58 59 func (c byChangeID) Len() int { return len(c) } 60 func (c byChangeID) Swap(i, j int) { c[i], c[j] = c[j], c[i] } 61 func (c byChangeID) Less(i, j int) bool { return c[i].ID() < c[j].ID() } 62 63 func loadState(path string) (*state.State, error) { 64 if path == "" { 65 path = "state.json" 66 } 67 r, err := os.Open(path) 68 if err != nil { 69 return nil, fmt.Errorf("cannot read the state file: %s", err) 70 } 71 defer r.Close() 72 73 return state.ReadState(nil, r) 74 } 75 76 func init() { 77 addDebugCommand("state", cmdDebugStateShortHelp, cmdDebugStateLongHelp, func() flags.Commander { 78 return &cmdDebugState{} 79 }, timeDescs.also(map[string]string{ 80 // TRANSLATORS: This should not start with a lowercase letter. 81 "change": i18n.G("ID of the change to inspect"), 82 "task": i18n.G("ID of the task to inspect"), 83 "dot": i18n.G("Dot (graphviz) output"), 84 "no-hold": i18n.G("Omit tasks in 'Hold' state in the change output"), 85 "changes": i18n.G("List all changes"), 86 }), nil) 87 } 88 89 type byLaneAndWaitTaskChain []*state.Task 90 91 func (t byLaneAndWaitTaskChain) Len() int { return len(t) } 92 func (t byLaneAndWaitTaskChain) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 93 func (t byLaneAndWaitTaskChain) Less(i, j int) bool { 94 // cover the typical case (just one lane), and order by first lane 95 if t[i].Lanes()[0] == t[j].Lanes()[0] { 96 return waitChainSearch(t[i], t[j]) 97 } 98 return t[i].Lanes()[0] < t[j].Lanes()[0] 99 } 100 101 func waitChainSearch(startT, searchT *state.Task) bool { 102 for _, cand := range startT.HaltTasks() { 103 if cand == searchT { 104 return true 105 } 106 if waitChainSearch(cand, searchT) { 107 return true 108 } 109 } 110 111 return false 112 } 113 114 func (c *cmdDebugState) writeDotOutput(st *state.State, changeID string) error { 115 st.Lock() 116 defer st.Unlock() 117 118 chg := st.Change(changeID) 119 if chg == nil { 120 return fmt.Errorf("no such change: %s", changeID) 121 } 122 123 fmt.Fprintf(Stdout, "digraph D{\n") 124 tasks := chg.Tasks() 125 for _, t := range tasks { 126 if c.NoHoldState && t.Status() == state.HoldStatus { 127 continue 128 } 129 fmt.Fprintf(Stdout, " %s [label=%q];\n", t.ID(), t.Kind()) 130 for _, wt := range t.WaitTasks() { 131 if c.NoHoldState && wt.Status() == state.HoldStatus { 132 continue 133 } 134 fmt.Fprintf(Stdout, " %s -> %s;\n", t.ID(), wt.ID()) 135 } 136 } 137 fmt.Fprintf(Stdout, "}\n") 138 139 return nil 140 } 141 142 func (c *cmdDebugState) showTasks(st *state.State, changeID string) error { 143 st.Lock() 144 defer st.Unlock() 145 146 chg := st.Change(changeID) 147 if chg == nil { 148 return fmt.Errorf("no such change: %s", changeID) 149 } 150 151 tasks := chg.Tasks() 152 sort.Sort(byLaneAndWaitTaskChain(tasks)) 153 154 w := tabwriter.NewWriter(Stdout, 5, 3, 2, ' ', 0) 155 fmt.Fprintf(w, "Lanes\tID\tStatus\tSpawn\tReady\tKind\tSummary\n") 156 for _, t := range tasks { 157 if c.NoHoldState && t.Status() == state.HoldStatus { 158 continue 159 } 160 var lanes []string 161 for _, lane := range t.Lanes() { 162 lanes = append(lanes, fmt.Sprintf("%d", lane)) 163 } 164 fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", 165 strings.Join(lanes, ","), 166 t.ID(), 167 t.Status().String(), 168 c.fmtTime(t.SpawnTime()), 169 c.fmtTime(t.ReadyTime()), 170 t.Kind(), 171 t.Summary()) 172 } 173 174 w.Flush() 175 176 for _, t := range tasks { 177 logs := t.Log() 178 if len(logs) > 0 { 179 fmt.Fprintf(Stdout, "---\n") 180 fmt.Fprintf(Stdout, "%s %s\n", t.ID(), t.Summary()) 181 for _, log := range logs { 182 fmt.Fprintf(Stdout, " %s\n", log) 183 } 184 } 185 } 186 187 return nil 188 } 189 190 func (c *cmdDebugState) showChanges(st *state.State) error { 191 st.Lock() 192 defer st.Unlock() 193 194 changes := st.Changes() 195 sort.Sort(byChangeID(changes)) 196 197 w := tabwriter.NewWriter(Stdout, 5, 3, 2, ' ', 0) 198 fmt.Fprintf(w, "ID\tStatus\tSpawn\tReady\tLabel\tSummary\n") 199 for _, chg := range changes { 200 fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", 201 chg.ID(), 202 chg.Status().String(), 203 c.fmtTime(chg.SpawnTime()), 204 c.fmtTime(chg.ReadyTime()), 205 chg.Kind(), 206 chg.Summary()) 207 } 208 w.Flush() 209 210 return nil 211 } 212 213 func (c *cmdDebugState) showTask(st *state.State, taskID string) error { 214 st.Lock() 215 defer st.Unlock() 216 217 task := st.Task(taskID) 218 if task == nil { 219 return fmt.Errorf("no such task: %s", taskID) 220 } 221 222 termWidth, _ := termSize() 223 termWidth -= 3 224 if termWidth > 100 { 225 // any wider than this and it gets hard to read 226 termWidth = 100 227 } 228 229 // the output of 'debug task' is yaml'ish 230 fmt.Fprintf(Stdout, "id: %s\nkind: %s\nsummary: %s\nstatus: %s\n", 231 taskID, task.Kind(), 232 task.Summary(), 233 task.Status().String()) 234 log := task.Log() 235 if len(log) > 0 { 236 fmt.Fprintf(Stdout, "log: |\n") 237 for _, msg := range log { 238 if err := wrapLine(Stdout, []rune(msg), " ", termWidth); err != nil { 239 break 240 } 241 } 242 fmt.Fprintln(Stdout) 243 } 244 245 fmt.Fprintf(Stdout, "halt-tasks:") 246 if len(task.HaltTasks()) == 0 { 247 fmt.Fprintln(Stdout, " []") 248 } else { 249 fmt.Fprintln(Stdout) 250 for _, ht := range task.HaltTasks() { 251 fmt.Fprintf(Stdout, " - %s (%s)\n", ht.Kind(), ht.ID()) 252 } 253 } 254 255 return nil 256 } 257 258 func (c *cmdDebugState) Execute(args []string) error { 259 st, err := loadState(c.Positional.StateFilePath) 260 if err != nil { 261 return err 262 } 263 264 // check valid combinations of args 265 var cmds []string 266 if c.Changes { 267 cmds = append(cmds, "--changes") 268 } 269 if c.ChangeID != "" { 270 cmds = append(cmds, "--change=") 271 } 272 if c.TaskID != "" { 273 cmds = append(cmds, "--task=") 274 } 275 if len(cmds) > 1 { 276 return fmt.Errorf("cannot use %s and %s together", cmds[0], cmds[1]) 277 } 278 279 if c.DotOutput && c.ChangeID == "" { 280 return fmt.Errorf("--dot can only be used with --change=") 281 } 282 if c.NoHoldState && c.ChangeID == "" { 283 return fmt.Errorf("--no-hold can only be used with --change=") 284 } 285 286 if c.Changes { 287 return c.showChanges(st) 288 } 289 290 if c.ChangeID != "" { 291 _, err := strconv.ParseInt(c.ChangeID, 0, 64) 292 if err != nil { 293 return fmt.Errorf("invalid change: %s", c.ChangeID) 294 } 295 if c.DotOutput { 296 return c.writeDotOutput(st, c.ChangeID) 297 } 298 return c.showTasks(st, c.ChangeID) 299 } 300 301 if c.TaskID != "" { 302 _, err := strconv.ParseInt(c.TaskID, 0, 64) 303 if err != nil { 304 return fmt.Errorf("invalid task: %s", c.TaskID) 305 } 306 return c.showTask(st, c.TaskID) 307 } 308 309 // show changes by default 310 return c.showChanges(st) 311 }