设为首页 收藏本站
查看: 1739|回复: 6

[经验分享] cacti获取数据流程分析

[复制链接]

尚未签到

发表于 2013-5-27 09:40:58 | 显示全部楼层 |阅读模式

cacti用于监控系统的各项运行指标,提示了操作界面和图表,是一个整合工具集,它完成两个核心任务:

1,指标数据的获取。

2,将数据通过数图进行展示。


在cacti中,图表的绘制、图表数据的存储是通过rrdtool工具实现的,《RRDtool简体中文教程》对rrdtool工具进行了介绍,是很好的资料。


获取数据的途径,视目标数据暴露的方式而定,如:

网卡数据流量、系统负载等数据已通过SNMP标准化,使用SNMP方式获取。

如果要监控nginx(stub_status)数据,该项数据是通过http方式暴露,需使用http获取数据。

如果要监控mysql-server(show status)数据,需要可以连接到mysql服务器,并有权限打印数据。


作为参照比较,nagios提供了check_by_ssh nrpe NSCA几种标准化、易于扩展的机制。如果要整合nagios和cacti的话,在获取数据层面,应该可以实施。如果有net-snmp开发能力,也可以把nginx、mysql-server的数据通过SNMP的方式暴露。


下面开始分析cacti“获取数据”部分的功能实现细节。

通过观察日志,可以对分析提供帮助。

打开日志方式:在cacti的"console" - "General" - "Poller Logging Level",需设置 >= POLLER_VERBOSITY_DEBUG


在我们配置cacti的过程中,会提到要求配置以下的计划任务,我们就从这个脚本开始。



[Shell] 纯文本查看 复制代码
1
*/5 * * * * /PATH/TO/php /PATH/TO/cacti/poller.php > /dev/null 2>&1


poller.php

---------------

根据cacti界面设置的参数,确定以何种方式获取数据

1,当计划任务启动poller.php进程后,这个进程的生存周期内执行几次数据轮询。

2,一次数据轮询使用几个进程(cmd进程)同时进行数据获取,如何分配任务给cmd进程。

3,cmd进程使用何种实现方式获取数据。

4,将cmd进程获取的数据解析后存入rrd。


1,当计划任务启动poller.php进程后,这个进程的生存周期内执行几次数据轮询。

涉及 $cron_interval,$poller_interval 两项参数

比如cron的周期是5分钟,poller周期是1分钟,则cron触发的poller.php进程,要负责安排5次数据轮询,以满足poller粒度。



[Shell] 纯文本查看 复制代码
1
$poller_runs = intval($cron_interval / $poller_interval);

poller.php进程的最大运行时间(比计划任务周期少2秒)



[Shell] 纯文本查看 复制代码
define("MAX_POLLER_RUNTIME", $poller_runs * $poller_interval - 2);

2,一次数据轮询使用几个进程(cmd进程)同时进行数据获取,如何分配任务给cmd进程。

这里先解释一下cmd进程,为了提高轮询数据的效率,cacti允许使用多个进程同时进行获取数据的工作,每个进程负责一定数量的host,因为这些进程运行cmd.php(默认是cmd.php,也可以是spine)的上下文,所取这个名称以方便描述。



[Shell] 纯文本查看 复制代码
$concurrent_processes = 2;

系统默认值为1,即只会使用一个进程负责所有主机的数据轮询,我们讨论 >= 2的场景。


cacti设置界面有如下说明,大意是说,当cmd进程使用cmd.php抓取数据时,可以通过增加这个参数,提高性能(多进程模型);当cmd进程使用spine执行数据抓取时,应该通过增加“Maximum Threads per Process”的值,提升性能(多线程模型)。

The number of concurrent processes to execute. Using a higher number when using cmd.php will improve performance. Performance improvements in spine are best resolved with the threads parameter

决定cmd进程数量的另一个因素是,cacti会查询当前有多少个任务(即cacti管理界面中Data Sources页面显示的条目内容),只有每个cmd进程都可以分配到至少一个任务时,才会启用。


cmd进程任务分配。

1,分配是在host粒度上进行的。即只分配host编号,该host上的所有子数据项都在一个进程中完成。

