Added update builds functionality
added firts grafana dashboard
This commit is contained in:
		
							parent
							
								
									913a998265
								
							
						
					
					
						commit
						7543878abf
					
				| @ -19,9 +19,10 @@ class APIclient(): | |||||||
|     client for working with ALBS API |     client for working with ALBS API | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     def __init__(self, api_root: str, jwt: str): |     def __init__(self, api_root: str, jwt: str, timeout: int): | ||||||
|         self.api_root = api_root |         self.api_root = api_root | ||||||
|         self.jwt = jwt |         self.jwt = jwt | ||||||
|  |         self.timeout = timeout | ||||||
| 
 | 
 | ||||||
|     def get_builds(self, page_num: int = 1) -> List[Build]: |     def get_builds(self, page_num: int = 1) -> List[Build]: | ||||||
|         ep = '/api/v1/builds' |         ep = '/api/v1/builds' | ||||||
| @ -29,7 +30,8 @@ class APIclient(): | |||||||
|         params = {'pageNumber': page_num} |         params = {'pageNumber': page_num} | ||||||
|         headers = {'accept': 'appilication/json'} |         headers = {'accept': 'appilication/json'} | ||||||
| 
 | 
 | ||||||
|         response = requests.get(url, params=params, headers=headers) |         response = requests.get( | ||||||
|  |             url, params=params, headers=headers, timeout=self.timeout) | ||||||
|         response.raise_for_status() |         response.raise_for_status() | ||||||
| 
 | 
 | ||||||
|         result = [] |         result = [] | ||||||
| @ -41,6 +43,14 @@ class APIclient(): | |||||||
|                               b, err, exc_info=True) |                               b, err, exc_info=True) | ||||||
|         return result |         return result | ||||||
| 
 | 
 | ||||||
|  |     def get_build(self, build_id: int) -> Build: | ||||||
|  |         ep = f'/api/v1/builds/{build_id}' | ||||||
|  |         url = urljoin(self.api_root, ep) | ||||||
|  |         headers = {'accept': 'application/json'} | ||||||
|  |         response = requests.get(url, headers=headers, timeout=self.timeout) | ||||||
|  |         response.raise_for_status() | ||||||
|  |         return self._parse_build(response.json()) | ||||||
|  | 
 | ||||||
|     def _parse_build_tasks(self, tasks_json: Dict, build_id: int) -> List[BuildTask]: |     def _parse_build_tasks(self, tasks_json: Dict, build_id: int) -> List[BuildTask]: | ||||||
|         result = [] |         result = [] | ||||||
|         for task in tasks_json: |         for task in tasks_json: | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| from datetime import datetime | from datetime import datetime | ||||||
| from typing import Union | from typing import Union, Dict | ||||||
| 
 | 
 | ||||||
| import psycopg2 | import psycopg2 | ||||||
| 
 | 
 | ||||||
| @ -10,46 +10,43 @@ from .models.db_config import DbConfig | |||||||
| 
 | 
 | ||||||
| class DB(): | class DB(): | ||||||
|     def __init__(self, config: DbConfig): |     def __init__(self, config: DbConfig): | ||||||
|         self.conf = config |         self.__conn = psycopg2.connect(database=config.name, | ||||||
|  |                                        host=config.host, | ||||||
|  |                                        user=config.username, | ||||||
|  |                                        password=config.password, | ||||||
|  |                                        port=config.port) | ||||||
| 
 | 
 | ||||||
|     def _get_conn(self): |     def close_conn(self): | ||||||
|         conn = psycopg2.connect(database=self.conf.name, |         self.__conn.close() | ||||||
|                                 host=self.conf.host, |  | ||||||
|                                 user=self.conf.username, |  | ||||||
|                                 password=self.conf.password, |  | ||||||
|                                 port=self.conf.port) |  | ||||||
|         return conn |  | ||||||
| 
 | 
 | ||||||
|     def insert_update_build(self, build: BuildDB): |     def __del__(self): | ||||||
|         sql = f''' |         self.close_conn() | ||||||
|  | 
 | ||||||
|  |     def insert_build(self, build: BuildDB): | ||||||
|  |         sql = ''' | ||||||
|                 INSERT INTO builds(id, url, created_at, finished_at) |                 INSERT INTO builds(id, url, created_at, finished_at) | ||||||
|                 VALUES (%s, %s, %s, %s) |                 VALUES (%s, %s, %s, %s); | ||||||
|                 ON CONFLICT (id) DO UPDATE SET |  | ||||||
|                 (url, created_at, finished_at) = (EXCLUDED.url, EXCLUDED.created_at, EXCLUDED.finished_at); |  | ||||||
|               ''' |               ''' | ||||||
|         with self._get_conn() as conn: | 
 | ||||||
|             cur = conn.cursor() |         cur = self.__conn.cursor() | ||||||
|         cur.execute(sql, (build.id, build.url, |         cur.execute(sql, (build.id, build.url, | ||||||
|                           build.created_at, build.finished_at)) |                           build.created_at, build.finished_at)) | ||||||
|             conn.commit() |         self.__conn.commit() | ||||||
| 
 | 
 | ||||||
|     def insert_update_buildtask(self, build_task: BuildTaskDB): |     def insert_buildtask(self, build_task: BuildTaskDB): | ||||||
|         sql = f""" |         sql = ''' | ||||||
|                 INSERT INTO build_tasks(id, build_id, arch_id, started_at, finished_at, status_id) |                 INSERT INTO build_tasks(id, build_id, arch_id, started_at, finished_at, status_id) | ||||||
|                 VALUES (%s, %s, %s, %s, %s, %s) |                 VALUES (%s, %s, %s, %s, %s, %s); | ||||||
|                 ON CONFLICT (id) DO UPDATE SET |               ''' | ||||||
|                 (id, build_id, arch_id, started_at, finished_at, status_id) = (EXCLUDED.ID, EXCLUDED.build_id, EXCLUDED.arch_id, EXCLUDED.started_at, EXCLUDED.finished_at, EXCLUDED.status_id) | 
 | ||||||
|               """ |         cur = self.__conn.cursor() | ||||||
|         with self._get_conn() as conn: |  | ||||||
|             cur = conn.cursor() |  | ||||||
|         cur.execute(sql, (build_task.id, build_task.build_id, build_task.arch_id, |         cur.execute(sql, (build_task.id, build_task.build_id, build_task.arch_id, | ||||||
|                           build_task.started_at, build_task.finished_at, build_task.status_id)) |                           build_task.started_at, build_task.finished_at, build_task.status_id)) | ||||||
|             conn.commit() |         self.__conn.commit() | ||||||
| 
 | 
 | ||||||
|     def get_latest_build_id(self) -> Union[int, None]: |     def get_latest_build_id(self) -> Union[int, None]: | ||||||
|         sql = "SELECT id from builds ORDER BY id DESC LIMIT 1;" |         sql = "SELECT id from builds ORDER BY id DESC LIMIT 1;" | ||||||
|         with self._get_conn() as conn: |         cur = self.__conn.cursor() | ||||||
|             cur = conn.cursor() |  | ||||||
|         cur.execute(sql) |         cur.execute(sql) | ||||||
|         val = cur.fetchone() |         val = cur.fetchone() | ||||||
|         if not val: |         if not val: | ||||||
| @ -59,8 +56,60 @@ class DB(): | |||||||
|     def cleanup_builds(self, oldest_to_keep: datetime) -> int: |     def cleanup_builds(self, oldest_to_keep: datetime) -> int: | ||||||
|         params = (int(oldest_to_keep.timestamp()),) |         params = (int(oldest_to_keep.timestamp()),) | ||||||
|         sql = "DELETE FROM builds WHERE created_at < %s;" |         sql = "DELETE FROM builds WHERE created_at < %s;" | ||||||
|         with self._get_conn() as conn: |         cur = self.__conn.cursor() | ||||||
|             cur = conn.cursor() |  | ||||||
|         cur.execute(sql, params) |         cur.execute(sql, params) | ||||||
|             conn.commit() |         self.__conn.commit() | ||||||
|         return cur.rowcount |         return cur.rowcount | ||||||
|  | 
 | ||||||
|  |     def get_unfinished_builds(self) -> Dict[int, Dict[int, int]]: | ||||||
|  |         """ | ||||||
|  |         Getting list of unfinished builds and build_tasks | ||||||
|  |         Dict[build_id, Dict[build_task_id, task_status_id]] | ||||||
|  |         """ | ||||||
|  |         res: Dict[int, Dict[int, int]] = {} | ||||||
|  | 
 | ||||||
