Русский
Русский
English
Статистика
Реклама

Template

Структура React REST API приложения TypeScript Styled-Components

09.03.2021 16:05:49 | Автор: admin

Доброго %время_суток, хабровчане!

Какие-либо статьи я еще не писал, опыта в этом нет от слова совсем. Поэтому, пожалуйста, не судите строго и прошу заранее прощения, если где-то совершу ошибки.

Это для меня совершенно новый опыт, мне никогда еще не доводилось делиться своим опытом и наработками с сообществом.

Предисловие

Когда я только начал знакомиться с React, мне приходилось искать, и искать довольно-таки долго, информацию, которая помогла бы мне грамотно организовать структуру моих будущих проектов, но дельной информации было мало.

Кто-то советовал делать плоскую структуру, в том числе и для папки, в которой находятся компоненты, кто-то же советовал просто разделять компоненты на "Молекулы", "Организмы" и т.д., но при этом, почти все, мало внимания уделяли сервисам, хукам, а именно тому, что содержало логику, не говорили о том, где лучше и как лучше их хранить.

НО, были и хорошие, одной из которых я вдохновился. В ней говорилось о том, что всю логику хранить в папке "Core". Ссылку на эту статью, на момент публикации, к сожалению, найти не могу, но как найду - обязательно прикреплю, либо отредактирую пост (если такое будет возможно, то на месте этого абзаца будет ссылка на статью), либо оставлю ссылку в виде комментария.

(!) Прошу вас, на время прочтения этой статьи, держать в голове мысль, что то, что тут предлагаю - это всего-лишь одна из идей того, как можно организовать структуру своего приложения. Не стоит воспринимать мои слова, как догму (это относится к тем, кто только начал свое знакомство с React), ведь я и сам новичок.

Буду очень рад, если вы оставите предложения по улучшению данной структуры, как и вашей конструктивной критике.

Components

Начну, пожалуй, с компонентов.

Компоненты, в данной структуре, разделяются на:

  • Умные (Smart)

  • Обычные (Ordinary)

  • Простые (Simple)

  • UI (UI, как ни странно)

  • Контейнеры (Containers)

  • Страницы (Pages)

Первые четыре группы (Smart, Ordinary, Simple и UI) хранятся в папке Components.

Поговорим немного о них:

  • UI компоненты - это те компоненты, которые заменяют нативные (стандартные) компоненты по типу: button, input, textarea, select и так далее.

    • Данные компоненты не могут использовать свое локальное хранилище и обращаться к глобальному.

  • Simple компоненты - это те компоненты, которые являются простыми, иначе говоря компоненты, в которых нет какой-либо логики, которые просто что-то рендерят.

    • Не могут использовать локальное хранилище и обращаться к глобальному.

    • Не могут использовать хуки, кроме тех, что изначально поставляются с React (за исключением useState).

    • Могут использовать в своей реализации UI компоненты.

  • Ordinary компоненты - это те компоненты, которые могут иметь какую-то логику, для отображения чего-либо.

    • Не могу использовать локальное хранилище, как и обращаться к глобальному.

    • Не могут использовать хуки, кроме тех, что изначально поставляются с React (за исключением useState).

    • Могут использовать в своей реализации Simple и UI компоненты.

  • Smart компоненты - это те компоненты, которые могут использовать относительно серьезную логику, для отображения чего-либо

    • Могут использовать локальное хранилище, как и обращаться к глобальному (не изменяя его)

    • Могут использовать все доступные хуки, кроме тех, что взаимодействуют с сетью

    • Могут использовать в своей реализации Ordinary, Simple и UI компоненты.

Структура папки Componets:

. src/     components/        ordinary        simple        smart        ui     ...

Оставшиеся две группы (Containers и Pages) имеют отдельные папки в корне приложения (папка src).

  • Containers - это те компоненты, которые формируют некие контейнеры, которые, в дальнейшем, используются для формирования страниц, которые, к тому же, содержать в своей реализации компоненты остальных четырех групп и при этом взаимодействовать, каким-либо образом, с сервисами или сетью, если говорить обобщенно.

  • Pages - это те компоненты, которые формируются благодаря контейнерам и компонентам из папки Components, если в этом есть необходимость. Могут, как и контейнеры, взаимодействовать с сервисами.

Структура корневой папки:

. src/     components/        ordinary        simple        smart        ui     containers     pages     ...

Сами компоненты должны иметь отдельные папки, есть 2 (это число не является константой) файла:

  • index.tsx - файл, в котором находится сам компонент

  • styled.ts - файл, в котором находятся стилизованные компоненты (его спокойно можно заменить на styles.sсss, либо же styles.css, в зависимости от того, чем вы пользуетесь для стилизации своих компонентов)

Пример компонента Align. Хотелось бы сказать, что этот компонент попадает под группу "Simple", так как он является глупым (не имеет нужды в локальном хранилище) и не заменяет никакой нативный, браузерный, UI компонент.

// index.tsximport React, { memo } from "react";import * as S from "./styled"; // Импортируем стилизованные компонентыconst Align = memo(({ children, axis, isAdaptable = false }: Readonly<Props>) => {return (<S.Align $axis={axis} $isAdaptable={isAdaptable}>{children}</S.Align>);});export { Align };export interface Props {axis: S.Axis;children?: React.ReactNode;isAdaptable?: boolean;}
// styled.tsimport styled, { css } from "styled-components";const notAdaptableMixin = css`width: 100%;height: 100%;max-height: 100%;max-width: 100%;`;const adaptableMixin = css<AlignProps>`width: ${(props) => !props.$axis.includes("x") && "100%"};height: ${(props) => !props.$axis.includes("y") && "100%"};min-width: ${(props) => props.$axis.includes("x") && "100%"};min-height: ${(props) => props.$axis.includes("y") && "100%"};`;export const Align = styled.div<AlignProps>`display: flex;flex-grow: 1;justify-content: ${(props) => (props.$axis.includes("x") ? "center" : "start")};align-items: ${(props) => (props.$axis.includes("y") ? "center" : "start")};${(props) => (props.$isAdaptable ? adaptableMixin : notAdaptableMixin)};`;export interface AlignProps {$axis: Axis;$isAdaptable?: boolean;}export type Axis = ("y" | "x")[] | "x" | "y";

Теперь, поговорим о самом сладком...

Core

Данная папка является "ядром" вашего приложения. В ней хранится все, для взаимодействия с сервером, глобальное хранилище, тема вашего приложения и т.д.

Эта папка содержит:

  • Config - в данной папке хранятся конфигурационные файлы приложения (например в ней можно хранить данные, необходимы для взаимодействия с бэкендом)

  • Constants - в данной папке находятся все константы, что используются в приложении (например сообщения об ошибках и предупреждениях)

  • Hooks - в данной папке хранятся все хуки кастомные хуки (хуки, что были сделаны вами).

  • Models - в данной папке хранятся модели, что приходят с бэкенда.

  • Schemes - в данной папке хранятся схемы форм, таблиц и т.д.

  • Services - в данной папке хранятся сами сервисы, благодаря которым и происходит общение с бэкендом.

  • Store - в данной папке хранятся схемы глобального хранилища (если Вы используете MobX), если же вы отдаете предпочтение Redux, то в данной папке могут хранится экшены, редьюсеры и т.д.

  • Theme (для Styled-Components) - в данной папке хранятся темы приложения.

  • Types - в данной папке хранятся вспомогательные типы, а также декларации модулей.

  • Utils - в данной папке хранятся вспомогательные, простые, функции, которые могут использоваться в хуках, либо же компонентах.

  • api.ts - в данном файле находится экземпляр HTTP клиента (например axios), который используют сервисы и который какой-то мутирует данные запросы (для передачи каких-либо заголовков, например).

