Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
df3187f
feat: stop button ui
Feudalman Dec 30, 2023
ac51263
feat: stop experiment
Feudalman Dec 30, 2023
286f3ab
fix: update time and duration
Feudalman Dec 30, 2023
8888677
feat: editor ui
Feudalman Dec 30, 2023
c8dac19
Squashed commit of the following:
Feudalman Dec 30, 2023
6749516
fix: overflow
Feudalman Dec 31, 2023
1a15b52
fix: some bug
Feudalman Dec 31, 2023
f5d2f79
feat: modal wrap
Feudalman Dec 31, 2023
c267c92
feat: modify project info
Feudalman Dec 31, 2023
3b81d9b
fix: experiment description
Feudalman Dec 31, 2023
cc02ec9
api: put experiment meta
swpfY Dec 31, 2023
1fc6f99
feat: modify experiment
Feudalman Dec 31, 2023
8356b68
fix: responsive data after modification
Feudalman Dec 31, 2023
9da5bc4
ui:fix some bug and add button icon
Feudalman Dec 31, 2023
e95d4d8
Merge branch 'main' into feature/modify-conf
Feudalman Dec 31, 2023
132453d
fix: some bug
Feudalman Dec 31, 2023
033fa30
ui: delete some button
Feudalman Dec 31, 2023
a55a223
Fixbug/table (#118)
Feudalman Dec 31, 2023
6a4f134
v0.1.2
SAKURA-CAT Dec 31, 2023
2e3b5ab
feat: stop button ui
Feudalman Dec 30, 2023
2a93c61
feat: stop experiment
Feudalman Dec 30, 2023
d7bfe87
feat: editor ui
Feudalman Dec 30, 2023
2fc3c8f
Squashed commit of the following:
Feudalman Dec 30, 2023
57a3deb
fix: overflow
Feudalman Dec 31, 2023
896229d
fix: some bug
Feudalman Dec 31, 2023
598855f
feat: modal wrap
Feudalman Dec 31, 2023
a9371f5
feat: modify project info
Feudalman Dec 31, 2023
722b987
fix: experiment description
Feudalman Dec 31, 2023
a9eafc2
api: put experiment meta
swpfY Dec 31, 2023
de7abc2
feat: modify experiment
Feudalman Dec 31, 2023
fcf0869
fix: responsive data after modification
Feudalman Dec 31, 2023
b519166
ui:fix some bug and add button icon
Feudalman Dec 31, 2023
5fdd305
fix: some bug
Feudalman Dec 31, 2023
8528499
ui: delete some button
Feudalman Dec 31, 2023
745d977
fix: rebase
Feudalman Jan 2, 2024
a8d094d
Merge branch 'feature/modify-conf' of github.com:SwanHubX/swanlab int…
Feudalman Jan 2, 2024
b97b2be
fix: pull origin
Feudalman Jan 2, 2024
c99d1e1
fix:home sider info modified
Feudalman Jan 2, 2024
be2cf03
refactor:decoupling
Feudalman Jan 2, 2024
c9cf78a
fix: clear error message
Feudalman Jan 2, 2024
84fad67
docs:explanatory note
Feudalman Jan 2, 2024
b0f029a
test: fix bug
Feudalman Jan 2, 2024
fb7b666
fix:line number
Feudalman Jan 2, 2024
057e021
fix:no number in first line
Feudalman Jan 2, 2024
a566ebd
fix:init message
Feudalman Jan 2, 2024
258b0dd
docs:previous message
Feudalman Jan 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions swanlab/log/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,13 @@ def getLevel(self):


class Consoler(sys.stdout.__class__, LeverCtl):
# 记录日志行数
__sum = 0

# 上一次输入到标准输出流的消息,用于判断前一个是否换行,即当前行是否为新一行的开头
# 只有第一次输入时为None
__previous_message = None

def __init__(self):
super().__init__(sys.stdout.buffer)
self.original_stdout = sys.stdout # 保存原始的 sys.stdout
Expand Down Expand Up @@ -143,12 +148,35 @@ def wrapper(self, *args, **kwargs):
def write(self, message):
self.original_stdout.write(message) # 先写入原始 sys.stdout,即输出到控制台
self.original_stdout.flush()
if self.checkLevel(message):
if not message == "\n":

# 检查记录等级,高于或等于写入等级即可写入日志文件
if not self.checkLevel(message):
return

if self.__previous_message is None:
self.__sum += 1
message = str(self.__sum) + " " + FONT.clear(message)
self.__previous_message = ""
# 如果直接就是换行,什么都不做
elif message == "\n":
pass
# 如果以换行符结尾,那么是一个正常的 print 打印,正常处理和输出,单独占一行
elif message.endswith("\n"):
if self.__previous_message.endswith("\n"):
self.__sum += 1
message = str(self.__sum) + " " + FONT.clear(message)
else:
message = FONT.clear(message)
# 如果是一个不带换行的字符串,需要判断一下前一个message是否带有换行
else:
if self.__previous_message.endswith("\n"):
self.__sum += 1
message = str(self.__sum) + " " + FONT.clear(message)
self.console.write(message)
self.console.flush()
else:
message = FONT.clear(message)
self.__previous_message = FONT.clear(message)
self.console.write(message)
self.console.flush()

def setLevel(self, level):
return super().setLevel(level)
Expand Down
2 changes: 1 addition & 1 deletion swanlab/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "swanlab",
"version": "0.1.1",
"version": "0.1.2",
"description": "",
"python": "true"
}
46 changes: 43 additions & 3 deletions swanlab/server/api/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
实验相关api,前缀:/experiment
"""
from datetime import datetime
from fastapi import APIRouter
from fastapi import APIRouter, Request
from ..module.resp import SUCCESS_200, NOT_FOUND_404
from ...env import swc
import os
Expand All @@ -17,9 +17,10 @@

# from ...utils import create_time
from urllib.parse import quote, unquote # 转码路径参数
from typing import List, Dict
from typing import List, Dict, Optional
from ...utils import get_a_lock, create_time
from ...log import swanlog as swl
from pydantic import BaseModel

router = APIRouter()

Expand Down Expand Up @@ -332,7 +333,7 @@ async def get_experimet_charts(experiment_id: int):


@router.get("/{experiment_id}/stop")
async def get_stop_charts(experiment_id: int):
async def stop_experiment(experiment_id: int):
"""停止实验