|  |         # getting unfinished builds | ||||||
|  |         sql = 'SELECT id FROM builds where finished_at is NULL;' | ||||||
|  |         cur = self.__conn.cursor() | ||||||
|  |         cur.execute(sql) | ||||||
|  |         for row in cur.fetchall(): | ||||||
|  |             res[row[0]] = {} | ||||||
|  | 
 | ||||||
|  |         # getting list of unfinished tasks | ||||||
|  |         sql = 'SELECT id, build_id, status_id FROM build_tasks WHERE status_id < 2;' | ||||||
|  |         cur = self.__conn.cursor() | ||||||
|  |         cur.execute(sql) | ||||||
|  |         for row in cur.fetchall(): | ||||||
|  |             build_task_id: int = row[0] | ||||||
|  |             build_id: int = row[1] | ||||||
|  |             status_id: int = row[2] | ||||||
|  |             try: | ||||||
|  |                 res[build_id][build_task_id] = status_id | ||||||
|  |             except KeyError: | ||||||
|  |                 res[build_id] = {build_task_id: status_id} | ||||||
|  | 
 | ||||||
|  |         return res | ||||||
|  | 
 | ||||||
|  |     def update_build(self, build: BuildDB): | ||||||
|  |         sql = ''' | ||||||
|  |                 UPDATE builds | ||||||
|  |                 SET finished_at = %s | ||||||
|  |                 WHERE id = %s; | ||||||
|  |               ''' | ||||||
|  | 
 | ||||||
|  |         cur = self.__conn.cursor() | ||||||
|  |         cur.execute(sql, (build.finished_at, build.id)) | ||||||
|  |         self.__conn.commit() | ||||||
|  | 
 | ||||||
|  |     def update_build_task(self, build: BuildTaskDB): | ||||||
|  |         sql = ''' | ||||||
|  |                 UPDATE build_tasks | ||||||
|  |                 SET status_id = %s, | ||||||
|  |                     started_at = %s, | ||||||
|  |                     finished_at = %s | ||||||
|  |                 WHERE id = %s; | ||||||
|  |               ''' | ||||||
|  |         cur = self.__conn.cursor() | ||||||
|  |         cur.execute(sql, (build.status_id, build.started_at, | ||||||
|  |                     build.finished_at, build.id)) | ||||||
|  |         self.__conn.commit() | ||||||
|  | |||||||
| @ -1,8 +1,13 @@ | |||||||
|  | # pylint: disable=relative-beyond-top-level | ||||||
|  | 
 | ||||||
| import logging | import logging | ||||||
|  | from typing import List, Dict | ||||||
| 
 | 
 | ||||||
| from ..models.extractor_config import ExtractorConfig | from ..models.extractor_config import ExtractorConfig | ||||||
| from ..api_client import APIclient | from ..models.enums import BuildTaskEnum | ||||||
|  | from ..models.build import BuildTask | ||||||
| from ..db import DB | from ..db import DB | ||||||
|  | from ..api_client import APIclient | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Extractor: | class Extractor: | ||||||
| @ -17,11 +22,11 @@ class Extractor: | |||||||
|         last_build_id = self.db.get_latest_build_id() |         last_build_id = self.db.get_latest_build_id() | ||||||
|         if not last_build_id: |         if not last_build_id: | ||||||
|             last_build_id = 0 |             last_build_id = 0 | ||||||
|         logging.info(f"last_build_id: {last_build_id}") |         logging.info("last_build_id: %s", last_build_id) | ||||||
|         stop = False |         stop = False | ||||||
| 
 | 
 | ||||||
|         while not stop: |         while not stop: | ||||||
|             logging.info(f"page: {page_num}") |             logging.info("page: %s", page_num) | ||||||
|             for build in self.api.get_builds(page_num): |             for build in self.api.get_builds(page_num): | ||||||
|                 # check if we shoud stop processing build |                 # check if we shoud stop processing build | ||||||
|                 if build.id <= last_build_id or \ |                 if build.id <= last_build_id or \ | ||||||
| @ -30,10 +35,10 @@ class Extractor: | |||||||
|                     break |                     break | ||||||
| 
 | 
 | ||||||