Примеры содержимого папок
// config/api.config.tsexport const serverURI = "http://localhost:8080";export const routesPrefix = '/api/v1';// config/routes.config.tsimport { routesPrefix } from "./api.config";export const productBrowserRoutes = {getOne: (to: string = ":code") => `/product/${to}`,search: (param: string = ":search") => `/search/${param}`,};export const productAPIRoutes = {getOne: (code: string) => `${routesPrefix}/product/code/${code}`,search: () => `${routesPrefix}/product/search`,};

Довольно-таки удобно хранить все роуты в одном файле. Если вдруг со стороны бэкенда изменятся роуты, то их легко можно будет изменить в одном файле, не проходясь, при этом, по всему проекту.

// constants/message.constants.tsexport const UNKNOWN_ERROR = "Неизвестная ошибка";
// hooks/useAPI.ts// Хук для взаимодействия с сервисами/* eslint-disable react-hooks/exhaustive-deps */import { useCallback, useEffect } from "react";import { useLocalObservable } from "mobx-react-lite";import type { API, Schema, Take } from "@core/types";function useAPI<F extends API.Service.Function<API.Response<any>>,R extends Take.FromServiceFunction.Response<F>,P extends Parameters<F>>(service: F, { isPendingAfterMount = false, isIgnoreHTTPErrors = false }: Options = {}) {const localStore = useLocalObservable<Store>(() => ({isPending: {value: isPendingAfterMount,set: function (value) {this.value = value;},},}));const call = useCallback(async (...params: P): Promise<R["result"]> => {localStore.isPending.set(true);try {const { data } = await service(...params);const { result } = data;localStore.isPending.set(false);return result;} catch (error) {if (isIgnoreHTTPErrors === false) {console.error(error);}localStore.isPending.set(false);throw error;}},[service, isIgnoreHTTPErrors]);const isPending = useCallback(() => {return localStore.isPending.value;}, []);useEffect(() => {localStore.isPending.set(isPendingAfterMount);}, [isPendingAfterMount]);return {call,isPending,};}export { useAPI };export interface Options {isPendingAfterMount?: boolean;isIgnoreHTTPErrors?: boolean;}type Store = Schema.Store<{ isPending: boolean }>;
// models/product.model.ts// Описание модели товараexport interface ProductModel {id: number;name: string;code: string;info: {description: string;note: string;};config: {isAllowedForPurchaseIfInStockZero: boolean;isInStock: boolean;};seo: {title: string;keywords: string;description: string;};}
// services/product.service.ts// Сервисы для взаимодействия с товарамиimport { api } from "../api";import { routesConfig } from "../config";import type { ProductModel } from "../models";import type { API } from "../types";export function getOne(code: string) {return api.get<API.Service.Response.GetOne<ProductModel>>(routesConfig.productAPIRoutes.getOne(code));}
// theme/index.ts// Тема приложенияimport { DefaultTheme } from "styled-components";export const theme: DefaultTheme = {colors: {primary: "#2648f1",intense: "#151e27",green: "#53d769",grey: "#626b73",red: "#f73d34",orange: "#fdb549",yellow: "#ffe243",white: "white",},};
// types/index.tsx// Вспомогательные типыimport type { AxiosResponse } from "axios";export namespace API {export namespace Service {export namespace Response {export type Upsert<T> = Response<T | null>;export type GetOne<T> = Response<T | null>;export type GetMany<T> = Response<{rows: T[];totalRowCount: number;totalPageCount: number;}>;}export type Function<T extends API.Response<any>, U extends any[] = any[]> = (...params: U) => Promise<AxiosResponse<T>>;}export type Response<T> = {status: number;result: T;};}
// utils/throttle.tsfunction throttle<P extends any[]>(func: (...params: P) => any, limit: number) {let inThrottle: boolean;return function (...params: P): any {if (!inThrottle) {inThrottle = true;func(...params);setTimeout(() => (inThrottle = false), limit);}};}export { throttle };
// store/index.tsximport { createContext } from "react";import { useLocalObservable } from "mobx-react-lite";import { app, App } from "./segments/app";import { layout, Layout } from "./segments/layout";import { counters, Counters } from "./segments/counters";export const combinedStore = { layout, app, counters };export const storeContext = createContext<StoreContext>(combinedStore);export function StoreProvider({ children }: { children: React.ReactNode }) {const store = useLocalObservable(() => combinedStore);return <storeContext.Provider value={store}>{children}</storeContext.Provider>;}export type StoreContext = {app: App;layout: Layout;counters: Counters;};
// api.ts// Экземпляр AXIOS для взаимодействия с серверомimport axios from "axios";import { apiConfig } from "./config";const api = axios.create({baseURL: apiConfig.serverURI,});api.interceptors.request.use((req) => {return {...req,baseURL: apiConfig.serverURI,};});export { api };

Ух ты! Как же много получилось.

И напоследок...

Есть еще несколько, немаловажных папок, которые также следует упомянуть:

  • Assets - в данной папке хранятся все статичные файлы, такие как: иконки, изображения, шрифты и т.д. (их, конечно же, также стоит группировать и разделять на папки)

  • Routes - в данной папке (либо же файле, кому как больше нравится) хранятся все роуты приложения (пример будет ниже).

  • Styles - в данной папке хранятся все глобальные стили, которые применяются ко всем элементам и документу, в том числе.

// routes/index.tsximport { Switch, Route } from "react-router-dom";// Экспортируем страницыimport { Product } from "../pages/Product";...import { NotFound } from "../pages/NotFound";import { routesConfig } from "../core/config";const Routes = () => {return (<Switch><Route exact path={routesConfig.productBrowserRoutes.getOne()}><Product /></Route>{/* Объявляем как-то роуты */}<Route><NotFound /></Route></Switch>);};export { Routes };

Остается еще 2 файла:

  • app.tsx - компонент приложения

Примерно так он может выглядеть:

// app.tsximport React, { useEffect } from "react";// Импортирует роутыimport { Routes } from "./routes";const App = () => {return (<Routes />);};export { App };
  • index.tsx - входной файл вашего приложения

Он же может выглядеть примерно так:

import React from "react";import ReactDOM from "react-dom";import { BrowserRouter } from "react-router-dom";import { ThemeProvider } from "styled-components";// импортируем нашеimport { App } from "./app";// импортируем глобальные стилиimport { BodyStyles } from "./styles";import { StoreProvider } from "../core/store";// импортируем темуimport { theme } from "../core/theme";import reportWebVitals from "./reportWebVitals";const app = document.getElementById("app");ReactDOM.render(<React.StrictMode><ThemeProvider theme={theme}><BodyStyles /><BrowserRouter><StoreProvider><App /></StoreProvider></BrowserRouter></ThemeProvider></React.StrictMode>,app);reportWebVitals();

И на этом, я думаю, стоит закончить.

Итоговая структура выглядит вот так:

. src/     assets/        fonts        icons     components/        ordinary        simple        smart        ui     containers     core/        config        constants        hooks        models        schemes        services        store        theme        types        utils        api.ts     pages     routes     styles     app.tsx     index.tsx

Заключение

Если вам понравилась эта статья и вы узнали что-то интересное для себя, то я очень этому рад.

Ссылка на репозиторий (за такой скудный ридми, мне еще не по силам разговорный английский).

Всем удачи и огромное спасибо за внимание.

Подробнее..

Zabbix-шаблон для мониторинга DFS-репликации

03.09.2020 00:21:01 | Автор: admin

