コード例 #1
0
def pingOnError(ctx, error):
  # if not dict yet, i.e. before the cli.core.cli_core group
  # Important to keep track of the didPing variable
  if ctx.obj is None: return

  # check if the error's ping was done
  didPing = 'unhandled_error_pinged' in ctx.obj.keys()
  if didPing: return

  # send to sentry.io via isitfit.io (check usage of sentry_proxy in cli.core)
  from sentry_sdk import capture_exception
  capture_exception(error)

  # proceed to ping matomo about the error (to be deprecated in full in favor of sentry)
  from isitfit.utils import ping_matomo
  exception_type = type(error).__name__ # https://techeplanet.com/python-catch-all-exceptions/
  exception_str = ""
  try:
    exception_str = str(error)
  except:
    pass

  ping_matomo("/error/unhandled/%s?message=%s"%(exception_type, exception_str))

  # save a flag saying that the error sent a ping
  # Note that it is not necessary to do more than that, such as storing a list of pinged errors,
  # because there will be exactly one error raise at most before the program fails
  ctx.obj['unhandled_error_pinged'] = True
コード例 #2
0
def analyze(ctx, filter_tags, save_details):
    # gather anonymous usage statistics
    ping_matomo("/cost/analyze?filter_tags=%s&save_details=%s" %
                (filter_tags, b2l(save_details)))

    # save to click context
    share_email = ctx.obj.get('share_email', [])

    #logger.info("Is it fit?")
    logger.info("Initializing...")

    # set up pipelines for ec2, redshift, and aggregator
    from isitfit.cost import ec2_cost_analyze, redshift_cost_analyze, account_cost_analyze
    mm_eca = ec2_cost_analyze(ctx, filter_tags, save_details)
    mm_rca = redshift_cost_analyze(share_email,
                                   filter_region=ctx.obj['filter_region'],
                                   ctx=ctx,
                                   filter_tags=filter_tags)

    # combine the 2 pipelines into a new pipeline
    mm_all = account_cost_analyze(mm_eca, mm_rca, ctx, share_email)

    # configure tqdm
    from isitfit.tqdmman import TqdmL2Quiet
    tqdml2 = TqdmL2Quiet(ctx)

    # Run pipeline
    mm_all.get_ifi(tqdml2)
コード例 #3
0
    def is_configured(self):
        from isitfit.utils import ping_matomo

        # check not None and not empty string
        if os.getenv('DATADOG_API_KEY', None):
            if os.getenv('DATADOG_APP_KEY', None):
                if self.print_configured:
                    logger.info("Datadog env vars available")
                    ping_matomo("/cost/setting?datadog.is_configured=True")
                    self.print_configured = False
                return True

        if self.print_configured:
            logger.info(
                "Datadog env vars missing. Set DATADOG_API_KEY and DATADOG_APP_KEY to get memory data from Datadog."
            )
            ping_matomo("/cost/setting?datadog.is_configured=False")

            import click
            display_msg = lambda x: click.secho(x, fg='yellow')
            display_msg(
                "Note: without the datadog integration, memory metrics are missing, thus only CPU is used, which is not representative for memory-bound applications."
            )
            display_msg(
                "If you gather memory metrics using another provider than datadog, please get in touch at https://www.autofitcloud.com/contact"
            )
            self.print_configured = False

        return False
コード例 #4
0
ファイル: base_iterator.py プロジェクト: autofitcloud/isitfit
    def count(self):
        # method 1
        # ec2_it = self.ec2_resource.instances.all()
        # return len(list(ec2_it))

        if self.n_entry is not None:
            return self.n_entry

        self.n_entry = len(list(self.iterate_core(True)))

        # interim result for timer data to calculate performance (seconds per ec2 or seconds per rds)
        from isitfit.utils import ping_matomo
        ping_matomo(
            "/cost/base_iterator/BaseIterator/count?service=%s&n_entry=%s&n_region=%s"
            % (self.service_name, self.n_entry, len(self.region_include)))

        # send message to logs for info
        if self.n_entry == 0 and len(self.region_include) == 0:
            msg_count = "Found no %s"
            logger.info(msg_count % (self.service_description))
        else:
            msg_count = "Found a total of %i %s in %i region(s) (other regions do not hold any %s)"
            logger.info(msg_count %
                        (self.n_entry, self.service_description,
                         len(self.region_include), self.service_name))

        return self.n_entry
