dstack 0.19.17__py3-none-any.whl → 0.19.19__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of dstack might be problematic. Click here for more details.

Files changed (86) hide show
  1. dstack/_internal/cli/services/configurators/fleet.py +111 -1
  2. dstack/_internal/cli/services/profile.py +1 -1
  3. dstack/_internal/core/backends/aws/compute.py +237 -18
  4. dstack/_internal/core/backends/base/compute.py +20 -2
  5. dstack/_internal/core/backends/cudo/compute.py +23 -9
  6. dstack/_internal/core/backends/gcp/compute.py +13 -7
  7. dstack/_internal/core/backends/lambdalabs/compute.py +2 -1
  8. dstack/_internal/core/compatibility/fleets.py +12 -11
  9. dstack/_internal/core/compatibility/gateways.py +9 -8
  10. dstack/_internal/core/compatibility/logs.py +4 -3
  11. dstack/_internal/core/compatibility/runs.py +29 -21
  12. dstack/_internal/core/compatibility/volumes.py +11 -8
  13. dstack/_internal/core/errors.py +4 -0
  14. dstack/_internal/core/models/common.py +45 -2
  15. dstack/_internal/core/models/configurations.py +9 -1
  16. dstack/_internal/core/models/fleets.py +2 -1
  17. dstack/_internal/core/models/profiles.py +8 -5
  18. dstack/_internal/core/models/resources.py +15 -8
  19. dstack/_internal/core/models/runs.py +41 -138
  20. dstack/_internal/core/models/volumes.py +14 -0
  21. dstack/_internal/core/services/diff.py +56 -3
  22. dstack/_internal/core/services/ssh/attach.py +2 -0
  23. dstack/_internal/server/app.py +37 -9
  24. dstack/_internal/server/background/__init__.py +66 -40
  25. dstack/_internal/server/background/tasks/process_fleets.py +19 -3
  26. dstack/_internal/server/background/tasks/process_gateways.py +47 -29
  27. dstack/_internal/server/background/tasks/process_idle_volumes.py +139 -0
  28. dstack/_internal/server/background/tasks/process_instances.py +13 -2
  29. dstack/_internal/server/background/tasks/process_placement_groups.py +4 -2
  30. dstack/_internal/server/background/tasks/process_running_jobs.py +14 -3
  31. dstack/_internal/server/background/tasks/process_runs.py +8 -4
  32. dstack/_internal/server/background/tasks/process_submitted_jobs.py +38 -7
  33. dstack/_internal/server/background/tasks/process_terminating_jobs.py +5 -3
  34. dstack/_internal/server/background/tasks/process_volumes.py +2 -2
  35. dstack/_internal/server/migrations/versions/35e90e1b0d3e_add_rolling_deployment_fields.py +6 -6
  36. dstack/_internal/server/migrations/versions/d5863798bf41_add_volumemodel_last_job_processed_at.py +40 -0
  37. dstack/_internal/server/models.py +1 -0
  38. dstack/_internal/server/routers/backends.py +23 -16
  39. dstack/_internal/server/routers/files.py +7 -6
  40. dstack/_internal/server/routers/fleets.py +47 -36
  41. dstack/_internal/server/routers/gateways.py +27 -18
  42. dstack/_internal/server/routers/instances.py +18 -13
  43. dstack/_internal/server/routers/logs.py +7 -3
  44. dstack/_internal/server/routers/metrics.py +14 -8
  45. dstack/_internal/server/routers/projects.py +33 -22
  46. dstack/_internal/server/routers/repos.py +7 -6
  47. dstack/_internal/server/routers/runs.py +49 -28
  48. dstack/_internal/server/routers/secrets.py +20 -15
  49. dstack/_internal/server/routers/server.py +7 -4
  50. dstack/_internal/server/routers/users.py +22 -19
  51. dstack/_internal/server/routers/volumes.py +34 -25
  52. dstack/_internal/server/schemas/logs.py +2 -2
  53. dstack/_internal/server/schemas/runs.py +17 -5
  54. dstack/_internal/server/services/fleets.py +358 -75
  55. dstack/_internal/server/services/gateways/__init__.py +17 -6
  56. dstack/_internal/server/services/gateways/client.py +5 -3
  57. dstack/_internal/server/services/instances.py +8 -0
  58. dstack/_internal/server/services/jobs/__init__.py +45 -0
  59. dstack/_internal/server/services/jobs/configurators/base.py +12 -1
  60. dstack/_internal/server/services/locking.py +104 -13
  61. dstack/_internal/server/services/logging.py +4 -2
  62. dstack/_internal/server/services/logs/__init__.py +15 -2
  63. dstack/_internal/server/services/logs/aws.py +2 -4
  64. dstack/_internal/server/services/logs/filelog.py +33 -27
  65. dstack/_internal/server/services/logs/gcp.py +3 -5
  66. dstack/_internal/server/services/proxy/repo.py +4 -1
  67. dstack/_internal/server/services/runs.py +139 -72
  68. dstack/_internal/server/services/services/__init__.py +2 -1
  69. dstack/_internal/server/services/users.py +3 -1
  70. dstack/_internal/server/services/volumes.py +15 -2
  71. dstack/_internal/server/settings.py +25 -6
  72. dstack/_internal/server/statics/index.html +1 -1
  73. dstack/_internal/server/statics/{main-d151637af20f70b2e796.js → main-64f8273740c4b52c18f5.js} +71 -67
  74. dstack/_internal/server/statics/{main-d151637af20f70b2e796.js.map → main-64f8273740c4b52c18f5.js.map} +1 -1
  75. dstack/_internal/server/statics/{main-d48635d8fe670d53961c.css → main-d58fc0460cb0eae7cb5c.css} +1 -1
  76. dstack/_internal/server/testing/common.py +48 -8
  77. dstack/_internal/server/utils/routers.py +31 -8
  78. dstack/_internal/utils/json_utils.py +54 -0
  79. dstack/api/_public/runs.py +13 -2
  80. dstack/api/server/_runs.py +12 -2
  81. dstack/version.py +1 -1
  82. {dstack-0.19.17.dist-info → dstack-0.19.19.dist-info}/METADATA +17 -14
  83. {dstack-0.19.17.dist-info → dstack-0.19.19.dist-info}/RECORD +86 -83
  84. {dstack-0.19.17.dist-info → dstack-0.19.19.dist-info}/WHEEL +0 -0
  85. {dstack-0.19.17.dist-info → dstack-0.19.19.dist-info}/entry_points.txt +0 -0
  86. {dstack-0.19.17.dist-info → dstack-0.19.19.dist-info}/licenses/LICENSE.md +0 -0