Я давно собирался настроить мониторинг службы DFS Replication на нашем Zabbix, но готовых шаблонов в сети не нашел. Попалось несколько заброшенных проектов тут и тут, но первый автор так и не довел до конца, а во втором не работала ссылка для скачивания шаблона. К тому же, оба ограничивались лишь мониторингом бэклогов, хотя по факту метрик намного больше. Поэтому я решил сделать свой велосипед с круглым рулем и турбинами шаблон сдискавери и скриптами. Начал уже давно, но довести дело до конца всё руки не доходили. Как говорится, нет худа без добра: на удаленке в самоизоляции наконец доделал. Работы было проделано много, но я не жадный, поэтому делюсь. :)

Before you begin

  • Далее в тексте под хостом я буду иметь в виду сервер с ролью DFSR, для которого настраивается мониторинг.

  • Иногда для краткости вместо словосочетаний группа репликации и реплицируемая папка я буду пользоваться аббревиатурами RG и RF.

В общем и целом

В первую очередь надо было определить для себя, что мониторить и как мониторить.

Ответить на второй вопрос мне было легко. Разумеется, это будет мониторинг агентом с LLD и кастомными скриптами. Выбирая язык для скриптов, я, не долго думая, остановился на PowerShell. Много возможностей, активно продвигается Microsoft, горячо любим мной :). Была еще мысль сделать на VBScript для легковесности совместимости со старыми версиями Windows, но, подумав, отказался от этой затеи.

Всего в решении два PS-скрипта: Get-DFSRObjectDiscovery.ps1 и Get-DFSRObjectParam.ps1

Как легко понять из названия, первый - для обнаружения объектов мониторинга (item или элемент данных в терминлогии Zabbix), второй - для получения значений свойств этих объектов. Данные в основном собираются посредством WMI-запросов. Разбирать скрипты здесь не буду, т.к. комментарии есть в самом коде.

Ответить на вопрос "что мониторить?" было сложнее. Методом тыка Полагаясь на свой опыт развертывания DFSR и изучив документацию, я выделил несколько основных сущностей, относящихся к DFSR, для каждой из этих сущностей составил список параметров, значения которых мне было бы интересно мониторить.

Итак, сущности:

  • группы репликации;

  • реплицируемые папки;

  • подключения;

  • тома DFSR;

  • партнеры;

  • общее состояние.

Метрики для каждой из сущностей и способы их сбора будут описаны ниже.

Данные будут собираться только для тех объектов DFSR, к которым имеет отношение хост. Например, если в Active Directory есть группа репликации MyRG3, но хост в нее не входит, то метрики для нее собираться не будут. Аналогично с папками и подключениями.

Для большинства айтемов и триггеров в шаблоне есть описания и ссылки на статьи из базы знаний Microsoft.

В лабе я тестировал шаблон на разных версиях Zabbix от 2.2 до 5.0 и Windows от 2008R2 SP1 до 2019, в продакшне опробовал на Zabbix 3.4, Zabbix 5.0 и Windows 2012 R2.

В шаблоне используются преобразования значений (value mapping), поэтому потребуются права суперадмина на сервере Zabbix.

Группы репликации (DFS Replication Groups)

Параметры:

  • количество исходящих подключений (outbound connections);

  • количество входящих подключений (inbound connections);

  • количество реплицируемых папок (number of folders);

  • отключенное расписание (blank schedule).

Все эти параметры и триггеры для них описаны в правиле обнаружения DFS Replication Groups LLD.

С количеством подключений и папок, думаю, понятно, про расписание немного поясню. Для группы репликации задается расписание по умолчанию, которое будут наследовать подключения, создаваемые между партнерами этой группы. Администратор может ограничить использование полосы пропускания в зависимости от дня недели и времени суток вплоть до полной остановки репликации в определенное время. В случае, если в расписании репликация отключена полностью для каждого часа каждого дня недели, этот параметр будет равен 1, в противном случае должен возвращаться 0.

С помощью триггеров отслеживается отключение расписания, изменение количества подключений и реплицируемых папок в группе. Триггеры простые, поэтому разбирать их не буду.

Реплицируемые папки (DFS Replicated Folders)

Параметры:

  • количество файлов в бэклогах (backlog size);

  • состояние (state)

  • включена или выключена (enabled)

  • режим "только чтение" ('read-only' mode)

  • настройка "Переместить удаленные файлы в папку конфликтов и удалений" ('remove deleted' enabled)

  • отказоустойчивость (redundancy)

  • размер, заданный для промежуточной папки (stage quota)

  • занятое место в промежуточной папке (stage used)

  • процент свободного места в промежуточной папке (stage free (percentage))

  • размер, заданный для папки конфликтов и удалений (conflict quota)

  • занятое место в папке конфликтов и удалений (conflict used)

  • процент свободного места в папке конфликтов и удалений (conflict free (percentage))

  • данные счетчиков производительности;

Для бэклогов создано правило обнаружения DFS Replicated Folders Backlog LLD. Я решил мониторить только исходящие бэклоги. Во-первых, DFSR - распределенная система, поэтому предполагается, что мониторинг будет настроен комплексный, на все DFSR-серверы. И, учитывая, что исходящий бэклог сервера = входящий бэклог его партнера, я решил не дублировать по сути одну и ту же метрику, привязывая ее к разным хостам. Во-вторых, очередь входящих файлов характеризует больше не локальный сервер, а его партнера, расходуя место в его промежуточной папке и, как правило, вызывая предупреждения в журнале событий этого партнера.

Для кастомизации мониторинга бэклогов есть 3 макроса:

{$BACKLOGMAXWARNING} - порог для warning-триггера (по умолчанию равен 10);

{$BACKLOGMAXAVERAGE} - порог для average-триггера (по умолчанию равен 100);

{$BACKLOGPERIOD} - как долго размер бэклога должен быть выше порогового значения (по умолчанию 15 минут).

Таким образом, если количество файлов в бэклоге превышает 10 в течение 15 минут, срабатывает warning-триггер. Если же количество файлов переваливает за 100, то срабатывает уже average-триггер.

Кстати, пока прорабатывал тему мониторинга DFSR, с удивлением обнаружил, что в Managment Pack для SCOM ("православная" система мониторинга для продуктов Microsoft) сбор данных о бэклогах по умолчанию отключен для экономии ресурсов сервера. Мне же это видится одной из главных метрик, дающих представление о состоянии сервиса. Поэтому я добавил для него еще и график:

За сбор остальных параметров (кроме счетчиков производительности) отвечает правило DFS Replicated Folders LLD. Здесь всё должно быть понятно, поясню только параметры state и redundancy.

State - это состояние папки, которое может принимать одно из следующих значений:

  • Uninitialized (0)

  • Initialized (1)

  • Initial Sync (2)

  • Auto Recovery (3)

  • Normal (4)

  • In Error (5)

Redundancy - это количество партнеров, на которых есть копия папки в состоянии Normal. Если окажется, что у папки нет рабочих копий ни на одном из партнеров, сработает соответствующий триггер.

Предвосхищая резонный вопрос про stage free (percentage) и conflict free (percentage), сразу отвечу. Да, можно было бы сделать их в виде вычисляемых айтемов, но я решил выполнять эти вычисления на стороне хостов, чтобы снизить нагрузку на zabbix-сервер.

Если в промежуточной папке или папке конфликтов остается менее 5% свободного места, срабатывают соответствующие триггеры. Стандартное значение 5% можно переназначить с помощью макросов {$STAGEDIRPFREEMIN} и {$CONFLICTDIRPFREEMIN}.

Для счетчиков производительности есть правило обнаружения DFS Replicated Folders PerfCounters LLD. Большинство прототипов в нем отключено по умолчанию, т.к., на мой взгляд, это лишняя информация, которая будет расходовать место в базе данных и отнимать процессорное время. Но ничто не мешает вам включить нужные счетчики как на уровне шаблона, так и для конкретного хоста или даже айтема на этом хосте. Кстати, при работе со счетчиками есть свои нюансы, о которых я расскажу позже в отдельной статье.