2,poller.php遍历所有的host,并累计任务数,当当前累计任务数 >= $items_per_process值(每个cmd进程平均完成任务数)时,就生成一个cmd进程,负责完成累计范围内的host的数据获取任务。


例如有如下任务

host1 2

host2 3

host3 3

如果$concurrent_processes = 2, $items_per_process = 8 / 2 = 4;

按主机任务进行累计和分配

累计host1 $items_launched = 2;

累计host2 $items_launched = 5; 此时累计任务数 >= $items_per_process; 派生一个cmd.php后台进程,并将开始host(1)和结束host(2)的id传递给这个进程,如下的调用。



[Shell] 纯文本查看 复制代码
exec_background('/PATH/TO/php', "-q /PATH/TO/cacti/cmd.php 1 2");

poller.php的代码如下:



[Shell] 纯文本查看 复制代码
exec_background($command_string, "$extra_args $first_host $last_host");

exec_background的实现,主要由下面这条语句实现。



[Shell] 纯文本查看 复制代码
exec($filename . ' ' . $args . ' > /dev/null &');

关于exec,手册有如下说明,所以' > /dev/null &'很重要。

如果程序使用此函数启动,为了能保持在后台运行,此程序必须将输出重定向到文件或其它输出流。 否则会导致 PHP 挂起,直至程序执行结束。

通过查看php的实现,exec、system、passthru都是基于php_exec的。

流程运行至此,获取数据的任务就交给cmd进程在后台运行了,cmd进程的实现,后面再分析。


3,cmd进程使用何种实现方式获取数据。

cmd进程有两个实现 cmd.php  spine。

cmd.php是一个php脚本,通过php解析运行。

spine,不是很了解。猜想它是一个可以访问系统调用、线程模型的可执行文件。它实现与cmd.php相同的功能。


cacti默认使用的是cmd.php,我们只分析这种方式。

cmd进程将指定host的数据项获取后,保存至数据库中(poller_output表),这里存储的数据是暂时性的,用于绘图的数据最终需要存储在rrd文件中。


4,将cmd进程获取的数据解析后存入rrd。

poller.php分配完后台cmd进程后,自已则开始检测后台cmd进程的处理结果。检测方式为:通过循环轮询数据库(poller_output表),如果没有完成的条目,执行usleep(500);如果有完成的条目,将数据通过rrdtool工具,写入到rdd文件,写入过程实现如下:

1)$rrdtool_pipe = rrd_init();

rrd_init()主要使用下面的代码实现:

$command = "/usr/bin/rrdtool - > /dev/null 2>&1";

popen($command, "w");


2)rrdtool_function_update($command_line, $rrdtool_pipe);

rrdtool_function_update()主要使用下面的代码实现:

fwrite($rrdtool_pipe, escape_command(" $command_line") . "\r\n")

fflush($rrdtool_pipe);


3)rrd_close($rrdtool_pipe);


如果成功完成的进程(end_time)数 >= 启动的后台进程数,则视为处理完成。

/* process poller commands */

exec_background($command_string, "$extra_args");

/PATH/TO/php -q /PATH/TO/cacti/poller_commands.php


cmd.php

---------------

接收两个参数 $first_host $last_host,脚本通过$_SERVER["argv"][1],$_SERVER["argv"][2] 获取,取出给定host的poller_items(cacti管理界面中Data Souries)记录。

如果没有提供参数($_SERVER["argc"] == 1),脚本会从数据库中取最近需要轮询数据的host。

下面是数据轮询流程的伪码。

