分析多层,分布式Web应用程序(服务器端)

时间:2013-03-02 20:10:04

标签: performance web-services web-applications profiling

我想profile来自服务器PoV的复杂Web应用程序。

根据上面的维基百科链接和Stack Overflow profiling标签描述,分析(在其中一个表单中)意味着获取应用程序的API /组件的列表(或图形表示),每个都有在运行期间调用的次数和时间。

请注意,与传统的单程序/单语言不同,Web服务器应用程序可能是:

  • 分布在多台计算机上
  • 不同的组件可能用不同的语言编写
  • 不同的组件可能在不同的操作系统等之上运行。

所以传统的“只使用分析器”答案并不容易适用于这个问题。

正在寻找:

  • 粗略的性能统计数据,如各种日志分析工具(例如模拟)或
  • 提供的统计数据
  • 客户端,每页性能统计信息,例如Google的Pagespeed或Yahoo!等工具。 Y!慢,瀑布图和浏览器组件加载时间)

相反,我正在寻找经典的探查器式报告:

  • 通话次数
  • 通话时长

by function / API / component-name,位于Web应用程序的服务器端上。

最重要的是,问题是:

  

如何分析多层,多平台,分布式Web应用程序?

首选基于免费软件的解决方案。

我一直在网上寻找解决方案一段时间,除了一些相当昂贵的商业产品外,找不到满足我需求的任何东西。最后,我咬紧牙关,想到了问题,并编写了我自己想要自由分享的解决方案。

我发布了自己的解决方案since this practice is encouraged on SO

此解决方案远非完美,例如,它处于非常高的级别(单个URL),这可能对所有用例都不利。然而,它极大地帮助我试图了解我的网络应用程序花费的时间。

本着开源和知识共享的精神,我欢迎其他人,特别是优秀的方法和解决方案。

2 个答案:

答案 0 :(得分:3)

考虑到传统的剖析器如何工作,应该直截了当地为这一挑战提出一般的自由软件解决方案。

让我们将问题分成两部分:

  • 收集数据
  • 展示数据

收集数据

假设我们可以将我们的Web应用程序分解为其个人 组成部分(API,功能)并测量所需的时间 每个部分都要完成。每个部分都被称为数千个 一天一次,所以我们可以整整一天收集这些数据 多个主机。当这一天结束时,我们会有一个非常大的 相关数据集。

顿悟#1: 将“功能”替换为“网址”,以及我们现有的 网络日志是“它”。我们需要的数据已经存在:

  • Web API的每个部分都由请求URL定义(可能   一些参数)
  • 每一行都会出现往返时间(通常以微秒为单位)   我们有一天,(周,月)价值的数据,方便这个数据

因此,如果我们可以访问所有分布式的标准Web日志 我们的Web应用程序的一部分,我们的问题的第一部分(收集 数据)已经解决。

呈现数据

现在我们有一个很大的数据集,但仍然没有真正的洞察力。 我们如何获得洞察力?

Epiphany#2: 直接可视化我们的(多个)网络服务器日志。

一张图片胜过1000字。我们可以使用哪张图片?

我们需要压缩数千或数百万行的100多个 web-server登录到一个简短的摘要,可以告诉大部分 关于我们表现的故事。换句话说:目标是生成 类似于分析器的报告,甚至更好:图形分析器报告, 直接来自我们的网络日志。

想象一下,我们可以映射:

  • 一维呼叫延迟
  • 对其他维度的调用次数和
  • 颜色的功能标识(基本上是第三维)

一张这样的图片:API的延迟密度图表 如下所示(功能名称是为了说明目的而制作的)。

图表:

The 1000 word story: stacked latency distribution of a web application by API

