Skip to content

Instantly share code, notes, and snippets.

@jdbcode
Last active January 27, 2026 20:32
Show Gist options
  • Select an option

  • Save jdbcode/43383d9dc28bf71d4f5d9449f17cfc04 to your computer and use it in GitHub Desktop.

Select an option

Save jdbcode/43383d9dc28bf71d4f5d9449f17cfc04 to your computer and use it in GitHub Desktop.
earth_engine_noncommercial_eecu_monitor.ipynb
Display the source blob
Display the rendered blob
Raw
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": [],
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/gist/jdbcode/43383d9dc28bf71d4f5d9449f17cfc04/earth_engine_noncommercial_eecu_monitor.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"source": [
"**Disclaimer**: This notebook aggregates and visualizes data from [Cloud Monitoring](https://docs.cloud.google.com/monitoring/docs) for informational purposes only. It is not an official Google product and the methods used for aggregation may not reflect official metric calculations."
],
"metadata": {
"id": "1zAWLpRRPlNA"
}
},
{
"cell_type": "code",
"source": [
"# @title 🌍 Earth Engine Noncommercial EECU Monitor\n",
"# @markdown Use this notebook to track daily and cumulative monthly EECU usage.\n",
"# @markdown ### **Instructions**\n",
"# @markdown 1. Enter your **Project ID**.\n",
"# @markdown 2. Select **User Tier** (Optional: Select \"None\" for raw usage, or a tier for quota comparisons).\n",
"# @markdown 3. Run the cell to check your status.\n",
"\n",
"import pandas as pd\n",
"import plotly.graph_objects as go\n",
"from plotly.subplots import make_subplots\n",
"import time\n",
"import calendar\n",
"from datetime import datetime, timedelta, timezone\n",
"from google.colab import auth\n",
"from google.cloud import monitoring_v3\n",
"from IPython.display import display, Markdown\n",
"\n",
"# --- USER CONFIGURATION ---\n",
"PROJECT_ID = \"ee-yourproject\" # @param {type:\"string\"}\n",
"SHOW_QUOTA_TIER = \"Community\" # @param [\"None\", \"Community\", \"Contributor\", \"Partner\"]\n",
"MONTHS_TO_SHOW = 12 # @param {type:\"slider\", min:1, max:12, step:1}\n",
"\n",
"# Define limits based on tier\n",
"TIER_LIMITS = {\n",
" \"Community\": 150,\n",
" \"Contributor\": 1000,\n",
" \"Partner\": 100000\n",
"}\n",
"QUOTA_LIMIT = TIER_LIMITS.get(SHOW_QUOTA_TIER)\n",
"\n",
"# Authenticate\n",
"try:\n",
" auth.authenticate_user()\n",
"except:\n",
" pass\n",
"\n",
"def get_eecu_data(project_id, months_back):\n",
" if not project_id or project_id == \"YOUR_PROJECT_ID_HERE\":\n",
" return pd.DataFrame()\n",
"\n",
" client = monitoring_v3.MetricServiceClient()\n",
" project_name = f\"projects/{project_id}\"\n",
"\n",
" # --- 1. DEFINE TIME WINDOW (CALENDAR ALIGNED) ---\n",
" now = datetime.now(timezone.utc)\n",
"\n",
" # Calculate start date (1st of the target month)\n",
" # Logic: If months_back=1, we want start date = 1st of current month.\n",
" # So we subtract (months_back - 1).\n",
" start_year = now.year\n",
" start_month = now.month - (months_back - 1)\n",
"\n",
" while start_month <= 0:\n",
" start_month += 12\n",
" start_year -= 1\n",
" start_time = datetime(start_year, start_month, 1, tzinfo=timezone.utc)\n",
"\n",
" interval = monitoring_v3.TimeInterval({\n",
" \"end_time\": {\"seconds\": int(now.timestamp()), \"nanos\": 0},\n",
" \"start_time\": {\"seconds\": int(start_time.timestamp()), \"nanos\": 0}\n",
" })\n",
"\n",
" try:\n",
" results = client.list_time_series(\n",
" request={\n",
" \"name\": project_name,\n",
" \"filter\": 'metric.type = \"earthengine.googleapis.com/project/cpu/usage_time\"',\n",
" \"interval\": interval,\n",
" \"view\": monitoring_v3.ListTimeSeriesRequest.TimeSeriesView.FULL,\n",
" \"aggregation\": {\n",
" \"alignment_period\": {\"seconds\": 86400},\n",
" \"per_series_aligner\": monitoring_v3.Aggregation.Aligner.ALIGN_SUM,\n",
" \"cross_series_reducer\": monitoring_v3.Aggregation.Reducer.REDUCE_SUM\n",
" }\n",
" }\n",
" )\n",
" except Exception as e:\n",
" print(f\"Error fetching data: {e}\")\n",
" return pd.DataFrame()\n",
"\n",
" data = []\n",
" for result in results:\n",
" for point in result.points:\n",
" data.append({\n",
" \"date\": point.interval.end_time,\n",
" \"daily_hours\": point.value.double_value / 3600\n",
" })\n",
"\n",
" if not data:\n",
" return pd.DataFrame(columns=[\"date\", \"daily_hours\"])\n",
"\n",
" df = pd.DataFrame(data)\n",
"\n",
" # --- 2. DATA PROCESSING ---\n",
" df[\"date\"] = pd.to_datetime(df[\"date\"]).dt.tz_localize(None).dt.normalize()\n",
" df = df.groupby(\"date\")[\"daily_hours\"].sum().to_frame()\n",
"\n",
" # --- 3. FILL GAPS ---\n",
" last_day_of_month = calendar.monthrange(now.year, now.month)[1]\n",
" end_of_month_date = datetime(now.year, now.month, last_day_of_month)\n",
"\n",
" full_idx = pd.date_range(\n",
" start=start_time.replace(tzinfo=None).date(),\n",
" end=end_of_month_date.date(),\n",
" freq='D'\n",
" )\n",
" df = df.reindex(full_idx, fill_value=0)\n",
" df.index.name = 'date'\n",
"\n",
" # --- 4. CUMULATIVE MATH ---\n",
" df[\"month_group\"] = df.index.to_period('M')\n",
" df[\"monthly_cumulative\"] = df.groupby(\"month_group\")[\"daily_hours\"].cumsum()\n",
"\n",
" return df\n",
"\n",
"# --- OUTPUT GENERATION ---\n",
"DAILY_COLOR = \"#298c8c\" # \"#4c78a8\"\n",
"CUMULATIVE_COLOR = \"#800074\" # \"#ff7f0e\"\n",
"\n",
"if PROJECT_ID != \"YOUR_PROJECT_ID_HERE\":\n",
" df = get_eecu_data(PROJECT_ID, MONTHS_TO_SHOW)\n",
"\n",
" if not df.empty and 'daily_hours' in df.columns:\n",
"\n",
" # --- STATUS CHECK ---\n",
" current_date = pd.Timestamp.today().normalize()\n",
" if current_date in df.index:\n",
" current_val = df.loc[current_date, \"monthly_cumulative\"]\n",
" else:\n",
" current_val = df[\"monthly_cumulative\"].iloc[-1]\n",
"\n",
" if QUOTA_LIMIT:\n",
" if current_val < QUOTA_LIMIT:\n",
" remaining = QUOTA_LIMIT - current_val\n",
" status_msg = f\"**✅ Within {SHOW_QUOTA_TIER} Tier** ({current_val:.1f} / {QUOTA_LIMIT} hrs used). You have **{remaining:.1f} hours** remaining.\"\n",
" else:\n",
" status_msg = f\"**⛔ Quota Depleted** ({current_val:.1f} hrs used). You have exceeded the {QUOTA_LIMIT}hr {SHOW_QUOTA_TIER} limit.\"\n",
" else:\n",
" status_msg = f\"**{current_val:.1f} hours** used this month.\"\n",
"\n",
" display(Markdown(f\"### Current Month Status: {status_msg}\"))\n",
"\n",
" # --- PLOTLY CHART ---\n",
" fig = make_subplots(specs=[[{\"secondary_y\": True}]])\n",
"\n",
" # A. Bar chart (daily)\n",
" fig.add_trace(\n",
" go.Bar(\n",
" x=df.index,\n",
" y=df['daily_hours'],\n",
" name=\"Daily Usage\",\n",
" marker_color=DAILY_COLOR,\n",
" opacity=0.7,\n",
" hovertemplate='%{x|%b %d}<br>Daily: %{y:.2f} hrs<extra></extra>'\n",
" ),\n",
" secondary_y=False,\n",
" )\n",
"\n",
" # B. Line chart (cumulative)\n",
" fig.add_trace(\n",
" go.Scatter(\n",
" x=df.index,\n",
" y=df['monthly_cumulative'],\n",
" name=\"Month Total\",\n",
" mode='lines',\n",
" line=dict(color=CUMULATIVE_COLOR, width=3),\n",
" hovertemplate='%{x|%b %d}<br>Month Total: %{y:.2f} hrs<extra></extra>'\n",
" ),\n",
" secondary_y=True,\n",
" )\n",
"\n",
" # C. month separators\n",
" month_starts = df.resample('MS').first().index\n",
" for date in month_starts:\n",
" fig.add_vline(x=date, line_width=1, line_dash=\"dash\", line_color=\"black\", opacity=0.3)\n",
"\n",
" # D. Quota guideline (based on selection)\n",
" max_monthly = df['monthly_cumulative'].max()\n",
" range_max = max_monthly * 1.1\n",
"\n",
" if QUOTA_LIMIT:\n",
" fig.add_hline(\n",
" y=QUOTA_LIMIT, line_dash=\"dot\", line_color=\"orange\", secondary_y=True,\n",
" )\n",
" # Ensure limit is visible if usage is low\n",
" range_max = max(range_max, QUOTA_LIMIT * 1.05)\n",
"\n",
" # Layout\n",
" fig.update_layout(\n",
" title_text=f\"Earth Engine EECU Usage (Last {MONTHS_TO_SHOW} Months)\",\n",
" hovermode=\"x unified\",\n",
" showlegend=False,\n",
" height=500,\n",
" bargap=0.1,\n",
" margin=dict(l=20, r=20, t=50, b=20),\n",
" template=\"simple_white\"\n",
" )\n",
"\n",
" max_daily = df['daily_hours'].max()\n",
" if pd.isna(max_daily) or max_daily == 0: max_daily = 1\n",
"\n",
" fig.update_yaxes(title_text=\"Daily Usage (EECU-Hours)\", color=DAILY_COLOR, secondary_y=False, showgrid=False, range=[0, max_daily * 1.1])\n",
" fig.update_yaxes(title_text=\"Cumulative Monthly Total (EECU-Hours)<br>(resets to zero on the first of each month)\", color=CUMULATIVE_COLOR, secondary_y=True, showgrid=False, range=[0, range_max])\n",
"\n",
" fig.show()\n",
"\n",
" # --- SUMMARY TABLE ---\n",
" summary = df.resample('ME')['daily_hours'].agg(['sum', 'mean', 'max']).round(2)\n",
" summary.columns = ['Total EECU-Hours', 'Avg Daily', 'Peak Day']\n",
" summary.index = summary.index.strftime('%B %Y')\n",
" summary = summary.reset_index().rename(columns={'date': 'Month'})\n",
"\n",
" # Display as a clean table\n",
" print(\"\\n\")\n",
" display(summary.style.format({\n",
" 'Total EECU-Hours': '{:.2f}',\n",
" 'Avg Daily': '{:.2f}',\n",
" 'Peak Day': '{:.2f}'}).hide(axis='index'))\n",
"\n",
" else:\n",
" print(\"No data found. Check your Project ID.\")\n",
"else:\n",
" print(\"Please enter a valid Project ID above.\")"
],
"metadata": {
"id": "4xMpkYkO2Z2C",
"cellView": "form"
},
"execution_count": null,
"outputs": []
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment