Beispiel #1
0
func (f *Factory) Make(service string, instanceId uint, data []byte) (sysconfig.Monitor, error) {
	var monitor sysconfig.Monitor
	switch service {
	case "mysql":
		// Load the MySQL instance info (DSN, name, etc.).
		mysqlIt := &proto.MySQLInstance{}
		if err := f.ir.Get(service, instanceId, mysqlIt); err != nil {
			return nil, err
		}

		// Parse the MySQL sysconfig config.
		config := &mysql.Config{}
		if err := json.Unmarshal(data, config); err != nil {
			return nil, err
		}

		// The user-friendly name of the service, e.g. sysconfig-mysql-db101:
		alias := "sysconfig-mysql-" + mysqlIt.Hostname

		// Make a MySQL sysconfig monitor.
		monitor = mysql.NewMonitor(
			alias,
			config,
			pct.NewLogger(f.logChan, alias),
			mysqlConn.NewConnection(mysqlIt.DSN),
		)
	default:
		return nil, errors.New("Unknown sysconfig monitor type: " + service)
	}
	return monitor, nil
}
Beispiel #2
0
// Even having a wrong user/pass, the monitor should be able to start
// It won't be able to connect to the DB, but it should start without
// blocking.
// This test if for cases where the agent starts but MySQL is down
func (s *TestSuite) TestStartWithInvalidDSN(t *C) {
	config := &mysql.Config{
		Config: mm.Config{
			ServiceInstance: proto.ServiceInstance{
				Service:    "mysql",
				InstanceId: 1,
			},
			Collect: 1,
			Report:  60,
		},
		Status: map[string]string{
			"threads_connected": "gauge",
			"threads_running":   "gauge",
		},
	}

	failDsn := "user:pass@tcp(127.0.0.2:3309)/"
	m := mysql.NewMonitor(s.name, config, s.logger, mysqlConn.NewConnection(failDsn), s.mrm)
	if m == nil {
		t.Fatal("Make new mysql.Monitor")
	}

	err := m.Start(s.tickChan, s.collectionChan)
	t.Assert(err, IsNil)
}
Beispiel #3
0
func (s *MysqlTestSuite) TestConnection(t *C) {
	conn := mysql.NewConnection(s.dsn)
	err := conn.Connect(1)
	t.Assert(err, IsNil)
	conn1 := conn.DB()

	err = conn.Connect(1)
	t.Assert(err, IsNil)
	conn2 := conn.DB()
	t.Check(conn1, Equals, conn2)

	conn.Close()
	t.Assert(conn.DB(), NotNil)

	/**
	 * we still have open connection,
	 * because we used Connect twice,
	 * so let's close it
	 */
	conn.Close()
	t.Assert(conn.DB(), IsNil)

	// lets test accidental extra closing
	conn.Close()
	t.Assert(conn.DB(), IsNil)
}
Beispiel #4
0
func NewSlowMySQL(dsn string) *SlowMySQL {
	n := &SlowMySQL{
		realConnection: mysql.NewConnection(dsn),
		globalDelay:    0,
	}
	return n
}
Beispiel #5
0
func (s *MysqlTestSuite) TestMissingSocketError(t *C) {
	// https://jira.percona.com/browse/PCT-791
	conn := mysql.NewConnection("percona:percona@unix(/foo/bar/my.sock)/")
	err := conn.Connect(1)
	t.Assert(
		fmt.Sprintf("%s", err),
		Equals,
		"Cannot connect to MySQL percona:<password-hidden>@unix(/foo/bar/my.sock)/: no such file or directory: /foo/bar/my.sock",
	)
}
Beispiel #6
0
func (i *Installer) createNewMySQLUser() (dsn mysql.DSN, err error) {
	// Auto-detect the root MySQL user connection options.
	superUserDSN := i.defaultDSN
	if i.flags.Bool["auto-detect-mysql"] {
		if err := i.autodetectDSN(&superUserDSN); err != nil {
			if i.flags.Bool["debug"] {
				log.Println(err)
			}
		}
	}
	fmt.Printf("MySQL root DSN: %s\n", superUserDSN)

	// Try to connect as root automatically.  If this fails and interactive is true,
	// start prompting user to enter valid root MySQL connection info.
	if err = i.verifyMySQLConnection(superUserDSN); err != nil {
		fmt.Printf("Error connecting to MySQL %s: %s\n", superUserDSN, err)
		if i.flags.Bool["interactive"] {
			if again, err := i.term.PromptBool("Try again?", "Y"); err != nil {
				return superUserDSN, err
			} else if !again {
				return superUserDSN, fmt.Errorf("Failed to connect to MySQL")
			}
			fmt.Println("Specify a root/super MySQL user to create a user for the agent")
			if err := i.getDSNFromUser(&superUserDSN); err != nil {
				return dsn, err
			}
		} else {
			// Can't auto-detect MySQL root user and not interactive, fail.
			return dsn, err
		}
	}

	// Check MySQL Version
	dsnString, err := superUserDSN.DSN()
	if err != nil {
		return dsn, err
	}
	tmpConn := mysql.NewConnection(dsnString)
	isVersionSupported, err := i.IsVersionSupported(tmpConn)
	if err != nil {
		return dsn, err
	}

	if !isVersionSupported {
		return dsn, fmt.Errorf("MySQL version not supported. It should be > %s", agent.MIN_SUPPORTED_MYSQL_VERSION)
	}

	dsn, err = i.createMySQLUser(superUserDSN)
	if err != nil {
		return dsn, err
	}

	return dsn, nil
}
Beispiel #7
0
func (s *TestSuite) SetUpSuite(t *C) {
	s.dsn = os.Getenv("PCT_TEST_MYSQL_DSN")
	if s.dsn == "" {
		t.Fatal("PCT_TEST_MYSQL_DSN is not set")
	}

	s.conn = mysql.NewConnection(s.dsn)
	if err := s.conn.Connect(1); err != nil {
		t.Fatal(err)
	}
}
Beispiel #8
0
func (s *ManagerTestSuite) SetUpSuite(t *C) {
	s.dsn = os.Getenv("PCT_TEST_MYSQL_DSN")
	if s.dsn == "" {
		t.Fatal("PCT_TEST_MYSQL_DSN is not set")
	}
	s.realmysql = mysql.NewConnection(s.dsn)
	if err := s.realmysql.Connect(1); err != nil {
		t.Fatal(err)
	}
	s.reset = []mysql.Query{
		mysql.Query{Set: "SET GLOBAL slow_query_log=OFF"},
		mysql.Query{Set: "SET GLOBAL long_query_time=10"},
	}

	s.nullmysql = mock.NewNullMySQL()

	s.logChan = make(chan *proto.LogEntry, 1000)
	s.logger = pct.NewLogger(s.logChan, "qan-test")

	s.intervalChan = make(chan *qan.Interval, 1)
	s.iter = mock.NewMockIntervalIter(s.intervalChan)
	s.iterFactory = &mock.IntervalIterFactory{
		Iters:     []qan.IntervalIter{s.iter},
		TickChans: make(map[qan.IntervalIter]chan time.Time),
	}

	s.dataChan = make(chan interface{}, 2)
	s.spool = mock.NewSpooler(s.dataChan)
	s.workerFactory = &qan.SlowLogWorkerFactory{}

	var err error
	s.tmpDir, err = ioutil.TempDir("/tmp", "agent-test")
	t.Assert(err, IsNil)

	if err := pct.Basedir.Init(s.tmpDir); err != nil {
		t.Fatal(err)
	}
	s.configDir = pct.Basedir.Dir("config")

	s.im = instance.NewRepo(pct.NewLogger(s.logChan, "im-test"), s.configDir, s.api)
	data, err := json.Marshal(&proto.MySQLInstance{
		Hostname: "db1",
		DSN:      s.dsn,
	})
	t.Assert(err, IsNil)
	s.im.Add("mysql", 1, data, false)
	s.mysqlInstance = proto.ServiceInstance{Service: "mysql", InstanceId: 1}

	links := map[string]string{
		"agent":     "http://localhost/agent",
		"instances": "http://localhost/instances",
	}
	s.api = mock.NewAPI("http://localhost", "http://localhost", "123", "abc-123-def", links)
}
Beispiel #9
0
func (i *Installer) useExistingMySQLUser() (mysql.DSN, error) {
	userDSN := i.defaultDSN
	userDSN.Username = "******"
	userDSN.Password = ""
	if i.flags.Bool["auto-detect-mysql"] {
		if err := i.autodetectDSN(&userDSN); err != nil {
			if i.flags.Bool["debug"] {
				log.Println(err)
			}
		}
	}
	for {
		// Let user specify the MySQL account to use for the agent.
		fmt.Println("Specify the existing MySQL user to use for the agent")
		if err := i.getDSNFromUser(&userDSN); err != nil {
			return userDSN, nil
		}

		// Verify DSN provided by user
		if err := i.verifyMySQLConnection(userDSN); err != nil {
			fmt.Printf("Error connecting to MySQL %s: %s\n", userDSN, err)
			if i.flags.Bool["interactive"] {
				if again, err := i.term.PromptBool("Try again?", "Y"); err != nil {
					return userDSN, err
				} else if !again {
					return userDSN, fmt.Errorf("Failed to connect to MySQL")
				}
				continue // again
			} else {
				// Can't auto-detect MySQL root user and not interactive, fail.
				return userDSN, err
			}
		}
		break
	}

	// Check MySQL Version
	dsnString, err := userDSN.DSN()
	if err != nil {
		return userDSN, err
	}
	tmpConn := mysql.NewConnection(dsnString)
	isVersionSupported, err := i.IsVersionSupported(tmpConn)
	if err != nil {
		return userDSN, err
	}
	if !isVersionSupported {
		return userDSN, fmt.Errorf("MySQL version not supported. It should be > %s", agent.MIN_SUPPORTED_MYSQL_VERSION)
	}
	return userDSN, nil // success
}
Beispiel #10
0
func TestMySQLConnection(dsn mysql.DSN) error {
	dsnString, err := dsn.DSN()
	if err != nil {
		return err
	}

	fmt.Printf("Testing MySQL connection %s...\n", dsn)
	conn := mysql.NewConnection(dsnString)
	if err := conn.Connect(1); err != nil {
		return err
	}
	defer conn.Close()
	return nil
}
Beispiel #11
0
func (i *Installer) verifyMySQLConnection(dsn mysql.DSN) (err error) {
	dsnString, err := dsn.DSN()
	if err != nil {
		return err
	}
	if i.flags.Bool["debug"] {
		log.Printf("verifyMySQLConnection: %#v %s\n", dsn, dsnString)
	}
	conn := mysql.NewConnection(dsnString)
	if err := conn.Connect(1); err != nil {
		return err
	}
	conn.Close()
	return nil
}
Beispiel #12
0
func (f *Factory) Make(service string, instanceId uint, data []byte) (mm.Monitor, error) {
	var monitor mm.Monitor
	switch service {
	case "mysql":
		// Load the MySQL instance info (DSN, name, etc.).
		mysqlIt := &proto.MySQLInstance{}
		if err := f.ir.Get(service, instanceId, mysqlIt); err != nil {
			return nil, err
		}

		// Parse the MySQL sysconfig config.
		config := &mysql.Config{}
		if err := json.Unmarshal(data, config); err != nil {
			return nil, err
		}

		// The user-friendly name of the service, e.g. sysconfig-mysql-db101:
		alias := "mm-mysql-" + mysqlIt.Hostname

		// Make a MySQL metrics monitor.
		monitor = mysql.NewMonitor(
			alias,
			config,
			pct.NewLogger(f.logChan, alias),
			mysqlConn.NewConnection(mysqlIt.DSN),
			f.mrm,
		)
	case "server":
		// Parse the system mm config.
		config := &system.Config{}
		if err := json.Unmarshal(data, config); err != nil {
			return nil, err
		}

		// Only one system for now, so no SystemInstance and no  "-instanceName" suffix.
		alias := "mm-system"

		// Make a MySQL metrics monitor.
		monitor = system.NewMonitor(
			alias,
			config,
			pct.NewLogger(f.logChan, alias),
		)
	default:
		return nil, errors.New("Unknown metrics monitor type: " + service)
	}
	return monitor, nil
}
Beispiel #13
0
func (i *Installer) createMySQLUser(dsn mysql.DSN) (mysql.DSN, error) {
	// Same host:port or socket, but different user and pass.
	userDSN := dsn
	userDSN.Username = "******"
	userDSN.Password = fmt.Sprintf("%p%d", &dsn, rand.Uint32())
	userDSN.OldPasswords = i.flags.Bool["old-passwords"]

	dsnString, _ := dsn.DSN()
	conn := mysql.NewConnection(dsnString)
	if err := conn.Connect(1); err != nil {
		return userDSN, err
	}
	defer conn.Close()
	grants := MakeGrant(dsn, userDSN.Username, userDSN.Password, i.flags.Int64["mysql-max-user-connections"])
	for _, grant := range grants {
		if i.flags.Bool["debug"] {
			log.Println(grant)
		}
		_, err := conn.DB().Exec(grant)
		if err != nil {
			return userDSN, fmt.Errorf("Error executing %s: %s", grant, err)
		}
	}

	// Go MySQL driver resolves localhost to 127.0.0.1 but localhost is a special
	// value for MySQL, so 127.0.0.1 may not work with a grant @localhost, so we
	// add a 2nd grant @127.0.0.1 to be sure.
	if dsn.Hostname == "localhost" {
		dsn2 := dsn
		dsn2.Hostname = "127.0.0.1"
		grants := MakeGrant(dsn2, userDSN.Username, userDSN.Password, i.flags.Int64["mysql-max-user-connections"])
		for _, grant := range grants {
			if i.flags.Bool["debug"] {
				log.Println(grant)
			}
			_, err := conn.DB().Exec(grant)
			if err != nil {
				return userDSN, fmt.Errorf("Error executing %s: %s", grant, err)
			}
		}
	}

	return userDSN, nil
}
Beispiel #14
0
func GetMySQLInfo(it *proto.MySQLInstance) error {
	conn := mysql.NewConnection(it.DSN)
	if err := conn.Connect(1); err != nil {
		return err
	}
	defer conn.Close()
	sql := "SELECT /* percona-agent */" +
		" CONCAT_WS('.', @@hostname, IF(@@port='3306',NULL,@@port)) AS Hostname," +
		" @@version_comment AS Distro," +
		" @@version AS Version"
	err := conn.DB().QueryRow(sql).Scan(
		&it.Hostname,
		&it.Distro,
		&it.Version,
	)
	if err != nil {
		return err
	}
	return nil
}
Beispiel #15
0
func (s *WorkerTestSuite) TestWorkerWithAnotherTZ(t *C) {
	if s.dsn == "" {
		t.Fatal("PCT_TEST_MYSQL_DSN is not set")
	}
	mysqlConn := mysql.NewConnection(s.dsn)
	err := mysqlConn.Connect(1)
	t.Assert(err, IsNil)
	defer mysqlConn.Close()
	mysqlConn.DB().Exec(`SET @@global.time_zone="-1:00"`)
	mysqlConn.DB().Exec(`SET time_zone="-1:00"`)
	// Reset the timezone or other tests could fail
	mysqlConn.DB().Exec(`SET @@global.time_zone="+00:00"`)
	defer mysqlConn.DB().Exec(`SET time_zone="+00:00"`)

	i := &qan.Interval{
		Number:      1,
		StartTime:   s.now,
		StopTime:    s.now.Add(1 * time.Minute),
		Filename:    inputDir + "slow001.log",
		StartOffset: 0,
		EndOffset:   524,
	}
	got, err := s.RunWorker(s.config, mysqlConn, i)
	t.Check(err, IsNil)
	expect := &qan.Result{}
	test.LoadMmReport(outputDir+"slow001.json", expect)

	// I'm going to use the same results for TestWorkerSlow001
	// But simulating tz = UTC -1

	expect.Class[0].Example.Ts = "2007-10-15 22:45:10"
	expect.Class[1].Example.Ts = "2007-10-15 22:43:52"

	sort.Sort(ByQueryId(got.Class))
	sort.Sort(ByQueryId(expect.Class))
	if ok, diff := IsDeeply(got, expect); !ok {
		Dump(got)
		t.Error(diff)
	}
}
Beispiel #16
0
func (s *TestSuite) TestCollectUserstats(t *C) {
	/**
	 * Disable and reset user stats.
	 */
	if _, err := s.db.Exec("set global userstat = off"); err != nil {
		t.Fatal(err)
	}
	if _, err := s.db.Exec("flush user_statistics"); err != nil {
		t.Fatal(err)
	}
	if _, err := s.db.Exec("flush index_statistics"); err != nil {
		t.Fatal(err)
	}

	config := &mysql.Config{
		Config: mm.Config{
			ServiceInstance: proto.ServiceInstance{
				Service:    "mysql",
				InstanceId: 1,
			},
			Collect: 1,
			Report:  60,
		},
		UserStats: true,
	}

	m := mysql.NewMonitor(s.name, config, s.logger, mysqlConn.NewConnection(dsn), s.mrm)
	if m == nil {
		t.Fatal("Make new mysql.Monitor")
	}

	err := m.Start(s.tickChan, s.collectionChan)
	if err != nil {
		t.Fatalf("Start monitor without error, got %s", err)
	}

	if ok := test.WaitStatus(5, m, s.name+"-mysql", "Connected"); !ok {
		t.Fatal("Monitor is ready")
	}

	var user, host string
	err = s.db.QueryRow("SELECT SUBSTRING_INDEX(CURRENT_USER(),'@',1) AS 'user', SUBSTRING_INDEX(CURRENT_USER(),'@',-1) AS host").Scan(&user, &host)
	if err != nil {
		t.Fatal(err)
	}

	// To get index stats, we need to use an index: mysq.user PK <host, user>
	rows, err := s.db.Query("select * from mysql.user where host=? and user=?", host, user)
	if err != nil {
		t.Fatal(err)
	}
	defer rows.Close()

	s.tickChan <- time.Now()
	got := test.WaitCollection(s.collectionChan, 1)
	if len(got) == 0 {
		t.Fatal("Got a collection after tick")
	}
	c := got[0]

	/**
	 * Monitor should have collected the user stats: just table and index.
	 * Values vary a little, but there should be a table metric for mysql.user
	 * because login uses this table.
	 */
	if len(c.Metrics) < 1 {
		t.Fatalf("Collect at least 1 user stat metric; got %+v", c.Metrics)
	}

	var tblStat mm.Metric
	var idxStat mm.Metric
	for _, m := range c.Metrics {
		switch m.Name {
		case "mysql/db.mysql/t.user/rows_read":
			tblStat = m
		case "mysql/db.mysql/t.user/idx.PRIMARY/rows_read":
			idxStat = m
		}
	}

	// At least 2 rows should have been read from mysql.user:
	//   1: our db connection
	//   2: the monitor's db connection
	if tblStat.Number < 2 {
		t.Errorf("mysql/db.mysql/t.user/rows_read >= 2, got %+v", tblStat)
	}

	// At least 1 index read on mysql.user PK due to our SELECT ^.
	if idxStat.Number < 1 {
		t.Errorf("mysql/db.mysql/t.user/idx.PRIMARY/rows_read >= 1, got %+v", idxStat)
	}

	// Stop montior, clean up.
	m.Stop()
}
Beispiel #17
0
func (s *TestSuite) TestStartCollectStop(t *C) {
	// Create the monitor.
	config := &mysql.Config{
		Config: sysconfig.Config{
			ServiceInstance: proto.ServiceInstance{
				Service:    "mysql",
				InstanceId: 1,
			},
		},
	}
	m := mysql.NewMonitor(s.name, config, s.logger, mysqlConn.NewConnection(dsn))
	if m == nil {
		t.Fatal("Make new mysql.Monitor")
	}

	// Start the monitor.
	err := m.Start(s.tickChan, s.reportChan)
	if err != nil {
		t.Fatalf("Start monitor without error, got %s", err)
	}

	// monitor=Ready once it has successfully connected to MySQL.  This may
	// take a few seconds (hopefully < 5) on a slow test machine.
	if ok := test.WaitStatusPrefix(5, m, s.name, "Idle"); !ok {
		t.Fatal("Monitor is ready")
	}

	// Send tick to make the monitor collect.
	now := time.Now().UTC()
	s.tickChan <- now
	got := test.WaitSystemConfig(s.reportChan, 1)
	if len(got) == 0 {
		t.Fatal("Got a sysconfig after tick")
	}
	c := got[0]

	if c.Ts != now.Unix() {
		t.Error("Report.Ts set to %s; got %s", now.Unix(), c.Ts)
	}

	if len(c.Settings) < 100 {
		t.Fatal("Collect > 100 vars; got %+v", c.Settings)
	}

	haveWaitTimeout := false
	val := ""
	for _, s := range c.Settings {
		if s[0] == "wait_timeout" {
			haveWaitTimeout = true
			val = s[1]
		}
	}
	if !haveWaitTimeout {
		t.Logf("%+v\n", c)
		t.Error("Got wait_timeout")
	}
	if val == "" {
		t.Error("wait_timeout has value")
	}

	/**
	 * Stop the monitor.
	 */

	m.Stop()

	if ok := test.WaitStatus(5, m, s.name, "Stopped"); !ok {
		t.Fatal("Monitor has stopped")
	}
}
func (s *ManagerTestSuite) TestHandleGetInfoMySQL(t *C) {
	if dsn == "" {
		t.Fatal("PCT_TEST_MYSQL_DSN is not set")
	}

	/**
	 * First get MySQL info manually.  This is what GetInfo should do, too.
	 */

	conn := mysql.NewConnection(dsn)
	if err := conn.Connect(1); err != nil {
		t.Fatal(err)
	}
	var hostname, distro, version string
	sql := "SELECT" +
		" CONCAT_WS('.', @@hostname, IF(@@port='3306',NULL,@@port)) AS Hostname," +
		" @@version_comment AS Distro," +
		" @@version AS Version"
	if err := conn.DB().QueryRow(sql).Scan(&hostname, &distro, &version); err != nil {
		t.Fatal(err)
	}

	/**
	 * Now use the instance manager and GetInfo to get MySQL info like API would.
	 */

	// Create an instance manager.
	m := instance.NewManager(s.logger, s.configDir, s.api)
	t.Assert(m, NotNil)

	// API sends Cmd[Service:"instance", Cmd:"GetInfo",
	//               Data:proto.ServiceInstance[Service:"mysql",
	//                                          Data:proto.MySQLInstance[]]]
	// Only DSN is needed.  We set Id just to test that it's not changed.
	mysqlIt := &proto.MySQLInstance{
		Id:  9,
		DSN: dsn,
	}
	mysqlData, err := json.Marshal(mysqlIt)
	t.Assert(err, IsNil)

	serviceIt := &proto.ServiceInstance{
		Service:  "mysql",
		Instance: mysqlData,
	}
	serviceData, err := json.Marshal(serviceIt)
	t.Assert(err, IsNil)

	cmd := &proto.Cmd{
		Cmd:     "GetInfo",
		Service: "instance",
		Data:    serviceData,
	}

	reply := m.Handle(cmd)

	got := &proto.MySQLInstance{}
	err = json.Unmarshal(reply.Data, got)
	t.Assert(err, IsNil)

	t.Check(got.Id, Equals, uint(9))        // not changed
	t.Check(got.DSN, Equals, mysqlIt.DSN)   // not changed
	t.Check(got.Hostname, Equals, hostname) // new
	t.Check(got.Distro, Equals, distro)     // new
	t.Check(got.Version, Equals, version)   // new
}
func (s *AnalyzerTestSuite) TestRealSlowLogWorker(t *C) {
	dsn := os.Getenv("PCT_TEST_MYSQL_DSN")
	if dsn == "" {
		t.Fatal("PCT_TEST_MYSQL_DSN is not set")
	}
	realmysql := mysql.NewConnection(dsn)
	if err := realmysql.Connect(1); err != nil {
		t.Fatal(err)
	}
	// Don't release all resources immediately because the worker needs the connection
	defer realmysql.Close()
	defer test.DrainRecvData(s.dataChan)

	config := s.config
	config.Start = []mysql.Query{
		mysql.Query{Set: "SET GLOBAL slow_query_log=OFF"},
		mysql.Query{Set: "SET GLOBAL long_query_time=0"},
		mysql.Query{Set: "SET GLOBAL slow_query_log=ON"},
	}
	config.Stop = []mysql.Query{
		mysql.Query{Set: "SET GLOBAL slow_query_log=OFF"},
		mysql.Query{Set: "SET GLOBAL long_query_time=10"},
	}

	worker := slowlog.NewWorker(pct.NewLogger(s.logChan, "qan-worker"), config, realmysql)
	//intervalChan := make(chan *qan.Interval, 1)
	//iter := mock.NewIter(intervalChan)

	a := qan.NewRealAnalyzer(
		pct.NewLogger(s.logChan, "qan-analyzer"),
		config,
		s.iter,
		realmysql,
		s.restartChan,
		worker,
		s.clock,
		s.spool,
	)
	err := a.Start()
	t.Assert(err, IsNil)
	if !test.WaitStatus(3, a, "qan-analyzer", "Idle") {
		t.Fatal("Timeout waiting for qan-analyzer=Idle")
	}

	now := time.Now().UTC()
	i := &qan.Interval{
		Number:      1,
		StartTime:   now,
		StopTime:    now.Add(1 * time.Minute),
		Filename:    inputDir + "slow001.log",
		StartOffset: 0,
		EndOffset:   524,
	}
	s.intervalChan <- i
	data := test.WaitData(s.dataChan)
	t.Assert(data, HasLen, 1)
	res := data[0].(*qan.Report)
	t.Check(res.Global.TotalQueries, Equals, uint64(2))

	err = a.Stop()
	t.Assert(err, IsNil)
}
func (s *WorkerTestSuite) TestRealWorker(t *C) {
	if s.dsn == "" {
		t.Fatal("PCT_TEST_MYSQL_DSN is not set")
	}
	mysqlConn := mysql.NewConnection(s.dsn)
	err := mysqlConn.Connect(1)
	t.Assert(err, IsNil)
	defer mysqlConn.Close()

	f := perfschema.NewRealWorkerFactory(s.logChan)
	w := f.Make("qan-worker", mysqlConn)

	start := []mysql.Query{
		mysql.Query{Verify: "performance_schema", Expect: "1"},
		mysql.Query{Set: "UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'statements_digest'"},
		mysql.Query{Set: "UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME LIKE 'statement/sql/%'"},
		mysql.Query{Set: "TRUNCATE performance_schema.events_statements_summary_by_digest"},
	}
	if err := mysqlConn.Set(start); err != nil {
		t.Fatal(err)
	}
	stop := []mysql.Query{
		mysql.Query{Set: "UPDATE performance_schema.setup_consumers SET ENABLED = 'NO' WHERE NAME = 'statements_digest'"},
		mysql.Query{Set: "UPDATE performance_schema.setup_instruments SET ENABLED = 'NO', TIMED = 'NO' WHERE NAME LIKE 'statement/sql/%'"},
	}
	defer func() {
		if err := mysqlConn.Set(stop); err != nil {
			t.Fatal(err)
		}
	}()

	// SCHEMA_NAME: NULL
	//      DIGEST: fbe070dfb47e4a2401c5be6b5201254e
	// DIGEST_TEXT: SELECT ? FROM DUAL
	_, err = mysqlConn.DB().Exec("SELECT 'teapot' FROM DUAL")

	// First interval.
	err = w.Setup(&qan.Interval{Number: 1, StartTime: time.Now().UTC()})
	t.Assert(err, IsNil)

	res, err := w.Run()
	t.Assert(err, IsNil)
	t.Check(res, IsNil)

	err = w.Cleanup()
	t.Assert(err, IsNil)

	// Some query activity between intervals.
	_, err = mysqlConn.DB().Exec("SELECT 'teapot' FROM DUAL")
	time.Sleep(1 * time.Second)

	// Second interval and a result.
	err = w.Setup(&qan.Interval{Number: 2, StartTime: time.Now().UTC()})
	t.Assert(err, IsNil)

	res, err = w.Run()
	t.Assert(res, NotNil)
	if len(res.Class) == 0 {
		t.Fatal("Expected len(res.Class) > 0")
	}
	var class *event.QueryClass
	for _, c := range res.Class {
		if c.Fingerprint == "SELECT ? FROM DUAL " {
			class = c
			break
		}
	}
	t.Assert(class, NotNil)
	// Digests on different versions or distros of MySQL don't match
	//t.Check(class.Id, Equals, "01C5BE6B5201254E")
	//t.Check(class.Fingerprint, Equals, "SELECT ? FROM DUAL ")
	queryTime := class.Metrics.TimeMetrics["Query_time"]
	if queryTime.Min == 0 {
		t.Error("Expected Query_time_min > 0")
	}
	if queryTime.Max == 0 {
		t.Error("Expected Query_time_max > 0")
	}
	if queryTime.Avg == 0 {
		t.Error("Expected Query_time_avg > 0")
	}
	if queryTime.Min > queryTime.Max {
		t.Error("Expected Query_time_min >= Query_time_max")
	}
	t.Check(class.Metrics.NumberMetrics["Rows_affected"].Sum, Equals, uint64(0))
	t.Check(class.Metrics.NumberMetrics["Rows_examined"].Sum, Equals, uint64(0))
	t.Check(class.Metrics.NumberMetrics["Rows_sent"].Sum, Equals, uint64(1))

	err = w.Cleanup()
	t.Assert(err, IsNil)
}
func (s *WorkerTestSuite) TestIterOutOfSeq(t *C) {
	if s.dsn == "" {
		t.Fatal("PCT_TEST_MYSQL_DSN is not set")
	}
	mysqlConn := mysql.NewConnection(s.dsn)
	err := mysqlConn.Connect(1)
	t.Assert(err, IsNil)
	defer mysqlConn.Close()

	f := perfschema.NewRealWorkerFactory(s.logChan)
	w := f.Make("qan-worker", mysqlConn)

	start := []mysql.Query{
		mysql.Query{Verify: "performance_schema", Expect: "1"},
		mysql.Query{Set: "UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'statements_digest'"},
		mysql.Query{Set: "UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME LIKE 'statement/sql/%'"},
		mysql.Query{Set: "TRUNCATE performance_schema.events_statements_summary_by_digest"},
	}
	if err := mysqlConn.Set(start); err != nil {
		t.Fatal(err)
	}
	stop := []mysql.Query{
		mysql.Query{Set: "UPDATE performance_schema.setup_consumers SET ENABLED = 'NO' WHERE NAME = 'statements_digest'"},
		mysql.Query{Set: "UPDATE performance_schema.setup_instruments SET ENABLED = 'NO', TIMED = 'NO' WHERE NAME LIKE 'statement/sql/%'"},
	}
	defer func() {
		if err := mysqlConn.Set(stop); err != nil {
			t.Fatal(err)
		}
	}()

	// SCHEMA_NAME: NULL
	//      DIGEST: fbe070dfb47e4a2401c5be6b5201254e
	// DIGEST_TEXT: SELECT ? FROM DUAL
	_, err = mysqlConn.DB().Exec("SELECT 'teapot' FROM DUAL")

	// First interval.
	err = w.Setup(&qan.Interval{Number: 1, StartTime: time.Now().UTC()})
	t.Assert(err, IsNil)

	res, err := w.Run()
	t.Assert(err, IsNil)
	t.Check(res, IsNil)

	err = w.Cleanup()
	t.Assert(err, IsNil)

	// Some query activity between intervals.
	_, err = mysqlConn.DB().Exec("SELECT 'teapot' FROM DUAL")
	time.Sleep(1 * time.Second)

	// Simulate the ticker being reset which results in it resetting
	// its internal interval number, so instead of 2 here we have 1 again.
	// Second interval and a result.
	err = w.Setup(&qan.Interval{Number: 1, StartTime: time.Now().UTC()})
	t.Assert(err, IsNil)

	res, err = w.Run()
	t.Assert(err, IsNil)
	t.Check(res, IsNil) // no result due to out of sequence interval

	err = w.Cleanup()
	t.Assert(err, IsNil)

	// Simulate normal operation resuming, i.e. interval 2.
	err = w.Setup(&qan.Interval{Number: 2, StartTime: time.Now().UTC()})
	t.Assert(err, IsNil)

	// Now there should be a result.
	res, err = w.Run()
	t.Assert(res, NotNil)
	if len(res.Class) == 0 {
		t.Error("Expected len(res.Class) > 0")
	}
}
Beispiel #22
0
func (s *TestSuite) TestStartCollectStop(t *C) {
	/**
	 * The mm manager uses a mm monitor factory to create monitors.  This is
	 * what the factory does...
	 */

	// First, monitors monitor an instance of some service (MySQL, RabbitMQ, etc.)
	// So the instance name and id are given.  Second, every monitor has its own
	// specific config info which is sent as the proto.Cmd.Data.  This config
	// embed a mm.Config which embed an instance.Config:
	config := &mysql.Config{
		Config: mm.Config{
			ServiceInstance: proto.ServiceInstance{
				Service:    "mysql",
				InstanceId: 1,
			},
			Collect: 1,
			Report:  60,
		},
		Status: map[string]string{
			"threads_connected": "gauge",
			"threads_running":   "gauge",
		},
	}

	// From the config, the factory determine's the monitor's name based on
	// the service instance it's monitoring, and it creates a mysql.Connector
	// for the DSN for that service (since it's a MySQL monitor in this case).
	// It creates the monitor with these args:

	m := mysql.NewMonitor(s.name, config, s.logger, mysqlConn.NewConnection(dsn), s.mrm)
	if m == nil {
		t.Fatal("Make new mysql.Monitor")
	}

	// The factory returns the monitor to the manager which starts it with
	// the necessary channels:
	err := m.Start(s.tickChan, s.collectionChan)
	if err != nil {
		t.Fatalf("Start monitor without error, got %s", err)
	}

	// monitor=Ready once it has successfully connected to MySQL.  This may
	// take a few seconds (hopefully < 5) on a slow test machine.
	if ok := test.WaitStatus(5, m, s.name+"-mysql", "Connected"); !ok {
		t.Fatal("Monitor is ready")
	}

	// The monitor should only collect and send metrics on ticks; we haven't ticked yet.
	got := test.WaitCollection(s.collectionChan, 0)
	if len(got) > 0 {
		t.Fatal("No tick, no collection; got %+v", got)
	}

	// Now tick.  This should make monitor collect.
	now := time.Now()
	s.tickChan <- now
	got = test.WaitCollection(s.collectionChan, 1)
	if len(got) == 0 {
		t.Fatal("Got a collection after tick")
	}
	c := got[0]

	if c.Ts != now.Unix() {
		t.Error("Collection.Ts set to %s; got %s", now.Unix(), c.Ts)
	}

	// Only two metrics should be reported, from the config ^: threads_connected,
	// threads_running.  Their values (from MySQL) are variable, but we know they
	// should be > 1 because we're a thread connected and running.
	if len(c.Metrics) != 2 {
		t.Fatal("Collected only configured metrics; got %+v", c.Metrics)
	}
	if c.Metrics[0].Name != "mysql/threads_connected" {
		t.Error("First metric is ", "mysql/threads_connected; got", c.Metrics[0].Name)
	}
	if c.Metrics[0].Number < 1 {
		t.Error("mysql/threads_connected > 1; got", c.Metrics[0].Number)
	}
	if c.Metrics[1].Name != "mysql/threads_running" {
		t.Error("Second metric is ", "mysql/threads_running got", c.Metrics[1].Name)
	}
	if c.Metrics[1].Number < 1 {
		t.Error("mysql/threads_running > 1; got", c.Metrics[0].Number)
	}

	/**
	 * Stop the monitor.
	 */

	m.Stop()

	if ok := test.WaitStatus(5, m, s.name, "Stopped"); !ok {
		t.Fatal("Monitor has stopped")
	}
}
func (s *WorkerTestSuite) TestIterClockReset(t *C) {
	if s.dsn == "" {
		t.Fatal("PCT_TEST_MYSQL_DSN is not set")
	}
	mysqlConn := mysql.NewConnection(s.dsn)
	err := mysqlConn.Connect(1)
	t.Assert(err, IsNil)
	defer mysqlConn.Close()

	f := perfschema.NewRealWorkerFactory(s.logChan)
	w := f.Make("qan-worker", mysqlConn)

	start := []mysql.Query{
		mysql.Query{Verify: "performance_schema", Expect: "1"},
		mysql.Query{Set: "UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'statements_digest'"},
		mysql.Query{Set: "UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME LIKE 'statement/sql/%'"},
		mysql.Query{Set: "TRUNCATE performance_schema.events_statements_summary_by_digest"},
	}
	if err := mysqlConn.Set(start); err != nil {
		t.Fatal(err)
	}
	stop := []mysql.Query{
		mysql.Query{Set: "UPDATE performance_schema.setup_consumers SET ENABLED = 'NO' WHERE NAME = 'statements_digest'"},
		mysql.Query{Set: "UPDATE performance_schema.setup_instruments SET ENABLED = 'NO', TIMED = 'NO' WHERE NAME LIKE 'statement/sql/%'"},
	}
	defer func() {
		if err := mysqlConn.Set(stop); err != nil {
			t.Fatal(err)
		}
	}()

	// Generate some perf schema data.
	_, err = mysqlConn.DB().Exec("SELECT 'teapot' FROM DUAL")

	// First interval.
	now := time.Now().UTC()
	err = w.Setup(&qan.Interval{Number: 1, StartTime: now})
	t.Assert(err, IsNil)

	res, err := w.Run()
	t.Assert(err, IsNil)
	t.Check(res, IsNil)

	err = w.Cleanup()
	t.Assert(err, IsNil)

	// Simulate the ticker sending a time that's earlier than the previous
	// tick, which shouldn't happen.
	now = now.Add(-1 * time.Minute)
	err = w.Setup(&qan.Interval{Number: 2, StartTime: now})
	t.Assert(err, IsNil)

	res, err = w.Run()
	t.Assert(err, IsNil)
	t.Check(res, IsNil) // no result due to out of sequence interval

	err = w.Cleanup()
	t.Assert(err, IsNil)

	// Simulate normal operation resuming.
	now = now.Add(1 * time.Minute)
	err = w.Setup(&qan.Interval{Number: 3, StartTime: now})
	t.Assert(err, IsNil)

	// Now there should be a result.
	res, err = w.Run()
	t.Assert(res, NotNil)
	if len(res.Class) == 0 {
		t.Error("Expected len(res.Class) > 0")
	}
}
Beispiel #24
0
// This test is the same as TestCollectInnoDBStats with the only difference that
// now we are simulating a MySQL disconnection.
// After a disconnection, we must still be able to collect InnoDB stats
func (s *TestSuite) TestHandleMySQLRestarts(t *C) {
	/**
	 * Disable and reset InnoDB metrics so we can test that the monitor enables and sets them.
	 */
	if _, err := s.db.Exec("set global innodb_monitor_disable = '%'"); err != nil {
		t.Fatal(err)
	}
	if _, err := s.db.Exec("set global innodb_monitor_reset_all = '%'"); err != nil {
		t.Fatal(err)
	}

	s.db.Exec("drop database if exists percona_agent_test")
	s.db.Exec("create database percona_agent_test")
	s.db.Exec("create table percona_agent_test.t (i int) engine=innodb")
	defer s.db.Exec("drop database if exists percona_agent_test")

	config := &mysql.Config{
		Config: mm.Config{
			ServiceInstance: proto.ServiceInstance{
				Service:    "mysql",
				InstanceId: 1,
			},
			Collect: 1,
			Report:  60,
		},
		Status: map[string]string{},
		InnoDB: []string{"dml_%"}, // same as above ^
	}

	m := mysql.NewMonitor(s.name, config, s.logger, mysqlConn.NewConnection(dsn), s.mrm)
	if m == nil {
		t.Fatal("Make new mysql.Monitor")
	}

	err := m.Start(s.tickChan, s.collectionChan)
	if err != nil {
		t.Fatalf("Start monitor without error, got %s", err)
	}

	if ok := test.WaitStatus(5, m, s.name+"-mysql", "Connected"); !ok {
		t.Fatal("Monitor is ready")
	}

	/**
	 * Simulate a MySQL disconnection by disabling InnoDB metrics and putting a
	 * true into the restart channel. The monitor must enable them again
	 */
	if _, err := s.db.Exec("set global innodb_monitor_disable = '%'"); err != nil {
		t.Fatal(err)
	}
	if _, err := s.db.Exec("set global innodb_monitor_reset_all = '%'"); err != nil {
		t.Fatal(err)
	}
	s.mrm.SimulateMySQLRestart()

	if ok := test.WaitStatus(5, m, s.name+"-mysql", "Connected"); !ok {
		t.Fatal("Monitor is ready")
	}

	// Do INSERT to increment dml_inserts before monitor collects.  If it enabled
	// the InnoDB metrics and collects them, we should get dml_inserts=1 this later..
	s.db.Exec("insert into percona_agent_test.t (i) values (42)")

	s.tickChan <- time.Now()
	got := test.WaitCollection(s.collectionChan, 1)
	if len(got) == 0 {
		t.Fatal("Got a collection after tick")
	}
	c := got[0]

	/**
	 * ...monitor should have collected the InnoDB metrics:
	 *
	 * mysql> SELECT NAME, SUBSYSTEM, COUNT, TYPE FROM INFORMATION_SCHEMA.INNODB_METRICS WHERE STATUS='enabled';
	 * +-------------+-----------+-------+----------------+
	 * | NAME        | SUBSYSTEM | COUNT | TYPE           |
	 * +-------------+-----------+-------+----------------+
	 * | dml_reads   | dml       |     0 | status_counter |
	 * | dml_inserts | dml       |     1 | status_counter |
	 * | dml_deletes | dml       |     0 | status_counter |
	 * | dml_updates | dml       |     0 | status_counter |
	 * +-------------+-----------+-------+----------------+
	 */
	if len(c.Metrics) != 4 {
		t.Fatal("Collect 4 InnoDB metrics; got %+v", c.Metrics)
	}
	expect := []mm.Metric{
		{Name: "mysql/innodb/dml/dml_reads", Type: "counter", Number: 0},
		{Name: "mysql/innodb/dml/dml_inserts", Type: "counter", Number: 1}, // <-- our INSERT
		{Name: "mysql/innodb/dml/dml_deletes", Type: "counter", Number: 0},
		{Name: "mysql/innodb/dml/dml_updates", Type: "counter", Number: 0},
	}
	if ok, diff := test.IsDeeply(c.Metrics, expect); !ok {
		t.Error(diff)
	}

	// Stop montior, clean up.
	m.Stop()
}