А вот одним из полезных, на мой взгляд, счетчиков счетчик Conflict Files Generated, который возвращает суммарное число файлов, проигравших в конфликтах для определенной RF. Поэтому для него есть соответствующий прототип айтемов и триггеры. Для кастомизации этих триггеров есть макросы:

{$CONFLICTSGENERATEDCHANGEWARNING} - пороговое значение, при превышении которого сработает warning-триггер (по умолчанию 10);

{$CONFLICTSGENERATEDCHANGEAVERAGE} - аналогично для average-триггера (по умолчанию 100);

{$CONFLICTSGENERATEDPERIOD} - период времени, в течение которого должно произойти нужное количество конфликтов, чтобы сработал триггер (по умолчанию 5 минут).

Таким образом, если за 5 минут обнаружится более 10-ти конфликтов, то сработает warning-триггер, если больше 100 - то average-триггер.

Зачем вообще отслеживать конфликты? Представим такую ситуацию. У нас есть общая папка, опубликованная в DFSN в виде виртуального пути \\abc.com\Share. Для папки есть два конечных объекта (реальные шары на файловых серверах): \\server1\Share и \\server2\Share. Эти шары входят в группу репликации и доступны конечным пользователям в режиме чтение+запись на обоих серверах. Файловые серверы расположены в разных AD-сайтах (пусть будет Office1 и Office2). Пользователь Иванов из Office1, обратившись по пути \\abc.com\Share, попадает на server1, а его коллега Петров из Office2 - на server2 (разумеется, для пользователей это происходит прозрачно и они не подозревают, что каждый из них работает со своей копией файлов, которые фактически расположены на разных серверах). Иванов и Петров открывают файл \\abc.com\Share\Важный_отчет.xlsx (каждый - со своего сервера) и заносят туда данные. А потом перед совещанием внезапно оказывается, что сохранились только те данные, которые внес Петров, а то, что сделано Ивановым, чудесным образом исчезло, хотя он честно жал Ctrl+S каждые 5 минут, как его учили технари. Благо, данные таки можно восстановить, но зуб на ИТ у Иванова останется, ибо виноват во всем админ Сидоров, который не предусмотрел такой сценарий.

Разумеется, есть случаи, когда конфликты - это нормальная ситуация, которая не приводит к потере бизнес-значимых данных, но обычно обилие конфликтов говорит о неверно продуманной архитектуре DFS-решения. И лучше об этом заранее узнать от системы мониторинга, чем потом от пользователей.

Разумеется, есть случаи, когда конфликты - это нормальная ситуация, которая не приводит к потере бизнес-значимых данных, но обычно обилие конфликтов говорит о неверно продуманной архитектуре DFS-решения. И лучше об этом заранее узнать от системы мониторинга, чем потом от пользователей.

Для RF есть 4 прототипа графиков:

  • использование места в папке конфликтов и удалений (conflict space usage)

  • использование места в промежуточной папке (stage space usage)

  • размер данных, полученных от партнеров с учетом сжатия и без него (received bytes)

  • количество принятых файлов и количество конфликтов (received files and conflicts)

Подключения (DFS Replication Connections)

Параметры:

  • состояние (state);

  • включено или выключено (enabled);

  • отключенное расписание (blank schedule);

  • данные счетчиков производительности.

Два правила обнаружения: DFS Replication Connections LLD - для первых трех параметров, DFS Replication Connections PerfCounters LLD - для счетчиков.

State - это состояние подключения, может быть таким:

  • Connecting (0)

  • Online (1)

  • Offline (2)

  • In Error (3)

Enabled - тут понятно.

Blank schedule - аналогично параметру для RG. Подключение может иметь индивидуальное расписание, отличное от дефолтного, заданного на уровне RG.

Как и для RF, прототипы айтемов здесь почти все отключены, оставлен только счетчик bytes received per second, для которого также есть график:

Тома DFSR (DFS Replication Service Volumes)

Параметры:

  • состояние (state);

  • данные счетчиков производительности.

Два правила обнаружения: DFS Replication Service Volumes LLD и DFS Replication Service Volumes PerfCounters LLD. Первое - для параметра state, который может принимать следующие значения:

  • Initialized (0)

  • Shutting Down (1)

  • In Error (2)

  • Auto Recovery (3)

Второе правило обнаружения используется для счетчиков производительности и по умолчанию отключено.

Партнеры (DFS Replication Partners)

Параметры:

  • доступность по PING (ping check);

  • доступность по WMI (WMI check).

За оба параметра отвечает правило обнаружения DFS Replication Partners LLD. Как следует из названия, это два типа проверки: проверяется, может ли хост "достучаться" до каждого из партнеров по ICMP и WMI. Подключение по WMI будет выполняться под учетной записью, из-под которой работает служба zabbix-агента. При этом единственное назначение WMI-проверки - убедиться, что установленный на хосте агент может связаться с DFSR-партнером для сбора параметров backlog size и redundancy (они были описаны выше при разборе метрик для реплицируемых папок). А для этого необходимо, чтобы учетная запись zabbix-агента обладала правами локального администратора на каждом из партнеров. Иными словами, WMI-проверка подскажет, если у учетной записи агента не хватает прав на каком-либо из партнеров. Выглядеть это будет вот так:

Общее состояние (General)

Параметры:

  • установлена ли роль DFSR (DFS Replication role installed);

  • количество групп репликации, в которые входит сервер (Number of replication groups);

  • количество ошибок и предупреждений в журнале событий DFSR (DFSR Event Log);

  • состояние службы (DFS Replication service state);

  • аптайм службы (DFS Replication service uptime);

  • версия службы (DFSR Service Version);

  • версия поставщика DFSR (DFSR Provider Version);

  • версия поставщика мониторинга DFSR (DFSR Monitoring Provider Version);

Последние два параметра по умолчанию отключены.

Здесь правила обнаружения не нужны, поэтому все параметры находятся в разделе Items шаблона.

Немного замечаний о мониторинге журнала событий. За это отвечают 3 айтема, каждый из которых мониторит события определенного уровня критичности:

  • DFSR Event Log: number of warnings

  • DFSR Event Log: number of errors

  • DFSR Event Log: number of critical errors

Парсинг журнала был отдан на откуп агенту, а точнее - PS-скрипту. На входе скрипт получает тип событий (предупреждение, ошибка, критическое) и период, за который нужно проанализировать журнал. На выходе отдает количество событий, соответствующих заданным критериям. Если за последний час в логе найдется хотя бы одно предупреждение или ошибка, то сработает триггер. Эти настройки можно поменять с помощью макросов:

{$DFSRLOGCRITICALMAX} - количество событий со статусом "Критическое" в логе DFSR, при превышении которого должен срабатывать high-триггер (по умолчанию 0);

{$DFSRLOGERRORSMAX} - количество событий со статусом "Ошибка" в логе DFSR, при превышении которого должен срабатывать average-триггер (по умолчанию 0);

{$DFSRLOGWARNINGSMAX} - количество событий со статусом "Предупреждение" в логе DFSR, при превышении которого должен срабатывать warning-триггер (по умолчанию 0);

{$DFSRLOGPERIOD} - за какое время надо анализировать лог (по умолчанию 1 час)

Состояние службы может принимать такие значения:

  • Service Starting (0)

  • Service Running (1)

  • Service Degraded (2)

  • Service Shutting Down (3)

  • Stopped (100)

  • Not Found (101)

Остальные параметры разбирать не буду, там всё ясно из названия.

Напоследок

