<?php
/**
 * Description of MonUtil.php
 *
 * @author caoyunmeng
 * Date: 2021/2/2
 * Time: 2:27 PM
 */
namespace Api\PhpUtils\Mon;

class MonUtil{
    /**
     * 向安装有ydbot服务的机器发送信息，进行监控打点
     *
     * @param string $send_msg  打点信息
     * @param string $ip  服务器ip，默认为宿主机ip(172.17.0.1)
     * @param string $port 端口，默认为15688
     * @return string
     */
    public static function base($send_msg, $ip = '172.17.0.1', $port = '15688'){
        if(isset($_SERVER['YIDIAN_LOCAL_IP']) && !empty($_SERVER['YIDIAN_LOCAL_IP'])){
            $ip = $_SERVER['YIDIAN_LOCAL_IP'];
        }
        $handle = stream_socket_client("udp://{$ip}:{$port}", $errno,$errstr, 1);

        if($handle){
            //在一个stream上设置超时
            stream_set_timeout($handle,0,15000);
            fwrite($handle, $send_msg."\n");
            //从封装协议文件指针中取得报头／元数据，返回数组，索引timed_out为超时信息，超时则为true，未超时则为false
            $info = stream_get_meta_data($handle);
            fclose($handle);

            if ($info['timed_out']) {
                return 'fwrite timeout!';
            } else {
                return $send_msg;
            }
        }else{
            return "stream_socket_client timeout ($errno):$errstr";
        }
    }

    /**
     * 针对第三方服务接入，返回状态码与请求响应总时间进行监控打点
     *
     * @param string $module 请求模块
     * @param string $url 请求第三方服务url
     * @param int $code 请求返回状态码
     * @param int $request_time 请求总时长
     * @return string 打点信息或""
     */
    public static function proxyMon($url, $code, $module='',$request_time = -899){
        $result = "";

        if(is_string($url) && !empty($url)){

            //截取请求第三方服务的uri
            $parse_url = parse_url($url);
            $preg_uri = $parse_url['path'] ?? $url;
            $host = $parse_url['host'] ?? 'other_depent';
            //替换$uri中的"."为"_", ":"为"_", "/"为"-"
            $request_uri = str_replace(array(".", ":", "/"),array("_", "_", "-"), $preg_uri);

            $request_module = str_replace(array(".", ":", "/"),array("_", "_", "-"), $host);
            if (empty($module)) {
                //默认取url 中的域名作为module
                $domain = str_replace(array(".", ":", "/"),array("_", "_", "-"), $_SERVER['SERVER_NAME'] ?? 'unknow_domain');
                $module =  $domain.'.'.($request_module ?? 'other_depent');
            }

            //接口返回状态码打点
            if(isset($code) && is_numeric($code)){
                $result = self::counting($module . "." . (string)$request_uri, (string)$code,1) . "\n";
            }else{
                $result = self::counting($module . "." . (string)$request_uri, (string)-999,1) . "\n";
            }

            //接口请求响应总时间打点
            if(!empty($request_time) && is_numeric($request_time) && $request_time != -899){
                $result .= self::timing($module.'.'.(string)$request_uri, "TotalTime", $request_time);
            }
        }
        return $result;
    }

    /**
     * @param $db_host  string 链接数据的集群
     * @param $operator string 数据库操作
     * @param $db_name  string 数据库名字 比如mysql ,mongodb
     * @param int $request_time
     */
    public static function dbMon($db_host,$operator,$db_name,$request_time = -799)
    {
        if ( empty($operator) || !is_string($db_host) || empty($db_name)) {
            return ;
        }

        if (empty($db_host)) {
            $host = 'unknow';
        } else {
            $host = str_replace(array(".", ":", "/",','),array("_", "_", "-",'_'), $db_host);
        }

        //记录请求量
        self::counting($db_name.'.'.$host,(string)$operator,1);

        //耗时打点
        if(!empty($request_time) && is_numeric($request_time) && $request_time != -799){
            $type = 'ms';
            if ($request_time < 1) {
                $request_time *= 1000;
                $type = 's';
            }
            $result = self::timing(($db_name.'.'.$host.'.'.$operator), "TotalTime", $request_time,$type);
        }
    }

    /**
     * @param string $module
     * @param string $index
     * @param int $value
     * @param string $type  监控打点类型
     * @return string  返回监控打点信息
     */
    public static function getMsg($module, $index, $value, $type){
        if(is_string($module) && !empty($module) && is_string($index) && !empty($index)){
            return "Ydbot.statsd.superfe.bp." . $module . "." . $index . ".1sec:" . $value . "|" . $type;
        }

        return "";
    }

    /**
     * 对值进行计数。在一个flush区间，把上报的值累加；flush输出的时候，将上报的值重新清零
     *
     * @param string $module 子模块名字
     * @param string $index  指标
     * @param int $value
     * @return mixed
     */
    public static function counting($module = 'api', $index = '', $value = 0){
        $result = "";

        $send_msg = self::getMsg($module, $index, $value, "c");
        if($send_msg){
            $result = self::base($send_msg);
        }

        return $result;
    }

    /**
     * @param $module string 项目模块
     * @param $event  string 事件
     * @param int $count
     * @return string
     */
    public static function send_count_msg(string $module,string $event, $count = 1)
    {
        if (!empty($_SERVER['YIDIAN_LOCAL_IP']))
        {
            $msg = sprintf("Ydbot.statsd.superfe.bp.%s.%s.1sec:%s|c",  $module,$event, $count);
            self::base($msg);
        }
    }

    /**
     * @param string $module  string 项目模块
     * @param $func_name      string  函数或者监听的事件名称
     * @param $cost
     */
    public static function send_time_cost_msg(string $module,$func_name, $cost)
    {
        if (!empty($_SERVER['YIDIAN_LOCAL_IP']))
        {
            $msg = sprintf("Ydbot.statsd.superfe.bp.%s.%s.1sec:%s|ms", $module, $func_name, $cost);
            self::base($msg);
        }
    }

    /**
     * 标量是任何可以测量的一维变量，可以通过显示的加入符号让系统进行值的累加，不加符号时添加的新值会覆盖旧值
     *
     * @param string $module 子模块名字
     * @param string $index  指标
     * @param int $value
     * @return mixed
     */
    public static function gauges($module = 'api', $index = '', $value = 0){
        $result = "";

        $send_msg = self::getMsg($module, $index, $value, "g");
        if($send_msg){
            $result = self::base($send_msg);
        }

        return $result;
    }

    /**
     * 记录flush期间，不重复的值。譬如对于一个接口，记录有多少个user访问
     *
     * @param string $module 子模块名字
     * @param string $index  指标
     * @param int $value
     * @return mixed
     */
    public static function sets($module = 'api', $index = '', $value = 0){
        $result = "";

        $send_msg = self::getMsg($module, $index, $value, "s");
        if($send_msg){
            $result = self::base($send_msg);
        }

        return $result;
    }

    /**
     * 记录某个操作的耗时，单位ms。在一个flush期间，会自动记录平均值（mean）、最大值（upper）、最小值（lower）、
     *  个数（count）以及部分百分值（P95/P99/P999）
     *
     * @param string $module 子模块名字
     * @param string $index  指标
     * @param int $value
     * @param string $type 单位
     * @return mixed
     */
    public static function timing($module = 'api', $index = '', $value = 0,$type='ms'){
        $result = "";

        $send_msg = self::getMsg($module, $index, $value, $type);
        if($send_msg){
            $result = self::base($send_msg);
        }

        return $result;
    }

    /**
     * 记录指标概率，Value为“100”和“0”。在一个flush期间，成功的概率
     *
     * @param string $module 子模块名字
     * @param string $index  指标
     * @param int $value
     * @return mixed
     */
    public static function rate($module = 'api', $index = '', $value = 0){
        $result = "";

        $send_msg = self::getMsg($module, $index, $value, "rate");
        if($send_msg){
            $result = self::base($send_msg);
        }

        return $result;
    }
    /**
     * 统计下接口返回的code
     *
     * @param $code
     * @return mixed  监控打点数据或""
     */
    public static function uri_code_count($code){
        $uri = explode('?', $_SERVER['REQUEST_URI']);

        //获取服务器的名字作为模块名,因grafana打点路径以"."分割，将服务器名中的"."转化为"_"
        $module = str_replace(".", "_", $_SERVER['SERVER_NAME']);

        //因grafana打点路径的key只支持大小写字母、数字、中划线和下划线，将uri中的"/"转化为"_"进行打点
        $index = str_replace("/","_", strval($uri[0]));
        //排除uri加后缀.php的情况
        $index = explode(".", $index);

        $index = substr($index[0],1). "." . strval($code);

        return self::counting($module, $index, 1);
    }
}
