标签 跟踪 下的文章

研究发现所有的网络拥塞控制算法都可能是非常不公平的

尽管在过去大约 40 年中提出了数百种控制计算机网络“拥堵”延迟的关键算法,但没有明显的赢家。一项新的研究发现,每种拥塞控制算法都无法解决“饿死”的问题,即让一些用户占据了所有的带宽,而至少有一个人基本上得不到带宽。拥塞控制算法依靠数据包的丢失和延迟作为细节来推断拥塞,并决定发送数据的速度。然而,数据包可能因为网络拥堵以外的原因而丢失和延迟 —— 这被称之为网络“抖动”。而拥塞控制算法无法区分由拥塞和抖动造成的延迟的区别,由抖动引起的延迟是不可预测的,最终会导致出现饥饿的情况。

消息来源:Slashdot
老王点评:这就是算法的不公平,或许,限定在算法的层面上,并无一个完美的解决方案。

TikTok 否认内置浏览器记录用户输入

研究发现,流行 iOS 应用如 TikTok、Instagram、FB Messenger 和 Facebook 都支持用户在应用内打开浏览器访问第三方网站,期间会植入跟踪代码。以 TikTok 为例,它会记录用户的所有键盘输入,包括密码信用卡信息等敏感数据,以及屏幕上的每一次点击。从技术上说这相当于安装了一个键盘记录器。在这一报道发表之后,TikTok 发表声明,证实该功能存在,但 TikTok 并没有利用这些代码。TikTok 表示应用内浏览器旨在提供更好的用户体验,它植入第三方网站的 JS 代码是为了调试、排错和性能监视。

消息来源:Solidot
老王点评:植入这种第三方 SDK,即便是没启用,但是谁知道你什么时候会启用呢?

美国绝大部分雇主正在跟踪员工的生产力

根据《纽约时报》的调查,美国 10 个最大的私人雇主中,有 8 个正在为他们的雇员追踪生产力指标。其中一些软件测量活动时间,观察键盘停顿,甚至默默地计算击键次数。他们看到了从写电子邮件的时间到键盘活动的一切。如果员工没有达到预期的目标,就会产生影响:一个催促的通知,一个跳过的奖金,或者一个在家工作的日子被剥夺。对于在返回办公室的斗争中投降的雇主来说,这种监控是保持控制感的一种方式。

消息来源:Slashdot
老王点评:这可真是“工具人”,我觉得这和用代码行数来统计程序员的绩效具有“异曲同工之妙”。

这会成为放弃基于 Chromium 的浏览器并开始使用 Firefox 的一个理由吗?也许吧,决定权在你。

Chrome 扩展追踪器

即使你有了所有的隐私扩展和各种保护功能,别人仍然有方法可以识别你或跟踪你。

请注意,并非所有浏览器都是如此,本文中,我们主要关注基于 Chromium 的浏览器,并将谷歌 Chrome 作为“主要嫌疑人”。

以前,在 Chromium 浏览器上,尽管别人已经能够检测到你已安装的扩展程序,但许多扩展程序都实施了某些保护措施来防止这种检测。

然而,一位名为 “z0ccc” 的安全研究人员发现了一种检测已安装 Chrome 浏览器扩展程序的新方法,该方法可进一步用于通过“浏览器指纹识别”来跟踪你

如果你还不知道的话: 浏览器指纹识别 Browser Fingerprinting 是指收集有关你的设备/浏览器的各种信息,以创建唯一的指纹 ID(哈希),从而在互联网上识别你的一种跟踪方法。“各种信息”包括:浏览器名称、版本、操作系统、已安装的扩展程序、屏幕分辨率和类似的技术数据。

这听起来像是一种无害的数据收集技术,但可以使用这种跟踪方法在线跟踪你。

检测谷歌 Chrome 扩展

研究人员发布了一个开源项目 “Extension Fingerprints”,你可以使用它来测试你安装的 Chrome 扩展是否能被检测到。

新技术涉及一种“时间差”方法,该工具比较了扩展程序获取资源的时间。与浏览器上未安装的其他扩展相比,受保护的扩展需要更多时间来获取资源。因此,这有助于从 1000 多个扩展列表中识别出一些扩展。

关键是:即使有了各种新的进步和技术来防止跟踪,Chrome 网上应用店的扩展也可以被检测到。

并且,在检测到已安装的扩展程序后,别人可以就使用浏览器指纹识别,对你进行在线跟踪。

令人惊讶的是,即使你安装有 uBlocker、AdBlocker、或 Privacy Badger(一些流行的以隐私为重点的扩展程序)之类的扩展程序,使用了这种方法,它们也都可以被检测到。

你可以在它的 GitHub 页面 上查看所有技术细节。如果你想自己测试它,请前往它的 扩展指纹识别网站 自行检查。

拯救 Firefox?

嗯,似乎是的,毕竟我出于各种原因,不断回到 Firefox

这个新发现的(跟踪)方法应该适用于所有基于 Chromium 的浏览器。我在 Brave 和谷歌 Chrome 上都测试了这个方法。研究人员还提到,该工具不能在使用微软应用商店中的扩展的微软 Edge 上工作。但是,相同的跟踪方法仍然有效。

正如研究人员指出,Mozilla Firefox 可以避免这种情况,因为每个浏览器实例的扩展 ID 都是唯一的。


via: https://news.itsfoss.com/chrome-extension-tracking/

作者:Ankush Das 选题:lkxed 译者:lkxed 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Grafana Tempo 是一个新的开源、大容量分布式跟踪后端。

 title=

Grafana 的 Tempo 是出自 Grafana 实验室的一个简单易用、大规模的、分布式的跟踪后端。Tempo 集成了 GrafanaPrometheus 以及 Loki,并且它只需要对象存储进行操作,因此成本低廉,操作简单。