Чтобы иметь наглядную картину, я создал для каждой RG соответствующую группу хостов на Zabbix-сервере и сделал для каждой RG скрин, на котором видно общее состояние хостов группы и графики для различных метрик.

Получилось примерно так:

В процессе продакшн-эксплуатации шаблона выявилась проблема с опросом значений счетчиков производительности для RF: по непонятным мне причинам агент Zabbix перестает получать их показания и генерирует в своем логе ошибки вида "perf_counter[\XXX\YYY]" is not supported: Cannot obtain performance information from collector. Средствами же самой Windows (perfmon, typeperf, Get-Counter) эти счетчики опрашиваются нормально. Лечится перезапуском службы Zabbix Agent. Проблема касается только RF-счетчиков, счетчики для других сущностей (например, для подключений) агент опрашивает без проблем.

Шаблон и инструкции по установке есть на GitHub и Zabbix Share. Забирайте!

Буду рад конструктивной критике и предложениям по улучшению шаблона.

Источники вдохновения

Monitoring DFSR

DFSR WMI Classes

DFSR Performance Objects, Their Counters, Corresponding WMI Classes, and Using WMIC or Vbscript to View Them

Get-DFSRBacklog (Technet gallery)

DFS Replication Backlog Discovery

DFS Replication Management Pack for Windows Server 2008 R2

Optional configuration for the DFS Replication Management Pack

PowerShell Zabbix Json и ConvertTo-Json2

Displaying Unicode in Powershell

powershell : changing the culture of current session

Searching the Active Directory with PowerShell

PowerShell scripting performance considerations

Подробнее..

Мемоизация в compile time вычислениях в C

02.03.2021 04:06:51 | Автор: admin

Программистам на C++ хорошо (надеюсь!) известно, что во время компиляции можно производить разнообразные вычисления. Лишь бы эти вычисления были "чистыми", без побочных эффектов. Это делается на шаблонах и на constexpr функциях.

Чистое выражение означает, что, сколько бы раз мы его ни попытались вычислить, мы получим один и тот же результат. Поэтому из соображений эффективности результат желательно мемоизировать, чтобы второй раз не делать ту же работу. Но насколько хорошо это умеет делать компилятор?

Для стресс-тестирования возьмём наивную формулу чисел Фибоначчи:

f(n) = (n < 2) ? 1 : f(n-1) + f(n-2)

Она интересна нам по нескольким причинам:

  • глубина рекурсии линейно зависит от n

  • без мемоизации она сводится к суммированию f(n) единичек, а это, напомню, экспонента по основанию золотого сечения

  • с мемоизацией к запоминанию n значений

Как вычислить эту формулу в compile time?

Для этого есть 3 техники.

