17.2 ksm源码讲解

news2024/11/15 8:48:38

本节重点介绍 :

  • k8s资源对象的 buildStores构造函数注入MetricFamilies
  • k8s client-go 之 Reflector
    • listAndWatch 方法
    • watchHandler 监听更新,调用add等action

架构图总结

image.png

项目地址

  • 地址

go get

 go get -v  -d  k8s.io/kube-state-metrics/v2@v2.1.1

源码分析

main.go 中的主流程

  • 位置 D:\go_path\pkg\mod\k8s.io\kube-state-metrics\v2@v2.1.1\main.go

初始化store.Builder

	storeBuilder := store.NewBuilder()

注册registery

	ksmMetricsRegistry := prometheus.NewRegistry()
	ksmMetricsRegistry.MustRegister(version.NewCollector("kube_state_metrics"))
	durationVec := promauto.With(ksmMetricsRegistry).NewHistogramVec(
		prometheus.HistogramOpts{
			Name:        "http_request_duration_seconds",
			Help:        "A histogram of requests for kube-state-metrics metrics handler.",
			Buckets:     prometheus.DefBuckets,
			ConstLabels: prometheus.Labels{"handler": "metrics"},
		}, []string{"method"},
	)
	storeBuilder.WithMetrics(ksmMetricsRegistry)

解析命令行中启用的resource

	var resources []string
	if len(opts.Resources) == 0 {
		klog.Info("Using default resources")
		resources = options.DefaultResources.AsSlice()
	} else {
		klog.Infof("Using resources %s", opts.Resources.String())
		resources = opts.Resources.AsSlice()
	}
  • 如果没有指定就用默认的 ,位置 D:\go_path\pkg\mod\k8s.io\kube-state-metrics\v2@v2.1.1\pkg\options\resource.go
	// DefaultResources represents the default set of resources in kube-state-metrics.
	DefaultResources = ResourceSet{
		"certificatesigningrequests":      struct{}{},
		"configmaps":                      struct{}{},
		"cronjobs":                        struct{}{},
		"daemonsets":                      struct{}{},
		"deployments":                     struct{}{},
		"endpoints":                       struct{}{},
		"horizontalpodautoscalers":        struct{}{},
		"ingresses":                       struct{}{},
		"jobs":                            struct{}{},
		"leases":                          struct{}{},
		"limitranges":                     struct{}{},
		"mutatingwebhookconfigurations":   struct{}{},
		"namespaces":                      struct{}{},
		"networkpolicies":                 struct{}{},
		"nodes":                           struct{}{},
		"persistentvolumes":               struct{}{},
		"persistentvolumeclaims":          struct{}{},
		"poddisruptionbudgets":            struct{}{},
		"pods":                            struct{}{},
		"replicasets":                     struct{}{},
		"replicationcontrollers":          struct{}{},
		"resourcequotas":                  struct{}{},
		"secrets":                         struct{}{},
		"services":                        struct{}{},
		"statefulsets":                    struct{}{},
		"storageclasses":                  struct{}{},
		"validatingwebhookconfigurations": struct{}{},
		"volumeattachments":               struct{}{},
	}

解析命令行中的启用的namespace

	if len(opts.Namespaces) == 0 {
		klog.Info("Using all namespace")
		storeBuilder.WithNamespaces(options.DefaultNamespaces)
	} else {
		if opts.Namespaces.IsAllNamespaces() {
			klog.Info("Using all namespace")
		} else {
			klog.Infof("Using %s namespaces", opts.Namespaces)
		}
		storeBuilder.WithNamespaces(opts.Namespaces)
	}
  • 如果没传入,则采集所有namespace的资源对象

根据命令行传入的 metrics 黑白名单进行设置

	allowDenyList, err := allowdenylist.New(opts.MetricAllowlist, opts.MetricDenylist)
	if err != nil {
		klog.Fatal(err)
	}

	err = allowDenyList.Parse()
	if err != nil {
		klog.Fatalf("error initializing the allowdeny list : %v", err)
	}

	klog.Infof("metric allow-denylisting: %v", allowDenyList.Status())

	storeBuilder.WithAllowDenyList(allowDenyList)

最为关键的一步

  • 具体干什么先不讲,先跳过
