Everything described in this article is based on the all-in-one architecture. In a production environment, it can be split into a distributed architecture based on actual needs.
Dependent technology stack:
- VM
- docker
- Elasticsearch
- Grafana
- Python3
All operations are performed in the VM
everything is on-premises and free (except VM)
The only thing you need is:
- a VM
- Memory: 16G is recommended
- Operating system: Ubuntu 22.04 (recommended, other operating systems have no difference except Docker installation)
- Port:
3000port needs to be released for Grafana to use, and22port can be determined by yourself.
Everything else is based on existing stuff, or based on open source software, no extra cost, for example:
- GitHub Organzations with Copilot enabled (I believe, you already have it)
- Docker (Community Version is enough)
- Elasticsearch (Community Version is enough)
- Grafana (Community Version is enough, do not need Grafana Cloud Account)
- CPUAD-Updater build from this project (MIT license)
For installation instructions, refer to Install Docker Engine. For Ubuntu 22.04, you can use the following command
apt install docker.ioverify
docker versionThe following content is obtained, indicating ok
Client:
Version: 24.0.7
API version: 1.43
Go version: go1.21.1
Git commit: 24.0.7-0ubuntu2~22.04.1
Built: Wed Mar 13 20:23:54 2024
OS/Arch: linux/amd64
Context: default
Server:
Engine:
Version: 24.0.7
API version: 1.43 (minimum version 1.12)
Go version: go1.21.1
Git commit: 24.0.7-0ubuntu2~22.04.1
Built: Wed Mar 13 20:23:54 2024
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.7.12
GitCommit:
runc:
Version: 1.1.12-0ubuntu2~22.04.1
GitCommit:
docker-init:
Version: 0.19.0
GitCommit:
Docker images for all app components are available in GitHub container registry:
- Elastic Search -
docker pull ghcr.io/satomic/copilot-usage-advanced-dashboard/elastic-search:main - grafana -
docker pull ghcr.io/satomic/copilot-usage-advanced-dashboard/grafana:main - grafana-updater -
docker pull ghcr.io/satomic/copilot-usage-advanced-dashboard/grafana-updater:main - cpuad-updater -
docker pull ghcr.io/satomic/copilot-usage-advanced-dashboard/cpuad-updater:main
Put all the work in the /srv directory, click download zip archive, unzip and rename the folder to copilot-usage-advanced-dashboard, or directly git clone
cd /srv
git clone https://github.com/satomic/copilot-usage-advanced-dashboard.git
cd copilot-usage-advanced-dashboard/src/cpuad-updater
verify
ls -ltrThe following content is obtained, indicating ok
total 84
-rw-r--r-- 1 root root 100 May 13 08:33 fetch.sh
-rw-r--r-- 1 root root 56 May 13 08:33 docker_build.sh
-rw-r--r-- 1 root root 754 May 13 08:33 create_es_indexes.sh
-rw-r--r-- 1 root root 1198 May 13 08:33 Dockerfile
-rw-r--r-- 1 root root 1195 May 13 08:33 log_utils.py
-rw-r--r-- 1 root root 68 May 13 08:33 requirements.txt
-rw-r--r-- 1 root root 193 May 13 08:33 push.sh
-rw-r--r-- 1 root root 5868 May 13 08:33 metrics_2_usage_convertor.py
-rw-r--r-- 1 root root 8 May 13 09:55 version
drwxr-xr-x 2 root root 4096 May 13 09:55 mapping
-rw-r--r-- 1 root root 33351 May 13 09:55 main.py
drwxr-xr-x 2 root root 4096 May 13 10:00 grafanaIf you already have ES, you can skip this step and go directly to the next step.
ES will not be exposed to the outside of the VM, so there is no need to enable
xpack.security.enabled
- Create a data persistence directory and a configuration file directory for Elasticsearch:
mkdir -p /srv/elasticsearch/data /srv/elasticsearch/config
- Grant read and write permissions.
chmod -R a+rw /srv/elasticsearch/
- Create the
elasticsearch.ymlconfiguration file in the/srv/elasticsearch/config/directory:cat >> /srv/elasticsearch/config/elasticsearch.yml << EOF network.host: 0.0.0.0 node.name: single-node cluster.name: es-docker-cluster path.data: /usr/share/elasticsearch/data path.logs: /usr/share/elasticsearch/logs discovery.type: single-node bootstrap.memory_lock: true EOF
- Use the following command to start Elasticsearch and bind the data directory and configuration file:
docker run -itd --restart always --name es \ -p 9200:9200 \ -e "xpack.security.enabled=false" \ -e "ES_JAVA_OPTS=-Xms4g -Xmx4g" \ -v /srv/elasticsearch/data:/usr/share/elasticsearch/data \ -v /srv/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml:ro \ docker.elastic.co/elasticsearch/elasticsearch:8.17.0
- verify
The following content is obtained, indicating ok
curl http://localhost:9200
{ "name": "single-node", "cluster_name": "es-docker-cluster", "cluster_uuid": "oO3mfjYWTZ6VZFSClDiSLA", "version": { "number": "8.17.0", "build_flavor": "default", "build_type": "docker", "build_hash": "2b6a7fed44faa321997703718f07ee0420804b41", "build_date": "2024-12-11T12:08:05.663969764Z", "build_snapshot": false, "lucene_version": "9.12.0", "minimum_wire_compatibility_version": "7.17.0", "minimum_index_compatibility_version": "7.0.0" }, "tagline": "You Know, for Search" }
- Confirm that you are in the correct path
cd /srv/copilot-usage-advanced-dashboard/src/cpuad-updater - Execute the script and create an index
The following content is obtained, indicating ok
export ELASTICSEARCH_URL="http://localhost:9200" bash create_es_indexes.sh
{"acknowledged":true,"shards_acknowledged":true,"index":"copilot_usage_total"} {"acknowledged":true,"shards_acknowledged":true,"index":"copilot_usage_breakdown"} {"acknowledged":true,"shards_acknowledged":true,"index":"copilot_usage_breakdown_chat"} {"acknowledged":true,"shards_acknowledged":true,"index":"copilot_seat_info_settings"} {"acknowledged":true,"shards_acknowledged":true,"index":"copilot_seat_assignments"} - verify
The following content is obtained, indicating ok
curl -X GET http://localhost:9200/_cat/indices?vhealth status index uuid pri rep docs.count docs.deleted store.size pri.store.size dataset.size yellow open copilot_usage_total XrOEfAngTS60VsuUz3Lbrw 1 1 0 0 227b 227b 227b yellow open copilot_seat_info_settings WtOBdBNUQRqua7wi7VANeQ 1 1 0 0 227b 227b 227b yellow open copilot_seat_assignments lK5t4SwASZizPQ_W4NX4KQ 1 1 0 0 227b 227b 227b yellow open copilot_usage_breakdown xE6tkg5GQEOP-EP8pwAkYg 1 1 0 0 227b 227b 227b yellow open copilot_usage_breakdown_chat 6R_1cdlIQQOCv4BoHPqXCw 1 1 0 0 227b 227b 227b
If you already have Grafana, you can skip this step and go directly to the next step.
- Creating a Data Path
mkdir -p /srv/grafana/data chmod -R a+rw /srv/grafana/data
- run
docker run -itd --restart always --name=grafana \ --net=host \ -p 3000:3000 \ -v /srv/grafana/data:/var/lib/grafana \ -e "GF_LOG_LEVEL=debug" \ grafana/grafana:11.4.0 - Access Grafana
- Access address:
http://<PUBLIC_IP_OF_YOUR_VM>:3000 - The default username and password are
admin/admin, please change the password
- Access address:
-
admin visit Administration → Users and access → Service accounts
-
input Display name,Role select
Admin, Create -
click Add service account token
-
click Generate token
-
Copy to clipboard and close
-
Now, you have obtained your Grafana Token
"<your_grafana_token>", please save it and set it as an environment variable in the VM, which will be used in the next steps.export GRAFANA_TOKEN="<your_grafana_token>"
-
Confirm that you are in the correct path
cd /srv/copilot-usage-advanced-dashboard/src/cpuad-updater -
run script, add data sources
export GRAFANA_URL="http://localhost:3000" bash grafana/add_grafana_data_sources.sh
-
Visit the Grafana UI to confirm that the addition was successful
- Confirm that you are in the correct path
cd /srv/copilot-usage-advanced-dashboard/src/cpuad-updater - Execute the script to generate a Grafana json model. Execute one of the following two commands
Get the output
# Generate Copilot Usage Advanced Dashboard python3 grafana/gen_grafana_model.py
Please import grafana/import-this-to-grafana-2025-05-13.json to Grafana Model saved to grafana/dashboard-model-2025-05-13.json, please import it to Grafana
-
Download the generated file to your local computer
scp root@<PUBLIC_IP_OF_YOUR_VM>:/srv/copilot-usage-advanced-dashboard/src/cpuad-updater/grafana/import-this-to-grafana-*.json . import-this-to-grafana-2024-12-17.json 100% 157KB 243.8KB/s 00:00
-
Copy the generated json file and import it into Grafana
Select the file to import, or paste the content directly
-
Import
-
Congratulations, you now have a complete dashboard, but there should be no data yet. Next, run the core program.
is the abbreviation of the first characters of Copilot Usage Advanced Dashboard Updater
Parameter description
GITHUB_PAT:- Your GitHub account needs to have Owner permissions for Organizations.
- Create a personal access token (classic) of your account with the
manage_billing:copilot,read:enterprise,read:orgscope. Then please replace<YOUR_GITHUB_PAT>with the actual PAT. - If you encounter PAT permission error, please Allow access via fine-grained personal access tokens in Organization's Settings - Personal access tokens.
ORGANIZATION_SLUGS: The Slugs of all Organizations that you want to monitor, which can be one or multiple separated by,(English symbol). If you are using Copilot Standalone, use your Standalone Slug here, prefixed withstandalone:, for examplestandalone:YOUR_STANDALONE_SLUG. Please replace<YOUR_ORGANIZATION_SLUGS>with the actual value. For example, the following types of values are supported:myOrg1myOrg1,myOrg2standalone:myStandaloneSlugmyOrg1,standalone:myStandaloneSlug
LOG_PATH: Log storage location, not recommended to modify. If modified, you need to modify the-vdata volume mapping simultaneously.EXECUTION_INTERVAL: Update interval, the default is to update the program every1hours.ELASTICSEARCH_URL: The URL of your Elasticsearch, the default ishttp://localhost:9200, if you have modified the port, please modify it here.TZ: Timezone, the default isGMT, if you want to change it to your local timezone, please refer to tz database. For example, if you are in Toronto, please change it toAmerica/Toronto.
docker run -itd \
--net=host \
--restart=always \
--name cpuad \
-e GITHUB_PAT="<YOUR_GITHUB_PAT>" \
-e ORGANIZATION_SLUGS="<YOUR_ORGANIZATION_SLUGS>" \
-e LOG_PATH="logs" \
-e EXECUTION_INTERVAL=1 \
-e ELASTICSEARCH_URL="http://localhost:9200" \
-e TZ="GMT" \ # change to your local timezone if needed
-v /srv/cpuad-updater-logs:/app/logs \
satomic/cpuad-updaterIf your Elasticsearch instance requires authentication, pass include the ELASTICSEARCH_USER and ELASTICSEARCH_PASS environment variables.
-e ELASTICSEARCH_USER="elastic"
-e ELASTICSEARCH_PASS="mypassword"- Confirm that you are in the correct path
cd /srv/copilot-usage-advanced-dashboard/src/cpuad-updater - Install Dependencies
python3 -m pip install -r requirements.txt
- Setting Environment Variables. If you are using Copilot Standalone, use your Standalone Slug here, prefixed with
standalone:, for examplestandalone:YOUR_STANDALONE_SLUG.export GITHUB_PAT="<YOUR_GITHUB_PAT>" export ORGANIZATION_SLUGS="<YOUR_ORGANIZATION_SLUGS>" export ELASTICSEARCH_URL="http://localhost:9200" export GRAFANA_URL="http://localhost:3000"
- run
python3 main.py
- output logs
2024-12-17 05:32:22,292 - [INFO] - Data saved to logs/2024-12-17/nekoaru_level3-team1_copilot_usage_2024-12-17.json 2024-12-17 05:32:22,292 - [INFO] - Fetched Copilot usage for team: level3-team1 2024-12-17 05:32:22,293 - [INFO] - Data saved to logs/2024-12-17/nekoaru_all_teams_copilot_usage_2024-12-17.json 2024-12-17 05:32:22,293 - [INFO] - Processing Copilot usage data for organization: nekoaru 2024-12-17 05:32:22,293 - [INFO] - Processing Copilot usage data for team: level1-team1 2024-12-17 05:32:22,293 - [WARNING] - No Copilot usage data found for team: level1-team1 2024-12-17 05:32:22,293 - [INFO] - Processing Copilot usage data for team: level2-team1 2024-12-17 05:32:22,293 - [WARNING] - No Copilot usage data found for team: level2-team1 2024-12-17 05:32:22,293 - [INFO] - Processing Copilot usage data for team: level2-team2 2024-12-17 05:32:22,293 - [WARNING] - No Copilot usage data found for team: level2-team2 2024-12-17 05:32:22,293 - [INFO] - Processing Copilot usage data for team: level3-team1 2024-12-17 05:32:22,293 - [WARNING] - No Copilot usage data found for team: level3-team1 2024-12-17 05:32:22,293 - [INFO] - Sleeping for 6 hours before next execution... 2024-12-17 05:32:22,293 - [INFO] - Heartbeat: still running...
At this moment, in the VM, you should be able to see 3 containers running (if you have deployed them from scratch based on docker), as follows:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1edffd12a522 satomic/cpuad-updater:20241221 "python3 main.py" 23 hours ago Up 10 hours cpuad
b19e467d48f1 grafana/grafana:11.4.0 "/run.sh" 25 hours ago Up 10 hours grafana
ee35b2a340f1 docker.elastic.co/elasticsearch/elasticsearch:8.17.0 "/bin/tini -- /usr/l…" 3 days ago Up 10 hours 0.0.0.0:9200->9200/tcp, :::9200->9200/tcp, 9300/tcp esAt this point, return to the Grafana page and refresh. You should be able to see the data.
or