コード例 #5
0
def optimize(ctx, n, filter_tags, allow_ec2_different_family):
    # gather anonymous usage statistics
    ping_matomo(
        "/cost/optimize?n=%i&filter_tags=%s&allow_ec2_different_family=%s" %
        (n, filter_tags, b2l(allow_ec2_different_family)))

    # save to context
    share_email = ctx.obj.get('share_email', [])
    ctx.obj['allow_ec2_different_family'] = allow_ec2_different_family

    #logger.info("Is it fit?")
    logger.info("Initializing...")

    from isitfit.cost import ec2_cost_optimize, redshift_cost_optimize, account_cost_optimize
    mm_eco = ec2_cost_optimize(ctx, n, filter_tags)
    mm_rco = redshift_cost_optimize(filter_region=ctx.obj['filter_region'],
                                    ctx=ctx,
                                    filter_tags=filter_tags)

    # merge and run pipelines
    mm_all = account_cost_optimize(mm_eco, mm_rco, ctx)

    # configure tqdm
    from isitfit.tqdmman import TqdmL2Quiet
    tqdml2 = TqdmL2Quiet(ctx)

    # Run pipeline
    mm_all.get_ifi(tqdml2)
コード例 #6
0
def version():
    # gather anonymous usage statistics
    from isitfit.utils import ping_matomo
    ping_matomo("/version")

    version_core()
    return
コード例 #7
0
  def show(self, file=None):
    # ping matomo about error
    from isitfit.utils import ping_matomo
    ping_matomo("/error?message=%s"%self.message)

    # continue
    from click._compat import get_text_stderr
    if file is None:
        file = get_text_stderr()

    # echo wrap
    color = 'red'
    def wrapecho(message):
      # from click.utils import echo
      # echo('Error: %s' % self.format_message(), file=file, color=color)

      click.secho(message, fg=color)

    # main error
    wrapecho('Error: %s' % self.format_message())

    # if error from terminal during execution (not on program boot)
    if self.ctx is not None:
      if self.ctx.obj is not None:
        # if isitfit installation is outdated, append a message to upgrade
        if self.ctx.obj.get('is_outdated', None):
          hint_1 = "Upgrade your isitfit installation with `pip3 install --upgrade isitfit` and try again."
          wrapecho(hint_1)

        # test that boto3 minimum command can run
        # This would fail for example for: `AWS_ACCESS_KEY_ID=wrong AWS_SECRET_ACCESS_KEY=alsowrong aws iam get-user`
        import boto3
        iam_client = boto3.client('iam')
        try:
            # response = iam_client.get_user()
            iam_client.get_user()
        except Exception as e:
            msg_e = str(e)
            hint_3 = f"""Hint: The command `aws iam get-user` has also failed with the following error:
      {msg_e}
      This might indicate a problem with your aws user's permissions and could be related to the current error in isitfit."""
            # Update 2020-01-09 Instead of raising an exception, just display a warning
            #from isitfit.cli.click_descendents import IsitfitCliError
            #raise IsitfitCliError(hint_3) from e

            # ping matomo about warning
            from isitfit.utils import ping_matomo
            ping_matomo("/warning/aws-iam-get-user?message=%s"%hint_3)

            # display on screen
            wrapecho(hint_3)


    # add link to github issues
    hint_2 = "Is this my fault? 😞 Please report it at https://github.com/autofitcloud/isitfit/issues/new or reach out to me at [email protected]"
    wrapecho(hint_2)
