From b03200b2afbb584c0fbb1d2d627d987023422b3f Mon Sep 17 00:00:00 2001 From: Lin Lin Date: Thu, 14 Aug 2025 15:27:41 -0700 Subject: [PATCH 01/10] enable postgres --- Procfile | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Procfile b/Procfile index 2dc630d1d..d8c93c06f 100644 --- a/Procfile +++ b/Procfile @@ -4,4 +4,4 @@ web: gunicorn --config gunicorn.conf.py gettingstarted.wsgi # migrations are run as part of app deployment, using Heroku's Release Phase feature: # https://docs.djangoproject.com/en/5.2/topics/migrations/ # https://devcenter.heroku.com/articles/release-phase -#release: ./manage.py migrate --no-input +release: ./manage.py migrate --no-input diff --git a/requirements.txt b/requirements.txt index d07820d18..28ec9ae2b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ dj-database-url>=3,<4 whitenoise>=6,<7 # Uncomment to use a Postgres database. -#psycopg[binary] +psycopg[binary] From 6b989ce44e222a3712e901f202c7c7116fd2a0d9 Mon Sep 17 00:00:00 2001 From: Lin Lin Date: Thu, 14 Aug 2025 16:16:53 -0700 Subject: [PATCH 02/10] added ao custom metrics --- hello/views.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/hello/views.py b/hello/views.py index bd7a34b00..5eefd1547 100644 --- a/hello/views.py +++ b/hello/views.py @@ -8,6 +8,18 @@ def index(request): return render(request, "index.html") +def aocustom(request): + # counts + print("count@user.clicks=1") + print("count#user.clicks.tagged=1 tag#user_id=2 tag#user_geo=earth") + # measures + print("measure#database.query=200ms") + print("measure#database.query.tagged=200ms tag#db_name=foo tag#db_type=postgres") + # samples + print("sample#database.size=40.9MB") + print("sample#database.size.tagged=40.9MB tag#db_name=foo tag#db_type=postgres") + return render(request, "index.html") + def db(request): # If you encounter errors visiting the `/db/` page on the example app, check that: From 99b22023afed15377150a22978cb2484caf95cf8 Mon Sep 17 00:00:00 2001 From: Lin Lin Date: Thu, 14 Aug 2025 16:24:36 -0700 Subject: [PATCH 03/10] added ao-custom route --- gettingstarted/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gettingstarted/urls.py b/gettingstarted/urls.py index b35f43dbb..95e5f7d01 100644 --- a/gettingstarted/urls.py +++ b/gettingstarted/urls.py @@ -23,6 +23,7 @@ urlpatterns = [ path("", hello.views.index, name="index"), path("db/", hello.views.db, name="db"), + path("ao-custom/", hello.views.aocustom, name="aocustom"), # Uncomment this and the entry in `INSTALLED_APPS` if you wish to use the Django admin feature: # https://docs.djangoproject.com/en/5.2/ref/contrib/admin/ # path("admin/", admin.site.urls), From 62aca48316d63b884d8d4098a5752fc99c59b48b Mon Sep 17 00:00:00 2001 From: Lin Lin Date: Thu, 14 Aug 2025 16:26:29 -0700 Subject: [PATCH 04/10] fix typo --- hello/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hello/views.py b/hello/views.py index 5eefd1547..78891b0ec 100644 --- a/hello/views.py +++ b/hello/views.py @@ -10,7 +10,7 @@ def index(request): def aocustom(request): # counts - print("count@user.clicks=1") + print("count#user.clicks=1") print("count#user.clicks.tagged=1 tag#user_id=2 tag#user_geo=earth") # measures print("measure#database.query=200ms") From fd96fe199e3b630ebab1724f8aaf94f2f74eb35a Mon Sep 17 00:00:00 2001 From: Lin Lin Date: Mon, 15 Sep 2025 18:06:17 -0700 Subject: [PATCH 05/10] added source tag (librato) --- hello/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hello/views.py b/hello/views.py index 78891b0ec..5a836a735 100644 --- a/hello/views.py +++ b/hello/views.py @@ -12,12 +12,15 @@ def aocustom(request): # counts print("count#user.clicks=1") print("count#user.clicks.tagged=1 tag#user_id=2 tag#user_geo=earth") + print("source=us-west count#user.clicks.sourced=1") # measures print("measure#database.query=200ms") print("measure#database.query.tagged=200ms tag#db_name=foo tag#db_type=postgres") + print("source=us-east measure#database.query.sourced=200ms") # samples print("sample#database.size=40.9MB") print("sample#database.size.tagged=40.9MB tag#db_name=foo tag#db_type=postgres") + print("source=another-source sample#database.size.sourced=40.9MB") return render(request, "index.html") From c8e46fe571c207aa895324092e1d2d4e0a9e4ff7 Mon Sep 17 00:00:00 2001 From: Lin Lin Date: Thu, 25 Sep 2025 14:24:27 -0700 Subject: [PATCH 06/10] disable postgres migration --- Procfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Procfile b/Procfile index d8c93c06f..d44e3e19c 100644 --- a/Procfile +++ b/Procfile @@ -4,4 +4,4 @@ web: gunicorn --config gunicorn.conf.py gettingstarted.wsgi # migrations are run as part of app deployment, using Heroku's Release Phase feature: # https://docs.djangoproject.com/en/5.2/topics/migrations/ # https://devcenter.heroku.com/articles/release-phase -release: ./manage.py migrate --no-input +# release: ./manage.py migrate --no-input From eedcd0d8459b51f9807f83fe46f287f62541bc21 Mon Sep 17 00:00:00 2001 From: Lin Lin Date: Thu, 2 Oct 2025 14:05:18 -0700 Subject: [PATCH 07/10] added sleep route --- gettingstarted/urls.py | 3 ++- hello/views.py | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/gettingstarted/urls.py b/gettingstarted/urls.py index 95e5f7d01..2818d201c 100644 --- a/gettingstarted/urls.py +++ b/gettingstarted/urls.py @@ -23,7 +23,8 @@ urlpatterns = [ path("", hello.views.index, name="index"), path("db/", hello.views.db, name="db"), - path("ao-custom/", hello.views.aocustom, name="aocustom"), + path("ao-custom/", hello.views.ao_custom, name="ao_custom"), + path("random-sleep/", hello.views.random_sleep, name="random_sleep"), # Uncomment this and the entry in `INSTALLED_APPS` if you wish to use the Django admin feature: # https://docs.djangoproject.com/en/5.2/ref/contrib/admin/ # path("admin/", admin.site.urls), diff --git a/hello/views.py b/hello/views.py index 5a836a735..33629ff20 100644 --- a/hello/views.py +++ b/hello/views.py @@ -1,3 +1,6 @@ +import random +import time + from django.shortcuts import render from .models import Greeting @@ -8,7 +11,7 @@ def index(request): return render(request, "index.html") -def aocustom(request): +def ao_custom(request): # counts print("count#user.clicks=1") print("count#user.clicks.tagged=1 tag#user_id=2 tag#user_geo=earth") @@ -41,3 +44,13 @@ def db(request): greetings = Greeting.objects.all() return render(request, "db.html", {"greetings": greetings}) + +def random_sleep(request): + # sleep for query parameter `sleepMs` milliseconds, or a random time between 0 and 3s if not specified + try: + sleep_s = int(request.GET["sleepMs"]) / 1000 + except (NameError, ValueError): + sleep_s = random.uniform(0, 3.0) + print(f"sleeping {sleep_s}s") + time.sleep(sleep_s) + return render(request, "index.html") From c6b5def736a56fcf616445af71a110792e7a8156 Mon Sep 17 00:00:00 2001 From: Lin Lin Date: Thu, 2 Oct 2025 14:15:48 -0700 Subject: [PATCH 08/10] added status and resource routes --- gettingstarted/urls.py | 4 +++- hello/views.py | 54 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/gettingstarted/urls.py b/gettingstarted/urls.py index 2818d201c..68b06c165 100644 --- a/gettingstarted/urls.py +++ b/gettingstarted/urls.py @@ -24,7 +24,9 @@ path("", hello.views.index, name="index"), path("db/", hello.views.db, name="db"), path("ao-custom/", hello.views.ao_custom, name="ao_custom"), - path("random-sleep/", hello.views.random_sleep, name="random_sleep"), + path("sleep/", hello.views.sleep, name="sleep"), + path("status/", hello.views.status, name="status"), + path("resource/", hello.views.resource, name="resource"), # Uncomment this and the entry in `INSTALLED_APPS` if you wish to use the Django admin feature: # https://docs.djangoproject.com/en/5.2/ref/contrib/admin/ # path("admin/", admin.site.urls), diff --git a/hello/views.py b/hello/views.py index 33629ff20..400434c5b 100644 --- a/hello/views.py +++ b/hello/views.py @@ -1,6 +1,8 @@ import random +import threading import time +from django.http import HttpResponse from django.shortcuts import render from .models import Greeting @@ -45,12 +47,54 @@ def db(request): return render(request, "db.html", {"greetings": greetings}) -def random_sleep(request): - # sleep for query parameter `sleepMs` milliseconds, or a random time between 0 and 3s if not specified +def sleep(request): + # sleep for query parameter `ms` milliseconds, or random duration up to 3s if not specified. try: - sleep_s = int(request.GET["sleepMs"]) / 1000 - except (NameError, ValueError): + sleep_s = int(request.GET["ms"]) / 1000 + except (KeyError, ValueError): sleep_s = random.uniform(0, 3.0) print(f"sleeping {sleep_s}s") time.sleep(sleep_s) - return render(request, "index.html") + return HttpResponse(f"slept {sleep_s} seconds", content_type="text/plain") + +def status(request): + # return query parameter `code` as status code, or 400 if not specified. + # throw unhandled exception on invalid value. + status_code = int(request.GET.get("code", 400)) + if status_code < 100 or status_code > 599: + raise ValueError("status code invalid range") + return HttpResponse(f"status {status_code}", content_type="text/plain", status=status_code) + +def resource(request): + # simulate memory allocation and cpu load. + # get size in KB and thread count from query parameters `kb` and `threads`. + try: + size_kb = int(request.GET["kb"]) * 1024 + except (KeyError, ValueError): + size_kb = 10 * 1024 + + try: + threads_count = int(request.GET["threads"]) + except (KeyError, ValueError): + threads_count = 16 + + def memory_and_cpu_load(tid, memory_size_kb): + data = bytearray(memory_size_kb * 1024) + end_time = time.time() + 5 # run for 5 seconds + iterations = 0 + while time.time() < end_time: + _ = sum(i ** 2 for i in range(1000)) + iterations += 1 + info[tid] = iterations + del data + + info = {} + threads = [] + for i in range(threads_count): + thread = threading.Thread(target=memory_and_cpu_load, args=(i, size_kb,)) + threads.append(thread) + thread.start() + for thread in threads: + thread.join() + + return HttpResponse(str(info), content_type="text/plain") \ No newline at end of file From 0c3503699016e07f29629a407c882ec68853bae5 Mon Sep 17 00:00:00 2001 From: Lin Lin Date: Fri, 3 Oct 2025 16:34:48 -0700 Subject: [PATCH 09/10] reenable migration --- Procfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Procfile b/Procfile index d44e3e19c..d8c93c06f 100644 --- a/Procfile +++ b/Procfile @@ -4,4 +4,4 @@ web: gunicorn --config gunicorn.conf.py gettingstarted.wsgi # migrations are run as part of app deployment, using Heroku's Release Phase feature: # https://docs.djangoproject.com/en/5.2/topics/migrations/ # https://devcenter.heroku.com/articles/release-phase -# release: ./manage.py migrate --no-input +release: ./manage.py migrate --no-input From 38bcfbe3b1d77f1d60e1eeec89382a6e50be47d9 Mon Sep 17 00:00:00 2001 From: Lin Lin Date: Thu, 30 Oct 2025 14:37:16 -0700 Subject: [PATCH 10/10] added logs endpoint --- gettingstarted/urls.py | 1 + hello/views.py | 28 +++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/gettingstarted/urls.py b/gettingstarted/urls.py index 68b06c165..e6a322c4a 100644 --- a/gettingstarted/urls.py +++ b/gettingstarted/urls.py @@ -27,6 +27,7 @@ path("sleep/", hello.views.sleep, name="sleep"), path("status/", hello.views.status, name="status"), path("resource/", hello.views.resource, name="resource"), + path("logs/", hello.views.logs, name="logs"), # Uncomment this and the entry in `INSTALLED_APPS` if you wish to use the Django admin feature: # https://docs.djangoproject.com/en/5.2/ref/contrib/admin/ # path("admin/", admin.site.urls), diff --git a/hello/views.py b/hello/views.py index 400434c5b..9500b8e1c 100644 --- a/hello/views.py +++ b/hello/views.py @@ -97,4 +97,30 @@ def memory_and_cpu_load(tid, memory_size_kb): for thread in threads: thread.join() - return HttpResponse(str(info), content_type="text/plain") \ No newline at end of file + return HttpResponse(str(info), content_type="text/plain") + +def logs(request): + # log a burst of messages based on query parameters `count` and `threads`, + # or 1024 messages each from 16 threads if not specified. + try: + msg_count = int(request.GET["count"]) + except (KeyError, ValueError): + msg_count = 1024 + try: + threads_count = int(request.GET["threads"]) + except (KeyError, ValueError): + threads_count = 16 + + def log_burst(tid, count): + for i in range(count): + print(f"log burst tid: {tid} message: {i}") + + threads = [] + for i in range(threads_count): + thread = threading.Thread(target=log_burst, args=(i, msg_count,)) + threads.append(thread) + thread.start() + for thread in threads: + thread.join() + + return HttpResponse(f"{threads_count} threads each logged {msg_count} messages", content_type="text/plain") \ No newline at end of file