Source code for swh.counters.api.server
# Copyright (C) 2021 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
import logging
import os
from typing import Any, Dict, Optional
from flask import abort, jsonify
from swh.core import config
from swh.core.api import RPCServerApp
from swh.counters import get_counters, get_history
from swh.counters.interface import CountersInterface, HistoryInterface
logger = logging.getLogger(__name__)
app: Optional[RPCServerApp] = None
[docs]
def make_app(config: Dict[str, Any]) -> RPCServerApp:
"""Initialize the remote api application."""
app = RPCServerApp(
__name__,
backend_class=CountersInterface,
backend_factory=lambda: get_counters(**config["counters"]),
)
handler = logging.StreamHandler()
app.logger.addHandler(handler)
app.config["counters"] = get_counters(**config["counters"])
if "history" in config:
app.add_backend_class(
backend_class=HistoryInterface,
backend_factory=lambda: get_history(**config["history"]),
)
app.config["history"] = get_history(**config["history"])
app.add_url_rule(
"/counters_history/<filename>", "history", get_history_file_content
)
app.add_url_rule("/", "index", index)
app.add_url_rule("/metrics", "metrics", get_metrics)
return app
[docs]
def index():
return "SWH Counters API server"
[docs]
def load_and_check_config(config_file: str) -> Dict[str, Any]:
"""Check the minimal configuration is set to run the api or raise an
error explanation.
Args:
config_file: Path to the configuration file to load
type: configuration type. For 'local' type, more
checks are done.
Raises:
Error if the setup is not as expected
Returns:
configuration as a dict
"""
if not config_file:
raise EnvironmentError("Configuration file must be defined")
if not os.path.exists(config_file):
raise FileNotFoundError("Configuration file %s does not exist" % (config_file,))
cfg = config.read(config_file)
if "counters" not in cfg:
raise KeyError("Missing 'counters' configuration")
return cfg
[docs]
def make_app_from_configfile():
"""Run the WSGI app from the webserver, loading the configuration from
a configuration file.
SWH_CONFIG_FILENAME environment variable defines the
configuration path to load.
"""
global app
if app is None:
config_file = os.environ.get("SWH_CONFIG_FILENAME")
api_cfg = load_and_check_config(config_file)
app = make_app(api_cfg)
return app
[docs]
def get_metrics():
"""expose the counters values in a prometheus format
detailed format:
# HELP swh_archive_object_total Software Heritage Archive object counters
# TYPE swh_archive_object_total gauge
swh_archive_object_total{col="value",object_type="<collection>"} <value>
...
"""
response = [
"# HELP swh_archive_object_total Software Heritage Archive object counters",
"# TYPE swh_archive_object_total gauge",
]
counters = app.config["counters"]
for collection in counters.get_counters():
collection_name = collection.decode("utf-8")
value = counters.get_count(collection)
line = 'swh_archive_object_total{col="value", object_type="%s"} %s' % (
collection_name,
value,
)
response.append(line)
response.append("")
return "\n".join(response)
[docs]
def get_history_file_content(filename: str):
assert app is not None
try:
content = app.config["history"].get_history(filename)
except FileNotFoundError:
abort(404)
return jsonify(content)