mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-09-18 21:50:56 +00:00
Merge branch 'master' into dejal
This commit is contained in:
commit
c12070124f
26 changed files with 837 additions and 256 deletions
|
@ -133,10 +133,16 @@ You got the downtime message either through email or SMS. This is the order of o
|
|||
When the new redis server is connected to the primary redis server:
|
||||
|
||||
# db-redis-story2 = moving to new server
|
||||
# db-redis-story = old server about to be shutdown
|
||||
# db-redis-story1 = old server about to be shutdown
|
||||
# Edit digitalocean.tf to change db-redis-story count to 2
|
||||
make plan
|
||||
make apply
|
||||
make firewall
|
||||
# Wait for redis to sync, takes 5-10 minutes
|
||||
# Edit redis/consul_service.json to switch primary to db-redis-story2
|
||||
make celery_stop
|
||||
make maintenance_on
|
||||
apd -l db-redis-story2 -t replicaofnoone
|
||||
aps -l db-redis-story,db-redis-story2 -t consul
|
||||
aps -l db-redis-story1,db-redis-story2 -t consul
|
||||
make maintenance_off
|
||||
make task
|
||||
|
|
|
@ -21,6 +21,13 @@
|
|||
become: yes
|
||||
sysctl: name=vm.overcommit_memory value=1 state=present reload=yes
|
||||
|
||||
- name: Template redis.conf file
|
||||
copy:
|
||||
src: /srv/newsblur/docker/redis/redis.conf
|
||||
dest: /srv/newsblur/docker/redis/redis.conf
|
||||
notify: restart redis
|
||||
register: updated_config
|
||||
|
||||
- name: Template redis_replica.conf file
|
||||
template:
|
||||
src: /srv/newsblur/docker/redis/redis_replica.conf.j2
|
||||
|
@ -40,7 +47,7 @@
|
|||
become: yes
|
||||
docker_container:
|
||||
name: redis
|
||||
image: redis:6.2.7
|
||||
image: redis:7
|
||||
state: started
|
||||
command: /usr/local/etc/redis/redis_server.conf
|
||||
container_default_behavior: no_defaults
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"service": {
|
||||
{% if inventory_hostname in ["db-redis-user", "db-redis-story1", "db-redis-session", "db-redis-pubsub"] %}
|
||||
{% if inventory_hostname in ["db-redis-user", "db-redis-story2", "db-redis-session", "db-redis-pubsub"] %}
|
||||
"name": "{{ inventory_hostname|regex_replace('\d+', '') }}",
|
||||
{% else %}
|
||||
"name": "{{ inventory_hostname|regex_replace('\d+', '') }}-staging",
|
||||
|
|
|
@ -1404,7 +1404,12 @@ def load_river_stories__redis(request):
|
|||
user_search = None
|
||||
offset = (page-1) * limit
|
||||
story_date_order = "%sstory_date" % ('' if order == 'oldest' else '-')
|
||||
|
||||
|
||||
if user.pk == 86178:
|
||||
# Disable Michael_Novakhov account
|
||||
logging.user(request, "~FCLoading ~FMMichael_Novakhov~SN's river, resource usage too high, ignoring.")
|
||||
return HttpResponse("Resource usage too high", status=429)
|
||||
|
||||
if infrequent:
|
||||
feed_ids = Feed.low_volume_feeds(feed_ids, stories_per_month=infrequent)
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ def TaskFeeds():
|
|||
r.zcard('tasked_feeds'),
|
||||
r.scard('queued_feeds'),
|
||||
r.zcard('scheduled_updates')))
|
||||
logging.debug(" ---> ~FBFeeds being tasked: ~SB%s" % feeds)
|
||||
|
||||
@app.task(name='task-broken-feeds')
|
||||
def TaskBrokenFeeds():
|
||||
|
|
|
@ -8,6 +8,7 @@ from django.views.decorators.http import condition
|
|||
from django.http import HttpResponseForbidden, HttpResponseRedirect, HttpResponse, Http404
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
# from django.db import IntegrityError
|
||||
from apps.rss_feeds.models import Feed, merge_feeds
|
||||
from apps.rss_feeds.models import MFetchHistory
|
||||
|
@ -510,19 +511,21 @@ def status(request):
|
|||
return HttpResponseForbidden()
|
||||
minutes = int(request.GET.get('minutes', 1))
|
||||
now = datetime.datetime.now()
|
||||
hour_ago = now + datetime.timedelta(minutes=minutes)
|
||||
username = request.GET.get('user', '') or request.GET.get('username', '')
|
||||
if username:
|
||||
user = User.objects.get(username=username)
|
||||
if username == "all":
|
||||
feeds = Feed.objects.filter(next_scheduled_update__lte=hour_ago).order_by('next_scheduled_update')
|
||||
else:
|
||||
user = request.user
|
||||
usersubs = UserSubscription.objects.filter(user=user)
|
||||
feed_ids = usersubs.values('feed_id')
|
||||
if minutes > 0:
|
||||
hour_ago = now + datetime.timedelta(minutes=minutes)
|
||||
feeds = Feed.objects.filter(pk__in=feed_ids, next_scheduled_update__lte=hour_ago).order_by('next_scheduled_update')
|
||||
else:
|
||||
hour_ago = now + datetime.timedelta(minutes=minutes)
|
||||
feeds = Feed.objects.filter(pk__in=feed_ids, last_update__gte=hour_ago).order_by('-last_update')
|
||||
if username:
|
||||
user = User.objects.get(username=username)
|
||||
else:
|
||||
user = request.user
|
||||
usersubs = UserSubscription.objects.filter(user=user)
|
||||
feed_ids = usersubs.values('feed_id')
|
||||
if minutes > 0:
|
||||
feeds = Feed.objects.filter(pk__in=feed_ids, next_scheduled_update__lte=hour_ago).order_by('next_scheduled_update')
|
||||
else:
|
||||
feeds = Feed.objects.filter(pk__in=feed_ids, last_update__gte=hour_ago).order_by('-last_update')
|
||||
|
||||
r = redis.Redis(connection_pool=settings.REDIS_FEED_UPDATE_POOL)
|
||||
queues = {
|
||||
|
|
|
@ -5,4 +5,5 @@ urlpatterns = [
|
|||
url(r'^dashboard_graphs', views.dashboard_graphs, name='statistics-graphs'),
|
||||
url(r'^feedback_table', views.feedback_table, name='feedback-table'),
|
||||
url(r'^revenue', views.revenue, name='revenue'),
|
||||
url(r'^slow', views.slow, name='slow'),
|
||||
]
|
||||
|
|
|
@ -1,12 +1,22 @@
|
|||
import base64
|
||||
import pickle
|
||||
import redis
|
||||
import datetime
|
||||
from operator import countOf
|
||||
from collections import defaultdict
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
from django.utils import feedgenerator
|
||||
from django.http import HttpResponseForbidden
|
||||
from apps.statistics.models import MStatistics, MFeedback
|
||||
from apps.statistics.rstats import round_time
|
||||
from apps.profile.models import PaymentHistory
|
||||
from utils import log as logging
|
||||
|
||||
|
||||
def dashboard_graphs(request):
|
||||
statistics = MStatistics.all()
|
||||
return render(
|
||||
|
@ -49,4 +59,60 @@ def revenue(request):
|
|||
request.META.get('HTTP_USER_AGENT', "")[:24]
|
||||
))
|
||||
return HttpResponse(rss.writeString('utf-8'), content_type='application/rss+xml')
|
||||
|
||||
|
||||
|
||||
@login_required
|
||||
def slow(request):
|
||||
r = redis.Redis(connection_pool=settings.REDIS_STATISTICS_POOL)
|
||||
if not request.user.is_staff and not settings.DEBUG:
|
||||
logging.user(request, "~SKNON-STAFF VIEWING SLOW STATUS!")
|
||||
assert False
|
||||
return HttpResponseForbidden()
|
||||
|
||||
now = datetime.datetime.now()
|
||||
all_queries = {}
|
||||
user_id_counts = {}
|
||||
path_counts = {}
|
||||
users = {}
|
||||
|
||||
for minutes_ago in range(60*6):
|
||||
dt_ago = now - datetime.timedelta(minutes=minutes_ago)
|
||||
minute = round_time(dt_ago, round_to=60)
|
||||
dt_ago_str = minute.strftime("%a %b %-d, %Y %H:%M")
|
||||
name = f"SLOW:{minute.strftime('%s')}"
|
||||
minute_queries = r.lrange(name, 0, -1)
|
||||
for query_raw in minute_queries:
|
||||
query = pickle.loads(base64.b64decode(query_raw))
|
||||
user_id = query['user_id']
|
||||
if dt_ago_str not in all_queries:
|
||||
all_queries[dt_ago_str] = []
|
||||
if user_id in users:
|
||||
user = users[user_id]
|
||||
elif int(user_id) != 0:
|
||||
try:
|
||||
user = User.objects.get(pk=user_id)
|
||||
except User.DoesNotExist:
|
||||
continue
|
||||
users[user_id] = user
|
||||
else:
|
||||
user = AnonymousUser()
|
||||
users[user_id] = user
|
||||
query['user'] = user
|
||||
query['datetime'] = minute
|
||||
all_queries[dt_ago_str].append(query)
|
||||
if user_id not in user_id_counts:
|
||||
user_id_counts[user_id] = 0
|
||||
user_id_counts[user_id] += 1
|
||||
if query['path'] not in path_counts:
|
||||
path_counts[query['path']] = 0
|
||||
path_counts[query['path']] += 1
|
||||
|
||||
user_counts = []
|
||||
for user_id, count in user_id_counts.items():
|
||||
user_counts.append({'user': users[user_id], 'count': count})
|
||||
|
||||
return render(request, 'statistics/slow.xhtml', {
|
||||
'all_queries': all_queries,
|
||||
'user_counts': user_counts,
|
||||
'path_counts': path_counts,
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
buildscript {
|
||||
ext.kotlin_version = '1.6.10'
|
||||
ext.kotlin_version = '1.6.21'
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
|
@ -9,9 +9,9 @@ buildscript {
|
|||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.2.1'
|
||||
classpath 'com.android.tools.build:gradle:7.2.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.40.1'
|
||||
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.40.5'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,22 +30,22 @@ apply plugin: 'kotlin-kapt'
|
|||
apply plugin: 'dagger.hilt.android.plugin'
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.fragment:fragment-ktx:1.5.0'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.5.2'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.9.2'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'com.google.code.gson:gson:2.8.9'
|
||||
implementation 'com.android.billingclient:billing:4.0.0'
|
||||
implementation 'nl.dionsegijn:konfetti:1.2.2'
|
||||
implementation 'com.google.android.play:core:1.10.3'
|
||||
implementation "com.google.android.material:material:1.6.1"
|
||||
implementation "androidx.preference:preference-ktx:1.2.0"
|
||||
implementation "androidx.browser:browser:1.4.0"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.0"
|
||||
implementation 'androidx.lifecycle:lifecycle-process:2.5.0'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.0-rc01'
|
||||
implementation "com.google.dagger:hilt-android:2.40.1"
|
||||
kapt "com.google.dagger:hilt-compiler:2.40.1"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
|
||||
implementation 'androidx.lifecycle:lifecycle-process:2.5.1'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.0'
|
||||
implementation "com.google.dagger:hilt-android:2.40.5"
|
||||
kapt "com.google.dagger:hilt-compiler:2.40.5"
|
||||
}
|
||||
|
||||
android {
|
||||
|
@ -54,8 +54,8 @@ android {
|
|||
applicationId "com.newsblur"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
versionCode 204
|
||||
versionName "12.0"
|
||||
versionCode 205
|
||||
versionName "12.0.1"
|
||||
}
|
||||
compileOptions.with {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
|
|
Binary file not shown.
|
@ -1,6 +1,6 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="150"
|
||||
android:viewportHeight="150">
|
||||
<path
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="150dp"
|
||||
android:height="150dp"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="150"
|
||||
android:viewportHeight="150">
|
||||
<path
|
||||
|
|
9
clients/android/NewsBlur/res/drawable/ic_search_2.xml
Normal file
9
clients/android/NewsBlur/res/drawable/ic_search_2.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:fillColor="#95968E"
|
||||
android:pathData="M39.8,41.95 L26.65,28.8q-1.5,1.3 -3.5,2.025 -2,0.725 -4.25,0.725 -5.4,0 -9.15,-3.75T6,18.75q0,-5.3 3.75,-9.05 3.75,-3.75 9.1,-3.75 5.3,0 9.025,3.75 3.725,3.75 3.725,9.05 0,2.15 -0.7,4.15 -0.7,2 -2.1,3.75L42,39.75ZM18.85,28.55q4.05,0 6.9,-2.875Q28.6,22.8 28.6,18.75t-2.85,-6.925Q22.9,8.95 18.85,8.95q-4.1,0 -6.975,2.875T9,18.75q0,4.05 2.875,6.925t6.975,2.875Z" />
|
||||
</vector>
|
|
@ -0,0 +1,4 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="@dimen/rounded_corner_radius_4dp" />
|
||||
</shape>
|
|
@ -21,15 +21,14 @@
|
|||
android:layout_height="match_parent"
|
||||
android:layout_toRightOf="@id/story_item_favicon_borderbar_1" />
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
<ImageView
|
||||
android:id="@+id/story_item_feedicon"
|
||||
android:layout_width="19dp"
|
||||
android:layout_height="19dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_toRightOf="@+id/story_item_favicon_borderbar_2"
|
||||
app:shapeAppearanceOverlay="@style/smallRoundImageShapeAppearance"/>
|
||||
android:layout_toRightOf="@+id/story_item_favicon_borderbar_2" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/story_item_feedtitle"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Keep in sync with ShortcutUtils-->
|
||||
<shortcut
|
||||
android:icon="@drawable/ic_search"
|
||||
android:icon="@drawable/ic_search_2"
|
||||
android:shortcutId="all_stories_search"
|
||||
android:shortcutShortLabel="@string/search">
|
||||
<intent
|
||||
|
|
|
@ -16,6 +16,7 @@ import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_METADATA
|
|||
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_SOCIAL
|
||||
import com.newsblur.service.NBSyncReceiver.Companion.UPDATE_STORY
|
||||
import com.newsblur.util.UIUtils.syncUpdateStatus
|
||||
import java.lang.IllegalStateException
|
||||
import java.util.*
|
||||
|
||||
class FeedUtils(
|
||||
|
@ -487,8 +488,13 @@ class FeedUtils(
|
|||
// NB: when our minSDKversion hits 28, it could be possible to start the service via the JobScheduler
|
||||
// with the setImportantWhileForeground() flag via an enqueue() and get rid of all legacy startService
|
||||
// code paths
|
||||
val i = Intent(context, NBSyncService::class.java)
|
||||
context.startService(i)
|
||||
try {
|
||||
val i = Intent(context, NBSyncService::class.java)
|
||||
context.startService(i)
|
||||
} catch (e: IllegalStateException) {
|
||||
// BackgroundServiceStartNotAllowedException
|
||||
Log.e(this, "triggerSync error: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,8 +7,7 @@ import android.view.View;
|
|||
import android.view.View.OnClickListener;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.google.android.material.imageview.ShapeableImageView;
|
||||
import com.google.android.material.shape.ShapeAppearanceModel;
|
||||
import com.newsblur.R;
|
||||
import com.newsblur.activity.Profile;
|
||||
import com.newsblur.view.FlowLayout;
|
||||
|
||||
|
@ -17,21 +16,21 @@ public class ViewUtils {
|
|||
private ViewUtils() {} // util class - no instances
|
||||
|
||||
public static ImageView createSharebarImage(final Context context, final String photoUrl, final String userId, ImageLoader iconLoader) {
|
||||
ShapeableImageView image = new ShapeableImageView(context);
|
||||
ImageView image = new ImageView(context);
|
||||
int imageLength = UIUtils.dp2px(context, 15);
|
||||
image.setMaxHeight(imageLength);
|
||||
image.setMaxWidth(imageLength);
|
||||
ShapeAppearanceModel shape = new ShapeAppearanceModel().withCornerSize(UIUtils.dp2px(context, 4));
|
||||
image.setShapeAppearanceModel(shape);
|
||||
|
||||
image.setClipToOutline(true);
|
||||
image.setBackgroundResource(R.drawable.shape_rounded_corners_4dp);
|
||||
|
||||
FlowLayout.LayoutParams imageParameters = new FlowLayout.LayoutParams(5, 5);
|
||||
|
||||
|
||||
imageParameters.height = imageLength;
|
||||
imageParameters.width = imageLength;
|
||||
|
||||
|
||||
image.setMaxHeight(imageLength);
|
||||
image.setMaxWidth(imageLength);
|
||||
|
||||
|
||||
image.setLayoutParams(imageParameters);
|
||||
iconLoader.displayImage(photoUrl, image);
|
||||
image.setOnClickListener(new OnClickListener() {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -19,6 +19,6 @@
|
|||
.NB-status td {
|
||||
border-top: 1px solid #F0F0F0;
|
||||
margin: 0;
|
||||
padding: 0 0;
|
||||
padding: 0 6px 0 0;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
<img src="/media/img/logo_512.png" class="logo">
|
||||
<h1>NewsBlur is in <span class="error404">maintenance mode</span></h1>
|
||||
<div class="description">
|
||||
<p>Moving to a larger Redis story DB, since the existing DB is buckling under the new load from the requirements of the new NewsBlur Premium Archive subscription.</p>
|
||||
<p>Moving to another Redis story DB, since the existing DB is having some issues. Check the daily load time graph to see how it's had an impact. Always good to do this, expect fast load times after this.</p>
|
||||
<p>To pass the time, <a href="http://mltshp.com/popular">check out what's popular on MLTSHP</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
{% load utils_tags tz %}
|
||||
|
||||
{% block bodyclass %}NB-body-status{% endblock %}
|
||||
{% block bodyclass %}NB-body-status NB-static{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
|||
<th style="white-space: nowrap">Last Update<br>Next Update</th>
|
||||
<th>Min to<br>next update</th>
|
||||
<th>Decay</th>
|
||||
<th>Last fetch</th>
|
||||
<th>Subs</th>
|
||||
<th>Active</th>
|
||||
<th>Premium</th>
|
||||
|
@ -29,11 +30,13 @@
|
|||
<th>Act. Prem</th>
|
||||
<th>Per Month</th>
|
||||
<th>Last Month</th>
|
||||
<th>In Archive</th>
|
||||
<th>File size (b)</th>
|
||||
</tr>
|
||||
{% for feed in feeds %}
|
||||
<tr>
|
||||
<td>{{ feed.pk }}</td>
|
||||
<td><img class="NB-favicon" src="/rss_feeds/icon/{{ feed.pk }}" /> {{ feed.feed_title|truncatewords:4 }}</td>
|
||||
<td title="{{ feed.feed_address }}"><img class="NB-favicon" src="/rss_feeds/icon/{{ feed.pk }}" /> {{ feed.feed_title|truncatewords:4 }}</td>
|
||||
<td>{{ feed.last_update|smooth_timedelta }}</td>
|
||||
<td class="NB-status-update" style="white-space: nowrap">
|
||||
{% localdatetime feed.last_update "%b %d, %Y %H:%M:%S" %}
|
||||
|
@ -42,6 +45,7 @@
|
|||
</td>
|
||||
<td>{{ feed.next_scheduled_update|smooth_timedelta }}</td>
|
||||
<td>{{ feed.min_to_decay }}</td>
|
||||
<td>{{ feed.last_load_time }}</td>
|
||||
<td>{{ feed.num_subscribers }}</td>
|
||||
<td style="color: {% if feed.active_subscribers == 0 %}lightgrey{% else %}darkblue{% endif %}">{{ feed.active_subscribers }}</td>
|
||||
<td style="color: {% if feed.premium_subscribers == 0 %}lightgrey{% else %}darkblue{% endif %}">{{ feed.premium_subscribers }}</td>
|
||||
|
@ -50,6 +54,8 @@
|
|||
<td style="color: {% if feed.active_premium_subscribers == 0 %}lightgrey{% else %}darkblue{% endif %}">{{ feed.active_premium_subscribers }}</td>
|
||||
<td style="color: {% if feed.average_stories_per_month == 0 %}lightgrey{% else %}{% endif %}">{{ feed.average_stories_per_month }}</td>
|
||||
<td style="color: {% if feed.stories_last_month == 0 %}lightgrey{% else %}{% endif %}">{{ feed.stories_last_month }}</td>
|
||||
<td style="color: {% if feed.archive_count == 0 %}lightgrey{% else %}{% endif %}">{{ feed.archive_count }}</td>
|
||||
<td style="color: {% if feed.fs_size_bytes == 0 %}lightgrey{% else %}{% endif %}">{{ feed.fs_size_bytes|commify }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
|
|
54
templates/statistics/slow.xhtml
Normal file
54
templates/statistics/slow.xhtml
Normal file
|
@ -0,0 +1,54 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% load utils_tags tz %}
|
||||
|
||||
{% block bodyclass %}NB-body-status NB-static{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="NB-module">
|
||||
|
||||
<div class="queries">
|
||||
<table class="NB-status">
|
||||
{% for user_count in user_counts %}
|
||||
<tr>
|
||||
{% if forloop.first %}<td rowspan={{user_counts|length}} valign=top><b>Users</b>{% endif %}
|
||||
<td><b>{{ user_count.user }}</b></td>
|
||||
<td>{{ user_count.count }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
<div class="queries">
|
||||
<table class="NB-status">
|
||||
{% for path, count in path_counts.items %}
|
||||
<tr>
|
||||
{% if forloop.first %}<td rowspan={{path_counts|length}} valign=top><b>Paths</b>{% endif %}
|
||||
<td><b>{{ path }}</b></td>
|
||||
<td>{{ count }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="NB-status">
|
||||
{% for dt_str, queries in all_queries.items %}
|
||||
{% for query in queries %}
|
||||
<tr>
|
||||
{% if forloop.first %}
|
||||
<td rowspan={{ queries|length }} valign=top> <b>
|
||||
{% localdatetime query.datetime "%a %b %d, %Y %H:%M" %}
|
||||
</b></td>
|
||||
{% endif %}
|
||||
<td>{{ query.user }}</td>
|
||||
<td>{{ query.time }}</td>
|
||||
<td>{{ query.method }}</td>
|
||||
<td>{{ query.path }}</td>
|
||||
<td>{% if query.data %}{{ query.data }}{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock content %}
|
|
@ -380,11 +380,12 @@ resource "digitalocean_droplet" "db-redis-sessions" {
|
|||
}
|
||||
|
||||
resource "digitalocean_droplet" "db-redis-story" {
|
||||
count = 1
|
||||
count = 2
|
||||
image = var.droplet_os
|
||||
name = "db-redis-story${count.index+1}"
|
||||
region = var.droplet_region
|
||||
size = contains([1], count.index) ? "m-8vcpu-64gb" : var.redis_story_droplet_size
|
||||
# size = var.redis_story_droplet_size
|
||||
ssh_keys = [digitalocean_ssh_key.default.fingerprint]
|
||||
provisioner "local-exec" {
|
||||
command = "/srv/newsblur/ansible/utils/generate_inventory.py; sleep 120"
|
||||
|
@ -466,12 +467,13 @@ resource "digitalocean_droplet" "db-postgres" {
|
|||
# servers=$(for i in {1..9}; do echo -n "-target=\"digitalocean_droplet.db-mongo-primary[$i]\" " ; done); tf plan -refresh=false `eval echo $servers`
|
||||
#
|
||||
resource "digitalocean_droplet" "db-mongo-primary" {
|
||||
count = 1
|
||||
count = 2
|
||||
backups = contains([0], count.index) ? false : true
|
||||
image = var.droplet_os
|
||||
name = "db-mongo-primary${count.index+1}"
|
||||
region = var.droplet_region
|
||||
size = contains([1], count.index) ? "m3-8vcpu-64gb" : var.mongo_primary_droplet_size
|
||||
# size = contains([1], count.index) ? "m3-8vcpu-64gb" : var.mongo_primary_droplet_size
|
||||
size = var.mongo_primary_droplet_size
|
||||
ssh_keys = [digitalocean_ssh_key.default.fingerprint]
|
||||
provisioner "local-exec" {
|
||||
command = "/srv/newsblur/ansible/utils/generate_inventory.py; sleep 120"
|
||||
|
|
|
@ -89,5 +89,5 @@ variable "elasticsearch_droplet_size" {
|
|||
|
||||
variable "redis_story_droplet_size" {
|
||||
type = string
|
||||
default = "m-8vcpu-64gb"
|
||||
default = "m-4vcpu-32gb"
|
||||
}
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
from django.conf import settings
|
||||
from utils import log as logging
|
||||
from apps.statistics.rstats import round_time
|
||||
import pickle
|
||||
import base64
|
||||
import time
|
||||
import redis
|
||||
|
||||
IGNORE_PATHS = [
|
||||
"/_haproxychk",
|
||||
]
|
||||
|
||||
RECORD_SLOW_REQUESTS_ABOVE_SECONDS = 10
|
||||
|
||||
class DumpRequestMiddleware:
|
||||
def process_request(self, request):
|
||||
if settings.DEBUG and request.path not in IGNORE_PATHS:
|
||||
|
@ -40,22 +46,31 @@ class DumpRequestMiddleware:
|
|||
redis_log
|
||||
))
|
||||
|
||||
return response
|
||||
|
||||
def elapsed_time(self, request):
|
||||
time_elapsed = ""
|
||||
if hasattr(request, 'start_time'):
|
||||
seconds = time.time() - request.start_time
|
||||
color = '~FB'
|
||||
if seconds >= 1:
|
||||
color = '~FR'
|
||||
elif seconds > .2:
|
||||
color = '~SB~FK'
|
||||
time_elapsed = "[%s%.4ss~SB] " % (
|
||||
color,
|
||||
seconds,
|
||||
)
|
||||
return time_elapsed
|
||||
if seconds > RECORD_SLOW_REQUESTS_ABOVE_SECONDS:
|
||||
r = redis.Redis(connection_pool=settings.REDIS_STATISTICS_POOL)
|
||||
pipe = r.pipeline()
|
||||
minute = round_time(round_to=60)
|
||||
name = f"SLOW:{minute.strftime('%s')}"
|
||||
user_id = request.user.pk if request.user.is_authenticated else "0"
|
||||
data_string = None
|
||||
if request.method == "GET":
|
||||
data_string = ' '.join([f"{key}={value}" for key, value in request.GET.items()])
|
||||
elif request.method == "GET":
|
||||
data_string = ' '.join([f"{key}={value}" for key, value in request.POST.items()])
|
||||
data = {
|
||||
"user_id": user_id,
|
||||
"time": round(seconds, 2),
|
||||
"path": request.path,
|
||||
"method": request.method,
|
||||
"data": data_string,
|
||||
}
|
||||
pipe.lpush(name, base64.b64encode(pickle.dumps(data)).decode('utf-8'))
|
||||
pipe.expire(name, 60*60*12) # 12 hours
|
||||
pipe.execute()
|
||||
|
||||
return response
|
||||
|
||||
def color_db(self, seconds, default):
|
||||
color = default
|
||||
|
|
Loading…
Add table
Reference in a new issue