CoolFace
Apppublic

elephantmipt/trackio_stable-diffusion-v1-5_stable-diffusion-v1-5_None

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
sqlite_storage.py293 linesDownload Raw Back to root
1import json2import os3import shutil4import sqlite35from datetime import datetime6from pathlib import Path7from threading import Lock8 9from huggingface_hub import hf_hub_download10from huggingface_hub.errors import EntryNotFoundError11 12try:  # absolute imports when installed13    from trackio.commit_scheduler import CommitScheduler14    from trackio.dummy_commit_scheduler import DummyCommitScheduler15    from trackio.utils import TRACKIO_DIR16except Exception:  # relative imports for local execution on Spaces17    from commit_scheduler import CommitScheduler18    from dummy_commit_scheduler import DummyCommitScheduler19    from utils import TRACKIO_DIR20 21 22class SQLiteStorage:23    _dataset_import_attempted = False24    _current_scheduler: CommitScheduler | DummyCommitScheduler | None = None25    _scheduler_lock = Lock()26 27    @staticmethod28    def _get_connection(db_path: Path) -> sqlite3.Connection:29        conn = sqlite3.connect(str(db_path))30        conn.row_factory = sqlite3.Row31        return conn32 33    @staticmethod34    def get_project_db_filename(project: str) -> Path:35        """Get the database filename for a specific project."""36        safe_project_name = "".join(37            c for c in project if c.isalnum() or c in ("-", "_")38        ).rstrip()39        if not safe_project_name:40            safe_project_name = "default"41        return f"{safe_project_name}.db"42 43    @staticmethod44    def get_project_db_path(project: str) -> Path:45        """Get the database path for a specific project."""46        filename = SQLiteStorage.get_project_db_filename(project)47        return TRACKIO_DIR / filename48 49    @staticmethod50    def init_db(project: str) -> Path:51        """52        Initialize the SQLite database with required tables.53        If there is a dataset ID provided, copies from that dataset instead.54        Returns the database path.55        """56        db_path = SQLiteStorage.get_project_db_path(project)57        db_path.parent.mkdir(parents=True, exist_ok=True)58        with SQLiteStorage.get_scheduler().lock:59            dataset_id = os.environ.get("TRACKIO_DATASET_ID")60            if dataset_id is not None and not SQLiteStorage._dataset_import_attempted:61                filename = SQLiteStorage.get_project_db_filename(project)62                try:63                    downloaded_path = hf_hub_download(64                        dataset_id, filename, repo_type="dataset"65                    )66                    shutil.copy(downloaded_path, db_path)67                except EntryNotFoundError:68                    pass69                SQLiteStorage._dataset_import_attempted = True70 71            with sqlite3.connect(db_path) as conn:72                cursor = conn.cursor()73                cursor.execute("""74                    CREATE TABLE IF NOT EXISTS metrics (75                        id INTEGER PRIMARY KEY AUTOINCREMENT,76                        timestamp TEXT NOT NULL,77                        run_name TEXT NOT NULL,78                        step INTEGER NOT NULL,79                        metrics TEXT NOT NULL80                    )81                """)82                cursor.execute(83                    """84                    CREATE INDEX IF NOT EXISTS idx_metrics_run_step85                    ON metrics(run_name, step)86                    """87                )88                conn.commit()89        return db_path90 91    @staticmethod92    def get_scheduler():93        """94        Get the scheduler for the database based on the environment variables.95        This applies to both local and Spaces.96        """97        with SQLiteStorage._scheduler_lock:98            if SQLiteStorage._current_scheduler is not None:99                return SQLiteStorage._current_scheduler100            hf_token = os.environ.get("HF_TOKEN")101            dataset_id = os.environ.get("TRACKIO_DATASET_ID")102            space_repo_name = os.environ.get("SPACE_REPO_NAME")103            if dataset_id is None or space_repo_name is None:104                scheduler = DummyCommitScheduler()105            else:106                scheduler = CommitScheduler(107                    repo_id=dataset_id,108                    repo_type="dataset",109                    folder_path=TRACKIO_DIR,110                    private=True,111                    squash_history=True,112                    token=hf_token,113                )114            SQLiteStorage._current_scheduler = scheduler115            return scheduler116 117    @staticmethod118    def log(project: str, run: str, metrics: dict):119        """120        Safely log metrics to the database. Before logging, this method will ensure the database exists121        and is set up with the correct tables. It also uses the scheduler to lock the database so122        that there is no race condition when logging / syncing to the Hugging Face Dataset.123        """124        db_path = SQLiteStorage.init_db(project)125 126        with SQLiteStorage.get_scheduler().lock:127            with SQLiteStorage._get_connection(db_path) as conn:128                cursor = conn.cursor()129 130                cursor.execute(131                    """132                    SELECT MAX(step) 133                    FROM metrics 134                    WHERE run_name = ?135                    """,136                    (run,),137                )138                last_step = cursor.fetchone()[0]139                current_step = 0 if last_step is None else last_step + 1140 141                current_timestamp = datetime.now().isoformat()142 143                cursor.execute(144                    """145                    INSERT INTO metrics146                    (timestamp, run_name, step, metrics)147                    VALUES (?, ?, ?, ?)148                    """,149                    (150                        current_timestamp,151                        run,152                        current_step,153                        json.dumps(metrics),154                    ),155                )156                conn.commit()157 158    @staticmethod159    def bulk_log(160        project: str,161        run: str,162        metrics_list: list[dict],163        steps: list[int] | None = None,164        timestamps: list[str] | None = None,165    ):166        """Bulk log metrics to the database with specified steps and timestamps."""167        if not metrics_list:168            return169 170        if steps is None:171            steps = list(range(len(metrics_list)))172 173        if timestamps is None:174            timestamps = [datetime.now().isoformat()] * len(metrics_list)175 176        if len(metrics_list) != len(steps) or len(metrics_list) != len(timestamps):177            raise ValueError(178                "metrics_list, steps, and timestamps must have the same length"179            )180 181        db_path = SQLiteStorage.init_db(project)182        with SQLiteStorage.get_scheduler().lock:183            with SQLiteStorage._get_connection(db_path) as conn:184                cursor = conn.cursor()185 186                data = []187                for i, metrics in enumerate(metrics_list):188                    data.append(189                        (190                            timestamps[i],191                            run,192                            steps[i],193                            json.dumps(metrics),194                        )195                    )196 197                cursor.executemany(198                    """199                    INSERT INTO metrics200                    (timestamp, run_name, step, metrics)201                    VALUES (?, ?, ?, ?)202                    """,203                    data,204                )205                conn.commit()206 207    @staticmethod208    def get_metrics(project: str, run: str) -> list[dict]:209        """Retrieve metrics for a specific run. The metrics also include the step count (int) and the timestamp (datetime object)."""210        db_path = SQLiteStorage.get_project_db_path(project)211        if not db_path.exists():212            return []213 214        with SQLiteStorage._get_connection(db_path) as conn:215            cursor = conn.cursor()216            cursor.execute(217                """218                SELECT timestamp, step, metrics219                FROM metrics220                WHERE run_name = ?221                ORDER BY timestamp222                """,223                (run,),224            )225 226            rows = cursor.fetchall()227            results = []228            for row in rows:229                metrics = json.loads(row["metrics"])230                metrics["timestamp"] = row["timestamp"]231                metrics["step"] = row["step"]232                results.append(metrics)233 234            return results235 236    @staticmethod237    def get_projects() -> list[str]:238        """239        Get list of all projects by scanning the database files in the trackio directory.240        """241        projects: set[str] = set()242        if not TRACKIO_DIR.exists():243            return []244 245        for db_file in TRACKIO_DIR.glob("*.db"):246            project_name = db_file.stem247            projects.add(project_name)248        return sorted(projects)249 250    @staticmethod251    def get_runs(project: str) -> list[str]:252        """Get list of all runs for a project."""253        db_path = SQLiteStorage.get_project_db_path(project)254        if not db_path.exists():255            return []256 257        with SQLiteStorage._get_connection(db_path) as conn:258            cursor = conn.cursor()259            cursor.execute(260                "SELECT DISTINCT run_name FROM metrics",261            )262            return [row[0] for row in cursor.fetchall()]263 264    @staticmethod265    def get_max_steps_for_runs(project: str, runs: list[str]) -> dict[str, int]:266        """Efficiently get the maximum step for multiple runs in a single query."""267        db_path = SQLiteStorage.get_project_db_path(project)268        if not db_path.exists():269            return {run: 0 for run in runs}270 271        with SQLiteStorage._get_connection(db_path) as conn:272            cursor = conn.cursor()273            placeholders = ",".join("?" * len(runs))274            cursor.execute(275                f"""276                SELECT run_name, MAX(step) as max_step277                FROM metrics278                WHERE run_name IN ({placeholders})279                GROUP BY run_name280                """,281                runs,282            )283 284            results = {run: 0 for run in runs}  # Default to 0 for runs with no data285            for row in cursor.fetchall():286                results[row["run_name"]] = row["max_step"]287 288            return results289 290    def finish(self):291        """Cleanup when run is finished."""292        pass293