我从一开始就参与了这个开源项目,所以我将介绍一些关于 Tempo 的基础知识,并说明为什么云原生社区会注意到它。

分布式跟踪

想要收集对应用程序请求的遥测数据是很常见的。但是在现在的服务器中,单个应用通常被分割为多个微服务,可能运行在几个不同的节点上。

分布式跟踪是一种获得关于应用的性能细粒度信息的方式,该应用程序可能由离散的服务组成。当请求到达一个应用时,它提供了该请求的生命周期的统一视图。Tempo 的分布式跟踪可以用于单体应用或微服务应用,它提供 请求范围的信息,使其成为可观察性的第三个支柱(另外两个是度量和日志)。

接下来是一个分布式跟踪系统生成应用程序甘特图的示例。它使用 Jaeger HotROD 的演示应用生成跟踪,并把它们存到 Grafana 云托管的 Tempo 上。这个图展示了按照服务和功能划分的请求处理时间。

 title=

减少索引的大小

在丰富且定义良好的数据模型中,跟踪包含大量信息。通常,跟踪后端有两种交互:使用元数据选择器(如服务名或者持续时间)筛选跟踪,以及筛选后的可视化跟踪。

为了加强搜索,大多数的开源分布式跟踪框架会对跟踪中的许多字段进行索引,包括服务名称、操作名称、标记和持续时间。这会导致索引很大,并迫使你使用 Elasticsearch 或者 Cassandra 这样的数据库。但是,这些很难管理,而且大规模运营成本很高,所以我在 Grafana 实验室的团队开始提出一个更好的解决方案。

在 Grafana 中,我们的待命调试工作流从使用指标报表开始(我们使用 Cortex 来存储我们应用中的指标,它是一个云原生基金会孵化的项目,用于扩展 Prometheus),深入研究这个问题,筛选有问题服务的日志(我们将日志存储在 Loki 中,它就像 Prometheus 一样,只不过 Loki 是存日志的),然后查看跟踪给定的请求。我们意识到,我们过滤时所需的所有索引信息都可以在 Cortex 和 Loki 中找到。但是,我们需要一个强大的集成,以通过这些工具实现跟踪的可发现性,并需要一个很赞的存储,以根据跟踪 ID 进行键值查找。

这就是 Grafana Tempo 项目的开始。通过专注于给定检索跟踪 ID 的跟踪,我们将 Tempo 设计为最小依赖性、大容量、低成本的分布式跟踪后端。

操作简单,性价比高

Tempo 使用对象存储后端,这是它唯一的依赖。它既可以被用于单一的二进制下,也可以用于微服务模式(请参考仓库中的 例子,了解如何轻松上手)。使用对象存储还意味着你可以存储大量的应用程序的痕迹,而无需任何采样。这可以确保你永远不会丢弃那百万分之一的出错或具有较高延迟的请求的跟踪。

与开源工具的强大集成

Grafana 7.3 包括了 Tempo 数据源,这意味着你可以在 Grafana UI 中可视化来自Tempo 的跟踪。而且,Loki 2.0 的新查询特性 使得 Tempo 中的跟踪更简单。为了与 Prometheus 集成,该团队正在添加对 范例 exemplar 的支持,范例是可以添加到时间序列数据中的高基数元数据信息。度量存储后端不会对它们建立索引,但是你可以在 Grafana UI 中检索和显示度量值。尽管范例可以存储各种元数据,但是在这个用例中,存储跟踪 ID 是为了与 Tempo 紧密集成。

这个例子展示了使用带有请求延迟直方图的范例,其中每个范例数据点都链接到 Tempo 中的一个跟踪。

 title=

元数据一致性

作为容器化应用程序运行的应用发出的遥测数据通常具有一些相关的元数据。这可以包括集群 ID、命名空间、吊舱 IP 等。这对于提供基于需求的信息是好的,但如果你能将元数据中包含的信息用于生产性的东西,那就更好了。 例如,你可以使用 Grafana 云代理将跟踪信息导入 Tempo 中,代理利用 Prometheus 服务发现机制轮询 Kubernetes API 以获取元数据信息,并且将这些标记添加到应用程序发出的跨域数据中。由于这些元数据也在 Loki 中也建立了索引,所以通过元数据转换为 Loki 标签选择器,可以很容易地从跟踪跳转到查看给定服务的日志。

下面是一个一致元数据的示例,它可用于Tempo跟踪中查看给定范围的日志。

云原生

Grafana Tempo 可以作为容器化应用,你可以在如 Kubernetes、Mesos 等编排引擎上运行它。根据获取/查询路径上的工作负载,各种服务可以水平伸缩。你还可以使用云原生的对象存储,如谷歌云存储、Amazon S3 或者 Tempo Azure 博客存储。更多的信息,请阅读 Tempo 文档中的 架构部分

试一试 Tempo

如果这对你和我们一样有用,可以 克隆 Tempo 仓库试一试。


via: https://opensource.com/article/21/2/tempo-distributed-tracing

作者:Annanay Agarwal 选题:lujun9972 译者:RiaXu 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

了解分布式跟踪中的主要体系结构决策,以及各部分如何组合在一起。

早在十年前,认真研究过分布式跟踪基本上只有学者和一小部分大型互联网公司中的人。对于任何采用微服务的组织来说,它如今成为一种筹码。其理由是确立的:微服务通常会发生让人意想不到的错误,而分布式跟踪则是描述和诊断那些错误的最好方法。

也就是说,一旦你准备将分布式跟踪集成到你自己的应用程序中,你将很快意识到对于不同的人来说“ 分布式跟踪 Distributed Tracing ”一词意味着不同的事物。此外,跟踪生态系统里挤满了具有相似内容的重叠项目。本文介绍了分布式跟踪系统中四个(可能)独立的功能模块,并描述了它们间将如何协同工作。