|                 # inserting build and build tasks |                 # inserting build and build tasks | ||||||
|                 logging.info(f"inserting {build.id}") |                 logging.info("inserting %s", build.id) | ||||||
|                 self.db.insert_update_build(build.as_db_model()) |                 self.db.insert_build(build.as_db_model()) | ||||||
|                 for build_task in build.build_tasks: |                 for build_task in build.build_tasks: | ||||||
|                     self.db.insert_update_buildtask(build_task.as_db_model()) |                     self.db.insert_buildtask(build_task.as_db_model()) | ||||||
|                 build_count += 1 |                 build_count += 1 | ||||||
|             page_num += 1 |             page_num += 1 | ||||||
|         return build_count |         return build_count | ||||||
| @ -43,3 +48,48 @@ class Extractor: | |||||||
|                      self.oldest_build_age.strftime("%m/%d/%Y, %H:%M:%S")) |                      self.oldest_build_age.strftime("%m/%d/%Y, %H:%M:%S")) | ||||||
|         removed_count = self.db.cleanup_builds(self.oldest_build_age) |         removed_count = self.db.cleanup_builds(self.oldest_build_age) | ||||||
|         logging.info('removed %d entries', removed_count) |         logging.info('removed %d entries', removed_count) | ||||||
|  | 
 | ||||||
|  |     def __update_build_tasks_statuses(self, build_tasks: List[BuildTask], | ||||||
|  |                                       build_tasks_status_db: Dict[int, int]): | ||||||
|  |         for b in build_tasks: | ||||||
|  |             if b.status_id != build_tasks_status_db[b.id]: | ||||||
|  |                 logging.info('build taks %d status have changed %s ->  %s. Updating DB', | ||||||
|  |                              b.id, BuildTaskEnum( | ||||||
|  |                                  build_tasks_status_db[b.id]).name, | ||||||
|  |                              BuildTaskEnum(b.status_id).name) | ||||||
|  |                 try: | ||||||
|  |                     self.db.update_build_task(b.as_db_model()) | ||||||
|  |                 except Exception as err:  # pylint: disable=broad-except | ||||||
|  |                     logging.error( | ||||||
|  |                         'failed to update build task %d: %s', | ||||||
|  |                         b.id, err, exc_info=True) | ||||||
|  |                 else: | ||||||
|  |                     logging.info('build task %d was updated', b.id) | ||||||
|  |             else: | ||||||
|  |                 logging.info( | ||||||
|  |                     "build_task %d is still %s. Skipping", b.id, BuildTaskEnum(b.status_id).name) | ||||||
|  | 
 | ||||||
|  |     def update_builds(self): | ||||||
|  |         logging.info('Getting list of tasks from DB') | ||||||
|  |         unfinished_tasks = self.db.get_unfinished_builds() | ||||||
|  |         for build_id, build_tasks_db in unfinished_tasks.items(): | ||||||
|  |             try: | ||||||
|  |                 logging.info('Getting status of build %d', build_id) | ||||||
|  |                 build = self.api.get_build(build_id) | ||||||
|  | 
 | ||||||
|  |                 logging.info('Updating build tasks') | ||||||
|  |                 build_tasks_to_check = [ | ||||||
|  |                     b for b in build.build_tasks if b.id in build_tasks_db] | ||||||
|  |                 self.__update_build_tasks_statuses( | ||||||
|  |                     build_tasks_to_check, build_tasks_db) | ||||||
|  | 
 | ||||||
|  |                 if build.finished_at: | ||||||
|  |                     logging.info( | ||||||
|  |                         "build is finished, we need to update finished_at attribute") | ||||||
|  |                     self.db.update_build(build.as_db_model()) | ||||||
|  | 
 | ||||||
|  |                 logging.info('finished proccessing build %d', build_id) | ||||||
|  | 
 | ||||||
|  |             except Exception as err:  # pylint: disable=broad-except | ||||||
|  |                 logging.error("Cant process build %d:  %s", | ||||||
|  |                               build_id, err, exc_info=True) | ||||||
|  | |||||||
| @ -1,9 +1,11 @@ | |||||||
| from datetime import datetime, timedelta | from datetime import datetime, timedelta | ||||||
| import logging | import logging | ||||||
| from logging.handlers import RotatingFileHandler | from logging.handlers import RotatingFileHandler | ||||||
|  | import time | ||||||
| 
 | 
 | ||||||
| import yaml | import yaml | ||||||
| 
 | 
 | ||||||
|  | # pylint: disable=relative-beyond-top-level | ||||||
| from ..api_client import APIclient | from ..api_client import APIclient | ||||||
| from ..db import DB | from ..db import DB | ||||||
| from .extractor import Extractor | from .extractor import Extractor | ||||||
| @ -49,15 +51,19 @@ def start(yml_path: str): | |||||||
|                                                       maxBytes=10000000, |                                                       maxBytes=10000000, | ||||||
|                                                       backupCount=3)]) |                                                       backupCount=3)]) | ||||||
| 
 | 
 | ||||||
