Merge pull request #1384 from wanyaoqi/feature/wyq/live-migrate-filter-cpu-model

feature: live migrate filter cpu model
This commit is contained in:
yunion-ci-robot
2019-06-27 16:58:50 +08:00
committed by GitHub
19 changed files with 169 additions and 72 deletions
+6 -3
View File
@@ -52,9 +52,6 @@ type ServerConfig struct {
Name string `json:"name"`
GuestStatus string `json:"guest_status"`
// HostId used by migrate
HostId string `json:"host_id"`
// DEPRECATED
Metadata map[string]string `json:"__meta__"`
ForGuests []*ForGuest `json:"for_guests"`
@@ -70,6 +67,12 @@ type ScheduleInput struct {
ScheduleBaseConfig
ServerConfig
// HostId used by migrate
HostId string `json:"host_id"`
LiveMigrate bool `json:"live_migrate"`
CpuDesc string `json:"cpu_desc"`
CpuMicrocode string `json:"cpu_microcode"`
}
func (input ScheduleInput) ToConditionInput() *jsonutils.JSONDict {
+1
View File
@@ -190,6 +190,7 @@ func (manager *SModelBaseManager) InitializeData() error {
}
func (manager *SModelBaseManager) ListItemExportKeys(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
q = q.AppendField(q.QueryFields()...)
return q, nil
}
+5 -4
View File
@@ -39,10 +39,11 @@ func (info *SDMISystemInfo) ToIPMISystemInfo() *SIPMISystemInfo {
}
type SCPUInfo struct {
Count int `json:"count"`
Model string `json:"desc"`
Freq int `json:"freq"`
Cache int `json:"cache"`
Count int `json:"count"`
Model string `json:"desc"`
Freq int `json:"freq"`
Cache int `json:"cache"`
Microcode string `json:"microcode"`
}
type SDMICPUInfo struct {
+1 -1
View File
@@ -2728,7 +2728,7 @@ func (self *SGuest) PerformBlockStreamFailed(ctx context.Context, userCred mccli
if len(self.BackupHostId) > 0 {
self.SetMetadata(ctx, "__mirror_job_status", "failed", userCred)
}
if self.Status == api.VM_BLOCK_STREAM {
if self.Status == api.VM_BLOCK_STREAM || self.Status == api.VM_RUNNING {
reason, _ := data.GetString("reason")
return nil, self.SetStatus(userCred, api.VM_BLOCK_STREAM_FAIL, reason)
}
+7 -2
View File
@@ -1482,9 +1482,14 @@ func (self *SGuest) GetExtraDetails(ctx context.Context, userCred mcclient.Token
}
func (manager *SGuestManager) ListItemExportKeys(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SModelBaseManager.ListItemExportKeys(ctx, q, userCred, query)
if err != nil {
return nil, err
}
exportKeys, _ := query.GetString("export_keys")
keys := strings.Split(exportKeys, ",")
// guest_id as filter key
if utils.IsInStringArray("ips", keys) {
guestIpsQuery := GuestnetworkManager.Query("guest_id").GroupBy("guest_id")
@@ -4150,7 +4155,7 @@ func (self *SGuest) ToSchedDesc() *schedapi.ScheduleInput {
self.FillDiskSchedDesc(config.ServerConfigs)
self.FillNetSchedDesc(config.ServerConfigs)
if len(self.HostId) > 0 && regutils.MatchUUID(self.HostId) {
config.HostId = self.HostId
desc.HostId = self.HostId
}
config.Project = self.ProjectId
/*tags := self.GetApptags()
+8 -7
View File
@@ -157,13 +157,14 @@ type SHost struct {
SysInfo jsonutils.JSONObject `nullable:"true" search:"admin" list:"admin" update:"admin" create:"admin_optional"` // Column(JSONEncodedDict, nullable=True)
SN string `width:"128" charset:"ascii" nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(VARCHAR(128, charset='ascii'), nullable=True)
CpuCount int `nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(TINYINT, nullable=True) # cpu count
NodeCount int8 `nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(TINYINT, nullable=True)
CpuDesc string `width:"64" charset:"ascii" nullable:"true" get:"admin" update:"admin" create:"admin_optional"` // Column(VARCHAR(64, charset='ascii'), nullable=True)
CpuMhz int `nullable:"true" get:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True) # cpu MHz
CpuCache int `nullable:"true" get:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True) # cpu Cache in KB
CpuReserved int `nullable:"true" default:"0" list:"admin" update:"admin" create:"admin_optional"` // Column(TINYINT, nullable=True, default=0)
CpuCmtbound float32 `nullable:"true" default:"8" list:"admin" update:"admin" create:"admin_optional"` // = Column(Float, nullable=True)
CpuCount int `nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(TINYINT, nullable=True) # cpu count
NodeCount int8 `nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(TINYINT, nullable=True)
CpuDesc string `width:"64" charset:"ascii" nullable:"true" get:"admin" update:"admin" create:"admin_optional"` // Column(VARCHAR(64, charset='ascii'), nullable=True)
CpuMhz int `nullable:"true" get:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True) # cpu MHz
CpuCache int `nullable:"true" get:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True) # cpu Cache in KB
CpuReserved int `nullable:"true" default:"0" list:"admin" update:"admin" create:"admin_optional"` // Column(TINYINT, nullable=True, default=0)
CpuCmtbound float32 `nullable:"true" default:"8" list:"admin" update:"admin" create:"admin_optional"` // = Column(Float, nullable=True)
CpuMicrocode string `width:"64" charset:"ascii" nullable:"true" get:"admin" update:"admin" create:"admin_optional"`
MemSize int `nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True) # memory size in MB
MemReserved int `nullable:"true" default:"0" list:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True, default=0) # memory reserved in MB
+5
View File
@@ -192,6 +192,11 @@ func (self *GuestStartAndSyncToBackupTask) OnRequestSyncToBackup(ctx context.Con
self.SetStageComplete(ctx, nil)
}
func (self *GuestStartAndSyncToBackupTask) OnRequestSyncToBackupFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
guest.SetStatus(self.UserCred, api.VM_BLOCK_STREAM_FAIL, "OnSyncToBackup")
self.SetStageFailed(ctx, data.String())
}
type GuestCreateBackupTask struct {
SSchedTask
}
@@ -56,6 +56,13 @@ func (self *GuestMigrateTask) GetSchedParams() (*schedapi.ScheduleInput, error)
preferHostId, _ := self.Params.GetString("prefer_host_id")
schedDesc.ServerConfig.PreferHost = preferHostId
}
guestStatus, _ := self.Params.GetString("guest_status")
if !jsonutils.QueryBoolean(self.Params, "is_rescue_mode", false) && (guestStatus == api.VM_RUNNING || guestStatus == api.VM_SUSPEND) {
schedDesc.LiveMigrate = true
host := guest.GetHost()
schedDesc.CpuDesc = host.CpuDesc
schedDesc.CpuMicrocode = host.CpuMicrocode
}
return schedDesc, nil
}
+1 -1
View File
@@ -62,7 +62,7 @@ func (self *GuestSyncstatusTask) OnGetStatusSucc(ctx context.Context, guest *mod
statusStr = api.VM_SUSPEND
case cloudprovider.CloudVMStatusStopped:
statusStr = api.VM_READY
case api.VM_BLOCK_STREAM: /// XXX ???
case api.VM_BLOCK_STREAM, api.VM_BLOCK_STREAM_FAIL:
break
default:
statusStr = api.VM_UNKNOWN
+16 -7
View File
@@ -29,6 +29,7 @@ import (
"yunion.io/x/log"
"yunion.io/x/pkg/util/regutils"
"yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/cloudcommon/sshkeys"
"yunion.io/x/onecloud/pkg/hostman/guestfs"
@@ -47,11 +48,12 @@ import (
const (
VNC_PORT_BASE = 5900
GUEST_RUNNING = "running"
GUEST_BLOCK_STREAM = "block_stream"
GUEST_SUSPEND = "suspend"
GUSET_STOPPED = "stopped"
GUEST_NOT_FOUND = "notfound"
GUEST_RUNNING = compute.VM_RUNNING
GUEST_BLOCK_STREAM = compute.VM_BLOCK_STREAM
GUEST_BLOCK_STREAM_FAIL = compute.VM_BLOCK_STREAM_FAIL
GUEST_SUSPEND = compute.VM_SUSPEND
GUSET_STOPPED = "stopped"
GUEST_NOT_FOUND = "notfound"
)
type SGuestManager struct {
@@ -360,8 +362,15 @@ func (m *SGuestManager) Status(sid string) string {
func (m *SGuestManager) GetStatus(sid string) string {
if guest, ok := m.Servers[sid]; ok {
if guest.Monitor != nil && guest.IsMaster() && !guest.IsMirrorJobSucc() {
return GUEST_BLOCK_STREAM
if guest.IsRunning() && guest.Monitor != nil && guest.IsMaster() {
mirrorStatus := guest.MirrorJobStatus()
if mirrorStatus.InProcess() {
return GUEST_BLOCK_STREAM
} else if mirrorStatus.IsFailed() {
timeutils2.AddTimeout(1*time.Second,
func() { guest.SyncMirrorJobFailed("Block job missing") })
return GUEST_BLOCK_STREAM_FAIL
}
}
if guest.IsRunning() {
return GUEST_RUNNING
+66 -32
View File
@@ -30,7 +30,7 @@ import (
"yunion.io/x/pkg/util/seclib"
"yunion.io/x/pkg/utils"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/appctx"
"yunion.io/x/onecloud/pkg/hostman/guestfs"
"yunion.io/x/onecloud/pkg/hostman/hostinfo/hostbridge"
@@ -329,7 +329,9 @@ func (s *SKVMGuestInstance) ImportServer(pendingDelete bool) {
action = "suspend"
}
log.Infof("%s is %s, pending_delete=%t", s.GetName(), action, pendingDelete)
s.SyncStatus()
if !s.IsSlave() {
s.SyncStatus()
}
}
}
@@ -445,24 +447,19 @@ func (s *SKVMGuestInstance) onReceiveQMPEvent(event *monitor.Event) {
if itype, ok := event.Data["type"]; ok {
stype, _ := itype.(string)
if stype == "mirror" {
if s.IsMirrorJobSucc() {
_, err := hostutils.UpdateServerStatus(context.Background(), s.GetId(), "running")
mirrorStatus := s.MirrorJobStatus()
if mirrorStatus.IsSucc() {
_, err := hostutils.UpdateServerStatus(context.Background(), s.GetId(), compute.VM_RUNNING)
if err != nil {
log.Errorf("onReceiveQMPEvent update server status error: %s", err)
}
} else if mirrorStatus.IsFailed() {
s.SyncMirrorJobFailed("Block job missing")
}
}
}
case event.Event == `"BLOCK_JOB_ERROR"`:
params := jsonutils.NewDict()
params.Set("reason", jsonutils.NewString("BLOCK_JOB_ERROR"))
_, err := modules.Servers.PerformAction(
hostutils.GetComputeSession(context.Background()),
s.GetId(), "block-stream-failed", params,
)
if err != nil {
log.Errorf("Server %s perform block-stream-failed got error %s", s.GetId(), err)
}
s.SyncMirrorJobFailed("BLOCK_JOB_ERROR")
case event.Event == `"GUEST_PANICKED"`:
// qemu runc state event source qemu/src/qapi/run-state.json
params := jsonutils.NewDict()
@@ -486,6 +483,18 @@ func (s *SKVMGuestInstance) onReceiveQMPEvent(event *monitor.Event) {
}
}
func (s *SKVMGuestInstance) SyncMirrorJobFailed(reason string) {
params := jsonutils.NewDict()
params.Set("reason", jsonutils.NewString(reason))
_, err := modules.Servers.PerformAction(
hostutils.GetComputeSession(context.Background()),
s.GetId(), "block-stream-failed", params,
)
if err != nil {
log.Errorf("Server %s perform block-stream-failed got error %s", s.GetId(), err)
}
}
func (s *SKVMGuestInstance) onMonitorConnected(ctx context.Context) {
log.Infof("Monitor connected ...")
s.Monitor.GetVersion(func(v string) {
@@ -507,7 +516,7 @@ func (s *SKVMGuestInstance) onGetQemuVersion(ctx context.Context, version string
}
} else if jsonutils.QueryBoolean(s.Desc, "is_master", false) {
s.startDiskBackupMirror(ctx)
if ctx != nil {
if ctx != nil && len(appctx.AppContextTaskId(ctx)) > 0 {
s.DoResumeTask(ctx)
} else {
if options.HostOptions.SetVncPassword {
@@ -533,9 +542,13 @@ func (s *SKVMGuestInstance) onMonitorDisConnect(err error) {
func (s *SKVMGuestInstance) startDiskBackupMirror(ctx context.Context) {
if ctx == nil || len(appctx.AppContextTaskId(ctx)) == 0 {
status := "running"
if !s.IsMirrorJobSucc() {
status = "block_stream"
status := compute.VM_RUNNING
mirrorStatus := s.MirrorJobStatus()
if mirrorStatus.InProcess() {
status = compute.VM_BLOCK_STREAM
} else if mirrorStatus.IsFailed() {
status = compute.VM_BLOCK_STREAM_FAIL
s.SyncMirrorJobFailed("mirror job missing")
}
hostutils.UpdateServerStatus(context.Background(), s.GetId(), status)
} else {
@@ -586,24 +599,39 @@ func (s *SKVMGuestInstance) IsMaster() bool {
return jsonutils.QueryBoolean(s.Desc, "is_master", false)
}
func (s *SKVMGuestInstance) IsSlave() bool {
return jsonutils.QueryBoolean(s.Desc, "is_slave", false)
}
func (s *SKVMGuestInstance) DiskCount() int {
disks, _ := s.Desc.GetArray("disks")
return len(disks)
}
func (s *SKVMGuestInstance) IsMirrorJobSucc() bool {
type MirrorJob int
func (ms MirrorJob) IsSucc() bool {
return ms == 1
}
func (ms MirrorJob) IsFailed() bool {
return ms == -1
}
func (ms MirrorJob) InProcess() bool {
return ms == 0
}
func (s *SKVMGuestInstance) MirrorJobStatus() MirrorJob {
res := make(chan *jsonutils.JSONArray)
s.Monitor.GetBlockJobs(func(jobs *jsonutils.JSONArray) {
res <- jobs
})
select {
case <-time.After(time.Second * 3):
return false
return 0
case v := <-res:
if v != nil {
if len(v.Value()) == 0 {
return true
}
if v != nil && v.Length() >= s.DiskCount() {
mirrorSuccCount := 0
for _, val := range v.Value() {
jobType, _ := val.GetString("type")
@@ -612,9 +640,13 @@ func (s *SKVMGuestInstance) IsMirrorJobSucc() bool {
mirrorSuccCount += 1
}
}
return mirrorSuccCount == s.DiskCount()
if mirrorSuccCount == s.DiskCount() {
return 1
} else {
return 0
}
} else {
return false
return -1
}
}
}
@@ -696,12 +728,14 @@ func (s *SKVMGuestInstance) SyncStatus() {
}
func (s *SKVMGuestInstance) CheckBlockOrRunning(jobs int) {
var status = "running"
var status = compute.VM_RUNNING
if jobs > 0 {
if s.IsMaster() && s.IsMirrorJobSucc() {
status = "running"
} else {
status = "block_stream"
mirrorStatus := s.MirrorJobStatus()
if mirrorStatus.InProcess() {
status = compute.VM_BLOCK_STREAM
} else if mirrorStatus.IsFailed() {
status = compute.VM_BLOCK_STREAM_FAIL
s.SyncMirrorJobFailed("Block job missing")
}
}
_, err := hostutils.UpdateServerStatus(context.Background(), s.Id, status)
@@ -811,7 +845,7 @@ func (s *SKVMGuestInstance) delTmpDisks(ctx context.Context, migrated bool) erro
if disk.Contains("path") {
diskPath, _ := disk.GetString("path")
d := storageman.GetManager().GetDiskByPath(diskPath)
if d != nil && d.GetType() == api.STORAGE_LOCAL && migrated {
if d != nil && d.GetType() == compute.STORAGE_LOCAL && migrated {
if err := d.DeleteAllSnapshot(); err != nil {
log.Errorln(err)
return err
@@ -1365,7 +1399,7 @@ func (s *SKVMGuestInstance) PrepareMigrate(liveMigrage bool) (*jsonutils.JSONDic
if disk.Contains("path") {
diskPath, _ := disk.GetString("path")
d := storageman.GetManager().GetDiskByPath(diskPath)
if d.GetType() == api.STORAGE_LOCAL {
if d.GetType() == compute.STORAGE_LOCAL {
back, err := d.PrepareMigrate(liveMigrage)
if err != nil {
return nil, err
+3
View File
@@ -742,6 +742,7 @@ func (h *SHostInfo) updateHostRecord(hostId string) {
content.Set("cpu_count", jsonutils.NewInt(int64(h.Cpu.cpuInfoProc.Count)))
content.Set("node_count", jsonutils.NewInt(int64(h.Cpu.cpuInfoDmi.Nodes)))
content.Set("cpu_desc", jsonutils.NewString(h.Cpu.cpuInfoProc.Model))
content.Set("cpu_microcode", jsonutils.NewString(h.Cpu.cpuInfoProc.Microcode))
content.Set("cpu_mhz", jsonutils.NewInt(int64(h.Cpu.cpuInfoProc.Freq)))
content.Set("cpu_cache", jsonutils.NewInt(int64(h.Cpu.cpuInfoProc.Cache)))
content.Set("mem_size", jsonutils.NewInt(int64(h.Mem.MemInfo.Total)))
@@ -1353,6 +1354,8 @@ func NewHostInfo() (*SHostInfo, error) {
res.Cpu = cpu
}
log.Infof("CPU Model %s Microcode %s", cpu.cpuInfoProc.Model, cpu.cpuInfoProc.Microcode)
mem, err := DetectMemoryInfo()
if err != nil {
return nil, err
+2
View File
@@ -311,6 +311,8 @@ type SSysInfo struct {
QemuVersion string `json:"qemu_version"`
OvsVersion string `json:"ovs_version"`
KvmModule string `json:"kvm_module"`
CpuModelName string `json:"cpu_model_name"`
CpuMicrocode string `json:"cpu_microcode"`
StorageType string `json:"storage_type"`
}
+1 -1
View File
@@ -58,7 +58,7 @@ func (self *ImageConvertTask) OnInit(ctx context.Context, obj db.IStandaloneMode
kwargs := jsonutils.NewDict()
kwargs.Set("name", jsonutils.NewString(image.GetName()))
osType, err := models.ImagePropertyManager.GetProperty(image.Id, api.IMAGE_OS_TYPE)
if err != nil {
if err == nil {
kwargs.Set("os_type", jsonutils.NewString(osType.Value))
}
notifyclient.SystemNotify(notify.NotifyPriorityNormal, notifyclient.IMAGE_ACTIVED, kwargs)
+10 -8
View File
@@ -31,14 +31,16 @@ const (
ErrNoEnoughAvailableGPUs = `no enough available GPUs`
ErrNotSupportNest = `nested function not supported`
ErrRequireMvs = `require mvs`
ErrRequireNoMvs = `require not mvs`
ErrHostIsSpecifiedForMigration = `host_id specified for migration`
ErrMoreThanOneSizeUnspecificSplit = `more than 1 size unspecific split`
ErrNoMoreSpaceForUnspecificSplit = `no more space for an unspecific split`
ErrSubtotalOfSplitExceedsDiskSize = `subtotal of split exceeds disk size`
ErrBaremetalHasAlreadyBeenOccupied = `baremetal has already been occupied`
ErrPrepaidHostOccupied = `prepaid host occupied`
ErrRequireMvs = `require mvs`
ErrRequireNoMvs = `require not mvs`
ErrHostIsSpecifiedForMigration = `host_id specified for migration`
ErrHostCpuModelIsNotMatchForLiveMigrate = `host cpu mode not match for live migrate`
ErrHostCpuMicrocodeNotMatchForLiveMigrate = `host cpu microcode not match for live migrate`
ErrMoreThanOneSizeUnspecificSplit = `more than 1 size unspecific split`
ErrNoMoreSpaceForUnspecificSplit = `no more space for an unspecific split`
ErrSubtotalOfSplitExceedsDiskSize = `subtotal of split exceeds disk size`
ErrBaremetalHasAlreadyBeenOccupied = `baremetal has already been occupied`
ErrPrepaidHostOccupied = `prepaid host occupied`
ErrUnknown = `unknown error`
)
@@ -38,9 +38,23 @@ func (p *MigratePredicate) PreExecute(u *core.Unit, cs []core.Candidater) (bool,
func (p *MigratePredicate) Execute(u *core.Unit, c core.Candidater) (bool, []core.PredicateFailureReason, error) {
h := predicates.NewPredicateHelper(p, u, c)
schedData := u.SchedData()
if u.SchedData().HostId == c.IndexKey() {
if schedData.HostId == c.IndexKey() {
h.Exclude(predicates.ErrHostIsSpecifiedForMigration)
return h.GetResult()
}
if schedData.LiveMigrate {
host := c.Getter().Host()
if schedData.CpuDesc != host.CpuDesc {
h.Exclude(predicates.ErrHostCpuModelIsNotMatchForLiveMigrate)
return h.GetResult()
}
if len(schedData.CpuMicrocode) > 0 && schedData.CpuMicrocode != host.CpuMicrocode {
h.Exclude(predicates.ErrHostCpuMicrocodeNotMatchForLiveMigrate)
return h.GetResult()
}
}
return h.GetResult()
+4
View File
@@ -63,6 +63,10 @@ func (b baseHostGetter) Zone() *computemodels.SZone {
return b.h.Zone
}
func (b baseHostGetter) Host() *computemodels.SHost {
return b.h.SHost
}
func (b baseHostGetter) Cloudprovider() *computemodels.SCloudprovider {
return b.h.Cloudprovider
}
+1
View File
@@ -56,6 +56,7 @@ type CandidatePropertyGetter interface {
Id() string
Name() string
Zone() *computemodels.SZone
Host() *computemodels.SHost
Cloudprovider() *computemodels.SCloudprovider
IsPublic() bool
DomainId() string
+10 -5
View File
@@ -78,9 +78,10 @@ func ParseDMISysinfo(lines []string) (*types.SDMISystemInfo, error) {
func ParseCPUInfo(lines []string) (*types.SCPUInfo, error) {
cnt := 0
var (
model string
freq string
cache string
model string
freq string
cache string
microcode string
)
lv := func(line string) string {
return strings.TrimSpace(line[strings.Index(line, ":")+1:])
@@ -95,6 +96,9 @@ func ParseCPUInfo(lines []string) (*types.SCPUInfo, error) {
if len(cache) == 0 && strings.HasPrefix(line, "cache size") {
cache = strings.TrimSpace(line[strings.Index(line, ":")+1 : strings.Index(line, " KB")])
}
if len(microcode) == 0 && strings.HasPrefix(line, "microcode") {
microcode = lv(line)
}
if strings.HasPrefix(line, "processor") {
cnt += 1
}
@@ -110,8 +114,9 @@ func ParseCPUInfo(lines []string) (*types.SCPUInfo, error) {
}
model = strings.TrimSpace(model)
info := &types.SCPUInfo{
Count: cnt,
Model: model,
Count: cnt,
Model: model,
Microcode: microcode,
}
info.Cache, _ = strconv.Atoi(cache)
freqF, _ := strconv.ParseFloat(freq, 32)