[backcolor=white !important]
[backcolor=white !important][size=1em]
[Shell] 纯文本查看 复制代码
$ping = new Net_Ping;
foreach ($polling_items as $item) {
    if (($new_host) && (!empty($host_id))) {
        /* perform the appropriate ping check of the host */
        /* 如果是一个新host,cmd进程会先检测host是否可到达。 */
        if ($ping->ping(... ...) {
            /* up or down*/
        }
    }
    /* 根据目标数据暴露的方式,提供了3种获取数据的方式 */
    if (!$host_down) {
        switch ($item["action"]) {
            case POLLER_ACTION_SNMP: /* snmp */
            case POLLER_ACTION_SCRIPT: /* script (popen) */
            case POLLER_ACTION_SCRIPT_PHP: /* script (php script server) */
            default:
        }
    }
}




下面来看3种获取数据的实现

POLLER_ACTION_SNMP

即通过snmp协议获取目标数据,需提供commuinty、OID等参数,如果启用了php_snmp扩展,可使用扩展提供了snmp函数获取数据;如果没有php_snmp扩展,可以通过php脚本调用net-snmp包提供的命令行工具来获取数据;或者用php的socket自己实现snmp协议也可以,以下是一些OID。

host信息

snmptranslate -Td -Of 1.3.6.1.2.1.25

.iso.org.dod.internet.mgmt.mib-2.host

网卡流量信息

RFC1213-MIB::ifDescr

RFC1213-MIB::ifInOctets

RFC1213-MIB::ifOutOctets


POLLER_ACTION_SCRIPT

主要由下面的调用语句实现,是基于popen实现的



[Shell] 纯文本查看 复制代码
$output = trim(exec_poll($command));

exec_poll()主要通过下面的调用实现:



[Shell] 纯文本查看 复制代码
$fp = popen($command, "r");
$output = fgets($fp, 8192);

POLLER_ACTION_SCRIPT_PHP

主要由下面语句调用,是基于proc_open实现的。这种方式通过一个可双向通信的子进程(运行script_server.php上下文,后面均称ss进程),相互配合完成任务。这种方式可以复用子进程,因此会减少一些系统开销。ss进程通过下面的调用初始化。



[Shell] 纯文本查看 复制代码
$cactiphp = proc_open("/PATH/TO/php -q /PATH/TO/cacti/script_server.php cmd", $cactides,$pipes);
关于proc_open的参数,作一些笔记,它表现如下的行为:

它派生(fork)一个子进程,子进程使用$cactides(参数2)指定的方式,初始化自己已打开的文件描述符表。如



[Shell] 纯文本查看 复制代码
cactides = array(
   0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
   1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
   2 => array("file", "/tmp/error-output.txt", "a"), // stderr is a file to write to
   3 => array("file", "/PATH/TO/file", 'rw'),
);

该参数(参数2)是一个索引数组,其中0,1,2三个索引位置已经固定分配(0 is stdin, 1 is stdout, while 2 is stderr),其它索引位置可以是任何有效的文件描述符编号。

索引数组的元素也需要是一个数组,其元素有以下约定。

第一个元素是文件描述符的类型,有两种类型可供选择:pipe和file。第二个元素的语义依赖于第一个元素,如果是pipe,第二个元素是文件描述符对应到管道的哪一端,"r"为读端,"w"为写端;管道的另一端将通过$pipes(参数3)返回。管道常用作进程间通信,它是半双工的,数据只能向一个方向流动,即会有只能“写”数据的一端和只能“读”数据的一端。如果是file,第二个元素值是打开文件的路径,第三个元素是打开文件的模式;

在上例中,ss进程的文件描述符0,对应到一个管道(pipe)的读端,描述符1对应到一个管道的写端,描述符2、3都对应到一个文件。$pipes(参数3)是一个数组,$pipes[0]对应描述符0(stdin)的管道的写端,$pipes[1]对应描述符1(stdout)的管道的读端。

文件描述符处理完成后,ss进程运行(exe函数簇)$cmd(第一个参数)程序的上下文,这里是(script_server.php)。进程上下文的替换不影响描述符表。


cmd进程生成ss进程后,在$pipes[1](对应ss进程的stdout)读取子进程的输出,以判定子进程是否已经成功启动。



[Shell] 纯文本查看 复制代码
$output = fgets($pipes[1], 1024);
substr_count($output, "Started") != 0

fgets会阻塞,直到从$pipes[1]中读取足够数据、或者EOF、或者cmd进程收到一个signal。然后在输出字串中查找"Started"串。


ss进程启动后,会在stdout输出下面的内容(包含“Started”串),以告知cmd进程,自己已经准备好了。



[Shell] 纯文本查看 复制代码
fputs(STDOUT, "PHP Script Server has Started - Parent is " . $environ . "\n");

然后ss进程阻塞在“读取stdin”上,等待cmd进程的指令



[Shell] 纯文本查看 复制代码
/* process waits for input and then calls functions as required */
while (1) {
    ... ...
    $input_string    = fgets(STDIN, 1024);
    ... ...
}

至此,cmd进程和ss进程,已完成协调工作的准备工作。当cmd进程需要ss进程配合获取数据时,cmd进程就往ss进程的stdin写命令,ss进程完成后,将结果写入stdout以返回给cmd进程。下面是调用代码:



[Shell] 纯文本查看 复制代码
$output = trim(str_replace("\n", "", exec_poll_php($item["arg1"], $using_proc_function, $pipes,$cactiphp)));

其中exec_poll_php的主要实现语句如下:



[Shell] 纯文本查看 复制代码
function exec_poll_php($command, $using_proc_function, $pipes, $proc_fd) {
    ... ...
    fwrite($pipes[0], $command . "\r\n");
    $output = fgets($pipes[1], 8192);
    ... ...
    return $output;
}

cmd进程的分析就到这里,下面我们来看一下ss进程的实现。


script_server.php

---------------

上文提到,ss进程初始完成后,便阻塞于 fgets(STDIN, 1024);以等待cmd进程的命令,下面我们就来看一下,ss进程从stdin获取命令后的流程实现。



[Shell] 纯文本查看 复制代码
$input_string    = fgets(STDIN, 1024);
/* 如果命令的前4个字符是“quit”,ss进程执行退出 */
if (substr($input_string,0,4) == "quit") {
    exit(1);
}
/* pull off the parameters */
$i = 0;
while ( true ) {
    /* 查找命令中的空格,以切分命令参数 */
    $pos = strpos($input_string, " ");
    if ($pos > 0) {
        switch ($i) {
            case 0:
                /* cut off include file as first part of input string and keep rest for further parsing */
                $include_file = trim(substr($input_string,0,$pos));
                $input_string = trim(strchr($input_string, " ")) . " ";
                break;
            case 1:
                /* cut off function as second part of input string and keep rest for further parsing */
                $function = trim(substr($input_string,0,$pos));
                $input_string = trim(strchr($input_string, " ")) . " ";
                break;
            case 2:
                /* take the rest as parameter(s) to the function stripped off previously */
                $parameters = trim($input_string);
                break 2;
            }
        }else{
            break;
        }
    $i++;
}

上面的代码显示,传递给ss进程的命令串以下面的格式组织:



[Shell] 纯文本查看 复制代码
include_file<空格>function名<空格>parameters串


include_file    的路径为/PATH/TO/cacti/scripts/include_file

function        名字为include_file文件中定义的函数名。

parameters      为function的参数列表。


通过这个机制,我们可以扩展新的获取数据的方法。


至此,获取数据流程实现就分析完了。平常用php写网页比较多一些,基本是与数据库打交道,不会涉及到进程层面的处理,cacti的实现恰好在把php用作shell方面给我一个学习的样例。学习过程中,涉及到了操作系统的一些知识,如系统调用、进程间通信、IO阻塞等;有时候会感叹:“哦,原来php是这样来实现这个功能的”。

感谢cacti、感谢开源。



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-6164-1-1.html 上篇帖子: cacti监控(3)配置cacti 下篇帖子: Cacti 监控windows server 2008图文详解

尚未签到

发表于 2013-5-27 10:10:43 | 显示全部楼层
一时的冲动,子孙的危机!

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

尚未签到

发表于 2013-5-27 12:48:38 | 显示全部楼层
生活***好玩,因为生活老***玩我!

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

尚未签到

发表于 2013-5-27 12:52:05 | 显示全部楼层
内练一口气,外练一口屁。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

尚未签到

发表于 2013-5-27 13:16:50 | 显示全部楼层
所有刻骨铭心的爱都灵魂游离于床上的瞬间!

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

尚未签到

发表于 2013-5-27 15:38:51 | 显示全部楼层
看尽天下A片,心中自然无码~

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

尚未签到

发表于 2013-5-27 18:11:04 | 显示全部楼层
有竞争才有进步嘛

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表