【NerdGraph】New Relic API - NerdGraphを使って、New Relicアカウントなしで見れるダッシュボードを生成してみた

# はじめに 

New Relicのダッシュボードに表示している情報を「New Relicユーザを持たない人へ見せることはできないか？」を調査・実装しましたのでそのまとめとなります。

今回は[NerdGraph](https://docs.newrelic.com/jp/docs/apis/nerdgraph/get-started/introduction-new-relic-nerdgraph/)を利用して、New Relicダッシュボード情報の取得し共有可能なモニタリンググラフ（ChartURL）を生成する方法を取りました。

## やりたかったこと

最近、私の会社でAWS環境の監視・モニタリングにNew Relicの利用をはじめました。現在では下記のようなさまざまなデータをNew Relicに集約しています。
 * サイトの稼働やサービスレベルの測定: Synthetics/SLM
 * EC2やECSやRDSなどのサーバのリソース状況モニタリング： New Relicエージェント/CloudWatch連携
 * 稼働しているアプリケーションのモニタリング: APM
などなど、運用・監視に必要な情報はすべてNew Relicにデータを転送しています。

New Relic上でぽちぽちして各データを確認すれば非常に便利かつ綺麗なUIで表示してくれますが、月間稼働レポートや全体のリソース状況を一目で簡単に把握できるようにするために**ダッシュボード**はかかせません！！ New Relic上のダッシュボードには、頻繁に閲覧する重要なデータの集約を行っています。

このダッシュボードにある情報をNew Relicアカウントを持っていない人へ連携する際に、その画面のスクショやPDFでもいいのですが、それだと **「受け取った側は確認したい期間や対象を絞ることができない」** ので、
* 実際のグラフをどうにか提供できないか？
* 可能ならば、New Relicで表示しているダッシュボードと同じような項目、順番で提供できないか？
* ダッシュボードは案件ごとにあるため多数存在している。なので、できれば自動的にダッシュボードが生成できないか？

というのがやりたかったこととなります。

元々は、Zabbix・AWS CloudWatch・Cacti等々を利用しモニタリングをしており、その際はダッシュボード提供をしていたので、今回New Relicでモニタリングを一元管理するにあたり、同じようなことができないか？ということで、調査をスタートしました。
これが実現できると、今まで通りNew Relicのユーザを持っていない社員やお客様へ、実際のモニタリングデータを提供できることが可能になります。

今回は備忘も兼ねて、APIを見つける方法などをどのように進めていったかを順を追って記載していこうと思います。

# New Relicアカウントなしでも参照可能なグラフの取得

まず確認したのはNew Relicにログインせずに表示可能なグラフデータはあるのか？という点です。
それは、すぐに見つかりました。

ダッシュボードに置かれている各ウィジェットの右上の「…」から「Get chart link」というものがあるので押下します。

![スクリーンショット_2023-12-18_20_19_22.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/5b711dd4-92f0-2970-c326-d5c32d2f29bc.jpeg)

すると、このようなURLを取得でき、

![スクリーンショット_2023-12-18_20_22_13.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/ab3be644-b54f-8cb5-5adb-b57f91f6ed8e.jpeg)

開いてみると、、全く同じグラフが取得できるではないですか！

![スクリーンショット_2023-12-18_20_23_43.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/f5c9e5f5-f595-65db-399a-e87df230d5f0.jpeg)

これをダッシュボードに設置しているグラフ分取得し、HTMLに置いてあげればダッシュボードの提供ができそうな予感がしてきました。
次に、ダッシュボードにあるウィジェットを全て取得したいので、New RelicのAPIを利用して取得する方法を確認します。

なお、このURLをHTMLのiframeにこのような形で記載すれば、HTML内にNew Relicのグラフを埋め込めることが可能です。

```
<iframe>https://chart-embed.service.newrelic.com....</iframe>
```


# New Relicダッシュボードのウィジェットを取得する

まずは、New Relic APIであるNerdGraphを利用し、先ほどの「ChartURL」の取得を行っていきます。


## NerdGraphとは

[NerdGraph](https://docs.newrelic.com/jp/docs/apis/nerdgraph/get-started/introduction-new-relic-nerdgraph/)


> NerdGraphは、いくつかのNew Relic APIの1つです。NerdGraphは、New Relicデータにクエリを実行して、特定の設定を実行するための推奨APIです。NerdGraphには、New RelicのさまざまなAPIおよびマイクロサービスからデータを返すための単一のAPIインタフェースがあります。NerdGraphには、時間経過とともに他の設定機能が追加される予定です。

[こちらのセットアップ](https://docs.newrelic.com/jp/docs/apis/nerdgraph/get-started/nerdgraph-explorer/#set-up)を参考にします。

利用するには
New Relicにログイン後 [NerdGraph API explorer](https://api.newrelic.com/graphiql)を開きます。
API実行するために、New Relicのユーザキーも必要となります。

下記ような画面からGUI上で操作していきます。

![スクリーンショット_2023-12-18_20_37_05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/72032931-d1d9-5b7d-cf6c-c34092ddd558.jpeg)

## NerdGraphで取得可能なダッシュボードデータの確認

まずは使えそうなクエリがあるかを調べてみます。xxxURLかな。と予想の元「url」と検索してみます。

![スクリーンショット_2023-12-18_21_55_06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/92f22a56-cff9-0fa3-96f5-af651e0101a8.jpeg)

結構な数ヒットしましたが、データ構造等から想像して使えそうなクエリを２つに絞りました。

![スクリーンショット 2023-12-18 21.53.45.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/195db649-7f7c-12e3-5b74-c6375d7930f0.jpeg)
![スクリーンショット 2023-12-18 21.52.29.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/8c9c94f7-f741-3e66-6185-2c0507c02cd7.jpeg)


### ChartURL取得API 候補１： DashboardLiveUrl.url

![スクリーンショット 2023-12-18 21.53.45.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/195db649-7f7c-12e3-5b74-c6375d7930f0.jpeg)

DashboardLiveUrl.urlの方がDashboardとついてるので良さそうに見えますが、実際に実行してみると
※url の右横にある□を押すとエディタに下記が表示されます。

![スクリーンショット 2023-12-18 22.19.00.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/6dca2ace-4be0-d4e7-1f10-cc99eb85caeb.jpeg)

```graphql
{
  actor {
    dashboard {
      liveUrls {
        liveUrls
      }
    }
  }
}
```

画面左の query を見ると、他にも出力すると良さそうな項目があるのでそちらを何個か選択し、下記のように選択して実行します。
※実行はピンクの「▷」のボタンから実行できます。

![スクリーンショット_2023-12-18_21_27_44.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/043bb865-4f4a-bb38-2ee4-a478ef9d9cda.jpeg)

実行結果を見ると、これだと順不同でウィジェットを取得している？ようで、ウィジェットの表示順番がぐちゃぐちゃになってしまいます。タイトル等で整理することでもできそうですが、今回は別の方法を取ることにしました。


### ChartURL取得API 候補２： NrdbResultContainer.embededChartUrl


![スクリーンショット 2023-12-18 21.52.29.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/8c9c94f7-f741-3e66-6185-2c0507c02cd7.jpeg)

こちらはNRQLを元にChartURLを生成できるクエリのようです。


例えば、下記を実行してみます。

```
{
  actor {
    account(id: <ACCOUNTID>) {
      nrql(query: "SELECT count(*) FROM Metric TIMESERIES") {
        embeddedChartUrl(chartType: LINE)
      }
    }
  }
}
```

実行すると、ChartURLが表示され

![スクリーンショット_2023-12-19_18_05_58.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/63eee74b-42c4-50df-66a3-9440794c1d78.jpeg)

NRQLのデータグラフを、生成されたURLから閲覧することができました。

![スクリーンショット_2023-12-19_18_08_35.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/e0c83be7-050b-de07-e113-e157887c7a90.jpeg)


ですので、別クエリからダッシュボードの左上から順にNRQLを取得できれば無事目的を達成できることになります。
今回はこちらのクエリを採用しました。

続けて、ダッシュボードのNRQLを取得するクエリを確認していきます。

## ダッシュボードNRQL取得API

同様に、ダッシュボードにあるウィジェット情報を取得できそうなクエリを「nrql」で検索して、Dashboard関連のクエリを探してみると、「DashboardLineWidgetConfiguration.nrqlQueries」が見つかりました。

![スクリーンショット 2023-12-18 22.34.05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/e76865dc-a0c3-ad1b-ecb3-27324333cf06.jpeg)

ためしに □ を押下して、エディタで開いてみると、
entities / nrqlQueries がエラーになっているようです。

![スクリーンショット_2023-12-18_22_36_56.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/a4e13668-b63a-e53c-ef29-84a64925787d.jpeg)

赤くなっている文字へカーソルを当ててみるとエラー内容を確認できました。

![スクリーンショット 2023-12-18 22.38.14.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/1a87e836-d817-7182-e9c9-58e9c5389356.jpeg)

![スクリーンショット 2023-12-18 22.38.36.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/426d6c09-2911-57cc-05ff-30ca57e6caa5.jpeg)


エラー内容の通り、**ダッシュボードのguid**が必要となりそうですので、
作成の元にしたい「New Relic ダッシュボードのguid」を

> New Relic - ダッシュボード - Metadata

から確認します。

![スクリーンショット_2023-12-18_22_40_46.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/fcc2b433-23ad-3a43-923f-c3839216a7cb.jpeg)

確認したguidを、
> actor > entities > guids!: "xxxx"

の箇所へ入力します。
また、同様に、「nrqlQueries」内にある「query」にチェックを入れ、下記のようにクエリを作成します。

![スクリーンショット_2023-12-18_22_44_56.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/cea5a1cd-8585-4490-2998-8534b8696a4d.jpeg)


```
{
  actor {
    entities(guids: "xxxxxxxx") {
      ... on DashboardEntity {
        pages {
          widgets {
            configuration {
              line {
                nrqlQueries {
                  query
                }
              }
            }
          }
        }
      }
    }
  }
}
```

こちらを実行することでようやくダッシュボードにあるNRQLを取得することに成功しました！

![スクリーンショット 2023-12-18 22.52.31.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/5afb219d-c71b-acdb-1ce5-f014768ab962.jpeg)

ただ、上記APIだと、Lineで表示されているグラフのみしか取得できないため、最終的には**configuration**からではなく、**rawConfiguration**を利用して、下記のようにしてダッシュボードの構成情報を取得するようにしています。

```
{
  actor {
    entity(guid: "xxxxxxxx") {
      ... on DashboardEntity {
        name
        permissions
        pages {
          name
          widgets {
            title
            rawConfiguration
            layout {
              width
            }
            visualization {
              id
            }
          }
        }
      }
    }
  }
}
```

これで、ダッシュボードの左上から順にウィジェットを複数取得することに成功しました。
また、こちらだとダッシュボードのページが複数になっていても全て取得できるようです。

![スクリーンショット_2023-12-18_22_57_42.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/35a1fe4b-9e35-1f07-eadd-ea769c07a16f.jpeg)

これで
  * NRQLをもとにChartURLを生成
  * ダッシュボードのNRQLを左上から順番に取得

のクエリを確認できました。
あとはこれをどのようにHTMLに変換していくかとなります。

# NerdGraph実行結果をHTMLに変換する

クエリを書いた状態で、 キー情報の横にある
「Tools」 - 「Copy as CURL」
を押下すれば、curlを実行するのに必要な情報を取得可能です。このようなcurlが取得できます。

```
curl https://api.newrelic.com/graphql \
  -H 'Content-Type: application/json' \
  -H 'API-Key: NRAK-xxxxxxxxxxxx' \
  --data-binary '{"query":"{\n  actor {\n    entity(guid: \"xxxxx\") {\n      ... on DashboardEntity {\n        name\n        permissions\n        pages {\n          name\n          widgets {\n            title\n            rawConfiguration\n            layout {\n              width\n            }\n            visualization {\n              id\n            }\n          }\n        }\n      }\n    }\n  }\n}\n", "variables":""}'
```

さて、このNerdGraphの実行結果をどのようにHTMLに変換するかですが、、、
レスポンスはJSONのような形で、すでに左上からウィジェットを取得できそうと言うことで、
今回は単純に上から順にHTMLにはめるだけよさそうです。そのため、Pythonを使ってレスポンスをループ・処理させることにしました。

## PythonでのHTML変換処理

### 処理概要
上記curlでクエリ実行できることがわかったので、Pythonの処理としては

```
1. 「ダッシュボードのウィジェット・NRQLを左上から順番に取得」するAPIを実行
2. ダッシュボードのウィジェットが取得できるので一つづつ処理を行う（ウィジェットごとにループ）
  2-a. NRQLを取得できたので、「NRQLをもとにChartURLを生成」するAPIにリクエストし、ChartURLを取得
  2-b. HTMLのiframeにChatURLを埋め込む
3. HTMLが完成！！
```

とループ処理させれば、New Relicのダッシュボードと同じデータ構造を作成することができます。

### 環境準備
今回はpythonでHTTPリクエストを実施するのに、**requests** を利用しています。
必要に応じて端末上で

> pip install requests

を実行し、モジュールをインストールしたら、下記で実行可能になります。

> python main.py

### レスポンスをHTMLに変換するPythonスクリプト

レスポンスの結果を１ウィジェットづつループさせる処理は下記の通り作成しました。
※一部CSS/JSを当て込む処理等々については省略して記載しています。

:::note warn
ダッシュボードに置いてあるウィジェットによってはエラーとなることもあるので、その場合は適宜修正して利用ください。

また、上部にある下記パラメータは適宜置換してください。

> NR_API_ENDPOINT = "https://api.newrelic.com/graphql" # New RelicAPIエンドポイント※先ほどのcurlから確認
API_KEY = "NRAK-xxxxxx" # curlに記載されていたAPIキー情報
DASHBOARD_GUID = "xxxxxxxxxx" # New RelicダッシュボードのGUID
:::

```main.py
import requests
import json

global DASHBOARD_GUID

NR_API_ENDPOINT = "https://api.newrelic.com/graphql" # New Relic APIエンドポイント※先ほどのcurlから確認
API_KEY = "NRAK-xxxxxx" # curlに記載されていたAPIキー情報
DASHBOARD_GUID = "xxxxxxxxxx" # New RelicダッシュボードのGUID

def post_request_to_newrelic_api(query):
  """
  New RelicAPIにリクエストを投げる
  """
  response = requests.post(NR_API_ENDPOINT, headers={'API-Key': f'{API_KEY}'}, json={"query": query })

  if response.status_code == 200:
    # JSON型にロード
    json_dictionary = json.loads(response.content)
    return json_dictionary

  else:
      raise Exception(f'Nerdgraph query failed {response.status_code}.')


def newrelic_api_get_chart_url(nr_query, nr_accountid, charttype):
  """
  NRQLをもとにCharURLを取得するAPIを実行するための処理
  """
  # NerdGraphにリクエスト
  # chartTypeがUPPERケースのみ許可されているのでcharttype.upper()する
  get_chart_url_query = """{ actor { account(id:""" + str(nr_accountid) + """) {nrql(query: " """+ nr_query + """ ") { embeddedChartUrl(chartType:""" + charttype.upper() + """)}}}}"""
  result = post_request_to_newrelic_api(get_chart_url_query)
  print(result)

  return result.get("data").get("actor").get("account").get("nrql", "").get("embeddedChartUrl","")


def nerdgraph_get_dashboard_entity():
  """
  ダッシュボードのウィジェット情報（NRQL/幅/配置等々）を取得
  """
  get_default_dashboard_query="""{
    actor {
      entity(guid: \"""" + DASHBOARD_GUID + """\") {
        ... on DashboardEntity {
          name
          permissions
          pages {
            name
            widgets {
              title
              rawConfiguration
              layout {
                width
              }
              visualization {
                id
              }
            }
          }
        }
      }
    }
  }"""

  return post_request_to_newrelic_api(get_default_dashboard_query)


def create_dashboard_html():
  entity = newrelic_api_get_dashboard_entity().get("data").get("actor").get("entity")

  # 最終的なHTML。変数を初期化
  html_text = ""
  # HTML headerの作成
  html_text += """
  <html lang=ja>
  <head>
    <link href="common.css" rel="stylesheet" />
    <script type="text/javascript" src="common.js"></script>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
  </head>

  <body>
    <hr />
  """

  # ダッシュボードの情報からNRQLを取得
  for page in entity.get("pages"):
    html_text += f'<h2> {page.get("name")} </h2>'

    div_flag = False
    # 左上から順番にウィジェット、テキストを取得
    for widget in page.get("widgets"):
      title = widget.get("title")
      text = widget.get("rawConfiguration").get("text")

      # Label(Text area)の処理
      if widget.get("visualization").get("id") == "viz.markdown":
        # Incidentsは不要
        if not text.startswith("# Incidents"):
          # ２回目以降はdivを閉じる
          if div_flag:
            html_text += '</div><br />'

          html_text += f'<hr />'
          html_text += f'<h3 id="{text}"> {text} </h3>'
          # 2回目以降はdivを閉じるためのフラグ
          div_flag = True

      # ウィジェットの処理
      if title != "":

        charttype = widget.get("visualization").get("id", "")

        for nrqlQueries in widget.get("rawConfiguration").get("nrqlQueries"):
            # NRQLに改行があるとエラーになるので排除する
            nrquery = nrqlQueries.get("query").replace("\n","")
            
            # NRQLのアカウントを確認
            if nrqlQueries.get("accountId") is not None:
                accountid = nrqlQueries.get("accountId")
            else:
                accountid = nrqlQueries.get("accountIds")[0]

            # ChartTypeは「viz.table」等なので「viz.」を削ってから渡す
            chart_url = nerdgraph_get_chart_url(nrquery, accountid, charttype.replace("viz.",""))
            
            html_text += f'<div>'
            html_text += f'<p>{title}</p>'
            html_text += f'<iframe scrolling="yes" frameborder="no" src="{chart_url}">'
            html_text += f'<!--{title}--></iframe></div>'
    html_text += "</div><br />\n"
  html_text += "</body></html>"

  print("HTML Output: \n" + html_text)


def main():
  create_dashboard_html()
  return "Success"


if __name__ == "__main__":
    main()

```

こちらを利用することで、APIからのJSONレスポンスをHTMLに変換可能です。
あとは、必要に応じてCSS/JS用のclass等の属性をHTMLに付与すれば見栄えも良くすることが可能です。


# ダッシュボードを公開するためのインフラ構成

最終的にはダッシュボードを表示するためにHTMLと、見栄えをきれいにするためにCSS/JSを用意しました。
また、普段AWSを利用しているのでその中でも、簡単にWebページをホストできるAmplifyを選択することにしました。

## AWS Amplifyとは？

AWS Amplifyは、AWSが提供する、モバイルアプリケーションとウェブアプリケーションの開発を容易にするための開発プラットフォームです。フロントエンド開発とクラウドサービスの統合をシームレスに行うことができます。

リポジトリ（CodeCommit）から直接アプリケーションを、ビルド ~ デプロイまでしてくれるCI/CDパイプラインをAWSマネージドで管理してくれるAWSサービスで、簡単なWebページの公開であれば非常に簡単に利用することができます。

## インフラ構成

HTML / CSS / JSを用意したら、あとはCodeCommitにそれらのソースコードをPUSHします。
ダッシュボードページの配信はAmplifyを用いてデプロイされますが、内部的にはCloudFront+S3構成となります。

また、先ほどPythonスクリプトはLambdaから実行できるようにしたので最終的にはこのような構成となりました。

![スクリーンショット 2023-12-19 16.48.16.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/4a5dddd8-c6e4-d121-5413-f3d4d8da10cb.jpeg)


# 最終的なダッシュボード

今回、実際に利用しているHTML/CSSなどは公開しておりませんが、最終的にはこのようなページを作成することができました。こちらのブログで提供可能なダッシュボード・データがなく、紹介がスクショになってしまい申し訳ないです。。。

![スクリーンショット_2023-12-19_18_56_33.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/2c4fde76-66e9-693e-d288-89a4b3dc3b9b.jpeg)

![スクリーンショット_2023-12-19_18_59_49.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/88ced0e7-01a7-3815-5f4d-f8ca3fddc7c4.jpeg)


New Relic上のダッシュボード設定元にChartURLをHTML上に並べたことで、自動でこのような画面を生成することが可能となりました。これで、New Relicアカウントにログインすることなく閲覧することが可能となりました。

# 今回できなかったこと・課題
## ウィジェットオプションを使う

New Relicのグラフ表示オプションにはy軸の範囲が大きすぎてデータが見づらい際にデータを見やすくするオプションや、データの表示形式をテーブルや円グラフなど種類を変えるなどのオプションが存在しています。

例えば、このように「Fit it to data」を選択すると、グラフのy軸を値にフィットさせてくれるオプションです。
![スクリーンショット_2023-12-19_16_13_38.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38195/c6f6bbb5-ea23-f98f-8400-a12292a2bc98.jpeg)

こちらのようなグラフオプションは、各グラフの右上にある「…」 - 「edit」からグラフの見栄えの設定が可能です。

今回のAPIからNRQLを使ってChartURLを生成する方法だと、このようなオプションの指定ができないため、データによっては見づらいグラフもありました。
現状、回避策はないようなので、今後のアップデートに期待となります。

## 一時URLの発行

発行したChartURLは一時的なものではなく発行すると、永久的にアクセス可能なURLとなっています。
そのため、万が一URLが漏洩した際は下記から取り消しをする必要がありそうでした。参考までに。

[NerdGraphチュートリアル](https://docs.newrelic.com/jp/docs/apis/nerdgraph/examples/manage-live-chart-urls-via-api/)

# さいごに

New Relic API - NerdGraphを使って、New Relicアカウントなしで閲覧可能なダッシュボードを生成してみました。
課題は残ったものの、NerdGraphの勉強にもなりましたし、なにより当初の目的を達成することができました！！

同じ悩みを持った誰かの参考になれば幸いです。
また、New Relicの仕様変更でこのような機能を提供していただけないか？については、引き続きウォッチしていきます。

今回作成したAmplifyの作成方法詳細については、また別の機会にブログにまとめようと思います。