コード例 #8
0
def pipeline_factory(mm_eca, mm_rca, ctx, share_email):
    """
    Combines the 2 pipelines from EC2 and Redshift
    mm_eca - pipeline of EC2 cost analyze
    mm_rca - pipeline of Redshift cost analyze
    ctx - click context
    share_email - list of emails or None
    """
    from isitfit.cost.mainManager import RunnerAccount
    mm_all = RunnerAccount("AWS cost analyze (EC2, Redshift) in all regions",
                           ctx)

    service_iterator = ServiceIterator(mm_eca, mm_rca)
    mm_all.set_iterator(service_iterator)

    # ping matomo at start of calculation
    from isitfit.utils import ping_matomo
    inject_timer_start = lambda context_pre: context_pre if ping_matomo(
        "/cost/analyze/account/start") else context_pre
    mm_all.add_listener('pre', inject_timer_start)

    service_calculator_get = ServiceCalculatorGet()
    mm_all.add_listener('ec2', service_calculator_get.per_service)

    service_calculator_save = ServiceCalculatorSave()
    mm_all.add_listener('ec2', service_calculator_save.per_service)

    service_calculator_binned = ServiceCalculatorBinned()
    mm_all.add_listener('ec2', service_calculator_binned.per_service)
    mm_all.add_listener('all', service_calculator_binned.after_all)

    # update dict and return it
    # https://stackoverflow.com/a/1453013/4126114
    # inject_analyzer = lambda context_all: dict({'analyzer': service_calculator_save}, **context_all)
    # inject_analyzer = lambda context_all: dict({'calculator_binned': service_calculator_binned}, **context_all)
    # mm_all.add_listener('all', inject_analyzer)
    #
    # service_reporter = ServiceReporterTotals()
    # service_reporter.emailTo = share_email
    # mm_all.add_listener('all', service_reporter.postprocess)
    # mm_all.add_listener('all', service_reporter.display)
    # mm_all.add_listener('all', service_reporter.email)

    # ping matomo at end of calculation
    inject_timer_end = lambda context_all: context_all if ping_matomo(
        "/cost/analyze/account/end") else context_all
    mm_all.add_listener('all', inject_timer_end)

    # display and email
    service_reporter = ServiceReporterBinned()
    service_reporter.emailTo = share_email
    mm_all.add_listener('all', service_reporter.display)
    mm_all.add_listener('all', service_reporter.email)

    # done
    return mm_all
コード例 #9
0
ファイル: tags.py プロジェクト: autofitcloud/isitfit
def tags(profile):
    # FIXME click bug: `isitfit command subcommand --help` is calling the code in here. Workaround is to check --help and skip the whole section
    import sys
    if '--help' in sys.argv: return

    # gather anonymous usage statistics
    from isitfit.utils import ping_matomo
    ping_matomo("/tags")

    pass
コード例 #10
0
ファイル: tags.py プロジェクト: autofitcloud/isitfit
def dump(ctx):
    # gather anonymous usage statistics
    from isitfit.utils import ping_matomo
    ping_matomo("/tags/dump")

    from ..tags.tagsDump import TagsDump
    tl = TagsDump(ctx)

    tl.fetch()
    tl.suggest()  # not really suggesting. Just dumping to csv
    tl.display()
コード例 #11
0
ファイル: cli.py プロジェクト: autofitcloud/isitfit
def migrate(ctx, not_dry_run):
  # usage stats
  from isitfit.utils import ping_matomo, b2l
  ping_matomo("/migrations/migrate?not_dry_run=%s"%b2l(not_dry_run))

  migman = ctx.obj['migman']
  migman.not_dry_run = not_dry_run
  migman.migrate_all()

  if not not_dry_run:
    click.echo("")
    click.secho("This was a simulated execution", fg="yellow")
    click.secho("Repeat using `isitfit migrations migrate --not-dry-run` for actual execution", fg='yellow')
コード例 #12
0
def cost(ctx, filter_region, ndays, profile):
    # FIXME click bug: `isitfit command subcommand --help` is calling the code in here. Workaround is to check --help and skip the whole section
    import sys
    if '--help' in sys.argv: return

    # gather anonymous usage statistics
    ping_matomo("/cost?filter_region=%s&ndays=%i" % (filter_region, ndays))

    # save to click context
    ctx.obj['ndays'] = ndays
    ctx.obj['filter_region'] = filter_region

    pass
コード例 #13
0
ファイル: cli.py プロジェクト: autofitcloud/isitfit
def show(ctx):
  # usage stats
  from isitfit.utils import ping_matomo
  ping_matomo("/migrations/show")

  migman = ctx.obj['migman']

  if migman.df_mig.shape[0]==0:
    click.echo("No pending migrations")
  else:
    click.echo("Pending migrations")
    click.echo(migman.df_mig[['migname', 'description']])
    click.echo("")
    click.secho("Use `isitfit migrations migrate` to execute them", fg="yellow")
