locustfile.py

Summary

Maintainability
F
4 days
Test Coverage
# -*- coding: utf-8 -*-
"""Load testing for the API and web app. Run from the root directory using the
`locust --host=https://api-stage.open.fec.gov/v1/` command, then open localhost:8089 to run tests.
"""

import os
import random
import resource

import locust


# Avoid "Too many open files" error
resource.setrlimit(resource.RLIMIT_NOFILE, (9999, 999999))

API_KEY = os.environ['FEC_API_KEY']

try:
    AUTH = (os.environ['FEC_USERNAME'], os.environ['FEC_PASSWORD'])
except KeyError:
    AUTH = None

CYCLES = range(1980, 2018, 2)
CANDIDATES = [
    'bush'
    'cheney',
    'gore',
    'lieberman',
    'kerry',
    'edwards',
    'obama',
    'biden',
    'mccain',
    'palin',
    'clinton',
    'sanders'
    'omalley',
    'rubio',
    'graham',
    'kasich',
]
TERMS = [
    'embezzle',
    'email',
    'department',
    'contribution',
    'commission'
]

# TODO: Add more small
small_records_sched_a = [
    {'contributor_name': "Teachout Zephyr"},
    {'contributor_state': 'GU'},
]

medium_records_sched_a = [
    # this one gave us issues before because it was mid-sized about 21,000
    {'committee_id': 'C00496067'},
    # these seemed mid sized on a given two year period
    {'contributor_city': 'Fresno'},
    {'contirbutor_city': 'Sedona'},
    {'contributor_occupation': 'government'},
]

large_records_sched_a = [
    {'committee_id': 'C00401224'},
    {'committee_id': ['C00401224', 'C00003418', 'C00010603', 'C00027466', 'C00005561', 'C00484642']},
    {'contributor_state': 'NY'},
    {'contributor_state': 'TX'},
    # some common last names in the US
    {'contributor_name': 'Smith'},
    {'contributor_name': 'Johnson'},
    # seeing this problem in production
    {'data_type': 'processed', 'sort_hide_null': 'true', 'committee_id': 'C00401224',
        'two_year_transaction_period': 2016, 'min_date': '07/01/2016', 'max_date': '07/31/2016',
        'sort': '-contribution_receipt_date', 'per_page': 30
    }
]


# took the worst performing queries from the log https://logs.fr.cloud.gov/goto/be56820fc05ef241c62c5641f16dcd3e
poor_performance_a = [
    {'sort_nulls_large': True, 'contributor_name': 'paul+johnson', 'two_year_transaction_period': 2014, 'min_date': '01%2F01%2F2013', 'max_date': '12%2F31%2F2014','contributor_state': 'IN', 'sort': '-contribution_receipt_date', 'per_page': 30, 'is_individual': True},
    {'sort_nulls_large': True, 'contributor_name': 'Robert+F+Pence', 'two_year_transaction_period': 2014, 'min_date': '01%2F01%2F2013', 'max_date': '12%2F31%2F2014', 'sort': '-contribution_receipt_date', 'per_page': 100, 'is_individual': True},
    {'sort_nulls_large': True, 'contributor_name': 'tom+lewis', 'contributor_name': 'thomas+lewis', 'two_year_transaction_period': 2016, 'min_date': '01%2F01%2F2015', 'max_date': '12%2F31%2F2016', 'sort': '-contribution_receipt_amount', 'per_page': 30, 'is_individual': True},
    {'sort_nulls_large': True, 'contributor_name': 'Becher%2C+S', 'two_year_transaction_period': 2016, 'min_date': '01%2F01%2F2015', 'max_date': '12%2F31%2F2016&', 'contributor_state': 'FL', 'sort': '-contribution_receipt_date', 'per_page': 30, 'is_individual': True},
    {'sort_nulls_large': True, 'contributor_name': 'Becher', 'two_year_transaction_period': 2016, 'min_date': '01%2F01%2F2015', 'max_date': '12%2F31%2F2016', 'contributor_state':'FL', 'sort':'-contribution_receipt_date', 'per_page': 30, 'is_individual': True},
]

poor_performance_b = [
    {'sort_nulls_large': True, 'two_year_transaction_period': 2016, 'per_page': 100, 'sort':'disbursement_date', 'last_disbursement_date': '2016-03-03', 'last_index': 4070720161305573871},
    {'sort_nulls_large': True, 'two_year_transaction_period': 2016, 'per_page': 100, 'sort':'disbursement_date', 'last_disbursement_date': '2016-03-03', 'last_index': 4062420161300286190},
    {'sort_nulls_large': True, 'two_year_transaction_period': 2016, 'per_page': 100, 'sort':'disbursement_date', 'last_disbursement_date': '2016-03-03', 'last_index': 4062120161299938749},
    {'sort_nulls_large': True, 'two_year_transaction_period': 2016, 'per_page': 100, 'sort':'disbursement_date', 'last_disbursement_date': '2016-03-03', 'last_index': 4061720161299122923},
    {'sort_nulls_large': True, 'two_year_transaction_period': 2016, 'per_page': 100, 'sort':'disbursement_date', 'last_disbursement_date': '2016-03-03', 'last_index': 4061720161299122723},
]