Parameters
Expand All @@ -354,3 +355,42 @@ async def get_stop_charts(experiment_id: int):
with get_a_lock(config_path, "w") as f:
ujson.dump(config, f, ensure_ascii=False, indent=4)
return SUCCESS_200({"update_time": create_time()})


@router.patch("/{experiment_id}/update")
async def update_experiment_config(experiment_id: int, request: Request):
"""修改实验的元信息

Parameters
----------
experiment_id : int
实验id
body : Body
name: str
实验名称
description: str
实验描述

Returns
-------
object
"""
body: dict = await request.json()
project_config_path = os.path.join(swc.root, "project.json")
with open(project_config_path, "r") as f:
project = ujson.load(f)
experiment = __find_experiment(experiment_id)
experiment_index = experiment["index"] - 1
# 修改实验名称
if not experiment["name"] == body["name"]:
project["experiments"][experiment_index]["name"] = body["name"]
# 修改实验目录名
old_path = os.path.join(swc.root, experiment["name"])
new_path = os.path.join(swc.root, body["name"])
os.rename(old_path, new_path)
# 修改实验描述
if not experiment["description"] == body["description"]:
project["experiments"][experiment_index]["description"] = body["description"]
with get_a_lock(project_config_path, "w") as f:
ujson.dump(project, f, indent=4, ensure_ascii=False)
return SUCCESS_200({"experiment": project["experiments"][experiment_index]})
29 changes: 27 additions & 2 deletions swanlab/server/api/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
项目相关的api,前缀:/project
"""
import os
from fastapi import APIRouter
from fastapi import APIRouter, Request

from ...utils import get_a_lock, create_time
from ..module.resp import SUCCESS_200, DATA_ERROR_500
from ..module import PT
from swanlab.env import swc
Expand Down Expand Up @@ -55,7 +57,7 @@ async def summaries(experiment_names: str):
tags = [f for f in os.listdir(experiment_path) if os.path.isdir(os.path.join(experiment_path, f))]
experiment_summaries = {}
for tag in tags:
if not tag in column:
if not unquote(tag) in column:
column.append(unquote(tag))
tag_path = os.path.join(experiment_path, tag)
logs = sorted([item for item in os.listdir(tag_path) if item != "_summary.json"])
Expand All @@ -68,3 +70,26 @@ async def summaries(experiment_names: str):
experiment_summaries[unquote(tag)] = tag_data["data"][-1]["data"]
data[name] = experiment_summaries
return SUCCESS_200(data={"tags": column, "summaries": data})


@router.patch("/update")
async def update(request: Request):
body = await request.json()
file_path = os.path.join(swc.root, "project.json")
with open(file_path, "r") as f:
project = ujson.load(f)
# 检查名字
if "name" in project and project["name"] == body["name"]:
pass
else:
project.update({"name": body["name"]})
# 检查描述
if "description" in project and project["description"] == body["description"]:
pass
else:
project.update({"description": body["description"]})
# project["update_time"] = create_time()
# 写入文件
with get_a_lock(file_path, "w") as f:
ujson.dump(project, f, indent=4, ensure_ascii=False)
return SUCCESS_200({"project": project})
2 changes: 1 addition & 1 deletion test/create_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
loss = 2**-epoch + random.random() / epoch + offset
print(f"epoch={epoch}, accuracy={acc}, loss={loss}")
sw.log({"loss": loss, "accuracy": acc})
sw.log({"accuracy2": f"{acc}", "test/loss2": f"{loss}"}, step=epochs - epoch)
# sw.log({"accuracy2": f"{acc}", "test/loss2": f"{loss}"}, step=epochs - epoch)
# sw.log({"loss3": loss, "accuracy3": acc}, step=1)

time.sleep(0.5)
Expand Down
13 changes: 13 additions & 0 deletions test/test_bug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import swanlab

# Initialize
swanlab.init()

for epoch in range(1, 20):
print("epoch", epoch, "|")
# Tracking index: `epoch`
swanlab.log({"epoch": epoch})

print("end")
print("===========", end="")
print("--------------------", end="")
2 changes: 1 addition & 1 deletion vue/src/assets/iconfont.js

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion vue/src/components/HomeSiderBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@
v-for="experiment in projectStore.experiments"
:key="experiment.experiment_id"
:to="getExperimentRouter(experiment)"
:title="experiment.name"
class="flex-shrink-0"
active-class="active-router"
>
<div class="w-4 h-4 rounded-full mr-3" :style="{ backgroundColor: experiment.color }"></div>
{{ experiment.name }}
<span class="truncate">{{ experiment.name }}</span>
</RouterLink>
</div>
<div class="border-t border-default h-20">
Expand Down
12 changes: 11 additions & 1 deletion vue/src/components/SLLoading.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<template>
<div class="relative flex items-center justify-center rounded-full overflow-hidden w-8 h-8 animate-spin">
<div
class="relative flex items-center justify-center rounded-full overflow-hidden animate-spin"
:class="size ? `w-${size} h-${size}` : 'w-8 h-8'"
>
<div class="absolute w-full h-full rounded-full border-4 border-t-slate-500 border-slate-200"></div>
<div class="w-3 h-3 z-10 rounded-full bg-slate-400"></div>
</div>
Expand All @@ -11,6 +14,13 @@
* @file: SLLoading.vue
* @since: 2023-12-12 17:35:53
**/

defineProps({
size: {
type: String,
default: '8'
}
})
</script>

<style lang="scss" scoped></style>
2 changes: 1 addition & 1 deletion vue/src/components/SLStatusLabel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const handleClick = () => {

<style lang="scss" scoped>
.sl-status-label {
@apply px-3 py-1 rounded-full text-sm text-center flex items-center;
@apply px-3 py-1 rounded-full text-sm text-center;
}
.stoped {
@apply bg-negative-dimmest text-negative-default;
Expand Down
68 changes: 68 additions & 0 deletions vue/src/components/config-editor/ConfigEditor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<template>
<button
class="flex gap-2 items-center px-2 py-1 border rounded-lg transition-all hover:bg-primary-default hover:text-white"
@click="() => (showModal = true)"
>
<SLIcon icon="modify" class="w-4 h-5" />
{{ $t('common.config-editor.button') }}
</button>
<SLModal class="px-10 py-6" max-w="550" v-model="showModal">
<!-- 如果后续项目和实验的可修改内容发生改变,这样容易重构一些 -->
<EditorWrap :type="type" @confirm="confirm"></EditorWrap>
</SLModal>
</template>

<script setup>
/**
* @description: 编辑项目/实验信息
* 目前文字部分只适配了项目和实验,如果之后需要使用这套弹窗模板修改别的部分的信息
* 只需要增加i18n的文字适配,点击后的操作使用 modify 这个 emit 暴露了出去
* @file: InfoEditor.vue
* @since: 2023-12-30 23:55:02
**/
import { ref } from 'vue'
import SLModal from '../SLModal.vue'
import EditorWrap from './EditorWrap.vue'
import SLIcon from '../SLIcon.vue'

// 是否展示弹窗
const showModal = ref(false)

defineProps({
type: {
type: String,
validator: (value) => {
// 目前type只适配了project和experiment两种模式
return ['project', 'experiment'].includes(value)
}
}
})

const emits = defineEmits(['modify'])

/**
* 点击修改后触发的确认函数
* @param {obj} newV 需要修改的值
* @param.name {string}
* @param.description {string}
*
* 这里使用了promise,是因为 emit 触发的 modify 很可能是异步的,而 emit 触发是同步的
* 该组件在点击确认按钮后会有两个缓冲操作:
* 1. 按钮显示处理中
* 2. 弹窗并不立即消失
* 可以看到,触发 modify 的时候会传递两个参数,一个是修改后的值,一个是回调函数
* 只有在外部的处理函数调用回调的同时,才会结束缓冲操作
*
* 这里为什么要用promise,是因为confirm也接受了一个回调函数,这个回调函数是子组件中传递的,负责取消按钮的处理状态
*/
const confirm = async (newV) => {
new Promise((resolve) => {
emits('modify', newV, () => {
showModal.value = false
resolve()
})
})
}
</script>

<style lang="scss" scoped></style>
Loading