コード例 #14
0
ファイル: cacheManager.py プロジェクト: autofitcloud/isitfit
    def handle_pre(self, context_pre):
        from isitfit.utils import ping_matomo

        # set up caching if requested
        self.fetch_envvars()
        if self.isSetup():
            self.connect()
            ping_matomo("/cost/setting?redis.is_configured=True")
            return context_pre

        # 0th pass to count
        n_ec2_total = context_pre['n_ec2_total']
        ping_matomo("/cost/setting?redis.is_configured=False")

        # if more than 10 servers, recommend caching with redis
        cond_prompt = n_ec2_total > 10 and not self.isSetup()
        if cond_prompt:
            from termcolor import colored
            logger.warning(
                colored(
                    """Since the number of EC2 instances is %i,
it is recommended to use redis for caching of downloaded CPU/memory metrics.
To do so
- install redis

    [sudo] apt-get install redis-server

- export environment variables

    export ISITFIT_REDIS_HOST=localhost
    export ISITFIT_REDIS_PORT=6379
    export ISITFIT_REDIS_DB=0

where ISITFIT_REDIS_DB is the ID of an unused database in redis.

And finally re-run isitfit as usual.
""" % n_ec2_total, "yellow"))
            import click
            # not using abort=True so that I can send a custom message in the abort
            continue_wo_redis = click.confirm(colored(
                'Would you like to continue without redis caching? ', 'cyan'),
                                              abort=False,
                                              default=True)
            if not continue_wo_redis:
                from isitfit.cli.click_descendents import IsitfitCliError
                raise IsitfitCliError("Aborting to set up redis.",
                                      context_pre['click_ctx'])

        # done
        return context_pre
コード例 #15
0
ファイル: cli.py プロジェクト: autofitcloud/isitfit
def migrations(ctx):
  # FIXME click bug: `isitfit command subcommand --help` is calling the code in here. Workaround is to check --help and skip the whole section
  import sys
  if '--help' in sys.argv: return

  # usage stats
  from isitfit.utils import ping_matomo
  ping_matomo("/migrations")

  from isitfit.migrations.migman import MigMan
  migman = MigMan()
  migman.connect()
  migman.read()

  ctx.obj['migman'] = migman
コード例 #16
0
ファイル: tags.py プロジェクト: autofitcloud/isitfit
def push(ctx, csv_filename, not_dry_run):
    # gather anonymous usage statistics
    from isitfit.utils import ping_matomo, b2l
    ping_matomo("/tags/push?csv_filename=%s&not_dry_run=%s" %
                (csv_filename, b2l(not_dry_run)))

    from ..tags.tagsPush import TagsPush

    tp = TagsPush(csv_filename, ctx)

    tp.read_csv()
    tp.validateTagsFile()
    tp.pullLatest()
    tp.diffLatest()
    tp.processPush(not not_dry_run)
コード例 #17
0
ファイル: datadog.py プロジェクト: autofitcloud/isitfit
def datadog(ctx):
  # FIXME click bug: `isitfit command subcommand --help` is calling the code in here. Workaround is to check --help and skip the whole section
  import sys
  if '--help' in sys.argv: return

  # usage stats
  from isitfit.utils import ping_matomo
  ping_matomo("/datadog")

  # manager of redis-pandas caching
  from isitfit.cost.cacheManager import RedisPandas as RedisPandasCacheManager
  cache_man = RedisPandasCacheManager()

  from isitfit.cost.metrics_datadog import DatadogCached
  ctx.obj['ddg'] = DatadogCached(cache_man)
コード例 #18
0
ファイル: tags.py プロジェクト: autofitcloud/isitfit
def suggest(ctx, advanced):
    # gather anonymous usage statistics
    from isitfit.utils import ping_matomo, b2l
    ping_matomo("/tags/suggest?advanced=%s" % b2l(advanced))

    tl = None
    if not advanced:
        from ..tags.tagsSuggestBasic import TagsSuggestBasic
        tl = TagsSuggestBasic(ctx)
    else:
        from ..tags.tagsSuggestAdvanced import TagsSuggestAdvanced
        tl = TagsSuggestAdvanced(ctx)

    tl.prepare()
    tl.fetch()
    tl.suggest()
    tl.display()