|     api = APIclient(api_root=config.albs_url, jwt=config.jwt) |     while True: | ||||||
|  |         logging.info('Starting extraction proccess') | ||||||
|  |         api = APIclient(api_root=config.albs_url, | ||||||
|  |                         jwt=config.jwt, timeout=config.api_timeout) | ||||||
|         db = DB(config.db_config) |         db = DB(config.db_config) | ||||||
|         extractor = Extractor(config, api, db) |         extractor = Extractor(config, api, db) | ||||||
|  | 
 | ||||||
|         logging.info('Starting builds insertion') |         logging.info('Starting builds insertion') | ||||||
|         inserted_count = -1 |         inserted_count = -1 | ||||||
|         try: |         try: | ||||||
|             inserted_count = extractor.extract_and_store() |             inserted_count = extractor.extract_and_store() | ||||||
|         except Exception as err:  # pylint: disable=broad-except |         except Exception as err:  # pylint: disable=broad-except | ||||||
|         logging.critical("Unhandled exeption %s", err, exc_info=True) |             logging.critical("Unhandled exception %s", err, exc_info=True) | ||||||
|         else: |         else: | ||||||
|             logging.info( |             logging.info( | ||||||
|                 'Build extaction was finished. %d builds were inserted', inserted_count) |                 'Build extaction was finished. %d builds were inserted', inserted_count) | ||||||
| @ -65,7 +71,20 @@ def start(yml_path: str): | |||||||
|         logging.info('Starting old builds removal') |         logging.info('Starting old builds removal') | ||||||
|         try: |         try: | ||||||
|             extractor.build_cleanup() |             extractor.build_cleanup() | ||||||
|     except Exception as err: |         except Exception as err:  # pylint: disable=broad-except | ||||||
|         logging.critical("Unhandled exeption %s", err, exc_info=True) |             logging.critical("Unhandled exception %s", err, exc_info=True) | ||||||
|         else: |         else: | ||||||
|             logging.info('Cleanup finished') |             logging.info('Cleanup finished') | ||||||
|  | 
 | ||||||
|  |         logging.info('Updating statuses of unfinished build tasks') | ||||||
|  |         try: | ||||||
|  |             extractor.update_builds() | ||||||
|  |         except Exception as err:  # pylint: disable=broad-except | ||||||
|  |             logging.critical("Unhandled exception %s", err, exc_info=True) | ||||||
|  |         else: | ||||||
|  |             logging.info('Update finished') | ||||||
|  | 
 | ||||||
|  |         extractor.db.close_conn() | ||||||
|  |         logging.info("Extraction was finished") | ||||||
|  |         logging.info("Sleeping for %d seconds", config.scrape_inteval) | ||||||
|  |         time.sleep(config.scrape_inteval) | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| from datetime import datetime | from datetime import datetime | ||||||
| from typing import List, Optional | from typing import List, Optional | ||||||
| from pydantic import BaseModel, HttpUrl | from pydantic import BaseModel, HttpUrl  # pylint: disable=no-name-in-module | ||||||
| from typing import Any |  | ||||||
| 
 | 
 | ||||||
| from .build_task import BuildTask | from .build_task import BuildTask | ||||||
| from .build_db import BuildDB | from .build_db import BuildDB | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| from typing import Optional, Dict, Any | from typing import Optional | ||||||
| from pydantic import BaseModel, HttpUrl | from pydantic import BaseModel, HttpUrl  # pylint: disable=no-name-in-module | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class BuildDB(BaseModel): | class BuildDB(BaseModel): | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| from datetime import datetime | from datetime import datetime | ||||||
| from typing import Optional | from typing import Optional | ||||||
| 
 | 
 | ||||||
| from pydantic import BaseModel | from pydantic import BaseModel  # pylint: disable=no-name-in-module | ||||||
| 
 | 
 | ||||||
| from .build_task_db import BuildTaskDB | from .build_task_db import BuildTaskDB | ||||||
| from .enums import ArchEnum | from .enums import ArchEnum | ||||||
|  | |||||||
| @ -1,13 +1,15 @@ | |||||||
| from datetime import datetime | from datetime import datetime | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| 
 | 
 | ||||||