Первая, хорошо известная с давних времён (с C++98): шаблоны с целочисленными параметрами и членами-константами. (В древности использовались enum'ы, потом появились статические константы).

// для удобства и краткости записи, будем наследоваться от шаблонного типаtemplate<unsigned n> struct int_ { static constexpr unsigned value = n; };template<unsigned n> struct f;template<> struct f<0> : int_<1> {};template<> struct f<1> : int_<1> {};template<unsigned n> struct f : int_< f<n-1>::value + f<n-2>::value > {};template<unsigned n> constexpr unsigned F = f<n>::value;

(будем использовать unsigned, потому что собственно значения чисел нам для опытов не нужны, а нарываться на целочисленное переполнение не хочется).

Вторая техника стала доступна с С++11: это constexpr-функции.

constexpr unsigned f(unsigned n) {  return n < 2 ? f(n-1) + f(n-2);}template<unsigned n> constexpr unsigned F = f(n);

Чем она подкупает, так это прозрачностью кода и минималистичностью. Есть ещё один плюс: мы можем вместо рекурсии написать цикл и получить более эффективную реализацию. За линейное, а если очень хочется, то и за логарифмическое время (возведение матриц в степень).

constexpr unsigned f(unsigned n) {  unsigned a = 1, b = 1;  for (i = 1; i < n; ++i) {    unsigned c = a + b;    a = b; b = c;  }  return b;}

Но не в этот раз.

Третья техника тоже связана с шаблонами, а именно expression template. Суть в том, что тип выражения выводится из типов аргументов. Конечно, для чисел Фибоначчи это выглядит извращением, но на expression template можно строить оптимальные планы сложных вычислений (например, цепочка из произведения нескольких матриц разного размера). Поэтому изучим и этот способ.

// снова воспользуемся типом, несущим числоtemplate<unsigned n> struct int_ { static constexpr unsigned value = n; };// поскольку мы делаем expression template, то введём арифметику на шаблонах:template<unsigned x, unsigned y> auto operator + (int_<x>, int_<y>) {  return int_<x+y>{};}template<unsigned n> auto f(int_<n> /*arg*/) {  if constexpr (n < 2) {    return int_<1>{};  } else {    // можем написать так (expression template же!)    return f(int_<n-1>{}) + f(int_<n-2>{});        // или так - подчёркивая, что вся информация есть прямо в типе,    // и собственно вызовы нам не нужны:    return decltype( f(int_<n-1>{}) + f(int_<n-2>{}) ){};    // или так:    using t1 = decltype(f(int_<n-1>{}));    using t2 = decltype(f(int_<n-2>{}));    return int_<t1::value + t2::value>{};  }}template<unsigned n> constexpr unsigned F = decltype(f(int_<n>{}))::value;

Кстати, обратите внимание: вместо перегрузок / специализаций шаблона мы смогли сделать ветвление прямо внутри функции, благодаря стейтменту if constexpr, появившемуся в C++17. С шаблоном класса такой фокус не прошёл бы. Плюсик к выразительности (уравновешивает минусик: мы написали функцию, которую не вызываем; впрочем, и от шаблона класса нам нужно лишь значение его члена, а никак не структура).

И ещё один штрих: наша функция не обязана быть constexpr. Можем даже отладочный вывод туда напихать.

Перейдём, наконец, к мемоизации.

Чтобы воплотить (instantiate) шаблон класса с данным параметром n, нам надо воплотить шаблон со значениями параметра n-1 и n-2, а их, соответственно, n-2 и n-3, и так далее до 1 и 0, а потом 2 и 1 (уже воплощены), 3 и 2 (и они тоже уже воплощены), и так далее. То есть, вторые ветки наших выражений будут обращаться к уже созданным типам.

Единственное ограничение, с которым мы тут сталкиваемся это глубина рекурсии.

У gcc порог называется -ftemplate-depth по умолчанию равен 900, у clang -ftemplate-backtrace-limit 1024.

Мемоизировать тип именованного шаблона с данными параметрами это естественно. А вот будет ли компилятор мемоизировать тип выражения? Это менее очевидно, но практика показывает: будет. Так что expression template тоже упирается в глубину рекурсии.

Наконец, constexpr функции. Снова упираемся в глубину рекурсии, но это уже другой порог: gcc называет его -fconxtexpr-depth, clang -fconstexpr-backtrace-limit, по умолчанию он равен 512.

Но всё было бы хорошо, если бы не одно странное но.

gcc вплоть до версии 9.3 умел мемоизировать функции! Поэтому можно было спокойно написать F<512>и даже F<1022>и почти мгновенно скомпилировать программу, выводящую соответственное число Фибоначчи.

А, начиная с версии 10.1, gcc перестал это делать. Он честно вызывает функцию экспоненциальное количество раз и упирается в порог количества операций -fconstexpr-ops-limit, по умолчанию 33554432.

Обратили внимание на два разных максимальных параметра?

F<512>и F<1022> откуда они взялись? Неужели можно числа Фибоначчи вычислять по-разному? Конечно, да.

template<unsigned n> struct f;template<unsigned n> struct g;template<> struct f<0> : int_<1> {};template<> struct f<1> : int_<1> {};template<unsigned n> struct f : int_< f<n-1>::value + f<n-2>::value > {};template<> struct g<0> : int_<1> {};template<> struct g<1> : int_<1> {};template<unsigned n> struct g : int_< g<n-2>::value + g<n-1>::value > {};template<unsigned n> constexpr unsigned F = f<n>::value;template<unsigned n> constexpr unsigned G = g<n>::value;

В первом случае мы в первой ветке спускаемся на 1, во втором на 2. И там и там мы, конечно, на каждом шаге получим все предшествующие значения и мемоизируем их, так что два раза одну работу делать не будем.

Та же история и с expression template.

GodBolt

Я ставил опыты на разных версиях компиляторов на сайте gcc.godbolt.org

В частности,

  • gcc 10.1

  • clang 11.0.0 ему труднее дались expression template

  • clang 9.0.0 если ему подсунуть слишком большое значение в expression template, то он просто крешится, вместо диагностики: пытается построить мега-дерево выражения

  • gcc 9.3 отлично справился с мемоизацией функции!

Максимальные значения параметров для разных способов выглядят так:

компилятор

gcc 9.3

gcc 10.1

clang 11.0.0

class template
(CT)

CT::F

899

899

1024

CT::G

1798

1798

2048

expression template
(ET)

ET::F

899

899

702

ET::G

1796

1796

1402

constexpr
(CE)

CE::F

512

35

26

CE::G

512

35

26

Полный код теста.

#include <iostream>template<unsigned n> struct int_ { static constexpr unsigned value = n; };template<unsigned x, unsigned y> auto operator + (int_<x>, int_<y>) {  return int_<x+y>{};}namespace CT {  // class templatetemplate<unsigned n> struct f;template<> struct f<0> : int_<1> {};template<> struct f<1> : int_<1> {};template<unsigned n> struct f : int_<f<n-1>::value + f<n-2>::value> {};template<unsigned n> struct g;template<> struct g<0> : int_<1> {};template<> struct g<1> : int_<1> {};template<unsigned n> struct g : int_<g<n-2>::value + g<n-1>::value> {};template<unsigned n> constexpr unsigned F = f<n>::value;template<unsigned n> constexpr unsigned G = g<n>::value;}  // namespace CTnamespace ET {  // expression templatetemplate<unsigned n> auto f(int_<n>) {  if constexpr (n < 2) {    return int_<1>{};  } else {    return f(int_<n-1>{}) + f(int_<n-2>{});  }}template<unsigned n> auto g(int_<n>) {  if constexpr (n < 2) {    return int_<1>{};  } else {    return g(int_<n-2>{}) + g(int_<n-1>{});  }}template<unsigned n> constexpr unsigned F = decltype(f(int_<n>{}))::value;template<unsigned n> constexpr unsigned G = decltype(g(int_<n>{}))::value;}  // namespace ETnamespace CE {  // constexprconstexpr unsigned f(unsigned n) {  return n < 2 ? 1 : f(n-1) + f(n-2);}constexpr unsigned g(unsigned n) {  return n < 2 ? 1 : g(n-1) + g(n-2);}template<unsigned n> constexpr unsigned F = f(n);template<unsigned n> constexpr unsigned G = g(n);}  // namespace CEint main() {  std::cout << CT::F<899> << std::endl;  std::cout << CT::G<1798> << std::endl;  std::cout << ET::F<899> << std::endl;  std::cout << ET::G<1796> << std::endl;  std::cout << CE::F<35> << std::endl;  std::cout << CE::G<35> << std::endl;}

Выводы?

Просто хотел поделиться своими наблюдениями.

А также показать существующие ограничения вычислений во время компиляции. Конечно же, то, что язык шаблонов и constexpr'ов тьюринг-полный, ещё не означает, что компилятор будет решать за вас проблему останова. Он или выдаст диагностику, или покрешится, или выжрет всю память.

Причём от версии к версии компилятора поведение может различаться. Поэтому пользуйтесь возможностями, но будьте аккуратны и не доводите до крайностей.

Кстати о "выжрать всю память"

Если мы помешаем мемоизации например, добавим ещё один параметр, принимающий уникальные значения, то, в отличие от constexpr-функций, компилятор будет биться до победы... или до поражения.

Функция, считающая количество операций для вычисления числа Фибоначчи

f(n, t) = n < 2 ? t+1 : f(n-1, f(n-2, t))f(n) = f(n, 0)

На expression templates будет выглядеть вот так.

template<unsigned n, unsigned t>auto f(int_<n>, int_<t>) {  if constexpr (n < 2) {    return int_<t+1>{};  } else {    return f(int_<n-1>{}, f(int_<n-2>{}, int_<t>{}));  }}int main() {  std::cout << decltype(f(int_<30>{}, int_<0>{}))::value << std::endl;}

Для 26 clang работал около полуминуты. Для 30 он сожрал больше 8 гигабайт памяти и загнал мой ноутбук в своп, после чего эксперимент пришлось прекратить.

Подробнее..

C20 удивить линкер четыремя строчками кода

09.06.2021 16:12:52 | Автор: admin

Представьте себе, что вы студент, изучающий современные фичи C++. И вам дали задачу по теме concepts/constraints. У преподавателя, конечно, есть референсное решение "как правильно", но для вас оно неочевидно, и вы навертели гору довольно запутанного кода, который всё равно не работает. (И вы дописываете и дописываете всё новые перегрузки и специализации шаблонов, покрывая всё новые и новые претензии компилятора).

А теперь представьте себе, что вы преподаватель, который увидел эту гору, и захотел помочь студенту. Вы стали упрощать и упрощать его код, и даже тупо комментировать куски юнит-тестов, чтобы оно хоть как-то заработало... А оно всё равно не работает. Причём, в зависимости от порядка юнит-тестов, выдаёт разные результаты или вообще не собирается. Где-то спряталось неопределённое поведение. Но какое?

Сперва преподаватель (то есть, я) минимизировал код вот до такого: https://gcc.godbolt.org/z/TaMTWqc1T

// пусть у нас есть концепты указателя и вектораtemplate<class T> concept Ptr = requires(T t) { *t; };template<class T> concept Vec = requires(T t) { t.begin(); t[0]; };// и три перегрузки функций, рекурсивно определённые друг через другаtemplate<class T> void f(T t) {  // (1)  std::cout << "general case " << __PRETTY_FUNCTION__ << std::endl;}template<Ptr T> void f(T t) {  // (2)  std::cout << "pointer to ";  f(*t);  // допустим, указатель не нулевой}template<Vec T> void f(T t) {  // (3)  std::cout << "vector of ";  f(t[0]);  // допустим, вектор не пустой}// и набор тестов (в разных файлах)int main() {  std::vector<int> v = {1};    // тест А  f(v);  // или тест Б  f(&v);  // или тест В  f(&v);  f(v);  // или тест Г  f(v);  f(&v);}

Мы ожидаем, что

  • f(v) выведет "vector of general case void f(T) [T=int]"

  • f(&v) выведет "pointer to vector of general case void f(T) [T=int]"

А вместо это получаем

  • А: "vector of general case void f(T) [T=int]"

  • Б: "pointer of general case void f(T) [T=std::vector<int>]" ?

  • В: clang выводит Б и А ?!, gcc ошибку линкера

  • Г: clang и gcc выводят ошибку линкера

Что здесь не так?!

А не так здесь две вещи. Первая это то, что из функции (2) видны объявления только (1) и (2), поэтому результат разыменования указателя вызывается как (1).

Без концептов и шаблонов это тоже прекрасно воспроизводится: https://gcc.godbolt.org/z/47qhYv6q4

void f(int x)    { std::cout << "int" << std::endl; }void g(char* p)  { std::cout << "char* -> "; f(*p); }  // f(int)void f(char x)   { std::cout << "char" << std::endl; }void g(char** p) { std::cout << "char** -> "; f(**p); }  // f(char)int main() {  char x;  char* p = &x;  f(x);  // char  g(p);  // char* -> int  g(&p); // char** -> char}

В отличие от инлайн-определений функций-членов в классе, где все объявления видны всем, определение свободной функции видит только то, что находится выше по файлу.

Из-за этого, кстати, для взаимно-рекурсивных функций приходится отдельно писать объявления, отдельно (ниже) определения.

Ладно, с этим разобрались. Вернёмся к шаблонам. Почему в тестах В и Г мы получили нечто, похожее на нарушение ODR?

Если мы перепишем код вот так:

template<class T> void f(T t) {.....}template<class T> void f(T t) requires Ptr<T> {.....}template<class T> void f(T t) requires Vec<T> {.....}

то ничего не изменится. Это просто другая форма записи. Требование соответствия концепту можно записать и так, и этак.

Но вот если прибегнем к старому доброму трюку SFINAE, https://gcc.godbolt.org/z/4sar6W6Kq

// добавим второй аргумент char или int - для разрешения неоднозначностиtemplate<class T, class = void> void f(T t, char) {.....}template<class T> auto f(T t, int) -> std::enable_if_t<Ptr<T>, void> {.....}template<class T> auto f(T t, int) -> std::enable_if_t<Vec<T>, void> {.....}..... f(v, 0) .......... f(&v, 0) .....

или ещё более старому доброму сопоставлению типов аргументов, https://gcc.godbolt.org/z/PsdhsG6Wr

template<class T> void f(T t) {.....}template<class T> void f(T* t) {.....}template<class T> void f(std::vector<T> t) {.....}

то всё станет работать. Не так, как нам хотелось бы (рекурсия по-прежнему сломана из-за правил видимости), но ожидаемо (вектор из f(T*) видится как "general case", из main - как "vector").

Что же ещё с концептами/ограничениями?

Коллективный разум, спасибо RSDN, подсказал ещё более минималистичный код!

Всего 4 строки: https://gcc.godbolt.org/z/qM8xYKfqe

template<class T> void f() {}void g() { f<int>(); }template<class T> void f() requires true {}void h() { f<int>(); }

Функция с ограничениями считается более предпочтительной, чем функция без них. Поэтому g() по правилам видимости выбирает из единственного варианта, а h() - из двух выбирает второй.

И вот этот код порождает некорректный объектный файл! В нём две функции с одинаковыми декорированными именами.

Оказывается, современные компиляторы (clang 12.0, gcc 12.0) не умеют учитывать requires в декорировании имён. Как когда-то старый глупый MSVC6 не учитывал параметры шаблона, если те не влияли на тип функции...

И, судя по ответам разработчиков, не только не умеют, но и не хотят. Отмазка: "если в разных точках программы одинаковые обращения к шаблону резолвятся по-разному, такая программа ill-formed, никакой диагностики при этом не нужно" (однако, ill-formed означает "не скомпилируется", а не "скомпилируется как попало"...)

Проблема известна с 2017 года, но прогресса пока нет.

Так что живите с этим. И не забывайте объявлять взаимно-рекурсивные функции до определений. А если увидите странные ошибки линкера, то хотя бы будете понимать, из-за чего они возникают. (А если компилятор будет инлайнить наобум, ну, тогда не повезло).

Подробнее..

Перенос форума IPB в bbPress WordPress

03.10.2020 00:12:20 | Автор: admin

"Invision Power Board" он же "Invision Community", я его назваю IPB.

Форум IPB хороший, функциональный во всех отношениях, даже больше того. Даже больше того - это практически полноценный CMS движок для сайта, взамен WP и подобным.

Но я давно использую для нескольких проектов CMS WP, хотя и люблю когда сайт написан с нуля без лишнего мусора. Примеры моих проектов для сравнения:

  • lagonaki.ru - CMS WP + серьезная букинг тема, дописанная-переписанная, симбиоз CRM с Bitrix24. Постоянно обновляемая + множество плагинов, которые так же обновляются. Обслуживается самостоятельно, нужная информация вносится без привлечения внешних разработчиков.

  • nik38.ru - написанный с нуля сайт, ничего лишнего. Много лет назад создан, чуть-чуть морально устарел, но работает исправно, доволен. Но единственный минус, чтобы что-то изменить, нужно привлекать отдельного программиста.

CMS WP как раз удобен тем, что он замечательно модернизируется, обновляется и поддерживается большинством плагинов. Например, нужен вам интерфейс для интернет коммерции - пожалуйста. Нужен плагин для SEO - пожалуйста. Нужен плагин для оптимизации - пожалуйста - несколько сот вариантов, и т.д. и т.п. В общем, лирика, теперь по делу.

Назрела пора реанимировать замороженный туристическо-альтруистический проект fisht.ru, у которого один из разделов "Форум" forum.fisht.ru, но жил он своей жизнью от основной части сайта. Закрыл форум вынуждено на регистрацию новых пользователей 3 года назад из-за обилия спамеров и отсутствия решения по борьбе со спамом. Предлагалось только обновить движок и заплатить за это 700$ + русификация...

Сейчас, начал изучать как можно объединить проект под WP и перенести форум с IPB платформы.

Наткнулся на cms2cms.com - проект, который конвертирует много форумов в другие форумы. Автоматизация процесса и техподдержка. Интересно, но очень дорого... Тем более автоматизация неважно сработала на первом этапе.

CMS2CMS - сайт для миграции форумов. Но дорогой :)CMS2CMS - сайт для миграции форумов. Но дорогой :)