storeBuilder.WithGenerateStoresFunc(storeBuilder.DefaultGenerateStoresFunc())

创建kubeClient

	kubeClient, vpaClient, err := createKubeClient(opts.Apiserver, opts.Kubeconfig)
	if err != nil {
		klog.Fatalf("Failed to create client: %v", err)
	}
	storeBuilder.WithKubeClient(kubeClient)
根据apiserver地址+kubeconfig配置文件创建 或者 使用 restclient.InClusterConfig创建client
  • 位置 D:\go_path\pkg\mod\k8s.io\client-go@v0.21.2\tools\clientcmd\client_config.go
func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
	if kubeconfigPath == "" && masterUrl == "" {
		klog.Warning("Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.")
		kubeconfig, err := restclient.InClusterConfig()
		if err == nil {
			return kubeconfig, nil
		}
		klog.Warning("error creating inClusterConfig, falling back to default config: ", err)
	}
	return NewNonInteractiveDeferredLoadingClientConfig(
		&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
		&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
}
  • 默认不传apiserver地址信息,采用inclusterconfig方式创建,启动日志如下
W0820 04:31:20.664175       1 client_config.go:543] Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.
  • inclusterconfig方式验证 sa和token

oklogrun 启动metrichandler

	var g run.Group

	m := metricshandler.New(
		opts,
		kubeClient,
		storeBuilder,
		opts.EnableGZIPEncoding,
	)
	// Run MetricsHandler
	{
		ctxMetricsHandler, cancel := context.WithCancel(ctx)
		g.Add(func() error {
			return m.Run(ctxMetricsHandler)
		}, func(error) {
			cancel()
		})
	}

metricshandler run

  • 位置 D:\go_path\pkg\mod\k8s.io\kube-state-metrics\v2@v2.1.1\pkg\metricshandler\metrics_handler.go
