Real-time analytics pipeline for GitHub repositories using Apache Pulsar, Docker Compose, and Python consumers.
- Q1: Top languages by repository count
- Q2: Top repositories by commit count
- Q3: Top languages with unit test folder indicators
- Q4: Top languages that show both unit-test and CI/CD indicators
While this project can be run locally on a single machine, for this assignment, it was deployed as a fully distributed system across 5 isolated OpenStack Virtual Machines to ensure high availability and true decoupled processing.
The infrastructure is divided as follows:
- VM 1 (Master Node): Hosts the Apache Pulsar broker (
pulsar://<Master_IP>:6650). - VM 2 (Ingestion Node): Hosts the
producercontainer, managing API rate limits and publishing to the Pulsar topic over the internal cloud network. - VM 3 (Analytics Node A): Hosts
consumer-language-stats. - VM 4 (Analytics Node B): Hosts
consumer-unit-test-analysis. - VM 5 (Analytics Node C): Hosts
consumer-ci-devops-analysis.
Deployment Strategy: The workload was distributed using isolated Docker Compose configurations pointing to the Master node. The consumers on the worker nodes asynchronously process messages from the Master node's Pulsar topic. Results were aggregated from the worker nodes via scp to generate the final PNG visualizations.
GitHub Search API (day-by-day crawl)
-> main.py --service producer
-> Pulsar topic: persistent://public/default/raw-repositories
-> main.py --service consumer-language-stats
-> main.py --service consumer-unit-test-analysis
-> main.py --service consumer-ci-devops-analysis
-> results/*.json
-> results/visualize.py -> results/*.png
The system runs as a streaming pipeline with one producer and three independent consumers.
- Producer stage
main.py --service producerrunsproducer/github_crawler.py.- It queries GitHub Search by date (
pushed:YYYY-MM-DD) and paginates up to 10 pages per day. - For each repository, it filters required fields and publishes JSON messages to:
persistent://public/default/raw-repositories
- Streaming stage (Apache Pulsar)
- Pulsar acts as the event backbone between ingestion and analytics.
- Producer and consumers are decoupled, so consumers can process in parallel.
- Consumer stage
main.py --service consumer-language-statscomputes Q1 and Q2.main.py --service consumer-unit-test-analysiscomputes Q3.main.py --service consumer-ci-devops-analysiscomputes Q4.- Each consumer has its own subscription on the same raw topic.
- Output stage
- Consumers write rolling JSON outputs every 10 processed messages into
results/. results/visualize.pyreads JSON outputs and generates PNG charts.
- Orchestration stage (Docker Compose)
docker-compose.ymlstarts Pulsar plus all Python services.- Python services share one base config via YAML anchors and only differ by command.
- Services use
restart: unless-stoppedfor better resilience.
- Docker Engine + Docker Compose
- GitHub Personal Access Token
- Optional local Python 3.11+ (only needed if you run
results/visualize.pyoutside Docker)
This project uses the GitHub REST API, so you should create a GitHub Personal Access Token (PAT) before running the producer.
-
Open GitHub and go to:
Settings->Developer settings->Personal access tokens->Tokens (classic) -
Click
Generate new token (classic). -
Give the token a clear name, for example
github-streaming-analytics. -
Select the minimum scope needed for this project:
public_repofor searching and reading public repositories. -
Click
Generate token. -
Copy the token immediately. GitHub will not show it again after you leave the page.
-
Add it to the project.
If you have not created your local env file yet:
cp .env_example .envThen open .env and set:
GITHUB_TOKEN=your_generated_token_hereThe app reads GITHUB_TOKEN automatically when you run docker compose up --build or any main.py --service ... command.
- Clone and enter the project.
git clone <your-repo-url>
cd github-streaming-analytics- Configure environment variables.
cp .env_example .envEdit .env and add your GitHub token plus the settings you want to use:
GITHUB_TOKEN=your_token_here
SEARCH_START_DATE=2023-01-01
SEARCH_END_DATE=2023-01-07
TOP_N=10
LOG_LEVEL=INFO
PULSAR_SERVICE_URL=pulsar://pulsar-broker:6650- Build and start the full stack.
docker compose up --build- Stop when done.
docker compose downUse this when you want to run a specific service outside Docker Compose.
- Install dependencies.
pip3 install -r requirements.txt-
Ensure Apache Pulsar is available and your
.envis configured. -
Run services in separate terminals.
# producer
python3 main.py --service producer
# consumer for Q1 and Q2
python3 main.py --service consumer-language-stats
# consumer for Q3
python3 main.py --service consumer-unit-test-analysis
# consumer for Q4
python3 main.py --service consumer-ci-devops-analysis- Generate plots (optional).
python3 results/visualize.pydocker compose ps
docker compose logs -f producer.envstores runtime settings used by producer and consumers.requirements.txtdefines Python dependencies for local runs and container builds.Dockerfilebuilds a shared Python image used by all app services.docker-compose.ymlorchestrates Pulsar + app services with a shared base service definition.pulsar/topic_config.yamldescribes topic defaults and per-topic retention settings.
Topic retention in pulsar/topic_config.yaml:
raw-repositories: 1440 minutes, 1024 MBlanguage-stats: 1440 minutes, 512 MBunit-test-analysis: 1440 minutes, 512 MBci-devops-analysis: 1440 minutes, 512 MB
| Variable | Purpose | Default |
|---|---|---|
GITHUB_TOKEN |
Auth token for higher GitHub API limits | empty |
SEARCH_START_DATE |
Crawl start date (YYYY-MM-DD) |
2023-01-01 |
SEARCH_END_DATE |
Crawl end date (YYYY-MM-DD) |
2023-01-01 |
TOP_N |
Number of entries kept in results | 10 |
LOG_LEVEL |
Application log verbosity (DEBUG, INFO, WARNING, ERROR) |
INFO |
PULSAR_SERVICE_URL |
Pulsar broker URL | pulsar://pulsar-broker:6650 |
All services use Python logging with a shared format:
%(asctime)s %(levelname)s [%(name)s] %(message)s
By default, logs run at INFO. Change verbosity using LOG_LEVEL in .env.
Examples:
LOG_LEVEL=DEBUGfor detailed troubleshootingLOG_LEVEL=INFOfor normal runsLOG_LEVEL=WARNINGto reduce noise
To inspect logs while running with Docker Compose:
docker compose logs -f producer
docker compose logs -f consumer-language-stats
docker compose logs -f consumer-unit-test-analysis
docker compose logs -f consumer-ci-devops-analysisUse a short date window first so you can validate the full pipeline quickly.
GITHUB_TOKEN=your_token_here
SEARCH_START_DATE=2023-01-01
SEARCH_END_DATE=2023-01-03
TOP_N=10
LOG_LEVEL=INFO
PULSAR_SERVICE_URL=pulsar://pulsar-broker:6650Recommendations:
- Always set
GITHUB_TOKENto reduce GitHub API throttling. - Start with 1-3 days of data, then increase the date range after validating outputs.
- Keep
PULSAR_SERVICE_URLaspulsar://pulsar-broker:6650when running with Docker Compose. - Keep
TOP_N=10initially; increase later if you need wider rankings.
Consumers continuously write:
results/language_stats.json(Q1 and Q2)results/unit_test_analysis.json(Q3)results/ci_devops_analysis.json(Q4)
Generate charts from those JSON files:
python results/visualize.pyExpected chart files:
results/q1_languages.pngresults/q2_commits.pngresults/q3_tdd.pngresults/q4_ci_devops.png
If running visualization locally for the first time:
pip3 install matplotlibThis warning is fixed in the current docker-compose.yml. If you still see it, make sure you are running the latest file version from your current branch.
This is fixed in the current results/visualize.py. You can validate syntax with:
python3 -m py_compile results/visualize.pypulsar: Apache Pulsar standalone brokerproducer: runsmain.py --service producerconsumer-language-stats: runsmain.py --service consumer-language-statsconsumer-unit-test-analysis: runsmain.py --service consumer-unit-test-analysisconsumer-ci-devops-analysis: runsmain.py --service consumer-ci-devops-analysis
All consumers currently subscribe to the same topic:
persistent://public/default/raw-repositories
.
├── Dockerfile
├── docker-compose.yml
├── .env_example
├── main.py
├── producer/
│ ├── github_crawler.py
│ └── rate_limiter.py
├── consumers/
│ ├── language_stats/consumer.py
│ ├── unit_test_analysis/consumer.py
│ └── ci_devops_analysis/consumer.py
├── pulsar/topic_config.yaml
├── results/
│ ├── language_stats.json
│ ├── unit_test_analysis.json
│ ├── ci_devops_analysis.json
│ └── visualize.py
└── report/
- GitHub Search is queried by
pushed:<date>and paginated up to 10 pages/day. - Commit counts and repository content checks trigger extra GitHub API calls and may hit rate limits on large date ranges.
- Results are periodically updated while consumers run (every 10 processed messages).
- GitHub Search API: https://docs.github.com/en/rest/search/search#search-repositories
- GitHub rate limits: https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api
- Apache Pulsar docs: https://pulsar.apache.org/docs/