Свои прогеры по горло загружены, решил поискать исполнителя на "Фрилансер". Нашел толкового парня, но он специалист в IPB, решает вопросы с модернизацией, дизайном, обновлением и т.д. Если нужно - обращайтесь к нему, зовут Олег. Ему огромное спасибо, за то что решил оперативно помочь, но я все же хотел не на IPB остаться, а именно с него "съехать". Много причин, но две основные "Лицензия" дорогая и разделение сайта на форум и сайт. Олег и подсказал, что оказывается есть возможность съехать стандартными средствами bbPress. Вот та самая статья: Invision IPB v3.1x, v3.2x, v3.3x & v3.4x Importer for bbPress. За что ему отдельное спасибо, люблю когда не навязывают свою услугу, а показывают как в действительности обстоят дела.

Установка bbPress и перенос данных из IPB

Работающая версия на 02.10.2020. Если хотите сэкономить себе часы, а может даже и дни свободного времени, то рекомендую воспользоваться работающей связкой версий WP и bbPress:

Долго мне пришлось разбираться, чтобы понять, что последняя версия Wordpress 5.5.1 и предыдущие версии 5.4 не идут с модулем bbPress 2.6.5, который обновлялся 2 месяца назад. В общем, это основная сложность, которая съела уйму времени.

Далее, активируем плагин bbPress, заходим в "Инструменты" - "Форумы" - "Импорт форумов" и выбираем платформу "Invision", далее по вашим настройкам. Там все дальше просто.

Плагин "bbPress" - Инструменты - Форумы - Импорт форумовПлагин "bbPress" - Инструменты - Форумы - Импорт форумов

Если сервер с которого вы будете переносить форум находится на одной площадке, то указываем localhost, если же другой, то, соответственно, тот, с которого переносим.

Карту настроек, которую использовал я, - используйте.Карту настроек, которую использовал я, - используйте.

Если у вас идет вот такая картинка, значит, перенос производится правильно!