func (m *MetricsHandler) Run(ctx context.Context) error {
	autoSharding := len(m.opts.Pod) > 0 && len(m.opts.Namespace) > 0

	if !autoSharding {
		klog.Info("Autosharding disabled")
		m.ConfigureSharding(ctx, m.opts.Shard, m.opts.TotalShards)
		<-ctx.Done()
		return ctx.Err()
	}
  • 默认不开启分片,执行m.ConfigureSharding
func (m *MetricsHandler) ConfigureSharding(ctx context.Context, shard int32, totalShards int) {
	m.mtx.Lock()
	defer m.mtx.Unlock()

	if m.cancel != nil {
		m.cancel()
	}
	if totalShards != 1 {
		klog.Infof("configuring sharding of this instance to be shard index %d (zero-indexed) out of %d total shards", shard, totalShards)
	}
	ctx, m.cancel = context.WithCancel(ctx)
	m.storeBuilder.WithSharding(shard, totalShards)
	m.storeBuilder.WithContext(ctx)
	m.metricsWriters = m.storeBuilder.Build()
	m.curShard = shard
	m.curTotalShards = totalShards
}
  • 这里会根据 storeBuilder执行Build方法

请求metric时对应的serveHttp方法

  • 位置 D:\go_path\pkg\mod\k8s.io\kube-state-metrics\v2@v2.1.1\pkg\metricshandler\metrics_handler.go
func (m *MetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	m.mtx.RLock()
	defer m.mtx.RUnlock()
	resHeader := w.Header()
	var writer io.Writer = w

	resHeader.Set("Content-Type", `text/plain; version=`+"0.0.4")

	if m.enableGZIPEncoding {
		// Gzip response if requested. Taken from
		// github.com/prometheus/client_golang/prometheus/promhttp.decorateWriter.
		reqHeader := r.Header.Get("Accept-Encoding")
		parts := strings.Split(reqHeader, ",")
		for _, part := range parts {
			part = strings.TrimSpace(part)
			if part == "gzip" || strings.HasPrefix(part, "gzip;") {
				writer = gzip.NewWriter(writer)
				resHeader.Set("Content-Encoding", "gzip")
			}
		}
	}

	for _, w := range m.metricsWriters {
		w.WriteAll(writer)
	}

	// In case we gzipped the response, we have to close the writer.
	if closer, ok := writer.(io.Closer); ok {
		closer.Close()
	}
}

  • 其中最关键的是,遍历 m.metricsWriters 调用WriteAll
	for _, w := range m.metricsWriters {
		w.WriteAll(writer)
	}

metricsWriters是何方神圣

  • 是在ConfigureSharding中执行的m.storeBuilder.Build()
m.metricsWriters = m.storeBuilder.Build()

m.storeBuilder.Build()

  • 位置 D:\go_path\pkg\mod\k8s.io\kube-state-metrics\v2@v2.1.1\internal\store\builder.go
// Build initializes and registers all enabled stores.
// It returns metrics writers which can be used to write out
// metrics from the stores.
func (b *Builder) Build() []metricsstore.MetricsWriter {
	if b.allowDenyList == nil {
		panic("allowDenyList should not be nil")
	}

	var metricsWriters []metricsstore.MetricsWriter
	var activeStoreNames []string

	for _, c := range b.enabledResources {
		constructor, ok := availableStores[c]
		if ok {
			stores := constructor(b)
			activeStoreNames = append(activeStoreNames, c)
			if len(stores) == 1 {
				metricsWriters = append(metricsWriters, stores[0])
			} else {
				metricsWriters = append(metricsWriters, metricsstore.NewMultiStoreMetricsWriter(stores))
			}
		}
	}

	klog.Infof("Active resources: %s", strings.Join(activeStoreNames, ","))

	return metricsWriters
}
  • 其中的核心就是遍历 availableStores,执行他们的 buildXXXXStore函数
  • 比如 configmap对应的就是
	"configmaps":                      func(b *Builder) []*metricsstore.MetricsStore { return b.buildConfigMapStores() },
  • 对应就是
func (b *Builder) buildConfigMapStores() []*metricsstore.MetricsStore {
	return b.buildStoresFunc(configMapMetricFamilies, &v1.ConfigMap{}, createConfigMapListWatch)
}
  • 看到这里发现每个资源对象都会调用 b.buildStoresFunc 注入MetricFamilies

b.buildStoresFunc

  • 这个buildStoresFunc 对应的就是main中
	storeBuilder.WithGenerateStoresFunc(storeBuilder.DefaultGenerateStoresFunc())

  • 底层是 buildStores这个函数
func (b *Builder) buildStores(
	metricFamilies []generator.FamilyGenerator,
	expectedType interface{},
	listWatchFunc func(kubeClient clientset.Interface, ns string) cache.ListerWatcher,
) []*metricsstore.MetricsStore {
	metricFamilies = generator.FilterMetricFamilies(b.allowDenyList, metricFamilies)
	composedMetricGenFuncs := generator.ComposeMetricGenFuncs(metricFamilies)
	familyHeaders := generator.ExtractMetricFamilyHeaders(metricFamilies)

	if isAllNamespaces(b.namespaces) {
		store := metricsstore.NewMetricsStore(
			familyHeaders,
			composedMetricGenFuncs,
		)
		listWatcher := listWatchFunc(b.kubeClient, v1.NamespaceAll)
		b.startReflector(expectedType, store, listWatcher)
		return []*metricsstore.MetricsStore{store}
	}

	stores := make([]*metricsstore.MetricsStore, 0, len(b.namespaces))
	for _, ns := range b.namespaces {
		store := metricsstore.NewMetricsStore(
			familyHeaders,
			composedMetricGenFuncs,
		)
		listWatcher := listWatchFunc(b.kubeClient, ns)
		b.startReflector(expectedType, store, listWatcher)
		stores = append(stores, store)
	}

	return stores
}

每种资源都会调用这个 buildStores函数

func (b *Builder) buildConfigMapStores() []*metricsstore.MetricsStore {
	return b.buildStoresFunc(configMapMetricFamilies, &v1.ConfigMap{}, createConfigMapListWatch)
}

传入三个参数

  • metrics的metricFamilies信息
  • 资源对象结构体
  • 资源对象对应的 ListWatch方法

composedMetricGenFuncs metrics gen方法

  • 生成一个metricGen的方法
  • 然后构造一个MetricsStore
		store := metricsstore.NewMetricsStore(
			familyHeaders,
			composedMetricGenFuncs,
		)
  • 构造一个listWatcher,一并传入 startReflector
		listWatcher := listWatchFunc(b.kubeClient, v1.NamespaceAll)
		b.startReflector(expectedType, store, listWatcher)

reflector用来watch特定的k8s API资源

func (b *Builder) startReflector(
	expectedType interface{},
	store cache.Store,
	listWatcher cache.ListerWatcher,
) {
	instrumentedListWatch := watch.NewInstrumentedListerWatcher(listWatcher, b.listWatchMetrics, reflect.TypeOf(expectedType).String())
	reflector := cache.NewReflector(sharding.NewShardedListWatch(b.shard, b.totalShards, instrumentedListWatch), expectedType, store, 0)
	go reflector.Run(b.ctx.Done())
}

metrics 更新

  • D:\go_path\pkg\mod\k8s.io\kube-state-metrics\v2@v2.1.1\pkg\metrics_store\metrics_store.go
func (s *MetricsStore) Add(obj interface{}) error {
	o, err := meta.Accessor(obj)
	if err != nil {
		return err
	}

	s.mutex.Lock()
	defer s.mutex.Unlock()

	families := s.generateMetricsFunc(obj)
	familyStrings := make([][]byte, len(families))

	for i, f := range families {
		familyStrings[i] = f.ByteSlice()
	}

	s.metrics[o.GetUID()] = familyStrings

	return nil
}

  • 当有对象更新时,会调用generateMetricsFunc生成对应的指标,塞入map中

k8s 的client-go reflector.watchHandler 监听到资源变化时 调用add

  • 源码位置 D:\go_path\pkg\mod\k8s.io\client-go@v0.21.2\tools\cache\reflector.go
// watchHandler watches w and keeps *resourceVersion up to date.
func (r *Reflector) watchHandler(start time.Time, w watch.Interface, resourceVersion *string, errc chan error, stopCh <-chan struct{}) error {
	eventCount := 0

	// Stopping the watcher should be idempotent and if we return from this function there's no way
	// we're coming back in with the same watch interface.
	defer w.Stop()

loop:
	for {
		select {
		case <-stopCh:
			return errorStopRequested
		case err := <-errc:
			return err
		case event, ok := <-w.ResultChan():
			if !ok {
				break loop
			}
			if event.Type == watch.Error {
				return apierrors.FromObject(event.Object)
			}
			if r.expectedType != nil {
				if e, a := r.expectedType, reflect.TypeOf(event.Object); e != a {
					utilruntime.HandleError(fmt.Errorf("%s: expected type %v, but watch event object had type %v", r.name, e, a))
					continue
				}
			}
			if r.expectedGVK != nil {
				if e, a := *r.expectedGVK, event.Object.GetObjectKind().GroupVersionKind(); e != a {
					utilruntime.HandleError(fmt.Errorf("%s: expected gvk %v, but watch event object had gvk %v", r.name, e, a))
					continue
				}
			}
			meta, err := meta.Accessor(event.Object)
			if err != nil {
				utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event))
				continue
			}
			newResourceVersion := meta.GetResourceVersion()
			switch event.Type {
			case watch.Added:
				err := r.store.Add(event.Object)
				if err != nil {
					utilruntime.HandleError(fmt.Errorf("%s: unable to add watch event object (%#v) to store: %v", r.name, event.Object, err))
				}
			case watch.Modified:
				err := r.store.Update(event.Object)
				if err != nil {
					utilruntime.HandleError(fmt.Errorf("%s: unable to update watch event object (%#v) to store: %v", r.name, event.Object, err))
				}
			case watch.Deleted:
				// TODO: Will any consumers need access to the "last known
				// state", which is passed in event.Object? If so, may need
				// to change this.
				err := r.store.Delete(event.Object)
				if err != nil {
					utilruntime.HandleError(fmt.Errorf("%s: unable to delete watch event object (%#v) from store: %v", r.name, event.Object, err))
				}
			case watch.Bookmark:
				// A `Bookmark` means watch has synced here, just update the resourceVersion
			default:
				utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event))
			}
			*resourceVersion = newResourceVersion
			r.setLastSyncResourceVersion(newResourceVersion)
			if rvu, ok := r.store.(ResourceVersionUpdater); ok {
				rvu.UpdateResourceVersion(newResourceVersion)
			}
			eventCount++
		}
	}

	watchDuration := r.clock.Since(start)
	if watchDuration < 1*time.Second && eventCount == 0 {
		return fmt.Errorf("very short watch: %s: Unexpected watch close - watch lasted less than a second and no items received", r.name)
	}
	klog.V(4).Infof("%s: Watch close - %v total %v items received", r.name, r.expectedTypeName, eventCount)
	return nil
}