分布式跟踪:一种思维模型

大多数用于跟踪的思维模型来源于 Google 的 Dapper 论文OpenTracing 使用相似的术语,因此,我们从该项目借用了以下术语:

 title=

  • 跟踪 Trace :事物在分布式系统运行的过程描述。
  • 跨度 Span :一种命名的定时操作,表示工作流的一部分。跨度可接受键值对标签以及附加到特定跨度实例的细粒度的、带有时间戳的结构化日志。
  • 跨度上下文 Span context :携带分布式事务的跟踪信息,包括当它通过网络或消息总线将服务传递给服务时。跨度上下文包含跟踪标识符、跨度标识符以及跟踪系统所需传播到下游服务的任何其他数据。

如果你想要深入研究这种思维模式的细节,请仔细参照 OpenTracing 技术规范

四大功能模块

从应用层分布式跟踪系统的观点来看,现代软件系统架构如下图所示:

 title=

现代软件系统的组件可分为三类:

  • 应用程序和业务逻辑:你的代码。
  • 广泛共享库:他人的代码
  • 广泛共享服务:他人的基础架构

这三类组件有着不同的需求,驱动着监控应用程序的分布式跟踪系统的设计。最终的设计得到了四个重要的部分:

  • 跟踪检测 API A tracing instrumentation API :修饰应用程序代码
  • 线路协议 Wire protocol :在 RPC 请求中与应用程序数据一同发送的规定
  • 数据协议 Data protocol :将异步信息(带外)发送到你的分析系统的规定
  • 分析系统 Analysis system :用于处理跟踪数据的数据库和交互式用户界面

为了更深入的解释这个概念,我们将深入研究驱动该设计的细节。如果你只需要我的一些建议,请跳转至下方的四大解决方案。

需求,细节和解释

应用程序代码、共享库以及共享式服务在操作上有显著的差别,这种差别严重影响了对其进行检测的请求操作。

检测应用程序代码和业务逻辑

在任何特定的微服务中,由微服务开发者编写的大部分代码是应用程序或者商业逻辑。这部分代码规定了特定区域的操作。通常,它包含任何特殊、独一无二的逻辑判断,这些逻辑判断首先证明了创建新型微服务的合理性。基本上按照定义,该代码通常不会在多个服务中共享或者以其他方式出现。

也即是说你仍需了解它,这也意味着需要以某种方式对它进行检测。一些监控和跟踪分析系统使用 黑盒代理 black-box agents 自动检测代码,另一些系统更想使用显式的白盒检测工具。对于后者,抽象跟踪 API 提供了许多对于微服务的应用程序代码来说更为实用的优势:

  • 抽象 API 允许你在不重新编写检测代码的条件下换新的监视工具。你可能想要变更云服务提供商、供应商和监测技术,而一大堆不可移植的检测代码将会为该过程增加有意义的开销和麻烦。
  • 事实证明,除了生产监控之外,该工具还有其他有趣的用途。现有的项目使用相同的跟踪工具来驱动测试工具、分布式调试器、“混沌工程”故障注入器和其他元应用程序。
  • 但更重要的是,若将应用程序组件提取到共享库中要怎么办呢?由上述内容可得到结论:

检测共享库

在大多数应用程序中出现的实用程序代码(处理网络请求、数据库调用、磁盘写操作、线程、并发管理等)通常情况下是通用的,而非特别应用于某个特定应用程序。这些代码会被打包成库和框架,而后就可以被装载到许多的微服务上并且被部署到多种不同的环境中。

其真正的不同是:对于共享代码,其他人则成为了使用者。大多数用户有不同的依赖关系和操作风格。如果尝试去使用该共享代码,你将会注意到几个常见的问题:

  • 你需要一个 API 来编写检测。然而,你的库并不知道你正在使用哪个分析系统。会有多种选择,并且运行在相同应用下的所有库无法做出不兼容的选择。
  • 由于这些包封装了所有网络处理代码,因此从请求报头注入和提取跨度上下文的任务往往指向 RPC 库。然而,共享库必须了解到每个应用程序正在使用哪种跟踪协议。
  • 最后,你不想强制用户使用相互冲突的依赖项。大多数用户有不同的依赖关系和操作风格。即使他们使用 gRPC,绑定的 gRPC 版本是否相同?因此任何你的库附带用于跟踪的监控 API 必定是免于依赖的。

因此,一个(a)没有依赖关系、(b)与线路协议无关、(c)使用流行的供应商和分析系统的抽象 API 应该是对检测共享库代码的要求。

检测共享式服务

最后,有时整个服务(或微服务集合体)的通用性足以使许多独立的应用程序使用它们。这种共享式服务通常由第三方托管和管理,例如缓存服务器、消息队列以及数据库。

从应用程序开发者的角度来看,理解共享式服务本质上是黑盒子是极其重要的。它不可能将你的应用程序监控注入到共享式服务。恰恰相反,托管服务通常会运行它自己的监控方案。

四个方面的解决方案

因此,抽象的跟踪应用程序接口将会帮助库发出数据并且注入/抽取跨度上下文。标准的线路协议将会帮助黑盒服务相互连接,而标准的数据格式将会帮助分离的分析系统合并其中的数据。让我们来看一下部分有希望解决这些问题的方案。

跟踪 API:OpenTracing 项目

如你所见,我们需要一个跟踪 API 来检测应用程序代码。为了将这种工具扩展到大多数进行跨度上下文注入和提取的共享库中,则必须以某种关键方式对 API 进行抽象。

