github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap/cmd_debug_timings_test.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_test
    21  
    22  import (
    23  	"fmt"
    24  	"net/http"
    25  	"strings"
    26  	"time"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/cmd/snap"
    31  )
    32  
    33  type timingsCmdArgs struct {
    34  	args, stdout, stderr, error string
    35  }
    36  
    37  var timingsTests = []timingsCmdArgs{{
    38  	args:  "debug timings",
    39  	error: "please provide change ID or type with --last=<type>, or query for --ensure=<name> or --startup=<name>",
    40  }, {
    41  	args:  "debug timings --ensure=seed 9",
    42  	error: "cannot use change id, 'startup' or 'ensure' together",
    43  }, {
    44  	args:  "debug timings --ensure=seed --startup=ifacemgr",
    45  	error: "cannot use change id, 'startup' or 'ensure' together",
    46  }, {
    47  	args:  "debug timings --last=install --all",
    48  	error: "cannot use 'all' with change id or 'last'",
    49  }, {
    50  	args:  "debug timings --last=remove",
    51  	error: `no changes of type "remove" found`,
    52  }, {
    53  	args:  "debug timings --startup=load-state 9",
    54  	error: "cannot use change id, 'startup' or 'ensure' together",
    55  }, {
    56  	args:  "debug timings --all 9",
    57  	error: "cannot use 'all' with change id or 'last'",
    58  }, {
    59  	args: "debug timings --last=install",
    60  	stdout: "ID   Status        Doing      Undoing  Summary\n" +
    61  		"40   Doing         910ms            -  lane 0 task bar summary\n" +
    62  		" ^                   1ms            -    foo summary\n" +
    63  		"  ^                  1ms            -      bar summary\n" +
    64  		"41   Done          210ms            -  lane 1 task baz summary\n" +
    65  		"42   Done          310ms            -  lane 1 task boo summary\n" +
    66  		"43   Done          310ms            -  lane 0 task doh summary\n\n",
    67  }, {
    68  	args: "debug timings 1",
    69  	stdout: "ID   Status        Doing      Undoing  Summary\n" +
    70  		"40   Doing         910ms            -  lane 0 task bar summary\n" +
    71  		" ^                   1ms            -    foo summary\n" +
    72  		"  ^                  1ms            -      bar summary\n" +
    73  		"41   Done          210ms            -  lane 1 task baz summary\n" +
    74  		"42   Done          310ms            -  lane 1 task boo summary\n" +
    75  		"43   Done          310ms            -  lane 0 task doh summary\n\n",
    76  }, {
    77  	args: "debug timings 1 --verbose",
    78  	stdout: "ID   Status        Doing      Undoing  Label  Summary\n" +
    79  		"40   Doing         910ms            -  bar    lane 0 task bar summary\n" +
    80  		" ^                   1ms            -  foo      foo summary\n" +
    81  		"  ^                  1ms            -  bar        bar summary\n" +
    82  		"41   Done          210ms            -  baz    lane 1 task baz summary\n" +
    83  		"42   Done          310ms            -  boo    lane 1 task boo summary\n" +
    84  		"43   Done          310ms            -  doh    lane 0 task doh summary\n\n",
    85  }, {
    86  	args: "debug timings --ensure=seed",
    87  	stdout: "ID    Status        Doing      Undoing  Summary\n" +
    88  		"seed                  8ms            -  \n" +
    89  		" ^                    8ms            -    baz summary\n" +
    90  		"  ^                   8ms            -      booze summary\n" +
    91  		"40    Doing         910ms            -  task bar summary\n" +
    92  		" ^                    1ms            -    foo summary\n" +
    93  		"  ^                   1ms            -      bar summary\n\n",
    94  }, {
    95  	args: "debug timings --ensure=seed --all",
    96  	stdout: "ID    Status        Doing      Undoing  Summary\n" +
    97  		"seed                  8ms            -  \n" +
    98  		" ^                    8ms            -    bar summary 1\n" +
    99  		" ^                    8ms            -    bar summary 2\n" +
   100  		"40    Doing         910ms            -  task bar summary\n" +
   101  		" ^                    1ms            -    foo summary\n" +
   102  		"  ^                   1ms            -      bar summary\n" +
   103  		"seed                  7ms            -  \n" +
   104  		" ^                    7ms            -    baz summary 2\n" +
   105  		"60    Doing         910ms            -  task bar summary\n" +
   106  		" ^                    1ms            -    foo summary\n" +
   107  		"  ^                   1ms            -      bar summary\n\n",
   108  }, {
   109  	args: "debug timings --ensure=seed --all --verbose",
   110  	stdout: "ID    Status        Doing      Undoing  Label  Summary\n" +
   111  		"seed                  8ms            -         \n" +
   112  		" ^                    8ms            -  abc      bar summary 1\n" +
   113  		" ^                    8ms            -  abc      bar summary 2\n" +
   114  		"40    Doing         910ms            -  bar    task bar summary\n" +
   115  		" ^                    1ms            -  foo      foo summary\n" +
   116  		"  ^                   1ms            -  bar        bar summary\n" +
   117  		"seed                  7ms            -         \n" +
   118  		" ^                    7ms            -  ghi      baz summary 2\n" +
   119  		"60    Doing         910ms            -  bar    task bar summary\n" +
   120  		" ^                    1ms            -  foo      foo summary\n" +
   121  		"  ^                   1ms            -  bar        bar summary\n\n",
   122  }, {
   123  	args: "debug timings --startup=ifacemgr",
   124  	stdout: "ID        Status        Doing      Undoing  Summary\n" +
   125  		"ifacemgr                  8ms            -  \n" +
   126  		" ^                        8ms            -    baz summary\n" +
   127  		"  ^                       8ms            -      booze summary\n\n",
   128  }, {
   129  	args: "debug timings --startup=ifacemgr --all",
   130  	stdout: "ID        Status        Doing      Undoing  Summary\n" +
   131  		"ifacemgr                  8ms            -  \n" +
   132  		" ^                        8ms            -    baz summary\n" +
   133  		"ifacemgr                  9ms            -  \n" +
   134  		" ^                        9ms            -    baz summary\n\n",
   135  }}
   136  
   137  func (s *SnapSuite) TestGetDebugTimings(c *C) {
   138  	s.mockCmdTimingsAPI(c)
   139  
   140  	restore := main.MockIsStdinTTY(true)
   141  	defer restore()
   142  
   143  	for _, test := range timingsTests {
   144  		s.stdout.Truncate(0)
   145  		s.stderr.Truncate(0)
   146  
   147  		c.Logf("Test: %s", test.args)
   148  
   149  		_, err := main.Parser(main.Client()).ParseArgs(strings.Fields(test.args))
   150  		if test.error != "" {
   151  			c.Check(err, ErrorMatches, test.error)
   152  		} else {
   153  			c.Check(err, IsNil)
   154  			c.Check(s.Stderr(), Equals, test.stderr)
   155  			c.Check(s.Stdout(), Equals, test.stdout)
   156  		}
   157  	}
   158  }
   159  
   160  func (s *SnapSuite) mockCmdTimingsAPI(c *C) {
   161  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   162  		c.Assert(r.Method, Equals, "GET")
   163  
   164  		if r.URL.Path == "/v2/debug" {
   165  			q := r.URL.Query()
   166  			aspect := q.Get("aspect")
   167  			c.Assert(aspect, Equals, "change-timings")
   168  
   169  			changeID := q.Get("change-id")
   170  			ensure := q.Get("ensure")
   171  			startup := q.Get("startup")
   172  			all := q.Get("all")
   173  
   174  			switch {
   175  			case changeID == "1":
   176  				// lane 0 and lane 1 tasks, interleaved
   177  				fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":[
   178  				{"change-id":"1", "change-timings":{
   179  					"41":{"doing-time":210000000, "status": "Done", "lane": 1, "ready-time": "2016-04-22T01:02:04Z", "kind": "baz", "summary": "lane 1 task baz summary"},
   180  					"43":{"doing-time":310000000, "status": "Done", "ready-time": "2016-04-25T01:02:04Z", "kind": "doh", "summary": "lane 0 task doh summary"},
   181  					"40":{"doing-time":910000000, "status": "Doing", "ready-time": "2016-04-20T00:00:00Z", "kind": "bar", "summary": "lane 0 task bar summary",
   182  						"doing-timings":[
   183  							{"label":"foo", "summary": "foo summary", "duration": 1000001},
   184  							{"level":1, "label":"bar", "summary": "bar summary", "duration": 1000002}
   185  						]},
   186  					"42":{"doing-time":310000000, "status": "Done", "lane": 1, "ready-time": "2016-04-23T01:02:04Z", "kind": "boo", "summary": "lane 1 task boo summary"}
   187  				}}]}`)
   188  			case ensure == "seed" && all == "false":
   189  				fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":[
   190  					{"change-id":"1",
   191  					    "total-duration": 8000002,
   192  						"ensure-timings": [
   193  								{"label":"baz", "summary": "baz summary", "duration": 8000001},
   194  								{"level":1, "label":"booze", "summary": "booze summary", "duration": 8000002}
   195  							],
   196  						"change-timings":{
   197  							"40":{"doing-time":910000000, "status": "Doing", "kind": "bar", "summary": "task bar summary",
   198  								"doing-timings":[
   199  									{"label":"foo", "summary": "foo summary", "duration": 1000001},
   200  									{"level":1, "label":"bar", "summary": "bar summary", "duration": 1000002}
   201  					]}}}]}`)
   202  			case ensure == "seed" && all == "true":
   203  				fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":[
   204  						{"change-id":"1",
   205  							"total-duration": 8000002,
   206  							"ensure-timings": [
   207  									{"label":"abc", "summary": "bar summary 1", "duration": 8000001},
   208  									{"label":"abc", "summary": "bar summary 2", "duration": 8000002}
   209  								],
   210  							"change-timings":{
   211  								"40":{"doing-time":910000000, "status": "Doing", "kind": "bar", "summary": "task bar summary",
   212  									"doing-timings":[
   213  										{"label":"foo", "summary": "foo summary", "duration": 1000001},
   214  										{"level":1, "label":"bar", "summary": "bar summary", "duration": 1000002}
   215  								]}}},
   216  						{"change-id":"2",
   217  							"total-duration": 7000002,
   218  							"ensure-timings": [{"label":"ghi", "summary": "baz summary 2", "duration": 7000002}],
   219  							"change-timings":{
   220  								"60":{"doing-time":910000000, "status": "Doing", "kind": "bar", "summary": "task bar summary",
   221  									"doing-timings":[
   222  										{"label":"foo", "summary": "foo summary", "duration": 1000001},
   223  										{"level":1, "label":"bar", "summary": "bar summary", "duration": 1000002}
   224  								]}}}]}`)
   225  			case startup == "ifacemgr" && all == "false":
   226  				fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":[
   227  					{"total-duration": 8000002, "startup-timings": [
   228  								{"label":"baz", "summary": "baz summary", "duration": 8000001},
   229  								{"level":1, "label":"booze", "summary": "booze summary", "duration": 8000002}
   230  					]}]}`)
   231  			case startup == "ifacemgr" && all == "true":
   232  				fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":[
   233  					{"total-duration": 8000002, "startup-timings": [
   234  						{"label":"baz", "summary": "baz summary", "duration": 8000001}
   235  					]},
   236  					{"total-duration": 9000002, "startup-timings": [
   237  						{"label":"baz", "summary": "baz summary", "duration": 9000001}
   238  					]}]}`)
   239  			default:
   240  				c.Errorf("unexpected request: %s, %s, %s", changeID, ensure, all)
   241  			}
   242  			return
   243  		}
   244  
   245  		// request for all changes on --last=...
   246  		if r.URL.Path == "/v2/changes" {
   247  			fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":[{
   248  				"id":   "1",
   249  				"kind": "install-snap",
   250  				"summary": "a",
   251  				"status": "Doing",
   252  				"ready": false,
   253  				"spawn-time": "2016-04-21T01:02:03Z",
   254  				"ready-time": "2016-04-21T01:02:04Z",
   255  				"tasks": [{"id":"99", "kind": "bar", "summary": ".", "status": "Doing", "progress": {"done": 0, "total": 1}, "spawn-time": "2016-04-21T01:02:03Z", "ready-time": "2016-04-21T01:02:04Z"}]
   256  			  }]}`)
   257  			return
   258  		}
   259  		c.Errorf("unexpected path %q", r.URL.Path)
   260  	})
   261  }
   262  
   263  type TaskDef struct {
   264  	TaskID    string
   265  	Lane      int
   266  	ReadyTime time.Time
   267  }
   268  
   269  func (s *SnapSuite) TestSortTimingsTasks(c *C) {
   270  	mkTime := func(timeStr string) time.Time {
   271  		t, err := time.Parse(time.RFC3339, timeStr)
   272  		c.Assert(err, IsNil)
   273  		return t
   274  	}
   275  
   276  	testData := []struct {
   277  		ChangeTimings map[string]main.ChangeTimings
   278  		Expected      []string
   279  	}{{
   280  		// nothing to do
   281  		ChangeTimings: map[string]main.ChangeTimings{},
   282  		Expected:      []string{},
   283  	}, {
   284  		ChangeTimings: map[string]main.ChangeTimings{
   285  			// tasks in lane 0 only
   286  			"1": {ReadyTime: mkTime("2019-04-21T00:00:00Z")},
   287  			"2": {ReadyTime: mkTime("2019-05-21T00:00:00Z")},
   288  			"3": {ReadyTime: mkTime("2019-02-21T00:00:00Z")},
   289  			"4": {ReadyTime: mkTime("2019-03-21T00:00:00Z")},
   290  			"5": {ReadyTime: mkTime("2019-01-21T00:00:00Z")},
   291  		},
   292  		Expected: []string{"5", "3", "4", "1", "2"},
   293  	}, {
   294  		// task in lane 1 with a task in lane 0 before and after it
   295  		ChangeTimings: map[string]main.ChangeTimings{
   296  			"1": {Lane: 1, ReadyTime: mkTime("2019-01-21T00:00:00Z")},
   297  			"2": {Lane: 0, ReadyTime: mkTime("2019-01-20T00:00:00Z")},
   298  			"3": {Lane: 0, ReadyTime: mkTime("2019-01-22T00:00:00Z")},
   299  		},
   300  		Expected: []string{"2", "1", "3"},
   301  	}, {
   302  		// tasks in lane 1 only
   303  		ChangeTimings: map[string]main.ChangeTimings{
   304  			"1": {Lane: 1, ReadyTime: mkTime("2019-01-21T00:00:00Z")},
   305  			"2": {Lane: 1, ReadyTime: mkTime("2019-01-20T00:00:00Z")},
   306  			"3": {Lane: 1, ReadyTime: mkTime("2019-01-16T00:00:00Z")},
   307  		},
   308  		Expected: []string{"3", "2", "1"},
   309  	}, {
   310  		// tasks in lanes 0, 1, 2 with tasks from line 0 before and after lanes 1, 2
   311  		ChangeTimings: map[string]main.ChangeTimings{
   312  			"1": {Lane: 1, ReadyTime: mkTime("2019-01-21T00:00:00Z")},
   313  			"2": {Lane: 0, ReadyTime: mkTime("2019-01-19T00:00:00Z")},
   314  			"3": {Lane: 2, ReadyTime: mkTime("2019-01-20T00:00:00Z")},
   315  			"4": {Lane: 0, ReadyTime: mkTime("2019-01-25T00:00:00Z")},
   316  			"5": {Lane: 1, ReadyTime: mkTime("2019-01-20T00:00:00Z")},
   317  			"6": {Lane: 2, ReadyTime: mkTime("2019-01-21T00:00:00Z")},
   318  			"7": {Lane: 0, ReadyTime: mkTime("2019-01-18T00:00:00Z")},
   319  			"8": {Lane: 0, ReadyTime: mkTime("2019-01-27T00:00:00Z")},
   320  		},
   321  		Expected: []string{"7", "2", "5", "1", "3", "6", "4", "8"},
   322  	}, {
   323  		// pathological case: lane 0 tasks have ready-time between lane 1 tasks
   324  		ChangeTimings: map[string]main.ChangeTimings{
   325  			"1": {Lane: 1, ReadyTime: mkTime("2019-01-20T00:00:00Z")},
   326  			"2": {Lane: 1, ReadyTime: mkTime("2019-01-30T00:00:00Z")},
   327  			"3": {Lane: 0, ReadyTime: mkTime("2019-01-27T00:00:00Z")},
   328  			"4": {Lane: 0, ReadyTime: mkTime("2019-01-25T00:00:00Z")},
   329  		},
   330  		Expected: []string{"1", "2", "4", "3"},
   331  	}}
   332  
   333  	for _, data := range testData {
   334  		tasks := main.SortTimingsTasks(data.ChangeTimings)
   335  		c.Check(tasks, DeepEquals, data.Expected)
   336  	}
   337  }