コード例 #19
0
def pipeline_factory(mm_eco, mm_rco, ctx):
    from isitfit.cost.mainManager import RunnerAccount
    mm_all = RunnerAccount("AWS cost optimize (EC2, Redshift) in all regions",
                           ctx)

    # add listener that checks the local sqlite database for a previous calculation
    # and display if available, then prompt user if desires to re-calculate
    # Note that if no desire to re-calculate, this raises an exception that bubbles up into get_ifi and aborts the pipeline early
    sqlite_man = SqliteMan(ctx)

    # Update 2019-12-27 will not display the database table ATM, in favor of using it in the interactive command
    # mm_all.add_listener('pre', sqlite_man.read_sqlite)

    # set up a pipeline that fetches fresh data
    from .account_cost_analyze import ServiceIterator, ServiceCalculatorGet
    iterator = ServiceIterator(mm_eco, mm_rco)
    mm_all.set_iterator(iterator)

    # ping matomo at start of calculation
    from isitfit.utils import ping_matomo
    inject_timer_start = lambda context_pre: context_pre if ping_matomo(
        "/cost/optimize/account/start") else context_pre
    mm_all.add_listener('pre', inject_timer_start)

    calculator_get = ServiceCalculatorGet()
    mm_all.add_listener('ec2', calculator_get.per_service)

    aggregator = ServiceAggregator()
    mm_all.add_listener('ec2', aggregator.per_service_save)
    mm_all.add_listener('all', aggregator.concat)

    mm_all.add_listener('all', sqlite_man.update_dtCreated)

    # ping matomo at end of calculation
    inject_timer_end = lambda context_all: context_all if ping_matomo(
        "/cost/optimize/account/end") else context_all
    mm_all.add_listener('all', inject_timer_end)

    # whether reading from sqlite or fresh data, display
    reporter = ServiceReporter()
    #mm_all.add_listener('all', reporter.display)
    mm_all.add_listener('all', reporter.display2)

    # done
    return mm_all
コード例 #20
0
def cli_core(ctx, debug, verbose, optimize, version, share_email,
             skip_check_upgrade, skip_prompt_email):
    # FIXME click bug: `isitfit cost --help` is calling the code in here. Workaround is to check --help
    import sys
    if '--help' in sys.argv: return

    # make sure that context is a dict
    ctx.ensure_object(dict)

    # set up exception aggregation in sentry.io
    from isitfit import sentry_proxy
    from isitfit.apiMan import BASE_URL
    sp_url = f"{BASE_URL}fwd/sentry"
    sentry_proxy.init(dsn=sp_url)

    # test exception caught by sentry. FIXME Dont commit this! :D
    # 1/0

    # usage stats
    # https://docs.python.org/3.5/library/string.html#format-string-syntax
    from isitfit.utils import ping_matomo, b2l
    ping_url = "/?debug={}&verbose={}&share_email={}&skip_check_upgrade={}"
    ping_url = ping_url.format(b2l(debug), b2l(verbose),
                               b2l(len(share_email) > 0),
                               b2l(skip_check_upgrade))
    ping_matomo(ping_url)

    # choose log level based on debug and verbose flags
    import logging
    logLevel = logging.DEBUG if debug else (
        logging.INFO if verbose else logging.WARNING)

    ch = logging.StreamHandler()
    ch.setLevel(logLevel)
    logger.addHandler(ch)
    logger.setLevel(logLevel)

    if debug:
        logger.debug("Enabled debug level")
        logger.debug("-------------------")

    # After adding the separate command for "cost" (i.e. `isitfit cost analyze`)
    # putting a note here to notify user of new usage
    # Ideally, this code would be deprecated though
    if ctx.invoked_subcommand is None:
        # if still used without subcommands, notify user of new usage
        #from .cost import analyze as cost_analyze, optimize as cost_optimize
        #if optimize:
        #  ctx.invoke(cost_optimize, filter_tags=filter_tags, n=n)
        #else:
        #  ctx.invoke(cost_analyze, filter_tags=filter_tags)
        from click.exceptions import UsageError
        if optimize:
            err_msg = "As of version 0.11, please use `isitfit cost optimize` instead of `isitfit --optimize`."
            ping_matomo("/error/UsageError?message=%s" % err_msg)
            raise UsageError(err_msg)
        elif version:
            # ctx.invoke(cli_version)
            err_msg = "As of version 0.11, please use `isitfit version` instead of `isitfit --version`."
            ping_matomo("/error/UsageError?message=%s" % err_msg)
            raise UsageError(err_msg)
        else:
            err_msg = "As of version 0.11, please use `isitfit cost analyze` instead of `isitfit` to calculate the cost-weighted utilization."
            ping_matomo("/error/UsageError?message=%s" % err_msg)
            raise UsageError(err_msg)

    # check if emailing requested
    if share_email is not None:
        max_n_recipients = 3
        if len(share_email) > max_n_recipients:
            err_msg = "Maximum allowed number of email recipients is %i. Received %i" % (
                max_n_recipients, len(share_email))
            ping_matomo("/error?message=%s" % err_msg)
            from click.exceptions import BadParameter
            raise BadParameter(err_msg, param_hint="--share-email")

        ctx.obj['share_email'] = share_email

    # check if current version is out-of-date
    if ctx.invoked_subcommand != 'version':
        if not skip_check_upgrade:
            from ..utils import prompt_upgrade
            is_outdated = prompt_upgrade('isitfit', isitfit_version)
            ctx.obj['is_outdated'] = is_outdated
            if is_outdated:
                ping_matomo("/version/prompt_upgrade?is_outdated=%s" %
                            b2l(is_outdated))

    if ctx.invoked_subcommand not in ['version', 'migrations']:
        # run silent migrations
        from isitfit.migrations.migman import silent_migrate
        migname_l = silent_migrate()
        if len(migname_l) > 0:
            from isitfit.utils import l2s
            migname_s = l2s(migname_l)
            ping_matomo("/migrations/silent?migname=%s" % (migname_s))

    # save `verbose` and `debug` for later tqdm
    ctx.obj['debug'] = debug
    ctx.obj['verbose'] = verbose
    # save skip-prompt-email for later usage
    ctx.obj['skip_prompt_email'] = skip_prompt_email