| from pydantic import HttpUrl, Field, BaseModel | from pydantic import HttpUrl, Field, BaseModel  # pylint: disable=no-name-in-module | ||||||
| 
 | 
 | ||||||
| from .db_config import DbConfig | from .db_config import DbConfig | ||||||
| 
 | 
 | ||||||
| # DEFAULTS | # DEFAULTS | ||||||
| ALBS_URL_DEFAULT = 'https://build.almalinux.org' | ALBS_URL_DEFAULT = 'https://build.almalinux.org' | ||||||
| LOG_FILE_DEFAULT = '/tmp/extractor.log' | LOG_FILE_DEFAULT = '/tmp/extractor.log' | ||||||
|  | API_DEFAULT = 30 | ||||||
|  | SCRAPE_INTERVAL_DEFAULT = 3600 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ExtractorConfig(BaseModel): | class ExtractorConfig(BaseModel): | ||||||
| @ -22,3 +24,8 @@ class ExtractorConfig(BaseModel): | |||||||
|         Field(description='oldest build age to extract and store') |         Field(description='oldest build age to extract and store') | ||||||
|     jwt: str = Field(description='ALBS JWT token') |     jwt: str = Field(description='ALBS JWT token') | ||||||
|     db_config: DbConfig = Field(description="database configuration") |     db_config: DbConfig = Field(description="database configuration") | ||||||
|  |     api_timeout: int = Field( | ||||||
|  |         description="max time in seconds to wait for API response", | ||||||
|  |         default=API_DEFAULT) | ||||||
|  |     scrape_inteval: int = Field(description='how often (in seconds) we will extract data from ALBS', | ||||||
|  |                                 default=SCRAPE_INTERVAL_DEFAULT) | ||||||
|  | |||||||
| @ -16,13 +16,13 @@ ON builds(finished_at); | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| -- build_taks_enum | -- build_taks_enum | ||||||
| DROP TABLE IF EXISTS build_task_enum CASCADE; | DROP TABLE IF EXISTS build_task_status_enum CASCADE; | ||||||
| CREATE TABLE IF NOT EXISTS build_task_enum( | CREATE TABLE IF NOT EXISTS build_task_status_enum( | ||||||
|     id INTEGER PRIMARY KEY, |     id INTEGER PRIMARY KEY, | ||||||
|     value VARCHAR(15) |     value VARCHAR(15) | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| INSERT INTO build_task_enum (id, value) | INSERT INTO build_task_status_enum (id, value) | ||||||
| VALUES | VALUES | ||||||
|     (0, 'idle'), |     (0, 'idle'), | ||||||
|     (1, 'started'), |     (1, 'started'), | ||||||
| @ -53,7 +53,7 @@ CREATE TABLE build_tasks ( | |||||||
|     id INTEGER PRIMARY KEY, |     id INTEGER PRIMARY KEY, | ||||||
|     build_id INTEGER REFERENCES builds(id) ON DELETE CASCADE, |     build_id INTEGER REFERENCES builds(id) ON DELETE CASCADE, | ||||||
|     arch_id INTEGER REFERENCES arch_enum(id) ON DELETE SET NULL, |     arch_id INTEGER REFERENCES arch_enum(id) ON DELETE SET NULL, | ||||||
|     status_id INTEGER REFERENCES  build_task_enum(id) ON DELETE SET NULL, |     status_id INTEGER REFERENCES build_task_status_enum(id) ON DELETE SET NULL, | ||||||
|     started_at REAL, |     started_at REAL, | ||||||
|     finished_at REAL |     finished_at REAL | ||||||
| ); | ); | ||||||
|  | |||||||
							
								
								
									
										280
									
								
								grafana-dashbords/albs_analytics.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										280
									
								
								grafana-dashbords/albs_analytics.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,280 @@ | |||||||
|  | { | ||||||
|  |   "__inputs": [ | ||||||
|  |     { | ||||||
|  |       "name": "DS_POSTGRESQL", | ||||||
|  |       "label": "PostgreSQL", | ||||||
|  |       "description": "", | ||||||
|  |       "type": "datasource", | ||||||
|  |       "pluginId": "postgres", | ||||||
|  |       "pluginName": "PostgreSQL" | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "__elements": {}, | ||||||
|  |   "__requires": [ | ||||||
|  |     { | ||||||
|  |       "type": "grafana", | ||||||
|  |       "id": "grafana", | ||||||
|  |       "name": "Grafana", | ||||||
|  |       "version": "9.3.2" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "type": "datasource", | ||||||
|  |       "id": "postgres", | ||||||
|  |       "name": "PostgreSQL", | ||||||
|  |       "version": "1.0.0" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "type": "panel", | ||||||
|  |       "id": "table", | ||||||
|  |       "name": "Table", | ||||||
|  |       "version": "" | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "annotations": { | ||||||
|  |     "list": [ | ||||||
|  |       { | ||||||
|  |         "builtIn": 1, | ||||||
|  |         "datasource": { | ||||||
|  |           "type": "grafana", | ||||||
|  |           "uid": "-- Grafana --" | ||||||
|  |         }, | ||||||
|  |         "enable": true, | ||||||
|  |         "hide": true, | ||||||
|  |         "iconColor": "rgba(0, 211, 255, 1)", | ||||||
|  |         "name": "Annotations & Alerts", | ||||||
|  |         "target": { | ||||||
|  |           "limit": 100, | ||||||
|  |           "matchAny": false, | ||||||
|  |           "tags": [], | ||||||
|  |           "type": "dashboard" | ||||||
|  |         }, | ||||||
|  |         "type": "dashboard" | ||||||
|  |       } | ||||||
|  |     ] | ||||||
|  |   }, | ||||||
|  |   "editable": true, | ||||||
|  |   "fiscalYearStartMonth": 0, | ||||||
|  |   "graphTooltip": 0, | ||||||
|  |   "id": null, | ||||||
|  |   "links": [], | ||||||
|  |   "liveNow": false, | ||||||
|  |   "panels": [ | ||||||
|  |     { | ||||||
|  |       "datasource": { | ||||||
|  |         "type": "postgres", | ||||||
|  |         "uid": "${DS_POSTGRESQL}" | ||||||
|  |       }, | ||||||
|  |       "description": "", | ||||||
|  |       "fieldConfig": { | ||||||
|  |         "defaults": { | ||||||
|  |           "color": { | ||||||
|  |             "mode": "thresholds" | ||||||
|  |           }, | ||||||
|  |           "custom": { | ||||||
|  |             "align": "auto", | ||||||
|  |             "displayMode": "auto", | ||||||
|  |             "inspect": false | ||||||
|  |           }, | ||||||
|  |           "mappings": [], | ||||||
|  |           "thresholds": { | ||||||
|  |             "mode": "absolute", | ||||||
|  |             "steps": [ | ||||||
|  |               { | ||||||
|  |                 "color": "green", | ||||||
|  |                 "value": null | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "color": "red", | ||||||
|  |                 "value": 80 | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         "overrides": [ | ||||||
|  |           { | ||||||
|  |             "matcher": { | ||||||
|  |               "id": "byName", | ||||||
|  |               "options": "id" | ||||||
|  |             }, | ||||||
|  |             "properties": [ | ||||||
|  |               { | ||||||
|  |                 "id": "custom.width", | ||||||
|  |                 "value": 54 | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             "matcher": { | ||||||
|  |               "id": "byName", | ||||||
|  |               "options": "created_at" | ||||||
|  |             }, | ||||||
|  |             "properties": [ | ||||||
|  |               { | ||||||
|  |                 "id": "custom.width", | ||||||
|  |                 "value": 226 | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             "matcher": { | ||||||
|  |               "id": "byName", | ||||||
|  |               "options": "finished_at" | ||||||
|  |             }, | ||||||
|  |             "properties": [ | ||||||
|  |               { | ||||||
|  |                 "id": "custom.width", | ||||||
|  |                 "value": 209 | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             "matcher": { | ||||||
|  |               "id": "byName", | ||||||
|  |               "options": "finished" | ||||||
|  |             }, | ||||||
|  |             "properties": [ | ||||||
|  |               { | ||||||
|  |                 "id": "custom.width", | ||||||
|  |                 "value": 187 | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             "matcher": { | ||||||
|  |               "id": "byName", | ||||||
|  |               "options": "created" | ||||||
|  |             }, | ||||||
|  |             "properties": [ | ||||||
|  |               { | ||||||
|  |                 "id": "custom.width", | ||||||
|  |                 "value": 213 | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             "matcher": { | ||||||
|  |               "id": "byName", | ||||||
|  |               "options": "url" | ||||||
|  |             }, | ||||||
|  |             "properties": [ | ||||||
|  |               { | ||||||
|  |                 "id": "custom.width", | ||||||
|  |                 "value": 279 | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           } | ||||||
|  |         ] | ||||||
|  |       }, | ||||||
|  |       "gridPos": { | ||||||
|  |         "h": 12, | ||||||
|  |         "w": 24, | ||||||
|  |         "x": 0, | ||||||
|  |         "y": 0 | ||||||
|  |       }, | ||||||
|  |       "id": 2, | ||||||
|  |       "options": { | ||||||
|  |         "footer": { | ||||||
|  |           "fields": "", | ||||||
|  |           "reducer": [ | ||||||
|  |             "sum" | ||||||
|  |           ], | ||||||
|  |           "show": false | ||||||
|  |         }, | ||||||
|  |         "showHeader": true, | ||||||
|  |         "sortBy": [ | ||||||
|  |           { | ||||||
|  |             "desc": true, | ||||||
|  |             "displayName": "duration (h)" | ||||||
|  |           } | ||||||
|  |         ] | ||||||
|  |       }, | ||||||
|  |       "pluginVersion": "9.3.2", | ||||||
|  |       "targets": [ | ||||||
|  |         { | ||||||
|  |           "cacheDurationSeconds": 300, | ||||||
|  |           "datasource": { | ||||||
|  |             "type": "postgres", | ||||||
|  |             "uid": "${DS_POSTGRESQL}" | ||||||
|  |           }, | ||||||
|  |           "editorMode": "code", | ||||||
|  |           "fields": [ | ||||||
|  |             { | ||||||
|  |               "jsonPath": "" | ||||||
|  |             } | ||||||
|  |           ], | ||||||
|  |           "format": "table", | ||||||
|  |           "hide": false, | ||||||
|  |           "method": "GET", | ||||||
|  |           "queryParams": "", | ||||||
|  |           "rawQuery": true, | ||||||
|  |           "rawSql": "SELECT id, url, created_at * 1000 as created, finished_at * 1000 as finished, (finished_at - created_at) / (60*60) as duration\nFROM builds\nWHERE $__unixEpochFilter(created_at) AND finished_at IS NOT NULL", | ||||||
|  |           "refId": "A", | ||||||
|  |           "sql": { | ||||||
|  |             "columns": [ | ||||||
|  |               { | ||||||
|  |                 "parameters": [], | ||||||
|  |                 "type": "function" | ||||||
|  |               } | ||||||
|  |             ], | ||||||
|  |             "groupBy": [ | ||||||
|  |               { | ||||||
|  |                 "property": { | ||||||
|  |                   "type": "string" | ||||||
|  |                 }, | ||||||
|  |                 "type": "groupBy" | ||||||
|  |               } | ||||||
|  |             ], | ||||||
|  |             "limit": 50 | ||||||
|  |           }, | ||||||
|  |           "urlPath": "" | ||||||
|  |         } | ||||||
|  |       ], | ||||||
|  |       "title": "Finished builds", | ||||||
|  |       "transformations": [ | ||||||
|  |         { | ||||||
|  |           "id": "convertFieldType", | ||||||
|  |           "options": { | ||||||
|  |             "conversions": [ | ||||||
|  |               { | ||||||
|  |                 "destinationType": "time", | ||||||
|  |                 "targetField": "created" | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "destinationType": "time", | ||||||
|  |                 "targetField": "finished" | ||||||
|  |               } | ||||||
|  |             ], | ||||||
|  |             "fields": {} | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "id": "organize", | ||||||
|  |           "options": { | ||||||
|  |             "excludeByName": {}, | ||||||
|  |             "indexByName": {}, | ||||||
|  |             "renameByName": { | ||||||
|  |               "duration": "duration (h)" | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       ], | ||||||
|  |       "type": "table" | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "schemaVersion": 37, | ||||||
|  |   "style": "dark", | ||||||
|  |   "tags": [], | ||||||
|  |   "templating": { | ||||||
|  |     "list": [] | ||||||
|  |   }, | ||||||
|  |   "time": { | ||||||
|  |     "from": "now-3h", | ||||||
|  |     "to": "now" | ||||||
|  |   }, | ||||||
|  |   "timepicker": {}, | ||||||
|  |   "timezone": "", | ||||||
|  |   "title": "albs_analytics", | ||||||
|  |   "uid": "02mg4oxVk", | ||||||
|  |   "version": 1, | ||||||
|  |   "weekStart": "" | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user