# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
package Apache::SizeLimit::Core;
use strict;
use warnings;
use Config;
use Exporter;
use vars qw(
$VERSION
$REQUEST_COUNT
$USE_SMAPS
$MAX_PROCESS_SIZE
$MAX_UNSHARED_SIZE
$MIN_SHARE_SIZE
$CHECK_EVERY_N_REQUESTS
$START_TIME
@ISA
@EXPORT_OK
);
@ISA = qw(Exporter);
@EXPORT_OK = qw(
$VERSION
$REQUEST_COUNT
$USE_SMAPS
$MAX_PROCESS_SIZE
$MAX_UNSHARED_SIZE
$MIN_SHARE_SIZE
$CHECK_EVERY_N_REQUESTS
$START_TIME
);
$VERSION = '0.97';
$REQUEST_COUNT = 1;
use constant IS_WIN32 => $Config{'osname'} eq 'MSWin32' ? 1 : 0;
sub set_max_process_size {
my $class = shift;
$MAX_PROCESS_SIZE = shift;
}
sub set_max_unshared_size {
my $class = shift;
$MAX_UNSHARED_SIZE = shift;
}
sub set_min_shared_size {
my $class = shift;
$MIN_SHARE_SIZE = shift;
}
sub set_check_interval {
my $class = shift;
$CHECK_EVERY_N_REQUESTS = shift;
}
sub get_check_interval { return $CHECK_EVERY_N_REQUESTS; }
sub set_start_time { $START_TIME ||= time(); }
sub get_start_time { return $START_TIME; }
sub get_and_pinc_request_count { return $REQUEST_COUNT++; }
sub get_request_count { return $REQUEST_COUNT++; }
# REVIEW - Why doesn't this use $r->warn or some other
# Apache/Apache::Log API?
sub _error_log {
my $class = shift;
print STDERR "[", scalar( localtime(time) ),
"] ($$) $class @_\n";
}
sub _limits_are_exceeded {
my $class = shift;
my ($size, $share, $unshared) = $class->_check_size();
return 1 if $MAX_PROCESS_SIZE && $size > $MAX_PROCESS_SIZE;
return 0 unless $share;
return 1 if $MIN_SHARE_SIZE && $share < $MIN_SHARE_SIZE;
return 1 if $MAX_UNSHARED_SIZE && $unshared > $MAX_UNSHARED_SIZE;
return 0;
}
sub _check_size {
my $class = shift;
my ($size, $share, $unshared) = $class->_platform_check_size();
return ($size, $share, defined $unshared ? $unshared : $size - $share);
}
sub _load {
my $mod = shift;
eval "require $mod"
or die
"You must install $mod for Apache::SizeLimit to work on your" .
" platform.";
}
BEGIN {
my ($major,$minor) = split(/\./, $Config{'osvers'});
if ($Config{'osname'} eq 'solaris' &&
(($major > 2) || ($major == 2 && $minor >= 6))) {
*_platform_check_size = \&_solaris_2_6_size_check;
*_platform_getppid = \&_perl_getppid;
}
elsif ($Config{'osname'} eq 'linux') {
_load('Linux::Pid');
*_platform_getppid = \&_linux_getppid;
if (eval { require Linux::Smaps && Linux::Smaps->new($$) }) {
$USE_SMAPS = 1;
*_platform_check_size = \&_linux_smaps_size_check;
}
else {
$USE_SMAPS = 0;
*_platform_check_size = \&_linux_size_check;
}
}
elsif ($Config{'osname'} =~ /(?:bsd|aix)/i) {
# on OSX, getrusage() is returning 0 for proc & shared size.
_load('BSD::Resource');
*_platform_check_size = \&_bsd_size_check;
*_platform_getppid = \&_perl_getppid;
}
# elsif (IS_WIN32i && $mod_perl::VERSION < 1.99) {
# _load('Win32::API');
#
# *_platform_check_size = \&_win32_size_check;
# *_platform_getppid = \&_perl_getppid;
# }
else {
die "Apache::SizeLimit is not implemented on your platform.";
}
}
sub _linux_smaps_size_check {
my $class = shift;
return $class->_linux_size_check() unless $USE_SMAPS;
my $s = Linux::Smaps->new($$)->all;
return ($s->size,
$s->shared_clean + $s->shared_dirty,
$s->private_clean + $s->private_dirty);
}
sub _linux_size_check {
my $class = shift;
my ($size, $share) = (0, 0);
if (open my $fh, '<', '/proc/self/statm') {
($size, $share) = (split /\s/, scalar <$fh>)[0,2];
close $fh;
}
else {
$class->_error_log("Fatal Error: couldn't access /proc/self/status");
}
# linux on intel x86 has 4KB page size...
return ($size * 4, $share * 4);
}
sub _solaris_2_6_size_check {
my $class = shift;
my $size = -s "/proc/self/as"
or $class->_error_log("Fatal Error: /proc/self/as doesn't exist or is empty");
$size = int($size / 1024);
# return 0 for share, to avoid undef warnings
return ($size, 0);
}
# rss is in KB but ixrss is in BYTES.
# This is true on at least FreeBSD, OpenBSD, & NetBSD
sub _bsd_size_check {
my @results = BSD::Resource::getrusage();
my $max_rss = $results[2];
my $max_ixrss = int ( $results[3] / 1024 );
return ($max_rss, $max_ixrss);
}
sub _win32_size_check {
my $class = shift;
# get handle on current process
my $get_current_process = Win32::API->new(
'kernel32',
'get_current_process',
[],
'I'
);
my $proc = $get_current_process->Call();
# memory usage is bundled up in ProcessMemoryCounters structure
# populated by GetProcessMemoryInfo() win32 call
my $DWORD = 'B32'; # 32 bits
my $SIZE_T = 'I'; # unsigned integer
# build a buffer structure to populate
my $pmem_struct = "$DWORD" x 2 . "$SIZE_T" x 8;
my $mem_counters
= pack( $pmem_struct, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
# GetProcessMemoryInfo is in "psapi.dll"
my $get_process_memory_info = new Win32::API(
'psapi',
'GetProcessMemoryInfo',
[ 'I', 'P', 'I' ],
'I'
);
my $bool = $get_process_memory_info->Call(
$proc,
$mem_counters,
length $mem_counters,
);
# unpack ProcessMemoryCounters structure
my $peak_working_set_size =
(unpack($pmem_struct, $mem_counters))[2];
# only care about peak working set size
my $size = int($peak_working_set_size / 1024);
return ($size, 0);
}
sub _perl_getppid { return getppid }
sub _linux_getppid { return Linux::Pid::getppid() }
1;
__END__
=head1 NAME
Apache::SizeLimit::Core - Because size does matter.
=head1 SYNOPSIS
DO NOT USE ME DIRECTLY
See Apache::SizeLimit for mod_perl 1.x
See Apache2::SizeLimit for mod_perl 2.x
=cut