Skip to content

Commit f9452cd

Browse files
Fixing some issues related to deployed Trackio Spaces (#386)
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
1 parent 6b55e9e commit f9452cd

10 files changed

Lines changed: 238 additions & 95 deletions

File tree

.changeset/shy-cobras-sneeze.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trackio": patch
3+
---
4+
5+
feat:Fixing some issues related to deployed Trackio Spaces

docs/source/environment_variables.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export TRACKIO_LOGO_LIGHT_URL="https://example.com/logo-light.png"
2323
export TRACKIO_LOGO_DARK_URL="https://example.com/logo-dark.png"
2424
```
2525

26+
> **Note:** For remote Trackio Spaces, these environment variables are only applied when the Space is first created via `trackio.init(space_id=...)`. To change logos on an existing Space, update the Space variables directly in the Hugging Face Space settings.
27+
2628
### `TRACKIO_PLOT_ORDER`
2729

2830
Controls the ordering of plots and metric groups in the Trackio dashboard. The value should be a comma-separated list of metric patterns that specify the desired order. Groups are preserved - if `train/loss` is specified first, all other `train/*` metrics will appear together in the train group, with `train/loss` appearing first within that group.
@@ -44,6 +46,8 @@ export TRACKIO_PLOT_ORDER="train/loss,val/loss"
4446
- Groups appear in the order of their first matching pattern
4547
- Unspecified metrics appear in alphabetical order after specified ones
4648

49+
> **Note:** For remote Trackio Spaces, this environment variable is only applied when the Space is first created via `trackio.init(space_id=...)`. To change the plot order on an existing Space, update the `TRACKIO_PLOT_ORDER` Space variable directly in the Hugging Face Space settings.
50+
4751
### `TRACKIO_THEME`
4852

4953
Sets the theme for the Trackio dashboard. Can be a built-in Gradio theme name or a theme from the Hugging Face Hub.
@@ -59,6 +63,8 @@ export TRACKIO_THEME="gstaff/xkcd"
5963
export TRACKIO_THEME="ParityError/Anime"
6064
```
6165

66+
> **Note:** For remote Trackio Spaces, this environment variable is only applied when the Space is first created via `trackio.init(space_id=...)`. To change the theme on an existing Space, update the `TRACKIO_THEME` Space variable directly in the Hugging Face Space settings.
67+
6268
### `TRACKIO_COLOR_PALETTE`
6369

6470
Customizes the color palette used for plot lines in the Trackio dashboard. The value should be a comma-separated list of hex color codes. These colors will be cycled through when plotting multiple runs.

tests/conftest.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
import tempfile
23
from pathlib import Path
34

@@ -8,6 +9,14 @@
89
from trackio.media import write_audio, write_video
910

1011

12+
@pytest.fixture
13+
def test_space_id():
14+
space_id = os.environ.get("TEST_SPACE_ID")
15+
if not space_id:
16+
pytest.skip("TEST_SPACE_ID environment variable not set")
17+
return space_id
18+
19+
1120
@pytest.fixture
1221
def temp_dir(monkeypatch):
1322
"""Fixture that creates a temporary TRACKIO_DIR."""

tests/e2e-spaces/test_metrics_available_on_spaces.py

Lines changed: 0 additions & 57 deletions
This file was deleted.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import secrets
2+
3+
import huggingface_hub
4+
from gradio_client import Client
5+
6+
import trackio
7+
8+
9+
def test_basic_logging(test_space_id):
10+
project_name = f"test_project_{secrets.token_urlsafe(8)}"
11+
run_name = "test_run"
12+
13+
trackio.init(project=project_name, name=run_name, space_id=test_space_id)
14+
trackio.log(metrics={"loss": 0.1})
15+
trackio.log(metrics={"loss": 0.2, "acc": 0.9})
16+
trackio.finish()
17+
18+
client = Client(test_space_id)
19+
20+
summary = client.predict(
21+
project=project_name, run=run_name, api_name="/get_run_summary"
22+
)
23+
assert summary["num_logs"] == 2
24+
assert "loss" in summary["metrics"]
25+
assert "acc" in summary["metrics"]
26+
27+
loss_values = client.predict(
28+
project=project_name,
29+
run=run_name,
30+
metric_name="loss",
31+
api_name="/get_metric_values",
32+
)
33+
assert len(loss_values) == 2
34+
assert loss_values[0]["value"] == 0.1
35+
assert loss_values[0]["step"] == 0
36+
assert loss_values[1]["value"] == 0.2
37+
assert loss_values[1]["step"] == 1
38+
39+
acc_values = client.predict(
40+
project=project_name,
41+
run=run_name,
42+
metric_name="acc",
43+
api_name="/get_metric_values",
44+
)
45+
assert len(acc_values) == 1
46+
assert acc_values[0]["value"] == 0.9
47+
assert acc_values[0]["step"] == 1
48+
49+
50+
def test_runs_data_persisted_after_restart(test_space_id):
51+
"""Test that runs with configs are correctly restored after Space restart."""
52+
project_name = f"test_project_{secrets.token_urlsafe(8)}"
53+
run_name = "test_run_with_config"
54+
55+
trackio.init(
56+
project=project_name,
57+
name=run_name,
58+
space_id=test_space_id,
59+
config={"learning_rate": 0.001, "epochs": 10},
60+
)
61+
trackio.log(metrics={"loss": 0.5})
62+
trackio.finish()
63+
64+
client = Client(test_space_id)
65+
66+
client.predict(api_name="/force_sync")
67+
68+
# This will force a restart of the Space
69+
huggingface_hub.add_space_variable(
70+
test_space_id, "TRACKIO_TEST_RESTART", secrets.token_urlsafe(8)
71+
)
72+
73+
client = Client(test_space_id)
74+
75+
headers, rows, run_names = client.predict(
76+
project=project_name, api_name="/get_runs_data"
77+
)
78+
79+
assert run_name in run_names
80+
assert any("0.001" in str(row) for row in rows)
81+
assert any("10" in str(row) for row in rows)

trackio/deploy.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@ def deploy_as_space(
160160
huggingface_hub.add_space_secret(space_id, "HF_TOKEN", hf_token)
161161
if dataset_id is not None:
162162
huggingface_hub.add_space_variable(space_id, "TRACKIO_DATASET_ID", dataset_id)
163-
164163
if logo_light_url := os.environ.get("TRACKIO_LOGO_LIGHT_URL"):
165164
huggingface_hub.add_space_variable(
166165
space_id, "TRACKIO_LOGO_LIGHT_URL", logo_light_url
@@ -169,13 +168,10 @@ def deploy_as_space(
169168
huggingface_hub.add_space_variable(
170169
space_id, "TRACKIO_LOGO_DARK_URL", logo_dark_url
171170
)
172-
173171
if plot_order := os.environ.get("TRACKIO_PLOT_ORDER"):
174172
huggingface_hub.add_space_variable(space_id, "TRACKIO_PLOT_ORDER", plot_order)
175-
176173
if theme := os.environ.get("TRACKIO_THEME"):
177174
huggingface_hub.add_space_variable(space_id, "TRACKIO_THEME", theme)
178-
179175
huggingface_hub.add_space_variable(space_id, "GRADIO_MCP_SERVER", "True")
180176

181177

@@ -211,36 +207,13 @@ def create_space_if_not_exists(
211207
try:
212208
huggingface_hub.repo_info(space_id, repo_type="space")
213209
print(f"* Found existing space: {SPACE_URL.format(space_id=space_id)}")
214-
if dataset_id is not None:
215-
huggingface_hub.add_space_variable(
216-
space_id, "TRACKIO_DATASET_ID", dataset_id
217-
)
218-
if logo_light_url := os.environ.get("TRACKIO_LOGO_LIGHT_URL"):
219-
huggingface_hub.add_space_variable(
220-
space_id, "TRACKIO_LOGO_LIGHT_URL", logo_light_url
221-
)
222-
if logo_dark_url := os.environ.get("TRACKIO_LOGO_DARK_URL"):
223-
huggingface_hub.add_space_variable(
224-
space_id, "TRACKIO_LOGO_DARK_URL", logo_dark_url
225-
)
226-
227-
if plot_order := os.environ.get("TRACKIO_PLOT_ORDER"):
228-
huggingface_hub.add_space_variable(
229-
space_id, "TRACKIO_PLOT_ORDER", plot_order
230-
)
231-
232-
if theme := os.environ.get("TRACKIO_THEME"):
233-
huggingface_hub.add_space_variable(space_id, "TRACKIO_THEME", theme)
234210
return
235211
except RepositoryNotFoundError:
236212
pass
237213
except HfHubHTTPError as e:
238214
if e.response.status_code in [401, 403]: # unauthorized or forbidden
239215
print("Need 'write' access token to create a Spaces repo.")
240216
huggingface_hub.login(add_to_git_credential=False)
241-
huggingface_hub.add_space_variable(
242-
space_id, "TRACKIO_DATASET_ID", dataset_id
243-
)
244217
else:
245218
raise ValueError(f"Failed to create Space: {e}")
246219

trackio/sqlite_storage.py

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ def export_to_parquet():
183183
"""
184184
Exports all projects' DB files as Parquet under the same path but with extension ".parquet".
185185
Also exports system_metrics to separate parquet files with "_system.parquet" suffix.
186+
Also exports configs to separate parquet files with "_configs.parquet" suffix.
186187
"""
187188
if not SQLiteStorage._dataset_import_attempted:
188189
return
@@ -196,6 +197,7 @@ def export_to_parquet():
196197
parquet_path = db_path.with_suffix(".parquet")
197198
system_parquet_path = db_path.with_suffix("") / ""
198199
system_parquet_path = TRACKIO_DIR / (db_path.stem + "_system.parquet")
200+
configs_parquet_path = TRACKIO_DIR / (db_path.stem + "_configs.parquet")
199201
if (not parquet_path.exists()) or (
200202
db_path.stat().st_mtime > parquet_path.stat().st_mtime
201203
):
@@ -243,6 +245,31 @@ def export_to_parquet():
243245
use_content_defined_chunking=True,
244246
)
245247

248+
if (not configs_parquet_path.exists()) or (
249+
db_path.stat().st_mtime > configs_parquet_path.stat().st_mtime
250+
):
251+
with sqlite3.connect(str(db_path)) as conn:
252+
try:
253+
configs_df = pd.read_sql("SELECT * FROM configs", conn)
254+
except Exception:
255+
configs_df = pd.DataFrame()
256+
if not configs_df.empty:
257+
config_data = configs_df["config"].copy()
258+
config_data = pd.DataFrame(
259+
config_data.apply(
260+
lambda x: deserialize_values(orjson.loads(x))
261+
).values.tolist(),
262+
index=configs_df.index,
263+
)
264+
del configs_df["config"]
265+
for col in config_data.columns:
266+
configs_df[col] = config_data[col]
267+
configs_df.to_parquet(
268+
configs_parquet_path,
269+
write_page_index=True,
270+
use_content_defined_chunking=True,
271+
)
272+
246273
@staticmethod
247274
def _cleanup_wal_sidecars(db_path: Path) -> None:
248275
"""Remove leftover -wal/-shm files for a DB basename (prevents disk I/O errors)."""
@@ -259,6 +286,7 @@ def import_from_parquet():
259286
"""
260287
Imports to all DB files that have matching files under the same path but with extension ".parquet".
261288
Also imports system_metrics from "_system.parquet" files.
289+
Also imports configs from "_configs.parquet" files.
262290
"""
263291
if not TRACKIO_DIR.exists():
264292
return
@@ -267,7 +295,9 @@ def import_from_parquet():
267295
parquet_names = [
268296
f
269297
for f in all_paths
270-
if f.endswith(".parquet") and not f.endswith("_system.parquet")
298+
if f.endswith(".parquet")
299+
and not f.endswith("_system.parquet")
300+
and not f.endswith("_configs.parquet")
271301
]
272302
for pq_name in parquet_names:
273303
parquet_path = TRACKIO_DIR / pq_name
@@ -310,6 +340,29 @@ def import_from_parquet():
310340
df.to_sql("system_metrics", conn, if_exists="replace", index=False)
311341
conn.commit()
312342

343+
configs_parquet_names = [f for f in all_paths if f.endswith("_configs.parquet")]
344+
for pq_name in configs_parquet_names:
345+
parquet_path = TRACKIO_DIR / pq_name
346+
db_name = pq_name.replace("_configs.parquet", DB_EXT)
347+
db_path = TRACKIO_DIR / db_name
348+
349+
df = pd.read_parquet(parquet_path)
350+
if "config" not in df.columns:
351+
config_data = df.copy()
352+
other_cols = ["id", "run_name", "created_at"]
353+
df = df[[c for c in other_cols if c in df.columns]]
354+
for col in other_cols:
355+
if col in config_data.columns:
356+
del config_data[col]
357+
config_data = orjson.loads(config_data.to_json(orient="records"))
358+
df["config"] = [
359+
orjson.dumps(serialize_values(row)) for row in config_data
360+
]
361+
362+
with sqlite3.connect(str(db_path), timeout=30.0) as conn:
363+
df.to_sql("configs", conn, if_exists="replace", index=False)
364+
conn.commit()
365+
313366
@staticmethod
314367
def get_scheduler():
315368
"""
@@ -330,7 +383,12 @@ def get_scheduler():
330383
repo_type="dataset",
331384
folder_path=TRACKIO_DIR,
332385
private=True,
333-
allow_patterns=["*.parquet", "*_system.parquet", "media/**/*"],
386+
allow_patterns=[
387+
"*.parquet",
388+
"*_system.parquet",
389+
"*_configs.parquet",
390+
"media/**/*",
391+
],
334392
squash_history=True,
335393
token=hf_token,
336394
on_before_commit=SQLiteStorage.export_to_parquet,

0 commit comments

Comments
 (0)