此示例中的一些观察结果

  • 我们有三个模态分布从根本上代表3 我们的申请中有不同的“世界”:
  • 最快的响应,以~300微秒为中心 延迟这些回复来自我们的清漆缓存
  • 第二快,耗时少于0.01秒 平均来说,来自我们中间层提供的各种API Web应用程序(Apache / Tomcat)
  • 最慢的响应,以0.1秒为中心 有时需要几秒钟才能回复,涉及到往返 到我们的SQL数据库。

我们可以看到对应用程序有多么戏剧性的缓存效果 (请注意,x轴是log10刻度)

我们可以明确地看到哪些API往往速度快而且速度慢 我们知道要关注什么。

我们可以看到每天最常调用哪些API。 我们还可以看到其中一些很少被称为,很难在图表上看到它们的颜色。

怎么做?

第一步是预处理和提取所需的子集数据 从日志。一个简单的实用程序,如Unix'在多个日志上剪切' 这可能就足够了。您可能还需要折叠多个 类似的URL到更短的字符串描述函数/ API之类的 '注册'或'购买'。如果您有多主机统一日志 由负载均衡器生成的视图,此任务可能更容易。我们 只提取API(URL)的名称及其延迟,所以我们 最终得到一个带有一对列的大文件,由TAB分隔

*API_Name   Latency_in_microSecs*


func_01    32734
func_01    32851
func_06    598452
...
func_11    232734

现在我们在结果数据对上运行下面的R脚本来生成 想要的图表(使用Hadley Wickham的精彩 ggplot2 库)。 Voilla!

生成图表的代码

最后,这是从API + Latency TSV数据文件生成图表的代码:

#!/usr/bin/Rscript --vanilla
#
# Generate stacked chart of API latencies by API from a TSV data-set
#
# ariel faigon - Dec 2012
#
.libPaths(c('~/local/lib/R',
         '/usr/lib/R/library',
         '/usr/lib/R/site-library'
))

suppressPackageStartupMessages(library(ggplot2))
# grid lib needed for 'unit()':
suppressPackageStartupMessages(library(grid))

#
# Constants: width, height, resolution, font-colors and styles
# Adapt to taste
#
wh.ratio = 2
WIDTH = 8
HEIGHT = WIDTH / wh.ratio
DPI = 200
FONTSIZE = 11
MyGray = gray(0.5)

title.theme   = element_text(family="FreeSans", face="bold.italic",
                        size=FONTSIZE)
x.label.theme = element_text(family="FreeSans", face="bold.italic",
                        size=FONTSIZE-1, vjust=-0.1)
y.label.theme = element_text(family="FreeSans", face="bold.italic",
                       size=FONTSIZE-1, angle=90, vjust=0.2)
x.axis.theme  = element_text(family="FreeSans", face="bold",
                        size=FONTSIZE-1, colour=MyGray)
y.axis.theme  = element_text(family="FreeSans", face="bold",
                        size=FONTSIZE-1, colour=MyGray)

#
# Function generating well-spaced & well-labeled y-axis (count) breaks
#
yscale_breaks <- function(from.to) {
    from <- 0
    to <- from.to[2]
    # round to 10 ceiling
    to <- ceiling(to / 10.0) * 10
    # Count major breaks on 10^N boundaries, include the 0
    n.maj = 1 + ceiling(log(to) / log(10))
    # if major breaks are too few, add minor-breaks half-way between them
    n.breaks <- ifelse(n.maj < 5, max(5, n.maj*2+1), n.maj)
    breaks <- as.integer(seq(from, to, length.out=n.breaks))
    breaks
}

#
# -- main
#

# -- process the command line args:  [tsv_file [png_file]]
#    (use defaults if they aren't provided)
#
argv <- commandArgs(trailingOnly = TRUE)
if (is.null(argv) || (length(argv) < 1)) {
    argv <- c(Sys.glob('*api-lat.tsv')[1])
}
tsvfile <- argv[1]
stopifnot(! is.na(tsvfile))
pngfile <- ifelse(is.na(argv[2]), paste(tsvfile, '.png', sep=''), argv[2])