OpenTracing 项目主要针对解决库开发者的问题,OpenTracing 是一个与供应商无关的跟踪 API,它没有依赖关系,并且迅速得到了许多监控系统的支持。这意味着,如果库附带了内置的本地 OpenTracing 工具,当监控系统在应用程序启动连接时,跟踪将会自动启动。

就个人而言,作为一个已经编写、发布和操作开源软件十多年的人,在 OpenTracing 项目上工作并最终解决这个观察性的难题令我十分满意。

除了 API 之外,OpenTracing 项目还维护了一个不断增长的工具列表,其中一些可以在这里找到。如果你想参与进来,无论是通过提供一个检测插件,对你自己的 OSS 库进行本地测试,或者仅仅只想问个问题,都可以通过 Gitter 向我们打招呼。

线路协议: HTTP 报头 trace-context

为了监控系统能进行互操作,以及减轻从一个监控系统切换为另外一个时带来的迁移问题,需要标准的线路协议来传播跨度上下文。

w3c 分布式跟踪上下文社区小组在努力制定此标准。目前的重点是制定一系列标准的 HTTP 报头。该规范的最新草案可以在此处找到。如果你对此小组有任何的疑问,邮件列表Gitter 聊天室是很好的解惑地点。

(LCTT 译注:本文原文发表于 2018 年 5 月,可能现在社区已有不同进展)

数据协议 (还未出现!!)

对于黑盒服务,在无法安装跟踪程序或无法与程序进行交互的情况下,需要使用数据协议从系统中导出数据。

目前这种数据格式和协议的开发工作尚处在初级阶段,并且大多在 w3c 分布式跟踪上下文工作组的上下文中进行工作。需要特别关注的是在标准数据模式中定义更高级别的概念,例如 RPC 调用、数据库语句等。这将允许跟踪系统对可用数据类型做出假设。OpenTracing 项目也通过定义一套标准标签集来解决这一事务。该计划是为了使这两项努力结果相互配合。

注意当前有一个中间地带。对于由应用程序开发者操作但不想编译或以其他方式执行代码修改的“网络设备”,动态链接可以帮助避免这种情况。主要的例子就是服务网格和代理,就像 Envoy 或者 NGINX。针对这种情况,可将兼容 OpenTracing 的跟踪器编译为共享对象,然后在运行时动态链接到可执行文件中。目前 C++ OpenTracing API 提供了该选项。而 JAVA 的 OpenTracing 跟踪器解析也在开发中。

这些解决方案适用于支持动态链接,并由应用程序开发者部署的的服务。但从长远来看,标准的数据协议可以更广泛地解决该问题。

分析系统:从跟踪数据中提取有见解的服务

最后不得不提的是,现在有足够多的跟踪监视解决方案。可以在此处找到已知与 OpenTracing 兼容的监控系统列表,但除此之外仍有更多的选择。我更鼓励你研究你的解决方案,同时希望你在比较解决方案时发现本文提供的框架能派上用场。除了根据监控系统的操作特性对其进行评级外(更不用提你是否喜欢 UI 和其功能),确保你考虑到了上述三个重要方面、它们对你的相对重要性以及你感兴趣的跟踪系统如何为它们提供解决方案。

结论

最后,每个部分的重要性在很大程度上取决于你是谁以及正在建立什么样的系统。举个例子,开源库的作者对 OpenTracing API 非常感兴趣,而服务开发者对 trace-context 规范更感兴趣。当有人说一部分比另一部分重要时,他们的意思通常是“一部分对我来说比另一部分重要”。

然而,事实是:分布式跟踪已经成为监控现代系统所必不可少的事物。在为这些系统进行构建模块时,“尽可能解耦”的老方法仍然适用。在构建像分布式监控系统一样的跨系统的系统时,干净地解耦组件是维持灵活性和前向兼容性地最佳方式。

感谢你的阅读!现在当你准备好在你自己的应用程序中实现跟踪服务时,你已有一份指南来了解他们正在谈论哪部分部分以及它们之间如何相互协作。


via: https://opensource.com/article/18/5/distributed-tracing

作者:Ted Young 选题:lujun9972 译者:chenmu-kk 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

 title=

Google Analytics (GA)这个最流行的用户活动追踪工具我们或多或少都听说过甚至使用过,但它的用途并不仅仅限于对页面访问的追踪。作为一个既实用又流行的工具,它已经受到了广泛的欢迎,因此我们将要在下文中介绍如何在各种 Angular 和 React 单页应用中使用 Google Analytics。

这篇文章源自这样一个问题:如何对单页应用中的页面访问进行跟踪?

通常来说,有很多的方法可以解决这个问题,在这里我们只讨论其中的一种方法。下面我会使用 Angular 来写出对应的实现,如果你使用的是 React,相关的用法和概念也不会有太大的差别。接下来就开始吧。

准备好应用程序

首先需要有一个 追踪 ID tracking ID 。在开始编写业务代码之前,要先准备好一个追踪 ID,通过这个唯一的标识,Google Analytics 才能识别出某个点击或者某个页面访问是来自于哪一个应用。

按照以下的步骤:

  1. 访问 https://analytics.google.com
  2. 提交指定信息以完成注册,并确保可用于 Web 应用,因为我们要开发的正是一个 Web 应用;
  3. 同意相关的条款,生成一个追踪 ID;
  4. 保存好追踪 ID。

追踪 ID 拿到了,就可以开始编写业务代码了。

添加 analytics.js 脚本

Google 已经帮我们做好了接入之前需要做的所有事情,接下来就是我们的工作了。不过我们要做的也很简单,只需要把下面这段脚本添加到应用的 index.html 里,就可以了:

<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
</script>

现在我们来看一下 Google Analytics 是如何在应用程序中初始化的。

创建追踪器

首先创建一个应用程序的追踪器。在 app.component.ts 中执行以下两个步骤:

  1. 声明一个名为 ga,类型为 any 的全局变量(在 Typescript 中需要制定变量类型);
  2. 将下面一行代码加入到 ngInInit() 中。
ga('create', <你的追踪 ID>, 'auto');

这样就已经成功地在应用程序中初始化了一个 Google Analytics 的追踪器了。由于追踪器的初始化是在 OnInit() 函数中执行的,因此每当应用程序启动,追踪器就会启动。

在单页应用中记录页面访问情况

我们需要实现的是记录 访问路由 route-visits

如何记录用户在一个应用中不同部分的访问,这是一个难点。从功能上来看,单页应用中的路由对应了传统多页面应用中各个页面之间的跳转,因此我们需要记录访问路由。要做到这一点尽管不算简单,但仍然是可以实现的。在 app.component.tsngOnInit() 函数中添加以下的代码片段:

import { Router, NavigationEnd } from '@angular/router';
...
constructor(public router: Router) {}
...
this.router.events.subscribe(
    event => {
        if (event instanceof NavigationEnd) {
            ga('set', 'page', event.urlAfterRedirects);
            ga('send', { hitType: 'pageview', hitCallback: () => { this.pageViewSent = true; }});
        }
    } 
);

神奇的是,只需要这么几行代码,就实现了 Angular 应用中记录页面访问情况的功能。

这段代码实际上做了以下几件事情:

  1. 从 Angular Router 中导入了 RouterNavigationEnd
  2. 通过构造函数中将 Router 添加到组件中;
  3. 然后订阅 router 事件,也就是由 Angular Router 发出的所有事件;
  4. 只要产生了一个 NavigationEnd 事件实例,就将路由和目标作为一个页面访问进行记录。

这样,只要使用到了页面路由,就会向 Google Analytics 发送一条页面访问记录,在 Google Analytics 的在线控制台中可以看到这些记录。

类似地,我们可以用相同的方式来记录除了页面访问之外的活动,例如某个界面的查看次数或者时长等等。只要像上面的代码那样使用 hitCallBack() 就可以在有需要收集的数据的时候让应用程序作出反应,这里我们做的事情仅仅是把一个变量的值设为 true,但实际上 hitCallBack() 中可以执行任何代码。

追踪用户交互活动

除了页面访问记录之外,Google Analytics 还经常被用于追踪用户的交互活动,例如某个按钮的点击情况。“提交按钮被用户点击了多少次?”,“产品手册会被经常查阅吗?”这些都是 Web 应用程序的产品评审会议上的常见问题。这一节我们将会介绍如何实现这些数据的统计。

按钮点击

设想这样一种场景,需要统计到应用程序中某个按钮或链接被点击的次数,这是一个和注册之类的关键动作关系最密切的数据指标。下面我们来举一个例子:

假设应用程序中有一个“感兴趣”按钮,用于显示即将推出的活动,你希望通过统计这个按钮被点击的次数来推测有多少用户对此感兴趣。那么我们可以使用以下的代码来实现这个功能:

...
params = {
    eventCategory:
    'Button'
    ,
    eventAction:
    'Click'
    ,
    eventLabel:
    'Show interest'
    ,
    eventValue:
    1
};

showInterest() {
    ga('send', 'event', this.params);
}
...

现在看下这段代码实际上做了什么。正如前面说到,当我们向 Google Analytics 发送数据的时候,Google Analytics 就会记录下来。因此我们可以向 send() 方法传递不同的参数,以区分不同的事件,例如两个不同按钮的点击记录。

1、首先我们定义了一个 params 对象,这个对象包含了以下几个字段:

  1. eventCategory – 交互发生的对象,这里对应的是按钮(button)
  2. eventAction – 发生的交互的类型,这里对应的是点击(click)
  3. eventLabel – 交互动作的标识,这里对应的是这个按钮的内容,也就是“感兴趣”
  4. eventValue – 与每个发生的事件实例相关联的值

由于这个例子中是要统计点击了“感兴趣”按钮的用户数,因此我们把 eventValue 的值定为 1。

2、对象构造完成之后,下一步就是将 params 对象作为请求负载发送到 Google Analytics,而这一步是通过事件绑定将 showInterest() 绑定在按钮上实现的。这也是使用 Google Analytics 追踪中最常用的发送事件方法。

至此,Google Analytics 就可以通过记录按钮的点击次数来统计感兴趣的用户数了。

追踪社交活动

Google Analytics 还可以通过应用程序追踪用户在社交媒体上的互动。其中一种场景就是在应用中放置类似 Facebook 的点赞按钮,下面我们来看看如何使用 Google Analytics 来追踪这一行为。

...
fbLikeParams = {
    socialNetwork:
        'Facebook',
    socialAction:
        'Like',
    socialTarget:
        'https://facebook.com/mypage'
};
...
fbLike() {
    ga('send', 'social', this.fbLikeParams);
}

如果你觉得这段代码似曾相识,那是因为它确实跟上面统计“感兴趣”按钮点击次数的代码非常相似。下面我们继续看其中每一步的内容:

1、构造发送的数据负载,其中包括以下字段:

  1. socialNetwork – 交互发生的社交媒体,例如 Facebook、Twitter 等等
  2. socialAction – 发生的交互类型,例如点赞、发表推文、分享等等
  3. socialTarget – 交互的目标 URL,一般是社交媒体账号的主页

2、下一步是增加一个函数来发送整个交互记录。和统计按钮点击数量时相比,这里使用 send() 的方式有所不同。另外,我们还需要把这个函数绑定到已有的点赞按钮上。

在追踪用户交互方面,Google Analytics 还可以做更多的事情,其中最重要的一种是针对异常的追踪,这让我们可以通过 Google Analytics 来追踪应用程序中出现的错误和异常。在本文中我们就不赘述这一点了,但我们鼓励读者自行探索。

