github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/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 IsSeeded bool `long:"is-seeded"` 45 46 // flags for --change=N output 47 DotOutput bool `long:"dot"` // XXX: mildly useful (too crowded in many cases), but let's have it just in case 48 // When inspecting errors/undone tasks, those in Hold state are usually irrelevant, make it possible to ignore them 49 NoHoldState bool `long:"no-hold"` 50 51 Positional struct { 52 StateFilePath string `positional-args:"yes" positional-arg-name:"<state-file>"` 53 } `positional-args:"yes"` 54 } 55 56 var cmdDebugStateShortHelp = i18n.G("Inspect a snapd state file.") 57 var cmdDebugStateLongHelp = i18n.G("Inspect a snapd state file, bypassing snapd API.") 58 59 type byChangeID []*state.Change 60 61 func (c byChangeID) Len() int { return len(c) } 62 func (c byChangeID) Swap(i, j int) { c[i], c[j] = c[j], c[i] } 63 func (c byChangeID) Less(i, j int) bool { return c[i].ID() < c[j].ID() } 64 65 func loadState(path string) (*state.State, error) { 66 if path == "" { 67 path = "state.json" 68 } 69 r, err := os.Open(path) 70 if err != nil { 71 return nil, fmt.Errorf("cannot read the state file: %s", err) 72 } 73 defer r.Close() 74 75 return state.ReadState(nil, r) 76 } 77 78 func init() { 79 addDebugCommand("state", cmdDebugStateShortHelp, cmdDebugStateLongHelp, func() flags.Commander { 80 return &cmdDebugState{} 81 }, timeDescs.also(map[string]string{ 82 // TRANSLATORS: This should not start with a lowercase letter. 83 "change": i18n.G("ID of the change to inspect"), 84 "task": i18n.G("ID of the task to inspect"), 85 "dot": i18n.G("Dot (graphviz) output"), 86 "no-hold": i18n.G("Omit tasks in 'Hold' state in the change output"), 87 "changes": i18n.G("List all changes"), 88 "is-seeded": i18n.G("Output seeding status (true or false)"), 89 }), nil) 90 } 91 92 type byLaneAndWaitTaskChain []*state.Task 93 94 func (t byLaneAndWaitTaskChain) Len() int { return len(t) } 95 func (t byLaneAndWaitTaskChain) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 96 func (t byLaneAndWaitTaskChain) Less(i, j int) bool { 97 // cover the typical case (just one lane), and order by first lane 98 if t[i].Lanes()[0] == t[j].Lanes()[0] { 99 return waitChainSearch(t[i], t[j]) 100 } 101 return t[i].Lanes()[0] < t[j].Lanes()[0] 102 } 103 104 func waitChainSearch(startT, searchT *state.Task) bool { 105 for _, cand := range startT.HaltTasks() { 106 if cand == searchT { 107 return true 108 } 109 if waitChainSearch(cand, searchT) { 110 return true 111 } 112 } 113 114 return false 115 } 116 117 func (c *cmdDebugState) writeDotOutput(st *state.State, changeID string) error { 118 st.Lock() 119 defer st.Unlock() 120 121 chg := st.Change(changeID) 122 if chg == nil { 123 return fmt.Errorf("no such change: %s", changeID) 124 } 125 126 fmt.Fprintf(Stdout, "digraph D{\n") 127 tasks := chg.Tasks() 128 for _, t := range tasks { 129 if c.NoHoldState && t.Status() == state.HoldStatus { 130 continue 131 } 132 fmt.Fprintf(Stdout, " %s [label=%q];\n", t.ID(), t.Kind()) 133 for _, wt := range t.WaitTasks() { 134 if c.NoHoldState && wt.Status() == state.HoldStatus { 135 continue 136 } 137 fmt.Fprintf(Stdout, " %s -> %s;\n", t.ID(), wt.ID()) 138 } 139 } 140 fmt.Fprintf(Stdout, "}\n") 141 142 return nil 143 } 144 145 func (c *cmdDebugState) showTasks(st *state.State, changeID string) error { 146 st.Lock() 147 defer st.Unlock() 148 149 chg := st.Change(changeID) 150 if chg == nil { 151 return fmt.Errorf("no such change: %s", changeID) 152 } 153 154 tasks := chg.Tasks() 155 sort.Sort(byLaneAndWaitTaskChain(tasks)) 156 157 w := tabwriter.NewWriter(Stdout, 5, 3, 2, ' ', 0) 158 fmt.Fprintf(w, "Lanes\tID\tStatus\tSpawn\tReady\tKind\tSummary\n") 159 for _, t := range tasks { 160 if c.NoHoldState && t.Status() == state.HoldStatus { 161 continue 162 } 163 var lanes []string 164 for _, lane := range t.Lanes() { 165 lanes = append(lanes, fmt.Sprintf("%d", lane)) 166 } 167 fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", 168 strings.Join(lanes, ","), 169 t.ID(), 170 t.Status().String(), 171 c.fmtTime(t.SpawnTime()), 172 c.fmtTime(t.ReadyTime()), 173 t.Kind(), 174 t.Summary()) 175 } 176 177 w.Flush() 178 179 for _, t := range tasks { 180 logs := t.Log() 181 if len(logs) > 0 { 182 fmt.Fprintf(Stdout, "---\n") 183 fmt.Fprintf(Stdout, "%s %s\n", t.ID(), t.Summary()) 184 for _, log := range logs { 185 fmt.Fprintf(Stdout, " %s\n", log) 186 } 187 } 188 } 189 190 return nil 191 } 192 193 func (c *cmdDebugState) showChanges(st *state.State) error { 194 st.Lock() 195 defer st.Unlock() 196 197 changes := st.Changes() 198 sort.Sort(byChangeID(changes)) 199 200 w := tabwriter.NewWriter(Stdout, 5, 3, 2, ' ', 0) 201 fmt.Fprintf(w, "ID\tStatus\tSpawn\tReady\tLabel\tSummary\n") 202 for _, chg := range changes { 203 fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", 204 chg.ID(), 205 chg.Status().String(), 206 c.fmtTime(chg.SpawnTime()), 207 c.fmtTime(chg.ReadyTime()), 208 chg.Kind(), 209 chg.Summary()) 210 } 211 w.Flush() 212 213 return nil 214 } 215 216 func (c *cmdDebugState) showIsSeeded(st *state.State) error { 217 st.Lock() 218 defer st.Unlock() 219 220 var isSeeded bool 221 err := st.Get("seeded", &isSeeded) 222 if err != nil && err != state.ErrNoState { 223 return err 224 } 225 fmt.Fprintf(Stdout, "%v\n", isSeeded) 226 227 return nil 228 } 229 230 func (c *cmdDebugState) showTask(st *state.State, taskID string) error { 231 st.Lock() 232 defer st.Unlock() 233 234 task := st.Task(taskID) 235 if task == nil { 236 return fmt.Errorf("no such task: %s", taskID) 237 } 238 239 termWidth, _ := termSize() 240 termWidth -= 3 241 if termWidth > 100 { 242 // any wider than this and it gets hard to read 243 termWidth = 100 244 } 245 246 // the output of 'debug task' is yaml'ish 247 fmt.Fprintf(Stdout, "id: %s\nkind: %s\nsummary: %s\nstatus: %s\n", 248 taskID, task.Kind(), 249 task.Summary(), 250 task.Status().String()) 251 log := task.Log() 252 if len(log) > 0 { 253 fmt.Fprintf(Stdout, "log: |\n") 254 for _, msg := range log { 255 if err := wrapLine(Stdout, []rune(msg), " ", termWidth); err != nil { 256 break 257 } 258 } 259 fmt.Fprintln(Stdout) 260 } 261 262 fmt.Fprintf(Stdout, "halt-tasks:") 263 if len(task.HaltTasks()) == 0 { 264 fmt.Fprintln(Stdout, " []") 265 } else { 266 fmt.Fprintln(Stdout) 267 for _, ht := range task.HaltTasks() { 268 fmt.Fprintf(Stdout, " - %s (%s)\n", ht.Kind(), ht.ID()) 269 } 270 } 271 272 return nil 273 } 274 275 func (c *cmdDebugState) Execute(args []string) error { 276 st, err := loadState(c.Positional.StateFilePath) 277 if err != nil { 278 return err 279 } 280 281 // check valid combinations of args 282 var cmds []string 283 if c.Changes { 284 cmds = append(cmds, "--changes") 285 } 286 if c.ChangeID != "" { 287 cmds = append(cmds, "--change=") 288 } 289 if c.TaskID != "" { 290 cmds = append(cmds, "--task=") 291 } 292 if c.IsSeeded != false { 293 cmds = append(cmds, "--is-seeded") 294 } 295 if len(cmds) > 1 { 296 return fmt.Errorf("cannot use %s and %s together", cmds[0], cmds[1]) 297 } 298 299 if c.IsSeeded { 300 return c.showIsSeeded(st) 301 } 302 303 if c.DotOutput && c.ChangeID == "" { 304 return fmt.Errorf("--dot can only be used with --change=") 305 } 306 if c.NoHoldState && c.ChangeID == "" { 307 return fmt.Errorf("--no-hold can only be used with --change=") 308 } 309 310 if c.Changes { 311 return c.showChanges(st) 312 } 313 314 if c.ChangeID != "" { 315 _, err := strconv.ParseInt(c.ChangeID, 0, 64) 316 if err != nil { 317 return fmt.Errorf("invalid change: %s", c.ChangeID) 318 } 319 if c.DotOutput { 320 return c.writeDotOutput(st, c.ChangeID) 321 } 322 return c.showTasks(st, c.ChangeID) 323 } 324 325 if c.TaskID != "" { 326 _, err := strconv.ParseInt(c.TaskID, 0, 64) 327 if err != nil { 328 return fmt.Errorf("invalid task: %s", c.TaskID) 329 } 330 return c.showTask(st, c.TaskID) 331 } 332 333 // show changes by default 334 return c.showChanges(st) 335 }