@@ -24,6 +24,7 @@ from dstack._internal.core.models.instances import (
24
24
  )
25
25
  from dstack._internal.core.models.profiles import (
26
26
  CreationPolicy,
27
+ RetryEvent,
27
28
  )
28
29
  from dstack._internal.core.models.repos.virtual import DEFAULT_VIRTUAL_REPO_ID, VirtualRunRepoData
29
30
  from dstack._internal.core.models.runs import (
@@ -105,6 +106,8 @@ async def list_user_runs(
105
106
  repo_id: Optional[str],
106
107
  username: Optional[str],
107
108
  only_active: bool,
109
+ include_jobs: bool,
110
+ job_submissions_limit: Optional[int],
108
111
  prev_submitted_at: Optional[datetime],
109
112
  prev_run_id: Optional[uuid.UUID],
110
113
  limit: int,
@@ -148,7 +151,14 @@ async def list_user_runs(
148
151
  runs = []
149
152
  for r in run_models:
150
153
  try:
151
- runs.append(run_model_to_run(r, return_in_api=True))
154
+ runs.append(
155
+ run_model_to_run(
156
+ r,
157
+ return_in_api=True,
158
+ include_jobs=include_jobs,
159
+ job_submissions_limit=job_submissions_limit,
160
+ )
161
+ )
152
162
  except pydantic.ValidationError:
153
163
  pass
154
164
  if len(run_models) > len(runs):
@@ -482,8 +492,9 @@ async def submit_run(
482
492
  select(func.pg_advisory_xact_lock(string_to_lock_id(lock_namespace)))
483
493
  )
484
494
 
485
- lock, _ = get_locker().get_lockset(lock_namespace)
495
+ lock, _ = get_locker(get_db().dialect_name).get_lockset(lock_namespace)
486
496
  async with lock:
497
+ # FIXME: delete_runs commits, so Postgres lock is released too early.
487
498
  if run_spec.run_name is None:
488
499
  run_spec.run_name = await _generate_run_name(
489
500
  session=session,
@@ -586,46 +597,29 @@ async def stop_runs(
586
597
  )
587
598
  run_models = res.scalars().all()
588
599
  run_ids = sorted([r.id for r in run_models])
589
- res = await session.execute(select(JobModel).where(JobModel.run_id.in_(run_ids)))
590
- job_models = res.scalars().all()
591
- job_ids = sorted([j.id for j in job_models])
592
600
  await session.commit()
593
- async with (
594
- get_locker().lock_ctx(RunModel.__tablename__, run_ids),
595
- get_locker().lock_ctx(JobModel.__tablename__, job_ids),
596
- ):
601
+ async with get_locker(get_db().dialect_name).lock_ctx(RunModel.__tablename__, run_ids):
602
+ res = await session.execute(
603
+ select(RunModel)
604
+ .where(RunModel.id.in_(run_ids))
605
+ .order_by(RunModel.id) # take locks in order
606
+ .with_for_update(key_share=True)
607
+ .execution_options(populate_existing=True)
608
+ )
609
+ run_models = res.scalars().all()
610
+ now = common_utils.get_current_datetime()
597
611
  for run_model in run_models:
598
- await stop_run(session=session, run_model=run_model, abort=abort)
599
-
600
-
601
- async def stop_run(session: AsyncSession, run_model: RunModel, abort: bool):
602
- res = await session.execute(
603
- select(RunModel)
604
- .where(RunModel.id == run_model.id)
605
- .order_by(RunModel.id) # take locks in order
606
- .with_for_update(key_share=True)
607
- .execution_options(populate_existing=True)
608
- )
609
- run_model = res.scalar_one()
610
- await session.execute(
611
- select(JobModel)
612
- .where(JobModel.run_id == run_model.id)
613
- .order_by(JobModel.id) # take locks in order
614
- .with_for_update(key_share=True)
615
- .execution_options(populate_existing=True)
616
- )
617
- if run_model.status.is_finished():
618
- return
619
- run_model.status = RunStatus.TERMINATING
620
- if abort:
621
- run_model.termination_reason = RunTerminationReason.ABORTED_BY_USER
622
- else:
623
- run_model.termination_reason = RunTerminationReason.STOPPED_BY_USER
624
- # process the run out of turn
625
- logger.debug("%s: terminating because %s", fmt(run_model), run_model.termination_reason.name)
626
- await process_terminating_run(session, run_model)
627
- run_model.last_processed_at = common_utils.get_current_datetime()
628
- await session.commit()
612
+ if run_model.status.is_finished():
613
+ continue
614
+ run_model.status = RunStatus.TERMINATING
615
+ if abort:
616
+ run_model.termination_reason = RunTerminationReason.ABORTED_BY_USER
617
+ else:
618
+ run_model.termination_reason = RunTerminationReason.STOPPED_BY_USER
619
+ run_model.last_processed_at = now
620
+ # The run will be terminated by process_runs.
621
+ # Terminating synchronously is problematic since it may take a long time.
622
+ await session.commit()
629
623
 
630
624
 
631
625
  async def delete_runs(
@@ -642,7 +636,7 @@ async def delete_runs(
642
636
  run_models = res.scalars().all()
643
637
  run_ids = sorted([r.id for r in run_models])
644
638
  await session.commit()
645
- async with get_locker().lock_ctx(RunModel.__tablename__, run_ids):
639
+ async with get_locker(get_db().dialect_name).lock_ctx(RunModel.__tablename__, run_ids):
646
640
  res = await session.execute(
647
641
  select(RunModel)
648
642
  .where(RunModel.id.in_(run_ids))
@@ -668,51 +662,33 @@ async def delete_runs(
668
662
 
669
663
  def run_model_to_run(
670
664
  run_model: RunModel,
671
- include_job_submissions: bool = True,
665
+ include_jobs: bool = True,
666
+ job_submissions_limit: Optional[int] = None,
672
667
  return_in_api: bool = False,
673
668
  include_sensitive: bool = False,
674
669
  ) -> Run:
675
670
  jobs: List[Job] = []
676
- run_jobs = sorted(run_model.jobs, key=lambda j: (j.replica_num, j.job_num, j.submission_num))
677
- for replica_num, replica_submissions in itertools.groupby(
678
- run_jobs, key=lambda j: j.replica_num
679
- ):
680
- for job_num, job_submissions in itertools.groupby(
681
- replica_submissions, key=lambda j: j.job_num
682
- ):
683
- submissions = []
684
- job_model = None
685
- for job_model in job_submissions:
686
- if include_job_submissions:
687
- job_submission = job_model_to_job_submission(job_model)
688
- if return_in_api:
689
- # Set default non-None values for 0.18 backward-compatibility
690
- # Remove in 0.19
691
- if job_submission.job_provisioning_data is not None:
692
- if job_submission.job_provisioning_data.hostname is None:
693
- job_submission.job_provisioning_data.hostname = ""
694
- if job_submission.job_provisioning_data.ssh_port is None:
695
- job_submission.job_provisioning_data.ssh_port = 22
696
- submissions.append(job_submission)
697
- if job_model is not None:
698
- # Use the spec from the latest submission. Submissions can have different specs
699
- job_spec = JobSpec.__response__.parse_raw(job_model.job_spec_data)
700
- if not include_sensitive:
701
- _remove_job_spec_sensitive_info(job_spec)
702
- jobs.append(Job(job_spec=job_spec, job_submissions=submissions))
671
+ if include_jobs:
672
+ jobs = _get_run_jobs_with_submissions(
673
+ run_model=run_model,
674
+ job_submissions_limit=job_submissions_limit,
675
+ return_in_api=return_in_api,
676
+ include_sensitive=include_sensitive,
677
+ )
703
678
 
704
679
  run_spec = RunSpec.__response__.parse_raw(run_model.run_spec)
705
680
 
706
681
  latest_job_submission = None
707
- if include_job_submissions:
682
+ if len(jobs) > 0 and len(jobs[0].job_submissions) > 0:
708
683
  # TODO(egor-s): does it make sense with replicas and multi-node?
709
- if jobs:
710
- latest_job_submission = jobs[0].job_submissions[-1]
684
+ latest_job_submission = jobs[0].job_submissions[-1]
711
685
 
712
686
  service_spec = None
713
687
  if run_model.service_spec is not None:
714
688
  service_spec = ServiceSpec.__response__.parse_raw(run_model.service_spec)
715
689
 
690
+ status_message = _get_run_status_message(run_model)
691
+ error = _get_run_error(run_model)
716
692
  run = Run(
717
693
  id=run_model.id,
718
694
  project_name=run_model.project.name,
@@ -720,18 +696,107 @@ def run_model_to_run(
720
696
  submitted_at=run_model.submitted_at.replace(tzinfo=timezone.utc),
721
697
  last_processed_at=run_model.last_processed_at.replace(tzinfo=timezone.utc),
722
698
  status=run_model.status,
699
+ status_message=status_message,
723
700
  termination_reason=run_model.termination_reason,
724
701
  run_spec=run_spec,
725
702
  jobs=jobs,
726
703
  latest_job_submission=latest_job_submission,
727
704
  service=service_spec,
728
705
  deployment_num=run_model.deployment_num,
706
+ error=error,
729
707
  deleted=run_model.deleted,
730
708
  )
731
709
  run.cost = _get_run_cost(run)
732
710
  return run
733
711
 
734
712
 
713
+ def _get_run_jobs_with_submissions(
714
+ run_model: RunModel,
715
+ job_submissions_limit: Optional[int],
716
+ return_in_api: bool = False,
717
+ include_sensitive: bool = False,
718
+ ) -> List[Job]:
719
+ jobs: List[Job] = []
720
+ run_jobs = sorted(run_model.jobs, key=lambda j: (j.replica_num, j.job_num, j.submission_num))
721
+ for replica_num, replica_submissions in itertools.groupby(
722
+ run_jobs, key=lambda j: j.replica_num
723
+ ):
724
+ for job_num, job_models in itertools.groupby(replica_submissions, key=lambda j: j.job_num):
725
+ submissions = []
726
+ job_model = None
727
+ if job_submissions_limit is not None:
728
+ if job_submissions_limit == 0:
729
+ # Take latest job submission to return its job_spec
730
+ job_models = list(job_models)[-1:]
731
+ else:
732
+ job_models = list(job_models)[-job_submissions_limit:]
733
+ for job_model in job_models:
734
+ if job_submissions_limit != 0:
735
+ job_submission = job_model_to_job_submission(job_model)
736
+ if return_in_api:
737
+ # Set default non-None values for 0.18 backward-compatibility
738
+ # Remove in 0.19
739
+ if job_submission.job_provisioning_data is not None:
740
+ if job_submission.job_provisioning_data.hostname is None:
741
+ job_submission.job_provisioning_data.hostname = ""
742
+ if job_submission.job_provisioning_data.ssh_port is None:
743
+ job_submission.job_provisioning_data.ssh_port = 22
744
+ submissions.append(job_submission)
745
+ if job_model is not None:
746
+ # Use the spec from the latest submission. Submissions can have different specs
747
+ job_spec = JobSpec.__response__.parse_raw(job_model.job_spec_data)
748
+ if not include_sensitive:
749
+ _remove_job_spec_sensitive_info(job_spec)
750
+ jobs.append(Job(job_spec=job_spec, job_submissions=submissions))
751
+ return jobs
752
+
753
+
754
+ def _get_run_status_message(run_model: RunModel) -> str:
755
+ if len(run_model.jobs) == 0:
756
+ return run_model.status.value
757
+
758
+ sorted_job_models = sorted(
759
+ run_model.jobs, key=lambda j: (j.replica_num, j.job_num, j.submission_num)
760
+ )
761
+ job_models_grouped_by_job = list(
762
+ list(jm)
763
+ for _, jm in itertools.groupby(sorted_job_models, key=lambda j: (j.replica_num, j.job_num))
764
+ )
765
+
766
+ if all(job_models[-1].status == JobStatus.PULLING for job_models in job_models_grouped_by_job):
767
+ # Show `pulling`` if last job submission of all jobs is pulling
768
+ return "pulling"
769
+
770
+ if run_model.status in [RunStatus.SUBMITTED, RunStatus.PENDING]:
771
+ # Show `retrying` if any job caused the run to retry
772
+ for job_models in job_models_grouped_by_job:
773
+ last_job_spec = JobSpec.__response__.parse_raw(job_models[-1].job_spec_data)
774
+ retry_on_events = last_job_spec.retry.on_events if last_job_spec.retry else []
775
+ last_job_termination_reason = _get_last_job_termination_reason(job_models)
776
+ if (
777
+ last_job_termination_reason
778
+ == JobTerminationReason.FAILED_TO_START_DUE_TO_NO_CAPACITY
779
+ and RetryEvent.NO_CAPACITY in retry_on_events
780
+ ):
781
+ # TODO: Show `retrying` for other retry events
782
+ return "retrying"
783
+
784
+ return run_model.status.value
785
+
786
+
787
+ def _get_last_job_termination_reason(job_models: List[JobModel]) -> Optional[JobTerminationReason]:
788
+ for job_model in reversed(job_models):
789
+ if job_model.termination_reason is not None:
790
+ return job_model.termination_reason
791
+ return None
792
+
793
+
794
+ def _get_run_error(run_model: RunModel) -> Optional[str]:
795
+ if run_model.termination_reason is None:
796
+ return None
797
+ return run_model.termination_reason.to_error()
798
+
799
+
735
800
  async def _get_pool_offers(
736
801
  session: AsyncSession,
737
802
  project: ProjectModel,
@@ -930,6 +995,8 @@ _TYPE_SPECIFIC_CONF_UPDATABLE_FIELDS = {
930
995
  "replicas",
931
996
  "scaling",
932
997
  # rolling deployment
998
+ # NOTE: keep this list in sync with the "Rolling deployment" section in services.md
999
+ "port",
933
1000
  "resources",
934
1001
  "volumes",
935
1002
  "docker",
@@ -22,7 +22,7 @@ from dstack._internal.core.errors import (
22
22
  from dstack._internal.core.models.configurations import SERVICE_HTTPS_DEFAULT, ServiceConfiguration
23
23
  from dstack._internal.core.models.gateways import GatewayConfiguration, GatewayStatus
24
24
  from dstack._internal.core.models.instances import SSHConnectionParams
25
- from dstack._internal.core.models.runs import Run, RunSpec, ServiceModelSpec, ServiceSpec
25
+ from dstack._internal.core.models.runs import JobSpec, Run, RunSpec, ServiceModelSpec, ServiceSpec
26
26
  from dstack._internal.server import settings
27
27
  from dstack._internal.server.models import GatewayModel, JobModel, ProjectModel, RunModel
28
28
  from dstack._internal.server.services.gateways import (
@@ -179,6 +179,7 @@ async def register_replica(
179
179
  async with conn.client() as client:
180
180
  await client.register_replica(
181
181
  run=run,
182
+ job_spec=JobSpec.__response__.parse_raw(job_model.job_spec_data),
182
183
  job_submission=job_submission,
183
184
  ssh_head_proxy=ssh_head_proxy,
184
185
  ssh_head_proxy_private_key=ssh_head_proxy_private_key,
@@ -44,7 +44,9 @@ async def list_users_for_user(
44
44
  session: AsyncSession,
45
45
  user: UserModel,
46
46
  ) -> List[User]:
47
- return await list_all_users(session=session)
47
+ if user.global_role == GlobalRole.ADMIN:
48
+ return await list_all_users(session=session)
49
+ return [user_model_to_user(user)]
48
50
 
49
51
 
50
52
  async def list_all_users(
@@ -223,7 +223,7 @@ async def create_volume(
223
223
  select(func.pg_advisory_xact_lock(string_to_lock_id(lock_namespace)))
224
224
  )
225
225
 
226
- lock, _ = get_locker().get_lockset(lock_namespace)
226
+ lock, _ = get_locker(get_db().dialect_name).get_lockset(lock_namespace)
227
227
  async with lock:
228
228
  if configuration.name is not None:
229
229
  volume_model = await get_project_volume_model_by_name(
@@ -262,7 +262,7 @@ async def delete_volumes(session: AsyncSession, project: ProjectModel, names: Li
262
262
  volumes_ids = sorted([v.id for v in volume_models])
263
263
  await session.commit()
264
264
  logger.info("Deleting volumes: %s", [v.name for v in volume_models])
265
- async with get_locker().lock_ctx(VolumeModel.__tablename__, volumes_ids):
265
+ async with get_locker(get_db().dialect_name).lock_ctx(VolumeModel.__tablename__, volumes_ids):
266
266
  # Refetch after lock
267
267
  res = await session.execute(
268
268
  select(VolumeModel)
@@ -401,6 +401,19 @@ def _validate_volume_configuration(configuration: VolumeConfiguration):
401
401
  if configuration.name is not None:
402
402
  validate_dstack_resource_name(configuration.name)
403
403
 
404
+ if configuration.volume_id is not None and configuration.auto_cleanup_duration is not None:
405
+ if (
406
+ isinstance(configuration.auto_cleanup_duration, int)
407
+ and configuration.auto_cleanup_duration > 0
408
+ ) or (
409
+ isinstance(configuration.auto_cleanup_duration, str)
410
+ and configuration.auto_cleanup_duration not in ("off", "-1")
411
+ ):
412
+ raise ServerClientError(
413
+ "External volumes (with volume_id) do not support auto_cleanup_duration. "
414
+ "Auto-cleanup only works for volumes created and managed by dstack."
415
+ )
416
+
404
417
 
405
418
  async def _delete_volume(session: AsyncSession, project: ProjectModel, volume_model: VolumeModel):
406
419
  volume = volume_model_to_volume(volume_model)
@@ -27,10 +27,27 @@ LOG_FORMAT = os.getenv("DSTACK_SERVER_LOG_FORMAT", "rich").lower()
27
27
  ALEMBIC_MIGRATIONS_LOCATION = os.getenv(
28
28
  "DSTACK_ALEMBIC_MIGRATIONS_LOCATION", "dstack._internal.server:migrations"
29
29
  )
30
- # Users may want to increase pool size to support more concurrent resources
31
- # if their db supports many connections
32
- DB_POOL_SIZE = int(os.getenv("DSTACK_DB_POOL_SIZE", 10))
33
- DB_MAX_OVERFLOW = int(os.getenv("DSTACK_DB_MAX_OVERFLOW", 10))
30
+
31
+ # Users may want to increase client pool size to support more concurrent resources
32
+ # if their db supports many connections.
33
+ DB_POOL_SIZE = int(os.getenv("DSTACK_DB_POOL_SIZE", 20))
34
+ DB_MAX_OVERFLOW = int(os.getenv("DSTACK_DB_MAX_OVERFLOW", 20))
35
+
36
+ # Scale the number of background processing tasks
37
+ # allowing to process more resources on one server replica.
38
+ # Not recommended to change on SQLite.
39
+ # DSTACK_DB_POOL_SIZE and DSTACK_DB_MAX_OVERFLOW
40
+ # must be increased proportionally.
41
+ SERVER_BACKGROUND_PROCESSING_FACTOR = int(
42
+ os.getenv("DSTACK_SERVER_BACKGROUND_PROCESSING_FACTOR", 1)
43
+ )
44
+
45
+ SERVER_BACKGROUND_PROCESSING_DISABLED = (
46
+ os.getenv("DSTACK_SERVER_BACKGROUND_PROCESSING_DISABLED") is not None
47
+ )
48
+ SERVER_BACKGROUND_PROCESSING_ENABLED = not SERVER_BACKGROUND_PROCESSING_DISABLED
49
+
50
+ SERVER_EXECUTOR_MAX_WORKERS = int(os.getenv("DSTACK_SERVER_EXECUTOR_MAX_WORKERS", 128))
34
51
 
35
52
  MAX_OFFERS_TRIED = int(os.getenv("DSTACK_SERVER_MAX_OFFERS_TRIED", 25))
36
53
 
@@ -97,7 +114,9 @@ SERVER_CODE_UPLOAD_LIMIT = int(os.getenv("DSTACK_SERVER_CODE_UPLOAD_LIMIT", 2 *
97
114
 
98
115
  SQL_ECHO_ENABLED = os.getenv("DSTACK_SQL_ECHO_ENABLED") is not None
99
116
 
117
+ SERVER_PROFILING_ENABLED = os.getenv("DSTACK_SERVER_PROFILING_ENABLED") is not None
118
+
100
119
  UPDATE_DEFAULT_PROJECT = os.getenv("DSTACK_UPDATE_DEFAULT_PROJECT") is not None
101
120
  DO_NOT_UPDATE_DEFAULT_PROJECT = os.getenv("DSTACK_DO_NOT_UPDATE_DEFAULT_PROJECT") is not None
102
- SKIP_GATEWAY_UPDATE = os.getenv("DSTACK_SKIP_GATEWAY_UPDATE", None) is not None
103
- ENABLE_PROMETHEUS_METRICS = os.getenv("DSTACK_ENABLE_PROMETHEUS_METRICS", None) is not None
121
+ SKIP_GATEWAY_UPDATE = os.getenv("DSTACK_SKIP_GATEWAY_UPDATE") is not None
122
+ ENABLE_PROMETHEUS_METRICS = os.getenv("DSTACK_ENABLE_PROMETHEUS_METRICS") is not None
@@ -1,3 +1,3 @@
1
1
  <!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>dstack</title><meta name="description" content="Get GPUs at the best prices and availability from a wide range of providers. No cloud account of your own is required.
2
2
  "/><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet"><meta name="og:title" content="dstack"><meta name="og:type" content="article"><meta name="og:image" content="/splash_thumbnail.png"><meta name="og:description" content="Get GPUs at the best prices and availability from a wide range of providers. No cloud account of your own is required.
3
- "><link rel="icon" type="image/x-icon" href="/assets/favicon.ico"><link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png"><link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png"><link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon-48x48.png"><link rel="manifest" href="/assets/manifest.webmanifest"><meta name="mobile-web-app-capable" content="yes"><meta name="theme-color" content="#fff"><meta name="application-name" content="dstackai"><link rel="apple-touch-icon" sizes="57x57" href="/assets/apple-touch-icon-57x57.png"><link rel="apple-touch-icon" sizes="60x60" href="/assets/apple-touch-icon-60x60.png"><link rel="apple-touch-icon" sizes="72x72" href="/assets/apple-touch-icon-72x72.png"><link rel="apple-touch-icon" sizes="76x76" href="/assets/apple-touch-icon-76x76.png"><link rel="apple-touch-icon" sizes="114x114" href="/assets/apple-touch-icon-114x114.png"><link rel="apple-touch-icon" sizes="120x120" href="/assets/apple-touch-icon-120x120.png"><link rel="apple-touch-icon" sizes="144x144" href="/assets/apple-touch-icon-144x144.png"><link rel="apple-touch-icon" sizes="152x152" href="/assets/apple-touch-icon-152x152.png"><link rel="apple-touch-icon" sizes="167x167" href="/assets/apple-touch-icon-167x167.png"><link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon-180x180.png"><link rel="apple-touch-icon" sizes="1024x1024" href="/assets/apple-touch-icon-1024x1024.png"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="apple-mobile-web-app-title" content="dstackai"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-640x1136.png"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1136x640.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-750x1334.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1334x750.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1125x2436.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2436x1125.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1170x2532.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2532x1170.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1179x2556.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2556x1179.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-828x1792.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1792x828.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2688.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2688x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2208.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2208x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1284x2778.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2778x1284.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1290x2796.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2796x1290.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1488x2266.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2266x1488.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1536x2048.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2048x1536.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1620x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1620.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1640x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1640.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2388.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2388x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2224.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2224x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-2048x2732.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2732x2048.png"><meta name="msapplication-TileColor" content="#fff"><meta name="msapplication-TileImage" content="/assets/mstile-144x144.png"><meta name="msapplication-config" content="/assets/browserconfig.xml"><link rel="yandex-tableau-widget" href="/assets/yandex-browser-manifest.json"><script defer="defer" src="/main-d151637af20f70b2e796.js"></script><link href="/main-d48635d8fe670d53961c.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div class="b-page-header" id="header"></div><div id="root"></div></body></html>
3
+ "><link rel="icon" type="image/x-icon" href="/assets/favicon.ico"><link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png"><link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png"><link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon-48x48.png"><link rel="manifest" href="/assets/manifest.webmanifest"><meta name="mobile-web-app-capable" content="yes"><meta name="theme-color" content="#fff"><meta name="application-name" content="dstackai"><link rel="apple-touch-icon" sizes="57x57" href="/assets/apple-touch-icon-57x57.png"><link rel="apple-touch-icon" sizes="60x60" href="/assets/apple-touch-icon-60x60.png"><link rel="apple-touch-icon" sizes="72x72" href="/assets/apple-touch-icon-72x72.png"><link rel="apple-touch-icon" sizes="76x76" href="/assets/apple-touch-icon-76x76.png"><link rel="apple-touch-icon" sizes="114x114" href="/assets/apple-touch-icon-114x114.png"><link rel="apple-touch-icon" sizes="120x120" href="/assets/apple-touch-icon-120x120.png"><link rel="apple-touch-icon" sizes="144x144" href="/assets/apple-touch-icon-144x144.png"><link rel="apple-touch-icon" sizes="152x152" href="/assets/apple-touch-icon-152x152.png"><link rel="apple-touch-icon" sizes="167x167" href="/assets/apple-touch-icon-167x167.png"><link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon-180x180.png"><link rel="apple-touch-icon" sizes="1024x1024" href="/assets/apple-touch-icon-1024x1024.png"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="apple-mobile-web-app-title" content="dstackai"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-640x1136.png"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1136x640.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-750x1334.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1334x750.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1125x2436.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2436x1125.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1170x2532.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2532x1170.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1179x2556.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2556x1179.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-828x1792.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1792x828.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2688.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2688x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2208.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2208x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1284x2778.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2778x1284.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1290x2796.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2796x1290.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1488x2266.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2266x1488.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1536x2048.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2048x1536.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1620x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1620.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1640x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1640.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2388.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2388x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2224.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2224x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-2048x2732.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2732x2048.png"><meta name="msapplication-TileColor" content="#fff"><meta name="msapplication-TileImage" content="/assets/mstile-144x144.png"><meta name="msapplication-config" content="/assets/browserconfig.xml"><link rel="yandex-tableau-widget" href="/assets/yandex-browser-manifest.json"><script defer="defer" src="/main-64f8273740c4b52c18f5.js"></script><link href="/main-d58fc0460cb0eae7cb5c.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div class="b-page-header" id="header"></div><div id="root"></div></body></html>