用户识别

隐私是一项权利,而不是奢侈品

Google Analytics 除了可以记录很多用户的操作和交互活动之外,这一节还将介绍一个不太常见的功能,就是可以控制是否对用户的身份进行追踪。

Cookies

Google Analytics 追踪用户活动的方式是基于 Cookies 的,因此我们可以自定义 Cookies 的名称以及一些其它的内容,请看下面这段代码:

trackingID =
    'UA-139883813-1'
;
cookieParams = {
    cookieName: 'myGACookie',
    cookieDomain: window.location.hostname,
    cookieExpires: 604800
};
...
ngOnInit() {
    ga('create', this.trackingID, this.cookieParams);
...
}

在上面这段代码中,我们设置了 Google Analytics Cookies 的名称、域以及过期时间,这就让我们能够将不同网站或 Web 应用的 Cookies 区分开来。因此我们需要为我们自己的应用程序的 Google Analytics 追踪器的 Cookies 设置一个自定义的标识1,而不是一个自动生成的随机标识。

IP 匿名

在某些场景下,我们可能不需要知道应用程序的流量来自哪里。例如对于一个按钮点击的追踪器,我们只需要关心按钮的点击量,而不需要关心点击者的地理位置。在这种场景下,Google Analytics 允许我们只追踪用户的操作行为,而不获取到用户的 IP 地址。

ipParams = {
    anonymizeIp: true
};
...
ngOnInit() {
    ...
    ga('set', this.ipParams);
    ...
}

在上面这段代码中,我们将 Google Analytics 追踪器的 abibymizeIp 参数设置为 true。这样用户的 IP 地址就不会被 Google Analytics 所追踪,这可以让用户知道自己的隐私正在被保护。

不被跟踪

还有些时候用户可能不希望自己的行为受到追踪,而 Google Analytics 也允许这样的需求。因此也存在让用户不被追踪的选项。

...
optOut() {
    window['ga-disable-UA-139883813-1'] = true;
}
...

optOut() 是一个自定义函数,它可以禁用页面中的 Google Analytics 追踪,我们可以使用按钮或复选框上得事件绑定来使用这一个功能,这样用户就可以选择是否会被 Google Analytics 追踪。

在本文中,我们讨论了 Google Analytics 集成到单页应用时的难点,并探索出了一种相关的解决方法。我们还了解到了如何在单页应用中追踪页面访问和用户交互,例如按钮点击、社交媒体活动等。

最后,我们还了解到 Google Analytics 为用户提供了保护隐私的功能,尤其是用户的一些隐私数据并不需要参与到统计当中的时候。而用户也可以选择完全不受到 Google Analytics 的追踪。除此以外,Google Analytics 还可以做到很多其它的事情,这就需要我们继续不断探索了。


via: https://opensourceforu.com/2019/10/all-that-you-can-do-with-google-analytics-and-more/

作者:Ashwin Sathian 选题:lujun9972 译者:HankChow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

ptrace(2)(“ 进程跟踪 process trace ”)系统调用通常都与调试有关。它是类 Unix 系统上通过原生调试器监测被调试进程的主要机制。它也是实现 strace 系统调用跟踪 system call trace )的常见方法。使用 Ptrace,跟踪器可以暂停被跟踪进程,检查和设置寄存器和内存,监视系统调用,甚至可以 拦截 intercepting 系统调用。

通过拦截功能,意味着跟踪器可以篡改系统调用参数,篡改系统调用的返回值,甚至阻塞某些系统调用。言外之意就是,一个跟踪器本身完全可以提供系统调用服务。这是件非常有趣的事,因为这意味着一个跟踪器可以仿真一个完整的外部操作系统,而这些都是在没有得到内核任何帮助的情况下由 Ptrace 实现的。

问题是,在同一时间一个进程只能被一个跟踪器附着,因此在那个进程的调试期间,不可能再使用诸如 GDB 这样的工具去仿真一个外部操作系统。另外的问题是,仿真系统调用的开销非常高。

在本文中,我们将专注于 x86-64 Linux 的 Ptrace,并将使用一些 Linux 专用的扩展。同时,在本文中,我们将忽略掉一些错误检查,但是完整的源代码仍然会包含这些错误检查。

本文中的可直接运行的示例代码在这里:https://github.com/skeeto/ptrace-examples

strace

在进入到最有趣的部分之前,我们先从回顾 strace 的基本实现来开始。它不是 DTrace,但 strace 仍然非常有用。

Ptrace 一直没有被标准化。它的接口在不同的操作系统上非常类似,尤其是在核心功能方面,但是在不同的系统之间仍然存在细微的差别。ptrace(2) 的原型基本上应该像下面这样,但特定的类型可能有些差别。

long ptrace(int request, pid_t pid, void *addr, void *data);

pid 是被跟踪进程的 ID。虽然同一个时间只有一个跟踪器可以附着到该进程上,但是一个跟踪器可以附着跟踪多个进程。

request 字段选择一个具体的 Ptrace 函数,比如 ioctl(2) 接口。对于 strace,只需要两个:

  • PTRACE_TRACEME:这个进程被它的父进程跟踪。
  • PTRACE_SYSCALL:继续跟踪,但是在下一下系统调用入口或出口时停止。
  • PTRACE_GETREGS:取得被跟踪进程的寄存器内容副本。

另外两个字段,addrdata,作为所选的 Ptrace 函数的一般参数。一般情况下,可以忽略一个或全部忽略,在那种情况下,传递零个参数。

strace 接口实质上是前缀到另一个命令之前。

$ strace [strace options] program [arguments]