Производится перенос форума IPB в bbPressПроизводится перенос форума IPB в bbPress

Если заметили, что все повисло там, где не должно было виснуть - нажмите паузу и потом запустите далее. Сразу в phpMyAdmin в вашей базе увидите, что цифра начала увеличиваться, значит процесс переноса идет.

Для кого вообще эта статья?

Этот статью написал для тех, кто будет искать выход переноса форума IPB на платформу WP. Я выбрал bbPress, т.к. это по сути создатели WP - оригинальная интеграция всегда лучше. Хотя отсутствие обновлений, у меня "съело" очень много времени...

Если используете nginx

Рекомендую сразу внести правки в конфигурационном файле nginx
Требуется установить в location @fallback - для http и для https

    proxy_connect_timeout       600;    proxy_send_timeout          600;    proxy_read_timeout          600;    send_timeout                600;

В противном случае, на определенных операциях настройки форума будет выдаваться ошибка. В частности у меня постоянно выдавалась ошибка, если я в bbPress "Инструменты" - "Форум" - "Восстановление форума" запускал процесс "Пересчет темы для меток тем", то операция уходила и заканчивалась "504 Gateway Time-outnginx/1.14.1".

Также огромное спасибо Никите Максименко и Егору Шалаев из службы тех поддержки TimeWeb, которая поучаствовала в запуске новой жизни проекта.

Буду признателен комментариям от тех, кто сталкивался детально с настройкой bbPress, т.к. один вопрос еще не решил. В частности это ошибочно считает темы и посты, нужно пересчитать, все никак не получается.

Ошибка в количестве тем и сообщений.Ошибка в количестве тем и сообщений.

Что интересно, если создать ФОРУМ, в него потом добавить другой подфорум и потом его вывести из-под него, то в новом созданном форме остается тоже самое количество из подфорума. Пример, я создал форум "Горы", в него перенес "Общие обсуждения", после "Общие обсуждения" вывел из "Горы" итог - равное количество Тем и сообщений. В общем, все, кроме этого, уже нормально и разобрался. Помогите, если кто-то сталкивался с этой проблемой.

Подробнее..

Наследование шаблонов в ванильном PHP за 35 строк кода?

29.05.2021 12:13:17 | Автор: admin

Попал мне как-то под руку проект на WordPress (WP), где понадобилось сделать кастомную тему. В WP шаблоны нативные, что хорошо, - не надо учить дополнительный язык. Но очень захотелось понаследовать шаблоны как в Twig, а PHP из коробки так не умеет.

Осталось решить вопрос наследования. После изучения проблемы, вдохновляться было решено библиотекой phpti, в которой нашлось пару моментов, которые очень захотелось поправить:

  • Автор библиотеки большими буквами написал Every Block is Always Executed!, т. е. все блоки выполняются, даже если переопределены, и никогда не будут выведены.

  • Второй момент - библиотека под капотом использует анализ стека вызовов, что, как мне кажется, чуть-чуть читерство.

  • Третий момент - хочу ещё быстрее. В библиотеке активно используется ob_start на чём и попробуем сэкономить пару спичек.

Библиотека phpti построена вокруг основной конструкции startblock/endblock и наследования при помощи import в начале файла:

// layout.php<!-- разметка --><?php startblock('blockName') ?><?php endblock() ?><!-- разметка -->
// index.php<?php include 'layout.php' ?> <!-- указываем родительский шаблон --><?php startblock('blockName') ?>    <!-- контент блока --><?php endblock() ?>

Некоторые наблюдения:

  • Вызовы вроде start/end можно заменить на анонимную функцию. Это избавит от необходимости сопоставлять вложенные старты и энды, а также сделает код контента ленивым.

  • Родительский шаблон указывается сверху файла. Это значит, что придётся сначала отрабатывать родительский, а потом дочерний шаблон. А значит придётся много чего буферизировать. А почему бы родителя не указать в конце?

  • Можно заметить, что на самом деле есть два разных типа блоков: одни определяются внутри шаблонов куда и выводят результат по месту, а другие по месту определения ничего не выводят, а только переопределяют родительские блоки.

С учётом вышесказанного, переделаем конструкцию на следующую:

// layout.php<!-- разметка --><?php slot('blockName', function(){ ?><?php }) ?><!-- разметка -->
// index.php<?php block('blockName', function(){ ?>    <!-- контент блока --><?php }) ?><?php include 'layout.php' ?> <!-- указываем родительский шаблон -->

Разделив блоки на slot и block, библиотеке больше не нужно пытаться понять, когда нужно выводить результат, а когда нужно только переопределить блок.

Вроде выглядит не слишком монструозно по сравнению с оригинальной структурой. Посмотрим, покроет ли такой подход все хотелки.

root.php - базовый шаблон, внизу иерархии:

<!DOCTYPE html><html>  <head>    <title><?php slot('title') /* слот - место в шаблоне */ ?></title>  </head>  <body>    <div id="root">      <?php slot('body', function () { /* слот с контентом по дефолту */?>        <p>'body' :: root.php</p>      <?php }) ?>    </div>  </body></html>

two-columns.php - промежуточный шаблон:

<?php block('title', function () { /* блок - контент для вставки в слот */?>  Title :: two-columns.php<?php });block('body', function () { ?>  <div id="two-columnts">    <div id="main">      <?php slot('main', function () { /* слот внутри блока */?>    <p>'main' :: two-columns.php</p>  <?php }) ?></div><div id="side">  <?php slot('side', function () { ?><p>'side' :: two-columns.php</p>  <?php }) ?></div>  </div>  <div id="footer">    <?php slot('footer', function () { ?>  <p>'footer' :: two-columns.php</p><?php }) ?>  </div><?php });include './root.php'; // наследуем от root.php

index.php - страница сайта, верхний шаблон:

<?phprequire_once '../src/InheritTpl.php'; block('title', function () { ?> 'title' :: index.php <?php });block('side', function () { ?>  <p>'side' :: index.php</p><?php }); block('main', function () { ?>  <div id="main-index"> <!-- Обернём содержимое от родителя -->    <?php super() /* тут выводим контент из родительского блока */?>  </div><?php });block('main', function () { /* Ещё раз тот же блок, почему бы и нет? */?>  <div id="main-index"> <!-- И ещё раз обернём содержимое -->    <?php super() ?>  </div><?php });// А 'footer' пусть остаётся как былinclude './two-columns.php';

Результат рендеринга (отформатирован для читабельности):

<!DOCTYPE html><html>  <head>    <title> 'title' :: index.php </title>  </head>  <body>    <div id="root">      <div id="two-columnts">        <div id="main">          <div id="main-index"> <!-- Обернём содержимое от родителя -->            <div id="main-index"> <!-- И ещё раз обернём содержимое -->              <p>'main' :: two-columns.php</p>            </div>          </div>        </div>        <div id="side">          <p>'side' :: index.php</p>        </div>      </div>      <div id="footer">        <p>'footer' :: two-columns.php</p>      </div>    </div>  </body></html>

Хотелки наследования удовлетворены. Но вот интересно, удалось ли сделать эту штуку быстрее?

Перепишем пример выше под библиотеку phpti. Дадим ей небольшую фору, т.к. в примере нет тяжеловесных переопределяемых блоков.

Сравнивать будем время 10,000 рендеров на PHP 8.0.2 и процессоре 3.6ГГц.

  • phpti: 0.831 секунд

  • сабж: 0.353 секунд

В качестве вывода можно сказать что размер библиотеки удалось сократить в 10 раз, при этом скорость работы механизма наследования увеличилась как минимум в 2 раза.

Посмотреть исходный код можно тут.

Подробнее..
Категории: Php , Template , Inheritance , Native , Plain

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru