Skip to main content

Flask: Application Performance Monitoring

Flask: Application Performance Monitoring

Application Performance Monitoring (APM) is critical for identifying bottlenecks, optimizing resource usage, and ensuring a seamless user experience in Flask applications. Flask, a lightweight Python web framework, can be integrated with APM tools to track metrics like request latency, database performance, and error rates. This tutorial explores Flask application performance monitoring, covering setup, tools like New Relic and Prometheus, and best practices for effective performance optimization.


01. Why Monitor Flask Application Performance?

Performance monitoring helps detect slow endpoints, resource leaks, and scalability issues, ensuring applications remain responsive under load. Flask’s minimalistic design requires external tools to collect and analyze metrics, enabling developers to optimize code, database queries, and infrastructure. APM ensures high availability, user satisfaction, and compliance with service-level agreements (SLAs).

Example: Basic Performance Monitoring with Prometheus

from flask import Flask
from prometheus_client import start_http_server, Histogram

app = Flask(__name__)

# Define a histogram for request latency
request_latency = Histogram('flask_request_latency_seconds', 'Request latency', ['endpoint'])

@app.route('/')
@request_latency.labels(endpoint='/').time()
def index():
    return "Hello, Flask!"

if __name__ == '__main__':
    start_http_server(8000)  # Prometheus metrics server
    app.run(debug=True, port=5000)

Output:

* Flask running on http://127.0.0.1:5000
* Prometheus metrics at http://127.0.0.1:8000/metrics
(Metrics include: flask_request_latency_seconds_sum{endpoint="/"} 0.001)

Explanation:

  • prometheus_client - Tracks request latency per endpoint.
  • start_http_server - Exposes metrics for Prometheus scraping.

02. Key Performance Monitoring Techniques

Monitoring Flask performance involves collecting metrics, profiling code, and visualizing data with APM tools. These techniques provide actionable insights for optimization. The table below summarizes key techniques and their applications:

Technique Description Use Case
Request Metrics Track latency, throughput, errors Identify slow endpoints
Database Monitoring Measure query performance Optimize slow queries
Code Profiling Analyze function execution time Pinpoint bottlenecks
APM Tool Integration Use New Relic, Prometheus Comprehensive monitoring
Alerting Notify on performance issues Proactive resolution


2.1 Tracking Request Metrics

Example: Request Latency and Error Rates

from flask import Flask, request
from prometheus_client import Counter, Histogram, make_wsgi_app
from werkzeug.middleware.dispatcher import DispatcherMiddleware
import time

app = Flask(__name__)

# Metrics
request_count = Counter('flask_requests_total', 'Total requests', ['method', 'endpoint', 'status'])
request_latency = Histogram('flask_request_latency_seconds', 'Request latency', ['endpoint'])

@app.before_request
def before_request():
    request.start_time = time.time()

@app.after_request
def after_request(response):
    endpoint = request.endpoint or 'unknown'
    status = str(response.status_code)
    request_count.labels(method=request.method, endpoint=endpoint, status=status).inc()
    latency = time.time() - request.start_time
    request_latency.labels(endpoint=endpoint).observe(latency)
    return response

app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {'/metrics': make_wsgi_app()})

@app.route('/')
def index():
    return "Hello, Flask!"

@app.route('/error')
def error():
    return "Error", 500

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Output (http://127.0.0.1:5000/metrics):

flask_requests_total{method="GET",endpoint="index",status="200"} 1.0
flask_request_latency_seconds_sum{endpoint="index"} 0.002
flask_requests_total{method="GET",endpoint="error",status="500"} 1.0

Explanation:

  • Middleware tracks request counts, latency, and status codes automatically.
  • Helps identify slow routes or high error rates.

2.2 Database Performance Monitoring

Example: SQLAlchemy Query Metrics

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from prometheus_client import Histogram, make_wsgi_app
from werkzeug.middleware.dispatcher import DispatcherMiddleware

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///example.db'
db = SQLAlchemy(app)

# Metrics
query_latency = Histogram('flask_query_latency_seconds', 'Database query latency', ['query_type'])

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))

app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {'/metrics': make_wsgi_app()})

@app.route('/users')
@query_latency.labels(query_type='select').time()
def get_users():
    users = User.query.all()
    return f"Users: {[user.name for user in users]}"

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True, port=5000)

Output (http://127.0.0.1:5000/metrics):

flask_query_latency_seconds_sum{query_type="select"} 0.005

Explanation:

  • Tracks database query latency to identify slow queries.
  • Can be extended to monitor specific query types or tables.

2.3 Code Profiling with Flask-Profiler

Example: Profiling with Flask-Profiler

from flask import Flask
from flask_profiler import Profiler

app = Flask(__name__)
app.config["flask_profiler"] = {
    "enabled": True,
    "storage": {"engine": "sqlite"},
    "basicAuth": {"enabled": False}
}
profiler = Profiler(app)

@app.route('/slow')
def slow():
    import time
    time.sleep(1)  # Simulate slow operation
    return "Slow endpoint"

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Output (http://127.0.0.1:5000/flask-profiler/):

Endpoint: /slow, Method: GET, Duration: ~1000ms

Explanation:

  • flask_profiler - Profiles endpoint execution times and stores results.
  • Identifies slow functions or operations for optimization.

2.4 APM Integration with New Relic

Example: New Relic Monitoring

from flask import Flask
import newrelic.agent

# Initialize New Relic
newrelic.agent.initialize('newrelic.ini')

app = Flask(__name__)

@app.route('/')
def index():
    return "Monitored by New Relic"

@app.route('/slow')
def slow():
    import time
    time.sleep(1)
    return "Slow endpoint"

if __name__ == '__main__':
    app.run(debug=True, port=5000)

New Relic Config (newrelic.ini):

[newrelic]
license_key = your_license_key
app_name = FlaskApp
monitor_mode = true

Output (New Relic Dashboard):

Transaction: /slow, Response Time: ~1000ms

Explanation:

  • New Relic tracks request performance, database queries, and errors.
  • Provides detailed transaction traces for bottleneck analysis.

2.5 Alerting on Performance Issues

Example: Prometheus Alerting

# prometheus.yml
rule_files:
  - 'alerts.yml'
scrape_configs:
  - job_name: 'flask_app'
    static_configs:
      - targets: ['localhost:5000']
# alerts.yml
groups:
- name: flask_alerts
  rules:
  - alert: HighLatency
    expr: histogram_quantile(0.95, sum(rate(flask_request_latency_seconds_bucket[5m])) by (endpoint)) > 0.5
    for: 2m
    labels:
      severity: warning
    annotations:
      summary: "High request latency"
      description: "95th percentile latency for {{ $labels.endpoint }} is {{ $value }} seconds"

Output (Prometheus Alerts):

Alert triggered if 95th percentile latency exceeds 0.5s for 2 minutes

Explanation:

  • Alerts on high latency to prompt optimization.
  • Integrates with notification systems like Alertmanager.

2.6 Incorrect Monitoring Setup

Example: Missing Metrics Exposure

from flask import Flask
from prometheus_client import Histogram

app = Flask(__name__)

request_latency = Histogram('flask_request_latency_seconds', 'Request latency')

@app.route('/')
def index():
    return "Hello, Flask!"

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Output:

* Running on http://127.0.0.1:5000
(No metrics exposed; Prometheus cannot scrape)

Explanation:

  • Missing make_wsgi_app or start_http_server prevents metric collection.
  • Solution: Add a metrics endpoint or server.

03. Effective Usage

3.1 Recommended Practices

  • Use middleware for automated metric collection and APM tools for detailed insights.

Example: Comprehensive APM Setup

from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from prometheus_client import Counter, Histogram, make_wsgi_app
from werkzeug.middleware.dispatcher import DispatcherMiddleware
import time
import newrelic.agent

newrelic.agent.initialize('newrelic.ini')

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///example.db'
db = SQLAlchemy(app)

# Metrics
request_count = Counter('flask_requests_total', 'Total requests', ['method', 'endpoint', 'status'])
request_latency = Histogram('flask_request_latency_seconds', 'Request latency', ['endpoint'])
query_latency = Histogram('flask_query_latency_seconds', 'Query latency', ['query_type'])

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50))

@app.before_request
def before_request():
    request.start_time = time.time()

@app.after_request
def after_request(response):
    endpoint = request.endpoint or 'unknown'
    status = str(response.status_code)
    request_count.labels(method=request.method, endpoint=endpoint, status=status).inc()
    latency = time.time() - request.start_time
    request_latency.labels(endpoint=endpoint).observe(latency)
    return response

app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {'/metrics': make_wsgi_app()})

@app.route('/')
def index():
    return "Monitored Flask App"

@app.route('/users')
@query_latency.labels(query_type='select').time()
def get_users():
    users = User.query.all()
    return f"Users: {[user.name for user in users]}"

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True, port=5000)

Output (http://127.0.0.1:5000/metrics):

flask_requests_total{method="GET",endpoint="index",status="200"} 1.0
flask_request_latency_seconds_sum{endpoint="index"} 0.001
flask_query_latency_seconds_sum{query_type="select"} 0.005

New Relic Dashboard:

Transaction: /users, Response Time: ~5ms, Database: ~4ms
  • Combines Prometheus for custom metrics and New Relic for detailed APM.
  • Tracks request and database performance.
  • Ready for Grafana visualization and alerting.

3.2 Practices to Avoid

  • Avoid collecting excessive metrics, which can degrade performance.

Example: Overloaded Metrics

from flask import Flask
from prometheus_client import Histogram, make_wsgi_app
from werkzeug.middleware.dispatcher import DispatcherMiddleware

app = Flask(__name__)

# Excessive metrics
request_latency = Histogram('flask_request_latency_seconds', 'Request latency', ['endpoint', 'method', 'status', 'user', 'ip'])

@app.before_request
def before_request():
    request.start_time = time.time()

@app.after_request
def after_request(response):
    endpoint = request.endpoint or 'unknown'
    request_latency.labels(
        endpoint=endpoint, method=request.method, status=response.status_code,
        user='anonymous', ip=request.remote_addr
    ).observe(time.time() - request.start_time)
    return response

app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {'/metrics': make_wsgi_app()})

@app.route('/')
def index():
    return "Overloaded metrics"

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Output:

* Running on http://127.0.0.1:5000
(Metrics endpoint generates excessive data, slowing app)
  • Too many metric labels increase memory and scraping overhead.
  • Solution: Limit labels to essential dimensions (e.g., endpoint, status).

04. Common Use Cases

4.1 Optimizing API Endpoints

Monitor and optimize API performance for low latency.

Example: API Performance Monitoring

from flask import Flask, jsonify
from prometheus_client import Histogram, make_wsgi_app
from werkzeug.middleware.dispatcher import DispatcherMiddleware
import time

app = Flask(__name__)

request_latency = Histogram('api_request_latency_seconds', 'API latency', ['endpoint'])

app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {'/metrics': make_wsgi_app()})

@app.route('/api/data')
@request_latency.labels(endpoint='/api/data').time()
def data():
    time.sleep(0.1)  # Simulate processing
    return jsonify({'data': 'secure'})

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Output (http://127.0.0.1:5000/metrics):

api_request_latency_seconds_sum{endpoint="/api/data"} 0.101

Explanation:

  • Tracks API latency to identify slow responses.
  • Guides optimization efforts like caching or query tuning.

4.2 Database Query Optimization

Monitor database performance to optimize slow queries.

Example: Database Query Monitoring

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from prometheus_client import Histogram, make_wsgi_app
from werkzeug.middleware.dispatcher import DispatcherMiddleware

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///example.db'
db = SQLAlchemy(app)

query_latency = Histogram('flask_query_latency_seconds', 'Query latency', ['query_type'])

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.String(200))

app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {'/metrics': make_wsgi_app()})

@app.route('/posts')
@query_latency.labels(query_type='select').time()
def get_posts():
    posts = Post.query.all()
    return f"Posts: {[post.content for post in posts]}"

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True, port=5000)

Output (http://127.0.0.1:5000/metrics):

flask_query_latency_seconds_sum{query_type="select"} 0.007

Explanation:

  • Measures query latency to identify inefficient database operations.
  • Supports optimization like indexing or query rewriting.

Conclusion

Application Performance Monitoring in Flask ensures optimal performance and reliability. Key takeaways:

  • Use Prometheus for custom metrics like request and query latency.
  • Integrate New Relic or Flask-Profiler for detailed APM and profiling.
  • Automate metric collection with middleware and set up alerting for issues.
  • Avoid excessive metrics to maintain performance.

With these practices, you can monitor and optimize Flask applications for high performance and user satisfaction!

Comments