def _get_db_conn(self): if self.db and self.db.closed != 0: self.db.close() self.db = None if not self.db: try: db_config = buildpackutil.get_database_config() if db_config['DatabaseType'] != 'PostgreSQL': raise Exception( 'Metrics only supports postgresql, not %s' % db_config['DatabaseType'] ) host_and_port = db_config['DatabaseHost'].split(':') host = host_and_port[0] if len(host_and_port) > 1: port = int(host_and_port[1]) else: port = 5432 self.db = psycopg2.connect( "options='-c statement_timeout=60s'", database=db_config['DatabaseName'], user=db_config['DatabaseUserName'], password=db_config['DatabasePassword'], host=host, port=port, connect_timeout=3, ) self.db.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) except Exception as e: logger.warn('METRICS: ' + e.message) return self.db
def _get_database_index_size(self): conn = self._get_db_conn() try: with conn.cursor() as cursor: cursor.execute( """ SELECT SUM(pg_relation_size(quote_ident(indexrelname)::text)) AS index_size FROM pg_tables t LEFT OUTER JOIN pg_class c ON t.tablename=c.relname LEFT OUTER JOIN ( SELECT c.relname AS ctablename, ipg.relname AS indexname, x.indnatts AS number_of_columns, idx_scan, idx_tup_read, idx_tup_fetch, indexrelname, indisunique FROM pg_index x JOIN pg_class c ON c.oid = x.indrelid JOIN pg_class ipg ON ipg.oid = x.indexrelid JOIN pg_stat_all_indexes psai ON x.indexrelid = psai.indexrelid ) AS foo ON t.tablename = foo.ctablename WHERE t.schemaname='public'; """ ) rows = cursor.fetchall() return int(rows[0][0]) except Exception as e: logger.warn( 'Metrics: Failed to get database index size, ' + str(e) ) return None
def run(): if not is_enabled(): return if not _is_installed(): logger.warn( "DataDog agent isn" "t installed yet but DD_API_KEY is set. " + "Please push or restage your app to complete DataDog installation." ) return e = dict(os.environ) e["DD_HOSTNAME"] = buildpackutil.get_hostname() e["DD_API_KEY"] = get_api_key() e["LD_LIBRARY_PATH"] = os.path.abspath(".local/datadog/lib/") subprocess.Popen( (".local/datadog/datadog-agent", "-c", ".local/datadog", "start"), env=e, ) # after datadog agent 6.3 is released, a separate process agent might # not be necessary any more: https://github.com/DataDog/datadog-process-agent/pull/124 subprocess.Popen( ( ".local/datadog/process-agent", "-logtostderr", "-config", ".local/datadog/datadog.yaml", ), env=e, )
def _get_database_mutations(self): conn = self._get_db_conn() try: db_config = buildpackutil.get_database_config() with conn.cursor() as cursor: cursor.execute("SELECT xact_commit, " " xact_rollback, " " tup_inserted, " " tup_updated, " " tup_deleted " "FROM pg_stat_database " "WHERE datname = '%s';" % (db_config['DatabaseName'], )) rows = cursor.fetchall() return { 'xact_commit': int(rows[0][0]), 'xact_rollback': int(rows[0][1]), 'tup_inserted': int(rows[0][2]), 'tup_updated': int(rows[0][3]), 'tup_deleted': int(rows[0][4]), } except Exception as e: logger.warn('Metrics: Failed to get database mutation stats, ' + str(e)) return None
def _set_up_postgres(): # TODO: set up a way to disable this, on shared database (mxapps.io) we # don't want to allow this. if not buildpackutil.i_am_primary_instance(): return dbconfig = database_config.get_database_config() for k in ( "DatabaseType", "DatabaseUserName", "DatabasePassword", "DatabaseHost", ): if k not in dbconfig: logger.warn( "Skipping database configuration for DataDog because " "configuration is not found. See database_config.py " "for details" ) return if dbconfig["DatabaseType"] != "PostgreSQL": return with open(".local/datadog/conf.d/postgres.yaml", "w") as fh: config = { "init_config": {}, "instances": [ { "host": dbconfig["DatabaseHost"].split(":")[0], "port": int(dbconfig["DatabaseHost"].split(":")[1]), "username": dbconfig["DatabaseUserName"], "password": dbconfig["DatabasePassword"], "dbname": dbconfig["DatabaseName"], } ], } fh.write(yaml.safe_dump(config))
def _inject_storage_stats(self, stats): storage_stats = {} try: storage_stats['get_number_of_files'] = self._get_number_of_files() except Exception as e: logger.warn('Metrics: Failed to retrieve number of files, ' + str(e)) stats["storage"] = storage_stats return stats
def _get_tags(): # Telegraf tags must be key / value tags = {} for kv in [t.split(":") for t in buildpackutil.get_tags()]: if len(kv) == 2: tags[kv[0]] = kv[1] else: logger.warn( 'Skipping tag "{}" from TAGS because not a key/value'.format( kv[0])) return tags
def _get_database_table_size(self): conn = self._get_db_conn() try: db_config = buildpackutil.get_database_config() with conn.cursor() as cursor: cursor.execute("SELECT pg_database_size('%s');" % (db_config['DatabaseName'], )) rows = cursor.fetchall() return int(rows[0][0]) except Exception as e: logger.warn('Metrics: Failed to get database data size, ' + str(e)) return None
def do_who(self, args): if self._report_not_running(): return if args: try: limitint = int(args) self._who(limitint) except ValueError: logger.warn("Could not parse argument to an integer. Use a " "number as argument to limit the amount of logged " "in users shown.") else: self._who()
def run(): if not is_enabled(): return if not _is_installed(): logger.warn( 'Telegraf isn\'t installed yet but APPMETRICS_TARGET is set. ' + 'Please push or restage your app to complete Telegraf installation.' ) return e = dict(os.environ) subprocess.Popen(('.local/telegraf/usr/bin/telegraf', '--config', '.local/telegraf/etc/telegraf/telegraf.conf'), env=e)
def do_create_admin_user(self, args=None): (pid_alive, m2ee_alive) = self.m2ee.check_alive() if not m2ee_alive: logger.warn("The application process needs to be running to " "create a user object in the application.") return print("This option will create an administrative user account, using " "the preset username and user role settings.") newpw1 = getpass.getpass("Type new password for this user: "******"Type new password for this user again: ") if newpw1 != newpw2: print("The passwords are not equal!") else: m2eeresponse = self.m2ee.client.create_admin_user( {"password": newpw1}) m2eeresponse.display_error()
def do_emptydb(self, args): if not self.m2ee.config.is_using_postgresql(): logger.error("Only PostgreSQL databases are supported right now.") return (pid_alive, m2ee_alive) = self.m2ee.check_alive() if pid_alive or m2ee_alive: logger.warn("The application process is still running, refusing " "to empty the database right now.") return logger.info("This command will drop all tables and sequences in " "database %s." % self.m2ee.config.get_pg_environment()['PGDATABASE']) answer = raw_input("Continue? (y)es, (N)o? ") if answer != 'y': print("Aborting!") return pgutil.emptydb(self.m2ee.config)
def _inject_m2ee_stats(self, stats): try: m2ee_stats, java_version = munin.get_stats_from_runtime( self.m2ee.client, self.m2ee.config) if 'sessions' in m2ee_stats: m2ee_stats['sessions']['user_sessions'] = {} m2ee_stats = munin.augment_and_fix_stats( m2ee_stats, self.m2ee.runner.get_pid(), java_version) critical_logs_count = len( self.m2ee.client.get_critical_log_messages()) m2ee_stats['critical_logs_count'] = critical_logs_count stats['mendix_runtime'] = m2ee_stats except Exception as e: logger.warn('Metrics: Failed to get Mendix Runtime metrics, ' + str(e)) return stats
def enable_runtime_agent(m2ee): # check already configured if 0 in [ v.find("-javaagent") for v in m2ee.config._conf["m2ee"]["javaopts"] ]: return if m2ee.config.get_runtime_version() >= 7.14: agent_config = "" agent_config_str = None if "METRICS_AGENT_CONFIG" in os.environ: agent_config_str = os.environ.get("METRICS_AGENT_CONFIG") elif "MetricsAgentConfig" in m2ee.config._conf["mxruntime"]: logger.warn( "Passing MetricsAgentConfig with Mendix Custom Runtime Setting is deprecated. " + "Please use METRICS_AGENT_CONFIG as environment variable." ) agent_config_str = m2ee.config._conf["mxruntime"][ "MetricsAgentConfig" ] if agent_config_str: try: # ensure that this contains valid json json.loads(agent_config_str) config_file_path = os.path.abspath( ".local/MetricsAgentConfig.json" ) with open(config_file_path, "w") as fh: fh.write(agent_config_str) agent_config = "=config=" + config_file_path except ValueError: logger.error( "Could not parse json from MetricsAgentConfig", exc_info=True, ) jar = os.path.abspath(".local/datadog/{}".format(MX_AGENT_JAR)) m2ee.config._conf["m2ee"]["javaopts"].extend( ["-javaagent:{}{}".format(jar, agent_config)] ) # if not explicitly set, default to statsd m2ee.config._conf["mxruntime"].setdefault( "com.mendix.metrics.Type", "statsd" )
def do_log(self, args): if self._cleanup_logging(): return logfile = self.m2ee.config.get_logfile() if not logfile: logger.warn("logfile location is not specified") return print("This command will start printing log information from the " "application right in the middle of all of the other output on " "your screen. This can be confusing, especially when you're " "typing something and everything gets messed up by the logging. " "Issuing the log command again will turn off logging output.") answer = raw_input("Do you want to start log output (y/N): ") if answer == 'y': cmd = ("tail", "-F", logfile) proc = subprocess.Popen(cmd) self.m2ee._logproc = proc self.prompt = "LOG %s" % self._default_prompt
def do_update_admin_user(self, args=None): (pid_alive, m2ee_alive) = self.m2ee.check_alive() if not m2ee_alive: logger.warn("The application process needs to be running to " "change user objects in the application.") return print("Using this function you can reset the password of an " "administrative user account.") username = raw_input("User name: ") newpw1 = getpass.getpass("Type new password for user %s: " % username) newpw2 = getpass.getpass("Type new password for user %s again: " % username) if newpw1 != newpw2: print("The passwords are not equal!") else: m2eeresponse = self.m2ee.client.update_admin_user( {"username": username, "password": newpw1}) m2eeresponse.display_error()
def do_emptydb(self, args): if not self.m2ee.config.is_using_postgresql(): logger.error("Only PostgreSQL databases are supported right now.") return (pid_alive, m2ee_alive) = self.m2ee.check_alive() if pid_alive or m2ee_alive: logger.warn("The application process is still running, refusing " "to empty the database right now.") return logger.info("This command will drop all tables and sequences in " "database %s." % self.m2ee.config.get_pg_environment()['PGDATABASE']) answer = ('y' if self.yolo_mode else raw_input("Continue? (y)es, (N)o? ")) if answer != 'y': print("Aborting!") return pgutil.emptydb(self.m2ee.config)
def do_log(self, args): if self._cleanup_logging(): return logfile = self.m2ee.config.get_logfile() if not logfile: logger.warn("logfile location is not specified") return print("This command will start printing log information from the " "application right in the middle of all of the other output on " "your screen. This can be confusing, especially when you're " "typing something and everything gets messed up by the logging. " "Issuing the log command again will turn off logging output.") answer = ('y' if self.yolo_mode else raw_input("Do you want to start log output (y/N): ")) if answer == 'y': cmd = ("tail", "-F", logfile) proc = subprocess.Popen(cmd) self.m2ee._logproc = proc self.prompt = "LOG %s" % self._default_prompt
def do_restoredb(self, args): if not self.m2ee.config.is_using_postgresql(): logger.error("Only PostgreSQL databases are supported right now.") return if not args: logger.error("restoredb needs the name of a dump file in %s as arg" "ument" % self.m2ee.config.get_database_dump_path()) return (pid_alive, m2ee_alive) = self.m2ee.check_alive() if pid_alive or m2ee_alive: logger.warn("The application is still running, refusing to " "restore the database right now.") return database_name = self.m2ee.config.get_pg_environment()['PGDATABASE'] answer = raw_input("This command will restore this dump into database " "%s. Continue? (y)es, (N)o? " % database_name) if answer != 'y': logger.info("Aborting!") return pgutil.restoredb(self.m2ee.config, args)
def run(): if not is_enabled(): return if not _is_installed(): logger.warn( "Telegraf isn't installed yet but APPMETRICS_TARGET is set. " + "Please push or restage your app to complete Telegraf installation." ) return e = dict(os.environ) subprocess.Popen( ( ".local/telegraf/usr/bin/telegraf", "--config", ".local/telegraf/etc/telegraf/telegraf.conf", ), env=e, )
def do_restoredb(self, args): if not self.m2ee.config.is_using_postgresql(): logger.error("Only PostgreSQL databases are supported right now.") return if not args: logger.error("restoredb needs the name of a dump file in %s as arg" "ument" % self.m2ee.config.get_database_dump_path()) return (pid_alive, m2ee_alive) = self.m2ee.check_alive() if pid_alive or m2ee_alive: logger.warn("The application is still running, refusing to " "restore the database right now.") return database_name = self.m2ee.config.get_pg_environment()['PGDATABASE'] answer = ('y' if self.yolo_mode else raw_input( "This command will restore this dump into database " "%s. Continue? (y)es, (N)o? " % database_name)) if answer != 'y': logger.info("Aborting!") return pgutil.restoredb(self.m2ee.config, args)
def do_update_admin_user(self, args=None): (pid_alive, m2ee_alive) = self.m2ee.check_alive() if not m2ee_alive: logger.warn("The application process needs to be running to " "change user objects in the application.") return print("Using this function you can reset the password of an " "administrative user account.") username = raw_input("User name: ") newpw1 = getpass.getpass("Type new password for user %s: " % username) newpw2 = getpass.getpass("Type new password for user %s again: " % username) if newpw1 != newpw2: print("The passwords are not equal!") else: m2eeresponse = self.m2ee.client.update_admin_user({ "username": username, "password": newpw1 }) m2eeresponse.display_error()
def _inject_health(self, stats): health = {} translation = {'healthy': 10, 'unknown': 7, 'sick': 3, 'critical': 0} stats['health'] = health try: health_response = self.m2ee.client.check_health() if health_response.has_error(): if (health_response.get_result() == 3 and health_response.get_cause() == "java.lang.IllegalArgument" "Exception: Action should not be null"): # Because of an incomplete implementation, in Mendix 2.5.4 or # 2.5.5 this means that the runtime is health-check # capable, but no health check microflow is defined. health['health'] = translation['unknown'] health['diagnosis'] = "No health check microflow defined" elif (health_response.get_result() == health_response.ERR_ACTION_NOT_FOUND): # Admin action 'check_health' does not exist. health['health'] = translation['unknown'] health['diagnosis'] = "No health check microflow defined" else: health['health'] = translation['critical'] health['diagnosis'] = "Health check failed unexpectedly: %s" \ % health_response.get_error() else: feedback = health_response.get_feedback() health['health'] = translation[feedback['health']] health['diagnosis'] = feedback['diagnosis'] if 'diagnosis' in feedback else '' health['response'] = health_response._json except Exception as e: logger.warn('Metrics: Failed to get health status, ' + str(e)) health['health'] = translation['critical'] health['diagnosis'] = "Health check failed unexpectedly: %s" % e return stats
def run(): if not is_enabled(): return if not _is_installed(): logger.warn('DataDog agent isn''t installed yet but DD_API_KEY is set. ' + 'Please push or restage your app to complete DataDog installation.') return e = dict(os.environ) e['DD_HOSTNAME'] = buildpackutil.get_hostname() e['DD_API_KEY'] = get_api_key() e['LD_LIBRARY_PATH'] = os.path.abspath('.local/datadog/lib/') subprocess.Popen(( '.local/datadog/datadog-agent', '-c', '.local/datadog', 'start', ), env=e) # after datadog agent 6.3 is released, a separate process agent might # not be necessary any more: https://github.com/DataDog/datadog-process-agent/pull/124 subprocess.Popen(( '.local/datadog/process-agent', '-logtostderr', '-config', '.local/datadog/datadog.yaml', ), env=e)
def _inject_health(self, stats): health = {} translation = {"healthy": 10, "unknown": 7, "sick": 4, "critical": 0} stats["health"] = health try: health_response = self.m2ee.client.check_health() if health_response.has_error(): if (health_response.get_result() == 3 and health_response.get_cause() == "java.lang.IllegalArgument" "Exception: Action should not be null"): # Because of an incomplete implementation, in Mendix 2.5.4 or # 2.5.5 this means that the runtime is health-check # capable, but no health check microflow is defined. health["health"] = translation["unknown"] health["diagnosis"] = "No health check microflow defined" elif (health_response.get_result() == health_response.ERR_ACTION_NOT_FOUND): # Admin action 'check_health' does not exist. health["health"] = translation["unknown"] health["diagnosis"] = "No health check microflow defined" else: health["health"] = translation["critical"] health["diagnosis"] = ( "Health check failed unexpectedly: %s" % health_response.get_error()) else: feedback = health_response.get_feedback() health["health"] = translation[feedback["health"]] health["diagnosis"] = (feedback["diagnosis"] if "diagnosis" in feedback else "") health["response"] = health_response._json except Exception as e: logger.warn("Metrics: Failed to get health status, " + str(e)) health["health"] = translation["critical"] health["diagnosis"] = "Health check failed unexpectedly: %s" % e return stats