// // Copyright (c) 2013-2022 The SRS Authors // // SPDX-License-Identifier: MIT or MulanPSL-2.0 // #include #ifdef SRS_APM #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; // See https://cloud.tencent.com/document/product/614/12445 namespace tencentcloud_api_sign { std::string sha1(const void *data, size_t len) { unsigned char digest[SHA_DIGEST_LENGTH]; SHA_CTX ctx; SHA1_Init(&ctx); SHA1_Update(&ctx, data, len); SHA1_Final(digest, &ctx); char c_sha1[SHA_DIGEST_LENGTH*2+1]; for (unsigned i = 0; i < SHA_DIGEST_LENGTH; ++i) { sprintf(&c_sha1[i*2], "%02x", (unsigned int)digest[i]); } return c_sha1; } std::string hmac_sha1(const char *key, const void *data, size_t len) { unsigned char digest[EVP_MAX_MD_SIZE]; unsigned digest_len; char c_hmacsha1[EVP_MAX_MD_SIZE*2+1]; #if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x10100000L HMAC_CTX ctx; HMAC_CTX_init(&ctx); HMAC_Init_ex(&ctx, key, strlen(key), EVP_sha1(), NULL); HMAC_Update(&ctx, (unsigned char*)data, len); HMAC_Final(&ctx, digest, &digest_len); HMAC_CTX_cleanup(&ctx); #else HMAC_CTX *ctx = HMAC_CTX_new(); HMAC_CTX_reset(ctx); HMAC_Init_ex(ctx, key, strlen(key), EVP_sha1(), NULL); HMAC_Update(ctx, (unsigned char *)data, len); HMAC_Final(ctx, digest, &digest_len); HMAC_CTX_free(ctx); #endif for (unsigned i = 0; i != digest_len; ++i) { sprintf(&c_hmacsha1[i*2], "%02x", (unsigned int)digest[i]); } return c_hmacsha1; } std::string urlencode(const char *s) { static unsigned char hexchars[] = "0123456789ABCDEF"; size_t length = strlen(s), pos = 0; unsigned char c_url[length*3+1]; const unsigned char *p = (const unsigned char *)s; for (; *p; ++p) { if (isalnum((unsigned char)*p) || (*p == '-') || (*p == '_') || (*p == '.') || (*p == '~')) { c_url[pos++] = *p; } else { c_url[pos++] = '%'; c_url[pos++] = hexchars[(*p)>>4]; c_url[pos++] = hexchars[(*p)&15U]; } } c_url[pos] = 0; return (char*)c_url; } std::string signature(const std::string &secret_id, const std::string &secret_key, std::string method, const std::string &path, const std::map ¶ms, const std::map &headers, long expire) { const size_t SIGNLEN = 1024; std::string http_request_info, uri_parm_list, header_list, str_to_sign, sign_key; transform(method.begin(), method.end(), method.begin(), ::tolower); http_request_info.reserve(SIGNLEN); http_request_info.append(method).append("\n").append(path).append("\n"); uri_parm_list.reserve(SIGNLEN); std::map::const_iterator iter; for (iter = params.begin(); iter != params.end(); ) { uri_parm_list.append(iter->first); http_request_info.append(iter->first).append("=") .append(urlencode(iter->second.c_str())); if (++iter != params.end()) { uri_parm_list.append(";"); http_request_info.append("&"); } } http_request_info.append("\n"); header_list.reserve(SIGNLEN); for (iter = headers.begin(); iter != headers.end(); ++iter) { sign_key = iter->first; transform(sign_key.begin(), sign_key.end(), sign_key.begin(), ::tolower); if (sign_key == "content-type" || sign_key == "content-md5" || sign_key == "host" || sign_key[0] == 'x') { header_list.append(sign_key); http_request_info.append(sign_key).append("=") .append(urlencode(iter->second.c_str())); header_list.append(";"); http_request_info.append("&"); } } if (!header_list.empty()) { header_list[header_list.size() - 1] = 0; http_request_info[http_request_info.size() - 1] = '\n'; } //printf("%s\nEOF\n", http_request_info.c_str()); char signed_time[SIGNLEN] = {0}; int signed_time_len = snprintf(signed_time, SIGNLEN, "%lu;%lu", time(0) - 60, time(0) + expire); //snprintf(signed_time, SIGNLEN, "1510109254;1510109314"); std::string signkey = hmac_sha1(secret_key.c_str(), signed_time, signed_time_len); str_to_sign.reserve(SIGNLEN); str_to_sign.append("sha1").append("\n") .append(signed_time).append("\n") .append(sha1(http_request_info.c_str(), http_request_info.size())) .append("\n"); //printf("%s\nEOF\n", str_to_sign.c_str()); std::stringstream c_signature; c_signature << "q-sign-algorithm=sha1&q-ak=" << secret_id.c_str() << "&q-sign-time=" << signed_time << "&q-key-time=" << signed_time << "&q-header-list=" << header_list.c_str() << "&q-url-param-list=" << uri_parm_list.c_str() << "&q-signature=" << hmac_sha1(signkey.c_str(), str_to_sign.c_str(), str_to_sign.size()).c_str(); return c_signature.str(); } } // See https://cloud.tencent.com/document/api/614/16873 class SrsClsLogContent : public ISrsEncoder { private: // required string key = 1; std::string key_; // required string value = 2; std::string value_; public: SrsClsLogContent(); virtual ~SrsClsLogContent(); public: SrsClsLogContent* set_key(std::string v); SrsClsLogContent* set_value(std::string v); public: virtual uint64_t nb_bytes(); srs_error_t encode(SrsBuffer* b); }; // See https://cloud.tencent.com/document/api/614/16873 class SrsClsLog : public ISrsEncoder { private: // required int64 time = 1; int64_t time_; // repeated Content contents= 2; std::vector contents_; public: SrsClsLog(); virtual ~SrsClsLog(); public: SrsClsLogContent* add_content(); SrsClsLog* set_time(int64_t v); public: virtual uint64_t nb_bytes(); srs_error_t encode(SrsBuffer* b); }; // See https://cloud.tencent.com/document/api/614/16873 class SrsClsLogGroup : public ISrsEncoder { private: // repeated Log logs= 1; std::vector logs_; // optional string source = 4; std::string source_; public: SrsClsLogGroup(); virtual ~SrsClsLogGroup(); public: SrsClsLogGroup* set_source(std::string v); SrsClsLog* add_log(); public: virtual uint64_t nb_bytes(); srs_error_t encode(SrsBuffer* b); }; // See https://cloud.tencent.com/document/api/614/16873 class SrsClsLogGroupList { private: // repeated LogGroup logGroupList = 1; std::vector groups_; public: SrsClsLogGroupList(); virtual ~SrsClsLogGroupList(); public: bool empty(); SrsClsLogGroup* add_log_group(); public: virtual uint64_t nb_bytes(); srs_error_t encode(SrsBuffer* b); }; SrsClsLogContent::SrsClsLogContent() { } SrsClsLogContent::~SrsClsLogContent() { } SrsClsLogContent* SrsClsLogContent::set_key(std::string v) { key_ = v; return this; } SrsClsLogContent* SrsClsLogContent::set_value(std::string v) { value_ = v; return this; } uint64_t SrsClsLogContent::nb_bytes() { uint64_t nn = SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(key_); nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(value_); return nn; } srs_error_t SrsClsLogContent::encode(SrsBuffer* b) { srs_error_t err = srs_success; // Encode the key. if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldString)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufString::encode(b, key_)) != srs_success) { return srs_error_wrap(err, "encode key=%s", key_.c_str()); } // Encode the value. if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldString)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufString::encode(b, value_)) != srs_success) { return srs_error_wrap(err, "encode value=%s", value_.c_str()); } return err; } SrsClsLog::SrsClsLog() { } SrsClsLog::~SrsClsLog() { for (std::vector::iterator it = contents_.begin(); it != contents_.end(); ++it) { SrsClsLogContent* content = *it; srs_freep(content); } } SrsClsLogContent* SrsClsLog::add_content() { SrsClsLogContent* content = new SrsClsLogContent(); contents_.push_back(content); return content; } SrsClsLog* SrsClsLog::set_time(int64_t v) { time_ = v; return this; } uint64_t SrsClsLog::nb_bytes() { uint64_t nn = SrsProtobufKey::sizeof_key() + SrsProtobufVarints::sizeof_varint(time_); for (std::vector::iterator it = contents_.begin(); it != contents_.end(); ++it) { SrsClsLogContent* content = *it; nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(content); } return nn; } srs_error_t SrsClsLog::encode(SrsBuffer* b) { srs_error_t err = srs_success; // Encode the time. if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldVarint)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufVarints::encode(b, time_)) != srs_success) { return srs_error_wrap(err, "encode time"); } // Encode each content. for (std::vector::iterator it = contents_.begin(); it != contents_.end(); ++it) { SrsClsLogContent* content = *it; if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldObject)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufObject::encode(b, content)) != srs_success) { return srs_error_wrap(err, "encode content"); } } return err; } SrsClsLogGroup::SrsClsLogGroup() { } SrsClsLogGroup::~SrsClsLogGroup() { for (std::vector::iterator it = logs_.begin(); it != logs_.end(); ++it) { SrsClsLog* log = *it; srs_freep(log); } } SrsClsLogGroup* SrsClsLogGroup::set_source(std::string v) { source_ = v; return this; } SrsClsLog* SrsClsLogGroup::add_log() { SrsClsLog* log = new SrsClsLog(); logs_.push_back(log); return log; } uint64_t SrsClsLogGroup::nb_bytes() { uint64_t nn = 0; for (std::vector::iterator it = logs_.begin(); it != logs_.end(); ++it) { SrsClsLog* log = *it; nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(log); } nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(source_); return nn; } srs_error_t SrsClsLogGroup::encode(SrsBuffer* b) { srs_error_t err = srs_success; // Encode each log. for (std::vector::iterator it = logs_.begin(); it != logs_.end(); ++it) { SrsClsLog* log = *it; if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldObject)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufObject::encode(b, log)) != srs_success) { return srs_error_wrap(err, "encode log"); } } // Encode the optional source. if ((err = SrsProtobufKey::encode(b, 4, SrsProtobufFieldString)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufString::encode(b, source_)) != srs_success) { return srs_error_wrap(err, "encode source=%s", source_.c_str()); } return err; } SrsClsLogGroupList::SrsClsLogGroupList() { } SrsClsLogGroupList::~SrsClsLogGroupList() { for (std::vector::iterator it = groups_.begin(); it != groups_.end(); ++it) { SrsClsLogGroup* group = *it; srs_freep(group); } } bool SrsClsLogGroupList::empty() { return groups_.empty(); } SrsClsLogGroup* SrsClsLogGroupList::add_log_group() { SrsClsLogGroup* group = new SrsClsLogGroup(); groups_.push_back(group); return group; } uint64_t SrsClsLogGroupList::nb_bytes() { uint64_t nn = 0; for (std::vector::iterator it = groups_.begin(); it != groups_.end(); ++it) { SrsClsLogGroup* group = *it; nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(group); } return nn; } srs_error_t SrsClsLogGroupList::encode(SrsBuffer* b) { srs_error_t err = srs_success; // Encode each group. for (std::vector::iterator it = groups_.begin(); it != groups_.end(); ++it) { SrsClsLogGroup* group = *it; if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldObject)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufObject::encode(b, group)) != srs_success) { return srs_error_wrap(err, "encode group"); } } return err; } SrsClsSugar::SrsClsSugar() { log_groups_ = new SrsClsLogGroupList(); log_group_ = log_groups_->add_log_group(); log_ = log_group_->add_log(); log_group_->set_source(srs_get_public_internet_address(true)); log_->set_time(srs_get_system_time() / SRS_UTIME_MILLISECONDS); kv("agent", RTMP_SIG_SRS_SERVER); string label = _srs_cls->label(); if (!label.empty()) { kv("label", label); } string tag = _srs_cls->tag(); if (!tag.empty()) { kv("tag", tag); } string server_id = SrsStatistic::instance()->server_id(); if (!server_id.empty()) { kv("id", server_id); } } SrsClsSugar::~SrsClsSugar() { srs_freep(log_groups_); } uint64_t SrsClsSugar::nb_bytes() { return log_groups_->nb_bytes(); } srs_error_t SrsClsSugar::encode(SrsBuffer* b) { return log_groups_->encode(b); } bool SrsClsSugar::empty() { return log_groups_->empty(); } SrsClsSugar* SrsClsSugar::kv(std::string k, std::string v) { log_->add_content()->set_key(k)->set_value(v); return this; } SrsClsSugars::SrsClsSugars() { } SrsClsSugars::~SrsClsSugars() { for (vector::iterator it = sugars.begin(); it != sugars.end(); ++it) { SrsClsSugar* sugar = *it; srs_freep(sugar); } } uint64_t SrsClsSugars::nb_bytes() { uint64_t size = 0; for (vector::iterator it = sugars.begin(); it != sugars.end(); ++it) { SrsClsSugar* sugar = *it; size += sugar->nb_bytes(); } return size; } srs_error_t SrsClsSugars::encode(SrsBuffer* b) { srs_error_t err = srs_success; for (vector::iterator it = sugars.begin(); it != sugars.end(); ++it) { SrsClsSugar* sugar = *it; if ((err = sugar->encode(b)) != srs_success) { return srs_error_wrap(err, "encode %d sugars", (int)sugars.size()); } } return err; } SrsClsSugar* SrsClsSugars::create() { SrsClsSugar* sugar = new SrsClsSugar(); sugars.push_back(sugar); return sugar; } SrsClsSugars* SrsClsSugars::slice(int max_size) { SrsClsSugars* v = new SrsClsSugars(); uint64_t v_size = 0; for (vector::iterator it = sugars.begin(); it != sugars.end();) { SrsClsSugar* sugar = *it; // Always consume it. it = sugars.erase(it); // If empty, ignore it. if (sugar->empty()) { srs_freep(sugar); continue; } // Not empty, append it, to make sure at least one elem. v->sugars.push_back(sugar); // Util exceed the max size. v_size += sugar->nb_bytes(); if ((int)v_size > max_size) { break; } } return v; } bool SrsClsSugars::empty() { return sugars.empty(); } int SrsClsSugars::size() { return (int)sugars.size(); } SrsClsClient* _srs_cls = NULL; SrsClsClient::SrsClsClient() { enabled_ = false; stat_heartbeat_ = false; stat_streams_ = false; debug_logging_ = false; heartbeat_ratio_ = 0; streams_ratio_ = 0; nn_logs_ = 0; sugars_ = new SrsClsSugars(); } SrsClsClient::~SrsClsClient() { srs_freep(sugars_); } bool SrsClsClient::enabled() { return enabled_; } string SrsClsClient::label() { return label_; } string SrsClsClient::tag() { return tag_; } uint64_t SrsClsClient::nn_logs() { return nn_logs_; } srs_error_t SrsClsClient::initialize() { srs_error_t err = srs_success; enabled_ = _srs_config->get_tencentcloud_cls_enabled(); if (!enabled_) { srs_trace("TencentCloud CLS is disabled"); return err; } label_ = _srs_config->get_tencentcloud_cls_label(); tag_ = _srs_config->get_tencentcloud_cls_tag(); stat_heartbeat_ = _srs_config->get_tencentcloud_cls_stat_heartbeat(); stat_streams_ = _srs_config->get_tencentcloud_cls_stat_streams(); debug_logging_ = _srs_config->get_tencentcloud_cls_debug_logging(); heartbeat_ratio_ = srs_max(1, _srs_config->get_tencentcloud_cls_heartbeat_ratio()); streams_ratio_ = srs_max(1, _srs_config->get_tencentcloud_cls_streams_ratio()); secret_id_ = _srs_config->get_tencentcloud_cls_secret_id(); if (secret_id_.empty()) { return srs_error_new(ERROR_CLS_INVALID_CONFIG, "CLS no config for secret_id"); } string secret_key = _srs_config->get_tencentcloud_cls_secret_key(); if (secret_key.empty()) { return srs_error_new(ERROR_CLS_INVALID_CONFIG, "CLS no config for secret_key"); } endpoint_ = _srs_config->get_tencentcloud_cls_endpoint(); if (endpoint_.empty()) { return srs_error_new(ERROR_CLS_INVALID_CONFIG, "CLS no config for endpoint"); } topic_ = _srs_config->get_tencentcloud_cls_topic_id(); if (topic_.empty()) { return srs_error_new(ERROR_CLS_INVALID_CONFIG, "CLS no config for topic_id"); } srs_trace("Initialize TencentCloud CLS label=%s, tag=%s, secret_id=%dB, secret_key=%dB, endpoint=%s, topic=%s, heartbeat=%d/%d, streams=%d/%d debug_logging=%d", label_.c_str(), tag_.c_str(), secret_id_.length(), secret_key.length(), endpoint_.c_str(), topic_.c_str(), stat_heartbeat_, heartbeat_ratio_, stat_streams_, streams_ratio_, debug_logging_); return err; } srs_error_t SrsClsClient::report() { srs_error_t err = srs_success; if ((err = dump_summaries(sugars_)) != srs_success) { return srs_error_wrap(err, "dump summary"); } if ((err = dump_streams(sugars_)) != srs_success) { return srs_error_wrap(err, "dump streams"); } if (sugars_->empty()) { return err; } SrsClsSugars* sugars = sugars_; SrsAutoFree(SrsClsSugars, sugars); sugars_ = new SrsClsSugars(); if ((err = send_logs(sugars)) != srs_success) { return srs_error_wrap(err, "cls"); } return err; } srs_error_t SrsClsClient::do_send_logs(ISrsEncoder* sugar, int count, int total) { srs_error_t err = srs_success; uint64_t size = sugar->nb_bytes(); // Max size is 5MB, error is 403:LogSizeExceed, see https://cloud.tencent.com/document/api/614/12402 if (size >= 5 * 1024 * 1024) { return srs_error_new(ERROR_CLS_EXCEED_SIZE, "exceed 5MB actual %d", size); } char* buf = new char[size]; SrsAutoFreeA(char, buf); memset(buf, 0, size); SrsBuffer b(buf, size); if ((err = sugar->encode(&b)) != srs_success) { return srs_error_wrap(err, "encode log"); } string body(buf, size); // Write a CLS log to service specified by url. string url = "http://" + endpoint_ + ":80/structuredlog?topic_id=" + topic_; SrsHttpUri uri; if ((err = uri.initialize(url)) != srs_success) { return srs_error_wrap(err, "http: post failed. url=%s", url.c_str()); } SrsHttpClient http; if ((err = http.initialize(uri.get_schema(), uri.get_host(), uri.get_port())) != srs_success) { return srs_error_wrap(err, "http: init client"); } // Sign the request, see https://cloud.tencent.com/document/product/614/56475 if (true) { map params; params["topic_id"] = topic_; map headers; headers["Host"] = uri.get_host(); headers["Content-Type"] = "application/x-protobuf"; http.set_header("Content-Type", "application/x-protobuf"); string method = "POST"; string secret_key = _srs_config->get_tencentcloud_cls_secret_key(); std::string signature = tencentcloud_api_sign::signature( secret_id_, secret_key, method, uri.get_path(), params, headers, 300 // Expire in seconds ); headers["Authorization"] = signature; http.set_header("Authorization", signature); } string path = uri.get_path(); if (!uri.get_query().empty()) { path += "?" + uri.get_query(); } // Start request and parse response. ISrsHttpMessage* msg = NULL; if ((err = http.post(path, body, &msg)) != srs_success) { return srs_error_wrap(err, "http: client post"); } SrsAutoFree(ISrsHttpMessage, msg); string res; uint16_t code = msg->status_code(); if ((err = msg->body_read_all(res)) != srs_success) { return srs_error_wrap(err, "http: body read"); } // ensure the http status is ok. if (code != SRS_CONSTS_HTTP_OK && code != SRS_CONSTS_HTTP_Created) { return srs_error_new(ERROR_HTTP_STATUS_INVALID, "http: status %d, body is %s", code, res.c_str()); } string request_id = msg->header()->get("X-Cls-Requestid"); if (request_id.empty() && !debug_logging_) { srs_warn("no CLS requestId for log %dB", body.length()); } if (debug_logging_) { string server_id = SrsStatistic::instance()->server_id(); srs_trace("CLS write logs=%d/%d, size=%dB, server_id=%s, request_id=%s", count, total, body.length(), server_id.c_str(), request_id.c_str()); } return err; } // For each upload, never exceed 2MB, to avoid burst of CPU or network usage. #define SRS_CLS_BATCH_MAX_LOG_SIZE 2 * 1024 * 1024 srs_error_t SrsClsClient::send_logs(SrsClsSugars* sugars) { srs_error_t err = srs_success; // Record the total logs sent out. int total = sugars->size(); nn_logs_ += total; // Never do infinite loop, limit to a max loop and drop logs if exceed. for (int i = 0; i < 128 && !sugars->empty(); ++i) { SrsClsSugars* v = sugars->slice(SRS_CLS_BATCH_MAX_LOG_SIZE); SrsAutoFree(SrsClsSugars, v); if ((err = do_send_logs((ISrsEncoder*)v, v->size(), total)) != srs_success) { return srs_error_wrap(err, "send %d/%d/%d logs", v->size(), i, total); } } return err; } srs_error_t SrsClsClient::dump_summaries(SrsClsSugars* sugars) { srs_error_t err = srs_success; // Ignore if disabled. if (!enabled_ || !stat_heartbeat_) { return err; } // Whether it's time to report heartbeat. static int nn_heartbeat = -1; bool interval_ok = nn_heartbeat == -1 || ++nn_heartbeat >= heartbeat_ratio_; if (interval_ok) { nn_heartbeat = 0; } if (!interval_ok) { return err; } SrsClsSugar* sugar = sugars->create(); sugar->kv("hint", "summary"); sugar->kv("version", RTMP_SIG_SRS_VERSION); sugar->kv("pid", srs_fmt("%d", getpid())); // Server ID to identify logs from a set of servers' logs. SrsStatistic::instance()->dumps_cls_summaries(sugar); SrsProcSelfStat* u = srs_get_self_proc_stat(); if (u->ok) { // The cpu usage of SRS, 1 means 1/1000 if (u->percent > 0) { sugar->kv("cpu", srs_fmt("%d", (int)(u->percent * 1000))); } } SrsPlatformInfo* p = srs_get_platform_info(); if (p->ok) { // The uptime of SRS, in seconds. if (p->srs_startup_time > 0) { sugar->kv("uptime", srs_fmt("%d", (int)((srs_get_system_time() - p->srs_startup_time) / SRS_UTIME_SECONDS))); } // The load of system, load every 1 minute, 1 means 1/1000. if (p->load_one_minutes > 0) { sugar->kv("load", srs_fmt("%d", (int)(p->load_one_minutes * 1000))); } } SrsRusage* r = srs_get_system_rusage(); SrsMemInfo* m = srs_get_meminfo(); if (r->ok && m->ok) { float self_mem_percent = 0; if (m->MemTotal > 0) { self_mem_percent = (float)(r->r.ru_maxrss / (double)m->MemTotal); } // The memory of SRS, 1 means 1/1000 if (self_mem_percent > 0) { sugar->kv("mem", srs_fmt("%d", (int)(self_mem_percent * 1000))); } } SrsProcSystemStat* s = srs_get_system_proc_stat(); if (s->ok) { // The cpu usage of system, 1 means 1/1000 if (s->percent > 0) { sugar->kv("cpu2", srs_fmt("%d", (int)(s->percent * 1000))); } } SrsNetworkRtmpServer* nrs = srs_get_network_rtmp_server(); if (nrs->ok) { // The number of connections of SRS. if (nrs->nb_conn_srs > 0) { sugar->kv("conn", srs_fmt("%d", nrs->nb_conn_srs)); } // The number of connections of system. if (nrs->nb_conn_sys > 0) { sugar->kv("conn2", srs_fmt("%d", nrs->nb_conn_sys)); } // The received kbps in 30s of SRS. if (nrs->rkbps_30s > 0) { sugar->kv("recv", srs_fmt("%d", nrs->rkbps_30s)); } // The sending out kbps in 30s of SRS. if (nrs->skbps_30s > 0) { sugar->kv("send", srs_fmt("%d", nrs->skbps_30s)); } } return err; } srs_error_t SrsClsClient::dump_streams(SrsClsSugars* sugars) { srs_error_t err = srs_success; // Ignore if disabled. if (!enabled_ || !stat_streams_) { return err; } // Whether it's time to report streams. static int nn_streams = -1; bool interval_ok = nn_streams == -1 || ++nn_streams >= streams_ratio_; if (interval_ok) { nn_streams = 0; } if (!interval_ok) { return err; } // Dumps all streams as sugars. SrsStatistic::instance()->dumps_cls_streams(sugars); return err; } SrsOtelExportTraceServiceRequest::SrsOtelExportTraceServiceRequest() { } SrsOtelExportTraceServiceRequest::~SrsOtelExportTraceServiceRequest() { for (vector::iterator it = spans_.begin(); it != spans_.end(); ++it) { SrsOtelResourceSpans* span = *it; srs_freep(span); } } SrsOtelResourceSpans* SrsOtelExportTraceServiceRequest::append() { SrsOtelResourceSpans* v = new SrsOtelResourceSpans(); spans_.push_back(v); return v; } uint64_t SrsOtelExportTraceServiceRequest::nb_bytes() { uint64_t nn = 0; for (vector::iterator it = spans_.begin(); it != spans_.end(); ++it) { SrsOtelResourceSpans* span = *it; nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(span); } return nn; } srs_error_t SrsOtelExportTraceServiceRequest::encode(SrsBuffer* b) { srs_error_t err = srs_success; // Encode each span. for (vector::iterator it = spans_.begin(); it != spans_.end(); ++it) { SrsOtelResourceSpans* span = *it; if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldObject)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufObject::encode(b, span)) != srs_success) { return srs_error_wrap(err, "encode span"); } } return err; } SrsOtelResourceSpans::SrsOtelResourceSpans() { resource_ = new SrsOtelResource(); } SrsOtelResourceSpans::~SrsOtelResourceSpans() { srs_freep(resource_); for (std::vector::iterator it = spans_.begin(); it != spans_.end(); ++it) { SrsOtelScopeSpans* span = *it; srs_freep(span); } } SrsOtelResource* SrsOtelResourceSpans::resource() { return resource_; } SrsOtelScopeSpans* SrsOtelResourceSpans::append() { SrsOtelScopeSpans* v = new SrsOtelScopeSpans(); spans_.push_back(v); return v; } uint64_t SrsOtelResourceSpans::nb_bytes() { uint64_t nn = SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(resource_); for (std::vector::iterator it = spans_.begin(); it != spans_.end(); ++it) { SrsOtelScopeSpans* span = *it; nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(span); } return nn; } srs_error_t SrsOtelResourceSpans::encode(SrsBuffer* b) { srs_error_t err = srs_success; // Encode the resource. if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldObject)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufObject::encode(b, resource_)) != srs_success) { return srs_error_wrap(err, "encode resource"); } // Encode scope spans. // Encode each group. for (std::vector::iterator it = spans_.begin(); it != spans_.end(); ++it) { SrsOtelScopeSpans* span = *it; if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldObject)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufObject::encode(b, span)) != srs_success) { return srs_error_wrap(err, "encode span"); } } return err; } SrsOtelResource::SrsOtelResource() { } SrsOtelResource::~SrsOtelResource() { for (vector::iterator it = attributes_.begin(); it != attributes_.end(); ++it) { SrsOtelAttribute* attribute = *it; srs_freep(attribute); } } SrsOtelResource* SrsOtelResource::add_addr(SrsOtelAttribute* v) { attributes_.push_back(v); return this; } uint64_t SrsOtelResource::nb_bytes() { uint64_t nn = 0; for (vector::iterator it = attributes_.begin(); it != attributes_.end(); ++it) { SrsOtelAttribute* attribute = *it; nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(attribute); } return nn; } srs_error_t SrsOtelResource::encode(SrsBuffer* b) { srs_error_t err = srs_success; // Encode attributes. for (vector::iterator it = attributes_.begin(); it != attributes_.end(); ++it) { SrsOtelAttribute* attribute = *it; if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldObject)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufObject::encode(b, attribute)) != srs_success) { return srs_error_wrap(err, "encode attribute"); } } return err; } SrsOtelAttribute::SrsOtelAttribute() { value_ = new SrsOtelAnyValue(); } SrsOtelAttribute::~SrsOtelAttribute() { srs_freep(value_); } const std::string& SrsOtelAttribute::key() { return key_; } SrsOtelAttribute* SrsOtelAttribute::kv(std::string k, std::string v) { SrsOtelAttribute* attr = new SrsOtelAttribute(); attr->key_ = k; attr->value_->set_string(v); return attr; } SrsOtelAttribute* SrsOtelAttribute::kvi(std::string k, int64_t v) { SrsOtelAttribute* attr = new SrsOtelAttribute(); attr->key_ = k; attr->value_->set_int(v); return attr; } uint64_t SrsOtelAttribute::nb_bytes() { uint64_t nn = SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(key_); nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(value_); return nn; } srs_error_t SrsOtelAttribute::encode(SrsBuffer* b) { srs_error_t err = srs_success; // Encode the key. if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldString)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufString::encode(b, key_)) != srs_success) { return srs_error_wrap(err, "encode key=%s", key_.c_str()); } // Encode the value. if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldObject)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufObject::encode(b, value_)) != srs_success) { return srs_error_wrap(err, "encode value"); } return err; } SrsOtelAnyValue::SrsOtelAnyValue() { used_field_id_ = 1; int_value_ = 0; } SrsOtelAnyValue::~SrsOtelAnyValue() { } SrsOtelAnyValue* SrsOtelAnyValue::set_string(const std::string& v) { string_value_ = v; used_field_id_ = 1; return this; } SrsOtelAnyValue* SrsOtelAnyValue::set_int(int64_t v) { int_value_ = v; used_field_id_ = 3; return this; } uint64_t SrsOtelAnyValue::nb_bytes() { uint64_t nn = 0; if (used_field_id_ == 1) nn = SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(string_value_); if (used_field_id_ == 3) nn = SrsProtobufKey::sizeof_key() + SrsProtobufVarints::sizeof_varint(int_value_); return nn; } srs_error_t SrsOtelAnyValue::encode(SrsBuffer* b) { srs_error_t err = srs_success; if (used_field_id_ == 1) { // Encode the string value. if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldString)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufString::encode(b, string_value_)) != srs_success) { return srs_error_wrap(err, "encode value=%s", string_value_.c_str()); } } else if (used_field_id_ == 3) { // Encode the int value. if ((err = SrsProtobufKey::encode(b, 3, SrsProtobufFieldVarint)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufVarints::encode(b, int_value_)) != srs_success) { return srs_error_wrap(err, "encode value=%" PRId64, int_value_); } } return err; } SrsOtelScopeSpans::SrsOtelScopeSpans() { scope_ = new SrsOtelScope(); } SrsOtelScopeSpans::~SrsOtelScopeSpans() { srs_freep(scope_); for (std::vector::iterator it = spans_.begin(); it != spans_.end(); ++it) { SrsOtelSpan* span = *it; srs_freep(span); } } SrsOtelScope* SrsOtelScopeSpans::scope() { return scope_; } SrsOtelScopeSpans* SrsOtelScopeSpans::swap(std::vector& spans) { spans_.swap(spans); return this; } int SrsOtelScopeSpans::size() { return (int)spans_.size(); } uint64_t SrsOtelScopeSpans::nb_bytes() { uint64_t nn = SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(scope_); for (std::vector::iterator it = spans_.begin(); it != spans_.end(); ++it) { SrsOtelSpan* span = *it; nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(span); } return nn; } srs_error_t SrsOtelScopeSpans::encode(SrsBuffer* b) { srs_error_t err = srs_success; // Encode the scope. if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldObject)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufObject::encode(b, scope_)) != srs_success) { return srs_error_wrap(err, "encode scope"); } // Encode each span. for (std::vector::iterator it = spans_.begin(); it != spans_.end(); ++it) { SrsOtelSpan* span = *it; if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldObject)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufObject::encode(b, span)) != srs_success) { return srs_error_wrap(err, "encode span"); } } return err; } SrsOtelScope::SrsOtelScope() { } SrsOtelScope::~SrsOtelScope() { } uint64_t SrsOtelScope::nb_bytes() { uint64_t nn = 0; nn = SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(name_); return nn; } srs_error_t SrsOtelScope::encode(SrsBuffer* b) { srs_error_t err = srs_success; // Encode the name. if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldString)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufString::encode(b, name_)) != srs_success) { return srs_error_wrap(err, "encode name=%s", name_.c_str()); } return err; } SrsOtelSpan::SrsOtelSpan() { start_time_unix_nano_ = 0; end_time_unix_nano_ = 0; status_ = new SrsOtelStatus(); } SrsOtelSpan::~SrsOtelSpan() { for (vector::iterator it = attributes_.begin(); it != attributes_.end(); ++it) { SrsOtelAttribute* attribute = *it; srs_freep(attribute); } for (vector::iterator it = events_.begin(); it != events_.end(); ++it) { SrsOtelEvent* event = *it; srs_freep(event); } for (vector::iterator it = links_.begin(); it != links_.end(); ++it) { SrsOtelLink* link = *it; srs_freep(link); } srs_freep(status_); } SrsOtelAttribute* SrsOtelSpan::attr(const std::string k) { for (vector::iterator it = attributes_.begin(); it != attributes_.end(); ++it) { SrsOtelAttribute* attribute = *it; if (attribute->key() == k) { return attribute; } } return NULL; } uint64_t SrsOtelSpan::nb_bytes() { uint64_t nn = 0; nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(trace_id_); nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(span_id_); if (!parent_span_id_.empty()) { nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(parent_span_id_); } nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(name_); nn += SrsProtobufKey::sizeof_key() + SrsProtobufVarints::sizeof_varint(kind_); nn += SrsProtobufKey::sizeof_key() + SrsProtobufFixed64::sizeof_int(start_time_unix_nano_); nn += SrsProtobufKey::sizeof_key() + SrsProtobufFixed64::sizeof_int(end_time_unix_nano_); for (vector::iterator it = attributes_.begin(); it != attributes_.end(); ++it) { SrsOtelAttribute* attribute = *it; nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(attribute); } for (vector::iterator it = events_.begin(); it != events_.end(); ++it) { SrsOtelEvent* event = *it; nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(event); } for (vector::iterator it = links_.begin(); it != links_.end(); ++it) { SrsOtelLink* link = *it; nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(link); } nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(status_); return nn; } srs_error_t SrsOtelSpan::encode(SrsBuffer* b) { srs_error_t err = srs_success; // Encode the trace id. if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldString)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufString::encode(b, trace_id_)) != srs_success) { return srs_error_wrap(err, "encode trace_id=%s", trace_id_.c_str()); } // Encode the span id. if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldString)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufString::encode(b, span_id_)) != srs_success) { return srs_error_wrap(err, "encode span_id=%s", span_id_.c_str()); } // Encode the parent span id. if (!parent_span_id_.empty()) { if ((err = SrsProtobufKey::encode(b, 4, SrsProtobufFieldString)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufString::encode(b, parent_span_id_)) != srs_success) { return srs_error_wrap(err, "encode parent_span_id=%s", parent_span_id_.c_str()); } } // Encode the name. if ((err = SrsProtobufKey::encode(b, 5, SrsProtobufFieldString)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufString::encode(b, name_)) != srs_success) { return srs_error_wrap(err, "encode name=%s", name_.c_str()); } // Encode the kind. if ((err = SrsProtobufKey::encode(b, 6, SrsProtobufFieldEnum)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufVarints::encode(b, kind_)) != srs_success) { return srs_error_wrap(err, "encode kind=%d", (int)kind_); } // Encode the start time. if ((err = SrsProtobufKey::encode(b, 7, SrsProtobufField64bit)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufFixed64::encode(b, start_time_unix_nano_)) != srs_success) { return srs_error_wrap(err, "encode start_time=%" PRId64, start_time_unix_nano_); } // Encode the end time. if ((err = SrsProtobufKey::encode(b, 8, SrsProtobufField64bit)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufFixed64::encode(b, end_time_unix_nano_)) != srs_success) { return srs_error_wrap(err, "encode end_time=%" PRId64, end_time_unix_nano_); } // Encode attribute if not empty. for (vector::iterator it = attributes_.begin(); it != attributes_.end(); ++it) { SrsOtelAttribute* attribute = *it; if ((err = SrsProtobufKey::encode(b, 9, SrsProtobufFieldObject)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufObject::encode(b, attribute)) != srs_success) { return srs_error_wrap(err, "encode attribute"); } } // Encode the events if not empty. for (vector::iterator it = events_.begin(); it != events_.end(); ++it) { SrsOtelEvent* event = *it; if ((err = SrsProtobufKey::encode(b, 11, SrsProtobufFieldObject)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufObject::encode(b, event)) != srs_success) { return srs_error_wrap(err, "encode event"); } } // Encode the links if not empty. for (vector::iterator it = links_.begin(); it != links_.end(); ++it) { SrsOtelLink* link = *it; if ((err = SrsProtobufKey::encode(b, 13, SrsProtobufFieldObject)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufObject::encode(b, link)) != srs_success) { return srs_error_wrap(err, "encode link"); } } // Encode the status. if ((err = SrsProtobufKey::encode(b, 15, SrsProtobufFieldObject)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufObject::encode(b, status_)) != srs_success) { return srs_error_wrap(err, "encode status"); } return err; } SrsOtelEvent::SrsOtelEvent() { time_ = srs_update_system_time(); } SrsOtelEvent::~SrsOtelEvent() { for (vector::iterator it = attributes_.begin(); it != attributes_.end(); ++it) { SrsOtelAttribute* attribute = *it; srs_freep(attribute); } } SrsOtelEvent* SrsOtelEvent::create(std::string v) { SrsOtelEvent* e = new SrsOtelEvent(); e->name_ = v; return e; } SrsOtelEvent* SrsOtelEvent::add_attr(SrsOtelAttribute* v) { attributes_.push_back(v); return this; } uint64_t SrsOtelEvent::nb_bytes() { uint64_t nn = 0; nn += SrsProtobufKey::sizeof_key() + SrsProtobufFixed64::sizeof_int(time_); nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(name_); for (vector::iterator it = attributes_.begin(); it != attributes_.end(); ++it) { SrsOtelAttribute* attribute = *it; nn += SrsProtobufKey::sizeof_key() + SrsProtobufObject::sizeof_object(attribute); } return nn; } srs_error_t SrsOtelEvent::encode(SrsBuffer* b) { srs_error_t err = srs_success; // Encode the time. if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufField64bit)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufFixed64::encode(b, time_)) != srs_success) { return srs_error_wrap(err, "encode time=%" PRId64, time_); } // Encode the name. if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldString)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufString::encode(b, name_)) != srs_success) { return srs_error_wrap(err, "encode key=%s", name_.c_str()); } // Encode attributes. for (vector::iterator it = attributes_.begin(); it != attributes_.end(); ++it) { SrsOtelAttribute* attribute = *it; if ((err = SrsProtobufKey::encode(b, 3, SrsProtobufFieldObject)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufObject::encode(b, attribute)) != srs_success) { return srs_error_wrap(err, "encode attribute"); } } return err; } SrsOtelLink::SrsOtelLink() { } SrsOtelLink::~SrsOtelLink() { } SrsOtelLink* SrsOtelLink::create() { return new SrsOtelLink(); } SrsOtelLink* SrsOtelLink::set_id(const std::string& trace_id, const std::string& span_id) { trace_id_ = trace_id; span_id_ = span_id; return this; } uint64_t SrsOtelLink::nb_bytes() { uint64_t nn = 0; nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(trace_id_); nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(span_id_); return nn; } srs_error_t SrsOtelLink::encode(SrsBuffer* b) { srs_error_t err = srs_success; // Encode the trace id. if ((err = SrsProtobufKey::encode(b, 1, SrsProtobufFieldString)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufString::encode(b, trace_id_)) != srs_success) { return srs_error_wrap(err, "encode trace_id=%s", trace_id_.c_str()); } // Encode the span id. if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldString)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufString::encode(b, span_id_)) != srs_success) { return srs_error_wrap(err, "encode span_id=%s", span_id_.c_str()); } return err; } SrsOtelStatus::SrsOtelStatus() { code_ = SrsApmStatusUnset; } SrsOtelStatus::~SrsOtelStatus() { } uint64_t SrsOtelStatus::nb_bytes() { uint64_t nn = 0; if (!message_.empty()) { nn += SrsProtobufKey::sizeof_key() + SrsProtobufString::sizeof_string(message_); } if (code_ != SrsApmStatusUnset) { nn += SrsProtobufKey::sizeof_key() + SrsProtobufVarints::sizeof_varint(code_); } return nn; } srs_error_t SrsOtelStatus::encode(SrsBuffer* b) { srs_error_t err = srs_success; // Encode the message. if (!message_.empty()) { if ((err = SrsProtobufKey::encode(b, 2, SrsProtobufFieldString)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufString::encode(b, message_)) != srs_success) { return srs_error_wrap(err, "encode message=%s", message_.c_str()); } } // Encode the status. if (code_ != SrsApmStatusUnset) { if ((err = SrsProtobufKey::encode(b, 3, SrsProtobufFieldEnum)) != srs_success) { return srs_error_wrap(err, "key"); } if ((err = SrsProtobufVarints::encode(b, code_)) != srs_success) { return srs_error_wrap(err, "encode kind=%d", (int) code_); } } return err; } SrsApmClient* _srs_apm = NULL; SrsApmContext::SrsApmContext(const std::string& name) { name_ = name; kind_ = SrsApmKindUnspecified; set_trace_id(srs_random_str(16)); set_span_id(srs_random_str(8)); // We must not use time cache, or span render might fail. start_time_ = srs_update_system_time(); ended_ = false; status_ = SrsApmStatusUnset; err_ = srs_success; span_ = NULL; parent_ = NULL; } SrsApmContext::~SrsApmContext() { srs_freep(err_); // Span is not created by context, so should never free it here. if (span_) { span_->ctx_ = NULL; span_ = NULL; } // Free all child context. for (vector::iterator it = childs_.begin(); it != childs_.end(); ++it) { SrsApmContext* ctx = *it; srs_freep(ctx); } for (vector::iterator it = attributes_.begin(); it != attributes_.end(); ++it) { SrsOtelAttribute* attribute = *it; srs_freep(attribute); } for (vector::iterator it = links_.begin(); it != links_.end(); ++it) { SrsOtelLink* link = *it; srs_freep(link); } } void SrsApmContext::set_trace_id(std::string v) { trace_id_ = v; str_trace_id_ = srs_string_dumps_hex(trace_id_.data(), trace_id_.length(), INT_MAX, 0, INT_MAX, 0); } void SrsApmContext::set_span_id(std::string v) { span_id_ = v; str_span_id_ = srs_string_dumps_hex(span_id_.data(), span_id_.length(), INT_MAX, 0, INT_MAX, 0); } const char* SrsApmContext::format_trace_id() { return str_trace_id_.c_str(); } const char* SrsApmContext::format_span_id() { return str_span_id_.c_str(); } SrsApmContext* SrsApmContext::root() { // Root is node that has no parent. if (!parent_) return this; // Use cached root or parent root, literally they should be the same. return parent_->root(); } void SrsApmContext::set_parent(SrsApmContext* parent) { if (ended_) return; parent_ = parent; if (parent) { set_trace_id(parent->trace_id_); parent_span_id_ = parent->span_id_; parent->childs_.push_back(this); } } void SrsApmContext::set_status(SrsApmStatus status, const std::string& description) { if (ended_) return; status_ = status; if (status == SrsApmStatusError) { description_ = description; } } bool SrsApmContext::all_ended() { if (!ended_) return false; for (vector::iterator it = childs_.begin(); it != childs_.end(); ++it) { SrsApmContext* ctx = *it; if (!ctx->all_ended()) return false; } return true; } int SrsApmContext::count_spans() { int nn = span_ ? 1 : 0; for (vector::iterator it = childs_.begin(); it != childs_.end(); ++it) { SrsApmContext* ctx = *it; nn += ctx->count_spans(); } return nn; } void SrsApmContext::update_trace_id(std::string v) { if (ended_) return; set_trace_id(v); for (vector::iterator it = childs_.begin(); it != childs_.end(); ++it) { SrsApmContext* ctx = *it; ctx->set_trace_id(v); } } void SrsApmContext::link(SrsApmContext* to) { if (ended_) return; links_.push_back(SrsOtelLink::create()->set_id(to->trace_id_, to->span_id_)); attributes_.push_back(SrsOtelAttribute::kv("link_trace", to->str_trace_id_)); attributes_.push_back(SrsOtelAttribute::kv("link_span", to->str_span_id_)); to->links_.push_back(SrsOtelLink::create()->set_id(trace_id_, span_id_)); to->attributes_.push_back(SrsOtelAttribute::kv("link_trace", str_trace_id_)); to->attributes_.push_back(SrsOtelAttribute::kv("link_span", str_span_id_)); srs_trace("APM: Link span %s(trace=%s, span=%s) with %s(trace=%s, span=%s)", name_.c_str(), str_trace_id_.c_str(), str_span_id_.c_str(), to->name_.c_str(), to->str_trace_id_.c_str(), to->str_span_id_.c_str() ); } void SrsApmContext::end() { if (ended_) return; ended_ = true; SrsOtelSpan* otel = new SrsOtelSpan(); otel->name_ = name_; otel->trace_id_ = trace_id_; otel->span_id_ = span_id_; otel->parent_span_id_ = parent_span_id_; otel->kind_ = kind_; otel->start_time_unix_nano_ = start_time_ * 1000; // We must not use time cache, or span render might fail. otel->end_time_unix_nano_ = srs_update_system_time() * 1000; otel->status_->code_ = status_; otel->status_->message_ = description_; otel->attributes_.swap(attributes_); otel->links_.swap(links_); // Insert server ip if not exists. if (!otel->attr("ip")) { otel->attributes_.push_back(SrsOtelAttribute::kv("ip", srs_get_public_internet_address())); } if (!otel->attr("component")) { otel->attributes_.push_back(SrsOtelAttribute::kv("component", "srs")); } // See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/exceptions.md if (err_ != srs_success) { // Set the events for detail about the error. otel->events_.push_back(SrsOtelEvent::create("exception") ->add_attr(SrsOtelAttribute::kv("exception.type", srs_fmt("code_%d_%s", srs_error_code(err_), srs_error_code_str(err_).c_str()))) ->add_attr(SrsOtelAttribute::kv("exception.message", srs_error_summary(err_))) ->add_attr(SrsOtelAttribute::kv("exception.stacktrace", srs_error_desc(err_))) ); // We also use HTTP status code for APM to class the error. Note that it also works for non standard HTTP status // code, for example, SRS error codes. otel->attributes_.push_back(SrsOtelAttribute::kv("http.status_code", srs_fmt("%d", srs_error_code(err_)))); } _srs_apm->snapshot(otel); srs_info("APM: Snapshot name=%s, trace=%s, span=%s", name_.c_str(), trace_id_.c_str(), span_id_.c_str()); } SrsApmSpan::SrsApmSpan(const std::string& name) { child_ = false; // Create the context for this span. ctx_ = new SrsApmContext(name); ctx_->span_ = this; // Default to internal span. ctx_->kind_ = SrsApmKindInternal; } SrsApmSpan::~SrsApmSpan() { end(); // Context might be freed by other span. if (!ctx_) return; // Span is not available. ctx_->span_ = NULL; // Dispose the context tree when all spans are ended. SrsApmContext* root = ctx_->root(); // Only free the tree when free the last span, because we might create new span even all spans are ended only if the // root span has not been freed, for example, when RTMP client cycle done, we create a final span. if (root->count_spans() == 0 && root->all_ended()) { srs_freep(root); } } const char* SrsApmSpan::format_trace_id() { return ctx_ ? ctx_->format_trace_id() : ""; } const char* SrsApmSpan::format_span_id() { return ctx_ ? ctx_->format_span_id() : ""; } ISrsApmSpan* SrsApmSpan::set_name(const std::string& name) { if (ctx_) ctx_->name_ = name; return this; } ISrsApmSpan* SrsApmSpan::set_kind(SrsApmKind kind) { if (ctx_) ctx_->kind_ = kind; return this; } ISrsApmSpan* SrsApmSpan::as_child(ISrsApmSpan* parent) { // Should not be child of multiple parent spans. if (child_) return this; // For child, always load parent from context. SrsApmSpan* span = dynamic_cast(parent); if (span) { ctx_->set_parent(span->ctx_); child_ = true; } return this; } ISrsApmSpan* SrsApmSpan::set_status(SrsApmStatus status, const std::string& description) { if (ctx_) ctx_->set_status(status, description); return this; } ISrsApmSpan* SrsApmSpan::record_error(srs_error_t err) { if (ctx_) ctx_->err_ = srs_error_copy(err); return this; } ISrsApmSpan* SrsApmSpan::attr(const std::string& k, const std::string& v) { if (ctx_) ctx_->attributes_.push_back(SrsOtelAttribute::kv(k, v)); return this; } ISrsApmSpan* SrsApmSpan::link(ISrsApmSpan* span) { SrsApmSpan* to = dynamic_cast(span); if (ctx_ && span) ctx_->link(to->ctx_); return this; } ISrsApmSpan* SrsApmSpan::end() { if (ctx_) ctx_->end(); return this; } ISrsApmSpan* SrsApmSpan::extract(SrsAmf0Object* h) { if (!ctx_) return this; SrsAmf0Any* prop = h->ensure_property_string("Traceparent"); if (!prop) return this; std::string trace_parent = prop->to_str(); vector vs = srs_string_split(trace_parent, "-"); if (vs.size() != 4) return this; // Update trace id for span and all its child spans. ctx_->update_trace_id(vs[1]); // Update the parent span id of span, note that the parent is NULL. ctx_->parent_span_id_ = vs[2]; return this; } ISrsApmSpan* SrsApmSpan::inject(SrsAmf0Object* h) { if (!ctx_) return this; string v = text_propagator(); if (!v.empty()) h->set("Traceparent", SrsAmf0Any::str(v.c_str())); return this; } std::string SrsApmSpan::text_propagator() { // FlagsSampled is a bitmask with the sampled bit set. A SpanContext // with the sampling bit set means the span is sampled. const uint8_t FlagsSampled = 0x01; const uint8_t supportedVersion = 0; static char buf[256]; // For text based propagation, for example, HTTP header "Traceparent: 00-bb8dedf16c53ab4b6ceb1f4ca6d985bb-29247096662468ab-01" // About the "%.2x", please see https://www.quora.com/What-does-2x-do-in-C-code for detail. int nn = snprintf(buf, sizeof(buf), "%.2x-%s-%s-%.2x", supportedVersion, ctx_->trace_id_.c_str(), ctx_->span_id_.c_str(), FlagsSampled); if (nn > 0 && nn < (int)sizeof(buf)) { return string(buf, nn); } return ""; } static int _srs_apm_key = -1; void _srs_apm_destructor(void* arg) { // We don't free the span, because it's not created by us. // Note that it's safe because keys will be reset when coroutine is terminated. //SrsApmSpan* span = (SrsApmSpan*)arg; } ISrsApmSpan* SrsApmSpan::store() { ISrsApmSpan* span = _srs_apm->load(); if (span == this) return this; int r0 = srs_thread_setspecific(_srs_apm_key, this); srs_assert(r0 == 0); return this; } ISrsApmSpan* SrsApmSpan::load() { if (_srs_apm_key < 0) { int r0 = srs_key_create(&_srs_apm_key, _srs_apm_destructor); srs_assert(r0 == 0); } void* span = srs_thread_getspecific(_srs_apm_key); return (ISrsApmSpan*)span; } SrsApmClient::SrsApmClient() { enabled_ = false; nn_spans_ = 0; } SrsApmClient::~SrsApmClient() { for (vector::iterator it = spans_.begin(); it != spans_.end(); ++it) { SrsOtelSpan* span = *it; srs_freep(span); } } srs_error_t SrsApmClient::initialize() { srs_error_t err = srs_success; enabled_ = _srs_config->get_tencentcloud_apm_enabled(); if (!enabled_) { srs_trace("TencentCloud APM is disabled"); return err; } team_ = _srs_config->get_tencentcloud_apm_team(); token_ = _srs_config->get_tencentcloud_apm_token(); endpoint_ = _srs_config->get_tencentcloud_apm_endpoint(); service_name_ = _srs_config->get_tencentcloud_apm_service_name(); debug_logging_ = _srs_config->get_tencentcloud_apm_debug_logging(); srs_trace("Initialize TencentCloud APM, team=%s, token=%dB, endpoint=%s, service_name=%s, debug_logging=%d", team_.c_str(), token_.length(), endpoint_.c_str(), service_name_.c_str(), debug_logging_); // Check authentication, the team or token. if (team_.empty()) { return srs_error_new(ERROR_APM_AUTH, "No authentication team for APM"); } if (token_.empty()) { return srs_error_new(ERROR_APM_AUTH, "No authentication token for APM"); } // Please note that 4317 is for GRPC/HTTP2, while SRS only support HTTP and the port shoule be 55681. if (srs_string_contains(endpoint_, ":4317")) { return srs_error_new(ERROR_APM_ENDPOINT, "Port 4317 is for GRPC over HTTP2 for APM"); } return err; } srs_error_t SrsApmClient::report() { srs_error_t err = do_report(); if (err != srs_success) { return srs_error_wrap(err, "team=%s, token=%dB", team_.c_str(), token_.length()); } return err; } srs_error_t SrsApmClient::do_report() { srs_error_t err = srs_success; if (spans_.empty()) return err; // Update statistaic for APM. nn_spans_ += spans_.size(); SrsOtelExportTraceServiceRequest* sugar = new SrsOtelExportTraceServiceRequest(); SrsAutoFree(SrsOtelExportTraceServiceRequest, sugar); SrsOtelResourceSpans* rs = sugar->append(); // See https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/resource/semantic_conventions rs->resource()->add_addr(SrsOtelAttribute::kv("service.name", service_name_)); // For Tencent Cloud APM authentication, see https://console.cloud.tencent.com/apm/monitor/access rs->resource()->add_addr(SrsOtelAttribute::kv("token", token_)); // For Tencent Cloud APM debugging, see https://console.cloud.tencent.com/apm/monitor/team rs->resource()->add_addr(SrsOtelAttribute::kv("tapm.team", team_)); SrsOtelScopeSpans* spans = rs->append(); spans->scope()->name_ = "srs"; spans->swap(spans_); // Send out over HTTP1. uint64_t size = sugar->nb_bytes(); if (size >= 5 * 1024 * 1024) { return srs_error_new(ERROR_APM_EXCEED_SIZE, "exceed 5MB actual %d", size); } char* buf = new char[size]; SrsAutoFreeA(char, buf); memset(buf, 0, size); SrsBuffer b(buf, size); if ((err = sugar->encode(&b)) != srs_success) { return srs_error_wrap(err, "encode log"); } string body(buf, size); // Write a CLS log to service specified by url. string url = "http://" + endpoint_ + "/v1/traces"; SrsHttpUri uri; if ((err = uri.initialize(url)) != srs_success) { return srs_error_wrap(err, "http: post failed. url=%s", url.c_str()); } SrsHttpClient http; if ((err = http.initialize(uri.get_schema(), uri.get_host(), uri.get_port())) != srs_success) { return srs_error_wrap(err, "http: init client"); } // Setup the request. if (true) { http.set_header("Content-Type", "application/x-protobuf"); } string path = uri.get_path(); if (!uri.get_query().empty()) { path += "?" + uri.get_query(); } // Start request and parse response. ISrsHttpMessage* msg = NULL; if ((err = http.post(path, body, &msg)) != srs_success) { return srs_error_wrap(err, "http: client post"); } SrsAutoFree(ISrsHttpMessage, msg); string res; uint16_t code = msg->status_code(); if ((err = msg->body_read_all(res)) != srs_success) { return srs_error_wrap(err, "http: body read"); } // ensure the http status is ok. if (code != SRS_CONSTS_HTTP_OK && code != SRS_CONSTS_HTTP_Created) { return srs_error_new(ERROR_HTTP_STATUS_INVALID, "http: status %d, body is %s", code, res.c_str()); } if (debug_logging_) { string server_id = SrsStatistic::instance()->server_id(); srs_trace("APM write team=%s, token=%dB, logs=%d, size=%dB, server_id=%s", team_.c_str(), token_.length(), spans->size(), body.length(), server_id.c_str()); } return err; } bool SrsApmClient::enabled() { return enabled_; } uint64_t SrsApmClient::nn_spans() { return nn_spans_; } ISrsApmSpan* SrsApmClient::span(const std::string& name) { if (!enabled_) return new ISrsApmSpan(); return new SrsApmSpan(name); } ISrsApmSpan* SrsApmClient::dummy() { return new ISrsApmSpan(); } ISrsApmSpan* SrsApmClient::extract(ISrsApmSpan* v, SrsAmf0Object* h) { SrsApmSpan* span = dynamic_cast(v); if (span) span->extract(h); return v; } ISrsApmSpan* SrsApmClient::inject(ISrsApmSpan* v, SrsAmf0Object* h) { SrsApmSpan* span = dynamic_cast(v); if (span) span->inject(h); return v; } void SrsApmClient::store(ISrsApmSpan* v) { SrsApmSpan* span = dynamic_cast(v); if (span) span->store(); } ISrsApmSpan* SrsApmClient::load() { static ISrsApmSpan* dummy = new ISrsApmSpan(); ISrsApmSpan* span = SrsApmSpan::load(); return span ? span : dummy; } void SrsApmClient::snapshot(SrsOtelSpan* span) { spans_.push_back(span); } #endif