# -- Read the data from the TSV file into an internal data.frame d
d <- read.csv(tsvfile, sep='\t', head=F)

# -- Give each data column a human readable name
names(d) <- c('API', 'Latency')

#
# -- Convert microseconds Latency (our weblog resolution) to seconds
#
d <- transform(d, Latency=Latency/1e6)

#
# -- Trim the latency axis:
#       Drop the few 0.001% extreme-slowest outliers on the right
#       to prevent them from pushing the bulk of the data to the left
Max.Lat <- quantile(d$Latency, probs=0.99999)
d <- subset(d, Latency < Max.Lat)

#
# -- API factor pruning
#       Drop rows where the APIs is less than small % of total calls
#
Rare.APIs.pct <- 0.001
if (Rare.APIs.pct > 0.0) {
    d.N <- nrow(d)
    API.counts <- table(d$API)
    d <- transform(d, CallPct=100.0*API.counts[d$API]/d.N)
    d <- d[d$CallPct > Rare.APIs.pct, ]
    d.N.new <- nrow(d)
}

#
# -- Adjust legend item-height &font-size
#    to the number of distinct APIs we have
#
API.count <- nlevels(as.factor(d$API))
Legend.LineSize <- ifelse(API.count < 20, 1.0, 20.0/API.count)
Legend.FontSize <- max(6, as.integer(Legend.LineSize * (FONTSIZE - 1)))
legend.theme  = element_text(family="FreeSans", face="bold.italic",
                        size=Legend.FontSize,
                        colour=gray(0.3))


# -- set latency (X-axis) breaks and labels (s.b made more generic)
lat.breaks <- c(0.00001, 0.0001, 0.001, 0.01, 0.1, 1, 10)
lat.labels <- sprintf("%g", lat.breaks)
#
# -- Generate the chart using ggplot
#
p <- ggplot(data=d, aes(x=Latency, y=..count../1000.0, group=API, fill=API)) +
   geom_bar(binwidth=0.01) +
      scale_x_log10(breaks=lat.breaks, labels=lat.labels) +
      scale_y_continuous(breaks=yscale_breaks) +
      ggtitle('APIs Calls & Latency Distribution') +
      xlab('Latency in seconds - log(10) scale') +
      ylab('Call count (in 1000s)') +
      theme(
            plot.title=title.theme,
            axis.title.y=y.label.theme,
            axis.title.x=x.label.theme,
            axis.text.x=x.axis.theme,
            axis.text.y=y.axis.theme,
            legend.text=legend.theme,
            legend.key.height=unit(Legend.LineSize, "line")
      )

#
# -- Save the plot into the png file
#
ggsave(p, file=pngfile, width=WIDTH, height=HEIGHT, dpi=DPI)

答案 1 :(得分:2)

您对“当天的回归”分析实践的讨论是正确的。 它总是有一个小问题:

  • 在非玩具软件中,它可能会找到一些东西,但对于bunch of reasons来说却找不到多少。

关于提高性能的机会是,如果你没有找到它们,软件不会中断,所以你只能假装它们不存在。 也就是说,在尝试不同的方法之前,找到它们。

在统计数据中,这称为类型2错误 - 假阴性。 有机会,但你没有找到它。 这意味着如果有人 知道如何找到它,那么它们将会赢得大奖。 Here's probably more than you ever wanted to know about that.

因此,如果您在Web应用程序中查看相同类型的内容 - 调用计数,时间测量,您不会比同样的非结果更好。

我不是网络应用程序,但多年前我在基于协议的工厂自动化应用程序中进行了大量的性能调整。 我使用了一种记录技术。 我不会说这很容易,但确实有效。 我看到做类似事情的人是here, where they use what they call a waterfall chart。 基本思路不是投入大量网络并进行大量测量,而是通过单个逻辑交易线程进行跟踪,分析不必要的延迟发生地点。

所以如果结果是你所追求的,我会低头看待这条思路。