コード例 #21
0
ファイル: datadog.py プロジェクト: autofitcloud/isitfit
def dump(ctx, date, aws_id):
  # usage stats
  from isitfit.utils import ping_matomo
  ping_matomo("/datadog/dump")

  ddgL1 = ctx.obj['ddg']

  # get as dataframe, daily
  df = ddgL1.get_metrics_all(aws_id)
  # drop nhours column since useless here
  del df['nhours']
  print("Daily usage")
  print(df)
  print("")

  # convert aws ID to datadog hostname
  dd_hostname = ddgL1.map_aws_dd[aws_id]

  # higher freq
  # query language, check note above in get_metrics_cpu
  SECONDS_PER_POINT = 60*10 # 60*60 # *24

  import datetime as dt
  date_str = date.strftime("%Y-%m-%d")
  dt_start="%s 00:00:00"%date_str
  dt_end="%s 23:59:59"%date_str
  dt_start = dt.datetime.strptime(dt_start, "%Y-%m-%d %H:%M:%S")
  dt_end = dt.datetime.strptime(dt_end, "%Y-%m-%d %H:%M:%S")
  import time
  conv2sec = lambda x: time.mktime(x.timetuple())
  ue_start = conv2sec(dt_start)
  ue_end   = conv2sec(dt_end)

  # query datadog, result as json
  # https://docs.datadoghq.com/api/?lang=python#query-timeseries-points
  #datadog_api.initialize()
  #m = datadog_api.api.Metric.query(start=ue_start, end=ue_end, query=query)
  #print(m)

  # repeat as dataframe
  from isitfit.cost.metrics_datadog import DatadogApiWrap
  apiwrap = DatadogApiWrap()
  df_all = []
  metric_all = [
    ('system.cpu.idle', 'cpu_idle_min', 'system.cpu.idle{host:%s}.rollup(min,%i)'),
    ('system.mem.free', 'mem_free_min', 'system.mem.free{host:%s}.rollup(min,%i)'),
    ('system.cpu.idle', 'cpu_idle_max', 'system.cpu.idle{host:%s}.rollup(max,%i)'),
    ('system.mem.free', 'mem_free_max', 'system.mem.free{host:%s}.rollup(max,%i)')
  ]
  for metric_name, col_name, query_t in metric_all:
    query_v = query_t%(dd_hostname, SECONDS_PER_POINT)
    df_i = apiwrap.metric_query(
      dd_hostname=dd_hostname,
      start=ue_start,
      end=ue_end,
      query=query_v,
      metric_name=metric_name,
      dfcol_name=col_name
    )
    df_i.set_index('ts_dt', inplace=True)
    df_all.append(df_i)

  # concat all
  import pandas as pd
  df_all = pd.concat(df_all, axis=1)
  pd.set_option("display.max_rows", None)
  print("Datadog details")
  print(df_all)