最小化的 strace 不需要任何选项,因此需要做的第一件事情是 —— 假设它至少有一个参数 —— 在 argv 尾部的 fork(2)exec(2) 被跟踪进程。但是在加载目标程序之前,新的进程将告知内核,目标程序将被它的父进程继续跟踪。被跟踪进程将被这个 Ptrace 系统调用暂停。

pid_t pid = fork();
switch (pid) {
    case -1: /* error */
        FATAL("%s", strerror(errno));
    case 0:  /* child */
        ptrace(PTRACE_TRACEME, 0, 0, 0);
        execvp(argv[1], argv + 1);
        FATAL("%s", strerror(errno));
}

父进程使用 wait(2) 等待子进程的 PTRACE_TRACEME,当 wait(2) 返回后,子进程将被暂停。

waitpid(pid, 0, 0);

在允许子进程继续运行之前,我们告诉操作系统,被跟踪进程和它的父进程应该一同被终止。一个真实的 strace 实现可能会设置其它的选择,比如: PTRACE_O_TRACEFORK

ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_EXITKILL);

剩余部分就是一个简单的、无休止的循环了,每循环一次捕获一个系统调用。循环体总共有四步:

  1. 等待进程进入下一个系统调用。
  2. 输出系统调用的一个描述。
  3. 允许系统调用去运行并等待返回。
  4. 输出系统调用返回值。

这个 PTRACE_SYSCALL 请求被用于等待下一个系统调用时开始,和等待那个系统调用退出。和前面一样,需要一个 wait(2) 去等待被跟踪进程进入期望的状态。

ptrace(PTRACE_SYSCALL, pid, 0, 0);
waitpid(pid, 0, 0);

wait(2) 返回时,进行了系统调用的线程的寄存器中写入了该系统调用的系统调用号及其参数。尽管如此,操作系统仍然没有为这个系统调用提供服务。这个细节对后续操作很重要。

接下来的一步是采集系统调用信息。这是各个系统架构不同的地方。在 x86-64 上,系统调用号是在 rax 中传递的,而参数(最多 6 个)是在 rdirsirdxr10r8r9 中传递的。这些寄存器是由另外的 Ptrace 调用读取的,不过这里再也不需要 wait(2) 了,因为被跟踪进程的状态再也不会发生变化了。

struct user_regs_struct regs;
ptrace(PTRACE_GETREGS, pid, 0, &regs);
long syscall = regs.orig_rax;

fprintf(stderr, "%ld(%ld, %ld, %ld, %ld, %ld, %ld)",
        syscall,
        (long)regs.rdi, (long)regs.rsi, (long)regs.rdx,
        (long)regs.r10, (long)regs.r8,  (long)regs.r9);

这里有一个警告。由于 内核的内部用途,系统调用号是保存在 orig_rax 中而不是 rax 中。而所有的其它系统调用参数都是非常简单明了的。

接下来是它的另一个 PTRACE_SYSCALLwait(2),然后是另一个 PTRACE_GETREGS 去获取结果。结果保存在 rax 中。

ptrace(PTRACE_GETREGS, pid, 0, &regs);
fprintf(stderr, " = %ld\n", (long)regs.rax);

这个简单程序的输出也是非常粗糙的。这里的系统调用都没有符号名,并且所有的参数都是以数字形式输出,甚至是一个指向缓冲区的指针也是如此。更完整的 strace 输出将能知道哪个参数是指针,并使用 process_vm_readv(2) 从被跟踪进程中读取哪些缓冲区,以便正确输出它们。

然而,这些仅仅是系统调用拦截的基础工作。

系统调用拦截

假设我们想使用 Ptrace 去实现如 OpenBSD 的 pledge(2) 这样的功能,它是 一个进程 承诺 pledge 只使用一套受限的系统调用。初步想法是,许多程序一般都有一个初始化阶段,这个阶段它们都需要进行许多的系统访问(比如,打开文件、绑定套接字、等等)。初始化完成以后,它们进行一个主循环,在主循环中它们处理输入,并且仅使用所需的、很少的一套系统调用。

在进入主循环之前,一个进程可以限制它自己只能运行所需要的几个操作。如果 程序有缺陷,能够通过恶意的输入去利用该缺陷,这个承诺可以有效地限制漏洞利用的实现。

使用与 strace 相同的模型,但不是输出所有的系统调用,我们既能够阻塞某些系统调用,也可以在它的行为异常时简单地终止被跟踪进程。终止它很容易:只需要在跟踪器中调用 exit(2)。因此,它也可以被设置为去终止被跟踪进程。阻塞系统调用和允许子进程继续运行都只是些雕虫小技而已。

最棘手的部分是当系统调用启动后没有办法去中断它。当跟踪器在入口从 wait(2) 中返回到系统调用时,从一开始停止一个系统调用的仅有方式是,终止被跟踪进程。

然而,我们不仅可以“搞乱”系统调用的参数,也可以改变系统调用号本身,将它修改为一个不存在的系统调用。返回时,在 errno通过正常的内部信号,我们就可以报告一个“友好的”错误信息。

for (;;) {
    /* Enter next system call */
    ptrace(PTRACE_SYSCALL, pid, 0, 0);
    waitpid(pid, 0, 0);

    struct user_regs_struct regs;
    ptrace(PTRACE_GETREGS, pid, 0, &regs);

    /* Is this system call permitted? */
    int blocked = 0;
    if (is_syscall_blocked(regs.orig_rax)) {
        blocked = 1;
        regs.orig_rax = -1; // set to invalid syscall
        ptrace(PTRACE_SETREGS, pid, 0, &regs);
    }

    /* Run system call and stop on exit */
    ptrace(PTRACE_SYSCALL, pid, 0, 0);
    waitpid(pid, 0, 0);

    if (blocked) {
        /* errno = EPERM */
        regs.rax = -EPERM; // Operation not permitted
        ptrace(PTRACE_SETREGS, pid, 0, &regs);
    }
}