架构图总结

image.png

本节重点介绍 :

  • k8s资源对象的 buildStores构造函数注入MetricFamilies
  • k8s client-go 之 Reflector
    • listAndWatch 方法
    • watchHandler 监听更新,调用add等action

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2158060.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uniapp微信小程序用户授权方法

效果 步骤 1&#xff09;div标签 <button type"primary" class"btn-login" click"getUserInfo">一键登录</button>2&#xff09;js方法 methods: {getUserInfo() {console.log("aaaa")uni.getUserProfile({desc: Wexin, …

Python画笔案例-056 绘制正方形金字塔

1、绘制正方形金字塔 通过 python 的turtle 库绘制 正方形金字塔,如下图: 2、实现代码 绘制正方形金字塔,以下为实现代码: """正方形金字塔.py """ import turtledef draw_square(length):for _ in

【Linux】yum、vim、gcc使用(超详细)

目录 yum 安装软件 卸载软件 查看安装包 安装一下好玩的命令 vim vim基本操作 模式切换 命令集 vim批量注释 vim配置 gcc 函数库 小知识点&#xff1a; Linux中常见的软件安装方式 --------- 下载&&安装 a、yum/apt b、rpm安装包安装 c、源码安装 y…

SDK(1.1note)

什么是SDK 控制台程序&#xff08;Console User interface&#xff09;CUI 对于控制台程序&#xff0c;通用&#xff0c;也就是平台无关 图形界面系统 可是如果你想要播放一个音乐&#xff0c;C语言有嘛&#xff1f; 所以需要平台开发&#xff0c;不可以移植性 平台会提供…

摒弃“流量思维”,以精准流量驱动企业发展——基于开源 AI 智能名片、链动 2+1 模式及 O2O 商城小程序的思考

摘要&#xff1a;本文深入探讨在当前竞争激烈的营销环境下&#xff0c;摒弃“流量思维”的紧迫性与必要性。强调做内容营销不能仅仅局限于发文案&#xff0c;而应摆脱一味追求阅读量、推荐量和粉丝数的误区&#xff0c;聚焦于获取精准流量。结合开源 AI 智能名片、链动 21 模式…

如何解决npm下载Puppeteer卡死的问题

亲测有效 export PUPPETEER_DOWNLOAD_BASE_URLhttps://cdn.npmmirror.com/binaries/chrome-for-testingnpm install https://github.com/cnpm/binary-mirror-config/pull/47https://github.com/cnpm/binary-mirror-config/pull/47 PS: 最开始看了下面这两个链接&#xff0c…

网络安全-长亭雷池的sql绕过

一、环境 雷池官网docker安装我的版本是 看官网介绍主要防御top10 二、讲解 我这里只描述通用型绕过&#xff0c;对于事件型不多描述&#xff0c;因为通用型的绕过是通杀的&#xff0c;差异化绕过 正常来说我们是因为没有一个很好的过滤所以造成第11关靶场的绕过 但是现在有了…

Stable Diffusion进阶篇:模型训练(附秋叶模型训练器)

前言 今天就来学习下Stable Diffusion关于微调训练方面的知识点。 今天没多少废话&#xff0c;因为我下午要去玩PTCG&#xff01;让我们冲冲冲 整理和输出教程属实不易&#xff0c;觉得这篇教程对你有所帮助的话&#xff0c;可以点击&#x1f447;二维码领取资料&#x1f618…

k8s介绍-搭建k8s

官网&#xff1a;https://kubernetes.io/ 应用部署方式演变 传统部署&#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上 优点&#xff1a;简单&#xff0c;不需要其他技术的参与 缺点&#xff1a;不能为应用程序定义资源使用边界&#xff0c;很难合理地分配计…

Active Directory 实验室设置第二部分- 添加数据到AD域

在之前的文章中&#xff0c;我们已经讨论了AD森林的安装和AD基础知识。在这篇文章中&#xff0c;让我们开始使用 AD 对象&#xff08;如用户对象、计算机对象、组对象、网络共享等&#xff09;填充 AD 环境&#xff0c;以及计算机如何加入域。 #1、添加用户对象 可以使用GUI、…

什么是CPQ?一文讲解什么是CPQ选型配置报价系统

什么是CPQ选型配置报价? CPQ&#xff0c;是英文名 Configure Price Quote的缩写&#xff0c;中文称为配置报价软件。该软件经常出现在销售行业。它是一种可以快速为企业报价的销售工具。企业在报价时&#xff0c;会综合考虑数量、折扣、产品可选功能等。CPQ软件能够整合企业的…

Linux学习之路 -- 线程 -- 互斥

目录 1、概念引入 2、互斥锁 1、pthread_mutex_init && pthread_ mutex_destory 2、pthread_mutex_lock && pthread_mutex_unlock 3、互斥锁原理的简单介绍 1、概念引入 为了介绍线程的同步与互斥&#xff0c;我们以抢票逻辑引入相关的概念。 示例代码…

harmonyOS 原来构建还有这么多弯弯绕绕

随着用户需求的不断增长&#xff0c;我们的 APP 已发展成功能丰富的超级APP&#xff0c;这也导致打包构建变得非常耗时&#xff0c;可能需要数小时&#xff0c;严重影响开发效率和产品迭代。通过采用模块化设计、增量构建、并行处理、缓存机制、优化依赖管理&#xff0c;以及云…

SSM整合步骤

目录 一、Mybatis整合Spring 1、整合后的maven坐标 2、核心配置文件 3、pojo、mapper、service配置 4、单测 二、整合SpringMVC 1、引入springMVC的坐标并配置tomcat 2、核心配置文件 3、controller配置 4、启动项目并测试 SSM SpringMVC Spring Mybatis 整合顺序&#xff1…

Spring AOP - 注解方式实现

前文已经讨论了基于配置文件方式实现Spring AOP&#xff08;Spring AOP - 配置文件方式实现&#xff09;&#xff0c;本文采用注解的方式实现前文相同的功能。配置步骤如下&#xff1a; 1、项目增加aop依赖&#xff08;pom.xml) <dependency><groupId>org.springfr…

大数据:快速入门Scala+Flink

一、什么是Scala Scala 是一种多范式编程语言&#xff0c;它结合了面向对象编程和函数式编程的特性。Scala 这个名字是“可扩展语言”&#xff08;Scalable Language&#xff09;的缩写&#xff0c;意味着它被设计为能够适应不同规模的项目&#xff0c;从小型脚本到大型分布式…

vue2 中使用 Tinymce 富文本编辑器详解

vue2.x使用Tinymce富文本 项目中Tinymce效果图安装依赖包/创建依赖文件创建skins文件夹汉化文件-zh_CN.js 封装组件Tinymce.vue组件中使用封装组件tinymce.vueTinymce 扩展插件集合 项目中Tinymce效果图 如果想先了解一下&#xff0c;可以浏览一博主整理的的TinyMCE中文文档&am…

【揭秘大脑与AI的鸿沟:电化学信号与非线性动态交互的奥秘】

目录 【揭秘大脑与AI的鸿沟:电化学信号与非线性动态交互的奥秘】 1. 信息传递的奇迹:电化学信号的奥秘 2. 非线性动态交互:大脑的智慧之源 3. 结构与功能的鸿沟:从并行分布到有限层次 结语:探索未知的边界 【揭秘大脑与AI的鸿沟:电化学信号与非线性动态交互的奥秘】…

【深度学习】【TensorRT】【C++】模型转化、环境搭建以及模型部署的详细教程

【深度学习】【TensorRT】【C】模型转化、环境搭建以及模型部署的详细教程 提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论 文章目录 【深度学习】【TensorRT】【C】模型转化、环境搭建以及模型部署的详细教程前言模型转换--pytorch转engineWindows平台搭…

[C#]winform 使用opencvsharp实现玉米粒计数

【算法介绍】 这段代码是使用OpenCvSharp库&#xff08;OpenCV的C#封装&#xff09;对图像进行处理&#xff0c;主要流程包括图像的二值化、腐蚀操作、距离变换、轮廓检测&#xff0c;并在原图上标出检测到的轮廓位置及数量。下面是对代码的详细解读&#xff1a; 初始化&…