class Tasks(locust.TaskSet):

    def on_start(self):
        self.candidates = self.fetch_ids('candidates', 'candidate_id')
        self.committees = self.fetch_ids('committees', 'committee_id')

    def fetch_ids(self, endpoint, key):
        params = {
            'api_key': API_KEY,
        }
        resp = self.client.get(endpoint, name='preload_ids', params=params)
        return [result[key] for result in resp.json()['results']]

    @locust.task
    def load_home(self):
        params = {
            'api_key': API_KEY,
        }
        self.client.get('', name='home', params=params)

    @locust.task
    def load_candidates_search(self, term=None):
        term = term or random.choice(CANDIDATES)
        params = {
            'api_key': API_KEY,
            'sort': '-receipts',
            'q': term,
        }
        self.client.get('candidates/search', name='candidate_search', params=params)

    @locust.task
    def load_committees_search(self, term=None):
        term = term or random.choice(CANDIDATES)
        params = {
            'api_key': API_KEY,
            'sort': '-receipts',
            'q': term,
        }
        self.client.get('committees', name='committee_search', params=params)

    @locust.task
    def load_candidates_table(self):
        params = {
            'cycle': [random.choice(CYCLES) for _ in range(3)],
            'api_key': API_KEY,
        }
        self.client.get('candidates', name='candidates_table', params=params)

    @locust.task
    def load_committees_table(self):
        params = {
            'cycle': [random.choice(CYCLES) for _ in range(3)],
            'api_key': API_KEY,
        }
        self.client.get('committees', name='committees_table', params=params)

    @locust.task
    def load_candidate_detail(self, candidate_id=None):
        params = {
            'api_key': API_KEY,
        }
        candidate_id = candidate_id or random.choice(self.candidates)
        self.client.get(os.path.join('candidate', candidate_id), name='candidate_detail', params=params)

    @locust.task
    def load_committee_detail(self, committee_id=None):
        params = {
            'api_key': API_KEY,
        }
        committee_id = committee_id or random.choice(self.committees)
        self.client.get(os.path.join('committee', committee_id), name='committee_detail', params=params)

    @locust.task
    def load_candidate_totals(self, candidate_id=None):
        params = {
            'api_key': API_KEY,
        }
        candidate_id = candidate_id or random.choice(self.candidates)
        self.client.get(os.path.join('candidate', candidate_id, 'totals'), name='candidate_totals', params=params)

    @locust.task
    def load_committee_totals(self, committee_id=None):
        params = {
            'api_key': API_KEY,
        }
        committee_id = committee_id or random.choice(self.committees)
        self.client.get(os.path.join('committee', committee_id, 'totals'), name='committee_totals', params=params)

    @locust.task
    def load_legal_documents_search(self, term=None):
        term = term or random.choice(CANDIDATES)
        params = {
            'q': term,
            'api_key': API_KEY,
        }
        self.client.get('legal/search', name='legal_search', params=params)

    @locust.task
    def get_document(self, term=None):
        params = {
            'api_key': API_KEY,
        }
        self.client.get('legal/docs/murs/7074', name='legal_get', params=params)

    @locust.task
    def load_schedule_a_small(self):
        params = random.choice(small_records_sched_a)
        params['api_key'] = API_KEY
        self.client.get('schedules/schedule_a/', name='schedule_a_small', params=params)

    @locust.task
    def load_schedule_a_medium(self):
        params = random.choice(medium_records_sched_a)
        params['api_key'] = API_KEY
        self.client.get('schedules/schedule_a/', name='schedule_a_medium', params=params)

    @locust.task
    def load_schedule_a_large(self):
        params = random.choice(large_records_sched_a)
        params['api_key'] = API_KEY
        self.client.get('schedules/schedule_a/', name='load_schedule_a_large', params=params)

    @locust.task
    def load_schedule_a_problematic(self):
        params = random.choice(poor_performance_a)
        params['api_key'] = API_KEY
        self.client.get('schedules/schedule_a/', name='load_schedule_a_problematic', params=params)

    @locust.task
    def load_schedule_b_problematic(self):
        params = random.choice(poor_performance_b)
        params['api_key'] = API_KEY
        self.client.get('schedules/schedule_b/', name='load_schedule_b_problematic', params=params)


class Swarm(locust.HttpLocust):
    task_set = Tasks
    min_wait = 5000
    max_wait = 10000