这个简单的示例只是检查了系统调用是否违反白名单或黑名单。而它们在这里并没有差别,比如,允许文件以只读而不是读写方式打开(open(2)),允许匿名内存映射但不允许非匿名映射等等。但是这里仍然没有办法去动态撤销被跟踪进程的权限。

跟踪器与被跟踪进程如何沟通?使用人为的系统调用!

创建一个人为的系统调用

对于我的这个类似于 pledge 的系统调用 —— 我可以通过调用 xpledge() 将它与真实的系统调用区分开 —— 我设置 10000 作为它的系统调用号,这是一个非常大的数字,真实的系统调用中从来不会用到它。

#define SYS_xpledge 10000

为演示需要,我同时构建了一个非常小的接口,这在实践中并不是个好主意。它与 OpenBSD 的 pledge(2) 稍有一些相似之处,它使用了一个 字符串接口事实上,设计一个健壮且安全的权限集是非常复杂的,正如在 pledge(2) 的手册页面上所显示的那样。下面是对被跟踪进程的系统调用的完整接口实现:

#define _GNU_SOURCE
#include <unistd.h>

#define XPLEDGE_RDWR (1 << 0)
#define XPLEDGE_OPEN (1 << 1)

#define xpledge(arg) syscall(SYS_xpledge, arg)

如果给它传递个参数 0 ,仅允许一些基本的系统调用,包括那些用于去分配内存的系统调用(比如 brk(2))。 PLEDGE_RDWR 位允许 各种 读和写的系统调用(read(2)readv(2)pread(2)preadv(2) 等等)。PLEDGE_OPEN 位允许 open(2)

为防止发生提升权限的行为,pledge() 会拦截它自己 —— 但这样也防止了权限撤销,以后再细说这方面内容。

在 xpledge 跟踪器中,我需要去检查这个系统调用:

/* Handle entrance */
switch (regs.orig_rax) {
    case SYS_pledge:
        register_pledge(regs.rdi);
        break;
}

操作系统将返回 ENOSYS(函数尚未实现),因为它不是一个真实的系统调用。为此在退出时我用一个 success(0) 去覆写它。

/* Handle exit */
switch (regs.orig_rax) {
    case SYS_pledge:
        ptrace(PTRACE_POKEUSER, pid, RAX * 8, 0);
        break;
}

我写了一小段测试程序去打开 /dev/urandom,做一个读操作,尝试去承诺后,然后试着第二次打开 /dev/urandom,然后确认它能够读取原始的 /dev/urandom 文件描述符。在没有承诺跟踪器的情况下运行,输出如下:

$ ./example
fread("/dev/urandom")[1] = 0xcd2508c7
XPledging...
XPledge failed: Function not implemented
fread("/dev/urandom")[2] = 0x0be4a986
fread("/dev/urandom")[1] = 0x03147604

做一个无效的系统调用并不会让应用程序崩溃。它只是失败,这是一个很方便的返回方式。当它在跟踪器下运行时,它的输出如下:

>$ ./xpledge ./example
fread("/dev/urandom")[1] = 0xb2ac39c4
XPledging...
fopen("/dev/urandom")[2]: Operation not permitted
fread("/dev/urandom")[1] = 0x2e1bd1c4

这个承诺很成功,第二次的 fopen(3) 并没有进行,因为跟踪器用一个 EPERM 阻塞了它。

可以将这种思路进一步发扬光大,比如,改变文件路径或返回一个假的结果。一个跟踪器可以很高效地 chroot 它的被跟踪进程,通过一个系统调用将任意路径传递给 root 从而实现 chroot 路径。它甚至可以对用户进行欺骗,告诉用户它以 root 运行。事实上,这些就是 Fakeroot NG 程序所做的事情。

仿真外部系统

假设你不满足于仅拦截一些系统调用,而是想拦截全部系统调用。你就会有了 一个打算在其它操作系统上运行的二进制程序,无需系统调用,这个二进制程序可以一直运行。

使用我在前面所描述的这些内容你就可以管理这一切。跟踪器可以使用一个假冒的东西去代替系统调用号,允许它失败,以及为系统调用本身提供服务。但那样做的效率很低。其实质上是对每个系统调用做了三个上下文切换:一个是在入口上停止,一个是让系统调用总是以失败告终,还有一个是在系统调用退出时停止。

从 2005 年以后,对于这个技术,PTrace 的 Linux 版本有更高效的操作:PTRACE_SYSEMU。PTrace 仅在每个系统调用发出时停止一次,在允许被跟踪进程继续运行之前,由跟踪器为系统调用提供服务。

for (;;) {
    ptrace(PTRACE_SYSEMU, pid, 0, 0);
    waitpid(pid, 0, 0);

    struct user_regs_struct regs;
    ptrace(PTRACE_GETREGS, pid, 0, &regs);

    switch (regs.orig_rax) {
        case OS_read:
            /* ... */

        case OS_write:
            /* ... */

        case OS_open:
            /* ... */

        case OS_exit:
            /* ... */

        /* ... and so on ... */
    }
}

从任何具有(足够)稳定的系统调用 ABI(LCTT 译注:应用程序二进制接口),在相同架构的机器上运行一个二进制程序时,你只需要 PTRACE_SYSEMU 跟踪器、一个加载器(用于代替 exec(2)),和这个二进制程序所需要(或仅运行静态的二进制程序)的任何系统库即可。

事实上,这听起来有点像一个有趣的周末项目。

参见


via: http://nullprogram.com/blog/2018/06/23/

作者:Chris Wellons 选题:lujun9972 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出